From 2cbfa3557ecaced114acb4f5553a543bfc4826b4 Mon Sep 17 00:00:00 2001 From: Vikram Goyal Date: Sun, 7 Dec 2025 14:58:29 +0800 Subject: [PATCH 1/4] Add merge-squash scaffolding and add download --- merge_squash/.gitmastery-exercise.json | 18 +++++++++ merge_squash/README.md | 18 +++++++++ merge_squash/__init__.py | 0 merge_squash/download.py | 51 ++++++++++++++++++++++++++ merge_squash/tests/__init__.py | 0 merge_squash/tests/specs/base.yml | 6 +++ merge_squash/tests/test_verify.py | 12 ++++++ merge_squash/verify.py | 11 ++++++ 8 files changed, 116 insertions(+) create mode 100644 merge_squash/.gitmastery-exercise.json create mode 100644 merge_squash/README.md create mode 100644 merge_squash/__init__.py create mode 100644 merge_squash/download.py create mode 100644 merge_squash/tests/__init__.py create mode 100644 merge_squash/tests/specs/base.yml create mode 100644 merge_squash/tests/test_verify.py create mode 100644 merge_squash/verify.py diff --git a/merge_squash/.gitmastery-exercise.json b/merge_squash/.gitmastery-exercise.json new file mode 100644 index 0000000..3a2d700 --- /dev/null +++ b/merge_squash/.gitmastery-exercise.json @@ -0,0 +1,18 @@ +{ + "exercise_name": "merge-squash", + "tags": [ + "git-branch", + "git-merge", + "git-reset" + ], + "requires_git": true, + "requires_github": false, + "base_files": {}, + "exercise_repo": { + "repo_type": "local", + "repo_name": "friends-cast", + "repo_title": null, + "create_fork": null, + "init": true + } +} \ No newline at end of file diff --git a/merge_squash/README.md b/merge_squash/README.md new file mode 100644 index 0000000..e733be9 --- /dev/null +++ b/merge_squash/README.md @@ -0,0 +1,18 @@ +# merge-squash + + + +## Task + + + +## Hints + + + diff --git a/merge_squash/__init__.py b/merge_squash/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/merge_squash/download.py b/merge_squash/download.py new file mode 100644 index 0000000..b54a50c --- /dev/null +++ b/merge_squash/download.py @@ -0,0 +1,51 @@ +from exercise_utils.file import create_or_update_file +from exercise_utils.git import add, commit, checkout + + +def setup(verbose: bool = False): + create_or_update_file( + "joey.txt", + """ + Matt LeBlanc + """, + ) + add(["."], verbose) + commit("Add Joey", verbose) + + create_or_update_file( + "phoebe.txt", + """ + Lisa Kudrow + """, + ) + add(["."], verbose) + commit("Add Phoebe", verbose) + + checkout("supporting", True, verbose) + create_or_update_file( + "mike.txt", + """ + Paul Rudd + """, + ) + add(["."], verbose) + commit("Add Mike", verbose) + + create_or_update_file( + "janice.txt", + """ + Maggie Wheeler + """, + ) + add(["."], verbose) + commit("Add Janice", verbose) + + checkout("main", False, verbose) + create_or_update_file( + "ross.txt", + """ + David Schwimmer + """, + ) + add(["."], verbose) + commit("Add Ross", verbose) diff --git a/merge_squash/tests/__init__.py b/merge_squash/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/merge_squash/tests/specs/base.yml b/merge_squash/tests/specs/base.yml new file mode 100644 index 0000000..00c3a53 --- /dev/null +++ b/merge_squash/tests/specs/base.yml @@ -0,0 +1,6 @@ +initialization: + steps: + - type: commit + empty: true + message: Empty commit + id: start diff --git a/merge_squash/tests/test_verify.py b/merge_squash/tests/test_verify.py new file mode 100644 index 0000000..ce67711 --- /dev/null +++ b/merge_squash/tests/test_verify.py @@ -0,0 +1,12 @@ +from git_autograder import GitAutograderTestLoader + +from ..verify import verify + +REPOSITORY_NAME = "merge-squash" + +loader = GitAutograderTestLoader(__file__, REPOSITORY_NAME, verify) + + +def test_base(): + with loader.load("specs/base.yml", "start"): + pass diff --git a/merge_squash/verify.py b/merge_squash/verify.py new file mode 100644 index 0000000..1288d3d --- /dev/null +++ b/merge_squash/verify.py @@ -0,0 +1,11 @@ +from git_autograder import ( + GitAutograderOutput, + GitAutograderExercise, + GitAutograderStatus, +) + + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + # INSERT YOUR GRADING CODE HERE + + return exercise.to_output([], GitAutograderStatus.SUCCESSFUL) From dc4ad65ac1b2398a3e750ddb0bfed6e21dc83dd0 Mon Sep 17 00:00:00 2001 From: Vikram Goyal Date: Wed, 10 Dec 2025 14:32:58 +0800 Subject: [PATCH 2/4] Add autograding (incomplete) There seems to be some issue with the squash option for the merge step in repo-smith --- merge_squash/README.md | 42 +++++++++--- merge_squash/tests/specs/base.yml | 63 +++++++++++++++++- .../tests/specs/missing_main_commits.yml | 60 +++++++++++++++++ .../tests/specs/non_squash_merge_used.yml | 66 +++++++++++++++++++ merge_squash/tests/specs/not_merged.yml | 63 ++++++++++++++++++ merge_squash/tests/test_verify.py | 25 +++++-- merge_squash/verify.py | 42 +++++++++++- 7 files changed, 345 insertions(+), 16 deletions(-) create mode 100644 merge_squash/tests/specs/missing_main_commits.yml create mode 100644 merge_squash/tests/specs/non_squash_merge_used.yml create mode 100644 merge_squash/tests/specs/not_merged.yml diff --git a/merge_squash/README.md b/merge_squash/README.md index e733be9..df947c2 100644 --- a/merge_squash/README.md +++ b/merge_squash/README.md @@ -1,18 +1,40 @@ # merge-squash - +You are keeping notes on the cast of a sitcom you've started watching. Initially, you kept main cast and supporting cast on two separate branches. + +```mermaid +gitGraph BT: + commit id: "Add Joey" + commit id: "Add Phoebe" + branch supporting + checkout supporting + commit id: "Add Mike" + commit id: "Add Janice" + checkout main + commit id: "Add Ross" +``` + +Now you wish to keep everything in the `main` branch. ## Task - +Squash-merge the `supporting` branch onto the `main` branch. + +The result should look as follows: + +```mermaid +gitGraph BT: + commit id: "Add Joey" + commit id: "Add Phoebe" + branch supporting + checkout supporting + commit id: "Add Mike" + commit id: "Add Janice" + checkout main + commit id: "Add Ross" + commit id: "Squash commit" +``` ## Hints - - +You have to commit manually after performing the squash merge. \ No newline at end of file diff --git a/merge_squash/tests/specs/base.yml b/merge_squash/tests/specs/base.yml index 00c3a53..c42a04b 100644 --- a/merge_squash/tests/specs/base.yml +++ b/merge_squash/tests/specs/base.yml @@ -2,5 +2,66 @@ initialization: steps: - type: commit empty: true - message: Empty commit + message: Set initial state id: start + - type: new-file + filename: joey.txt + contents: | + Matt LeBlanc + - type: add + files: + - joey.txt + - type: commit + message: Add Joey + + - type: new-file + filename: phoebe.txt + contents: | + Lisa Kudrow + - type: add + files: + - phoebe.txt + - type: commit + message: Add Phoebe + + - type: branch + branch-name: supporting + - type: checkout + branch-name: supporting + + - type: new-file + filename: mike.txt + contents: | + Paul Rudd + - type: add + files: + - mike.txt + - type: commit + message: Add Mike + + - type: new-file + filename: janice.txt + contents: | + Maggie Wheeler + - type: add + files: + - janice.txt + - type: commit + message: Add Janice + + - type: checkout + branch-name: main + + - type: new-file + filename: ross.txt + contents: | + David Schwimmer + - type: add + files: + - ross.txt + - type: commit + message: Add Ross + + - type: merge + squash: true + branch-name: supporting diff --git a/merge_squash/tests/specs/missing_main_commits.yml b/merge_squash/tests/specs/missing_main_commits.yml new file mode 100644 index 0000000..655f64b --- /dev/null +++ b/merge_squash/tests/specs/missing_main_commits.yml @@ -0,0 +1,60 @@ +initialization: + steps: + - type: commit + empty: true + message: Set initial state + id: start + - type: new-file + filename: joey.txt + contents: | + Matt LeBlanc + - type: add + files: + - joey.txt + - type: commit + message: Add Joey + + - type: branch + branch-name: supporting + - type: checkout + branch-name: supporting + + - type: new-file + filename: mike.txt + contents: | + Paul Rudd + - type: add + files: + - mike.txt + - type: commit + message: Add Mike + + - type: new-file + filename: janice.txt + contents: | + Maggie Wheeler + - type: add + files: + - janice.txt + - type: commit + message: Add Janice + + - type: checkout + branch-name: main + + - type: new-file + filename: ross.txt + contents: | + David Schwimmer + - type: add + files: + - ross.txt + - type: commit + message: Add Ross + + - type: merge + branch-name: supporting + squash: true + + - type: commit + message: Squash commit diff --git a/merge_squash/tests/specs/non_squash_merge_used.yml b/merge_squash/tests/specs/non_squash_merge_used.yml new file mode 100644 index 0000000..71150c8 --- /dev/null +++ b/merge_squash/tests/specs/non_squash_merge_used.yml @@ -0,0 +1,66 @@ +initialization: + steps: + - type: commit + empty: true + message: Set initial state + id: start + - type: new-file + filename: joey.txt + contents: | + Matt LeBlanc + - type: add + files: + - joey.txt + - type: commit + message: Add Joey + + - type: new-file + filename: phoebe.txt + contents: | + Lisa Kudrow + - type: add + files: + - phoebe.txt + - type: commit + message: Add Phoebe + + - type: branch + branch-name: supporting + - type: checkout + branch-name: supporting + + - type: new-file + filename: mike.txt + contents: | + Paul Rudd + - type: add + files: + - mike.txt + - type: commit + message: Add Mike + + - type: new-file + filename: janice.txt + contents: | + Maggie Wheeler + - type: add + files: + - janice.txt + - type: commit + message: Add Janice + + - type: checkout + branch-name: main + + - type: new-file + filename: ross.txt + contents: | + David Schwimmer + - type: add + files: + - ross.txt + - type: commit + message: Add Ross + + - type: merge + branch-name: supporting diff --git a/merge_squash/tests/specs/not_merged.yml b/merge_squash/tests/specs/not_merged.yml new file mode 100644 index 0000000..e89e099 --- /dev/null +++ b/merge_squash/tests/specs/not_merged.yml @@ -0,0 +1,63 @@ +initialization: + steps: + - type: commit + empty: true + message: Set initial state + id: start + - type: new-file + filename: joey.txt + contents: | + Matt LeBlanc + - type: add + files: + - joey.txt + - type: commit + message: Add Joey + + - type: new-file + filename: phoebe.txt + contents: | + Lisa Kudrow + - type: add + files: + - phoebe.txt + - type: commit + message: Add Phoebe + + - type: branch + branch-name: supporting + - type: checkout + branch-name: supporting + + - type: new-file + filename: mike.txt + contents: | + Paul Rudd + - type: add + files: + - mike.txt + - type: commit + message: Add Mike + + - type: new-file + filename: janice.txt + contents: | + Maggie Wheeler + - type: add + files: + - janice.txt + - type: commit + message: Add Janice + + - type: checkout + branch-name: main + + - type: new-file + filename: ross.txt + contents: | + David Schwimmer + - type: add + files: + - ross.txt + - type: commit + message: Add Ross diff --git a/merge_squash/tests/test_verify.py b/merge_squash/tests/test_verify.py index ce67711..bb57c95 100644 --- a/merge_squash/tests/test_verify.py +++ b/merge_squash/tests/test_verify.py @@ -1,6 +1,11 @@ -from git_autograder import GitAutograderTestLoader +from git_autograder import GitAutograderStatus, GitAutograderTestLoader, assert_output -from ..verify import verify +from ..verify import ( + SQUASH_NOT_USED, + MAIN_COMMITS_INCORRECT, + CHANGES_FROM_SUPPORTING_NOT_PRESENT, + verify +) REPOSITORY_NAME = "merge-squash" @@ -8,5 +13,17 @@ def test_base(): - with loader.load("specs/base.yml", "start"): - pass + with loader.load("specs/base.yml") as output: + assert_output(output, GitAutograderStatus.SUCCESSFUL) + +def test_non_squash_merge_used(): + with loader.load("specs/non_squash_merge_used.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [SQUASH_NOT_USED]) + +def test_not_merged(): + with loader.load("specs/not_merged.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [CHANGES_FROM_SUPPORTING_NOT_PRESENT]) + +def test_missing_main_commits(): + with loader.load("specs/missing_main_commits.yml") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [MAIN_COMMITS_INCORRECT]) diff --git a/merge_squash/verify.py b/merge_squash/verify.py index 1288d3d..95dc927 100644 --- a/merge_squash/verify.py +++ b/merge_squash/verify.py @@ -4,8 +4,48 @@ GitAutograderStatus, ) +ADD_JOEY = "Add Joey" +ADD_PHOEBE = "Add Phoebe" +ADD_ROSS = "Add Ross" + +SQUASH_NOT_USED = ( + "You should be using squash merge, not regular merge." +) + +MAIN_COMMITS_INCORRECT = ( + "The main branch does not contain at least one of these commits: 'Add Joey', 'Add Phoebe' or 'Add Ross'." +) + +CHANGES_FROM_SUPPORTING_NOT_PRESENT = ( + "The main branch does not contain both files 'mike.txt' and 'janice.txt'." +) + def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - # INSERT YOUR GRADING CODE HERE + main_branch = exercise.repo.branches.branch("main") + + commit_messages_in_main = [c.commit.message.strip() for c in main_branch.commits] + + merge_commits = [c for c in main_branch.commits if len(c.parents) > 1] + if merge_commits: + print("Yo" + merge_commits[0].commit.message) + print(commit_messages_in_main) + print(merge_commits[0].parents[0].commit.message) + print(merge_commits[0].parents[1].commit.message) + print(main_branch.commits[0].stats.files) + raise exercise.wrong_answer([SQUASH_NOT_USED]) + + with exercise.repo.files.file_or_none("mike.txt") as mike_file: + if mike_file is None: + raise exercise.wrong_answer([CHANGES_FROM_SUPPORTING_NOT_PRESENT]) + + with exercise.repo.files.file_or_none("janice.txt") as janice_file: + if janice_file is None: + raise exercise.wrong_answer([CHANGES_FROM_SUPPORTING_NOT_PRESENT]) + + if not all( + msg in commit_messages_in_main for msg in [ADD_JOEY, ADD_PHOEBE, ADD_ROSS] + ): + raise exercise.wrong_answer([MAIN_COMMITS_INCORRECT]) return exercise.to_output([], GitAutograderStatus.SUCCESSFUL) From e39ecf5d7d09598f0b8f599c496942cd08cd3089 Mon Sep 17 00:00:00 2001 From: Vikram Goyal Date: Wed, 10 Dec 2025 15:08:07 +0800 Subject: [PATCH 3/4] Clean up autograding --- merge_squash/tests/test_verify.py | 3 +++ merge_squash/verify.py | 11 +---------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/merge_squash/tests/test_verify.py b/merge_squash/tests/test_verify.py index bb57c95..ec45c00 100644 --- a/merge_squash/tests/test_verify.py +++ b/merge_squash/tests/test_verify.py @@ -16,14 +16,17 @@ def test_base(): with loader.load("specs/base.yml") as output: assert_output(output, GitAutograderStatus.SUCCESSFUL) + def test_non_squash_merge_used(): with loader.load("specs/non_squash_merge_used.yml") as output: assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [SQUASH_NOT_USED]) + def test_not_merged(): with loader.load("specs/not_merged.yml") as output: assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [CHANGES_FROM_SUPPORTING_NOT_PRESENT]) + def test_missing_main_commits(): with loader.load("specs/missing_main_commits.yml") as output: assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [MAIN_COMMITS_INCORRECT]) diff --git a/merge_squash/verify.py b/merge_squash/verify.py index 95dc927..dfde6c5 100644 --- a/merge_squash/verify.py +++ b/merge_squash/verify.py @@ -4,10 +4,6 @@ GitAutograderStatus, ) -ADD_JOEY = "Add Joey" -ADD_PHOEBE = "Add Phoebe" -ADD_ROSS = "Add Ross" - SQUASH_NOT_USED = ( "You should be using squash merge, not regular merge." ) @@ -28,11 +24,6 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: merge_commits = [c for c in main_branch.commits if len(c.parents) > 1] if merge_commits: - print("Yo" + merge_commits[0].commit.message) - print(commit_messages_in_main) - print(merge_commits[0].parents[0].commit.message) - print(merge_commits[0].parents[1].commit.message) - print(main_branch.commits[0].stats.files) raise exercise.wrong_answer([SQUASH_NOT_USED]) with exercise.repo.files.file_or_none("mike.txt") as mike_file: @@ -44,7 +35,7 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: raise exercise.wrong_answer([CHANGES_FROM_SUPPORTING_NOT_PRESENT]) if not all( - msg in commit_messages_in_main for msg in [ADD_JOEY, ADD_PHOEBE, ADD_ROSS] + msg in commit_messages_in_main for msg in ["Add Joey", "Add Phoebe", "Add Ross"] ): raise exercise.wrong_answer([MAIN_COMMITS_INCORRECT]) From c9629ffbb94eee7803f9773cc9e27861d559fdd1 Mon Sep 17 00:00:00 2001 From: Vikram Goyal Date: Thu, 11 Dec 2025 17:10:14 +0800 Subject: [PATCH 4/4] Fix verification logic and add message --- merge_squash/verify.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/merge_squash/verify.py b/merge_squash/verify.py index dfde6c5..df0f085 100644 --- a/merge_squash/verify.py +++ b/merge_squash/verify.py @@ -20,12 +20,6 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: main_branch = exercise.repo.branches.branch("main") - commit_messages_in_main = [c.commit.message.strip() for c in main_branch.commits] - - merge_commits = [c for c in main_branch.commits if len(c.parents) > 1] - if merge_commits: - raise exercise.wrong_answer([SQUASH_NOT_USED]) - with exercise.repo.files.file_or_none("mike.txt") as mike_file: if mike_file is None: raise exercise.wrong_answer([CHANGES_FROM_SUPPORTING_NOT_PRESENT]) @@ -34,9 +28,15 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: if janice_file is None: raise exercise.wrong_answer([CHANGES_FROM_SUPPORTING_NOT_PRESENT]) + commit_messages_in_main = [c.commit.message.strip() for c in main_branch.commits] + merge_commits = [c for c in main_branch.commits if len(c.parents) > 1] + + if merge_commits or ("Squash" not in commit_messages_in_main[0]): + raise exercise.wrong_answer([SQUASH_NOT_USED]) + if not all( msg in commit_messages_in_main for msg in ["Add Joey", "Add Phoebe", "Add Ross"] ): raise exercise.wrong_answer([MAIN_COMMITS_INCORRECT]) - return exercise.to_output([], GitAutograderStatus.SUCCESSFUL) + return exercise.to_output(["Good job performing a merge squash!"], GitAutograderStatus.SUCCESSFUL)