From 7c554f2c40c37f46f9aadaaffaf9a48cb21aa9a7 Mon Sep 17 00:00:00 2001 From: dingZvel Date: Sat, 1 Nov 2025 18:26:10 +0800 Subject: [PATCH 1/4] Implement hands on hp-push-tags --- exercise_utils/git.py | 15 +++++++++++++++ hands_on/push_tags.py | 26 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 hands_on/push_tags.py diff --git a/exercise_utils/git.py b/exercise_utils/git.py index 5df9919..8e2bb25 100644 --- a/exercise_utils/git.py +++ b/exercise_utils/git.py @@ -10,6 +10,21 @@ def tag(tag_name: str, verbose: bool) -> None: run_command(["git", "tag", tag_name], verbose) +def annotated_tag(tag_name: str, verbose: bool) -> None: + """Adds an annotated tag to the latest commit with the given tag_name.""" + run_command(["git", "tag", "-a", tag_name], verbose) + + +def tag_with_options(tag_name: str, options: List[str], verbose: bool) -> None: + """Tags with the given tag_name with specified options.""" + run_command(["git", "tag", tag_name, *options], verbose) + + +def annotated_tag_with_options(tag_name: str, options: List[str], verbose: bool) -> None: + """Adds an annotated tag with the given tag_name with specified options.""" + run_command(["git", "tag", "-a", tag_name, *options], verbose) + + def add(files: List[str], verbose: bool) -> None: """Adds a given list of file paths.""" run_command(["git", "add", *files], verbose) diff --git a/hands_on/push_tags.py b/hands_on/push_tags.py new file mode 100644 index 0000000..19a3ac3 --- /dev/null +++ b/hands_on/push_tags.py @@ -0,0 +1,26 @@ +import os +from exercise_utils.cli import run_command +from exercise_utils.git import tag_with_options, annotated_tag_with_options + +__requires_git__ = True +__requires_github__ = True + +REPO_NAME = "samplerepo-preferences" + + +def download(verbose: bool): + + run_command(["gh", "repo", "fork", f"git-mastery/{REPO_NAME}", "--clone"], + verbose) + + if os.path.isdir(REPO_NAME): + os.chdir(REPO_NAME) + else: + # If user already has an repo with the same name, the gh command will + # atomatically create the fork with the name appended with "-1" + os.chdir(f"{REPO_NAME}-1") + + tag_with_options("v1.0", ["HEAD~1"], verbose) + annotated_tag_with_options("v0.9", ["HEAD~2", "-m", "First beta release"], verbose) + + pass From 598a3b8f13f4ed24c1d43bbdd12ad9ab9db26d80 Mon Sep 17 00:00:00 2001 From: dingZvel Date: Sat, 1 Nov 2025 18:56:30 +0800 Subject: [PATCH 2/4] Fix the cloned repo directory name to REPO_NAME --- hands_on/push_tags.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/hands_on/push_tags.py b/hands_on/push_tags.py index 19a3ac3..886c6b0 100644 --- a/hands_on/push_tags.py +++ b/hands_on/push_tags.py @@ -10,15 +10,10 @@ def download(verbose: bool): - run_command(["gh", "repo", "fork", f"git-mastery/{REPO_NAME}", "--clone"], + run_command(["gh", "repo", "fork", f"git-mastery/{REPO_NAME}", REPO_NAME, "--clone"], verbose) - if os.path.isdir(REPO_NAME): - os.chdir(REPO_NAME) - else: - # If user already has an repo with the same name, the gh command will - # atomatically create the fork with the name appended with "-1" - os.chdir(f"{REPO_NAME}-1") + os.chdir(REPO_NAME) tag_with_options("v1.0", ["HEAD~1"], verbose) annotated_tag_with_options("v0.9", ["HEAD~2", "-m", "First beta release"], verbose) From 5dc73ff3b1bb4e9c142a4db6076c5afd9e60dd77 Mon Sep 17 00:00:00 2001 From: dingZvel Date: Mon, 3 Nov 2025 00:23:52 +0800 Subject: [PATCH 3/4] Notify user when fork name is changed --- hands_on/push_tags.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/hands_on/push_tags.py b/hands_on/push_tags.py index 886c6b0..c4b6a22 100644 --- a/hands_on/push_tags.py +++ b/hands_on/push_tags.py @@ -1,4 +1,5 @@ import os +import subprocess from exercise_utils.cli import run_command from exercise_utils.git import tag_with_options, annotated_tag_with_options @@ -10,8 +11,24 @@ def download(verbose: bool): - run_command(["gh", "repo", "fork", f"git-mastery/{REPO_NAME}", REPO_NAME, "--clone"], - verbose) + # Cannot use run_command function because returned object should not be None. + username = subprocess.run( + ["gh", "api", "user", "-q", ".login"], + capture_output=True, + text=True, + ).stdout.strip() + + # Cannot use run_command function because return code is needed. + check_repo = subprocess.run( + ["gh", "repo", "view", f"{username}/{REPO_NAME}"], + capture_output=True, + text=True, + ) + + if check_repo.returncode == 0: + print(f"{username}/{REPO_NAME} already exists, the fork repo will be named as {username}/{REPO_NAME}-1") + + run_command(["gh", "repo", "fork", f"git-mastery/{REPO_NAME}", REPO_NAME, "--clone"], verbose) os.chdir(REPO_NAME) From d851e1d0dc0c5890007bc34d84ea4a2ba98a07b7 Mon Sep 17 00:00:00 2001 From: dingZvel Date: Mon, 3 Nov 2025 01:41:50 +0800 Subject: [PATCH 4/4] Abort script when a fork already exists --- hands_on/push_tags.py | 44 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/hands_on/push_tags.py b/hands_on/push_tags.py index c4b6a22..533b24b 100644 --- a/hands_on/push_tags.py +++ b/hands_on/push_tags.py @@ -8,25 +8,55 @@ REPO_NAME = "samplerepo-preferences" - -def download(verbose: bool): - +def get_username() -> str: # Cannot use run_command function because returned object should not be None. - username = subprocess.run( + return subprocess.run( ["gh", "api", "user", "-q", ".login"], capture_output=True, text=True, ).stdout.strip() + +def check_same_repo_name(username: str, repo_name: str) -> None: # Cannot use run_command function because return code is needed. check_repo = subprocess.run( - ["gh", "repo", "view", f"{username}/{REPO_NAME}"], + ["gh", "repo", "view", f"{username}/{repo_name}"], capture_output=True, text=True, ) - if check_repo.returncode == 0: - print(f"{username}/{REPO_NAME} already exists, the fork repo will be named as {username}/{REPO_NAME}-1") + print(f"Warning: {username}/{REPO_NAME} already exists, the fork repo will be " + f"named as {username}/{REPO_NAME}-1") + +def check_existing_fork(username: str, fork_owner_name: str, repo_name: str) -> None: + try: + result = subprocess.run( + ["gh", + "api", + f"repos/{fork_owner_name}/{repo_name}/forks", + "-q", + f'''.[] | .owner.login | select(. =="{username}")''', + ], + capture_output=True, + text=True, + check=True, + ) + if result.stdout == username: + print(f"ERROR: A fork of {fork_owner_name}/{repo_name} already exists! " + "Please delete the fork and run this download operation again.\n" + "!Aborting...") + exit(1) + except subprocess.CalledProcessError as e: + print(e.stderr) + exit(1) + + + + +def download(verbose: bool): + username = get_username() + check_existing_fork(username, "git-mastery", REPO_NAME) + check_same_repo_name(username, REPO_NAME) run_command(["gh", "repo", "fork", f"git-mastery/{REPO_NAME}", REPO_NAME, "--clone"], verbose)