From f972c8b7cd4256f28287a7d72da78198ec532710 Mon Sep 17 00:00:00 2001 From: nimaldanyathk Date: Tue, 6 Jan 2026 09:15:23 +0530 Subject: [PATCH] feat: implement pit tag command --- .DS_Store | Bin 0 -> 8196 bytes pit-project/.DS_Store | Bin 0 -> 6148 bytes pit-project/commands/__init__.py | 1 + pit-project/commands/checkout.py | 25 ++++++++++++ pit-project/commands/tag.py | 51 ++++++++++++++++++++++++ pit-project/pit.py | 64 +++++++++++++++++-------------- pit-project/utils/repository.py | 13 +++++++ 7 files changed, 125 insertions(+), 29 deletions(-) create mode 100644 .DS_Store create mode 100644 pit-project/.DS_Store create mode 100644 pit-project/commands/tag.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..7bd0a14b9fd1f448b69030250385b6c2660664d0 GIT binary patch literal 8196 zcmeHMJxc>Y5S`Toq6sJ{A|iMemWshYa7x4>e>zqWS*3J)$MAJl+LuVY%V@POh=N@ZAc7}r$ z;;B|CZXN{nM$A(tL;+Di6c7bO0a4($D1bAYTQ23?H&z)%0a4&zD!}gt51rAs7#h^C z4h*^k0A?_)8?MLs0_a)vErteRK~p9aXhM}OF_a0%yl4GUeg`)KC zSl`p`q&LdZt30ADEs}tAph&Sf={A zTxSb~;|b_ZQK;vh)2%y7|3--apviVxRlE&)DG6k$lZ*iz8Xie|_g=5C#5@0>g&tG}r&@ z>EHkVPC~*|6c7alRzPJ+o24~`+1fg`0N2_c`Yt**_6rT_5De_X^ZE|~;P*caaqU@~ UZ!t6o3z~cg&@#v%3jC-7U#x}rivR!s literal 0 HcmV?d00001 diff --git a/pit-project/.DS_Store b/pit-project/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..736414bd67dde0950ab88f91a432ab5edbe7e8d4 GIT binary patch literal 6148 zcmeHKJ5Iwu5Ph2j5fq9@NQiX4LV5}l8F7FdAmjrCDaMf?1qIR;q2dCFi*N)^zyY`c z9W8HmSBZ_22pWXYOf>uK*_qw(Tg#IHU^=r=8)yQkQw1wK99D?Tip&ooPt!6X~dqdtGN^OO6R zqfOqUA9zp4`79zX%a?J83@LwoTw#RkLZ+4T$5lT093S331|i4NT4}b-Zj)ajud0>2 zwlKpDE;0F%5$^Viv-w;AyS?xE%EjliXdOOP%N~XO@h3*R(;fxC zIH2|D!(l@6VZzQPbSTE{&h;bb4ikISQ5jGMiVRf!ZBy$1@%sM17^HW~fHLr}7%;V@ zn{@a{p|+MDPHJsLJ*A3>pY^y1;e{*3^p#S4Ky^ZUBp1Xup!G-#MgIsm8gx(wewBew D*%NcA literal 0 HcmV?d00001 diff --git a/pit-project/commands/__init__.py b/pit-project/commands/__init__.py index f362b78..fe38807 100644 --- a/pit-project/commands/__init__.py +++ b/pit-project/commands/__init__.py @@ -17,3 +17,4 @@ # from . import push # from . import pull # from . import clone +from . import tag diff --git a/pit-project/commands/checkout.py b/pit-project/commands/checkout.py index 7e20dac..b0b2482 100644 --- a/pit-project/commands/checkout.py +++ b/pit-project/commands/checkout.py @@ -42,6 +42,31 @@ def run(args): sys.exit(1) return # Branch checkout successful + # Check if a tag + target_name = targets[0] + tags = repository.get_all_tags(repo_root) + if len(targets) == 1 and target_name in tags: + try: + commit_hash = repository.get_tag_commit(repo_root, target_name) + head_path = os.path.join(repo_root, '.pit', 'HEAD') + + # Detached HEAD state + with open(head_path, 'w') as f: + f.write(commit_hash) + + # Note: checkout.py currently does NOT update working directory. + # This is a limitation of the current checkout implementation. + + print(f"Note: checking out '{target_name}'.") + print("\nYou are in 'detached HEAD' state. You can look around, make experimental") + print("changes and commit them, and you can discard any commits you make in this") + print("state without impacting any branches by switching back to a branch.") + print(f"\nHEAD is now at {commit_hash[:7]}") + except Exception as e: + print(f"Error checking out tag: {e}", file=sys.stderr) + sys.exit(1) + return + # If not a single branch, assume it's one or more file paths print("Restoring file(s) from index...") index_path = os.path.join(repo_root, '.pit', 'index') diff --git a/pit-project/commands/tag.py b/pit-project/commands/tag.py new file mode 100644 index 0000000..7fa8ddb --- /dev/null +++ b/pit-project/commands/tag.py @@ -0,0 +1,51 @@ +# The command: pit tag [] +# What it does: Creates a lightweight tag ref pointing to the current HEAD, or lists existing tags. +# How it does: To create a tag, it gets the HEAD commit hash and writes it to a file in `.pit/refs/tags/`. To list, it reads that directory. +# What data structure it uses: Files references (similar to branches). + +import os +import sys +from utils import repository + +def run(args): + repo_root = repository.find_repo_root() + if not repo_root: + print("fatal: not a pit repository", file=sys.stderr) + sys.exit(1) + + if args.name: + create_tag(repo_root, args.name) + else: + list_tags(repo_root) + +def create_tag(repo_root, name): + # Validate tag name? (Simple check for now) + if not name or '/' in name or '\\' in name or name.startswith('.'): + print(f"fatal: Invalid tag name '{name}'", file=sys.stderr) + sys.exit(1) + + head_commit = repository.get_head_commit(repo_root) + if not head_commit: + print("fatal: Failed to resolve 'HEAD' as a valid revision.", file=sys.stderr) + sys.exit(1) + + tags_dir = os.path.join(repo_root, '.pit', 'refs', 'tags') + os.makedirs(tags_dir, exist_ok=True) + + tag_path = os.path.join(tags_dir, name) + if os.path.exists(tag_path): + print(f"fatal: tag '{name}' already exists", file=sys.stderr) + sys.exit(1) + + with open(tag_path, 'w') as f: + f.write(head_commit) + + print(f"Created tag '{name}' at {head_commit[:7]}") + +def list_tags(repo_root): + tags = repository.get_all_tags(repo_root) + if not tags: + return + + for tag in sorted(tags): + print(tag) diff --git a/pit-project/pit.py b/pit-project/pit.py index b9378ef..ba3ea34 100644 --- a/pit-project/pit.py +++ b/pit-project/pit.py @@ -2,7 +2,8 @@ from commands import ( init, add, commit, log, status, config, branch, checkout, diff, merge, reset, - revert, remote, push, pull, clone + revert, tag + # remote, push, pull, clone ) # The main entry point for the Pit version control system @@ -78,34 +79,39 @@ def main(): revert_parser.add_argument("commit_hash", help="The commit hash to revert.") revert_parser.set_defaults(func=revert.run) - # Command: remote - remote_parser = subparsers.add_parser("remote", help="Manage remote repositories (HTTPS only)") - remote_parser.add_argument("subcommand", help="Subcommand: add, remove, list, set-url") - remote_parser.add_argument("name", nargs="?", help="Remote name") - remote_parser.add_argument("url", nargs="?", help="HTTPS URL (e.g., https://github.com/user/repo.git)") - remote_parser.set_defaults(func=remote.run) - - # Command: push - push_parser = subparsers.add_parser("push", help="Push to remote repository via HTTPS") - push_parser.add_argument("remote", help="Remote name") - push_parser.add_argument("branch", help="Branch to push") - push_parser.add_argument("-u", "--set-upstream", action="store_true", - help="Set upstream branch tracking") - push_parser.add_argument("-f", "--force", action="store_true", - help="Force push (overwrite remote)") - push_parser.set_defaults(func=push.run) - - # Command: pull - pull_parser = subparsers.add_parser("pull", help="Fetch and integrate changes from remote") - pull_parser.add_argument("remote", help="Remote name") - pull_parser.add_argument("branch", help="Branch to pull") - pull_parser.set_defaults(func=pull.run) - - # Command: clone - clone_parser = subparsers.add_parser("clone", help="Clone a repository into a new directory.") - clone_parser.add_argument("repository_url", help="The HTTPS URL of the repository to clone.") - clone_parser.add_argument("directory", nargs="?", help="The name of the directory to clone into.") - clone_parser.set_defaults(func=clone.run) + # # Command: remote + # remote_parser = subparsers.add_parser("remote", help="Manage remote repositories (HTTPS only)") + # remote_parser.add_argument("subcommand", help="Subcommand: add, remove, list, set-url") + # remote_parser.add_argument("name", nargs="?", help="Remote name") + # remote_parser.add_argument("url", nargs="?", help="HTTPS URL (e.g., https://github.com/user/repo.git)") + # remote_parser.set_defaults(func=remote.run) + + # # Command: push + # push_parser = subparsers.add_parser("push", help="Push to remote repository via HTTPS") + # push_parser.add_argument("remote", help="Remote name") + # push_parser.add_argument("branch", help="Branch to push") + # push_parser.add_argument("-u", "--set-upstream", action="store_true", + # help="Set upstream branch tracking") + # push_parser.add_argument("-f", "--force", action="store_true", + # help="Force push (overwrite remote)") + # push_parser.set_defaults(func=push.run) + + # # Command: pull + # pull_parser = subparsers.add_parser("pull", help="Fetch and integrate changes from remote") + # pull_parser.add_argument("remote", help="Remote name") + # pull_parser.add_argument("branch", help="Branch to pull") + # pull_parser.set_defaults(func=pull.run) + + # # Command: clone + # clone_parser = subparsers.add_parser("clone", help="Clone a repository into a new directory.") + # clone_parser.add_argument("repository_url", help="The HTTPS URL of the repository to clone.") + # clone_parser.add_argument("directory", nargs="?", help="The name of the directory to clone into.") + # clone_parser.set_defaults(func=clone.run) + + # Command: tag + tag_parser = subparsers.add_parser("tag", help="Create, list, delete or verify a tag object signed with GPG.") + tag_parser.add_argument("name", nargs="?", help="The name of the tag to create.") + tag_parser.set_defaults(func=tag.run) # Parse the arguments args = parser.parse_args() diff --git a/pit-project/utils/repository.py b/pit-project/utils/repository.py index c5427fa..569f0e9 100644 --- a/pit-project/utils/repository.py +++ b/pit-project/utils/repository.py @@ -61,4 +61,17 @@ def get_branch_commit(repo_root, branch_name): # Retrieves the commit hash that if not os.path.exists(branch_path): return None with open(branch_path, 'r') as f: + return f.read().strip() + +def get_all_tags(repo_root): # Lists all tag names by reading the refs/tags directory + tags_dir = os.path.join(repo_root, '.pit', 'refs', 'tags') + if not os.path.isdir(tags_dir): + return [] + return [name for name in os.listdir(tags_dir) if not name.startswith('.')] + +def get_tag_commit(repo_root, tag_name): # Retrieves the commit hash that a given tag points to + tag_path = os.path.join(repo_root, '.pit', 'refs', 'tags', tag_name) + if not os.path.exists(tag_path): + return None + with open(tag_path, 'r') as f: return f.read().strip() \ No newline at end of file