Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
18 changes: 18 additions & 0 deletions ff_undo/.gitmastery-exercise.json
Original file line number Diff line number Diff line change
@@ -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
}
}
41 changes: 41 additions & 0 deletions ff_undo/README.md
Original file line number Diff line number Diff line change
@@ -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

<details>
<summary>Hint 1: Check your branches</summary>

Use `git branch` to see the current branches and verify `main` and `others` exist.
</details>

<details>
<summary>Hint 2: View commit history</summary>

Use `git log --oneline` on `main` to identify the merge commit that needs to be undone.
</details>

<details>
<summary>Hint 3: Undo the merge</summary>

You can undo a merge using: `git reset --hard <commit-before-merge>`
</details>
Empty file added ff_undo/__init__.py
Empty file.
39 changes: 39 additions & 0 deletions ff_undo/download.py
Original file line number Diff line number Diff line change
@@ -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)
Empty file added ff_undo/tests/__init__.py
Empty file.
19 changes: 19 additions & 0 deletions ff_undo/tests/specs/base.yml
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions ff_undo/tests/specs/branch_missing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
initialization:
steps:
- type: commit
id: start
message: "Add Rick"
- type: commit
message: "Add Morty"
15 changes: 15 additions & 0 deletions ff_undo/tests/specs/main_commits_incorrect.yml
Original file line number Diff line number Diff line change
@@ -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"
23 changes: 23 additions & 0 deletions ff_undo/tests/specs/merge_not_undone.yml
Original file line number Diff line number Diff line change
@@ -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"
15 changes: 15 additions & 0 deletions ff_undo/tests/specs/others_commits_incorrect.yml
Original file line number Diff line number Diff line change
@@ -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"
37 changes: 37 additions & 0 deletions ff_undo/tests/test_verify.py
Original file line number Diff line number Diff line change
@@ -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])
62 changes: 62 additions & 0 deletions ff_undo/verify.py
Original file line number Diff line number Diff line change
@@ -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,
)