From bd52f301bee2d746a99f13b3abddb0fd5dcb6ecf Mon Sep 17 00:00:00 2001 From: keaharvan Date: Sun, 16 Nov 2025 12:49:19 +0800 Subject: [PATCH 1/5] Test downloads --- merge_undo/.gitmastery-exercise.json | 18 ++++++++ merge_undo/README.md | 26 +++++++++++ merge_undo/__init__.py | 0 merge_undo/download.py | 69 ++++++++++++++++++++++++++++ merge_undo/res/README.md | 9 ++++ merge_undo/tests/__init__.py | 0 merge_undo/tests/specs/base.yml | 6 +++ merge_undo/tests/test_verify.py | 12 +++++ merge_undo/verify.py | 11 +++++ 9 files changed, 151 insertions(+) create mode 100644 merge_undo/.gitmastery-exercise.json create mode 100644 merge_undo/README.md create mode 100644 merge_undo/__init__.py create mode 100644 merge_undo/download.py create mode 100644 merge_undo/res/README.md create mode 100644 merge_undo/tests/__init__.py create mode 100644 merge_undo/tests/specs/base.yml create mode 100644 merge_undo/tests/test_verify.py create mode 100644 merge_undo/verify.py diff --git a/merge_undo/.gitmastery-exercise.json b/merge_undo/.gitmastery-exercise.json new file mode 100644 index 0000000..60d3093 --- /dev/null +++ b/merge_undo/.gitmastery-exercise.json @@ -0,0 +1,18 @@ +{ + "exercise_name": "merge-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/merge_undo/README.md b/merge_undo/README.md new file mode 100644 index 0000000..84ad728 --- /dev/null +++ b/merge_undo/README.md @@ -0,0 +1,26 @@ +# merge-undo + +Scenario: You are keeping notes on the characters of a play that you are writing. In the main story line (which is in the `main` branch), you introduced two characters, Rick and Morty. You had two other characters in two separate branches `daughter` and `son-in-law`. Just now, you introduced these two characters to the main story line by merging the two branches to the `main` branch. + +// insert the current revision graph here + +However, now you realise this is premature, and wish to undo that change. + +## Task + +Undo the merging of branches `son-in-law` and `daughter`. + +The result should be as follows: + +// insert the expected revision graph here + +## Hints + + + diff --git a/merge_undo/__init__.py b/merge_undo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/merge_undo/download.py b/merge_undo/download.py new file mode 100644 index 0000000..cc1f154 --- /dev/null +++ b/merge_undo/download.py @@ -0,0 +1,69 @@ +__resources__ = {"README.md": "README.md"} + +from exercise_utils.cli import run_command +from exercise_utils.file import create_or_update_file +from exercise_utils.git import add, checkout, commit, merge_with_message +from exercise_utils.gitmastery import create_start_tag + + +def setup(verbose: bool = False): + create_start_tag(verbose) + + # main branch + checkout("main", False, verbose) + create_or_update_file( + "rick.txt", + """ + Scientist + """, + ) + add(["rick.txt"], verbose) + commit("Add Rick", verbose) + create_or_update_file( + "morty.txt", + """ + Boy + """, + ) + add(["morty.txt"], verbose) + commit("Add morty", verbose) + + # daughter branch + checkout("daughter", True, verbose) + create_or_update_file( + "beth.txt", + """ + Vet + """, + ) + add(["beth.txt"], verbose) + commit("Add Beth", verbose) + + # son-in-law branch + checkout("main", False, verbose) # switch to main first + + checkout("son-in-law", True, verbose) + create_or_update_file( + "jerry.txt", + """ + Salesman + """, + ) + add(["jerry.txt"], verbose) + commit("Add Herry", verbose) + + # Append morty as a grandson + checkout("main", False, verbose) + create_or_update_file( + "morty.txt", + """ + Boy + Grandson + """, + ) + add(["morty.txt"], verbose) + commit("Mention Morty is grandson", verbose) + + # Merge daughter and son-in-law to main story + merge_with_message("daughter", False, "Introduce Beth", verbose) + merge_with_message("son-in-law", False, "Introduce Jerry", verbose) diff --git a/merge_undo/res/README.md b/merge_undo/res/README.md new file mode 100644 index 0000000..f9433f6 --- /dev/null +++ b/merge_undo/res/README.md @@ -0,0 +1,9 @@ +# merge-undo + +Scenario: You are keeping notes on the characters of a play that you are writing. In the main story line (which is in the `main` branch), you introduced two characters, Rick and Morty. You had two other characters in two separate branches `daughter` and `son-in-law`. Just now, you introduced these two characters to the main story line by merging the two branches to the `main` branch. + +However, now you realise this is premature, and wish to undo that change. + +## Task + +Undo the merging of branches `son-in-law` and `daughter`. diff --git a/merge_undo/tests/__init__.py b/merge_undo/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/merge_undo/tests/specs/base.yml b/merge_undo/tests/specs/base.yml new file mode 100644 index 0000000..00c3a53 --- /dev/null +++ b/merge_undo/tests/specs/base.yml @@ -0,0 +1,6 @@ +initialization: + steps: + - type: commit + empty: true + message: Empty commit + id: start diff --git a/merge_undo/tests/test_verify.py b/merge_undo/tests/test_verify.py new file mode 100644 index 0000000..45541a6 --- /dev/null +++ b/merge_undo/tests/test_verify.py @@ -0,0 +1,12 @@ +from git_autograder import GitAutograderTestLoader + +from ..verify import verify + +REPOSITORY_NAME = "merge-undo" + +loader = GitAutograderTestLoader(__file__, REPOSITORY_NAME, verify) + + +def test_base(): + with loader.load("specs/base.yml", "start"): + pass diff --git a/merge_undo/verify.py b/merge_undo/verify.py new file mode 100644 index 0000000..1288d3d --- /dev/null +++ b/merge_undo/verify.py @@ -0,0 +1,11 @@ +from git_autograder import ( + GitAutograderOutput, + GitAutograderExercise, + GitAutograderStatus, +) + + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + # INSERT YOUR GRADING CODE HERE + + return exercise.to_output([], GitAutograderStatus.SUCCESSFUL) From 32d840bc22eb793e8ab8e3fa22b6ddae871c0b51 Mon Sep 17 00:00:00 2001 From: keaharvan Date: Sun, 16 Nov 2025 14:54:37 +0800 Subject: [PATCH 2/5] Add verification and base test --- merge_undo/download.py | 1 - merge_undo/tests/specs/base.yml | 56 +++++++++++++++++++++++++ merge_undo/tests/test_verify.py | 73 +++++++++++++++++++++++++++++++-- merge_undo/verify.py | 55 ++++++++++++++++++++++++- 4 files changed, 178 insertions(+), 7 deletions(-) diff --git a/merge_undo/download.py b/merge_undo/download.py index cc1f154..614ad4a 100644 --- a/merge_undo/download.py +++ b/merge_undo/download.py @@ -1,6 +1,5 @@ __resources__ = {"README.md": "README.md"} -from exercise_utils.cli import run_command from exercise_utils.file import create_or_update_file from exercise_utils.git import add, checkout, commit, merge_with_message from exercise_utils.gitmastery import create_start_tag diff --git a/merge_undo/tests/specs/base.yml b/merge_undo/tests/specs/base.yml index 00c3a53..e4a1315 100644 --- a/merge_undo/tests/specs/base.yml +++ b/merge_undo/tests/specs/base.yml @@ -1,6 +1,62 @@ initialization: steps: + # 1. Initialize with an empty commit - type: commit empty: true message: Empty commit id: start + + # 2. Add rick.txt and commit + - type: new-file + filename: rick.txt + contents: Scientist + - type: commit + message: Add Rick + + # 3. Add morty.txt and commit + - type: new-file + filename: morty.txt + contents: Boy + - type: commit + message: Add morty + id: morty-initial-commit # This commit serves as the base for daughter and son-in-law + + # 4. Create 'daughter' branch, checkout, add beth.txt and commit + - type: branch + branch-name: daughter + from-commit: morty-initial-commit # Branch from 'morty-initial-commit' + - type: checkout + branch-name: daughter + - type: new-file + filename: beth.txt + contents: Vet + - type: commit + message: Add Beth + + # 5. Checkout back to main, then create 'son-in-law' branch, checkout, add jerry.txt and commit + - type: checkout + branch-name: main + - type: branch + branch-name: son-in-law + from-commit: morty-initial-commit # Branch from 'morty-initial-commit' + - type: checkout + branch-name: son-in-law + - type: new-file + filename: jerry.txt + contents: Salesman + - type: commit + message: Add Jerry + + # 6. Checkout back to main, modify morty.txt and commit. + # This commit will be the final HEAD of the main branch in the "passed" state. + - type: checkout + branch-name: main + - type: modify-file + filename: morty.txt + contents: | + Boy + Grandson + - type: commit + message: Mention Morty is grandson + id: final-main-commit # This is the commit main should point to for success + diff --git a/merge_undo/tests/test_verify.py b/merge_undo/tests/test_verify.py index 45541a6..ebb4744 100644 --- a/merge_undo/tests/test_verify.py +++ b/merge_undo/tests/test_verify.py @@ -1,6 +1,16 @@ -from git_autograder import GitAutograderTestLoader +from git_autograder import GitAutograderStatus, GitAutograderTestLoader, assert_output -from ..verify import verify +from ..verify import ( + NOT_ON_MAIN, + RESET_MESSAGE, + UNCOMMITTED_CHANGES, + DETACHED_HEAD, + MERGES_NOT_UNDONE, + MAIN_WRONG_COMMIT, + DAUGHTER_BRANCH_MISSING, + SON_IN_LAW_BRANCH_MISSING, + verify, +) REPOSITORY_NAME = "merge-undo" @@ -8,5 +18,60 @@ def test_base(): - with loader.load("specs/base.yml", "start"): - pass + with loader.load("specs/base.yml", "start") as output: + assert_output(output, GitAutograderStatus.SUCCESSFUL) + + +def test_detached_head(): + with loader.load("specs/detached_head.yml", "start") as output: + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [DETACHED_HEAD, RESET_MESSAGE], + ) + + +def test_merges_not_undone(): + with loader.load("specs/merges_not_undone.yml", "start") as output: + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [MERGES_NOT_UNDONE, RESET_MESSAGE], + ) + + +def test_daughter_branch_missing(): + with loader.load("specs/daughter_branch_missing.yml", "start") as output: + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [DAUGHTER_BRANCH_MISSING, RESET_MESSAGE], + ) + + +def test_sonInLaw_branch_missing(): + with loader.load("specs/sonInLaw_branch_missing.yml", "start") as output: + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [SON_IN_LAW_BRANCH_MISSING, RESET_MESSAGE], + ) + + +def test_main_wrong_commit(): + with loader.load("specs/main_wrong_commit.yml", "start") as output: + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [MAIN_WRONG_COMMIT, RESET_MESSAGE], + ) + + +def test_uncommitted(): + with loader.load("specs/uncommitted.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [UNCOMMITTED_CHANGES]) + + +def test_not_main(): + with loader.load("specs/not_main.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [NOT_ON_MAIN]) diff --git a/merge_undo/verify.py b/merge_undo/verify.py index 1288d3d..9a063f2 100644 --- a/merge_undo/verify.py +++ b/merge_undo/verify.py @@ -4,8 +4,59 @@ GitAutograderStatus, ) +UNCOMMITTED_CHANGES = "You still have uncommitted changes. Commit them first on the appropriate branch first!" +NOT_ON_MAIN = ( + "You aren't currently on the main branch. Checkout to that branch and try again!" +) +DETACHED_HEAD = "You should not be in a detached HEAD state! Use git checkout main to get back to main" +MERGES_NOT_UNDONE = ( + "It appears the merge commits are still in the history of the 'main' branch. This shouldn't be the case" +) +MAIN_WRONG_COMMIT = "The 'main' branch is not pointing to the correct commit. It should be pointing to the commit made just before the merges." +DAUGHTER_BRANCH_MISSING = "The 'daughter' branch seems to have been deleted. It should still exist." +SON_IN_LAW_BRANCH_MISSING = "The 'son-in-law' branch seems to have been deleted. It should still exist." +RESET_MESSAGE = 'Reset the repository using "gitmastery progress reset" and start again' +SUCCESS_MESSAGE = "Great work with undoing the merges! Try listing the directory to see what has changed." + def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - # INSERT YOUR GRADING CODE HERE + """ + Verifies that the user has successfully undone the last two merges on the main branch. + """ + repo = exercise.repo.repo + + if repo.is_dirty(): + raise exercise.wrong_answer([UNCOMMITTED_CHANGES]) + + try: + if repo.active_branch.name != "main": + raise exercise.wrong_answer([NOT_ON_MAIN]) + except TypeError: + raise exercise.wrong_answer([DETACHED_HEAD]) + + if not exercise.repo.branches.has_branch("daughter"): + raise exercise.wrong_answer([DAUGHTER_BRANCH_MISSING, RESET_MESSAGE]) + + if not exercise.repo.branches.has_branch("son-in-law"): + raise exercise.wrong_answer([SON_IN_LAW_BRANCH_MISSING, RESET_MESSAGE]) + + main_branch = exercise.repo.branches.branch("main") + + main_head_commit = main_branch.latest_commit + expected_commit_message = "Mention Morty is grandson" + + if main_head_commit.commit.message.strip() != expected_commit_message: + raise exercise.wrong_answer([MAIN_WRONG_COMMIT, RESET_MESSAGE]) + + # After reset, merge messages shouldn't be in history + main_history = main_branch.commits + main_history_messages = [c.commit.message.strip() for c in main_history] + + merge_messages = ["Introduce Beth", "introduce Jerry"] + if any(msg in main_history_messages for msg in merge_messages): + raise exercise.wrong_answer([MERGES_NOT_UNDONE, RESET_MESSAGE]) - return exercise.to_output([], GitAutograderStatus.SUCCESSFUL) + return exercise.to_output( + [SUCCESS_MESSAGE], + GitAutograderStatus.SUCCESSFUL, + ) From c0ccdc8247549adb2cb9a6bfdfae9fb1f9ba2165 Mon Sep 17 00:00:00 2001 From: keaharvan Date: Sun, 16 Nov 2025 16:05:47 +0800 Subject: [PATCH 3/5] Fix verification errors --- merge_undo/tests/specs/base.yml | 18 +++---- merge_undo/tests/specs/merges_not_undone.yml | 56 ++++++++++++++++++++ merge_undo/tests/test_verify.py | 26 ++------- merge_undo/verify.py | 22 ++------ 4 files changed, 73 insertions(+), 49 deletions(-) create mode 100644 merge_undo/tests/specs/merges_not_undone.yml diff --git a/merge_undo/tests/specs/base.yml b/merge_undo/tests/specs/base.yml index e4a1315..0754f4d 100644 --- a/merge_undo/tests/specs/base.yml +++ b/merge_undo/tests/specs/base.yml @@ -19,12 +19,12 @@ initialization: contents: Boy - type: commit message: Add morty - id: morty-initial-commit # This commit serves as the base for daughter and son-in-law + id: morty-initial-commit - # 4. Create 'daughter' branch, checkout, add beth.txt and commit + # 4. Create 'daughter' branch, add beth.txt and commit - type: branch branch-name: daughter - from-commit: morty-initial-commit # Branch from 'morty-initial-commit' + from-commit: morty-initial-commit - type: checkout branch-name: daughter - type: new-file @@ -33,12 +33,12 @@ initialization: - type: commit message: Add Beth - # 5. Checkout back to main, then create 'son-in-law' branch, checkout, add jerry.txt and commit + # 5. Create 'son-in-law' branch, add jerry.txt and commit - type: checkout branch-name: main - type: branch branch-name: son-in-law - from-commit: morty-initial-commit # Branch from 'morty-initial-commit' + from-commit: morty-initial-commit - type: checkout branch-name: son-in-law - type: new-file @@ -47,16 +47,16 @@ initialization: - type: commit message: Add Jerry - # 6. Checkout back to main, modify morty.txt and commit. - # This commit will be the final HEAD of the main branch in the "passed" state. + # 6. Checkout back to main, edit morty.txt and commit. + # This is the final state of the main branch. - type: checkout branch-name: main - - type: modify-file + - type: edit-file filename: morty.txt contents: | Boy Grandson - type: commit message: Mention Morty is grandson - id: final-main-commit # This is the commit main should point to for success + id: final-main-commit diff --git a/merge_undo/tests/specs/merges_not_undone.yml b/merge_undo/tests/specs/merges_not_undone.yml new file mode 100644 index 0000000..227dbc5 --- /dev/null +++ b/merge_undo/tests/specs/merges_not_undone.yml @@ -0,0 +1,56 @@ +initialization: + steps: + - type: commit + empty: true + message: Empty commit + id: start + - type: new-file + filename: rick.txt + contents: Scientist + - type: commit + message: Add Rick + - type: new-file + filename: morty.txt + contents: Boy + - type: commit + message: Add morty + id: morty-initial-commit + - type: branch + branch-name: daughter + from-commit: morty-initial-commit + - type: checkout + branch-name: daughter + - type: new-file + filename: beth.txt + contents: Vet + - type: commit + message: Add Beth + - type: checkout + branch-name: main + - type: branch + branch-name: son-in-law + from-commit: morty-initial-commit + - type: checkout + branch-name: son-in-law + - type: new-file + filename: jerry.txt + contents: Salesman + - type: commit + message: Add Jerry + - type: checkout + branch-name: main + - type: edit-file + filename: morty.txt + contents: | + Boy + Grandson + - type: commit + message: Mention Morty is grandson + - type: merge + branch-name: daughter + message: Introduce Beth + no-ff: true + - type: merge + branch-name: son-in-law + message: introduce Jerry + no-ff: true diff --git a/merge_undo/tests/test_verify.py b/merge_undo/tests/test_verify.py index ebb4744..1c2ac4a 100644 --- a/merge_undo/tests/test_verify.py +++ b/merge_undo/tests/test_verify.py @@ -7,8 +7,6 @@ DETACHED_HEAD, MERGES_NOT_UNDONE, MAIN_WRONG_COMMIT, - DAUGHTER_BRANCH_MISSING, - SON_IN_LAW_BRANCH_MISSING, verify, ) @@ -22,15 +20,6 @@ def test_base(): assert_output(output, GitAutograderStatus.SUCCESSFUL) -def test_detached_head(): - with loader.load("specs/detached_head.yml", "start") as output: - assert_output( - output, - GitAutograderStatus.UNSUCCESSFUL, - [DETACHED_HEAD, RESET_MESSAGE], - ) - - def test_merges_not_undone(): with loader.load("specs/merges_not_undone.yml", "start") as output: assert_output( @@ -40,21 +29,12 @@ def test_merges_not_undone(): ) -def test_daughter_branch_missing(): - with loader.load("specs/daughter_branch_missing.yml", "start") as output: - assert_output( - output, - GitAutograderStatus.UNSUCCESSFUL, - [DAUGHTER_BRANCH_MISSING, RESET_MESSAGE], - ) - - -def test_sonInLaw_branch_missing(): - with loader.load("specs/sonInLaw_branch_missing.yml", "start") as output: +def test_detached_head(): + with loader.load("specs/detached_head.yml", "start") as output: assert_output( output, GitAutograderStatus.UNSUCCESSFUL, - [SON_IN_LAW_BRANCH_MISSING, RESET_MESSAGE], + [DETACHED_HEAD, RESET_MESSAGE], ) diff --git a/merge_undo/verify.py b/merge_undo/verify.py index 9a063f2..4809834 100644 --- a/merge_undo/verify.py +++ b/merge_undo/verify.py @@ -13,8 +13,6 @@ "It appears the merge commits are still in the history of the 'main' branch. This shouldn't be the case" ) MAIN_WRONG_COMMIT = "The 'main' branch is not pointing to the correct commit. It should be pointing to the commit made just before the merges." -DAUGHTER_BRANCH_MISSING = "The 'daughter' branch seems to have been deleted. It should still exist." -SON_IN_LAW_BRANCH_MISSING = "The 'son-in-law' branch seems to have been deleted. It should still exist." RESET_MESSAGE = 'Reset the repository using "gitmastery progress reset" and start again' SUCCESS_MESSAGE = "Great work with undoing the merges! Try listing the directory to see what has changed." @@ -34,29 +32,19 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: except TypeError: raise exercise.wrong_answer([DETACHED_HEAD]) - if not exercise.repo.branches.has_branch("daughter"): - raise exercise.wrong_answer([DAUGHTER_BRANCH_MISSING, RESET_MESSAGE]) - - if not exercise.repo.branches.has_branch("son-in-law"): - raise exercise.wrong_answer([SON_IN_LAW_BRANCH_MISSING, RESET_MESSAGE]) - main_branch = exercise.repo.branches.branch("main") + main_history = main_branch.commits + + if any(len(c.commit.parents) > 1 for c in main_history): + raise exercise.wrong_answer([MERGES_NOT_UNDONE, RESET_MESSAGE]) main_head_commit = main_branch.latest_commit expected_commit_message = "Mention Morty is grandson" - if main_head_commit.commit.message.strip() != expected_commit_message: raise exercise.wrong_answer([MAIN_WRONG_COMMIT, RESET_MESSAGE]) - # After reset, merge messages shouldn't be in history - main_history = main_branch.commits - main_history_messages = [c.commit.message.strip() for c in main_history] - - merge_messages = ["Introduce Beth", "introduce Jerry"] - if any(msg in main_history_messages for msg in merge_messages): - raise exercise.wrong_answer([MERGES_NOT_UNDONE, RESET_MESSAGE]) - return exercise.to_output( [SUCCESS_MESSAGE], GitAutograderStatus.SUCCESSFUL, ) + From 2dd02967d7003545cf41ae4a39151fbe9f7ac58a Mon Sep 17 00:00:00 2001 From: keaharvan Date: Sun, 16 Nov 2025 16:28:09 +0800 Subject: [PATCH 4/5] Add remaining verification checks --- merge_undo/tests/specs/base.yml | 12 ++--- merge_undo/tests/specs/detached_head.yml | 51 ++++++++++++++++++++ merge_undo/tests/specs/main_wrong_commit.yml | 42 ++++++++++++++++ merge_undo/tests/specs/not_main.yml | 50 +++++++++++++++++++ merge_undo/tests/test_verify.py | 6 --- merge_undo/verify.py | 6 +-- 6 files changed, 149 insertions(+), 18 deletions(-) create mode 100644 merge_undo/tests/specs/detached_head.yml create mode 100644 merge_undo/tests/specs/main_wrong_commit.yml create mode 100644 merge_undo/tests/specs/not_main.yml diff --git a/merge_undo/tests/specs/base.yml b/merge_undo/tests/specs/base.yml index 0754f4d..1996521 100644 --- a/merge_undo/tests/specs/base.yml +++ b/merge_undo/tests/specs/base.yml @@ -1,19 +1,18 @@ initialization: steps: - # 1. Initialize with an empty commit - type: commit empty: true message: Empty commit id: start - # 2. Add rick.txt and commit + # Add rick.txt and commit - type: new-file filename: rick.txt contents: Scientist - type: commit message: Add Rick - # 3. Add morty.txt and commit + # Add morty.txt and commit - type: new-file filename: morty.txt contents: Boy @@ -21,7 +20,7 @@ initialization: message: Add morty id: morty-initial-commit - # 4. Create 'daughter' branch, add beth.txt and commit + # Create 'daughter' branch, add beth.txt and commit - type: branch branch-name: daughter from-commit: morty-initial-commit @@ -33,7 +32,7 @@ initialization: - type: commit message: Add Beth - # 5. Create 'son-in-law' branch, add jerry.txt and commit + # Create 'son-in-law' branch, add jerry.txt and commit - type: checkout branch-name: main - type: branch @@ -47,8 +46,7 @@ initialization: - type: commit message: Add Jerry - # 6. Checkout back to main, edit morty.txt and commit. - # This is the final state of the main branch. + # Checkout back to main, edit morty.txt and commit. - type: checkout branch-name: main - type: edit-file diff --git a/merge_undo/tests/specs/detached_head.yml b/merge_undo/tests/specs/detached_head.yml new file mode 100644 index 0000000..e2991c3 --- /dev/null +++ b/merge_undo/tests/specs/detached_head.yml @@ -0,0 +1,51 @@ +initialization: + steps: + - type: commit + empty: true + message: Empty commit + id: start + - type: new-file + filename: rick.txt + contents: Scientist + - type: commit + message: Add Rick + - type: new-file + filename: morty.txt + contents: Boy + - type: commit + message: Add morty + id: morty-initial-commit + - type: branch + branch-name: daughter + from-commit: morty-initial-commit + - type: checkout + branch-name: daughter + - type: new-file + filename: beth.txt + contents: Vet + - type: commit + message: Add Beth + - type: checkout + branch-name: main + - type: branch + branch-name: son-in-law + from-commit: morty-initial-commit + - type: checkout + branch-name: son-in-law + - type: new-file + filename: jerry.txt + contents: Salesman + - type: commit + message: Add Jerry + - type: checkout + branch-name: main + - type: edit-file + filename: morty.txt + contents: | + Boy + Grandson + - type: commit + message: Mention Morty is grandson + - type: checkout + commit-hash: main + diff --git a/merge_undo/tests/specs/main_wrong_commit.yml b/merge_undo/tests/specs/main_wrong_commit.yml new file mode 100644 index 0000000..598b9c5 --- /dev/null +++ b/merge_undo/tests/specs/main_wrong_commit.yml @@ -0,0 +1,42 @@ +initialization: + steps: + - type: commit + empty: true + message: Empty commit + id: start + - type: new-file + filename: rick.txt + contents: Scientist + - type: commit + message: Add Rick + - type: new-file + filename: morty.txt + contents: Boy + - type: commit + message: Add morty + id: morty-initial-commit + - type: branch + branch-name: daughter + from-commit: morty-initial-commit + - type: checkout + branch-name: daughter + - type: new-file + filename: beth.txt + contents: Vet + - type: commit + message: Add Beth + - type: checkout + branch-name: main + - type: branch + branch-name: son-in-law + from-commit: morty-initial-commit + - type: checkout + branch-name: son-in-law + - type: new-file + filename: jerry.txt + contents: Salesman + - type: commit + message: Add Jerry + - type: checkout + branch-name: main + diff --git a/merge_undo/tests/specs/not_main.yml b/merge_undo/tests/specs/not_main.yml new file mode 100644 index 0000000..68995de --- /dev/null +++ b/merge_undo/tests/specs/not_main.yml @@ -0,0 +1,50 @@ +initialization: + steps: + - type: commit + empty: true + message: Empty commit + id: start + - type: new-file + filename: rick.txt + contents: Scientist + - type: commit + message: Add Rick + - type: new-file + filename: morty.txt + contents: Boy + - type: commit + message: Add morty + id: morty-initial-commit + - type: branch + branch-name: daughter + from-commit: morty-initial-commit + - type: checkout + branch-name: daughter + - type: new-file + filename: beth.txt + contents: Vet + - type: commit + message: Add Beth + - type: checkout + branch-name: main + - type: branch + branch-name: son-in-law + from-commit: morty-initial-commit + - type: checkout + branch-name: son-in-law + - type: new-file + filename: jerry.txt + contents: Salesman + - type: commit + message: Add Jerry + - type: checkout + branch-name: main + - type: edit-file + filename: morty.txt + contents: | + Boy + Grandson + - type: commit + message: Mention Morty is grandson + - type: checkout + branch-name: daughter diff --git a/merge_undo/tests/test_verify.py b/merge_undo/tests/test_verify.py index 1c2ac4a..9c194e9 100644 --- a/merge_undo/tests/test_verify.py +++ b/merge_undo/tests/test_verify.py @@ -3,7 +3,6 @@ from ..verify import ( NOT_ON_MAIN, RESET_MESSAGE, - UNCOMMITTED_CHANGES, DETACHED_HEAD, MERGES_NOT_UNDONE, MAIN_WRONG_COMMIT, @@ -47,11 +46,6 @@ def test_main_wrong_commit(): ) -def test_uncommitted(): - with loader.load("specs/uncommitted.yml") as output: - assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [UNCOMMITTED_CHANGES]) - - def test_not_main(): with loader.load("specs/not_main.yml") as output: assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [NOT_ON_MAIN]) diff --git a/merge_undo/verify.py b/merge_undo/verify.py index 4809834..d4598d2 100644 --- a/merge_undo/verify.py +++ b/merge_undo/verify.py @@ -4,7 +4,6 @@ GitAutograderStatus, ) -UNCOMMITTED_CHANGES = "You still have uncommitted changes. Commit them first on the appropriate branch first!" NOT_ON_MAIN = ( "You aren't currently on the main branch. Checkout to that branch and try again!" ) @@ -23,14 +22,11 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: """ repo = exercise.repo.repo - if repo.is_dirty(): - raise exercise.wrong_answer([UNCOMMITTED_CHANGES]) - try: if repo.active_branch.name != "main": raise exercise.wrong_answer([NOT_ON_MAIN]) except TypeError: - raise exercise.wrong_answer([DETACHED_HEAD]) + raise exercise.wrong_answer([DETACHED_HEAD, RESET_MESSAGE]) main_branch = exercise.repo.branches.branch("main") main_history = main_branch.commits From c85a3661c8dcbb78144fe656caeb50c0d6fe3e69 Mon Sep 17 00:00:00 2001 From: keaharvan Date: Sun, 16 Nov 2025 20:25:30 +0800 Subject: [PATCH 5/5] Add git graphs --- merge_undo/README.md | 41 +++++++++++++++++++++++++++------------- merge_undo/res/README.md | 32 +++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/merge_undo/README.md b/merge_undo/README.md index 84ad728..e330c5e 100644 --- a/merge_undo/README.md +++ b/merge_undo/README.md @@ -2,7 +2,21 @@ Scenario: You are keeping notes on the characters of a play that you are writing. In the main story line (which is in the `main` branch), you introduced two characters, Rick and Morty. You had two other characters in two separate branches `daughter` and `son-in-law`. Just now, you introduced these two characters to the main story line by merging the two branches to the `main` branch. -// insert the current revision graph here +```mermaid +gitGraph + commit id: "Add Rick" + commit id: "Add morty" + branch daughter + branch son-in-law + checkout daughter + commit id: "Add Beth" + checkout son-in-law + commit id: "Add Jerry" + checkout main + commit id: "Mention Morty is grandson" + merge daughter id: "Introduce Beth" + merge son-in-law id: "Introduce Jerry" +``` However, now you realise this is premature, and wish to undo that change. @@ -12,15 +26,16 @@ Undo the merging of branches `son-in-law` and `daughter`. The result should be as follows: -// insert the expected revision graph here - -## Hints - - - +```mermaid +gitGraph + commit id: "Add Rick" + commit id: "Add morty" + branch daughter + branch son-in-law + checkout daughter + commit id: "Add Beth" + checkout son-in-law + commit id: "Add Jerry" + checkout main + commit id: "Mention Morty is grandson" +``` diff --git a/merge_undo/res/README.md b/merge_undo/res/README.md index f9433f6..e330c5e 100644 --- a/merge_undo/res/README.md +++ b/merge_undo/res/README.md @@ -2,8 +2,40 @@ Scenario: You are keeping notes on the characters of a play that you are writing. In the main story line (which is in the `main` branch), you introduced two characters, Rick and Morty. You had two other characters in two separate branches `daughter` and `son-in-law`. Just now, you introduced these two characters to the main story line by merging the two branches to the `main` branch. +```mermaid +gitGraph + commit id: "Add Rick" + commit id: "Add morty" + branch daughter + branch son-in-law + checkout daughter + commit id: "Add Beth" + checkout son-in-law + commit id: "Add Jerry" + checkout main + commit id: "Mention Morty is grandson" + merge daughter id: "Introduce Beth" + merge son-in-law id: "Introduce Jerry" +``` + However, now you realise this is premature, and wish to undo that change. ## Task Undo the merging of branches `son-in-law` and `daughter`. + +The result should be as follows: + +```mermaid +gitGraph + commit id: "Add Rick" + commit id: "Add morty" + branch daughter + branch son-in-law + checkout daughter + commit id: "Add Beth" + checkout son-in-law + commit id: "Add Jerry" + checkout main + commit id: "Mention Morty is grandson" +```