From 5d875a18fd72149e76441606116ad477e416d0d8 Mon Sep 17 00:00:00 2001 From: jiaxin Date: Wed, 26 Nov 2025 15:32:39 +0800 Subject: [PATCH 01/18] Add setup instructions --- branch_previous/download.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 branch_previous/download.py diff --git a/branch_previous/download.py b/branch_previous/download.py new file mode 100644 index 0000000..0aa394c --- /dev/null +++ b/branch_previous/download.py @@ -0,0 +1,32 @@ +from exercise_utils.file import create_or_update_file, append_to_file +from exercise_utils.git import add, commit +from exercise_utils.gitmastery import create_start_tag + +__resources__ = {"README.md": "README.md"} + + +def setup(verbose: bool = False): + # First commit: Describe night + create_or_update_file( + "story.txt", + "It was a dark and stormy night.\n" + ) + add(["story.txt"], verbose) + commit("Describe night", verbose) + + # Second commit: Describe location + append_to_file( + "story.txt", + "It was a dark and stormy night.\nI was alone in my room.\n" + ) + add(["story.txt"], verbose) + commit("Describe location", verbose) + + # Third commit: Mention noise + create_or_update_file( + "story.txt", + "It was a dark and stormy night.\nI was alone in my room.\nI heard a strange noise.\n", + ) + add(["story.txt"], verbose) + commit("Mention noise", verbose) + From bf4791dd871c854dab557fd9d23de7b4fe7a6a84 Mon Sep 17 00:00:00 2001 From: jiaxin Date: Wed, 26 Nov 2025 18:18:04 +0800 Subject: [PATCH 02/18] Add autograder logic --- branch_previous/verify.py | 133 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 branch_previous/verify.py diff --git a/branch_previous/verify.py b/branch_previous/verify.py new file mode 100644 index 0000000..e260c3c --- /dev/null +++ b/branch_previous/verify.py @@ -0,0 +1,133 @@ +from typing import Optional + +from git_autograder import ( + GitAutograderOutput, + GitAutograderExercise, + GitAutograderStatus, + GitAutograderBranch, + GitAutograderCommit, +) + +MAIN_BRANCH_CHANGED = "The 'main' branch has been changed. Please ensure it remains unchanged for this exercise." +BRANCH_MISSING = "The '{branch_name}' branch is missing." +WRONG_START = "The '{branch_name}' branch should start from the second commit (with message 'Describe location')." +WRONG_CONTENT = "The '{branch_name}' branch should have the line '{expected_content}' added to story.txt." +NO_COMMIT = "You need to commit the changes in the '{branch_name}' branch." + +def get_commit_from_message(exercise: GitAutograderExercise, message: str) -> Optional[GitAutograderCommit]: + """Find a commit with the given message.""" + commits = list(exercise.repo.repo.iter_commits(all=True)) + for commit in commits: + if message.strip() == commit.message.strip(): + return commit + return None + +def check_main_branch_unchanged(exercise: GitAutograderExercise) -> None: + """Check that the main branch has not been changed.""" + branch_helper = exercise.repo.branches + commits = branch_helper.branch("main").commits + + if len(commits) != 3: + raise exercise.wrong_answer([MAIN_BRANCH_CHANGED]) + +def check_branch_changes( + branch_name: str, + prev_commit: GitAutograderCommit, + expected_content: str, + expected_filename: str, + exercise: GitAutograderExercise + ) -> None: + """Check that the latest commit in the branch has the expected changes in the expected file.""" + + latest_commit = exercise.repo.branches.branch(branch_name).latest_commit.commit + + diff_index = latest_commit.diff(prev_commit, create_patch=True) + + # A correct answer in this exercise would only have 1 diff + if len(diff_index) != 1: + raise exercise.wrong_answer([WRONG_CONTENT.format( + branch_name=branch_name, + expected_content=expected_content + )]) + + diff = diff_index[0] + + # Remove '+' sign and any surrounding whitespace + diff_text = diff.diff.decode("utf-8")[1:].strip() + print(f"Diff text for branch {branch_name}: '{diff_text}'") + + if expected_content != diff_text: + raise exercise.wrong_answer([WRONG_CONTENT.format( + branch_name=branch_name, + expected_content=expected_content + )]) + + return + +def check_branch_structure( + branch_name: str, + expected_branch_length: int, + expected_start_commit: GitAutograderCommit, + exercise: GitAutograderExercise + ) -> None: + + # Check if branch exists + branch_helper = exercise.repo.branches + if not branch_helper.has_branch(branch_name): + raise exercise.wrong_answer([BRANCH_MISSING.format(branch_name=branch_name)]) + + # Check that user made commits in the branch + branch = branch_helper.branch(branch_name) + print("branch",branch_name, len(list(branch.commits))) + if len(list(branch.commits)) < expected_branch_length: + raise exercise.wrong_answer([NO_COMMIT.format(branch_name=branch_name)]) + + # Check that branch starts from correct commit + latest_commit = branch.latest_commit + if not latest_commit.is_child(expected_start_commit): + raise exercise.wrong_answer([WRONG_START.format(branch_name=branch_name)]) + + return + + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + # Find the "Describe location" commit + describe_location_commit = get_commit_from_message(exercise, "Describe location") + + check_branch_structure( + branch_name="visitor-line", + expected_branch_length=3, + expected_start_commit=describe_location_commit, + exercise=exercise + ) + + check_branch_structure( + branch_name="sleep-line", + expected_branch_length=3, + expected_start_commit=describe_location_commit, + exercise=exercise + ) + + check_branch_changes( + branch_name="visitor-line", + prev_commit=describe_location_commit, + expected_content="I heard someone knocking at the door.", + expected_filename="story.txt", + exercise=exercise + ) + + check_branch_changes( + branch_name="sleep-line", + prev_commit=describe_location_commit, + expected_content="I fell asleep on the couch.", + expected_filename="story.txt", + exercise=exercise + ) + + return exercise.to_output( + [ + "Excellent work! You've successfully created branches from a previous commit and explored alternative storylines!" + ], + GitAutograderStatus.SUCCESSFUL, + ) + From 3e0ed4a0c56e8062ab1483ea017a7a2959bd661d Mon Sep 17 00:00:00 2001 From: jiaxin Date: Wed, 26 Nov 2025 18:23:20 +0800 Subject: [PATCH 03/18] Add first draft of test cases --- branch_previous/.gitmastery-exercise.json | 16 +++++++ branch_previous/README.md | 34 ++++++++++++++ branch_previous/__init__.py | 0 branch_previous/tests/__init__.py | 0 branch_previous/tests/specs/base.yml | 40 +++++++++++++++++ .../tests/specs/sleep_branch_missing.yml | 31 +++++++++++++ .../tests/specs/visitor_branch_missing.yml | 31 +++++++++++++ .../tests/specs/visitor_no_commit.yml | 35 +++++++++++++++ .../tests/specs/visitor_wrong_content.yml | 39 ++++++++++++++++ .../tests/specs/visitor_wrong_start.yml | 40 +++++++++++++++++ branch_previous/tests/test_verify.py | 44 +++++++++++++++++++ 11 files changed, 310 insertions(+) create mode 100644 branch_previous/.gitmastery-exercise.json create mode 100644 branch_previous/README.md create mode 100644 branch_previous/__init__.py create mode 100644 branch_previous/tests/__init__.py create mode 100644 branch_previous/tests/specs/base.yml create mode 100644 branch_previous/tests/specs/sleep_branch_missing.yml create mode 100644 branch_previous/tests/specs/visitor_branch_missing.yml create mode 100644 branch_previous/tests/specs/visitor_no_commit.yml create mode 100644 branch_previous/tests/specs/visitor_wrong_content.yml create mode 100644 branch_previous/tests/specs/visitor_wrong_start.yml create mode 100644 branch_previous/tests/test_verify.py diff --git a/branch_previous/.gitmastery-exercise.json b/branch_previous/.gitmastery-exercise.json new file mode 100644 index 0000000..098ee28 --- /dev/null +++ b/branch_previous/.gitmastery-exercise.json @@ -0,0 +1,16 @@ +{ + "exercise_name": "branch-previous", + "tags": [ + "git-branch" + ], + "requires_git": true, + "requires_github": false, + "base_files": {}, + "exercise_repo": { + "repo_type": "local", + "repo_name": "horror-story", + "repo_title": null, + "create_fork": null, + "init": true + } +} \ No newline at end of file diff --git a/branch_previous/README.md b/branch_previous/README.md new file mode 100644 index 0000000..8e561a5 --- /dev/null +++ b/branch_previous/README.md @@ -0,0 +1,34 @@ +# branch-previous + +You are writing the outline for a story, in `story.txt`. You have written the first few steps of the storyline. You are not very happy with the way the story is progressing, and wish to explore a few alternative storylines starting from a previous step. + +## Task + +1. Start a new branch named `visitor-line`, starting from the second commit (i.e., commit with message "Describe location"). +2. Add the line `I heard someone knocking at the door.` to the `story.txt`. +3. Commit the change. You may use any suitable commit message. +4. Start a new branch named `sleep-line`, starting from the same starting point as before. +5. Add the line `I fell asleep on the couch.` to the `story.txt`. +6. Commit the change. You may use any suitable commit message. + +## Hints + +
+How do I create a branch from a specific commit? + +You can create a branch from a specific commit using: +```bash +git branch +``` + +Or you can checkout directly to a new branch from a specific commit: +```bash +git checkout -b +``` +
+ +
+How do I find the commit hash? + +Use `git log` to view the commit history and find the hash of the commit with message "Describe location". +
diff --git a/branch_previous/__init__.py b/branch_previous/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/branch_previous/tests/__init__.py b/branch_previous/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/branch_previous/tests/specs/base.yml b/branch_previous/tests/specs/base.yml new file mode 100644 index 0000000..335eb77 --- /dev/null +++ b/branch_previous/tests/specs/base.yml @@ -0,0 +1,40 @@ +initialization: + steps: + - type: commit + message: Describe night + files: + - name: story.txt + content: "It was a dark and stormy night.\n" + id: start + - type: commit + message: Describe location + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\n" + id: second_commit + - type: commit + message: Mention noise + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\nI heard a strange noise.\n" + - type: bash + runs: | + SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b visitor-line $SECOND_COMMIT + - type: commit + message: Add visitor line + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\nI heard someone knocking at the door.\n" + - type: bash + runs: | + SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b sleep-line $SECOND_COMMIT + - type: commit + message: Add sleep line + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\nI fell asleep on the couch.\n" + - type: checkout + branch-name: main + diff --git a/branch_previous/tests/specs/sleep_branch_missing.yml b/branch_previous/tests/specs/sleep_branch_missing.yml new file mode 100644 index 0000000..5b46983 --- /dev/null +++ b/branch_previous/tests/specs/sleep_branch_missing.yml @@ -0,0 +1,31 @@ +initialization: + steps: + - type: commit + message: Describe night + files: + - name: story.txt + content: "It was a dark and stormy night.\n" + id: start + - type: commit + message: Describe location + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\n" + id: second_commit + - type: commit + message: Mention noise + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\nI heard a strange noise.\n" + # Only create visitor-line branch, not sleep-line + - type: bash + runs: | + SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b visitor-line $SECOND_COMMIT + - type: commit + message: Add visitor line + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\nI heard someone knocking at the door.\n" + - type: checkout + branch-name: main diff --git a/branch_previous/tests/specs/visitor_branch_missing.yml b/branch_previous/tests/specs/visitor_branch_missing.yml new file mode 100644 index 0000000..89f6b9b --- /dev/null +++ b/branch_previous/tests/specs/visitor_branch_missing.yml @@ -0,0 +1,31 @@ +initialization: + steps: + - type: commit + message: Describe night + files: + - name: story.txt + content: "It was a dark and stormy night.\n" + id: start + - type: commit + message: Describe location + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\n" + id: second_commit + - type: commit + message: Mention noise + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\nI heard a strange noise.\n" + # Only create sleep-line branch, not visitor-line + - type: bash + runs: | + SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b sleep-line $SECOND_COMMIT + - type: commit + message: Add sleep line + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\nI fell asleep on the couch.\n" + - type: checkout + branch-name: main diff --git a/branch_previous/tests/specs/visitor_no_commit.yml b/branch_previous/tests/specs/visitor_no_commit.yml new file mode 100644 index 0000000..469e859 --- /dev/null +++ b/branch_previous/tests/specs/visitor_no_commit.yml @@ -0,0 +1,35 @@ +initialization: + steps: + - type: commit + message: Describe night + files: + - name: story.txt + content: "It was a dark and stormy night.\n" + id: start + - type: commit + message: Describe location + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\n" + id: second_commit + - type: commit + message: Mention noise + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\nI heard a strange noise.\n" + # visitor-line branch created but no commit made + - type: bash + runs: | + SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git branch visitor-line $SECOND_COMMIT + - type: bash + runs: | + SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b sleep-line $SECOND_COMMIT + - type: commit + message: Add sleep line + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\nI fell asleep on the couch.\n" + - type: checkout + branch-name: main diff --git a/branch_previous/tests/specs/visitor_wrong_content.yml b/branch_previous/tests/specs/visitor_wrong_content.yml new file mode 100644 index 0000000..7347740 --- /dev/null +++ b/branch_previous/tests/specs/visitor_wrong_content.yml @@ -0,0 +1,39 @@ +initialization: + steps: + - type: commit + message: Describe night + files: + - name: story.txt + content: "It was a dark and stormy night.\n" + id: start + - type: commit + message: Describe location + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\n" + id: second_commit + - type: commit + message: Mention noise + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\nI heard a strange noise.\n" + - type: bash + runs: | + SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b visitor-line $SECOND_COMMIT + - type: commit + message: Add visitor line + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\nWrong content here.\n" + - type: bash + runs: | + SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b sleep-line $SECOND_COMMIT + - type: commit + message: Add sleep line + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\nI fell asleep on the couch.\n" + - type: checkout + branch-name: main diff --git a/branch_previous/tests/specs/visitor_wrong_start.yml b/branch_previous/tests/specs/visitor_wrong_start.yml new file mode 100644 index 0000000..b3514db --- /dev/null +++ b/branch_previous/tests/specs/visitor_wrong_start.yml @@ -0,0 +1,40 @@ +initialization: + steps: + - type: commit + message: Describe night + files: + - name: story.txt + content: "It was a dark and stormy night.\n" + id: start + - type: commit + message: Describe location + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\n" + id: second_commit + - type: commit + message: Mention noise + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\nI heard a strange noise.\n" + # visitor-line starts from wrong commit (first instead of second) + - type: bash + runs: | + FIRST_COMMIT=$(git log --all --grep="Describe night" --format=%H -n 1) + git checkout -b visitor-line $FIRST_COMMIT + - type: commit + message: Add visitor line + files: + - name: story.txt + content: "It was a dark and stormy night.\nI heard someone knocking at the door.\n" + - type: bash + runs: | + SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b sleep-line $SECOND_COMMIT + - type: commit + message: Add sleep line + files: + - name: story.txt + content: "It was a dark and stormy night.\nI was alone in my room.\nI fell asleep on the couch.\n" + - type: checkout + branch-name: main diff --git a/branch_previous/tests/test_verify.py b/branch_previous/tests/test_verify.py new file mode 100644 index 0000000..1fc3193 --- /dev/null +++ b/branch_previous/tests/test_verify.py @@ -0,0 +1,44 @@ +from git_autograder import GitAutograderTestLoader, assert_output +from git_autograder.status import GitAutograderStatus + +from ..verify import ( + BRANCH_MISSING, + NO_COMMIT, + WRONG_CONTENT, + WRONG_START, + verify, +) + +REPOSITORY_NAME = "branch-previous" + +loader = GitAutograderTestLoader(__file__, REPOSITORY_NAME, verify) + + +def test_base(): + with loader.load("specs/base.yml") as output: + assert_output(output, GitAutograderStatus.SUCCESSFUL) + + +def test_visitor_branch_missing(): + with loader.load("specs/visitor_branch_missing.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [BRANCH_MISSING.format(branch_name="visitor-line")]) + + +def test_sleep_branch_missing(): + with loader.load("specs/sleep_branch_missing.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [BRANCH_MISSING.format(branch_name="sleep-line")]) + + +def test_visitor_wrong_start(): + with loader.load("specs/visitor_wrong_start.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [WRONG_START.format(branch_name="visitor-line")]) + + +def test_visitor_wrong_content(): + with loader.load("specs/visitor_wrong_content.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [WRONG_CONTENT.format(branch_name="visitor-line", expected_content="I heard someone knocking at the door.")]) + + +def test_visitor_no_commit(): + with loader.load("specs/visitor_no_commit.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [NO_COMMIT.format(branch_name="visitor-line")]) From d12f986e149038762a2c4f402b43734363e8ded1 Mon Sep 17 00:00:00 2001 From: jiaxin Date: Wed, 26 Nov 2025 18:38:17 +0800 Subject: [PATCH 04/18] Edit autograding logic --- branch_previous/tests/test_verify.py | 2 +- branch_previous/verify.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/branch_previous/tests/test_verify.py b/branch_previous/tests/test_verify.py index 1fc3193..970d7f5 100644 --- a/branch_previous/tests/test_verify.py +++ b/branch_previous/tests/test_verify.py @@ -41,4 +41,4 @@ def test_visitor_wrong_content(): def test_visitor_no_commit(): with loader.load("specs/visitor_no_commit.yml") as output: - assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [NO_COMMIT.format(branch_name="visitor-line")]) + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [WRONG_START.format(branch_name="visitor-line")]) diff --git a/branch_previous/verify.py b/branch_previous/verify.py index e260c3c..bd0d90e 100644 --- a/branch_previous/verify.py +++ b/branch_previous/verify.py @@ -38,7 +38,7 @@ def check_branch_changes( exercise: GitAutograderExercise ) -> None: """Check that the latest commit in the branch has the expected changes in the expected file.""" - + print(f"Checking changes in branch {branch_name}...") latest_commit = exercise.repo.branches.branch(branch_name).latest_commit.commit diff_index = latest_commit.diff(prev_commit, create_patch=True) @@ -78,12 +78,12 @@ def check_branch_structure( # Check that user made commits in the branch branch = branch_helper.branch(branch_name) - print("branch",branch_name, len(list(branch.commits))) - if len(list(branch.commits)) < expected_branch_length: - raise exercise.wrong_answer([NO_COMMIT.format(branch_name=branch_name)]) + latest_commit = branch.latest_commit + print("branch", branch_name, len(list(branch.commits))) + # if latest_commit == expected_start_commit: + # raise exercise.wrong_answer([WRONG_START.format(branch_name=branch_name)]) # Check that branch starts from correct commit - latest_commit = branch.latest_commit if not latest_commit.is_child(expected_start_commit): raise exercise.wrong_answer([WRONG_START.format(branch_name=branch_name)]) From fa1466820e4427f0f47f9652df28047db3dbec84 Mon Sep 17 00:00:00 2001 From: jia xin Date: Wed, 26 Nov 2025 23:19:39 +0800 Subject: [PATCH 05/18] Remove checkout main at the end of tests --- branch_previous/tests/specs/base.yml | 3 --- branch_previous/tests/specs/sleep_branch_missing.yml | 2 -- branch_previous/tests/specs/visitor_branch_missing.yml | 2 -- branch_previous/tests/specs/visitor_no_commit.yml | 2 -- branch_previous/tests/specs/visitor_wrong_content.yml | 2 -- branch_previous/tests/specs/visitor_wrong_start.yml | 2 -- 6 files changed, 13 deletions(-) diff --git a/branch_previous/tests/specs/base.yml b/branch_previous/tests/specs/base.yml index 335eb77..75ffc4f 100644 --- a/branch_previous/tests/specs/base.yml +++ b/branch_previous/tests/specs/base.yml @@ -35,6 +35,3 @@ initialization: files: - name: story.txt content: "It was a dark and stormy night.\nI was alone in my room.\nI fell asleep on the couch.\n" - - type: checkout - branch-name: main - diff --git a/branch_previous/tests/specs/sleep_branch_missing.yml b/branch_previous/tests/specs/sleep_branch_missing.yml index 5b46983..349d264 100644 --- a/branch_previous/tests/specs/sleep_branch_missing.yml +++ b/branch_previous/tests/specs/sleep_branch_missing.yml @@ -27,5 +27,3 @@ initialization: files: - name: story.txt content: "It was a dark and stormy night.\nI was alone in my room.\nI heard someone knocking at the door.\n" - - type: checkout - branch-name: main diff --git a/branch_previous/tests/specs/visitor_branch_missing.yml b/branch_previous/tests/specs/visitor_branch_missing.yml index 89f6b9b..e02b9a8 100644 --- a/branch_previous/tests/specs/visitor_branch_missing.yml +++ b/branch_previous/tests/specs/visitor_branch_missing.yml @@ -27,5 +27,3 @@ initialization: files: - name: story.txt content: "It was a dark and stormy night.\nI was alone in my room.\nI fell asleep on the couch.\n" - - type: checkout - branch-name: main diff --git a/branch_previous/tests/specs/visitor_no_commit.yml b/branch_previous/tests/specs/visitor_no_commit.yml index 469e859..efe2252 100644 --- a/branch_previous/tests/specs/visitor_no_commit.yml +++ b/branch_previous/tests/specs/visitor_no_commit.yml @@ -31,5 +31,3 @@ initialization: files: - name: story.txt content: "It was a dark and stormy night.\nI was alone in my room.\nI fell asleep on the couch.\n" - - type: checkout - branch-name: main diff --git a/branch_previous/tests/specs/visitor_wrong_content.yml b/branch_previous/tests/specs/visitor_wrong_content.yml index 7347740..18695b5 100644 --- a/branch_previous/tests/specs/visitor_wrong_content.yml +++ b/branch_previous/tests/specs/visitor_wrong_content.yml @@ -35,5 +35,3 @@ initialization: files: - name: story.txt content: "It was a dark and stormy night.\nI was alone in my room.\nI fell asleep on the couch.\n" - - type: checkout - branch-name: main diff --git a/branch_previous/tests/specs/visitor_wrong_start.yml b/branch_previous/tests/specs/visitor_wrong_start.yml index b3514db..6102bb4 100644 --- a/branch_previous/tests/specs/visitor_wrong_start.yml +++ b/branch_previous/tests/specs/visitor_wrong_start.yml @@ -36,5 +36,3 @@ initialization: files: - name: story.txt content: "It was a dark and stormy night.\nI was alone in my room.\nI fell asleep on the couch.\n" - - type: checkout - branch-name: main From cb18aa35b96b1ef7311b0f03152b1eb679be4611 Mon Sep 17 00:00:00 2001 From: jia xin Date: Wed, 26 Nov 2025 23:50:21 +0800 Subject: [PATCH 06/18] Fix write to file functionality in tests --- branch_previous/tests/specs/base.yml | 42 +++++++++++------- .../tests/specs/sleep_branch_missing.yml | 35 +++++++++------ .../tests/specs/visitor_branch_missing.yml | 17 ++++++++ .../tests/specs/visitor_no_commit.yml | 36 ++++++++++------ .../tests/specs/visitor_wrong_content.yml | 43 ++++++++++++------- .../tests/specs/visitor_wrong_start.yml | 43 ++++++++++++------- branch_previous/verify.py | 35 ++++++--------- 7 files changed, 160 insertions(+), 91 deletions(-) diff --git a/branch_previous/tests/specs/base.yml b/branch_previous/tests/specs/base.yml index 75ffc4f..f43e8e7 100644 --- a/branch_previous/tests/specs/base.yml +++ b/branch_previous/tests/specs/base.yml @@ -1,37 +1,49 @@ initialization: steps: + - type: bash + runs: echo "It was a dark and stormy night." > story.txt + - type: add + files: + - story.txt - type: commit message: Describe night - files: - - name: story.txt - content: "It was a dark and stormy night.\n" id: start + - type: bash + runs: echo "I was alone in my room." >> story.txt + - type: add + files: + - story.txt - type: commit message: Describe location - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\n" + empty: false id: second_commit + - type: bash + runs: echo "I heard a strange noise." >> story.txt + - type: add + files: + - story.txt - type: commit message: Mention noise - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\nI heard a strange noise.\n" + empty: false - type: bash runs: | SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) git checkout -b visitor-line $SECOND_COMMIT + echo "I heard someone knocking at the door." >> story.txt + - type: add + files: + - story.txt - type: commit message: Add visitor line - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\nI heard someone knocking at the door.\n" + empty: false - type: bash runs: | SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) git checkout -b sleep-line $SECOND_COMMIT + echo "I fell asleep on the couch." >> story.txt + - type: add + files: + - story.txt - type: commit message: Add sleep line - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\nI fell asleep on the couch.\n" + empty: false diff --git a/branch_previous/tests/specs/sleep_branch_missing.yml b/branch_previous/tests/specs/sleep_branch_missing.yml index 349d264..b3c410b 100644 --- a/branch_previous/tests/specs/sleep_branch_missing.yml +++ b/branch_previous/tests/specs/sleep_branch_missing.yml @@ -1,29 +1,40 @@ initialization: steps: + - type: bash + runs: echo "It was a dark and stormy night." > story.txt + - type: add + files: + - story.txt - type: commit message: Describe night - files: - - name: story.txt - content: "It was a dark and stormy night.\n" + empty: false id: start + - type: bash + runs: echo "I was alone in my room." >> story.txt + - type: add + files: + - story.txt - type: commit message: Describe location - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\n" + empty: false id: second_commit + - type: bash + runs: echo "I heard a strange noise." >> story.txt + - type: add + files: + - story.txt - type: commit message: Mention noise - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\nI heard a strange noise.\n" + empty: false # Only create visitor-line branch, not sleep-line - type: bash runs: | SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) git checkout -b visitor-line $SECOND_COMMIT + echo "I heard someone knocking at the door." >> story.txt + - type: add + files: + - story.txt - type: commit message: Add visitor line - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\nI heard someone knocking at the door.\n" + empty: false diff --git a/branch_previous/tests/specs/visitor_branch_missing.yml b/branch_previous/tests/specs/visitor_branch_missing.yml index e02b9a8..95599b5 100644 --- a/branch_previous/tests/specs/visitor_branch_missing.yml +++ b/branch_previous/tests/specs/visitor_branch_missing.yml @@ -1,17 +1,31 @@ initialization: steps: + - type: bash + runs: echo "It was a dark and stormy night." > story.txt + - type: add + files: + - story.txt - type: commit message: Describe night files: - name: story.txt content: "It was a dark and stormy night.\n" id: start + - type: bash + runs: | + echo "It was a dark and stormy night." > story.txt + echo "I was alone in my room." >> story.txt - type: commit message: Describe location files: - name: story.txt content: "It was a dark and stormy night.\nI was alone in my room.\n" id: second_commit + - type: bash + runs: | + echo "It was a dark and stormy night." > story.txt + echo "I was alone in my room." >> story.txt + echo "I heard a strange noise." >> story.txt - type: commit message: Mention noise files: @@ -22,6 +36,9 @@ initialization: runs: | SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) git checkout -b sleep-line $SECOND_COMMIT + echo "It was a dark and stormy night." > story.txt + echo "I was alone in my room." >> story.txt + echo "I fell asleep on the couch." >> story.txt - type: commit message: Add sleep line files: diff --git a/branch_previous/tests/specs/visitor_no_commit.yml b/branch_previous/tests/specs/visitor_no_commit.yml index efe2252..846a4ec 100644 --- a/branch_previous/tests/specs/visitor_no_commit.yml +++ b/branch_previous/tests/specs/visitor_no_commit.yml @@ -1,33 +1,45 @@ initialization: steps: + - type: bash + runs: echo "It was a dark and stormy night." > story.txt + - type: add + files: + - story.txt - type: commit message: Describe night - files: - - name: story.txt - content: "It was a dark and stormy night.\n" + empty: false id: start + - type: bash + runs: echo "I was alone in my room." >> story.txt + - type: add + files: + - story.txt - type: commit message: Describe location - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\n" + empty: false id: second_commit + - type: bash + runs: echo "I heard a strange noise." >> story.txt + - type: add + files: + - story.txt - type: commit message: Mention noise - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\nI heard a strange noise.\n" + empty: false # visitor-line branch created but no commit made - type: bash runs: | SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) git branch visitor-line $SECOND_COMMIT + # sleep-line branch created with commit - type: bash runs: | SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) git checkout -b sleep-line $SECOND_COMMIT + echo "I fell asleep on the couch." >> story.txt + - type: add + files: + - story.txt - type: commit message: Add sleep line - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\nI fell asleep on the couch.\n" + empty: false diff --git a/branch_previous/tests/specs/visitor_wrong_content.yml b/branch_previous/tests/specs/visitor_wrong_content.yml index 18695b5..7f6aa87 100644 --- a/branch_previous/tests/specs/visitor_wrong_content.yml +++ b/branch_previous/tests/specs/visitor_wrong_content.yml @@ -1,37 +1,50 @@ initialization: steps: + - type: bash + runs: echo "It was a dark and stormy night." > story.txt + - type: add + files: + - story.txt - type: commit message: Describe night - files: - - name: story.txt - content: "It was a dark and stormy night.\n" + empty: false id: start + - type: bash + runs: echo "I was alone in my room." >> story.txt + - type: add + files: + - story.txt - type: commit message: Describe location - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\n" + empty: false id: second_commit + - type: bash + runs: echo "I heard a strange noise." >> story.txt + - type: add + files: + - story.txt - type: commit message: Mention noise - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\nI heard a strange noise.\n" + empty: false - type: bash runs: | SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) git checkout -b visitor-line $SECOND_COMMIT + echo "Wrong content here." >> story.txt + - type: add + files: + - story.txt - type: commit message: Add visitor line - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\nWrong content here.\n" + empty: false - type: bash runs: | SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) git checkout -b sleep-line $SECOND_COMMIT + echo "I fell asleep on my couch." >> story.txt + - type: add + files: + - story.txt - type: commit message: Add sleep line - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\nI fell asleep on the couch.\n" + empty: false diff --git a/branch_previous/tests/specs/visitor_wrong_start.yml b/branch_previous/tests/specs/visitor_wrong_start.yml index 6102bb4..3008520 100644 --- a/branch_previous/tests/specs/visitor_wrong_start.yml +++ b/branch_previous/tests/specs/visitor_wrong_start.yml @@ -1,38 +1,51 @@ initialization: steps: + - type: bash + runs: echo "It was a dark and stormy night." > story.txt + - type: add + files: + - story.txt - type: commit message: Describe night - files: - - name: story.txt - content: "It was a dark and stormy night.\n" + empty: false id: start + - type: bash + runs: echo "I was alone in my room." >> story.txt + - type: add + files: + - story.txt - type: commit message: Describe location - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\n" + empty: false id: second_commit + - type: bash + runs: echo "I heard a strange noise." >> story.txt + - type: add + files: + - story.txt - type: commit message: Mention noise - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\nI heard a strange noise.\n" + empty: false # visitor-line starts from wrong commit (first instead of second) - type: bash runs: | FIRST_COMMIT=$(git log --all --grep="Describe night" --format=%H -n 1) git checkout -b visitor-line $FIRST_COMMIT + echo "I heard someone knocking at the door." >> story.txt + - type: add + files: + - story.txt - type: commit message: Add visitor line - files: - - name: story.txt - content: "It was a dark and stormy night.\nI heard someone knocking at the door.\n" + empty: false - type: bash runs: | SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) git checkout -b sleep-line $SECOND_COMMIT + echo "I fell asleep on the couch." >> story.txt + - type: add + files: + - story.txt - type: commit message: Add sleep line - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\nI fell asleep on the couch.\n" + empty: false diff --git a/branch_previous/verify.py b/branch_previous/verify.py index bd0d90e..4bde41f 100644 --- a/branch_previous/verify.py +++ b/branch_previous/verify.py @@ -39,29 +39,20 @@ def check_branch_changes( ) -> None: """Check that the latest commit in the branch has the expected changes in the expected file.""" print(f"Checking changes in branch {branch_name}...") - latest_commit = exercise.repo.branches.branch(branch_name).latest_commit.commit + latest_commit = exercise.repo.branches.branch(branch_name).latest_commit + with latest_commit.file("story.txt") as content: + print(content) + if not content: + raise exercise.wrong_answer([WRONG_CONTENT.format( + branch_name=branch_name, + expected_content=expected_content + )]) + if expected_content not in content: + raise exercise.wrong_answer([WRONG_CONTENT.format( + branch_name=branch_name, + expected_content=expected_content + )]) - diff_index = latest_commit.diff(prev_commit, create_patch=True) - - # A correct answer in this exercise would only have 1 diff - if len(diff_index) != 1: - raise exercise.wrong_answer([WRONG_CONTENT.format( - branch_name=branch_name, - expected_content=expected_content - )]) - - diff = diff_index[0] - - # Remove '+' sign and any surrounding whitespace - diff_text = diff.diff.decode("utf-8")[1:].strip() - print(f"Diff text for branch {branch_name}: '{diff_text}'") - - if expected_content != diff_text: - raise exercise.wrong_answer([WRONG_CONTENT.format( - branch_name=branch_name, - expected_content=expected_content - )]) - return def check_branch_structure( From 8ffb7396704144ba913bb6026cb3eecc6fe7718a Mon Sep 17 00:00:00 2001 From: jia xin Date: Wed, 26 Nov 2025 23:55:59 +0800 Subject: [PATCH 07/18] Change expected error for missing commit --- branch_previous/tests/test_verify.py | 3 +-- branch_previous/verify.py | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/branch_previous/tests/test_verify.py b/branch_previous/tests/test_verify.py index 970d7f5..24a1caf 100644 --- a/branch_previous/tests/test_verify.py +++ b/branch_previous/tests/test_verify.py @@ -3,7 +3,6 @@ from ..verify import ( BRANCH_MISSING, - NO_COMMIT, WRONG_CONTENT, WRONG_START, verify, @@ -41,4 +40,4 @@ def test_visitor_wrong_content(): def test_visitor_no_commit(): with loader.load("specs/visitor_no_commit.yml") as output: - assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [WRONG_START.format(branch_name="visitor-line")]) + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [WRONG_CONTENT.format(branch_name="visitor-line", expected_content="I heard someone knocking at the door.")]) diff --git a/branch_previous/verify.py b/branch_previous/verify.py index 4bde41f..c033bcd 100644 --- a/branch_previous/verify.py +++ b/branch_previous/verify.py @@ -4,7 +4,6 @@ GitAutograderOutput, GitAutograderExercise, GitAutograderStatus, - GitAutograderBranch, GitAutograderCommit, ) @@ -12,7 +11,6 @@ BRANCH_MISSING = "The '{branch_name}' branch is missing." WRONG_START = "The '{branch_name}' branch should start from the second commit (with message 'Describe location')." WRONG_CONTENT = "The '{branch_name}' branch should have the line '{expected_content}' added to story.txt." -NO_COMMIT = "You need to commit the changes in the '{branch_name}' branch." def get_commit_from_message(exercise: GitAutograderExercise, message: str) -> Optional[GitAutograderCommit]: """Find a commit with the given message.""" @@ -70,7 +68,7 @@ def check_branch_structure( # Check that user made commits in the branch branch = branch_helper.branch(branch_name) latest_commit = branch.latest_commit - print("branch", branch_name, len(list(branch.commits))) + # if latest_commit == expected_start_commit: # raise exercise.wrong_answer([WRONG_START.format(branch_name=branch_name)]) From aa28ab822a3ce7558c4ba4113c92fc67511457a9 Mon Sep 17 00:00:00 2001 From: jia xin Date: Sat, 29 Nov 2025 10:45:12 +0800 Subject: [PATCH 08/18] Add test for branch from third commit --- ..._commit.yml => visitor_commit_missing.yml} | 0 ...l => visitor_wrong_start_first_commit.yml} | 0 .../specs/visitor_wrong_third_commit.yml | 51 ++++++++++++++++++ branch_previous/tests/test_verify.py | 16 ++++-- branch_previous/verify.py | 52 ++++++------------- 5 files changed, 77 insertions(+), 42 deletions(-) rename branch_previous/tests/specs/{visitor_no_commit.yml => visitor_commit_missing.yml} (100%) rename branch_previous/tests/specs/{visitor_wrong_start.yml => visitor_wrong_start_first_commit.yml} (100%) create mode 100644 branch_previous/tests/specs/visitor_wrong_third_commit.yml diff --git a/branch_previous/tests/specs/visitor_no_commit.yml b/branch_previous/tests/specs/visitor_commit_missing.yml similarity index 100% rename from branch_previous/tests/specs/visitor_no_commit.yml rename to branch_previous/tests/specs/visitor_commit_missing.yml diff --git a/branch_previous/tests/specs/visitor_wrong_start.yml b/branch_previous/tests/specs/visitor_wrong_start_first_commit.yml similarity index 100% rename from branch_previous/tests/specs/visitor_wrong_start.yml rename to branch_previous/tests/specs/visitor_wrong_start_first_commit.yml diff --git a/branch_previous/tests/specs/visitor_wrong_third_commit.yml b/branch_previous/tests/specs/visitor_wrong_third_commit.yml new file mode 100644 index 0000000..fd4f7d7 --- /dev/null +++ b/branch_previous/tests/specs/visitor_wrong_third_commit.yml @@ -0,0 +1,51 @@ +initialization: + steps: + - type: bash + runs: echo "It was a dark and stormy night." > story.txt + - type: add + files: + - story.txt + - type: commit + message: Describe night + empty: false + id: start + - type: bash + runs: echo "I was alone in my room." >> story.txt + - type: add + files: + - story.txt + - type: commit + message: Describe location + empty: false + id: second_commit + - type: bash + runs: echo "I heard a strange noise." >> story.txt + - type: add + files: + - story.txt + - type: commit + message: Mention noise + empty: false + # visitor-line starts from wrong commit (third instead of second) + - type: bash + runs: | + THIRD_COMMIT=$(git log --all --grep="Mention noise" --format=%H -n 1) + git checkout -b visitor-line $THIRD_COMMIT + echo "I heard someone knocking at the door." >> story.txt + - type: add + files: + - story.txt + - type: commit + message: Add visitor line + empty: false + - type: bash + runs: | + SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b sleep-line $SECOND_COMMIT + echo "I fell asleep on the couch." >> story.txt + - type: add + files: + - story.txt + - type: commit + message: Add sleep line + empty: false diff --git a/branch_previous/tests/test_verify.py b/branch_previous/tests/test_verify.py index 24a1caf..8aded0c 100644 --- a/branch_previous/tests/test_verify.py +++ b/branch_previous/tests/test_verify.py @@ -5,6 +5,7 @@ BRANCH_MISSING, WRONG_CONTENT, WRONG_START, + COMMIT_MISSING, verify, ) @@ -28,8 +29,13 @@ def test_sleep_branch_missing(): assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [BRANCH_MISSING.format(branch_name="sleep-line")]) -def test_visitor_wrong_start(): - with loader.load("specs/visitor_wrong_start.yml") as output: +def test_visitor_wrong_start_first_commit(): + with loader.load("specs/visitor_wrong_start_first_commit.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [WRONG_START.format(branch_name="visitor-line")]) + + +def test_visitor_wrong_start_third_commit(): + with loader.load("specs/visitor_wrong_start_third_commit.yml") as output: assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [WRONG_START.format(branch_name="visitor-line")]) @@ -38,6 +44,6 @@ def test_visitor_wrong_content(): assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [WRONG_CONTENT.format(branch_name="visitor-line", expected_content="I heard someone knocking at the door.")]) -def test_visitor_no_commit(): - with loader.load("specs/visitor_no_commit.yml") as output: - assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [WRONG_CONTENT.format(branch_name="visitor-line", expected_content="I heard someone knocking at the door.")]) +def test_visitor_commit_missing(): + with loader.load("specs/visitor_commit_missing.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [COMMIT_MISSING.format(branch_name="visitor-line")]) diff --git a/branch_previous/verify.py b/branch_previous/verify.py index c033bcd..2369fcf 100644 --- a/branch_previous/verify.py +++ b/branch_previous/verify.py @@ -7,44 +7,29 @@ GitAutograderCommit, ) -MAIN_BRANCH_CHANGED = "The 'main' branch has been changed. Please ensure it remains unchanged for this exercise." +from git.objects.commit import Commit + BRANCH_MISSING = "The '{branch_name}' branch is missing." +COMMIT_MISSING = "No commits were made in the '{branch_name}' branch." WRONG_START = "The '{branch_name}' branch should start from the second commit (with message 'Describe location')." WRONG_CONTENT = "The '{branch_name}' branch should have the line '{expected_content}' added to story.txt." -def get_commit_from_message(exercise: GitAutograderExercise, message: str) -> Optional[GitAutograderCommit]: +def get_commit_from_message(exercise: GitAutograderExercise, message: str) -> Optional[Commit]: """Find a commit with the given message.""" commits = list(exercise.repo.repo.iter_commits(all=True)) for commit in commits: if message.strip() == commit.message.strip(): return commit return None - -def check_main_branch_unchanged(exercise: GitAutograderExercise) -> None: - """Check that the main branch has not been changed.""" - branch_helper = exercise.repo.branches - commits = branch_helper.branch("main").commits - if len(commits) != 3: - raise exercise.wrong_answer([MAIN_BRANCH_CHANGED]) - -def check_branch_changes( +def check_file_changes( branch_name: str, - prev_commit: GitAutograderCommit, expected_content: str, - expected_filename: str, exercise: GitAutograderExercise ) -> None: - """Check that the latest commit in the branch has the expected changes in the expected file.""" - print(f"Checking changes in branch {branch_name}...") + latest_commit = exercise.repo.branches.branch(branch_name).latest_commit with latest_commit.file("story.txt") as content: - print(content) - if not content: - raise exercise.wrong_answer([WRONG_CONTENT.format( - branch_name=branch_name, - expected_content=expected_content - )]) if expected_content not in content: raise exercise.wrong_answer([WRONG_CONTENT.format( branch_name=branch_name, @@ -55,8 +40,7 @@ def check_branch_changes( def check_branch_structure( branch_name: str, - expected_branch_length: int, - expected_start_commit: GitAutograderCommit, + expected_start_commit: Commit, exercise: GitAutograderExercise ) -> None: @@ -65,51 +49,45 @@ def check_branch_structure( if not branch_helper.has_branch(branch_name): raise exercise.wrong_answer([BRANCH_MISSING.format(branch_name=branch_name)]) - # Check that user made commits in the branch branch = branch_helper.branch(branch_name) - latest_commit = branch.latest_commit + latest_commit = branch.latest_commit.commit - # if latest_commit == expected_start_commit: - # raise exercise.wrong_answer([WRONG_START.format(branch_name=branch_name)]) + # Check that user made commits in the branch + if latest_commit == expected_start_commit: + raise exercise.wrong_answer([COMMIT_MISSING.format(branch_name=branch_name)]) # Check that branch starts from correct commit - if not latest_commit.is_child(expected_start_commit): + if not expected_start_commit in latest_commit.parents: raise exercise.wrong_answer([WRONG_START.format(branch_name=branch_name)]) return def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - # Find the "Describe location" commit + describe_location_commit = get_commit_from_message(exercise, "Describe location") check_branch_structure( branch_name="visitor-line", - expected_branch_length=3, expected_start_commit=describe_location_commit, exercise=exercise ) check_branch_structure( branch_name="sleep-line", - expected_branch_length=3, expected_start_commit=describe_location_commit, exercise=exercise ) - check_branch_changes( + check_file_changes( branch_name="visitor-line", - prev_commit=describe_location_commit, expected_content="I heard someone knocking at the door.", - expected_filename="story.txt", exercise=exercise ) - check_branch_changes( + check_file_changes( branch_name="sleep-line", - prev_commit=describe_location_commit, expected_content="I fell asleep on the couch.", - expected_filename="story.txt", exercise=exercise ) From 58ffe266e11a5f430433bb027fa523d6b319384b Mon Sep 17 00:00:00 2001 From: jia xin Date: Sat, 29 Nov 2025 17:58:42 +0800 Subject: [PATCH 09/18] Clean up code --- branch_previous/README.md | 34 ++----- branch_previous/download.py | 23 +++-- branch_previous/res/expected-tree.png | Bin 0 -> 57334 bytes branch_previous/tests/specs/base.yml | 14 +-- ...h_missing.yml => sleep_missing_branch.yml} | 8 +- .../tests/specs/sleep_wrong_content.yml | 50 +++++++++ .../tests/specs/visitor_branch_missing.yml | 46 --------- .../tests/specs/visitor_missing_branch.yml | 32 ++++++ ...missing.yml => visitor_missing_commit.yml} | 12 +-- .../tests/specs/visitor_wrong_content.yml | 14 +-- .../visitor_wrong_start_first_commit.yml | 10 +- ...l => visitor_wrong_start_third_commit.yml} | 10 +- branch_previous/tests/test_verify.py | 75 +++++++++++--- branch_previous/verify.py | 96 ++++++++---------- 14 files changed, 241 insertions(+), 183 deletions(-) create mode 100644 branch_previous/res/expected-tree.png rename branch_previous/tests/specs/{sleep_branch_missing.yml => sleep_missing_branch.yml} (79%) create mode 100644 branch_previous/tests/specs/sleep_wrong_content.yml delete mode 100644 branch_previous/tests/specs/visitor_branch_missing.yml create mode 100644 branch_previous/tests/specs/visitor_missing_branch.yml rename branch_previous/tests/specs/{visitor_commit_missing.yml => visitor_missing_commit.yml} (70%) rename branch_previous/tests/specs/{visitor_wrong_third_commit.yml => visitor_wrong_start_third_commit.yml} (82%) diff --git a/branch_previous/README.md b/branch_previous/README.md index 8e561a5..e3cc83b 100644 --- a/branch_previous/README.md +++ b/branch_previous/README.md @@ -1,34 +1,20 @@ # branch-previous -You are writing the outline for a story, in `story.txt`. You have written the first few steps of the storyline. You are not very happy with the way the story is progressing, and wish to explore a few alternative storylines starting from a previous step. +You are writing the outline for a story, in `story.txt`. + +You have written the first few steps of the storyline. + +You are not very happy with the way the story is progressing, and wish to explore a few alternative storylines starting from a previous step. ## Task -1. Start a new branch named `visitor-line`, starting from the second commit (i.e., commit with message "Describe location"). -2. Add the line `I heard someone knocking at the door.` to the `story.txt`. +1. Start a new branch named `visitor-line`, starting from the second commit (i.e., commit `Add second line`). +2. Add the line `I heard someone knocking at the door`. to the `story.txt`. 3. Commit the change. You may use any suitable commit message. 4. Start a new branch named `sleep-line`, starting from the same starting point as before. -5. Add the line `I fell asleep on the couch.` to the `story.txt`. +5. Add the line `I fell asleep on the couch`. to the `story.txt`. 6. Commit the change. You may use any suitable commit message. -## Hints - -
-How do I create a branch from a specific commit? - -You can create a branch from a specific commit using: -```bash -git branch -``` - -Or you can checkout directly to a new branch from a specific commit: -```bash -git checkout -b -``` -
- -
-How do I find the commit hash? +## Expected Revision Graph -Use `git log` to view the commit history and find the hash of the commit with message "Describe location". -
+![Expected Revision Graph](horror-story/expected-tree.png) diff --git a/branch_previous/download.py b/branch_previous/download.py index 0aa394c..4d3342d 100644 --- a/branch_previous/download.py +++ b/branch_previous/download.py @@ -1,32 +1,33 @@ from exercise_utils.file import create_or_update_file, append_to_file from exercise_utils.git import add, commit -from exercise_utils.gitmastery import create_start_tag -__resources__ = {"README.md": "README.md"} +__resources__ = {"expected-tree.png": "expected-tree.png"} def setup(verbose: bool = False): - # First commit: Describe night create_or_update_file( - "story.txt", - "It was a dark and stormy night.\n" + "story.txt", + """ + It was a dark and stormy night. + """ ) add(["story.txt"], verbose) commit("Describe night", verbose) - # Second commit: Describe location append_to_file( "story.txt", - "It was a dark and stormy night.\nI was alone in my room.\n" + """ + I was alone in my room. + """ ) add(["story.txt"], verbose) commit("Describe location", verbose) - # Third commit: Mention noise - create_or_update_file( + append_to_file( "story.txt", - "It was a dark and stormy night.\nI was alone in my room.\nI heard a strange noise.\n", + """ + I heard a strange noise. + """ ) add(["story.txt"], verbose) commit("Mention noise", verbose) - diff --git a/branch_previous/res/expected-tree.png b/branch_previous/res/expected-tree.png new file mode 100644 index 0000000000000000000000000000000000000000..bf43f8b920c8562fd441e512f81cdcf4168a18e1 GIT binary patch literal 57334 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 Nf{g0x(pPUj{$Ec_mVp2O literal 0 HcmV?d00001 diff --git a/branch_previous/tests/specs/base.yml b/branch_previous/tests/specs/base.yml index f43e8e7..8b6ea1c 100644 --- a/branch_previous/tests/specs/base.yml +++ b/branch_previous/tests/specs/base.yml @@ -16,7 +16,7 @@ initialization: - type: commit message: Describe location empty: false - id: second_commit + id: describe_location_commit - type: bash runs: echo "I heard a strange noise." >> story.txt - type: add @@ -27,23 +27,23 @@ initialization: empty: false - type: bash runs: | - SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b visitor-line $SECOND_COMMIT + DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b visitor-line $DESCRIBE_LOCATION_COMMIT echo "I heard someone knocking at the door." >> story.txt - type: add files: - story.txt - type: commit - message: Add visitor line + message: Mention knocking empty: false - type: bash runs: | - SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b sleep-line $SECOND_COMMIT + DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b sleep-line $DESCRIBE_LOCATION_COMMIT echo "I fell asleep on the couch." >> story.txt - type: add files: - story.txt - type: commit - message: Add sleep line + message: Mention sleeping empty: false diff --git a/branch_previous/tests/specs/sleep_branch_missing.yml b/branch_previous/tests/specs/sleep_missing_branch.yml similarity index 79% rename from branch_previous/tests/specs/sleep_branch_missing.yml rename to branch_previous/tests/specs/sleep_missing_branch.yml index b3c410b..4269a9f 100644 --- a/branch_previous/tests/specs/sleep_branch_missing.yml +++ b/branch_previous/tests/specs/sleep_missing_branch.yml @@ -17,7 +17,7 @@ initialization: - type: commit message: Describe location empty: false - id: second_commit + id: describe_location_commit - type: bash runs: echo "I heard a strange noise." >> story.txt - type: add @@ -29,12 +29,12 @@ initialization: # Only create visitor-line branch, not sleep-line - type: bash runs: | - SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b visitor-line $SECOND_COMMIT + DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b visitor-line $DESCRIBE_LOCATION_COMMIT echo "I heard someone knocking at the door." >> story.txt - type: add files: - story.txt - type: commit - message: Add visitor line + message: Mention knocking empty: false diff --git a/branch_previous/tests/specs/sleep_wrong_content.yml b/branch_previous/tests/specs/sleep_wrong_content.yml new file mode 100644 index 0000000..a3d5ed1 --- /dev/null +++ b/branch_previous/tests/specs/sleep_wrong_content.yml @@ -0,0 +1,50 @@ +initialization: + steps: + - type: bash + runs: echo "It was a dark and stormy night." > story.txt + - type: add + files: + - story.txt + - type: commit + message: Describe night + empty: false + id: start + - type: bash + runs: echo "I was alone in my room." >> story.txt + - type: add + files: + - story.txt + - type: commit + message: Describe location + empty: false + id: describe_location_commit + - type: bash + runs: echo "I heard a strange noise." >> story.txt + - type: add + files: + - story.txt + - type: commit + message: Mention noise + empty: false + - type: bash + runs: | + DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b visitor-line $DESCRIBE_LOCATION_COMMIT + echo "I heard someone knocking at the door." >> story.txt + - type: add + files: + - story.txt + - type: commit + message: Mention knocking + empty: false + - type: bash + runs: | + DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b sleep-line $DESCRIBE_LOCATION_COMMIT + echo "Wrong content here." >> story.txt + - type: add + files: + - story.txt + - type: commit + message: Mention sleeping + empty: false diff --git a/branch_previous/tests/specs/visitor_branch_missing.yml b/branch_previous/tests/specs/visitor_branch_missing.yml deleted file mode 100644 index 95599b5..0000000 --- a/branch_previous/tests/specs/visitor_branch_missing.yml +++ /dev/null @@ -1,46 +0,0 @@ -initialization: - steps: - - type: bash - runs: echo "It was a dark and stormy night." > story.txt - - type: add - files: - - story.txt - - type: commit - message: Describe night - files: - - name: story.txt - content: "It was a dark and stormy night.\n" - id: start - - type: bash - runs: | - echo "It was a dark and stormy night." > story.txt - echo "I was alone in my room." >> story.txt - - type: commit - message: Describe location - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\n" - id: second_commit - - type: bash - runs: | - echo "It was a dark and stormy night." > story.txt - echo "I was alone in my room." >> story.txt - echo "I heard a strange noise." >> story.txt - - type: commit - message: Mention noise - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\nI heard a strange noise.\n" - # Only create sleep-line branch, not visitor-line - - type: bash - runs: | - SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b sleep-line $SECOND_COMMIT - echo "It was a dark and stormy night." > story.txt - echo "I was alone in my room." >> story.txt - echo "I fell asleep on the couch." >> story.txt - - type: commit - message: Add sleep line - files: - - name: story.txt - content: "It was a dark and stormy night.\nI was alone in my room.\nI fell asleep on the couch.\n" diff --git a/branch_previous/tests/specs/visitor_missing_branch.yml b/branch_previous/tests/specs/visitor_missing_branch.yml new file mode 100644 index 0000000..1a4c5a7 --- /dev/null +++ b/branch_previous/tests/specs/visitor_missing_branch.yml @@ -0,0 +1,32 @@ +initialization: + steps: + - type: bash + runs: echo "It was a dark and stormy night." > story.txt + - type: add + files: + - story.txt + - type: commit + message: Describe night + id: start + - type: bash + runs: | + echo "I was alone in my room." >> story.txt + - type: commit + message: Describe location + empty: false + id: second_commit + - type: bash + runs: | + echo "I heard a strange noise." >> story.txt + - type: commit + message: Mention noise + empty: false + # Only create sleep-line branch, not visitor-line + - type: bash + runs: | + DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b sleep-line $DESCRIBE_LOCATION_COMMIT + echo "I fell asleep on the couch." >> story.txt + - type: commit + message: Mention sleeping + empty: false diff --git a/branch_previous/tests/specs/visitor_commit_missing.yml b/branch_previous/tests/specs/visitor_missing_commit.yml similarity index 70% rename from branch_previous/tests/specs/visitor_commit_missing.yml rename to branch_previous/tests/specs/visitor_missing_commit.yml index 846a4ec..ee16bb5 100644 --- a/branch_previous/tests/specs/visitor_commit_missing.yml +++ b/branch_previous/tests/specs/visitor_missing_commit.yml @@ -17,7 +17,7 @@ initialization: - type: commit message: Describe location empty: false - id: second_commit + id: describe_location_commit - type: bash runs: echo "I heard a strange noise." >> story.txt - type: add @@ -29,17 +29,17 @@ initialization: # visitor-line branch created but no commit made - type: bash runs: | - SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git branch visitor-line $SECOND_COMMIT + DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b visitor-line $DESCRIBE_LOCATION_COMMIT # sleep-line branch created with commit - type: bash runs: | - SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b sleep-line $SECOND_COMMIT + DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b sleep-line $DESCRIBE_LOCATION_COMMIT echo "I fell asleep on the couch." >> story.txt - type: add files: - story.txt - type: commit - message: Add sleep line + message: Mention sleeping empty: false diff --git a/branch_previous/tests/specs/visitor_wrong_content.yml b/branch_previous/tests/specs/visitor_wrong_content.yml index 7f6aa87..2476ddb 100644 --- a/branch_previous/tests/specs/visitor_wrong_content.yml +++ b/branch_previous/tests/specs/visitor_wrong_content.yml @@ -17,7 +17,7 @@ initialization: - type: commit message: Describe location empty: false - id: second_commit + id: describe_location_commit - type: bash runs: echo "I heard a strange noise." >> story.txt - type: add @@ -28,23 +28,23 @@ initialization: empty: false - type: bash runs: | - SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b visitor-line $SECOND_COMMIT + DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b visitor-line $DESCRIBE_LOCATION_COMMIT echo "Wrong content here." >> story.txt - type: add files: - story.txt - type: commit - message: Add visitor line + message: Mention knocking empty: false - type: bash runs: | - SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b sleep-line $SECOND_COMMIT + DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b sleep-line $DESCRIBE_LOCATION_COMMIT echo "I fell asleep on my couch." >> story.txt - type: add files: - story.txt - type: commit - message: Add sleep line + message: Mention sleeping empty: false diff --git a/branch_previous/tests/specs/visitor_wrong_start_first_commit.yml b/branch_previous/tests/specs/visitor_wrong_start_first_commit.yml index 3008520..ad47985 100644 --- a/branch_previous/tests/specs/visitor_wrong_start_first_commit.yml +++ b/branch_previous/tests/specs/visitor_wrong_start_first_commit.yml @@ -17,7 +17,7 @@ initialization: - type: commit message: Describe location empty: false - id: second_commit + id: describe_location_commit - type: bash runs: echo "I heard a strange noise." >> story.txt - type: add @@ -36,16 +36,16 @@ initialization: files: - story.txt - type: commit - message: Add visitor line + message: Mention knocking empty: false - type: bash runs: | - SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b sleep-line $SECOND_COMMIT + DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b sleep-line $DESCRIBE_LOCATION_COMMIT echo "I fell asleep on the couch." >> story.txt - type: add files: - story.txt - type: commit - message: Add sleep line + message: Mention sleeping empty: false diff --git a/branch_previous/tests/specs/visitor_wrong_third_commit.yml b/branch_previous/tests/specs/visitor_wrong_start_third_commit.yml similarity index 82% rename from branch_previous/tests/specs/visitor_wrong_third_commit.yml rename to branch_previous/tests/specs/visitor_wrong_start_third_commit.yml index fd4f7d7..42c3401 100644 --- a/branch_previous/tests/specs/visitor_wrong_third_commit.yml +++ b/branch_previous/tests/specs/visitor_wrong_start_third_commit.yml @@ -17,7 +17,7 @@ initialization: - type: commit message: Describe location empty: false - id: second_commit + id: describe_location_commit - type: bash runs: echo "I heard a strange noise." >> story.txt - type: add @@ -36,16 +36,16 @@ initialization: files: - story.txt - type: commit - message: Add visitor line + message: Mention knocking empty: false - type: bash runs: | - SECOND_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b sleep-line $SECOND_COMMIT + DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) + git checkout -b sleep-line $DESCRIBE_LOCATION_COMMIT echo "I fell asleep on the couch." >> story.txt - type: add files: - story.txt - type: commit - message: Add sleep line + message: Mention sleeping empty: false diff --git a/branch_previous/tests/test_verify.py b/branch_previous/tests/test_verify.py index 8aded0c..2630f5b 100644 --- a/branch_previous/tests/test_verify.py +++ b/branch_previous/tests/test_verify.py @@ -2,10 +2,10 @@ from git_autograder.status import GitAutograderStatus from ..verify import ( - BRANCH_MISSING, + MISSING_BRANCH, WRONG_CONTENT, WRONG_START, - COMMIT_MISSING, + MISSING_COMMIT, verify, ) @@ -19,31 +19,74 @@ def test_base(): assert_output(output, GitAutograderStatus.SUCCESSFUL) -def test_visitor_branch_missing(): - with loader.load("specs/visitor_branch_missing.yml") as output: - assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [BRANCH_MISSING.format(branch_name="visitor-line")]) +def test_visitor_missing_branch(): + with loader.load("specs/visitor_missing_branch.yml") as output: + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [MISSING_BRANCH.format(branch_name="visitor-line")], + ) -def test_sleep_branch_missing(): - with loader.load("specs/sleep_branch_missing.yml") as output: - assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [BRANCH_MISSING.format(branch_name="sleep-line")]) +def test_sleep_missing_branch(): + with loader.load("specs/sleep_missing_branch.yml") as output: + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [MISSING_BRANCH.format(branch_name="sleep-line")], + ) def test_visitor_wrong_start_first_commit(): with loader.load("specs/visitor_wrong_start_first_commit.yml") as output: - assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [WRONG_START.format(branch_name="visitor-line")]) + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [WRONG_START.format(branch_name="visitor-line")], + ) def test_visitor_wrong_start_third_commit(): with loader.load("specs/visitor_wrong_start_third_commit.yml") as output: - assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [WRONG_START.format(branch_name="visitor-line")]) + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [WRONG_START.format(branch_name="visitor-line")], + ) def test_visitor_wrong_content(): with loader.load("specs/visitor_wrong_content.yml") as output: - assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [WRONG_CONTENT.format(branch_name="visitor-line", expected_content="I heard someone knocking at the door.")]) - - -def test_visitor_commit_missing(): - with loader.load("specs/visitor_commit_missing.yml") as output: - assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [COMMIT_MISSING.format(branch_name="visitor-line")]) + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [ + WRONG_CONTENT.format( + branch_name="visitor-line", + expected_content="I heard someone knocking at the door.", + ) + ], + ) + + +def test_sleep_wrong_content(): + with loader.load("specs/sleep_wrong_content.yml") as output: + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [ + WRONG_CONTENT.format( + branch_name="sleep-line", + expected_content="I fell asleep on the couch.", + ) + ], + ) + + +def test_visitor_missing_commit(): + with loader.load("specs/visitor_missing_commit.yml") as output: + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [MISSING_COMMIT.format(branch_name="visitor-line")], + ) diff --git a/branch_previous/verify.py b/branch_previous/verify.py index 2369fcf..9c7f28d 100644 --- a/branch_previous/verify.py +++ b/branch_previous/verify.py @@ -4,15 +4,25 @@ GitAutograderOutput, GitAutograderExercise, GitAutograderStatus, - GitAutograderCommit, ) from git.objects.commit import Commit -BRANCH_MISSING = "The '{branch_name}' branch is missing." -COMMIT_MISSING = "No commits were made in the '{branch_name}' branch." -WRONG_START = "The '{branch_name}' branch should start from the second commit (with message 'Describe location')." -WRONG_CONTENT = "The '{branch_name}' branch should have the line '{expected_content}' added to story.txt." +MISSING_BRANCH = "The '{branch_name}' branch is missing." +MISSING_COMMIT = "No commits were made in the '{branch_name}' branch." +WRONG_START = ( + "The '{branch_name}' branch should start from the second commit " + "(with message 'Describe location')." +) +WRONG_CONTENT = ( + "The '{branch_name}' branch should have the line '{expected_content}' " + "added to story.txt." +) +SUCCESS_MESSAGE = ( + "Excellent work! You've successfully created branches from a " + "previous commit and explored alternative storylines!" +) + def get_commit_from_message(exercise: GitAutograderExercise, message: str) -> Optional[Commit]: """Find a commit with the given message.""" @@ -21,80 +31,62 @@ def get_commit_from_message(exercise: GitAutograderExercise, message: str) -> Op if message.strip() == commit.message.strip(): return commit return None - -def check_file_changes( - branch_name: str, - expected_content: str, - exercise: GitAutograderExercise - ) -> None: - - latest_commit = exercise.repo.branches.branch(branch_name).latest_commit - with latest_commit.file("story.txt") as content: - if expected_content not in content: - raise exercise.wrong_answer([WRONG_CONTENT.format( - branch_name=branch_name, - expected_content=expected_content - )]) - - return -def check_branch_structure( - branch_name: str, - expected_start_commit: Commit, - exercise: GitAutograderExercise - ) -> None: + +def verify_branch( + branch_name: str, + expected_start_commit: Commit, + expected_content: str, + exercise: GitAutograderExercise, +) -> None: + """ + Check that the given branch exists, starts from the expected commit, + and contains the expected content in story.txt. + """ # Check if branch exists branch_helper = exercise.repo.branches if not branch_helper.has_branch(branch_name): - raise exercise.wrong_answer([BRANCH_MISSING.format(branch_name=branch_name)]) + raise exercise.wrong_answer([MISSING_BRANCH.format(branch_name=branch_name)]) branch = branch_helper.branch(branch_name) - latest_commit = branch.latest_commit.commit + latest_commit = branch.latest_commit # Check that user made commits in the branch - if latest_commit == expected_start_commit: - raise exercise.wrong_answer([COMMIT_MISSING.format(branch_name=branch_name)]) + if latest_commit.commit == expected_start_commit: + raise exercise.wrong_answer([MISSING_COMMIT.format(branch_name=branch_name)]) - # Check that branch starts from correct commit - if not expected_start_commit in latest_commit.parents: + # Check that previous commit of latest commit is the expected start commit + if expected_start_commit not in latest_commit.commit.parents: raise exercise.wrong_answer([WRONG_START.format(branch_name=branch_name)]) - - return + + # Check that the expected content is in story.txt + with latest_commit.file("story.txt") as content: + if expected_content not in content: + raise exercise.wrong_answer([WRONG_CONTENT.format( + branch_name=branch_name, + expected_content=expected_content + )]) def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - describe_location_commit = get_commit_from_message(exercise, "Describe location") - check_branch_structure( + verify_branch( branch_name="visitor-line", expected_start_commit=describe_location_commit, - exercise=exercise - ) - - check_branch_structure( - branch_name="sleep-line", - expected_start_commit=describe_location_commit, - exercise=exercise - ) - - check_file_changes( - branch_name="visitor-line", expected_content="I heard someone knocking at the door.", exercise=exercise ) - check_file_changes( + verify_branch( branch_name="sleep-line", + expected_start_commit=describe_location_commit, expected_content="I fell asleep on the couch.", exercise=exercise ) return exercise.to_output( - [ - "Excellent work! You've successfully created branches from a previous commit and explored alternative storylines!" - ], + [SUCCESS_MESSAGE], GitAutograderStatus.SUCCESSFUL, ) - From c0c2e3f3acec68366359e4e423063341d2f505c7 Mon Sep 17 00:00:00 2001 From: jia xin Date: Sun, 30 Nov 2025 08:50:10 +0800 Subject: [PATCH 10/18] Change branching logic in tests --- branch_previous/tests/specs/base.yml | 6 ++---- branch_previous/tests/specs/sleep_missing_branch.yml | 3 +-- branch_previous/tests/specs/sleep_wrong_content.yml | 6 ++---- branch_previous/tests/specs/visitor_missing_branch.yml | 3 +-- branch_previous/tests/specs/visitor_missing_commit.yml | 7 ++----- branch_previous/tests/specs/visitor_wrong_content.yml | 6 ++---- .../tests/specs/visitor_wrong_start_first_commit.yml | 6 ++---- .../tests/specs/visitor_wrong_start_third_commit.yml | 6 ++---- 8 files changed, 14 insertions(+), 29 deletions(-) diff --git a/branch_previous/tests/specs/base.yml b/branch_previous/tests/specs/base.yml index 8b6ea1c..3c06e78 100644 --- a/branch_previous/tests/specs/base.yml +++ b/branch_previous/tests/specs/base.yml @@ -27,8 +27,7 @@ initialization: empty: false - type: bash runs: | - DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b visitor-line $DESCRIBE_LOCATION_COMMIT + git checkout -b visitor-line HEAD~1 echo "I heard someone knocking at the door." >> story.txt - type: add files: @@ -38,8 +37,7 @@ initialization: empty: false - type: bash runs: | - DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b sleep-line $DESCRIBE_LOCATION_COMMIT + git checkout -b sleep-line HEAD~1 echo "I fell asleep on the couch." >> story.txt - type: add files: diff --git a/branch_previous/tests/specs/sleep_missing_branch.yml b/branch_previous/tests/specs/sleep_missing_branch.yml index 4269a9f..7b4f2f4 100644 --- a/branch_previous/tests/specs/sleep_missing_branch.yml +++ b/branch_previous/tests/specs/sleep_missing_branch.yml @@ -29,8 +29,7 @@ initialization: # Only create visitor-line branch, not sleep-line - type: bash runs: | - DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b visitor-line $DESCRIBE_LOCATION_COMMIT + git checkout -b visitor-line HEAD~1 echo "I heard someone knocking at the door." >> story.txt - type: add files: diff --git a/branch_previous/tests/specs/sleep_wrong_content.yml b/branch_previous/tests/specs/sleep_wrong_content.yml index a3d5ed1..2dc7cb2 100644 --- a/branch_previous/tests/specs/sleep_wrong_content.yml +++ b/branch_previous/tests/specs/sleep_wrong_content.yml @@ -28,8 +28,7 @@ initialization: empty: false - type: bash runs: | - DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b visitor-line $DESCRIBE_LOCATION_COMMIT + git checkout -b visitor-line HEAD~1 echo "I heard someone knocking at the door." >> story.txt - type: add files: @@ -39,8 +38,7 @@ initialization: empty: false - type: bash runs: | - DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b sleep-line $DESCRIBE_LOCATION_COMMIT + git checkout -b sleep-line HEAD~1 echo "Wrong content here." >> story.txt - type: add files: diff --git a/branch_previous/tests/specs/visitor_missing_branch.yml b/branch_previous/tests/specs/visitor_missing_branch.yml index 1a4c5a7..6a31d7f 100644 --- a/branch_previous/tests/specs/visitor_missing_branch.yml +++ b/branch_previous/tests/specs/visitor_missing_branch.yml @@ -24,8 +24,7 @@ initialization: # Only create sleep-line branch, not visitor-line - type: bash runs: | - DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b sleep-line $DESCRIBE_LOCATION_COMMIT + git checkout -b sleep-line HEAD~1 echo "I fell asleep on the couch." >> story.txt - type: commit message: Mention sleeping diff --git a/branch_previous/tests/specs/visitor_missing_commit.yml b/branch_previous/tests/specs/visitor_missing_commit.yml index ee16bb5..1b577fe 100644 --- a/branch_previous/tests/specs/visitor_missing_commit.yml +++ b/branch_previous/tests/specs/visitor_missing_commit.yml @@ -28,14 +28,11 @@ initialization: empty: false # visitor-line branch created but no commit made - type: bash - runs: | - DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b visitor-line $DESCRIBE_LOCATION_COMMIT + runs: git checkout -b visitor-line HEAD~1 # sleep-line branch created with commit - type: bash runs: | - DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b sleep-line $DESCRIBE_LOCATION_COMMIT + git checkout -b sleep-line HEAD~1 echo "I fell asleep on the couch." >> story.txt - type: add files: diff --git a/branch_previous/tests/specs/visitor_wrong_content.yml b/branch_previous/tests/specs/visitor_wrong_content.yml index 2476ddb..7892a19 100644 --- a/branch_previous/tests/specs/visitor_wrong_content.yml +++ b/branch_previous/tests/specs/visitor_wrong_content.yml @@ -28,8 +28,7 @@ initialization: empty: false - type: bash runs: | - DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b visitor-line $DESCRIBE_LOCATION_COMMIT + git checkout -b visitor-line HEAD~1 echo "Wrong content here." >> story.txt - type: add files: @@ -39,8 +38,7 @@ initialization: empty: false - type: bash runs: | - DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b sleep-line $DESCRIBE_LOCATION_COMMIT + git checkout -b sleep-line HEAD~1 echo "I fell asleep on my couch." >> story.txt - type: add files: diff --git a/branch_previous/tests/specs/visitor_wrong_start_first_commit.yml b/branch_previous/tests/specs/visitor_wrong_start_first_commit.yml index ad47985..c6ea145 100644 --- a/branch_previous/tests/specs/visitor_wrong_start_first_commit.yml +++ b/branch_previous/tests/specs/visitor_wrong_start_first_commit.yml @@ -29,8 +29,7 @@ initialization: # visitor-line starts from wrong commit (first instead of second) - type: bash runs: | - FIRST_COMMIT=$(git log --all --grep="Describe night" --format=%H -n 1) - git checkout -b visitor-line $FIRST_COMMIT + git checkout -b visitor-line HEAD~2 echo "I heard someone knocking at the door." >> story.txt - type: add files: @@ -40,8 +39,7 @@ initialization: empty: false - type: bash runs: | - DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b sleep-line $DESCRIBE_LOCATION_COMMIT + git checkout -b sleep-line HEAD~1 echo "I fell asleep on the couch." >> story.txt - type: add files: diff --git a/branch_previous/tests/specs/visitor_wrong_start_third_commit.yml b/branch_previous/tests/specs/visitor_wrong_start_third_commit.yml index 42c3401..f902f73 100644 --- a/branch_previous/tests/specs/visitor_wrong_start_third_commit.yml +++ b/branch_previous/tests/specs/visitor_wrong_start_third_commit.yml @@ -29,8 +29,7 @@ initialization: # visitor-line starts from wrong commit (third instead of second) - type: bash runs: | - THIRD_COMMIT=$(git log --all --grep="Mention noise" --format=%H -n 1) - git checkout -b visitor-line $THIRD_COMMIT + git checkout -b visitor-line echo "I heard someone knocking at the door." >> story.txt - type: add files: @@ -40,8 +39,7 @@ initialization: empty: false - type: bash runs: | - DESCRIBE_LOCATION_COMMIT=$(git log --all --grep="Describe location" --format=%H -n 1) - git checkout -b sleep-line $DESCRIBE_LOCATION_COMMIT + git checkout -b sleep-line HEAD~1 echo "I fell asleep on the couch." >> story.txt - type: add files: From 80e502061e264ccc60c4c5449e9504f8a6d219d9 Mon Sep 17 00:00:00 2001 From: jia xin Date: Sun, 30 Nov 2025 09:02:45 +0800 Subject: [PATCH 11/18] Fix issues with HEAD logic --- branch_previous/tests/specs/visitor_missing_commit.yml | 2 +- .../tests/specs/visitor_wrong_start_third_commit.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/branch_previous/tests/specs/visitor_missing_commit.yml b/branch_previous/tests/specs/visitor_missing_commit.yml index 1b577fe..89a5591 100644 --- a/branch_previous/tests/specs/visitor_missing_commit.yml +++ b/branch_previous/tests/specs/visitor_missing_commit.yml @@ -32,7 +32,7 @@ initialization: # sleep-line branch created with commit - type: bash runs: | - git checkout -b sleep-line HEAD~1 + git checkout -b sleep-line echo "I fell asleep on the couch." >> story.txt - type: add files: diff --git a/branch_previous/tests/specs/visitor_wrong_start_third_commit.yml b/branch_previous/tests/specs/visitor_wrong_start_third_commit.yml index f902f73..e718444 100644 --- a/branch_previous/tests/specs/visitor_wrong_start_third_commit.yml +++ b/branch_previous/tests/specs/visitor_wrong_start_third_commit.yml @@ -39,7 +39,7 @@ initialization: empty: false - type: bash runs: | - git checkout -b sleep-line HEAD~1 + git checkout -b sleep-line HEAD~2 echo "I fell asleep on the couch." >> story.txt - type: add files: From a7ae7b80decb1fefc6192b16b00867ffcd719484 Mon Sep 17 00:00:00 2001 From: jiaxin Date: Mon, 1 Dec 2025 10:59:06 +0800 Subject: [PATCH 12/18] Replace echo steps with repo-smith equivalent --- branch_previous/tests/specs/base.yml | 29 +++++++++------- .../tests/specs/sleep_missing_branch.yml | 22 ++++++++----- .../tests/specs/sleep_wrong_content.yml | 29 +++++++++------- .../tests/specs/visitor_missing_branch.yml | 33 ++++++++++++------- .../tests/specs/visitor_missing_commit.yml | 22 ++++++++----- .../tests/specs/visitor_wrong_content.yml | 29 +++++++++------- .../visitor_wrong_start_first_commit.yml | 29 +++++++++------- .../visitor_wrong_start_third_commit.yml | 29 +++++++++------- 8 files changed, 133 insertions(+), 89 deletions(-) diff --git a/branch_previous/tests/specs/base.yml b/branch_previous/tests/specs/base.yml index 3c06e78..4ec8b3b 100644 --- a/branch_previous/tests/specs/base.yml +++ b/branch_previous/tests/specs/base.yml @@ -1,15 +1,17 @@ initialization: steps: - - type: bash - runs: echo "It was a dark and stormy night." > story.txt + - type: new-file + filename: story.txt + contents: "It was a dark and stormy night.\n" - type: add files: - story.txt - type: commit message: Describe night id: start - - type: bash - runs: echo "I was alone in my room." >> story.txt + - type: append-file + filename: story.txt + contents: "I was alone in my room.\n" - type: add files: - story.txt @@ -17,8 +19,9 @@ initialization: message: Describe location empty: false id: describe_location_commit - - type: bash - runs: echo "I heard a strange noise." >> story.txt + - type: append-file + filename: story.txt + contents: "I heard a strange noise.\n" - type: add files: - story.txt @@ -26,9 +29,10 @@ initialization: message: Mention noise empty: false - type: bash - runs: | - git checkout -b visitor-line HEAD~1 - echo "I heard someone knocking at the door." >> story.txt + runs: git checkout -b visitor-line HEAD~1 + - type: append-file + filename: story.txt + contents: "I heard someone knocking at the door.\n" - type: add files: - story.txt @@ -36,9 +40,10 @@ initialization: message: Mention knocking empty: false - type: bash - runs: | - git checkout -b sleep-line HEAD~1 - echo "I fell asleep on the couch." >> story.txt + runs: git checkout -b sleep-line HEAD~1 + - type: append-file + filename: story.txt + contents: "I fell asleep on the couch.\n" - type: add files: - story.txt diff --git a/branch_previous/tests/specs/sleep_missing_branch.yml b/branch_previous/tests/specs/sleep_missing_branch.yml index 7b4f2f4..cab52a8 100644 --- a/branch_previous/tests/specs/sleep_missing_branch.yml +++ b/branch_previous/tests/specs/sleep_missing_branch.yml @@ -1,7 +1,8 @@ initialization: steps: - - type: bash - runs: echo "It was a dark and stormy night." > story.txt + - type: new-file + filename: story.txt + contents: "It was a dark and stormy night.\n" - type: add files: - story.txt @@ -9,8 +10,9 @@ initialization: message: Describe night empty: false id: start - - type: bash - runs: echo "I was alone in my room." >> story.txt + - type: append-file + filename: story.txt + contents: "I was alone in my room.\n" - type: add files: - story.txt @@ -18,8 +20,9 @@ initialization: message: Describe location empty: false id: describe_location_commit - - type: bash - runs: echo "I heard a strange noise." >> story.txt + - type: append-file + filename: story.txt + contents: "I heard a strange noise.\n" - type: add files: - story.txt @@ -28,9 +31,10 @@ initialization: empty: false # Only create visitor-line branch, not sleep-line - type: bash - runs: | - git checkout -b visitor-line HEAD~1 - echo "I heard someone knocking at the door." >> story.txt + runs: git checkout -b visitor-line HEAD~1 + - type: append-file + filename: story.txt + contents: "I heard someone knocking at the door.\n" - type: add files: - story.txt diff --git a/branch_previous/tests/specs/sleep_wrong_content.yml b/branch_previous/tests/specs/sleep_wrong_content.yml index 2dc7cb2..8cd47ff 100644 --- a/branch_previous/tests/specs/sleep_wrong_content.yml +++ b/branch_previous/tests/specs/sleep_wrong_content.yml @@ -1,7 +1,8 @@ initialization: steps: - - type: bash - runs: echo "It was a dark and stormy night." > story.txt + - type: new-file + filename: story.txt + contents: "It was a dark and stormy night.\n" - type: add files: - story.txt @@ -9,8 +10,9 @@ initialization: message: Describe night empty: false id: start - - type: bash - runs: echo "I was alone in my room." >> story.txt + - type: append-file + filename: story.txt + contents: "I was alone in my room.\n" - type: add files: - story.txt @@ -18,8 +20,9 @@ initialization: message: Describe location empty: false id: describe_location_commit - - type: bash - runs: echo "I heard a strange noise." >> story.txt + - type: append-file + filename: story.txt + contents: "I heard a strange noise.\n" - type: add files: - story.txt @@ -27,9 +30,10 @@ initialization: message: Mention noise empty: false - type: bash - runs: | - git checkout -b visitor-line HEAD~1 - echo "I heard someone knocking at the door." >> story.txt + runs: git checkout -b visitor-line HEAD~1 + - type: append-file + filename: story.txt + contents: "I heard someone knocking at the door.\n" - type: add files: - story.txt @@ -37,9 +41,10 @@ initialization: message: Mention knocking empty: false - type: bash - runs: | - git checkout -b sleep-line HEAD~1 - echo "Wrong content here." >> story.txt + runs: git checkout -b sleep-line HEAD~1 + - type: append-file + filename: story.txt + contents: "Wrong content here.\n" - type: add files: - story.txt diff --git a/branch_previous/tests/specs/visitor_missing_branch.yml b/branch_previous/tests/specs/visitor_missing_branch.yml index 6a31d7f..4789ad3 100644 --- a/branch_previous/tests/specs/visitor_missing_branch.yml +++ b/branch_previous/tests/specs/visitor_missing_branch.yml @@ -1,31 +1,42 @@ initialization: steps: - - type: bash - runs: echo "It was a dark and stormy night." > story.txt + - type: new-file + filename: story.txt + contents: "It was a dark and stormy night.\n" - type: add files: - story.txt - type: commit message: Describe night id: start - - type: bash - runs: | - echo "I was alone in my room." >> story.txt + - type: append-file + filename: story.txt + contents: "I was alone in my room.\n" + - type: add + files: + - story.txt - type: commit message: Describe location empty: false id: second_commit - - type: bash - runs: | - echo "I heard a strange noise." >> story.txt + - type: append-file + filename: story.txt + contents: "I heard a strange noise.\n" + - type: add + files: + - story.txt - type: commit message: Mention noise empty: false # Only create sleep-line branch, not visitor-line - type: bash - runs: | - git checkout -b sleep-line HEAD~1 - echo "I fell asleep on the couch." >> story.txt + runs: git checkout -b sleep-line HEAD~1 + - type: append-file + filename: story.txt + contents: "I fell asleep on the couch.\n" + - type: add + files: + - story.txt - type: commit message: Mention sleeping empty: false diff --git a/branch_previous/tests/specs/visitor_missing_commit.yml b/branch_previous/tests/specs/visitor_missing_commit.yml index 89a5591..dc599ea 100644 --- a/branch_previous/tests/specs/visitor_missing_commit.yml +++ b/branch_previous/tests/specs/visitor_missing_commit.yml @@ -1,7 +1,8 @@ initialization: steps: - - type: bash - runs: echo "It was a dark and stormy night." > story.txt + - type: new-file + filename: story.txt + contents: "It was a dark and stormy night.\n" - type: add files: - story.txt @@ -9,8 +10,9 @@ initialization: message: Describe night empty: false id: start - - type: bash - runs: echo "I was alone in my room." >> story.txt + - type: append-file + filename: story.txt + contents: "I was alone in my room.\n" - type: add files: - story.txt @@ -18,8 +20,9 @@ initialization: message: Describe location empty: false id: describe_location_commit - - type: bash - runs: echo "I heard a strange noise." >> story.txt + - type: append-file + filename: story.txt + contents: "I heard a strange noise.\n" - type: add files: - story.txt @@ -31,9 +34,10 @@ initialization: runs: git checkout -b visitor-line HEAD~1 # sleep-line branch created with commit - type: bash - runs: | - git checkout -b sleep-line - echo "I fell asleep on the couch." >> story.txt + runs: git checkout -b sleep-line + - type: append-file + filename: story.txt + contents: "I fell asleep on the couch.\n" - type: add files: - story.txt diff --git a/branch_previous/tests/specs/visitor_wrong_content.yml b/branch_previous/tests/specs/visitor_wrong_content.yml index 7892a19..34e3bb8 100644 --- a/branch_previous/tests/specs/visitor_wrong_content.yml +++ b/branch_previous/tests/specs/visitor_wrong_content.yml @@ -1,7 +1,8 @@ initialization: steps: - - type: bash - runs: echo "It was a dark and stormy night." > story.txt + - type: new-file + filename: story.txt + contents: "It was a dark and stormy night.\n" - type: add files: - story.txt @@ -9,8 +10,9 @@ initialization: message: Describe night empty: false id: start - - type: bash - runs: echo "I was alone in my room." >> story.txt + - type: append-file + filename: story.txt + contents: "I was alone in my room.\n" - type: add files: - story.txt @@ -18,8 +20,9 @@ initialization: message: Describe location empty: false id: describe_location_commit - - type: bash - runs: echo "I heard a strange noise." >> story.txt + - type: append-file + filename: story.txt + contents: "I heard a strange noise.\n" - type: add files: - story.txt @@ -27,9 +30,10 @@ initialization: message: Mention noise empty: false - type: bash - runs: | - git checkout -b visitor-line HEAD~1 - echo "Wrong content here." >> story.txt + runs: git checkout -b visitor-line HEAD~1 + - type: append-file + filename: story.txt + contents: "Wrong content here.\n" - type: add files: - story.txt @@ -37,9 +41,10 @@ initialization: message: Mention knocking empty: false - type: bash - runs: | - git checkout -b sleep-line HEAD~1 - echo "I fell asleep on my couch." >> story.txt + runs: git checkout -b sleep-line HEAD~1 + - type: append-file + filename: story.txt + contents: "I fell asleep on my couch.\n" - type: add files: - story.txt diff --git a/branch_previous/tests/specs/visitor_wrong_start_first_commit.yml b/branch_previous/tests/specs/visitor_wrong_start_first_commit.yml index c6ea145..6946543 100644 --- a/branch_previous/tests/specs/visitor_wrong_start_first_commit.yml +++ b/branch_previous/tests/specs/visitor_wrong_start_first_commit.yml @@ -1,7 +1,8 @@ initialization: steps: - - type: bash - runs: echo "It was a dark and stormy night." > story.txt + - type: new-file + filename: story.txt + contents: "It was a dark and stormy night.\n" - type: add files: - story.txt @@ -9,8 +10,9 @@ initialization: message: Describe night empty: false id: start - - type: bash - runs: echo "I was alone in my room." >> story.txt + - type: append-file + filename: story.txt + contents: "I was alone in my room.\n" - type: add files: - story.txt @@ -18,8 +20,9 @@ initialization: message: Describe location empty: false id: describe_location_commit - - type: bash - runs: echo "I heard a strange noise." >> story.txt + - type: append-file + filename: story.txt + contents: "I heard a strange noise.\n" - type: add files: - story.txt @@ -28,9 +31,10 @@ initialization: empty: false # visitor-line starts from wrong commit (first instead of second) - type: bash - runs: | - git checkout -b visitor-line HEAD~2 - echo "I heard someone knocking at the door." >> story.txt + runs: git checkout -b visitor-line HEAD~2 + - type: append-file + filename: story.txt + contents: "I heard someone knocking at the door.\n" - type: add files: - story.txt @@ -38,9 +42,10 @@ initialization: message: Mention knocking empty: false - type: bash - runs: | - git checkout -b sleep-line HEAD~1 - echo "I fell asleep on the couch." >> story.txt + runs: git checkout -b sleep-line HEAD~1 + - type: append-file + filename: story.txt + contents: "I fell asleep on the couch.\n" - type: add files: - story.txt diff --git a/branch_previous/tests/specs/visitor_wrong_start_third_commit.yml b/branch_previous/tests/specs/visitor_wrong_start_third_commit.yml index e718444..c2f027c 100644 --- a/branch_previous/tests/specs/visitor_wrong_start_third_commit.yml +++ b/branch_previous/tests/specs/visitor_wrong_start_third_commit.yml @@ -1,7 +1,8 @@ initialization: steps: - - type: bash - runs: echo "It was a dark and stormy night." > story.txt + - type: new-file + filename: story.txt + contents: "It was a dark and stormy night.\n" - type: add files: - story.txt @@ -9,8 +10,9 @@ initialization: message: Describe night empty: false id: start - - type: bash - runs: echo "I was alone in my room." >> story.txt + - type: append-file + filename: story.txt + contents: "I was alone in my room.\n" - type: add files: - story.txt @@ -18,8 +20,9 @@ initialization: message: Describe location empty: false id: describe_location_commit - - type: bash - runs: echo "I heard a strange noise." >> story.txt + - type: append-file + filename: story.txt + contents: "I heard a strange noise.\n" - type: add files: - story.txt @@ -28,9 +31,10 @@ initialization: empty: false # visitor-line starts from wrong commit (third instead of second) - type: bash - runs: | - git checkout -b visitor-line - echo "I heard someone knocking at the door." >> story.txt + runs: git checkout -b visitor-line + - type: append-file + filename: story.txt + contents: "I heard someone knocking at the door.\n" - type: add files: - story.txt @@ -38,9 +42,10 @@ initialization: message: Mention knocking empty: false - type: bash - runs: | - git checkout -b sleep-line HEAD~2 - echo "I fell asleep on the couch." >> story.txt + runs: git checkout -b sleep-line HEAD~2 + - type: append-file + filename: story.txt + contents: "I fell asleep on the couch.\n" - type: add files: - story.txt From 94c201613bb7d335dccad70f68d0f4b38dde6ee4 Mon Sep 17 00:00:00 2001 From: jiaxin Date: Mon, 1 Dec 2025 11:10:30 +0800 Subject: [PATCH 13/18] Use mermaid to render git graph --- branch_previous/README.md | 15 ++++++++++++++- branch_previous/download.py | 2 +- branch_previous/res/expected-tree.png | Bin 57334 -> 0 bytes 3 files changed, 15 insertions(+), 2 deletions(-) delete mode 100644 branch_previous/res/expected-tree.png diff --git a/branch_previous/README.md b/branch_previous/README.md index e3cc83b..4132134 100644 --- a/branch_previous/README.md +++ b/branch_previous/README.md @@ -17,4 +17,17 @@ You are not very happy with the way the story is progressing, and wish to explor ## Expected Revision Graph -![Expected Revision Graph](horror-story/expected-tree.png) +```mermaid +gitGraph BT: + commit id: "Describe night" + commit id: "Describe location" + branch visitor-line + checkout main + branch sleep-line + checkout main + commit id: "Mention noise" + checkout visitor-line + commit id: "Mention knocking" + checkout sleep-line + commit id: "Mention sleeping" +``` \ No newline at end of file diff --git a/branch_previous/download.py b/branch_previous/download.py index 4d3342d..ed1867c 100644 --- a/branch_previous/download.py +++ b/branch_previous/download.py @@ -1,7 +1,7 @@ from exercise_utils.file import create_or_update_file, append_to_file from exercise_utils.git import add, commit -__resources__ = {"expected-tree.png": "expected-tree.png"} +__resources__ = {} def setup(verbose: bool = False): diff --git a/branch_previous/res/expected-tree.png b/branch_previous/res/expected-tree.png deleted file mode 100644 index bf43f8b920c8562fd441e512f81cdcf4168a18e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57334 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 Nf{g0x(pPUj{$Ec_mVp2O From 9483824a39e3dd32a0ad8152755bf803e8f96755 Mon Sep 17 00:00:00 2001 From: jiaxin Date: Mon, 1 Dec 2025 11:23:29 +0800 Subject: [PATCH 14/18] Edit commit searching and matching logic --- branch_previous/verify.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/branch_previous/verify.py b/branch_previous/verify.py index 9c7f28d..d020bd0 100644 --- a/branch_previous/verify.py +++ b/branch_previous/verify.py @@ -4,6 +4,7 @@ GitAutograderOutput, GitAutograderExercise, GitAutograderStatus, + GitAutograderCommit, ) from git.objects.commit import Commit @@ -24,11 +25,12 @@ ) -def get_commit_from_message(exercise: GitAutograderExercise, message: str) -> Optional[Commit]: +def get_commit_from_message(exercise: GitAutograderExercise, message: str) -> Optional[GitAutograderCommit]: """Find a commit with the given message.""" - commits = list(exercise.repo.repo.iter_commits(all=True)) + commits = list(exercise.repo.branches.branch("main").commits) + print([commit.commit.message for commit in commits]) for commit in commits: - if message.strip() == commit.message.strip(): + if message.strip() == commit.commit.message.strip(): return commit return None @@ -53,11 +55,11 @@ def verify_branch( latest_commit = branch.latest_commit # Check that user made commits in the branch - if latest_commit.commit == expected_start_commit: + if latest_commit.commit == expected_start_commit.commit: raise exercise.wrong_answer([MISSING_COMMIT.format(branch_name=branch_name)]) # Check that previous commit of latest commit is the expected start commit - if expected_start_commit not in latest_commit.commit.parents: + if expected_start_commit.commit not in latest_commit.commit.parents: raise exercise.wrong_answer([WRONG_START.format(branch_name=branch_name)]) # Check that the expected content is in story.txt From bfbe0ce9bc228d1fcaec5d398dd0930b999c3869 Mon Sep 17 00:00:00 2001 From: jiaxin Date: Mon, 1 Dec 2025 11:29:08 +0800 Subject: [PATCH 15/18] Remove resource block --- branch_previous/download.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/branch_previous/download.py b/branch_previous/download.py index ed1867c..b49e3d9 100644 --- a/branch_previous/download.py +++ b/branch_previous/download.py @@ -1,9 +1,6 @@ from exercise_utils.file import create_or_update_file, append_to_file from exercise_utils.git import add, commit -__resources__ = {} - - def setup(verbose: bool = False): create_or_update_file( "story.txt", From 05af3f5d51454821683b5290b4995e3ae4ef8a34 Mon Sep 17 00:00:00 2001 From: jiaxin Date: Mon, 1 Dec 2025 11:38:44 +0800 Subject: [PATCH 16/18] Remove Commit import --- branch_previous/verify.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/branch_previous/verify.py b/branch_previous/verify.py index d020bd0..b93736e 100644 --- a/branch_previous/verify.py +++ b/branch_previous/verify.py @@ -7,8 +7,6 @@ GitAutograderCommit, ) -from git.objects.commit import Commit - MISSING_BRANCH = "The '{branch_name}' branch is missing." MISSING_COMMIT = "No commits were made in the '{branch_name}' branch." WRONG_START = ( From 02e4f052decfccdcea37603bfafff60bd0d5703a Mon Sep 17 00:00:00 2001 From: jiaxin Date: Mon, 1 Dec 2025 11:40:44 +0800 Subject: [PATCH 17/18] Use hexsha to compare commits --- branch_previous/verify.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/branch_previous/verify.py b/branch_previous/verify.py index b93736e..629ce47 100644 --- a/branch_previous/verify.py +++ b/branch_previous/verify.py @@ -26,7 +26,6 @@ def get_commit_from_message(exercise: GitAutograderExercise, message: str) -> Optional[GitAutograderCommit]: """Find a commit with the given message.""" commits = list(exercise.repo.branches.branch("main").commits) - print([commit.commit.message for commit in commits]) for commit in commits: if message.strip() == commit.commit.message.strip(): return commit @@ -35,7 +34,7 @@ def get_commit_from_message(exercise: GitAutograderExercise, message: str) -> Op def verify_branch( branch_name: str, - expected_start_commit: Commit, + expected_start_commit: GitAutograderCommit, expected_content: str, exercise: GitAutograderExercise, ) -> None: @@ -53,11 +52,11 @@ def verify_branch( latest_commit = branch.latest_commit # Check that user made commits in the branch - if latest_commit.commit == expected_start_commit.commit: + if latest_commit.hexsha == expected_start_commit.hexsha: raise exercise.wrong_answer([MISSING_COMMIT.format(branch_name=branch_name)]) # Check that previous commit of latest commit is the expected start commit - if expected_start_commit.commit not in latest_commit.commit.parents: + if expected_start_commit.hexsha not in [parent.hexsha for parent in latest_commit.commit.parents]: raise exercise.wrong_answer([WRONG_START.format(branch_name=branch_name)]) # Check that the expected content is in story.txt From af63ff1a66128c753a511f21c155b742da73b9db Mon Sep 17 00:00:00 2001 From: jia xin Date: Sat, 6 Dec 2025 11:37:47 +0800 Subject: [PATCH 18/18] Refactor get_commit_from_message --- branch_previous/verify.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/branch_previous/verify.py b/branch_previous/verify.py index 629ce47..99bbafb 100644 --- a/branch_previous/verify.py +++ b/branch_previous/verify.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import List, Optional from git_autograder import ( GitAutograderOutput, @@ -23,9 +23,8 @@ ) -def get_commit_from_message(exercise: GitAutograderExercise, message: str) -> Optional[GitAutograderCommit]: - """Find a commit with the given message.""" - commits = list(exercise.repo.branches.branch("main").commits) +def get_commit_from_message(commits: List[GitAutograderCommit], message: str) -> Optional[GitAutograderCommit]: + """Find a commit with the given message from a list of commits.""" for commit in commits: if message.strip() == commit.commit.message.strip(): return commit @@ -69,7 +68,8 @@ def verify_branch( def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - describe_location_commit = get_commit_from_message(exercise, "Describe location") + commits = list(exercise.repo.branches.branch("main").commits) + describe_location_commit = get_commit_from_message(commits, "Describe location") verify_branch( branch_name="visitor-line",