Skip to content
Open
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
Binary file added .DS_Store
Binary file not shown.
Binary file added pit-project/.DS_Store
Binary file not shown.
1 change: 1 addition & 0 deletions pit-project/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@
# from . import pull
# from . import clone
from . import stash
from . import tag
61 changes: 58 additions & 3 deletions pit-project/commands/checkout.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,23 @@ def run(args):
elif len(targets) == 1 and _is_branch(repo_root, targets[0]):
handle_branch_checkout(repo_root, targets[0])

# Case 3: checkout <file>...
# Case 3: checkout <tag>
elif len(targets) == 1 and _is_tag(repo_root, targets[0]):
handle_tag_checkout(repo_root, targets[0])

# Case 4: checkout <file>...
else:
handle_file_restore(repo_root, targets)

def _is_branch(repo_root, name):
branches = repository.get_all_branches(repo_root)
return name in branches

def _is_tag(repo_root, name):
tags = repository.get_all_tags(repo_root)
return name in tags


def handle_create_and_checkout(repo_root, branch_name):
# 1. Check if branch already exists
if _is_branch(repo_root, branch_name):
Expand Down Expand Up @@ -71,6 +80,42 @@ def handle_branch_checkout(repo_root, branch_name):

perform_checkout(repo_root, branch_name)



def handle_tag_checkout(repo_root, tag_name):
# Similar to branch checkout, but results in detached HEAD
# 1. Validate clean
if not is_clean(repo_root):
print("error: Your local changes would be overwritten by checkout.", file=sys.stderr)
sys.exit(1)

# 2. Get commit
commit_hash = repository.get_tag_commit(repo_root, tag_name)
if not commit_hash:
print(f"fatal: tag '{tag_name}' not found.", file=sys.stderr)
sys.exit(1)

# 3. Update Workdir & Index
# Reuse perform_checkout logic's parts
target_files = objects.get_commit_files(repo_root, commit_hash)
current_commit = repository.get_head_commit(repo_root)
# If in detached HEAD or initial state
current_files = objects.get_commit_files(repo_root, current_commit) if current_commit else {}

update_working_directory(repo_root, current_files, target_files)
update_index(repo_root, target_files)

# 4. Detached HEAD
head_path = os.path.join(repo_root, '.pit', 'HEAD')
with open(head_path, 'w') as f:
f.write(commit_hash)

print(f"Note: checking out '{tag_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]}")

def perform_checkout(repo_root, target_branch):
# 1. Validate clean state
if not is_clean(repo_root):
Expand Down Expand Up @@ -154,13 +199,23 @@ def is_clean(repo_root):
return True

def load_index(repo_root):

index_path = os.path.join(repo_root, '.pit', 'index')
index_files = {}
if os.path.exists(index_path):
with open(index_path, 'r') as f:
for line in f:
hash_val, path = line.strip().split(' ', 1)
index_files[path] = hash_val
parts = line.strip().split(' ')
if len(parts) >= 4:
# New format: hash mtime size path
hash_val = parts[0]
# mtime = parts[1], size = parts[2] - not used here
path = " ".join(parts[3:])
index_files[path] = hash_val
else:
# Old format
hash_val, path = line.strip().split(' ', 1)
index_files[path] = hash_val
return index_files

def update_working_directory(repo_root, current_files, target_files):
Expand Down
51 changes: 51 additions & 0 deletions pit-project/commands/tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# The command: pit tag [<name>]
# 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/<name>`. 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)
17 changes: 14 additions & 3 deletions pit-project/pit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from commands import (
init, add, commit, log, status, config,
branch, checkout, diff, merge, reset,
revert, clean,
revert, clean, stash, tag
# remote, push, pull, clone
)

Expand Down Expand Up @@ -87,6 +87,12 @@ def main():
clean_parser.add_argument("-d", action="store_true", help="Remove untracked directories as well.")
clean_parser.set_defaults(func=clean.run)

# Command: stash
stash_parser = subparsers.add_parser("stash", help="Stash the changes in a dirty working directory away.")
stash_parser.add_argument("action", nargs="?", choices=["push", "list", "pop", "show", "drop", "clear"], default="push", help="The action to perform (push, list, pop, show, drop, clear)")
stash_parser.add_argument("stash_arg", nargs="?", help="Stash index (e.g. stash@{0}) or message for push")
stash_parser.set_defaults(func=stash.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")
Expand All @@ -99,9 +105,9 @@ def main():
# 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")
# help="Set upstream branch tracking")
# push_parser.add_argument("-f", "--force", action="store_true",
# # help="Force push (overwrite remote)")
# help="Force push (overwrite remote)")
# push_parser.set_defaults(func=push.run)

# # Command: pull
Expand All @@ -116,6 +122,11 @@ def main():
# 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()

Expand Down
4 changes: 2 additions & 2 deletions pit-project/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ def write_config(key, value): # Sets a configuration key to a value and writes i
raise FileNotFoundError("Not a Pit repository.")

config_path = get_config_path(repo_root)
os.makedirs(os.path.dirname(config_path), exist_ok=True)
config = configparser.ConfigParser()
os.makedirs(os.path.dirname(config_path), exist_ok=True)
config = configparser.ConfigParser()

if os.path.exists(config_path):
config.read(config_path)
Expand Down
13 changes: 13 additions & 0 deletions pit-project/utils/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()