From 87d6f6554ba3f5e6396e485657a23d0d50bdba1f Mon Sep 17 00:00:00 2001 From: vanphuc Date: Sat, 1 Nov 2025 00:35:13 +0800 Subject: [PATCH 1/4] Add the ff-undo exercise --- ff_undo/.gitmastery-exercise.json | 18 ++++++++++ ff_undo/README.md | 18 ++++++++++ ff_undo/__init__.py | 0 ff_undo/download.py | 36 +++++++++++++++++++ ff_undo/tests/__init__.py | 0 ff_undo/tests/test_verify.py | 42 ++++++++++++++++++++++ ff_undo/verify.py | 59 +++++++++++++++++++++++++++++++ 7 files changed, 173 insertions(+) create mode 100644 ff_undo/.gitmastery-exercise.json create mode 100644 ff_undo/README.md create mode 100644 ff_undo/__init__.py create mode 100644 ff_undo/download.py create mode 100644 ff_undo/tests/__init__.py create mode 100644 ff_undo/tests/test_verify.py create mode 100644 ff_undo/verify.py diff --git a/ff_undo/.gitmastery-exercise.json b/ff_undo/.gitmastery-exercise.json new file mode 100644 index 0000000..53fddb6 --- /dev/null +++ b/ff_undo/.gitmastery-exercise.json @@ -0,0 +1,18 @@ +{ + "exercise_name": "ff-undo", + "tags": [ + "git-branch", + "git-merge", + "git-reset" + ], + "requires_git": true, + "requires_github": false, + "base_files": {}, + "exercise_repo": { + "repo_type": "local", + "repo_name": "play-characters", + "repo_title": null, + "create_fork": null, + "init": true + } +} \ No newline at end of file diff --git a/ff_undo/README.md b/ff_undo/README.md new file mode 100644 index 0000000..2c23c95 --- /dev/null +++ b/ff_undo/README.md @@ -0,0 +1,18 @@ +# ff-undo + + + +## Task + + + +## Hints + + + diff --git a/ff_undo/__init__.py b/ff_undo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ff_undo/download.py b/ff_undo/download.py new file mode 100644 index 0000000..0f4db87 --- /dev/null +++ b/ff_undo/download.py @@ -0,0 +1,36 @@ +from exercise_utils.cli import run_command +from exercise_utils.gitmastery import create_start_tag + +__resources__ = {} + + +def setup(verbose: bool = False): + # Marks the start of setup (Git-Mastery internal logging) + create_start_tag(verbose) + + # Create initial files and commits + run_command('echo "Scientist" > rick.txt', verbose) + run_command('git add .', verbose) + run_command('git commit -m "Add Rick"', verbose) + + run_command('echo "Boy" > morty.txt', verbose) + run_command('git add .', verbose) + run_command('git commit -m "Add Morty"', verbose) + + # Create and switch to branch 'others' + run_command('git switch -c others', verbose) + run_command('echo "No job" > birdperson.txt', verbose) + run_command('git add .', verbose) + run_command('git commit -m "Add Birdperson"', verbose) + + run_command('echo "Cyborg" >> birdperson.txt', verbose) + run_command('git add .', verbose) + run_command('git commit -m "Add Cyborg to birdperson.txt"', verbose) + + run_command('echo "Spy" > tammy.txt', verbose) + run_command('git add .', verbose) + run_command('git commit -m "Add Tammy"', verbose) + + # Merge back into main + run_command('git switch main', verbose) + run_command('git merge others -m "Introduce others"', verbose) \ No newline at end of file diff --git a/ff_undo/tests/__init__.py b/ff_undo/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ff_undo/tests/test_verify.py b/ff_undo/tests/test_verify.py new file mode 100644 index 0000000..30dc776 --- /dev/null +++ b/ff_undo/tests/test_verify.py @@ -0,0 +1,42 @@ +from git_autograder import GitAutograderTestLoader, assert_output +from git_autograder.status import GitAutograderStatus +from ..verify import ( + MERGE_NOT_UNDONE, + MAIN_COMMITS_INCORRECT, + OTHERS_COMMITS_INCORRECT, + OTHERS_BRANCH_MISSING, + verify +) + +REPOSITORY_NAME = "ff-undo" + +loader = GitAutograderTestLoader(__file__, REPOSITORY_NAME, verify) + +def test_correct_solution(): + """Main has only Rick + Morty, others still exists with Birdperson, Cyborg, Tammy""" + with loader.load("specs/base.yml") as output: + assert_output(output, GitAutograderStatus.SUCCESSFUL) + + +def test_merge_not_undone(): + """Student did not undo merge (main still contains merge commit)""" + with loader.load("specs/merge_not_undone.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [MERGE_NOT_UNDONE]) + + +def test_branch_missing(): + """Student deleted 'others' branch""" + with loader.load("specs/branch_missing.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [OTHERS_BRANCH_MISSING]) + + +def test_main_commits_incorrect(): + """Student modified or removed main commits""" + with loader.load("specs/main_commits_incorrect.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [MAIN_COMMITS_INCORRECT]) + + +def test_others_commits_incorrect(): + """Student changed commits on others branch""" + with loader.load("specs/others_commits_incorrect.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [OTHERS_COMMITS_INCORRECT]) diff --git a/ff_undo/verify.py b/ff_undo/verify.py new file mode 100644 index 0000000..e11bc8e --- /dev/null +++ b/ff_undo/verify.py @@ -0,0 +1,59 @@ +from git_autograder import ( + GitAutograderOutput, + GitAutograderExercise, + GitAutograderStatus, +) + +MERGE_NOT_UNDONE = ( + "The merge commit 'Introduce others' is still present on the main branch. " + "You need to undo the merge so that only Rick and Morty remain on main." +) +MAIN_COMMITS_INCORRECT = ( + "The main branch does not contain the expected commits " + "The main branch does not contain both commits 'Add Rick' and 'Add Morty'." +) +OTHERS_COMMITS_INCORRECT = ( + "The others branch does not contain the expected commits " + "'Add Birdperson', 'Add Cyborg to birdperson.txt', and 'Add Tammy'." +) +OTHERS_BRANCH_MISSING = ( + "The branch 'others' no longer exists. You should not delete it, only undo the merge on main." +) + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + + # Check if branch others exists + if not exercise.repo.branches.has_branch("others"): + raise exercise.wrong_answer([OTHERS_BRANCH_MISSING]) + + # Take all commit messages on main + commit_messages_in_main = [c.message.strip() for c in exercise.repo.repo.iter_commits("main")] + + # Take all commit messages on others + commit_messages_in_others = [c.message.strip() for c in exercise.repo.repo.iter_commits("others")] + + # Check that the merge commit is not present on main + if any("Introduce others" in msg for msg in commit_messages_in_main): + raise exercise.wrong_answer([MERGE_NOT_UNDONE]) + + # Check that commits in main are only the initial 2 commits + has_rick = any("Add Rick" in msg for msg in commit_messages_in_main) + has_morty = any("Add Morty" in msg for msg in commit_messages_in_main) + if len(commit_messages_in_main) != 2 or not (has_rick and has_morty): + raise exercise.wrong_answer([MAIN_COMMITS_INCORRECT]) + + # Check that the merge commit is not present on others + if any("Introduce others" in msg for msg in commit_messages_in_others): + raise exercise.wrong_answer([MERGE_NOT_UNDONE]) + + # Check that commits in others are only the initial 3 commits + has_birdperson = any("Add Birdperson" in msg for msg in commit_messages_in_others) + has_cyborg = any("Add Cyborg to birdperson.txt" in msg for msg in commit_messages_in_others) + has_tammy = any("Add Tammy" in msg for msg in commit_messages_in_others) + if len(commit_messages_in_others) != 3 or not (has_birdperson and has_cyborg and has_tammy): + raise exercise.wrong_answer([OTHERS_COMMITS_INCORRECT]) + + return exercise.to_output( + ["You have successfully undone the merge of branch 'others'."], + GitAutograderStatus.SUCCESSFUL, + ) From 966108ebfe445d5d422f219cae7fdfefbdec899a Mon Sep 17 00:00:00 2001 From: vanphuc Date: Sat, 1 Nov 2025 00:40:34 +0800 Subject: [PATCH 2/4] Add yml file for test_verify --- ff_undo/tests/specs/base.yml | 22 ++++++++++++++++ ff_undo/tests/specs/branch_missing.yml | 10 ++++++++ .../tests/specs/main_commits_incorrect.yml | 18 +++++++++++++ ff_undo/tests/specs/merge_not_undone.yml | 25 +++++++++++++++++++ .../tests/specs/others_commits_incorrect.yml | 19 ++++++++++++++ 5 files changed, 94 insertions(+) create mode 100644 ff_undo/tests/specs/base.yml create mode 100644 ff_undo/tests/specs/branch_missing.yml create mode 100644 ff_undo/tests/specs/main_commits_incorrect.yml create mode 100644 ff_undo/tests/specs/merge_not_undone.yml create mode 100644 ff_undo/tests/specs/others_commits_incorrect.yml diff --git a/ff_undo/tests/specs/base.yml b/ff_undo/tests/specs/base.yml new file mode 100644 index 0000000..a73079c --- /dev/null +++ b/ff_undo/tests/specs/base.yml @@ -0,0 +1,22 @@ +initialization: + steps: + - type: commit + id: start + empty: true + message: Empty commit + - type: commit + message: "Add Rick" + - type: commit + message: "Add Morty" + - type: branch + branch-name: others + - type: checkout + branch-name: others + - type: commit + message: "Add Birdperson" + - type: commit + message: "Add Cyborg to birdperson.txt" + - type: commit + message: "Add Tammy" + + diff --git a/ff_undo/tests/specs/branch_missing.yml b/ff_undo/tests/specs/branch_missing.yml new file mode 100644 index 0000000..8fe1a43 --- /dev/null +++ b/ff_undo/tests/specs/branch_missing.yml @@ -0,0 +1,10 @@ +initialization: + steps: + - type: commit + id: start + empty: true + message: Empty commit + - type: commit + message: "Add Rick" + - type: commit + message: "Add Morty" diff --git a/ff_undo/tests/specs/main_commits_incorrect.yml b/ff_undo/tests/specs/main_commits_incorrect.yml new file mode 100644 index 0000000..510c4ab --- /dev/null +++ b/ff_undo/tests/specs/main_commits_incorrect.yml @@ -0,0 +1,18 @@ +initialization: + steps: + - type: commit + id: start + empty: true + message: Empty commit + - type: commit + message: "Add Morty" + - type: branch + branch-name: others + - type: checkout + branch-name: others + - type: commit + message: "Add Birdperson" + - type: commit + message: "Add Cyborg to birdperson.txt" + - type: commit + message: "Add Tammy" diff --git a/ff_undo/tests/specs/merge_not_undone.yml b/ff_undo/tests/specs/merge_not_undone.yml new file mode 100644 index 0000000..e812179 --- /dev/null +++ b/ff_undo/tests/specs/merge_not_undone.yml @@ -0,0 +1,25 @@ +initialization: + steps: + - type: commit + id: start + empty: true + message: Empty commit + - type: commit + message: "Add Rick" + - type: commit + message: "Add Morty" + - type: branch + branch-name: others + - type: checkout + branch-name: others + - type: commit + message: "Add Birdperson" + - type: commit + message: "Add Cyborg to birdperson.txt" + - type: commit + message: "Add Tammy" + - type: checkout + branch-name: main + - type: merge + branch-name: others + message: "Introduce others" diff --git a/ff_undo/tests/specs/others_commits_incorrect.yml b/ff_undo/tests/specs/others_commits_incorrect.yml new file mode 100644 index 0000000..b0acc2a --- /dev/null +++ b/ff_undo/tests/specs/others_commits_incorrect.yml @@ -0,0 +1,19 @@ +initialization: + steps: + - type: commit + id: start + empty: true + message: Empty commit + - type: commit + message: "Add Rick" + - type: commit + message: "Add Morty" + - type: branch + branch-name: others + - type: checkout + branch-name: others + - type: commit + message: "Add Birdperson" + - type: commit + message: "Add Tammy" + From dace21b646955fde5f37fe5cb7b5dff313b113e3 Mon Sep 17 00:00:00 2001 From: vanphuc Date: Sat, 1 Nov 2025 01:27:28 +0800 Subject: [PATCH 3/4] Add README file and also fix the verify to check the mistake --- ff_undo/README.md | 42 ++++++++++++++++++------ ff_undo/tests/specs/base.yml | 3 +- ff_undo/tests/specs/merge_not_undone.yml | 1 + ff_undo/tests/test_verify.py | 5 --- ff_undo/verify.py | 17 +++++----- 5 files changed, 43 insertions(+), 25 deletions(-) diff --git a/ff_undo/README.md b/ff_undo/README.md index 2c23c95..289ff6e 100644 --- a/ff_undo/README.md +++ b/ff_undo/README.md @@ -1,18 +1,40 @@ # ff-undo - +This exercise focuses on **undoing a merge in Git**. You will practice how to revert unwanted merge commits while keeping branches and commits intact. ## Task - +You have a repository with two branches: + +- `main` branch, which initially contains commits: + - `Add Rick` + - `Add Morty` +- `others` branch, which contains commits: + - `Add Birdperson` + - `Add Cyborg to birdperson.txt` + - `Add Tammy` + +A merge with fast forward from `others` into `main` has been done incorrectly. Your task is: + +1. **Undo the merge on `main`**, so that only `Add Rick` and `Add Morty` remain on `main`. +2. Ensure the `others` branch still exists with all its commits intact. +3. Do not delete any commits; only undo the merge on `main`. ## Hints - - +
+Hint 1: Check your branches + +Use `git branch` to see the current branches and verify `main` and `others` exist. +
+ +
+Hint 2: View commit history + +Use `git log --oneline` on `main` to identify the merge commit that needs to be undone. +
+ +
+Hint 3: Undo the merge + +You can undo a merge using: `git reset --hard ` diff --git a/ff_undo/tests/specs/base.yml b/ff_undo/tests/specs/base.yml index a73079c..b77c9fe 100644 --- a/ff_undo/tests/specs/base.yml +++ b/ff_undo/tests/specs/base.yml @@ -18,5 +18,6 @@ initialization: message: "Add Cyborg to birdperson.txt" - type: commit message: "Add Tammy" - + - type: checkout + branch-name: main diff --git a/ff_undo/tests/specs/merge_not_undone.yml b/ff_undo/tests/specs/merge_not_undone.yml index e812179..0a77d8a 100644 --- a/ff_undo/tests/specs/merge_not_undone.yml +++ b/ff_undo/tests/specs/merge_not_undone.yml @@ -22,4 +22,5 @@ initialization: branch-name: main - type: merge branch-name: others + no-ff: false message: "Introduce others" diff --git a/ff_undo/tests/test_verify.py b/ff_undo/tests/test_verify.py index 30dc776..097bd76 100644 --- a/ff_undo/tests/test_verify.py +++ b/ff_undo/tests/test_verify.py @@ -13,30 +13,25 @@ loader = GitAutograderTestLoader(__file__, REPOSITORY_NAME, verify) def test_correct_solution(): - """Main has only Rick + Morty, others still exists with Birdperson, Cyborg, Tammy""" with loader.load("specs/base.yml") as output: assert_output(output, GitAutograderStatus.SUCCESSFUL) def test_merge_not_undone(): - """Student did not undo merge (main still contains merge commit)""" with loader.load("specs/merge_not_undone.yml") as output: assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [MERGE_NOT_UNDONE]) def test_branch_missing(): - """Student deleted 'others' branch""" with loader.load("specs/branch_missing.yml") as output: assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [OTHERS_BRANCH_MISSING]) def test_main_commits_incorrect(): - """Student modified or removed main commits""" with loader.load("specs/main_commits_incorrect.yml") as output: assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [MAIN_COMMITS_INCORRECT]) def test_others_commits_incorrect(): - """Student changed commits on others branch""" with loader.load("specs/others_commits_incorrect.yml") as output: assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [OTHERS_COMMITS_INCORRECT]) diff --git a/ff_undo/verify.py b/ff_undo/verify.py index e11bc8e..a5baec9 100644 --- a/ff_undo/verify.py +++ b/ff_undo/verify.py @@ -5,8 +5,8 @@ ) MERGE_NOT_UNDONE = ( - "The merge commit 'Introduce others' is still present on the main branch. " - "You need to undo the merge so that only Rick and Morty remain on main." + "The merge commit 'Introduce others' is still present. " + "You need to undo the merge." ) MAIN_COMMITS_INCORRECT = ( "The main branch does not contain the expected commits " @@ -33,24 +33,23 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: commit_messages_in_others = [c.message.strip() for c in exercise.repo.repo.iter_commits("others")] # Check that the merge commit is not present on main - if any("Introduce others" in msg for msg in commit_messages_in_main): + has_birdperson_in_main = any("Add Birdperson" in msg for msg in commit_messages_in_main) + has_cyborg_in_main = any("Add Cyborg to birdperson.txt" in msg for msg in commit_messages_in_main) + has_tammy_in_main = any("Add Tammy" in msg for msg in commit_messages_in_main) + if has_birdperson_in_main or has_birdperson_in_main or has_tammy_in_main: raise exercise.wrong_answer([MERGE_NOT_UNDONE]) # Check that commits in main are only the initial 2 commits has_rick = any("Add Rick" in msg for msg in commit_messages_in_main) has_morty = any("Add Morty" in msg for msg in commit_messages_in_main) - if len(commit_messages_in_main) != 2 or not (has_rick and has_morty): + if len(commit_messages_in_main) != 3 or not (has_rick and has_morty): raise exercise.wrong_answer([MAIN_COMMITS_INCORRECT]) - # Check that the merge commit is not present on others - if any("Introduce others" in msg for msg in commit_messages_in_others): - raise exercise.wrong_answer([MERGE_NOT_UNDONE]) - # Check that commits in others are only the initial 3 commits has_birdperson = any("Add Birdperson" in msg for msg in commit_messages_in_others) has_cyborg = any("Add Cyborg to birdperson.txt" in msg for msg in commit_messages_in_others) has_tammy = any("Add Tammy" in msg for msg in commit_messages_in_others) - if len(commit_messages_in_others) != 3 or not (has_birdperson and has_cyborg and has_tammy): + if len(commit_messages_in_others) != 6 or not (has_birdperson and has_cyborg and has_tammy): raise exercise.wrong_answer([OTHERS_COMMITS_INCORRECT]) return exercise.to_output( From 7abe28c5836d626a6e778d5a89c6a9f9a2d63041 Mon Sep 17 00:00:00 2001 From: vanphuc Date: Sat, 1 Nov 2025 09:28:53 +0800 Subject: [PATCH 4/4] Change the notification for not undo merge --- ff_undo/verify.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ff_undo/verify.py b/ff_undo/verify.py index a5baec9..42faac8 100644 --- a/ff_undo/verify.py +++ b/ff_undo/verify.py @@ -5,7 +5,6 @@ ) MERGE_NOT_UNDONE = ( - "The merge commit 'Introduce others' is still present. " "You need to undo the merge." ) MAIN_COMMITS_INCORRECT = (