Files
Githug-Android/app/src/main/java/solutions/tretter/githugandroid/levels/AdvancedLevels.kt
Joe Tretter 16d7809150 Auto-commit after successful build: update app gameplay/UI, improve build setup
Changed files:\napp/build.gradle.kts
app/src/main/java/solutions/tretter/githugandroid/GameModels.kt
app/src/main/java/solutions/tretter/githugandroid/GitHelpCommands.kt
app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt
app/src/main/java/solutions/tretter/githugandroid/GitRuntime.kt
app/src/main/java/solutions/tretter/githugandroid/Visual.kt
app/src/main/java/solutions/tretter/githugandroid/levels/AdvancedLevels.kt
app/src/main/java/solutions/tretter/githugandroid/levels/LevelCatalog.kt
app/src/test/java/solutions/tretter/githugandroid/GitHelpCommandsTest.kt
app/src/test/java/solutions/tretter/githugandroid/GitSandboxEngineTest.kt
2026-05-05 19:27:33 -05:00

54 lines
23 KiB
Kotlin

package solutions.tretter.githugandroid
internal fun advancedLevels(): List<Level> = listOf(
level("clone", description = "Clone the repository at https://github.com/Gazler/cloneme.", hints = listOf("You should have a look at this site: https://github.com/Gazler/cloneme."), setup = { RepoState() }, validator = commandAnswer("git clone https://github.com/Gazler/cloneme")),
level("clone_to_folder", description = "Clone the repository at https://github.com/Gazler/cloneme into the folder `my_cloned_repo`.", hints = listOf("This is like the last level, `git clone` has an optional argument."), setup = { RepoState() }, validator = commandAnswer("git clone https://github.com/Gazler/cloneme my_cloned_repo")),
level("ignore", description = "The text editor 'vim' creates files ending in `.swp` (swap files) for all files that are currently open. We don't want them creeping into the repository. Make this repository ignore those swap files which are ending in `.swp`.", hints = listOf("You may have noticed there is a file named `.gitignore` in the repository."), setup = { RepoState(initialized = true, files = listOf(GitFile(".gitignore", tracked = true)), branches = mapOf("master" to 0)) }, validator = repoPredicate { it.files.find { f -> f.name == ".gitignore" }?.content?.contains("*.swp") == true }),
level("include", description = "Notice a few files with the '.a' extension. We want git to ignore all the files except the 'lib.a' file.", hints = listOf("Using `git help ignore`, read about the optional prefix to negate a pattern."), setup = { RepoState(initialized = true, files = listOf(GitFile(".gitignore", tracked = true), GitFile("lib.a"), GitFile("main.a")), branches = mapOf("master" to 0)) }, validator = repoPredicate { it.files.find { f -> f.name == ".gitignore" }?.content?.let { c -> "*.a" in c && "!lib.a" in c } == true }),
level("status", description = "Among the files in this repository, which of them is untracked? (Enter the file name on the prompt!)", hints = listOf("You are looking for a command to identify the status of the repository."), setup = { RepoState(initialized = true, files = listOf(GitFile("database.yml"), GitFile("README", tracked = true)), branches = mapOf("master" to 0)) }, validator = commandAnswer("database.yml")),
level("number_of_files_committed", description = "There are some files in this repository; how many of them are staged for a commit?", hints = listOf("You are looking for a command to identify the status of the repository (resembles a Linux command)."), setup = { RepoState(initialized = true, files = listOf(GitFile("rubyfile1.rb", staged = true), GitFile("rubyfile4.rb", staged = true, tracked = true), GitFile("rubyfile5.rb", tracked = true), GitFile("rubyfile6.rb"), GitFile("rubyfile7.rb")), branches = mapOf("master" to 1)) }, validator = commandAnswer("2")),
level("rm", description = "A file has been removed from the working tree, but not from the repository. Identify this file and remove it.", hints = emptyList(), setup = { RepoState(initialized = true, files = listOf(GitFile("deleteme.rb", tracked = true, deleted = true)), commits = listOf(CommitNode("0000001", "Added a temp file")), branches = mapOf("master" to 1)) }, validator = { _, command -> command.contains("deleteme.rb") }),
level("rm_cached", description = "A file has accidentally been added to your staging area. Identify and remove it from the staging area. *NOTE* Do not remove the file from the file system, only from git.", hints = listOf("You may need to use more than one command to complete this."), setup = { RepoState(initialized = true, files = listOf(GitFile("deleteme.rb", staged = true), GitFile(".gitignore", staged = true)), branches = mapOf("master" to 0)) }, validator = { repo, _ -> repo.files.any { it.name == "deleteme.rb" && !it.staged } }),
level("stash", description = "You've made some changes and want to work on them later. You should save them, but don't commit them.", hints = listOf("It's like stashing. Try finding an appropriate git command."), setup = { RepoState(initialized = true, files = listOf(GitFile("lyrics.txt", tracked = true)), branches = mapOf("master" to 1)) }, validator = { _, command -> command.startsWith("git stash") }),
level("rename", description = "We have a file called `oldfile.txt`. We want to rename it to `newfile.txt` and stage this change.", hints = listOf("Take a look at `git mv`."), setup = { RepoState(initialized = true, files = listOf(GitFile("oldfile.txt", tracked = true)), commits = listOf(CommitNode("0000001", "Commited oldfile.txt")), branches = mapOf("master" to 1)) }, validator = { repo, _ -> repo.files.any { it.name == "newfile.txt" } }),
level("restructure", description = "You added some files to your repository, but now realize that your project needs to be restructured. Make a new folder named `src` and use Git move all of the .html files into this folder.", hints = listOf("You'll have to use mkdir, and `git mv`."), setup = { RepoState(initialized = true, files = listOf(GitFile("about.html", tracked = true), GitFile("contact.html", tracked = true), GitFile("index.html", tracked = true)), commits = listOf(CommitNode("0000001", "adding web content.")), branches = mapOf("master" to 1)) }, validator = repoPredicate { listOf("src/about.html", "src/contact.html", "src/index.html").all { target -> it.files.any { f -> f.name == target } } }),
level("log", description = "Identify the hash of the latest commit.", hints = listOf("You need to investigate the logs."), setup = { RepoState(initialized = true, commits = listOf(CommitNode("0000001", "THIS IS THE COMMIT YOU ARE LOOKING FOR!")), branches = mapOf("master" to 1)) }, validator = commitHashAnswer("0000001")),
level("push_tags", description = "A tag in the local repository isn't pushed into remote repository. Push it now.", hints = listOf("Take a look at `--tags` flag of `git push`"), setup = { RepoState(initialized = true, tags = listOf("tag_to_be_pushed"), branches = mapOf("master" to 2), remotes = mapOf("origin" to "remote")) }, validator = { _, command -> command.contains("push") && command.contains("--tags") }),
level("commit_amend", description = "The `README` file has been committed, but it looks like the file `forgotten_file.rb` was missing from the commit. Add the file and amend your previous commit to include it.", hints = listOf("Running `git commit --help` will display the man page and possible flags."), setup = { RepoState(initialized = true, files = listOf(GitFile("README", tracked = true), GitFile("forgotten_file.rb")), commits = listOf(CommitNode("0000001", "Initial commit")), branches = mapOf("master" to 1)) }, validator = { repo, command -> repo.files.any { it.name == "forgotten_file.rb" && it.tracked } && "--amend" in command }),
level("commit_in_future", description = "Commit your changes with the future date (e.g. tomorrow).", hints = listOf("Build a time format, and commit your code using --date parameter."), setup = { RepoState(initialized = true, files = listOf(GitFile("README", staged = true)), branches = mapOf("master" to 0)) }, validator = { repo, command -> repo.commits.isNotEmpty() && "--date" in command }),
level("reset", description = "There are two files to be committed. The goal was to add each file as a separate commit, however both were added by accident. Unstage the file `to_commit_second.rb` using the reset command (don't commit anything).", hints = listOf("git status will tell you the command you need to run."), setup = { RepoState(initialized = true, files = listOf(GitFile("to_commit_first.rb", staged = true), GitFile("to_commit_second.rb", staged = true), GitFile("README", tracked = true)), commits = listOf(CommitNode("0000001", "Initial commit")), branches = mapOf("master" to 1)) }, validator = { repo, _ -> repo.files.any { it.name == "to_commit_first.rb" && it.staged } && repo.files.any { it.name == "to_commit_second.rb" && !it.staged } }),
level("reset_soft", description = "You committed too soon. Now you want to undo the last commit, while keeping the index.", hints = listOf("What are some options you can use with `git reset`?"), setup = { RepoState(initialized = true, files = listOf(GitFile("README", tracked = true), GitFile("newfile.rb", tracked = true)), commits = listOf(CommitNode("0000001", "Initial commit"), CommitNode("0000002", "Premature commit")), branches = mapOf("master" to 2)) }, validator = { repo, command -> repo.commits.size <= 1 && repo.files.any { it.name == "newfile.rb" && it.staged } && command.contains("--soft") }),
level("checkout_file", description = "A file has been modified, but you don't want to keep the modification. Checkout the `config.rb` file from the last commit.", hints = listOf("You will need to do some research on the checkout command for this one."), setup = { RepoState(initialized = true, files = listOf(GitFile("config.rb", "This is the initial config file\nThese are changes you don't want to keep!", tracked = true)), commits = listOf(CommitNode("0000001", "Added initial config file")), branches = mapOf("master" to 1)) }, validator = { repo, _ -> repo.files.find { it.name == "config.rb" }?.content?.contains("don't want to keep") == false }),
level("remote", description = "This project has a remote repository. Identify it.", hints = listOf("You are looking for a remote. You can run `git` for a list of commands."), setup = { RepoState(initialized = true, remotes = mapOf("my_remote_repo" to "https://github.com/Gazler/githug"), branches = mapOf("master" to 0)) }, validator = commandAnswer("my_remote_repo")),
level("remote_url", description = "The remote repositories have a url associated to them. Please enter the url of remote_location.", hints = listOf("You can run `git remote --help` for the man pages."), setup = { RepoState(initialized = true, remotes = mapOf("my_remote_repo" to "https://github.com/Gazler/githug", "remote_location" to "https://github.com/githug/not_a_repo"), branches = mapOf("master" to 0)) }, validator = commandAnswer("https://github.com/githug/not_a_repo")),
level("pull", description = "You need to pull changes from your origin repository.", hints = listOf("Check out the remote repositories and research `git pull`."), setup = { RepoState(initialized = true, remotes = mapOf("origin" to "https://github.com/pull-this/thing-to-pull"), branches = mapOf("master" to 1)) }, validator = { _, command -> command.startsWith("git pull") }),
level("remote_add", description = "Add a remote repository called `origin` with the url https://github.com/githug/githug", hints = listOf("You can run `git remote --help` for the man pages."), setup = { RepoState(initialized = true, branches = mapOf("master" to 0)) }, validator = repoPredicate { it.remotes["origin"] == "https://github.com/githug/githug" }),
level("push", description = "Your local master branch has diverged from the remote origin/master branch. Rebase your branch onto origin/master and push it to remote.", hints = listOf("Take a look at `git fetch`, `git pull`, and `git push`."), setup = { RepoState(initialized = true, remotes = mapOf("origin" to "remote"), branches = mapOf("master" to 3)) }, validator = { _, command -> command.startsWith("git push") }),
level("diff", description = "Since your last commit, file `app.rb` was modified. Find out which line has changed.", hints = listOf("You are looking for the difference since your last commit."), setup = { RepoState(initialized = true, files = listOf(GitFile("app.rb", tracked = true)), branches = mapOf("master" to 1)) }, validator = commandAnswer("26")),
level("blame", description = "Identify who put a password inside the file `config.rb`.", hints = listOf("You want to research the `git blame` command."), setup = { RepoState(initialized = true, files = listOf(GitFile("config.rb", tracked = true)), branches = mapOf("master" to 1)) }, validator = { _, command -> command.isNotBlank() && !command.startsWith("git") }),
level("checkout_tag", description = "You need to fix a bug in the version 1.2 of your app. Checkout the tag `v1.2`.", hints = listOf("There's no big difference between checking out a branch and checking out a tag."), setup = { RepoState(initialized = true, commits = listOf(CommitNode("1", "Initial commit"), CommitNode("2", "Some changes"), CommitNode("3", "Some more changes"), CommitNode("4", "Yet more changes"), CommitNode("5", "Changes galore")), tags = listOf("v1.0", "v1.2", "v1.5"), branches = mapOf("master" to 5)) }, validator = { _, command -> command.contains("checkout") && command.contains("v1.2") }),
level("checkout_tag_over_branch", description = "You need to fix a bug in the version 1.2 of your app. Checkout the tag `v1.2` (Note: There is also a branch named `v1.2`).", hints = listOf("You should think about specifying you're after the tag named `v1.2` (think `tags/`)."), setup = { RepoState(initialized = true, tags = listOf("v1.0", "v1.2", "v1.5"), branches = mapOf("master" to 5, "v1.2" to 6)) }, validator = { _, command -> "tags/v1.2" in command || command.trim() == "git checkout refs/tags/v1.2" }),
level("branch_at", description = "You forgot to branch at the previous commit and made a commit on top of it. Create the branch test_branch at the commit before the last.", hints = listOf("Just like creating a branch, but you have to pass an extra argument."), setup = { RepoState(initialized = true, commits = listOf(CommitNode("1", "Adding file1"), CommitNode("2", "Updating file1"), CommitNode("3", "Updating file1 again")), branches = mapOf("master" to 3)) }, validator = repoPredicate { "test_branch" in it.branches }),
level("delete_branch", description = "You have created too many branches for your project. There is an old branch in your repo called 'delete_me', you should delete it.", hints = listOf("Running 'git --help branch' will give you a list of branch commands."), setup = { RepoState(initialized = true, branches = mapOf("master" to 1, "delete_me" to 1)) }, validator = repoPredicate { "delete_me" !in it.branches }),
level("push_branch", description = "You've made some changes to a local branch and want to share it, but aren't yet ready to merge it with the 'master' branch. Push only 'test_branch' to the remote repository", hints = listOf("Investigate the options in `git push` using `git push --help`"), setup = { RepoState(initialized = true, branches = mapOf("master" to 2, "other_branch" to 3, "test_branch" to 4), remotes = mapOf("origin" to "remote")) }, validator = { _, command -> command.startsWith("git push") && command.contains("test_branch") }),
level("merge", description = "We have a file in the branch 'feature'. Let's merge it with the master branch.", hints = listOf("You want to research the `git merge` command."), setup = { RepoState(initialized = true, files = listOf(GitFile("file1", tracked = true)), branches = mapOf("master" to 1, "feature" to 2)) }, validator = repoPredicate { it.files.any { f -> f.name == "file2" } }),
level("fetch", description = "Looks like a new branch was pushed into our remote repository. Get the changes without merging them with the local repository", hints = listOf("Look up the 'git fetch' command"), setup = { RepoState(initialized = true, branches = mapOf("master" to 1), remotes = mapOf("origin" to "remote")) }, validator = { _, command -> command.startsWith("git fetch") }),
level("rebase", description = "We are using a git rebase workflow and the feature branch is ready to go into master. Let's rebase the feature branch onto our master branch.", hints = listOf("You want to research the `git rebase` command"), setup = { RepoState(initialized = true, branches = mapOf("master" to 2, "feature" to 3)) }, validator = { _, command -> command.startsWith("git rebase") }),
level("rebase_onto", description = "You have created your branch from `wrong_branch` and already made some commits, and you realise that you needed to create your branch from `master`. Rebase your commits onto `master` branch so that you don't have `wrong_branch` commits.", hints = listOf("You want to research the `git rebase` commands `--onto` argument"), setup = { RepoState(initialized = true, branches = mapOf("master" to 1, "wrong_branch" to 2, "readme-update" to 4)) }, validator = { _, command -> command.startsWith("git rebase") && command.contains("--onto") }),
level("repack", description = "Optimise how your repository is packaged ensuring that redundant packs are removed.", hints = listOf("You want to research the `git repack` command."), setup = { RepoState(initialized = true, files = listOf(GitFile("foo", tracked = true)), commits = listOf(CommitNode("1", "Added foo")), branches = mapOf("master" to 1)) }, validator = { _, command -> command.startsWith("git repack") }),
level("cherry-pick", description = "Your new feature isn't worth the time and you're going to delete it. But it has one commit that fills in `README` file, and you want this commit to be on the master as well.", hints = listOf("Sneak a peek at the `git help cherry-pick` command."), setup = { RepoState(initialized = true, branches = mapOf("master" to 2, "feature" to 3)) }, validator = { _, command -> command.startsWith("git cherry-pick") }),
level("grep", description = "Your project's deadline approaches, you should evaluate how many TODOs are left in your code", hints = listOf("You want to research the `git grep` command."), setup = { RepoState(initialized = true, files = listOf(GitFile("app.rb", "# TODO\n# TODO\n# TODO\n# TODO", tracked = true)), branches = mapOf("master" to 1)) }, validator = commandAnswer("4")),
level("rename_commit", description = "Correct the typo in the message of your first (non-root) commit.", hints = listOf("Take a look the `-i` flag of the rebase command."), setup = { RepoState(initialized = true, commits = listOf(CommitNode("1", "Initial commit"), CommitNode("2", "First coommit"), CommitNode("3", "Second commit")), branches = mapOf("master" to 3)) }, validator = { _, command -> command.startsWith("git rebase -i") || command.contains("First commit") }),
level("squash", description = "You have committed several times but would like all those changes to be one commit.", hints = listOf("Take a look at the `-i` flag of the rebase command."), setup = { RepoState(initialized = true, commits = listOf(CommitNode("1", "Initial Commit"), CommitNode("2", "Adding README"), CommitNode("3", "Updating README (squash this commit into Adding README)"), CommitNode("4", "Updating README (squash this commit into Adding README)"), CommitNode("5", "Updating README (squash this commit into Adding README)")), branches = mapOf("master" to 5)) }, validator = repoPredicate { it.commits.size <= 2 }),
level("merge_squash", description = "Merge all commits from the long-feature-branch as a single commit.", hints = listOf("Take a look at the `--squash` option of the merge command. Don't forget to commit the merge!"), setup = { RepoState(initialized = true, branches = mapOf("master" to 2, "long-feature-branch" to 4)) }, validator = { _, command -> command.contains("merge") && command.contains("--squash") }),
level("reorder", description = "You have committed several times but in the wrong order. Please reorder your commits.", hints = listOf("Take a look the `-i` flag of the rebase command."), setup = { RepoState(initialized = true, commits = listOf(CommitNode("1", "Initial Setup"), CommitNode("2", "First commit"), CommitNode("3", "Third commit"), CommitNode("4", "Second commit")), branches = mapOf("master" to 4)) }, validator = { _, command -> command.startsWith("git rebase -i") }),
level("bisect", description = "A bug was introduced somewhere along the way. You know that running `ruby prog.rb 5` should output 15. You can also run `make test`. What are the first 7 chars of the hash of the commit (the abbreviated hash) that introduced the bug?", hints = emptyList(), setup = { RepoState(initialized = true, branches = mapOf("master" to 7)) }, validator = commandAnswer("18ed2ac")),
level("stage_lines", description = "You've made changes within a single file that belong to two different features, but neither of the changes are yet staged. Stage only the changes belonging to the first feature.", hints = listOf("Read about the flags which can be passed to the `add` command."), setup = { RepoState(initialized = true, files = listOf(GitFile("feature.rb", "this is the class of my feature\nThis change belongs to the first feature\nThis change belongs to the second feature", tracked = true)), branches = mapOf("master" to 1)) }, validator = { _, command -> command.startsWith("git add") && ("-p" in command || "--patch" in command) }),
level("find_old_branch", description = "You have been working on a branch but got distracted by a major issue. Switch back to that branch even though you forgot the name of it.", hints = listOf("Ever played with the `git reflog` command?"), setup = { RepoState(initialized = true, branches = mapOf("master" to 1, "solve_world_hunger" to 2)) }, validator = repoPredicate { it.headBranch == "solve_world_hunger" }),
level("revert", description = "You have committed several times but want to undo the middle commit. All commits have been pushed, so you can't change existing history.", hints = listOf("Try the revert command."), setup = { RepoState(initialized = true, commits = listOf(CommitNode("1", "First commit"), CommitNode("2", "Bad commit"), CommitNode("3", "Second commit")), branches = mapOf("master" to 3)) }, validator = { repo, command -> repo.commits.any { it.message.contains("Revert") } || command.startsWith("git revert") }),
level("restore", description = "You decided to delete your latest commit by running `git reset --hard HEAD^` (not a smart thing to do). Now you changed your mind and want that commit back. Restore the deleted commit.", hints = listOf("The commit is still floating around somewhere. Have you checked out `git reflog`?"), setup = { RepoState(initialized = true, files = listOf(GitFile("file1", tracked = true), GitFile("file2", tracked = true)), commits = listOf(CommitNode("1", "Initial commit"), CommitNode("2", "First commit")), branches = mapOf("master" to 2)) }, validator = repoPredicate { it.files.any { f -> f.name == "file3" } }),
level("conflict", description = "You need to merge mybranch into the current branch (master). But there may be some incorrect changes in mybranch which may cause conflicts. Solve any merge-conflicts you come across and finish the merge.", hints = emptyList(), setup = { RepoState(initialized = true, files = listOf(GitFile("poem.txt", tracked = true)), branches = mapOf("master" to 2, "mybranch" to 2)) }, validator = { _, command -> command.startsWith("git merge") || command.startsWith("git commit") }),
level("submodule", description = "You want to include the files from the following repo: `https://github.com/jackmaney/githug-include-me` into the folder `./githug-include-me`. Do this without manually cloning the repo or copying the files from the repo into this repo.", hints = listOf("Take a look at `git submodule`."), setup = { RepoState(initialized = true, branches = mapOf("master" to 0)) }, validator = { _, command -> command.startsWith("git submodule add") }),
level("contribute", description = "This is the final level, the goal is to contribute to this repository by making a pull request on GitHub. Please note that this level is designed to encourage you to add a valid contribution to Githug, not testing your ability to create a pull request. Contributions that are likely to be accepted are levels, bug fixes and improved documentation.", hints = listOf("Forking the repository would be a good start!"), setup = { RepoState() }, validator = { _, command -> command.isNotBlank() }),
)