Move native level setups into level files
- Delete NativeLevelFixtures.kt and make native repository setup an optional property of each Level. - Move every existing native setup into its corresponding level file so setup, validation, and tests stay together. - Add the missing Blame native setup with config.rb history and a Spider Man password commit. - Add a native runtime regression that verifies `git blame config.rb` identifies Spider Man on the password line. - Verify the full JVM test suite using the compiled host Git runtime.
This commit is contained in:
61
README.md
61
README.md
@@ -39,16 +39,34 @@ It is designed to be idempotent and uses project-relative paths to:
|
|||||||
- install required Android SDK packages
|
- install required Android SDK packages
|
||||||
- write `local.properties`
|
- write `local.properties`
|
||||||
|
|
||||||
|
Available commands:
|
||||||
|
|
||||||
|
| Command | Purpose | Output |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `bash ./AndroidProjectTooling.sh` | Provision or refresh the local Android/JDK toolchain only. | Toolchain under `./jdk` and `./android-sdk` |
|
||||||
|
| `bash ./AndroidProjectTooling.sh --test` | Compile host Git, set `GITHUG_TEST_GIT_BINARY`, and run JVM unit tests. | Test reports under `app/build/reports/` |
|
||||||
|
| `bash ./AndroidProjectTooling.sh --build` | Build the debug APK. | `app/build/outputs/apk/debug/githug-android-debug.apk` |
|
||||||
|
| `bash ./AndroidProjectTooling.sh --build-aab` | Build the debug Android App Bundle. | `app/build/outputs/bundle/debug/githug-android-debug.aab` |
|
||||||
|
| `bash ./AndroidProjectTooling.sh --build-release-aab` | Build the release Android App Bundle. | `app/build/outputs/bundle/release/githug-android-release.aab` |
|
||||||
|
| `bash ./AndroidProjectTooling.sh --compile-git` | Compile Git for the development host and all Android target ABIs. | Host and Android `libgit.so` binaries |
|
||||||
|
|
||||||
To run the JVM unit test suite after ensuring the local toolchain is ready:
|
To run the JVM unit test suite after ensuring the local toolchain is ready:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash ./AndroidProjectTooling.sh --test
|
bash ./AndroidProjectTooling.sh --test
|
||||||
```
|
```
|
||||||
|
|
||||||
To build the debug APK:
|
To build installable/debuggable artifacts:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash ./AndroidProjectTooling.sh --build
|
bash ./AndroidProjectTooling.sh --build
|
||||||
|
bash ./AndroidProjectTooling.sh --build-aab
|
||||||
|
```
|
||||||
|
|
||||||
|
To build the release AAB intended for Play Store style distribution work:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash ./AndroidProjectTooling.sh --build-release-aab
|
||||||
```
|
```
|
||||||
|
|
||||||
The script avoids depending on the system Java version by provisioning a project-local **JDK 17**, which is required by current Android SDK command-line tools.
|
The script avoids depending on the system Java version by provisioning a project-local **JDK 17**, which is required by current Android SDK command-line tools.
|
||||||
@@ -59,13 +77,50 @@ To avoid repeated expensive SDK verification, the script writes a small state fi
|
|||||||
|
|
||||||
If all required components were verified successfully, the script will skip SDK verification/reinstallation for the next **24 hours** unless required directories are missing.
|
If all required components were verified successfully, the script will skip SDK verification/reinstallation for the next **24 hours** unless required directories are missing.
|
||||||
|
|
||||||
When `--build` is used, the script also:
|
When `--build`, `--build-aab`, or `--build-release-aab` is used, the script also:
|
||||||
|
|
||||||
- increments `versionCode` by 1
|
- increments `versionCode` by 1
|
||||||
- increments the patch component of `versionName`, for example `0.1.0` to `0.1.1`
|
- increments the patch component of `versionName`, for example `0.1.0` to `0.1.1`
|
||||||
- renames the debug APK to `githug-android-debug.apk`
|
- renames the generated artifact to a stable `githug-android-*` filename
|
||||||
- attempts to create a git commit after a successful build if there are source changes
|
- attempts to create a git commit after a successful build if there are source changes
|
||||||
|
|
||||||
|
The build commands currently run these Gradle tasks:
|
||||||
|
|
||||||
|
- `--build`: `assembleDebug`
|
||||||
|
- `--build-aab`: `bundleDebug`
|
||||||
|
- `--build-release-aab`: `bundleRelease`
|
||||||
|
|
||||||
|
## Git Binary Compilation
|
||||||
|
|
||||||
|
Native Git is compiled with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash ./CompileGitForAllTargetPlatforms.sh [--host | --android | --all]
|
||||||
|
```
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
| Command | Purpose | Output |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `bash ./CompileGitForAllTargetPlatforms.sh --host` | Compile Git for the development machine. | `build/host-git/libgit.so` |
|
||||||
|
| `bash ./CompileGitForAllTargetPlatforms.sh --android` | Cross-compile Git for Android ABIs served by Google Play. | `app/src/main/jniLibs/<abi>/libgit.so` |
|
||||||
|
| `bash ./CompileGitForAllTargetPlatforms.sh --all` | Compile both host and Android targets. This is the default. | Host and Android outputs |
|
||||||
|
|
||||||
|
Android ABI outputs:
|
||||||
|
|
||||||
|
- `app/src/main/jniLibs/arm64-v8a/libgit.so`
|
||||||
|
- `app/src/main/jniLibs/armeabi-v7a/libgit.so`
|
||||||
|
- `app/src/main/jniLibs/x86/libgit.so`
|
||||||
|
- `app/src/main/jniLibs/x86_64/libgit.so`
|
||||||
|
|
||||||
|
The `--test` tooling command automatically runs `CompileGitForAllTargetPlatforms.sh --host` first and exports `GITHUG_TEST_GIT_BINARY=build/host-git/libgit.so`. This keeps JVM tests on the same `GitRepositoryRuntime` path as the app, including the packaged-runtime helper resolution behavior.
|
||||||
|
|
||||||
|
The `--compile-git` tooling command is a convenience wrapper for:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash ./CompileGitForAllTargetPlatforms.sh --all
|
||||||
|
```
|
||||||
|
|
||||||
## Runtime Architecture
|
## Runtime Architecture
|
||||||
|
|
||||||
The command engine has two execution paths behind one app-facing runtime:
|
The command engine has two execution paths behind one app-facing runtime:
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ android {
|
|||||||
applicationId = "solutions.tretter.githugandroid"
|
applicationId = "solutions.tretter.githugandroid"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 127
|
versionCode = 128
|
||||||
versionName = "0.1.126"
|
versionName = "0.1.127"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package solutions.tretter.githugandroid
|
package solutions.tretter.githugandroid
|
||||||
|
|
||||||
import androidx.compose.runtime.saveable.listSaver
|
import androidx.compose.runtime.saveable.listSaver
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
enum class PlayMode(val label: String) {
|
enum class PlayMode(val label: String) {
|
||||||
CLI_ONLY("CLI ONLY"),
|
CLI_ONLY("CLI ONLY"),
|
||||||
@@ -46,6 +47,7 @@ data class Level(
|
|||||||
val commandSuggestions: List<String>,
|
val commandSuggestions: List<String>,
|
||||||
val validator: (RepoState, String) -> Boolean,
|
val validator: (RepoState, String) -> Boolean,
|
||||||
val setup: () -> RepoState,
|
val setup: () -> RepoState,
|
||||||
|
val nativeSetup: (NativeLevelSetup.() -> Boolean)? = null,
|
||||||
val testCases: List<LevelTestCase> = emptyList(),
|
val testCases: List<LevelTestCase> = emptyList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,6 +56,91 @@ data class LevelTestCase(
|
|||||||
val commands: List<String>,
|
val commands: List<String>,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class NativeLevelSetup internal constructor(
|
||||||
|
internal val sandbox: File,
|
||||||
|
private val runGit: (File, List<String>) -> Int,
|
||||||
|
) {
|
||||||
|
fun resetFiles() {
|
||||||
|
sandbox.listFiles()
|
||||||
|
?.filterNot { it.name == ".git" }
|
||||||
|
?.forEach { it.deleteRecursively() }
|
||||||
|
git("checkout", "-B", "master")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun git(vararg arguments: String): Int = runGit(sandbox, arguments.toList())
|
||||||
|
|
||||||
|
fun git(directory: File, vararg arguments: String): Int = runGit(directory, arguments.toList())
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(path: String, content: String = "") {
|
||||||
|
File(sandbox, path).apply {
|
||||||
|
parentFile?.mkdirs()
|
||||||
|
writeText(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun append(path: String, content: String) {
|
||||||
|
File(sandbox, path).appendText(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(vararg paths: String) {
|
||||||
|
git("add", *paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun commit(message: String, author: String? = null) {
|
||||||
|
if (author == null) {
|
||||||
|
git("commit", "-m", message)
|
||||||
|
} else {
|
||||||
|
git("commit", "--author", author, "-m", message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addCommit(message: String, vararg paths: String, author: String? = null) {
|
||||||
|
add(*paths)
|
||||||
|
commit(message, author)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeIn(directory: File, path: String, content: String = "") {
|
||||||
|
File(directory, path).apply {
|
||||||
|
parentFile?.mkdirs()
|
||||||
|
writeText(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addCommitIn(directory: File, message: String, vararg paths: String) {
|
||||||
|
git(directory, "add", *paths)
|
||||||
|
git(directory, "commit", "-m", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun siblingRepo(name: String): File {
|
||||||
|
val directory = File(sandbox.parentFile ?: sandbox, "${sandbox.name}-$name")
|
||||||
|
directory.deleteRecursively()
|
||||||
|
initRepo(directory)
|
||||||
|
return directory
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkoutNew(branch: String) {
|
||||||
|
git("checkout", "-b", branch)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkout(branch: String) {
|
||||||
|
git("checkout", branch)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tag(name: String) {
|
||||||
|
git("tag", "-f", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun sampleLevels(): List<Level> = allGithugLevels()
|
fun sampleLevels(): List<Level> = allGithugLevels()
|
||||||
|
|
||||||
val RepoStateSaver = listSaver<RepoState, Any>(
|
val RepoStateSaver = listSaver<RepoState, Any>(
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class GitRepositoryRuntime private constructor(
|
|||||||
runGit(nativeGit, sandbox, listOf("checkout", "-B", desired.headBranch))
|
runGit(nativeGit, sandbox, listOf("checkout", "-B", desired.headBranch))
|
||||||
}
|
}
|
||||||
|
|
||||||
materializeNativeGitState(nativeGit, sandbox, desired, level.id)
|
materializeNativeGitState(nativeGit, sandbox, desired, level)
|
||||||
}
|
}
|
||||||
|
|
||||||
return inspectSandbox(level)
|
return inspectSandbox(level)
|
||||||
@@ -443,17 +443,15 @@ class GitRepositoryRuntime private constructor(
|
|||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun materializeNativeGitState(nativeGit: File, sandbox: File, desired: RepoState, levelId: String) {
|
private fun materializeNativeGitState(nativeGit: File, sandbox: File, desired: RepoState, level: Level) {
|
||||||
if (levelId == "push") {
|
level.nativeSetup?.let { setup ->
|
||||||
materializeNativePushLevel(nativeGit, sandbox)
|
val nativeSetup = NativeLevelSetup(sandbox) { directory, arguments ->
|
||||||
return
|
|
||||||
}
|
|
||||||
if (materializeNativeLevelFixture(levelId, sandbox) { directory, arguments ->
|
|
||||||
runGit(nativeGit, directory, arguments).exitCode
|
runGit(nativeGit, directory, arguments).exitCode
|
||||||
}
|
}
|
||||||
) {
|
if (nativeSetup.setup()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
desired.config.forEach { (key, value) ->
|
desired.config.forEach { (key, value) ->
|
||||||
runGit(nativeGit, sandbox, listOf("config", key, value))
|
runGit(nativeGit, sandbox, listOf("config", key, value))
|
||||||
@@ -506,62 +504,6 @@ class GitRepositoryRuntime private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun materializeNativePushLevel(nativeGit: File, sandbox: File) {
|
|
||||||
sandbox.listFiles()
|
|
||||||
?.filterNot { it.name == ".git" }
|
|
||||||
?.forEach { it.deleteRecursively() }
|
|
||||||
|
|
||||||
fun writeFile(name: String, content: String = "$name\n") {
|
|
||||||
File(sandbox, name).apply {
|
|
||||||
parentFile?.mkdirs()
|
|
||||||
writeText(content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun commitIn(directory: File, message: String, vararg paths: String) {
|
|
||||||
runGit(nativeGit, directory, listOf("add") + paths)
|
|
||||||
runGit(nativeGit, directory, listOf("commit", "-m", message))
|
|
||||||
}
|
|
||||||
|
|
||||||
writeFile("file1")
|
|
||||||
commitIn(sandbox, "First commit", "file1")
|
|
||||||
writeFile("file2")
|
|
||||||
commitIn(sandbox, "Second commit", "file2")
|
|
||||||
|
|
||||||
val parent = sandbox.parentFile ?: sandbox
|
|
||||||
val remoteDir = File(parent, "${sandbox.name}-origin.git")
|
|
||||||
val remoteWorkTree = File(parent, "${sandbox.name}-origin-work")
|
|
||||||
remoteDir.deleteRecursively()
|
|
||||||
remoteWorkTree.deleteRecursively()
|
|
||||||
remoteDir.mkdirs()
|
|
||||||
remoteWorkTree.mkdirs()
|
|
||||||
runGit(nativeGit, remoteDir, listOf("init", "--bare", "-b", "master"))
|
|
||||||
runGit(nativeGit, sandbox, listOf("remote", "add", "push-setup-origin", remoteDir.absolutePath))
|
|
||||||
runGit(nativeGit, sandbox, listOf("push", "push-setup-origin", "master"))
|
|
||||||
runGit(nativeGit, sandbox, listOf("remote", "remove", "push-setup-origin"))
|
|
||||||
|
|
||||||
fun runRemoteGit(arguments: List<String>): ProcessExecutionResult {
|
|
||||||
return runGit(
|
|
||||||
nativeGit,
|
|
||||||
sandbox,
|
|
||||||
listOf("--git-dir", remoteDir.absolutePath, "--work-tree", remoteWorkTree.absolutePath) + arguments,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
runRemoteGit(listOf("checkout", "-f", "master"))
|
|
||||||
File(remoteWorkTree, "file4").writeText("file4\n")
|
|
||||||
runRemoteGit(listOf("add", "file4"))
|
|
||||||
runRemoteGit(listOf("commit", "-m", "Fourth commit"))
|
|
||||||
|
|
||||||
writeFile("file3")
|
|
||||||
commitIn(sandbox, "Third commit", "file3")
|
|
||||||
runGit(nativeGit, sandbox, listOf("remote", "add", "origin", remoteDir.absolutePath))
|
|
||||||
runGit(nativeGit, sandbox, listOf("fetch", "origin"))
|
|
||||||
runGit(nativeGit, sandbox, listOf("branch", "--set-upstream-to=origin/master", "master"))
|
|
||||||
runGit(nativeGit, sandbox, listOf("config", "rebase.autoStash", "true"))
|
|
||||||
File(sandbox, "file3").appendText("local worktree change\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun filesForSetupCommit(files: List<GitFile>, index: Int, commitCount: Int): List<GitFile> {
|
private fun filesForSetupCommit(files: List<GitFile>, index: Int, commitCount: Int): List<GitFile> {
|
||||||
if (files.isEmpty()) return emptyList()
|
if (files.isEmpty()) return emptyList()
|
||||||
if (commitCount <= 1) return files
|
if (commitCount <= 1) return files
|
||||||
|
|||||||
@@ -1,434 +0,0 @@
|
|||||||
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()
|
|
||||||
"diff" -> fixture.diff()
|
|
||||||
"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 diff(): Boolean {
|
|
||||||
resetFiles()
|
|
||||||
write("app.rb", diffLevelBaselineAppRb())
|
|
||||||
addCommit("Add app routes", "app.rb")
|
|
||||||
write("app.rb", diffLevelModifiedAppRb())
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun diffLevelBaselineAppRb(): String = buildString {
|
|
||||||
appendLine("require 'sinatra'")
|
|
||||||
appendLine("require 'json'")
|
|
||||||
appendLine()
|
|
||||||
appendLine("helpers do")
|
|
||||||
appendLine(" def get_response(source)")
|
|
||||||
appendLine(" JSON.parse(File.read(source))['message']")
|
|
||||||
appendLine(" end")
|
|
||||||
appendLine("end")
|
|
||||||
appendLine()
|
|
||||||
appendLine("get '/' do")
|
|
||||||
appendLine(" @message = 'hello'")
|
|
||||||
appendLine(" erb :index")
|
|
||||||
appendLine("end")
|
|
||||||
appendLine()
|
|
||||||
appendLine("get '/page' do")
|
|
||||||
appendLine(" @message = 'page'")
|
|
||||||
appendLine(" erb :page")
|
|
||||||
appendLine("end")
|
|
||||||
appendLine()
|
|
||||||
appendLine("get '/yet_another' do")
|
|
||||||
appendLine(" @message = 'another'")
|
|
||||||
appendLine(" erb :success")
|
|
||||||
appendLine("end")
|
|
||||||
appendLine()
|
|
||||||
appendLine("get '/another_page' do")
|
|
||||||
appendLine(" @message = get_response('data.json')")
|
|
||||||
appendLine(" erb :another")
|
|
||||||
appendLine("end")
|
|
||||||
appendLine()
|
|
||||||
appendLine("# end of application")
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun diffLevelModifiedAppRb(): String =
|
|
||||||
diffLevelBaselineAppRb().replace("get_response('data.json')", "get_response('server.json')")
|
|
||||||
@@ -13,9 +13,57 @@ internal fun blameLevel(): Level = level(
|
|||||||
description = "Identify who put a password inside the file `config.rb`.",
|
description = "Identify who put a password inside the file `config.rb`.",
|
||||||
hints = listOf("You want to research the `git blame` command."),
|
hints = listOf("You want to research the `git blame` command."),
|
||||||
commandSuggestions = listOf("git blame config.rb"),
|
commandSuggestions = listOf("git blame config.rb"),
|
||||||
setup = { RepoState(initialized = true, files = listOf(GitFile("config.rb", tracked = true)), branches = mapOf("master" to 1)) },
|
setup = {
|
||||||
|
RepoState(
|
||||||
|
initialized = true,
|
||||||
|
files = listOf(GitFile("config.rb", blameLevelFinalConfigRb(), tracked = true)),
|
||||||
|
commits = listOf(
|
||||||
|
CommitNode("0000001", "Add default config"),
|
||||||
|
CommitNode("0000002", "Add secret config"),
|
||||||
|
CommitNode("0000003", "Document timeout"),
|
||||||
|
),
|
||||||
|
branches = mapOf("master" to 3),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
nativeSetup = {
|
||||||
|
resetFiles()
|
||||||
|
write(
|
||||||
|
"config.rb",
|
||||||
|
"""
|
||||||
|
Githug::Application.configure do
|
||||||
|
config.cache_classes = true
|
||||||
|
config.log_level = :info
|
||||||
|
end
|
||||||
|
""".trimIndent() + "\n",
|
||||||
|
)
|
||||||
|
addCommit("Add default config", "config.rb", author = "Peter Parker <peter@example.com>")
|
||||||
|
write(
|
||||||
|
"config.rb",
|
||||||
|
"""
|
||||||
|
Githug::Application.configure do
|
||||||
|
config.cache_classes = true
|
||||||
|
config.log_level = :info
|
||||||
|
config.password = "correct horse battery staple"
|
||||||
|
end
|
||||||
|
""".trimIndent() + "\n",
|
||||||
|
)
|
||||||
|
addCommit("Add secret config", "config.rb", author = "Spider Man <spider@example.com>")
|
||||||
|
append("config.rb", "config.timeout = 30\n")
|
||||||
|
addCommit("Document timeout", "config.rb", author = "Mary Jane <mary@example.com>")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = commandAnswer("Spider Man"),
|
validator = commandAnswer("Spider Man"),
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("answer author", "Spider Man"),
|
levelTestCase("answer author", "Spider Man"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun blameLevelFinalConfigRb(): String =
|
||||||
|
"""
|
||||||
|
Githug::Application.configure do
|
||||||
|
config.cache_classes = true
|
||||||
|
config.log_level = :info
|
||||||
|
config.password = "correct horse battery staple"
|
||||||
|
end
|
||||||
|
config.timeout = 30
|
||||||
|
""".trimIndent() + "\n"
|
||||||
|
|||||||
@@ -14,6 +14,16 @@ internal fun branchAtLevel(): Level = level(
|
|||||||
hints = listOf("Just like creating a branch, but you have to pass an extra argument."),
|
hints = listOf("Just like creating a branch, but you have to pass an extra argument."),
|
||||||
commandSuggestions = listOf("git branch test_branch HEAD~1"),
|
commandSuggestions = listOf("git branch test_branch HEAD~1"),
|
||||||
setup = { RepoState(initialized = true, commits = listOf(CommitNode("1", "Adding file1"), CommitNode("2", "Updating file1"), CommitNode("3", "Updating file1 again")), branches = mapOf("master" to 3)) },
|
setup = { RepoState(initialized = true, commits = listOf(CommitNode("1", "Adding file1"), CommitNode("2", "Updating file1"), CommitNode("3", "Updating file1 again")), branches = mapOf("master" to 3)) },
|
||||||
|
nativeSetup = {
|
||||||
|
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")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> repo.branches["test_branch"] == 2 },
|
validator = repoPredicate { repo -> repo.branches["test_branch"] == 2 },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("branch at previous commit", "git branch test_branch HEAD~1"),
|
levelTestCase("branch at previous commit", "git branch test_branch HEAD~1"),
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ internal fun checkoutFileLevel(): Level = level(
|
|||||||
hints = listOf("You will need to do some research on the checkout command for this one."),
|
hints = listOf("You will need to do some research on the checkout command for this one."),
|
||||||
commandSuggestions = listOf("git checkout -- config.rb"),
|
commandSuggestions = listOf("git checkout -- config.rb"),
|
||||||
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)) },
|
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)) },
|
||||||
|
nativeSetup = {
|
||||||
|
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!")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> repo.files.find { it.name == "config.rb" }?.content == "This is the initial config file" },
|
validator = repoPredicate { repo -> repo.files.find { it.name == "config.rb" }?.content == "This is the initial config file" },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("checkout file from head", "git checkout -- config.rb"),
|
levelTestCase("checkout file from head", "git checkout -- config.rb"),
|
||||||
|
|||||||
@@ -14,9 +14,30 @@ internal fun checkoutTagLevel(): Level = level(
|
|||||||
hints = listOf("There's no big difference between checking out a branch and checking out a tag."),
|
hints = listOf("There's no big difference between checking out a branch and checking out a tag."),
|
||||||
commandSuggestions = listOf("git checkout v1.2"),
|
commandSuggestions = listOf("git checkout v1.2"),
|
||||||
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)) },
|
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)) },
|
||||||
|
nativeSetup = {
|
||||||
|
checkoutTagHistory()
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> repo.headBranch == "tags/v1.2" || repo.branches.keys.any { "detached at v1.2" in it } },
|
validator = repoPredicate { repo -> repo.headBranch == "tags/v1.2" || repo.branches.keys.any { "detached at v1.2" in it } },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("checkout tag", "git checkout v1.2"),
|
levelTestCase("checkout tag", "git checkout v1.2"),
|
||||||
levelTestCase("checkout explicit tag", "git checkout tags/v1.2"),
|
levelTestCase("checkout explicit tag", "git checkout tags/v1.2"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
internal fun NativeLevelSetup.checkoutTagHistory() {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ internal fun checkoutTagOverBranchLevel(): Level = level(
|
|||||||
hints = listOf("You should think about specifying you're after the tag named `v1.2` (think `tags/`)."),
|
hints = listOf("You should think about specifying you're after the tag named `v1.2` (think `tags/`)."),
|
||||||
commandSuggestions = listOf("git checkout tags/v1.2"),
|
commandSuggestions = listOf("git checkout tags/v1.2"),
|
||||||
setup = { RepoState(initialized = true, tags = listOf("v1.0", "v1.2", "v1.5"), branches = mapOf("master" to 5, "v1.2" to 6)) },
|
setup = { RepoState(initialized = true, tags = listOf("v1.0", "v1.2", "v1.5"), branches = mapOf("master" to 5, "v1.2" to 6)) },
|
||||||
|
nativeSetup = {
|
||||||
|
checkoutTagHistory()
|
||||||
|
checkoutNew("v1.2")
|
||||||
|
write("file3", "some feature\n")
|
||||||
|
addCommit("Developing new features", "file3")
|
||||||
|
checkout("master")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> repo.headBranch == "tags/v1.2" || repo.branches.keys.any { "detached at v1.2" in it } },
|
validator = repoPredicate { repo -> repo.headBranch == "tags/v1.2" || repo.branches.keys.any { "detached at v1.2" in it } },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("checkout tag namespace", "git checkout tags/v1.2"),
|
levelTestCase("checkout tag namespace", "git checkout tags/v1.2"),
|
||||||
|
|||||||
@@ -21,8 +21,51 @@ internal fun diffLevel(): Level = level(
|
|||||||
branches = mapOf("master" to 1),
|
branches = mapOf("master" to 1),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
nativeSetup = {
|
||||||
|
resetFiles()
|
||||||
|
write("app.rb", diffLevelBaselineAppRb())
|
||||||
|
addCommit("Add app routes", "app.rb")
|
||||||
|
write("app.rb", diffLevelModifiedAppRb())
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = commandAnswer("26"),
|
validator = commandAnswer("26"),
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("answer changed line", "26"),
|
levelTestCase("answer changed line", "26"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
internal fun diffLevelBaselineAppRb(): String = buildString {
|
||||||
|
appendLine("require 'sinatra'")
|
||||||
|
appendLine("require 'json'")
|
||||||
|
appendLine()
|
||||||
|
appendLine("helpers do")
|
||||||
|
appendLine(" def get_response(source)")
|
||||||
|
appendLine(" JSON.parse(File.read(source))['message']")
|
||||||
|
appendLine(" end")
|
||||||
|
appendLine("end")
|
||||||
|
appendLine()
|
||||||
|
appendLine("get '/' do")
|
||||||
|
appendLine(" @message = 'hello'")
|
||||||
|
appendLine(" erb :index")
|
||||||
|
appendLine("end")
|
||||||
|
appendLine()
|
||||||
|
appendLine("get '/page' do")
|
||||||
|
appendLine(" @message = 'page'")
|
||||||
|
appendLine(" erb :page")
|
||||||
|
appendLine("end")
|
||||||
|
appendLine()
|
||||||
|
appendLine("get '/yet_another' do")
|
||||||
|
appendLine(" @message = 'another'")
|
||||||
|
appendLine(" erb :success")
|
||||||
|
appendLine("end")
|
||||||
|
appendLine()
|
||||||
|
appendLine("get '/another_page' do")
|
||||||
|
appendLine(" @message = get_response('data.json')")
|
||||||
|
appendLine(" erb :another")
|
||||||
|
appendLine("end")
|
||||||
|
appendLine()
|
||||||
|
appendLine("# end of application")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun diffLevelModifiedAppRb(): String =
|
||||||
|
diffLevelBaselineAppRb().replace("get_response('data.json')", "get_response('server.json')")
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package solutions.tretter.githugandroid
|
package solutions.tretter.githugandroid
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Port of the upstream ruby-githug `fetch` level.
|
* Port of the upstream ruby-githug `fetch` level.
|
||||||
*
|
*
|
||||||
@@ -14,6 +16,22 @@ internal fun fetchLevel(): Level = level(
|
|||||||
hints = listOf("Look up the 'git fetch' command"),
|
hints = listOf("Look up the 'git fetch' command"),
|
||||||
commandSuggestions = listOf("git fetch origin"),
|
commandSuggestions = listOf("git fetch origin"),
|
||||||
setup = { RepoState(initialized = true, branches = mapOf("master" to 1), remotes = mapOf("origin" to "remote")) },
|
setup = { RepoState(initialized = true, branches = mapOf("master" to 1), remotes = mapOf("origin" to "remote")) },
|
||||||
|
nativeSetup = {
|
||||||
|
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")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> "origin/new_branch" in repo.fetchedBranches && repo.headBranch == "master" },
|
validator = repoPredicate { repo -> "origin/new_branch" in repo.fetchedBranches && repo.headBranch == "master" },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("fetch origin", "git fetch origin"),
|
levelTestCase("fetch origin", "git fetch origin"),
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ internal fun level(
|
|||||||
setup: () -> RepoState,
|
setup: () -> RepoState,
|
||||||
validator: (RepoState, String) -> Boolean,
|
validator: (RepoState, String) -> Boolean,
|
||||||
testCases: List<LevelTestCase> = emptyList(),
|
testCases: List<LevelTestCase> = emptyList(),
|
||||||
|
nativeSetup: (NativeLevelSetup.() -> Boolean)? = null,
|
||||||
): Level = Level(
|
): Level = Level(
|
||||||
id = id,
|
id = id,
|
||||||
title = title,
|
title = title,
|
||||||
@@ -76,6 +77,7 @@ internal fun level(
|
|||||||
commandSuggestions = commandSuggestions,
|
commandSuggestions = commandSuggestions,
|
||||||
validator = loggingValidator(id, title, validator),
|
validator = loggingValidator(id, title, validator),
|
||||||
setup = setup,
|
setup = setup,
|
||||||
|
nativeSetup = nativeSetup,
|
||||||
testCases = testCases,
|
testCases = testCases,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,16 @@ internal fun mergeLevel(): Level = level(
|
|||||||
hints = listOf("You want to research the `git merge` command."),
|
hints = listOf("You want to research the `git merge` command."),
|
||||||
commandSuggestions = listOf("git merge feature"),
|
commandSuggestions = listOf("git merge feature"),
|
||||||
setup = { RepoState(initialized = true, files = listOf(GitFile("file1", tracked = true)), branches = mapOf("master" to 1, "feature" to 2)) },
|
setup = { RepoState(initialized = true, files = listOf(GitFile("file1", tracked = true)), branches = mapOf("master" to 1, "feature" to 2)) },
|
||||||
|
nativeSetup = {
|
||||||
|
resetFiles()
|
||||||
|
write("file1")
|
||||||
|
addCommit("added file1", "file1")
|
||||||
|
checkoutNew("feature")
|
||||||
|
write("file2")
|
||||||
|
addCommit("added file2", "file2")
|
||||||
|
checkout("master")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> repo.files.any { it.name == "file2" && it.tracked } },
|
validator = repoPredicate { repo -> repo.files.any { it.name == "file2" && it.tracked } },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("merge feature", "git merge feature"),
|
levelTestCase("merge feature", "git merge feature"),
|
||||||
|
|||||||
@@ -14,6 +14,22 @@ internal fun mergeSquashLevel(): Level = level(
|
|||||||
hints = listOf("Take a look at the `--squash` option of the merge command. Don't forget to commit the merge!"),
|
hints = listOf("Take a look at the `--squash` option of the merge command. Don't forget to commit the merge!"),
|
||||||
commandSuggestions = listOf("git merge --squash long-feature-branch", "git commit -m \"Merge long feature\""),
|
commandSuggestions = listOf("git merge --squash long-feature-branch", "git commit -m \"Merge long feature\""),
|
||||||
setup = { RepoState(initialized = true, files = listOf(GitFile("file1", tracked = true)), branches = mapOf("master" to 2, "long-feature-branch" to 4)) },
|
setup = { RepoState(initialized = true, files = listOf(GitFile("file1", tracked = true)), branches = mapOf("master" to 2, "long-feature-branch" to 4)) },
|
||||||
|
nativeSetup = {
|
||||||
|
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")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> "merge-squash" in repo.maintenanceActions && repo.commits.isNotEmpty() },
|
validator = repoPredicate { repo -> "merge-squash" in repo.maintenanceActions && repo.commits.isNotEmpty() },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("squash merge then commit", "git merge --squash long-feature-branch", "git commit -m \"Merge long feature\""),
|
levelTestCase("squash merge then commit", "git merge --squash long-feature-branch", "git commit -m \"Merge long feature\""),
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package solutions.tretter.githugandroid
|
package solutions.tretter.githugandroid
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Port of the upstream ruby-githug `pull` level.
|
* Port of the upstream ruby-githug `pull` level.
|
||||||
*
|
*
|
||||||
@@ -14,6 +16,22 @@ internal fun pullLevel(): Level = level(
|
|||||||
hints = listOf("Check out the remote repositories and research `git pull`."),
|
hints = listOf("Check out the remote repositories and research `git pull`."),
|
||||||
commandSuggestions = listOf("git pull origin master"),
|
commandSuggestions = listOf("git pull origin master"),
|
||||||
setup = { RepoState(initialized = true, remotes = mapOf("origin" to "remote"), branches = mapOf("master" to 1)) },
|
setup = { RepoState(initialized = true, remotes = mapOf("origin" to "remote"), branches = mapOf("master" to 1)) },
|
||||||
|
nativeSetup = {
|
||||||
|
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")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo ->
|
validator = repoPredicate { repo ->
|
||||||
"origin/master" in repo.fetchedBranches &&
|
"origin/master" in repo.fetchedBranches &&
|
||||||
repo.files.any { it.name == "remote_file" && it.tracked } &&
|
repo.files.any { it.name == "remote_file" && it.tracked } &&
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package solutions.tretter.githugandroid
|
package solutions.tretter.githugandroid
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Port of the upstream ruby-githug `push_branch` level.
|
* Port of the upstream ruby-githug `push_branch` level.
|
||||||
*
|
*
|
||||||
@@ -14,6 +16,28 @@ internal fun pushBranchLevel(): Level = level(
|
|||||||
hints = listOf("Investigate the options in `git push` using `git push --help`"),
|
hints = listOf("Investigate the options in `git push` using `git push --help`"),
|
||||||
commandSuggestions = listOf("git push origin test_branch"),
|
commandSuggestions = listOf("git push origin test_branch"),
|
||||||
setup = { RepoState(initialized = true, branches = mapOf("master" to 2, "other_branch" to 3, "test_branch" to 4), remotes = mapOf("origin" to "remote"), headBranch = "master") },
|
setup = { RepoState(initialized = true, branches = mapOf("master" to 2, "other_branch" to 3, "test_branch" to 4), remotes = mapOf("origin" to "remote"), headBranch = "master") },
|
||||||
|
nativeSetup = {
|
||||||
|
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")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> "origin/test_branch" in repo.pushedBranches && "origin/master" !in repo.pushedBranches && "origin/other_branch" !in repo.pushedBranches },
|
validator = repoPredicate { repo -> "origin/test_branch" in repo.pushedBranches && "origin/master" !in repo.pushedBranches && "origin/other_branch" !in repo.pushedBranches },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("push named branch", "git push origin test_branch"),
|
levelTestCase("push named branch", "git push origin test_branch"),
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package solutions.tretter.githugandroid
|
package solutions.tretter.githugandroid
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Port of the upstream ruby-githug `push` level.
|
* Port of the upstream ruby-githug `push` level.
|
||||||
*
|
*
|
||||||
@@ -31,6 +33,49 @@ internal fun pushLevel(): Level = level(
|
|||||||
fetchedBranches = setOf("origin/master"),
|
fetchedBranches = setOf("origin/master"),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
nativeSetup = {
|
||||||
|
resetFiles()
|
||||||
|
write("file1", "file1\n")
|
||||||
|
addCommit("First commit", "file1")
|
||||||
|
write("file2", "file2\n")
|
||||||
|
addCommit("Second commit", "file2")
|
||||||
|
|
||||||
|
val parent = sandbox.parentFile ?: sandbox
|
||||||
|
val remoteDir = File(parent, "${sandbox.name}-origin.git")
|
||||||
|
val remoteWorkTree = File(parent, "${sandbox.name}-origin-work")
|
||||||
|
remoteDir.deleteRecursively()
|
||||||
|
remoteWorkTree.deleteRecursively()
|
||||||
|
remoteDir.mkdirs()
|
||||||
|
remoteWorkTree.mkdirs()
|
||||||
|
git(remoteDir, "init", "--bare", "-b", "master")
|
||||||
|
git("remote", "add", "push-setup-origin", remoteDir.absolutePath)
|
||||||
|
git("push", "push-setup-origin", "master")
|
||||||
|
git("remote", "remove", "push-setup-origin")
|
||||||
|
|
||||||
|
fun remoteGit(vararg arguments: String) {
|
||||||
|
git(
|
||||||
|
"--git-dir",
|
||||||
|
remoteDir.absolutePath,
|
||||||
|
"--work-tree",
|
||||||
|
remoteWorkTree.absolutePath,
|
||||||
|
*arguments,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteGit("checkout", "-f", "master")
|
||||||
|
File(remoteWorkTree, "file4").writeText("file4\n")
|
||||||
|
remoteGit("add", "file4")
|
||||||
|
remoteGit("commit", "-m", "Fourth commit")
|
||||||
|
|
||||||
|
write("file3", "file3\n")
|
||||||
|
addCommit("Third commit", "file3")
|
||||||
|
git("remote", "add", "origin", remoteDir.absolutePath)
|
||||||
|
git("fetch", "origin")
|
||||||
|
git("branch", "--set-upstream-to=origin/master", "master")
|
||||||
|
git("config", "rebase.autoStash", "true")
|
||||||
|
append("file3", "local worktree change\n")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo ->
|
validator = repoPredicate { repo ->
|
||||||
"origin/master" in repo.pushedBranches &&
|
"origin/master" in repo.pushedBranches &&
|
||||||
repo.commits.size >= 4 &&
|
repo.commits.size >= 4 &&
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package solutions.tretter.githugandroid
|
package solutions.tretter.githugandroid
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Port of the upstream ruby-githug `push_tags` level.
|
* Port of the upstream ruby-githug `push_tags` level.
|
||||||
*
|
*
|
||||||
@@ -14,6 +16,21 @@ internal fun pushTagsLevel(): Level = level(
|
|||||||
hints = listOf("Take a look at `--tags` flag of `git push`"),
|
hints = listOf("Take a look at `--tags` flag of `git push`"),
|
||||||
commandSuggestions = listOf("git push --tags"),
|
commandSuggestions = listOf("git push --tags"),
|
||||||
setup = { RepoState(initialized = true, tags = listOf("tag_to_be_pushed"), branches = mapOf("master" to 2), remotes = mapOf("origin" to "remote")) },
|
setup = { RepoState(initialized = true, tags = listOf("tag_to_be_pushed"), branches = mapOf("master" to 2), remotes = mapOf("origin" to "remote")) },
|
||||||
|
nativeSetup = {
|
||||||
|
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)
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> "tag_to_be_pushed" in repo.pushedTags },
|
validator = repoPredicate { repo -> "tag_to_be_pushed" in repo.pushedTags },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("push all tags", "git push --tags"),
|
levelTestCase("push all tags", "git push --tags"),
|
||||||
|
|||||||
@@ -14,6 +14,18 @@ internal fun rebaseLevel(): Level = level(
|
|||||||
hints = listOf("You want to research the `git rebase` command"),
|
hints = listOf("You want to research the `git rebase` command"),
|
||||||
commandSuggestions = listOf("git checkout feature", "git rebase master"),
|
commandSuggestions = listOf("git checkout feature", "git rebase master"),
|
||||||
setup = { RepoState(initialized = true, headBranch = "master", branches = mapOf("master" to 2, "feature" to 2)) },
|
setup = { RepoState(initialized = true, headBranch = "master", branches = mapOf("master" to 2, "feature" to 2)) },
|
||||||
|
nativeSetup = {
|
||||||
|
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")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo ->
|
validator = repoPredicate { repo ->
|
||||||
repo.headBranch == "feature" &&
|
repo.headBranch == "feature" &&
|
||||||
repo.commits.take(3).map { it.message } == listOf("add feature", "add content", "init commit")
|
repo.commits.take(3).map { it.message } == listOf("add feature", "add content", "init commit")
|
||||||
|
|||||||
@@ -14,6 +14,22 @@ internal fun rebaseOntoLevel(): Level = level(
|
|||||||
hints = listOf("You want to research the `git rebase` commands `--onto` argument"),
|
hints = listOf("You want to research the `git rebase` commands `--onto` argument"),
|
||||||
commandSuggestions = listOf("git rebase --onto master wrong_branch readme-update"),
|
commandSuggestions = listOf("git rebase --onto master wrong_branch readme-update"),
|
||||||
setup = { RepoState(initialized = true, headBranch = "readme-update", branches = mapOf("master" to 1, "wrong_branch" to 2, "readme-update" to 4)) },
|
setup = { RepoState(initialized = true, headBranch = "readme-update", branches = mapOf("master" to 1, "wrong_branch" to 2, "readme-update" to 4)) },
|
||||||
|
nativeSetup = {
|
||||||
|
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")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> repo.headBranch == "readme-update" && "rebase-onto" in repo.maintenanceActions },
|
validator = repoPredicate { repo -> repo.headBranch == "readme-update" && "rebase-onto" in repo.maintenanceActions },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("rebase onto explicit branch", "git rebase --onto master wrong_branch readme-update"),
|
levelTestCase("rebase onto explicit branch", "git rebase --onto master wrong_branch readme-update"),
|
||||||
|
|||||||
@@ -14,6 +14,16 @@ internal fun renameCommitLevel(): Level = level(
|
|||||||
hints = listOf("Take a look the `-i` flag of the rebase command."),
|
hints = listOf("Take a look the `-i` flag of the rebase command."),
|
||||||
commandSuggestions = listOf("git rebase -i HEAD~2"),
|
commandSuggestions = listOf("git rebase -i HEAD~2"),
|
||||||
setup = { RepoState(initialized = true, commits = listOf(CommitNode("1", "Initial commit"), CommitNode("2", "First coommit"), CommitNode("3", "Second commit")), branches = mapOf("master" to 3)) },
|
setup = { RepoState(initialized = true, commits = listOf(CommitNode("1", "Initial commit"), CommitNode("2", "First coommit"), CommitNode("3", "Second commit")), branches = mapOf("master" to 3)) },
|
||||||
|
nativeSetup = {
|
||||||
|
resetFiles()
|
||||||
|
write("README")
|
||||||
|
addCommit("Initial commit", "README")
|
||||||
|
write("file1")
|
||||||
|
addCommit("First coommit", "file1")
|
||||||
|
write("file2")
|
||||||
|
addCommit("Second commit", "file2")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> repo.commits.any { it.message == "First commit" } && repo.commits.none { it.message.contains("coommit") } },
|
validator = repoPredicate { repo -> repo.commits.any { it.message == "First commit" } && repo.commits.none { it.message.contains("coommit") } },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("interactive rebase rename", "git rebase -i HEAD~2"),
|
levelTestCase("interactive rebase rename", "git rebase -i HEAD~2"),
|
||||||
|
|||||||
@@ -14,6 +14,18 @@ internal fun reorderLevel(): Level = level(
|
|||||||
hints = listOf("Take a look the `-i` flag of the rebase command."),
|
hints = listOf("Take a look the `-i` flag of the rebase command."),
|
||||||
commandSuggestions = listOf("git rebase -i HEAD~3"),
|
commandSuggestions = listOf("git rebase -i HEAD~3"),
|
||||||
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)) },
|
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)) },
|
||||||
|
nativeSetup = {
|
||||||
|
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")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> repo.commits.map { it.message }.filter { it.endsWith("commit") } == listOf("First commit", "Second commit", "Third commit") },
|
validator = repoPredicate { repo -> repo.commits.map { it.message }.filter { it.endsWith("commit") } == listOf("First commit", "Second commit", "Third commit") },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("interactive reorder", "git rebase -i HEAD~3"),
|
levelTestCase("interactive reorder", "git rebase -i HEAD~3"),
|
||||||
|
|||||||
@@ -14,6 +14,15 @@ internal fun resetLevel(): Level = level(
|
|||||||
hints = listOf("git status will tell you the command you need to run."),
|
hints = listOf("git status will tell you the command you need to run."),
|
||||||
commandSuggestions = listOf("git reset to_commit_second.rb"),
|
commandSuggestions = listOf("git reset to_commit_second.rb"),
|
||||||
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)) },
|
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)) },
|
||||||
|
nativeSetup = {
|
||||||
|
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")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> repo.commits.size == 1 && repo.files.any { it.name == "to_commit_first.rb" && it.staged } && repo.files.any { it.name == "to_commit_second.rb" && !it.staged } },
|
validator = repoPredicate { repo -> repo.commits.size == 1 && repo.files.any { it.name == "to_commit_first.rb" && it.staged } && repo.files.any { it.name == "to_commit_second.rb" && !it.staged } },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("reset path", "git reset to_commit_second.rb"),
|
levelTestCase("reset path", "git reset to_commit_second.rb"),
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ internal fun resetSoftLevel(): Level = level(
|
|||||||
hints = listOf("What are some options you can use with `git reset`?"),
|
hints = listOf("What are some options you can use with `git reset`?"),
|
||||||
commandSuggestions = listOf("git reset --soft HEAD^"),
|
commandSuggestions = listOf("git reset --soft HEAD^"),
|
||||||
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)) },
|
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)) },
|
||||||
|
nativeSetup = {
|
||||||
|
resetFiles()
|
||||||
|
write("README")
|
||||||
|
addCommit("Initial commit", "README")
|
||||||
|
write("newfile.rb")
|
||||||
|
addCommit("Premature commit", "newfile.rb")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> repo.commits.size == 1 && repo.files.any { it.name == "newfile.rb" && it.staged } },
|
validator = repoPredicate { repo -> repo.commits.size == 1 && repo.files.any { it.name == "newfile.rb" && it.staged } },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("soft reset caret", "git reset --soft HEAD^"),
|
levelTestCase("soft reset caret", "git reset --soft HEAD^"),
|
||||||
|
|||||||
@@ -14,6 +14,17 @@ internal fun restoreLevel(): Level = level(
|
|||||||
hints = listOf("The commit is still floating around somewhere. Have you checked out `git reflog`?"),
|
hints = listOf("The commit is still floating around somewhere. Have you checked out `git reflog`?"),
|
||||||
commandSuggestions = listOf("git checkout HEAD@{1} -- file3"),
|
commandSuggestions = listOf("git checkout HEAD@{1} -- file3"),
|
||||||
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)) },
|
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)) },
|
||||||
|
nativeSetup = {
|
||||||
|
resetFiles()
|
||||||
|
write("file1")
|
||||||
|
addCommit("Initial commit", "file1")
|
||||||
|
write("file2")
|
||||||
|
addCommit("First commit", "file2")
|
||||||
|
write("file3")
|
||||||
|
addCommit("Restore this commit", "file3")
|
||||||
|
git("reset", "--hard", "HEAD^")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> repo.files.any { it.name == "file3" && it.tracked } },
|
validator = repoPredicate { repo -> repo.files.any { it.name == "file3" && it.tracked } },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("checkout file from reflog commit", "git checkout HEAD@{1} -- file3"),
|
levelTestCase("checkout file from reflog commit", "git checkout HEAD@{1} -- file3"),
|
||||||
|
|||||||
@@ -14,6 +14,16 @@ internal fun revertLevel(): Level = level(
|
|||||||
hints = listOf("Try the revert command."),
|
hints = listOf("Try the revert command."),
|
||||||
commandSuggestions = listOf("git revert HEAD~1"),
|
commandSuggestions = listOf("git revert HEAD~1"),
|
||||||
setup = { RepoState(initialized = true, commits = listOf(CommitNode("1", "First commit"), CommitNode("2", "Bad commit"), CommitNode("3", "Second commit")), branches = mapOf("master" to 3), pushedBranches = setOf("origin/master")) },
|
setup = { RepoState(initialized = true, commits = listOf(CommitNode("1", "First commit"), CommitNode("2", "Bad commit"), CommitNode("3", "Second commit")), branches = mapOf("master" to 3), pushedBranches = setOf("origin/master")) },
|
||||||
|
nativeSetup = {
|
||||||
|
resetFiles()
|
||||||
|
write("file1")
|
||||||
|
addCommit("First commit", "file1")
|
||||||
|
write("file3")
|
||||||
|
addCommit("Bad commit", "file3")
|
||||||
|
write("file2")
|
||||||
|
addCommit("Second commit", "file2")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> repo.commits.any { it.message.startsWith("Revert") } },
|
validator = repoPredicate { repo -> repo.commits.any { it.message.startsWith("Revert") } },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("revert middle commit", "git revert HEAD~1"),
|
levelTestCase("revert middle commit", "git revert HEAD~1"),
|
||||||
|
|||||||
@@ -14,6 +14,20 @@ internal fun squashLevel(): Level = level(
|
|||||||
hints = listOf("Take a look at the `-i` flag of the rebase command."),
|
hints = listOf("Take a look at the `-i` flag of the rebase command."),
|
||||||
commandSuggestions = listOf("git rebase -i HEAD~4"),
|
commandSuggestions = listOf("git rebase -i HEAD~4"),
|
||||||
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)) },
|
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)) },
|
||||||
|
nativeSetup = {
|
||||||
|
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")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> repo.commits.size <= 2 && repo.commits.any { it.message == "Adding README" } },
|
validator = repoPredicate { repo -> repo.commits.size <= 2 && repo.commits.any { it.message == "Adding README" } },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("interactive squash", "git rebase -i HEAD~4"),
|
levelTestCase("interactive squash", "git rebase -i HEAD~4"),
|
||||||
|
|||||||
@@ -14,6 +14,21 @@ internal fun stashLevel(): Level = level(
|
|||||||
hints = listOf("It's like stashing. Try finding an appropriate git command."),
|
hints = listOf("It's like stashing. Try finding an appropriate git command."),
|
||||||
commandSuggestions = listOf("git stash", "git status"),
|
commandSuggestions = listOf("git stash", "git status"),
|
||||||
setup = { RepoState(initialized = true, files = listOf(GitFile("lyrics.txt", "modified lyrics", tracked = true)), commits = listOf(CommitNode("0000001", "Initial commit")), branches = mapOf("master" to 1)) },
|
setup = { RepoState(initialized = true, files = listOf(GitFile("lyrics.txt", "modified lyrics", tracked = true)), commits = listOf(CommitNode("0000001", "Initial commit")), branches = mapOf("master" to 1)) },
|
||||||
|
nativeSetup = {
|
||||||
|
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")
|
||||||
|
true
|
||||||
|
},
|
||||||
validator = repoPredicate { repo -> repo.stashes.isNotEmpty() && repo.files.none { it.staged } },
|
validator = repoPredicate { repo -> repo.stashes.isNotEmpty() && repo.files.none { it.staged } },
|
||||||
testCases = listOf(
|
testCases = listOf(
|
||||||
levelTestCase("stash changes", "git stash"),
|
levelTestCase("stash changes", "git stash"),
|
||||||
|
|||||||
@@ -232,6 +232,27 @@ class GitSandboxEngineTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun nativeBlameLevelShowsPasswordAuthor() {
|
||||||
|
val git = testGitBinary()
|
||||||
|
assumeTrue(git.exists() && git.canExecute())
|
||||||
|
val root = Files.createTempDirectory("githug-blame-level").toFile()
|
||||||
|
try {
|
||||||
|
val runtime = GitRepositoryRuntime(root, git)
|
||||||
|
val level = blameLevel()
|
||||||
|
val repo = runtime.prepareLevel(level)
|
||||||
|
|
||||||
|
assertTrue(repo.files.single { it.name == "config.rb" }.content.contains("password"))
|
||||||
|
|
||||||
|
val (_, blameOutput) = runtime.execute(level, repo, "git blame config.rb")
|
||||||
|
|
||||||
|
assertTrue(blameOutput.any { it.contains("Spider Man") && it.contains("password") })
|
||||||
|
assertTrue(level.validator(repo, "Spider Man"))
|
||||||
|
} finally {
|
||||||
|
root.deleteRecursively()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun cdDotDotShortcutMovesToParentDirectory() {
|
fun cdDotDotShortcutMovesToParentDirectory() {
|
||||||
val repo = RepoState(initialized = true, currentDir = "src/main")
|
val repo = RepoState(initialized = true, currentDir = "src/main")
|
||||||
|
|||||||
Reference in New Issue
Block a user