diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..7d13c4a Binary files /dev/null and b/.DS_Store differ 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..3eabfbc --- /dev/null +++ b/ff_undo/README.md @@ -0,0 +1,41 @@ +# 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 ` +
\ No newline at end of file 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..686c15b --- /dev/null +++ b/ff_undo/download.py @@ -0,0 +1,39 @@ +from exercise_utils.git import ( + add, + commit, + checkout, + merge_with_message, +) +from exercise_utils.file import ( + create_or_update_file, + append_to_file, +) + +def setup(verbose: bool = False): + # Create initial files and commits + create_or_update_file("rick.txt", "Scientist\n") + add(["rick.txt"], verbose) + commit("Add Rick", verbose) + + create_or_update_file("morty.txt", "Boy\n") + add(["morty.txt"], verbose) + commit("Add Morty", verbose) + + # Create and switch to branch 'others' + checkout("others", create_branch=True, verbose=verbose) + + create_or_update_file("birdperson.txt", "No job\n") + add(["birdperson.txt"], verbose) + commit("Add Birdperson", verbose) + + append_to_file("birdperson.txt", "Cyborg\n") + add(["birdperson.txt"], verbose) + commit("Add Cyborg to birdperson.txt", verbose) + + create_or_update_file("tammy.txt", "Spy\n") + add(["tammy.txt"], verbose) + commit("Add Tammy", verbose) + + # Merge back into main + checkout("main", create_branch=False, verbose=verbose) + merge_with_message("others", ff=True, message="Introduce others", verbose=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/specs/base.yml b/ff_undo/tests/specs/base.yml new file mode 100644 index 0000000..23847df --- /dev/null +++ b/ff_undo/tests/specs/base.yml @@ -0,0 +1,19 @@ +initialization: + steps: + - type: commit + id: start + 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 \ No newline at end of file diff --git a/ff_undo/tests/specs/branch_missing.yml b/ff_undo/tests/specs/branch_missing.yml new file mode 100644 index 0000000..dcc9aaa --- /dev/null +++ b/ff_undo/tests/specs/branch_missing.yml @@ -0,0 +1,7 @@ +initialization: + steps: + - type: commit + id: start + message: "Add Rick" + - type: commit + message: "Add Morty" \ No newline at end of file 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..0a6e399 --- /dev/null +++ b/ff_undo/tests/specs/main_commits_incorrect.yml @@ -0,0 +1,15 @@ +initialization: + steps: + - type: commit + id: start + 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" \ No newline at end of file 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..3778fe4 --- /dev/null +++ b/ff_undo/tests/specs/merge_not_undone.yml @@ -0,0 +1,23 @@ +initialization: + steps: + - type: commit + id: start + 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 + no-ff: false + message: "Introduce others" \ No newline at end of file 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..59fa6c6 --- /dev/null +++ b/ff_undo/tests/specs/others_commits_incorrect.yml @@ -0,0 +1,15 @@ +initialization: + steps: + - type: commit + id: start + 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" diff --git a/ff_undo/tests/test_verify.py b/ff_undo/tests/test_verify.py new file mode 100644 index 0000000..097bd76 --- /dev/null +++ b/ff_undo/tests/test_verify.py @@ -0,0 +1,37 @@ +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(): + with loader.load("specs/base.yml") as output: + assert_output(output, GitAutograderStatus.SUCCESSFUL) + + +def test_merge_not_undone(): + with loader.load("specs/merge_not_undone.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [MERGE_NOT_UNDONE]) + + +def test_branch_missing(): + with loader.load("specs/branch_missing.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [OTHERS_BRANCH_MISSING]) + + +def test_main_commits_incorrect(): + with loader.load("specs/main_commits_incorrect.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [MAIN_COMMITS_INCORRECT]) + + +def test_others_commits_incorrect(): + 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..83c73f8 --- /dev/null +++ b/ff_undo/verify.py @@ -0,0 +1,62 @@ +from git_autograder import ( + GitAutograderOutput, + GitAutograderExercise, + GitAutograderStatus, +) + +ADD_RICK = "Add Rick" +ADD_MORTY = "Add Morty" +ADD_BIRDPERSON = "Add Birdperson" +ADD_CYBORG = "Add Cyborg to birdperson.txt" +ADD_TAMMY = "Add Tammy" + +MERGE_NOT_UNDONE = ( + "You need to undo the merge." +) +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: + # Get branches + main_branch = exercise.repo.branches.branch("main") + others_branch = exercise.repo.branches.branch_or_none("others") + + # Check if branch others exists + if others_branch is None: + raise exercise.wrong_answer([OTHERS_BRANCH_MISSING]) + + # Take all commit messages on main + commit_messages_in_main = [c.commit.message.strip() for c in main_branch.commits] + + # Take all commit messages on others + commit_messages_in_others = [c.commit.message.strip() for c in others_branch.commits] + + # Check that the merge commit is not present on main + if any(msg in commit_messages_in_main for msg in [ADD_BIRDPERSON, ADD_CYBORG, ADD_TAMMY]): + raise exercise.wrong_answer([MERGE_NOT_UNDONE]) + + # Check that commits in main are only the initial 2 commits + if len(commit_messages_in_main) != 2 or not all( + msg in commit_messages_in_main for msg in [ADD_RICK, ADD_MORTY] + ): + raise exercise.wrong_answer([MAIN_COMMITS_INCORRECT]) + + # Check that commits in others are only the initial 3 commits + if len(commit_messages_in_others) != 5 or not all( + msg in commit_messages_in_others for msg in [ADD_BIRDPERSON, ADD_CYBORG, ADD_TAMMY] + ): + raise exercise.wrong_answer([OTHERS_COMMITS_INCORRECT]) + + return exercise.to_output( + ["You have successfully undone the merge of branch 'others'."], + GitAutograderStatus.SUCCESSFUL, + )