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
90 changes: 90 additions & 0 deletions src/tinyleaf/git_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,96 @@ def fetch(project_dir):
return {"success": True, "message": (out + err).strip()}


def switch_branch(project_dir, branch):
"""Switch to an existing branch.

Returns:
Dict with success status and message.
"""
if not has_git(project_dir):
return {"success": False, "message": "Not a git repository"}

rc, out, err = _run_git(project_dir, "switch", branch)
if rc != 0:
return {"success": False, "message": (err or out).strip()}
return {"success": True, "message": (out + err).strip()}


def create_branch(project_dir, name, start_point=None):
"""Create and switch to a new branch.

Returns:
Dict with success status and message.
"""
if not has_git(project_dir):
return {"success": False, "message": "Not a git repository"}

# Validate branch name
rc, _, err = _run_git(project_dir, "check-ref-format", "--branch", name)
if rc != 0:
return {"success": False, "message": f"Invalid branch name: {name}"}

args = ["switch", "-c", name]
if start_point:
args.append(start_point)

rc, out, err = _run_git(project_dir, *args)
if rc != 0:
return {"success": False, "message": (err or out).strip()}
return {"success": True, "message": (out + err).strip()}


def delete_branch(project_dir, name, force=False):
"""Delete a branch.

Returns:
Dict with success status and message.
"""
if not has_git(project_dir):
return {"success": False, "message": "Not a git repository"}

# Refuse to delete the current branch
rc, out, _ = _run_git(project_dir, "branch", "--show-current")
if rc == 0 and out.strip() == name:
return {"success": False, "message": f"Cannot delete the current branch: {name}"}

flag = "-D" if force else "-d"
rc, out, err = _run_git(project_dir, "branch", flag, name)
if rc != 0:
return {"success": False, "message": (err or out).strip()}
return {"success": True, "message": (out + err).strip()}


def stash(project_dir):
"""Stash working directory changes.

Returns:
Dict with success status and message.
"""
if not has_git(project_dir):
return {"success": False, "message": "Not a git repository"}

rc, out, err = _run_git(project_dir, "stash")
if rc != 0:
return {"success": False, "message": (err or out).strip()}
return {"success": True, "message": (out + err).strip()}


def stash_pop(project_dir):
"""Pop the latest stash.

Returns:
Dict with success status and message.
"""
if not has_git(project_dir):
return {"success": False, "message": "Not a git repository"}

rc, out, err = _run_git(project_dir, "stash", "pop")
if rc != 0:
return {"success": False, "message": (err or out).strip()}
return {"success": True, "message": (out + err).strip()}


def status(project_dir):
"""Get git status as structured data.

Expand Down
33 changes: 33 additions & 0 deletions src/tinyleaf/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,39 @@ def handle_git_branches(query_params, config, name):
return git_ops.list_branches(project_dir)


def handle_git_switch_branch(body, config, name):
project_dir = _get_project_dir(config, name)
branch = body.get("branch", "").strip()
if not branch:
abort(400, "Branch name required")
return git_ops.switch_branch(project_dir, branch)


def handle_git_create_branch(body, config, name):
project_dir = _get_project_dir(config, name)
branch_name = body.get("name", "").strip()
if not branch_name:
abort(400, "Branch name required")
start_point = body.get("start_point", "").strip() or None
return git_ops.create_branch(project_dir, branch_name, start_point=start_point)


def handle_git_delete_branch(body, config, name, branch_name):
project_dir = _get_project_dir(config, name)
force = body.get("force", False)
return git_ops.delete_branch(project_dir, branch_name, force=force)


def handle_git_stash(config, name):
project_dir = _get_project_dir(config, name)
return git_ops.stash(project_dir)


def handle_git_stash_pop(config, name):
project_dir = _get_project_dir(config, name)
return git_ops.stash_pop(project_dir)


def handle_git_status(config, name):
project_dir = _get_project_dir(config, name)
return git_ops.status(project_dir)
Expand Down
35 changes: 35 additions & 0 deletions src/tinyleaf/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,41 @@ def api_git_branches(request, name):
return handle_git_branches(request.query_params, _config, name)


@app.post("/api/projects/<name>/git/branches/switch")
def api_git_switch_branch(request, name):
from tinyleaf.handlers import handle_git_switch_branch

return handle_git_switch_branch(request.json(), _config, name)


@app.post("/api/projects/<name>/git/branches")
def api_git_create_branch(request, name):
from tinyleaf.handlers import handle_git_create_branch

return handle_git_create_branch(request.json(), _config, name)


@app.delete("/api/projects/<name>/git/branches/<branch_name>")
def api_git_delete_branch(request, name, branch_name):
from tinyleaf.handlers import handle_git_delete_branch

return handle_git_delete_branch(_json_body(request), _config, name, branch_name)


@app.post("/api/projects/<name>/git/stash")
def api_git_stash(request, name):
from tinyleaf.handlers import handle_git_stash

return handle_git_stash(_config, name)


@app.post("/api/projects/<name>/git/stash/pop")
def api_git_stash_pop(request, name):
from tinyleaf.handlers import handle_git_stash_pop

return handle_git_stash_pop(_config, name)


@app.get("/api/projects/<name>/git/status")
def api_git_status(request, name):
from tinyleaf.handlers import handle_git_status
Expand Down
Loading