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
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
}
}
40 changes: 40 additions & 0 deletions ff_undo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +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

<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>`
Empty file added ff_undo/__init__.py
Empty file.
36 changes: 36 additions & 0 deletions ff_undo/download.py
Original file line number Diff line number Diff line change
@@ -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)
Empty file added ff_undo/tests/__init__.py
Empty file.
23 changes: 23 additions & 0 deletions ff_undo/tests/specs/base.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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

10 changes: 10 additions & 0 deletions ff_undo/tests/specs/branch_missing.yml
Original file line number Diff line number Diff line change
@@ -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"
18 changes: 18 additions & 0 deletions ff_undo/tests/specs/main_commits_incorrect.yml
Original file line number Diff line number Diff line change
@@ -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"
26 changes: 26 additions & 0 deletions ff_undo/tests/specs/merge_not_undone.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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
no-ff: false
message: "Introduce others"
19 changes: 19 additions & 0 deletions ff_undo/tests/specs/others_commits_incorrect.yml
Original file line number Diff line number Diff line change
@@ -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"

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])
57 changes: 57 additions & 0 deletions ff_undo/verify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from git_autograder import (
GitAutograderOutput,
GitAutograderExercise,
GitAutograderStatus,
)

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:

# 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
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) != 3 or not (has_rick and has_morty):
raise exercise.wrong_answer([MAIN_COMMITS_INCORRECT])

# 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) != 6 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,
)