Improve upstream parity for level fixtures
- Add native per-level fixtures for complex histories, tags, branch graphs, rebases, resets, restores, stashes, and local remotes. - Strengthen repository inspection so branch commit counts, remote-tracking branches, and detached tag checkouts reflect real Git state. - Align Fetch, Pull, Push Branch, Branch At, and Rebase validators/tests with upstream-style repository state. - Keep the prompt callout and native filesystem tab-completion fixes from the previous batch.
This commit is contained in:
@@ -0,0 +1,389 @@
|
||||
package solutions.tretter.githugandroid
|
||||
|
||||
import java.io.File
|
||||
|
||||
internal fun materializeNativeLevelFixture(
|
||||
levelId: String,
|
||||
sandbox: File,
|
||||
runGit: (File, List<String>) -> Int,
|
||||
): Boolean {
|
||||
val fixture = NativeLevelFixture(sandbox, runGit)
|
||||
return when (levelId) {
|
||||
"branch_at" -> fixture.branchAt()
|
||||
"checkout_tag" -> fixture.checkoutTag()
|
||||
"checkout_tag_over_branch" -> fixture.checkoutTagOverBranch()
|
||||
"fetch" -> fixture.fetch()
|
||||
"pull" -> fixture.pull()
|
||||
"push_branch" -> fixture.pushBranch()
|
||||
"push_tags" -> fixture.pushTags()
|
||||
"merge" -> fixture.merge()
|
||||
"rebase" -> fixture.rebase()
|
||||
"rebase_onto" -> fixture.rebaseOnto()
|
||||
"merge_squash" -> fixture.mergeSquash()
|
||||
"reset" -> fixture.reset()
|
||||
"reset_soft" -> fixture.resetSoft()
|
||||
"restore" -> fixture.restore()
|
||||
"squash" -> fixture.squash()
|
||||
"reorder" -> fixture.reorder()
|
||||
"rename_commit" -> fixture.renameCommit()
|
||||
"revert" -> fixture.revert()
|
||||
"stash" -> fixture.stash()
|
||||
"checkout_file" -> fixture.checkoutFile()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private class NativeLevelFixture(
|
||||
private val sandbox: File,
|
||||
private val runGit: (File, List<String>) -> Int,
|
||||
) {
|
||||
private fun resetFiles() {
|
||||
sandbox.listFiles()
|
||||
?.filterNot { it.name == ".git" }
|
||||
?.forEach { it.deleteRecursively() }
|
||||
git("checkout", "-B", "master")
|
||||
}
|
||||
|
||||
private fun git(vararg arguments: String): Int = runGit(sandbox, arguments.toList())
|
||||
|
||||
private fun git(directory: File, vararg arguments: String): Int = runGit(directory, arguments.toList())
|
||||
|
||||
private fun initRepo(directory: File) {
|
||||
directory.mkdirs()
|
||||
val initResult = git(directory, "init", "-b", "master")
|
||||
if (initResult != 0) {
|
||||
git(directory, "init")
|
||||
git(directory, "checkout", "-B", "master")
|
||||
}
|
||||
git(directory, "config", "receive.denyCurrentBranch", "ignore")
|
||||
}
|
||||
|
||||
private fun write(path: String, content: String = "") {
|
||||
File(sandbox, path).apply {
|
||||
parentFile?.mkdirs()
|
||||
writeText(content)
|
||||
}
|
||||
}
|
||||
|
||||
private fun append(path: String, content: String) {
|
||||
File(sandbox, path).appendText(content)
|
||||
}
|
||||
|
||||
private fun add(vararg paths: String) {
|
||||
git("add", *paths)
|
||||
}
|
||||
|
||||
private fun commit(message: String) {
|
||||
git("commit", "-m", message)
|
||||
}
|
||||
|
||||
private fun addCommit(message: String, vararg paths: String) {
|
||||
add(*paths)
|
||||
commit(message)
|
||||
}
|
||||
|
||||
private fun writeIn(directory: File, path: String, content: String = "") {
|
||||
File(directory, path).apply {
|
||||
parentFile?.mkdirs()
|
||||
writeText(content)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addCommitIn(directory: File, message: String, vararg paths: String) {
|
||||
git(directory, "add", *paths)
|
||||
git(directory, "commit", "-m", message)
|
||||
}
|
||||
|
||||
private fun siblingRepo(name: String): File {
|
||||
val directory = File(sandbox.parentFile ?: sandbox, "${sandbox.name}-$name")
|
||||
directory.deleteRecursively()
|
||||
initRepo(directory)
|
||||
return directory
|
||||
}
|
||||
|
||||
private fun checkoutNew(branch: String) {
|
||||
git("checkout", "-b", branch)
|
||||
}
|
||||
|
||||
private fun checkout(branch: String) {
|
||||
git("checkout", branch)
|
||||
}
|
||||
|
||||
private fun tag(name: String) {
|
||||
git("tag", "-f", name)
|
||||
}
|
||||
|
||||
fun branchAt(): Boolean {
|
||||
resetFiles()
|
||||
write("file1")
|
||||
addCommit("Adding file1", "file1")
|
||||
write("file1", "content")
|
||||
addCommit("Updating file1", "file1")
|
||||
append("file1", "\nAdding some more text")
|
||||
addCommit("Updating file1 again", "file1")
|
||||
return true
|
||||
}
|
||||
|
||||
fun checkoutFile(): Boolean {
|
||||
resetFiles()
|
||||
write("config.rb", "This is the initial config file")
|
||||
addCommit("Added initial config file", "config.rb")
|
||||
append("config.rb", "\nThese are changes you don't want to keep!")
|
||||
return true
|
||||
}
|
||||
|
||||
fun checkoutTag(): Boolean {
|
||||
resetFiles()
|
||||
write("app.rb")
|
||||
addCommit("Initial commit", "app.rb")
|
||||
append("app.rb", "some changes\n")
|
||||
addCommit("Some changes", "app.rb")
|
||||
tag("v1.0")
|
||||
append("app.rb", "some more changes\n")
|
||||
addCommit("Some more changes", "app.rb")
|
||||
tag("v1.2")
|
||||
append("app.rb", "yet more changes\n")
|
||||
addCommit("Yet more changes", "app.rb")
|
||||
append("app.rb", "changes galore\n")
|
||||
addCommit("Changes galore", "app.rb")
|
||||
tag("v1.5")
|
||||
return true
|
||||
}
|
||||
|
||||
fun checkoutTagOverBranch(): Boolean {
|
||||
checkoutTag()
|
||||
checkoutNew("v1.2")
|
||||
write("file3", "some feature\n")
|
||||
addCommit("Developing new features", "file3")
|
||||
checkout("master")
|
||||
return true
|
||||
}
|
||||
|
||||
fun fetch(): Boolean {
|
||||
resetFiles()
|
||||
write("master_file")
|
||||
addCommit("Commits master_file", "master_file")
|
||||
val remote = siblingRepo("origin")
|
||||
git("remote", "add", "fetch-setup-origin", File(remote, ".git").absolutePath)
|
||||
git("push", "fetch-setup-origin", "master")
|
||||
git("remote", "remove", "fetch-setup-origin")
|
||||
git(remote, "checkout", "-f", "master")
|
||||
git(remote, "checkout", "-b", "new_branch")
|
||||
writeIn(remote, "file1")
|
||||
addCommitIn(remote, "Commits file 1", "file1")
|
||||
git("remote", "add", "origin", File(remote, ".git").absolutePath)
|
||||
git("branch", "--set-upstream-to=origin/master", "master")
|
||||
return true
|
||||
}
|
||||
|
||||
fun pull(): Boolean {
|
||||
resetFiles()
|
||||
write("local_file")
|
||||
addCommit("Initial local commit", "local_file")
|
||||
val remote = siblingRepo("origin")
|
||||
git("remote", "add", "pull-setup-origin", File(remote, ".git").absolutePath)
|
||||
git("push", "pull-setup-origin", "master")
|
||||
git("remote", "remove", "pull-setup-origin")
|
||||
git(remote, "checkout", "-f", "master")
|
||||
writeIn(remote, "remote_file", "pulled from origin\n")
|
||||
addCommitIn(remote, "Pulled commit", "remote_file")
|
||||
git("remote", "add", "origin", File(remote, ".git").absolutePath)
|
||||
git("config", "branch.master.remote", "origin")
|
||||
git("config", "branch.master.merge", "refs/heads/master")
|
||||
return true
|
||||
}
|
||||
|
||||
fun pushBranch(): Boolean {
|
||||
resetFiles()
|
||||
write("file1")
|
||||
addCommit("committed changes on master", "file1")
|
||||
val remote = siblingRepo("origin")
|
||||
git("remote", "add", "push-branch-setup-origin", File(remote, ".git").absolutePath)
|
||||
git("push", "push-branch-setup-origin", "master")
|
||||
git("remote", "remove", "push-branch-setup-origin")
|
||||
git(remote, "checkout", "-f", "master")
|
||||
write("file2")
|
||||
addCommit("If this commit gets pushed to repo, then you have lost the level :( ", "file2")
|
||||
checkoutNew("other_branch")
|
||||
write("file3")
|
||||
addCommit("If this commit gets pushed to repo, then you have lost the level :( ", "file3")
|
||||
checkoutNew("test_branch")
|
||||
write("file4")
|
||||
addCommit("committed change on test_branch", "file4")
|
||||
git("remote", "add", "origin", File(remote, ".git").absolutePath)
|
||||
git("branch", "--set-upstream-to=origin/master", "master")
|
||||
checkout("master")
|
||||
return true
|
||||
}
|
||||
|
||||
fun pushTags(): Boolean {
|
||||
resetFiles()
|
||||
write("file1")
|
||||
addCommit("First commit", "file1")
|
||||
tag("tag_to_be_pushed")
|
||||
write("file2")
|
||||
addCommit("Second commit", "file2")
|
||||
val remote = siblingRepo("origin")
|
||||
git("remote", "add", "push-tags-setup-origin", File(remote, ".git").absolutePath)
|
||||
git("push", "push-tags-setup-origin", "master")
|
||||
git("remote", "remove", "push-tags-setup-origin")
|
||||
git(remote, "checkout", "-f", "master")
|
||||
git("remote", "add", "origin", File(remote, ".git").absolutePath)
|
||||
return true
|
||||
}
|
||||
|
||||
fun merge(): Boolean {
|
||||
resetFiles()
|
||||
write("file1")
|
||||
addCommit("added file1", "file1")
|
||||
checkoutNew("feature")
|
||||
write("file2")
|
||||
addCommit("added file2", "file2")
|
||||
checkout("master")
|
||||
return true
|
||||
}
|
||||
|
||||
fun rebase(): Boolean {
|
||||
resetFiles()
|
||||
write("README", "readme\n")
|
||||
addCommit("init commit", "README")
|
||||
checkoutNew("feature")
|
||||
write("feature", "feature\n")
|
||||
addCommit("add feature", "feature")
|
||||
checkout("master")
|
||||
append("README", "content\n")
|
||||
addCommit("add content", "README")
|
||||
return true
|
||||
}
|
||||
|
||||
fun rebaseOnto(): Boolean {
|
||||
resetFiles()
|
||||
write("authors.md", "https://github.com/janis-vitols\n")
|
||||
addCommit("Create authors file", "authors.md")
|
||||
checkoutNew("wrong_branch")
|
||||
write("authors.md", "None\n")
|
||||
addCommit("Wrong changes", "authors.md")
|
||||
checkoutNew("readme-update")
|
||||
write("README.md", "# SuperApp\n")
|
||||
addCommit("Add app name in readme", "README.md")
|
||||
append("README.md", "## About\n")
|
||||
addCommit("Add `About` header in readme", "README.md")
|
||||
append("README.md", "## Install\n")
|
||||
addCommit("Add `Install` header in readme", "README.md")
|
||||
return true
|
||||
}
|
||||
|
||||
fun mergeSquash(): Boolean {
|
||||
resetFiles()
|
||||
write("file1")
|
||||
addCommit("First commit", "file1")
|
||||
checkoutNew("long-feature-branch")
|
||||
write("file3", "some feature\n")
|
||||
addCommit("Developing new features", "file3")
|
||||
append("file3", "getting awesomer\n")
|
||||
addCommit("Takes", "file3")
|
||||
append("file3", "and awesomer!\n")
|
||||
addCommit("Time", "file3")
|
||||
checkout("master")
|
||||
write("file2")
|
||||
addCommit("Second commit", "file2")
|
||||
return true
|
||||
}
|
||||
|
||||
fun reset(): Boolean {
|
||||
resetFiles()
|
||||
write("README")
|
||||
addCommit("Initial commit", "README")
|
||||
write("to_commit_first.rb")
|
||||
write("to_commit_second.rb")
|
||||
add("to_commit_first.rb", "to_commit_second.rb")
|
||||
return true
|
||||
}
|
||||
|
||||
fun resetSoft(): Boolean {
|
||||
resetFiles()
|
||||
write("README")
|
||||
addCommit("Initial commit", "README")
|
||||
write("newfile.rb")
|
||||
addCommit("Premature commit", "newfile.rb")
|
||||
return true
|
||||
}
|
||||
|
||||
fun restore(): Boolean {
|
||||
resetFiles()
|
||||
write("file1")
|
||||
addCommit("Initial commit", "file1")
|
||||
write("file2")
|
||||
addCommit("First commit", "file2")
|
||||
write("file3")
|
||||
addCommit("Restore this commit", "file3")
|
||||
git("reset", "--hard", "HEAD^")
|
||||
return true
|
||||
}
|
||||
|
||||
fun squash(): Boolean {
|
||||
resetFiles()
|
||||
write(".hidden")
|
||||
addCommit("Initial Commit", ".hidden")
|
||||
write("README")
|
||||
addCommit("Adding README", "README")
|
||||
write("README", "hey there")
|
||||
addCommit("Updating README (squash this commit into Adding README)", "README")
|
||||
append("README", "\nAdding some more text")
|
||||
addCommit("Updating README (squash this commit into Adding README)", "README")
|
||||
append("README", "\neven more text")
|
||||
addCommit("Updating README (squash this commit into Adding README)", "README")
|
||||
return true
|
||||
}
|
||||
|
||||
fun reorder(): Boolean {
|
||||
resetFiles()
|
||||
write("README")
|
||||
addCommit("Initial Setup", "README")
|
||||
write("file1")
|
||||
addCommit("First commit", "file1")
|
||||
write("file3")
|
||||
addCommit("Third commit", "file3")
|
||||
write("file2")
|
||||
addCommit("Second commit", "file2")
|
||||
return true
|
||||
}
|
||||
|
||||
fun renameCommit(): Boolean {
|
||||
resetFiles()
|
||||
write("README")
|
||||
addCommit("Initial commit", "README")
|
||||
write("file1")
|
||||
addCommit("First coommit", "file1")
|
||||
write("file2")
|
||||
addCommit("Second commit", "file2")
|
||||
return true
|
||||
}
|
||||
|
||||
fun revert(): Boolean {
|
||||
resetFiles()
|
||||
write("file1")
|
||||
addCommit("First commit", "file1")
|
||||
write("file3")
|
||||
addCommit("Bad commit", "file3")
|
||||
write("file2")
|
||||
addCommit("Second commit", "file2")
|
||||
return true
|
||||
}
|
||||
|
||||
fun stash(): Boolean {
|
||||
resetFiles()
|
||||
write(
|
||||
"lyrics.txt",
|
||||
"""
|
||||
Down in Louisiana in that sunny clime,
|
||||
They play a class of music that is super fine,
|
||||
And it makes no difference if its rain or shine,
|
||||
You can hear that that jazz band music playing all the time.
|
||||
""".trimIndent() + "\n",
|
||||
)
|
||||
addCommit("Add some lyrics", "lyrics.txt")
|
||||
append("lyrics.txt", "\nHey!\n")
|
||||
return true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user