Skip to content

Git Changes Panel

The right sidebar's Git tab shows the working-tree status of the active project's git repository, with a built-in unified-diff viewer for any selected file plus quick actions for commit, pull, and push.

What it shows

  • Branch chip at the top with ahead/behind counters when an upstream is configured.
  • Staged section - files with index-vs-HEAD changes (git add-ed but not committed).
  • Changed section - files with worktree-vs-index changes.
  • Untracked section - files not in git, marked with ??.
  • Selection checkboxes on each row so you can choose files for bulk stage / unstage operations.
  • A diff viewer at the bottom of the panel that displays the unified patch for whichever file is selected, with +/ lines colorized.
  • Commit / Pull / Push buttons for quick actions in the sidebar. Commit opens a dialog and commits the currently staged changes only. Pull runs git pull --ff-only so the UI never blocks on an interactive merge flow. Push runs git push for the current branch/upstream.
  • Stage selected / Unstage selected buttons for moving the checked files in or out of the index without leaving the app.

Clicking any row opens its diff. Untracked files have no git diff; the viewer shows a placeholder for them.

The panel refreshes when the workspace gains focus (so coming back from a terminal pane shows fresh state). Background polling is intentionally off to save battery on Android - use the refresh button in the panel header for an explicit re-fetch.

If the project's working directory is not a git repository, the panel says so without raising an error. Genuine errors (git not on PATH, peer unreachable for a project on another node) are surfaced as a banner.

Wire protocol

GitStatus

jsonc
// client → daemon
{ "type": "git_status", "project_id": "<uuid>" }

The daemon shells out to:

git -C <project.working_dir> status --porcelain=v2 --branch -z

with LANG=C and LC_ALL=C for parse stability, and parses the result into a git_status_result.

jsonc
// daemon → client (success)
{
  "type": "git_status_result",
  "project_id": "<uuid>",
  "is_repo": true,
  "branch": "main",
  "upstream": "origin/main",
  "ahead": 1,
  "behind": 0,
  "staged":   [{ "path": "src/lib.rs", "status": "M" }],
  "unstaged": [{ "path": "README.md", "status": "M" }],
  "untracked": ["new.txt"]
}

is_repo = false is not an error - it means the directory is not a git repository (or git exits with the "not a git repository" message). git_error is reserved for project-not-found, peer-unreachable, and spawn-failure cases.

status is the single-letter porcelain v2 code: M modified, A added, D deleted, R renamed, C copied, T type changed, U unmerged. For renames and copies the old_path field carries the original path.

GitDiff

jsonc
// client → daemon
{ "type": "git_diff", "project_id": "<uuid>", "path": "src/lib.rs", "staged": false }

staged = false returns the worktree-vs-index diff (git diff); staged = true returns the index-vs-HEAD diff (git diff --cached). Path traversal (.., absolute paths) is rejected before invoking git.

jsonc
// daemon → client
{
  "type": "git_diff_result",
  "project_id": "<uuid>",
  "path": "src/lib.rs",
  "staged": false,
  "patch": "diff --git a/src/lib.rs b/src/lib.rs\n@@ -1 +1 @@\n-old\n+new\n"
}

The patch is the raw git diff text (color-disabled with --no-color). The frontend parses it line-by-line for syntax-coloring; the daemon does not pre-render.

Errors

jsonc
{
  "type": "git_error",
  "project_id": "<uuid>",
  "message": "git: not on PATH"
}

GitCommit, GitPull, GitPush

jsonc
// client → daemon
{ "type": "git_commit", "project_id": "<uuid>", "message": "Ship the sidebar actions" }
{ "type": "git_pull", "project_id": "<uuid>" }
{ "type": "git_push", "project_id": "<uuid>" }

git_commit commits staged changes only with git commit -m <message>. git_pull runs git pull --ff-only. git_push runs git push.

jsonc
// daemon → client (success)
{
  "type": "git_action_result",
  "project_id": "<uuid>",
  "action": "commit",
  "message": "[main abc1234] Ship the sidebar actions"
}
jsonc
// daemon → client (failure)
{
  "type": "git_action_error",
  "project_id": "<uuid>",
  "action": "pull",
  "message": "fatal: Not possible to fast-forward, aborting."
}

GitStage, GitUnstage

jsonc
// client → daemon
{ "type": "git_stage", "project_id": "<uuid>", "paths": ["src/lib.rs", "README.md"] }
{ "type": "git_unstage", "project_id": "<uuid>", "paths": ["src/lib.rs"] }

git_stage runs git add -- <paths...>. git_unstage runs git restore --staged -- <paths...>.

Both operations answer with the same git_action_result / git_action_error envelope used by commit/pull/push, with action set to stage or unstage.

Multi-node behavior

When the project's node_id points at a peer other than the daemon receiving the request, the daemon forwards the message over the inter-node WebSocket (mirroring ListOsUsers / GetDaemonLog / ListProjectFiles) and relays the peer's reply back. The client sees a single response.

Run-as-user

When the project has a run_as_users entry for the local node, both git status and git diff are wrapped in the same shim used for session spawns (runuser --session-command on Unix, the hive-runas.exe helper on Windows). This means the user's ~/.gitconfig, credentials, hooks, and gitignore behave exactly as they do in their own shell.

Caching and invalidation

  • Status is cached per project. A new git_status_result invalidates all cached diffs for that project (the working tree may have moved).
  • Diffs are cached per (project, path, staged-flag) triple. Invalidated by a new status push or explicit project reset.

Frontend integration

  • Pinia store: src/stores/git.ts (useGitStore)
  • Components:
    • src/components/sidebar-right/GitChangesPanel.vue - header, lists, section sticky-headers, status row clickable → diff
    • src/components/sidebar-right/DiffView.vue - header chip + colorized <pre> patch viewer
  • Tauri commands in src-tauri/src/commands/git.rs: git_status(projectId), git_diff(projectId, path, staged), git_commit(projectId, message), git_pull(projectId), git_push(projectId), git_stage(projectId, paths), git_unstage(projectId, paths).
  • Events: hive:git-status, hive:git-diff, hive:git-error, hive:git-action-result, hive:git-action-error (registered in src/composables/useTauriEvents.ts)

Limitations

  • Commit still includes the current staged set only. If you need to commit a subset, first select those files and use Stage selected / Unstage selected so the index matches exactly what you want to commit.
  • No diff for untracked files (git doesn't produce one).
  • No file watcher; refresh on focus + manual button only.
  • Pull is fast-forward only. Non-fast-forward updates still need to be resolved in a terminal or external Git client.
  • The diff viewer uses a plain <pre> with class-based coloring, not CodeMirror's merge view - keeping the panel lightweight.

Hive - remote AI coding agents over WebSocket.