Skip to content

feat: surface branch git state in timeline with actionable rows#695

Open
matt2e wants to merge 6 commits intomainfrom
better-git-state-management
Open

feat: surface branch git state in timeline with actionable rows#695
matt2e wants to merge 6 commits intomainfrom
better-git-state-management

Conversation

@matt2e
Copy link
Copy Markdown
Contributor

@matt2e matt2e commented May 7, 2026

Summary

  • Adds BranchGitState computation that determines the relationship between local branch, remote tracking branch, and base branch (ahead/behind/diverged/up-to-date)
  • Surfaces git state as actionable timeline rows in the branch timeline, with push/pull/force-push actions and real-time progress tracking via session IDs
  • Optimizes git state computation by parallelizing independent git commands and batching remote operations into shell scripts

Test plan

  • Unit tests added for git state computation (state_tests.rs)
  • Verify timeline rows appear correctly for branches that are ahead, behind, diverged, or up-to-date with their remote
  • Verify push/pull/force-push actions trigger correctly from timeline rows
  • Verify progress states update in real-time during push/force-push operations

🤖 Generated with Claude Code

matt2e and others added 6 commits May 7, 2026 16:02
Add git state detection and display as timeline rows, showing the
relationship between local branches, their remote tracking branches,
and base branches. Each state row includes contextual actions:

- Dirty worktree: commit or stash changes
- Diverged from origin: rebase onto origin and force-push
- Behind/ahead of origin: push or pull
- Base branch updates: rebase onto latest base
- Merge conflicts: show conflicted files

Backend adds a git state module that computes branch state by comparing
local, remote, and base branch refs. Frontend renders state as styled
timeline rows with icons, badges, and action buttons including progress
states for async operations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
- Combine two separate git fetch calls into a single invocation with
  both refspecs to halve network round-trips. Falls back to base-only
  fetch when the branch refspec is missing on the remote.
- Extract duplicated is_conflicted_status into a shared status_parse
  module to avoid drift between state.rs and worktree.rs.
- Add comment clarifying the rename/copy arrow format in revert_paths
  is display-only and never passed back to git commands.
- Fix confirmForcePush to keep the dialog open when another operation
  is in progress, so the user understands why the action was blocked.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Use std::thread::scope to run independent git commands concurrently
in two phases:

- Phase 1: Fetch runs in parallel with local-only commands (rev-parse
  HEAD, branch --show-current, status --porcelain) since those read
  local state unaffected by fetch.
- Phase 2: After fetch completes, upstream and base state computations
  run in parallel since both need up-to-date remote refs but are
  independent of each other.

This reduces wall-clock time by overlapping network I/O (fetch) with
local git queries, and overlapping the two post-fetch ref comparisons.

The run_git closure gains a Sync bound to allow shared access across
threads. All existing callers already satisfy this since their closures
only capture shared references and spawn processes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Replace up to 10 individual ws_exec round-trips with at most 2 batched
shell scripts when computing branch git state for remote (Blox)
projects:

- Script 1 (fetch + local state): performs git fetch, rev-parse HEAD,
  branch --show-current, and status --porcelain in a single sh -c call.
  Handles missing-ref fallback internally. Replaces 4-5 round-trips.

- Script 2 (post-fetch ref state): computes upstream SHA, left-right
  counts, merge-base, and base branch state in a single sh -c call.
  Replaces up to 6 round-trips.

When the fetch TTL cache is fresh, only a lightweight local-state
script + Script 2 runs, giving 2 round-trips total (down from 6-7).

The local code path (compute_local_branch_git_state) is unchanged
since process spawn overhead is negligible locally.

Adds run_workspace_shell helper to branches.rs for executing sh -c
scripts via ws_exec, and a resolve_repo_path helper to timeline.rs
for resolving workspace repo paths used as script arguments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Replace the instant-clearing pushingOrigin boolean and the
commandPipelinePending flag with session ID tracking that keeps
progress states active for the actual duration of the push operation.

Push and force push buttons now show "Pushing…" while the session
runs, and clicking them opens the session dialog so users can see
pipeline steps and output. The force push button switches from
danger-red to resume-btn style during progress. The rebase button
is disabled with "Force push in progress" while a force push runs.

Session IDs are cleared in the session-status-changed listener when
the push session completes, errors, or is cancelled, ensuring the
progress state matches the real operation lifecycle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Replace hardcoded /tmp/_staged_fetch_err and /tmp/_staged_fetch_err2
paths with mktemp-generated unique temp files. The previous approach
would clobber error output when two branches triggered a batched fetch
concurrently on the same Blox workspace. The script now creates a
unique temp file at startup and registers a trap to clean it up on
exit, eliminating the race condition.

Also resolve clippy warnings:
- bind_instead_of_map: use map + unwrap_or instead of and_then + Some
- needless_lifetimes: remove explicit lifetime in prs.rs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
@matt2e matt2e requested review from baxen and wesbillman as code owners May 7, 2026 07:32
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 09024acd65

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +380 to 382
let rebase_ref = rebase_ref_for_target(&branch, target.as_deref());
let steps = build_commit_pipeline_steps(&kind, &rebase_ref);
let pipeline = PipelineExecution::from_steps(&steps).with_kind(kind);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve origin-targeted queued rebases

When a diverged branch requests rebase_branch(..., target: "origin") while another session is running/queued, this stores the origin-targeted steps only inside the queued session's pipeline. The queued-session runner later ignores those stored steps and rebuilds the pipeline from base_branch_name(&ctx.branch) in start_queued_commit_pipeline_for_branch, so the queued action silently becomes a rebase onto the base branch instead of origin/{branch} once it starts. Persist the target/kind data used for rebuilding, or reuse the stored queued pipeline steps, so queued diverged-row rebases run the same command the user requested.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant