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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions glossary_branch_pull/.gitmastery-exercise.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"exercise_name": "glossary-branch-pull",
"tags": [
"git-push"
],
"requires_git": true,
"requires_github": true,
"base_files": {},
"exercise_repo": {
"repo_type": "local",
"repo_name": "funny-glossary",
"repo_title": null,
"create_fork": null,
"init": false
}
}
1 change: 1 addition & 0 deletions glossary_branch_pull/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See https://git-mastery.github.io/lessons/remoteBranchPull/exercise-glossary-branch-pull.html
Empty file.
40 changes: 40 additions & 0 deletions glossary_branch_pull/download.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from exercise_utils.cli import run_command
from exercise_utils.file import create_or_update_file
from exercise_utils.git import add, checkout, commit, remove_remote
from exercise_utils.github_cli import (
clone_repo_with_gh,
get_github_username,
fork_repo,
has_repo,
delete_repo,
)

TARGET_REPO = "git-mastery/samplerepo-funny-glossary"
FORK_NAME = "gitmastery-samplerepo-funny-glossary"


def setup(verbose: bool = False):
username = get_github_username(verbose)
full_repo_name = f"{username}/{FORK_NAME}"

if has_repo(full_repo_name, True, verbose):
delete_repo(full_repo_name, verbose)

fork_repo(TARGET_REPO, FORK_NAME, verbose, False)
clone_repo_with_gh(f"{username}/{FORK_NAME}", verbose, ".")
remove_remote("upstream", verbose)

run_command(["git", "branch", "-dr", "origin/VWX"], verbose)

checkout("ABC", False, verbose)
run_command(["git", "reset", "--hard", "HEAD~1"], verbose)

checkout("DEF", False, verbose)
run_command(["git", "reset", "--hard", "HEAD~1"], verbose)
create_or_update_file(
"d.txt",
"""
documentation: Evidence that someone once cared.
""")
add(["d.txt"], verbose)
commit("Add 'documentation'", verbose)
113 changes: 113 additions & 0 deletions glossary_branch_pull/test_verify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from contextlib import contextmanager
from typing import Iterator, Tuple

from exercise_utils.test import GitAutograderTest, GitAutograderTestLoader, assert_output
from git_autograder import GitAutograderStatus
from repo_smith.repo_smith import RepoSmith

from .verify import (
verify,
BRANCH_MISSING,
BRANCH_NOT_TRACKING,
REMOTE_COMMIT_MISSING,
LOCAL_COMMIT_MISSING,
)

REPOSITORY_NAME = "glossary-branch-pull"

loader = GitAutograderTestLoader(REPOSITORY_NAME, verify)


@contextmanager
def base_setup() -> Iterator[Tuple[GitAutograderTest, RepoSmith]]:
with loader.start(include_remote_repo=True) as (test, rs, rs_remote):
remote_path = str(rs_remote.repo.git_dir)
rs.git.remote_add("origin", remote_path)

rs.git.commit(allow_empty=True, message="Initial commit")

rs.git.checkout("ABC", branch=True)
rs.git.commit(allow_empty=True, message="Add 'cache'")
rs.git.push("origin", "ABC", set_upstream=True)
rs.git.reset("HEAD~1", hard=True)

rs.git.checkout("DEF", branch=True)
rs.git.commit(allow_empty=True, message="Add 'exception'")
rs.git.push("origin", "DEF", set_upstream=True)
rs.git.reset("HEAD~1", hard=True)
rs.git.commit(allow_empty=True, message="Add 'documentation'")

yield (test, rs)

def test_no_changes():
with base_setup() as (test, rs):
output = test.run()
assert_output(
output,
GitAutograderStatus.UNSUCCESSFUL,
[
BRANCH_MISSING.format(branch="STU"),
BRANCH_MISSING.format(branch="VWX"),
REMOTE_COMMIT_MISSING.format(branch="ABC"),
REMOTE_COMMIT_MISSING.format(branch="DEF")
])


def test_branch_not_tracking():
with base_setup() as (test, rs):
rs.git.checkout("STU", branch=True)
rs.git.checkout("VWX", branch=True)

output = test.run()
assert_output(
output,
GitAutograderStatus.UNSUCCESSFUL,
[
BRANCH_NOT_TRACKING.format(branch="STU"),
BRANCH_NOT_TRACKING.format(branch="VWX"),
REMOTE_COMMIT_MISSING.format(branch="ABC"),
REMOTE_COMMIT_MISSING.format(branch="DEF")
])


def test_def_local_commit_missing():
with base_setup() as (test, rs):
rs.git.checkout("STU", branch=True)
rs.git.push("origin", "STU")

rs.git.checkout("VWX", branch=True)
rs.git.push("origin", "VWX")

rs.git.checkout("ABC")
rs.git.commit(allow_empty=True, message="Add 'cache'")

rs.git.checkout("DEF")
rs.git.reset("HEAD~1", hard=True)
rs.git.fetch("origin")
rs.git.merge("origin/DEF")

output = test.run()
assert_output(
output,
GitAutograderStatus.UNSUCCESSFUL,
[LOCAL_COMMIT_MISSING])


def test_successful_changes():
with base_setup() as (test, rs):
rs.git.checkout("VWX", branch=True)
rs.git.push("origin", "VWX", set_upstream=True)

rs.git.checkout("STU", branch=True)
rs.git.push("origin", "STU", set_upstream=True)

rs.git.checkout("ABC")
rs.git.fetch("origin")
rs.git.merge("origin/ABC")

rs.git.checkout("DEF")
rs.git.fetch("origin")
rs.git.merge("origin/DEF")

output = test.run()
assert_output(output, GitAutograderStatus.SUCCESSFUL)
80 changes: 80 additions & 0 deletions glossary_branch_pull/verify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from typing import List
from git_autograder import (
GitAutograderCommit,
GitAutograderOutput,
GitAutograderExercise,
GitAutograderStatus,
)

BRANCH_MISSING = "The local {branch} branch is not created."
BRANCH_NOT_TRACKING = "The local {branch} branch does not track origin/{branch}."
REMOTE_COMMIT_MISSING = "New commit in the remote {branch} branch is not pulled to the local {branch} branch."
LOCAL_COMMIT_MISSING = "The original local commit on DEF is missing. " \
"You may have lost your work instead of merging."


def get_commit_from_message(commits: List[GitAutograderCommit], message: str) \
-> GitAutograderCommit | None:
"""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
return None

def get_commit_from_hexsha(commits: List[GitAutograderCommit], hexsha: str) \
-> GitAutograderCommit | None:
"""Find a commit with the given hexsha from a list of commits."""
for commit in commits:
if hexsha.strip() == commit.commit.hexsha.strip():
return commit
return None

def verify(exercise: GitAutograderExercise) -> GitAutograderOutput:
repo = exercise.repo
comments = []

if not repo.branches.has_branch("STU"):
comments.append(BRANCH_MISSING.format(branch="STU"))
else:
stu_branch = repo.branches.branch("STU").branch
remote_stu = stu_branch.tracking_branch()
if not remote_stu or remote_stu.name != "origin/STU":
comments.append(BRANCH_NOT_TRACKING.format(branch="STU"))

if not repo.branches.has_branch("VWX"):
comments.append(BRANCH_MISSING.format(branch="VWX"))
else:
vwx_branch = repo.branches.branch("VWX").branch
remote_vwx = vwx_branch.tracking_branch()
if not remote_vwx or remote_vwx.name != "origin/VWX":
comments.append(BRANCH_NOT_TRACKING.format(branch="VWX"))

if not repo.branches.has_branch("ABC"):
comments.append(BRANCH_MISSING.format(branch="ABC"))
else:
abc_commits = repo.branches.branch("ABC").commits
abc_branch = repo.branches.branch("ABC").branch
remote_abc = abc_branch.tracking_branch()
if not remote_abc or remote_abc.name != "origin/ABC":
comments.append(BRANCH_NOT_TRACKING.format(branch="ABC"))
elif not get_commit_from_hexsha(abc_commits, remote_abc.commit.hexsha):
comments.append(REMOTE_COMMIT_MISSING.format(branch="ABC"))

if not repo.branches.has_branch("DEF"):
comments.append(BRANCH_MISSING.format(branch="DEF"))
else:
def_commits = repo.branches.branch("DEF").commits
if not get_commit_from_message(def_commits, "Add 'documentation'"):
comments.append(LOCAL_COMMIT_MISSING)
def_branch = repo.branches.branch("DEF").branch
remote_def = def_branch.tracking_branch()
if not remote_def or remote_def.name != "origin/DEF":
comments.append(BRANCH_NOT_TRACKING.format(branch="DEF"))
elif not get_commit_from_hexsha(def_commits, remote_def.commit.hexsha):
comments.append(REMOTE_COMMIT_MISSING.format(branch="DEF"))

if comments:
raise exercise.wrong_answer(comments)
return exercise.to_output([
"Great work! All required branches are present and correctly set up."
], GitAutograderStatus.SUCCESSFUL)