Skip to content

Add a Git Graph (commit DAG) panel with right-click git operations#12043

Open
0xBB2B wants to merge 61 commits into
warpdotdev:masterfrom
0xBB2B:bb/git-graph
Open

Add a Git Graph (commit DAG) panel with right-click git operations#12043
0xBB2B wants to merge 61 commits into
warpdotdev:masterfrom
0xBB2B:bb/git-graph

Conversation

@0xBB2B
Copy link
Copy Markdown

@0xBB2B 0xBB2B commented Jun 2, 2026

Description

Adds a Git Graph tab to the left tools panel: renders the active repository's commit history as a colored DAG (lanes, nodes, merge/fork connectors, HEAD/branch/remote/tag badges), shows a selected commit's details + changed files, and opens a read-only diff of any changed file in the main area. Includes a repository picker (multi-repo), branch filtering, "load more" pagination, manual refresh, and Settings → Git toggles. Inspired by vscode-git-graph.

On top of the read-only browsing surface, a separately flag-gated layer adds right-click git operations:

  • Commit: add tag, create branch, checkout, cherry-pick, revert, drop, merge / rebase / reset onto the current branch, copy hash/subject.
  • Tag: view details, delete, push, create archive, copy name.
  • Remote branch: checkout, delete, merge, pull, create archive, unselect, copy name.
  • Local branch: checkout, rename, delete, merge-into / rebase-onto current, push, create archive, unselect, copy name (the current branch omits self-only ops).

Every mutating action is gated by explicit input — a name prompt, a reset-mode picker, the OS save dialog for archives, or a yes/no confirmation. A running op dims the panel with a centered "Working…" overlay; on success the graph reloads (fetch --prune for a remote-branch delete), on failure the git error shows in a dismissable banner.

The read-only base and the write layer are gated independently so the base can ship without the mutating layer:

  • Read-only panel: FeatureFlag::GitGraph (Dogfood) + cargo feature git_graph + the show_git_graph setting.
  • Write operations: FeatureFlag::GitGraphWrite (Dogfood).

Spec: specs/GH12041/product.md (numbered behavior invariants) + tech.md, both updated to as-built.

Linked Issue

Closes #12041.

Testing

  • Unit tests: lane-layout (layout_tests.rs), git parsing/discovery (data_tests.rs), write-op arg-builders + confirmations (ops_tests.rs), and per-target menu contents (menu_tests.rs).
  • Integration tests: test_git_graph_loads_commits (read path), test_git_graph_create_branch (write path → branch appears after reload), test_git_graph_op_error_banner (a failing op surfaces — and renders — the error banner).
  • Manually tested locally with ./script/run --features git_graph.

CHANGELOG-NEW-FEATURE: Git Graph panel — visualize commit history as a DAG, inspect commits and file diffs, and run common git operations (checkout, branch/tag, merge, rebase, reset, cherry-pick, revert, push/pull, archive) from right-click menus, without leaving Warp.

0xBB2B and others added 29 commits May 29, 2026 17:29
The codegraph MCP tooling builds a multi-hundred-MB local index under
.codegraph/. Ignore it so it never gets accidentally committed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Read-only commit DAG visualization as a new left-panel tab, mirroring
vscode-git-graph. PRODUCT.md captures scope/UX/non-goals; TECH.md covers
the render-layer constraints (rect-only primitives -> orthogonal rounded
connectors), module layout, the lane-assignment algorithm, integration
points, testing, and a phased roadmap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 1 (pure logic, no UI yet):
- data.rs: CommitNode/RefLabel types, git-log output parsing
  (parse_commit_log, parse_decorate) and async load_commit_graph.
- layout.rs: assign_lanes — the per-row lane-assignment algorithm that
  turns a commit list into drawable lanes/dots/connectors. No lane
  compaction; first parent continues its column so merged branches
  visually rejoin the mainline.
- 20 unit tests covering parsing edge cases and DAG shapes (linear,
  fork, merge, octopus, multi-root, single/empty, lane reuse).

Verified via an isolated crate since the warp crate cannot build on a
machine without Xcode's metal compiler (warpui build.rs requirement).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 2 wires the feature end-to-end behind FeatureFlag::GitGraph
(cargo feature git_graph -> features.rs bridge, on for dogfood builds):
- GitGraphView follows the active pane's working directory, runs
  git log asynchronously, and renders a plain-text commit list
  (short hash + ref labels + subject) with NoRepo / Loading / Error /
  empty states.
- left_panel.rs integration: toolbelt button (Icon::GitBranch), view
  switching, focus, active-state, and snapshot restore mapping.
- compute_left_panel_views gates the tab on cfg(local_fs) + the flag,
  matching the existing project-explorer / global-search tabs.

The lane layout (layout.rs) is intentionally not consumed yet; Phase 3
plugs it into a custom row canvas, which removes its dead-code warnings.

Verified: cargo check is clean (0 errors) and the warp-oss binary
builds and launches without panic under --features local_fs,git_graph.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 3 replaces the plain-text rows with the lane graph:
- row_canvas.rs: GitGraphRowCanvas, a custom Element that paints one
  row's lanes/dot/connectors via the scene's rect primitives. Since the
  render layer has no line/path/bezier, edges are orthogonal polylines
  (thin rects) and the node is a corner-radius square (circle); lanes
  cycle through a 7-color palette.
- layout.rs: GraphRow gains node_continues_up so the canvas knows
  whether to draw the upper half of the node's lane.
- view.rs: each row is [lane canvas | short hash + ref labels + subject]
  at a uniform 22px height; the loaded list now consumes assign_lanes.

This wires layout.rs into the view, removing its dead-code warnings.
Verified: cargo check clean, 20 git_graph unit tests pass, and the app
renders the graph (visually confirmed) under --features local_fs,git_graph.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 4a adds interactivity:
- data.rs: CommitDetail/ChangedFile + load_commit_detail (git show
  --numstat) + parse_commit_detail/parse_numstat (2 new unit tests).
- view.rs: GitGraphView becomes a TypedActionView; rows are wrapped in
  Hoverable and dispatch SelectCommit(i). Selecting a commit loads its
  detail asynchronously and shows it in a 2:1 vertical split below the
  graph: full message, author/committer, full hash, and the changed-file
  list (virtualized, with +adds/-dels). Stale results are dropped if the
  selection or repository changed.
- left_panel.rs: construct the view via add_typed_action_view so clicks
  route to its handler.

Also fixes a startup crash: nesting two MainAxisSize::Max flex columns
fed the inner one an infinite constraint and panicked when a restored
window reopened with a commit selected. render() now uses a single
column with Shrinkable factors.

Verified: cargo check clean, 22 git_graph unit tests pass, and the app
runs and switches commit details without crashing (visually confirmed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 4b adds a header bar to the Git Graph view (shown whenever a
working directory is set): a commit-count / status label on the left and
a refresh icon button on the right. Clicking it dispatches
GitGraphAction::Refresh, which re-runs the load for the current repo.

Auto-refresh on repository changes is intentionally deferred: it needs a
RepoMetadataModel handle threaded into the view plus per-repo filtering,
debounce, and large-repo perf care; the manual button covers the need
for now.

Verified: cargo check clean, 22 git_graph unit tests pass, app runs and
the button reloads the graph (visually confirmed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- "Load more" row appends the next page (skip=len, re-runs lane layout)
- per-ref color badges (HEAD/local/remote/tag) left of the subject
- detail panel: top drag-bar to resize (100..70% window), close button

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the manual (and near-invisible) "Load more" button with
scroll-to-end auto-loading: UniformList reports its visible range via
notify_visible_items, and nearing the list end prefetches the next page.
The trailing row is now a self-animating "Loading more commits…" shimmer
indicator instead of a click target.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- commit list rows now show a hover/selected background via the shared
  ItemHighlightState (consistent with file tree / global search rows)
- detail metadata (full hash, author, committer, message) is merged into
  one selectable Text wrapped in SelectableArea; drag to select, and
  Cmd/Ctrl+C copies it via a view-scoped FixedBinding. The virtualized
  changed-file list stays non-selectable for now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…set scroll on refresh

- a non-repo directory now maps to the clean centered "Not a Git repository" placeholder (icon + title) instead of dumping the raw git log error (incl. the stray trailing comma); real git errors are squashed to one concise line

- empty/loading/error states are vertically + horizontally centered via Align

- switching the active pane group now also refreshes the Git Graph working dir, so returning to a git project recovers instead of being stuck on the previous (non-repo) directory

- reload (refresh / repo switch) resets the list scroll to the top

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce GitSettings.show_git_graph (default true, cloud-synced) as a user preference for the Git Graph panel. The panel is gated by BOTH FeatureFlag::GitGraph (per-channel availability / staged rollout — Dev via DOGFOOD_FLAGS) AND the user setting (preference within channels where it's available): compute_left_panel_views checks both and re-renders when the setting changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oggle

New SettingsSection::Git + GitSettingsPageView (settings_view/git_page.rs) showing a 'Git Graph' toggle bound to GitSettings.show_git_graph. Wired into SettingsPageViewHandle, the dispatch / should_render matches, FromStr, the page registry and the sidebar nav (under Code). The page is uncategorized so future git-related settings are added by pushing another SettingsWidget.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the separately-virtualized changed-file UniformList with a single ClippedScrollable that holds the metadata, the file label and all file rows, so a long commit message scrolls together with the file list. The detail scroll resets to the top when a different commit is selected. TECH.md updated to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
工作目录下按可配深度(默认 1)发现 git 仓库:rev-parse 向上探锚点自身所属仓库,再 BFS 扫描第 1..=depth 层子目录的 .git 标记。发现多个时顶部条左侧出现仓库下拉切换(单仓库置灰仅展示当前仓库名,NoRepo 不显示),原提交计数让位给下拉。

新增 GitSettings.git_graph_scan_depth(u32,默认 1),Git 设置页加 0–3 档深度下拉。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
顶部条新增分支过滤下拉:按选中分支跑 git log(替代 --all),分支太多时收敛图谱。本地+远程分支、默认全选、保持打开连续勾选、整行可点;浮层顶部固定 Select all / Deselect all 批量操作。

细节:仓库下拉选中行用中性高亮(fg_overlay_4)区别于悬停的 accent;分支按钮仅选一个时显示该分支名;超长仓库/分支名统一省略号裁剪并限宽,避免溢出或顶出刷新按钮。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
点击 Git Graph 提交详情里的变更文件,在当前 tab 主区打开一个只读 diff pane,
展示该提交对此文件的改动(commit~1..commit,逐行红绿、可选中不可编辑)。

主要改动:
- 数据层 load_file_diff_at_commit:取父版本内容作 diff base + `git diff` 解析
  hunks(复用 code_review 的 parse_diff_hunks),处理新增/根/合并提交边界。
- 新增 CommitDiffView / CommitDiffPane(IPaneType::CommitDiff),复用底层只读
  InlineDiffView 渲染;抽出共享 convert_hunks_to_diff_deltas。
- 事件链:GitGraph → left_panel → workspace 开 pane;在当前 tab 内分屏打开、
  复用同一 pane 原地更新内容(连点不堆叠、关闭后可重开)。
- pane header 常显关闭按钮 + Maximize/Minimize 菜单;点击 diff 内容激活 pane
  并显示活跃标记。

顺带改进 Git Graph:
- 每仓库记忆分支选择,切 tab / cd 后按仓库恢复,仅"刷新"按钮重置。
- cd 到新目录时跟随其所在仓库。
- 修复短历史时详情区不全尺寸 / 拖动错位(提交列表改 Expanded 撑满)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
之前没有任何代码为 CommitDiffPane 驱动 show_active_pane_indicator
(终端 pane 靠 is_active_session 自驱,非终端 pane 无人驱动),导致
聚焦该只读 diff pane 时左上角不出现活跃三角。

- CommitDiffView::set_focus_handle 订阅 pane group 焦点变化,按
  split_pane_state().is_focused_pane() 同步设置该标志。
- 终端 refresh_pane_header 的判定从 is_active_session 收紧为
  is_active_session && is_pane_focused:焦点切到分屏中的非终端 pane 时
  active_session 仍指向本终端(供新建终端继承 cwd,不应清空),若不收紧
  会出现终端与被聚焦 pane 同时亮三角。纯终端场景行为不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 详情区首次打开默认占窗口高度 1/3,之后保持用户拖动值
- Resizable 新增可选拖条 hover 高亮(with_dragbar_hover_color);
  hover 状态移入共享 ResizableState 跨重渲染保留,修复纯 hover 时高亮丢失

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
详情区重排为分层信息:加粗标题 / 作者·相对时间 / 完整 hash / 正文,
文件区加分隔线与汇总行;增删计数改 monospace 按最大位数右对齐成列,
红绿语义色复用 diff 编辑器同源配色,文件名提亮、过窄从左裁切保住文件名。

修复框选复制失效:键盘绑定只对焦点链(responder chain)生效,而 GitGraphView
从不聚焦,导致 Cmd/Ctrl+C 不触发 CopySelection(框选靠命中测试、不改焦点)。
改为 select_commit 与框选起始(selection None→Some)时 focus_self 自举焦点,
解决"第一次能复制、之后不能"。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
overlay 滚动条铺在内容右缘 8px 上,原先右对齐的 `-N` 删除数被整列遮住。
给滚动内容预留「滚动条宽度 + 6px 呼吸间隙」的右内边距修复。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
关掉 hover/active 时 diff hunk 标记条加宽(3→8px),统一恒为 3px,
消除 git graph 只读 diff pane 里改动标记条粗细不一。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
.codegraph/ 是本地 codegraph MCP 工具的索引产物,与 git graph 功能无关,
属夹带改动;应放入个人全局 gitignore,不污染项目仓库。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prepare the git graph feature for upstream contribution: translate all
Chinese doc/inline comments, the relative_time UI labels, and one log
message to English. relative_time now uses correct English plurals; no
behavior change otherwise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an end-to-end integration test that builds a small git repository,
opens the Git Graph panel, and asserts the commit graph loads and renders
its commits. Exposes minimal pub(crate) read-only accessors on
GitGraphView / LeftPanelView / WorkspaceView for the test harness,
following the existing code_review / view_getters pattern.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Format the git graph feature code to satisfy presubmit's cargo fmt --check.
Only branch-introduced files are affected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	crates/warp_features/src/lib.rs
Rename specs/git-graph -> specs/GH12041 (product.md/tech.md lowercase) for the
GH#12041 feature request. Rewrite both docs to reflect the as-built feature:
add file diff pane, multi-repository picker, branch filtering, and Git settings
(previously wrongly listed as non-goals); restructure product.md into numbered,
testable behavior invariants.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented Jun 2, 2026

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have the users @0xBB2B on file. In order for us to review and merge your code, each contributor must visit https://cla.warp.dev to read and agree to our CLA. Once you have done so, please comment @cla-bot check to trigger another check.

0xBB2B and others added 9 commits June 2, 2026 17:11
Right-clicking the short hash left of a commit's subject now opens a
focused menu with a single "Copy Short Hash to Clipboard", copying the
7-char hash shown rather than the commit menu's full 40-char hash. The
handler mirrors the ref badges and sits above the row handler, so a
right-click landing on the hash takes precedence over the commit menu.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each tab now restores its own manually-picked repo after switching away
and back, instead of snapping to the repo the anchor lives in. Keyed by
anchor in saved_repo_selections, mirroring saved_branch_selections.

Extract the repo-selection decision into the pure pick_selected_repo()
and cover its branches with unit tests.

Refresh no longer resets the branch filter — reload restores the saved
selection, dropping only branches that no longer exist after the fetch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…achable remotes

A private repo with no VPN or cached credentials made the refresh button's
`git fetch --prune` hang forever, freezing the panel on the loading state.

- Set GIT_TERMINAL_PROMPT=0 so an auth-requiring fetch fails fast instead of
  blocking on a credential prompt the user never sees.
- Bound the fetch with a 10s timeout (kill_on_drop reaps the git child); skip
  the fetch entirely on a repo with no remote, to avoid a spurious banner.
- Surface a dismissable "Couldn't reach remote — showing local graph." banner
  on failure, still falling back to a local reload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…es row

Auto-refresh: subscribe to the selected repo's .git via repo_metadata's Repository watcher, so terminal/external git operations (commit, checkout, branch, file edits) reload the graph — preserving the user's scroll position and selection by commit hash. local_fs-gated; reuses the same filesystem watch the terminal git status already uses.

Uncommitted-changes row: when the working tree is dirty, show a synthetic hollow-node row 'Uncommitted Changes (N)' at the top of the graph connected to HEAD. N counts staged+unstaged+untracked files and updates live on file changes. Clicking it shows the working-tree-vs-HEAD file list in the detail pane; clicking a file opens the diff.

The two features share view.rs plumbing (load_commits, render_commit_list), so they land together. 78 unit tests; local_fs + non-local_fs compile + clippy -D warnings clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the per-branch checkbox list (plus Select all / Deselect all
buttons) with a dropdown that defaults to "Show All" and a pinned
"Filter Branches…" search box. An empty selection now means Show All
(no ref filter = --all); clicking a branch switches out of Show All into
an explicit multi-selection, and clearing the last one returns to Show
All. The button summarises the selection as "Show All", a single branch
name, or the selected names comma-joined.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pport force

Add a force checkbox to the confirmation dialog of every force-capable
write op reachable from the right-click menus, covering 5 operations:
push branch/tag, checkout commit/branch, and delete local branch.

- Push branch uses --force-with-lease (refused if the remote moved past
  what we last fetched, guarding others' work); push tag uses bare
  --force (a tag has no remote-tracking ref for a lease to compare).
- Checkout uses --force (discard uncommitted changes that block the
  switch); delete local branch switches -d to -D (delete unmerged).

The force state lives as a field on GitWriteOp as the single source of
truth, so args() inserts the flag from it. The dialog reuses the existing
Checkbox component; toggling dispatches ToggleConfirmForce to flip the
op's force, and Confirm runs the forced op — run_op/run_write_op unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Within one tab, switching the active split pane (or CDing the focused
pane) now moves the Git Graph to that pane's repository, instead of the
panel staying on the most-recently-added directory.

- Drive the panel from the focused pane's *directory* via a new
  `focused_dir` on `FocusedRepoChanged` (the focused pane's repo root, or
  its working directory when it isn't inside a repo). Anchoring on the
  directory rather than `focused_repo` means a non-repo dir is still
  scanned for nested repos and surfaces them; a dir with no git anywhere
  falls through to the "Not a Git repository" placeholder. Gate the emit
  on `focused_dir` so it also fires between two non-repo directories.
- Keep the existing graph on screen during a reload (repo/branch switch,
  auto-refresh) instead of flashing the "Loading…" placeholder; the new
  page swaps in atomically once it lands.
- Unify the two NoRepo paths into `enter_no_repo_state` so the branch
  filter is cleared (and hidden) when there's no repo, fixing a stale
  branch selector left over after switching to a non-repo directory.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… of a garbled diff

git already reports binary files in its diff output (`Binary files ... differ` with no hunks), but that signal was dropped, so binary files were fed to the text editor — rendering the parent revision's raw bytes or a blank pane. Capture git's verdict in the data layer as an `is_binary` flag, thread it through to the commit diff pane, and show a single centered line when set.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ne-line diff

A symbolic link's blob content is just its target path (a single line, no
trailing newline), so feeding it to the text editor rendered a lone "CLAUDE.md"
entry that looked broken/empty. Detect git's symlink file mode (120000) in the
diff metadata, and — mirroring the binary-file handling — show a centered
placeholder naming the target ("Symbolic link → <target>") instead.

Replace the `is_binary: bool` flag with a three-state `DiffPreview`
(Text / Binary / Symlink) so "how to preview" has one source of truth and no
invalid combinations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Alorse
Copy link
Copy Markdown

Alorse commented Jun 4, 2026

This feature and a Git status into Project explorer panel, It is the only thing that Warp lacks to be perfect.

0xBB2B and others added 13 commits June 5, 2026 02:51
Turn the detail area's flat changed-file list into a collapsible directory
tree (pure logic in the new file_tree module), keeping file leaves' original
index into detail.files so opening a diff and per-file mouse-state stay
index-based. Add a Bookmark icon for tag refs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	crates/warp_features/src/lib.rs
git show --numstat compresses renames into the path column using brace
syntax (`a/{old => new}/c.rs`) or a bare `old => new`. parse_numstat
stored that verbatim, so the file tree split it on `/` into bogus dirs
like `{app`, `auth => crates`, `src}`, and opening a diff hit a path
that doesn't exist. Normalize to the post-rename real path at parse time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "you are here" hollow ring used to mark the HEAD commit on the graph
lane. Drop it there (every real commit now draws a plain filled dot) and
render the ring just left of the current branch's pill instead, where it
reads more directly against the branch name. The synthetic uncommitted
row stays hollow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Color file/folder names by working-tree status (modified/untracked/
deleted/conflicted), show right-aligned M/U/A/D letter badges on files
and a dot on folders, and dim gitignored files a shade further. Folders
roll up the highest-priority status among their descendants.

Reuses the existing GitRepoStatusModel watcher: extends it with a
per-file status map (gated on FeatureFlag::GitGraph) and a reusable
file_statuses_against_head helper, so there's no extra git invocation
when the flag is off.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Git defaults to core.quotePath=true, so any path with non-ASCII bytes
(e.g. CJK filenames) is octal-escaped and double-quoted in command
output ("docs/\346\236\266..."). Every run_git_command caller parses
this output programmatically, so the escaped form is never wanted — it
surfaced as garbled filenames in the git-graph changed-file tree and
would silently break path matching in the explorer status decoration.

Set core.quotePath=false globally alongside diff.autoRefreshIndex=false
so git emits raw UTF-8, matching the from_utf8_lossy decode downstream.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lanes are painted before the node dot, so a filled dot covers the line
stubs underneath. The hollow ring has a transparent center (so the row's
hover/selection highlight shows through), which let the vertical lane
line painted beneath it bleed through the center and read as a filled dot.

Trim the node-column vertical segments to the ring's edge for a hollow
node so nothing crosses its transparent interior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	app/src/code/file_tree/view.rs
The preserve-anchor reload treated "no relocated commit selection" as
"selected commit vanished" and blanked the detail pane. With the
uncommitted row selected the relocated selection is always None (the
row is not hash-addressed), so the watcher event from a large in-flight
file change landed after the row was clicked and wiped the just-loaded
detail. Route the decision through detail_refresh_after_reload: re-read
the uncommitted detail in place while the tree is dirty, and clear it
only when the tree became clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every watcher signal used to trigger a full reload (git log + stashes +
status), so an event storm — builds, bulk file writes, agents editing
the tree — fanned out into a git-process storm. Throttle the drain
trailing-edge: the first signal after a quiet period reloads
immediately (a terminal commit still shows up right away), further
signals inside the window collapse into one catch-up reload at the
window's end, so the graph always converges on the final state. This
also covers the signal that used to be silently dropped when it arrived
while a reload was already in flight.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pure re-formatting from RUSTC_BOOTSTRAP=1 cargo fmt with the project's
import settings (imports_granularity=Module, group_imports=
StdExternalCrate); no functional change. Clears the drift so future
format runs stay scoped to actual edits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The toolbar refresh went through repo re-discovery, which resets the
selection, scroll position, and open detail pane. Reuse the auto-refresh
in-place reload (hash re-anchoring + detail keep/refresh/clear) after
the fetch instead; an unloaded graph (Error / NoRepo) still falls back
to a full re-discovery as the recovery path. Stamping the throttle
clock also absorbs the watcher signals the manual fetch triggers itself.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0xBB2B and others added 6 commits June 5, 2026 19:39
…s feature

left_panel_view / open_git_graph_panel / git_graph_view are only called
from the feature-gated integration_testing module, so a normal build saw
them as zero-caller and fired dead_code warnings (a hard error under CI's
-D warnings).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… when it starts

A manual refresh snapshotted the selection/scroll anchor into
LoadAnchor::Preserve at reload start and wrote it back unconditionally
when the async chain (fetch -> branches -> commits) landed. A selection
made while the chain was in flight (quick refresh x2, then clicking
another commit) was rolled back to the stale snapshot - or, when the
snapshot was the uncommitted row (selected = None), misread as a
vanished commit and the open detail blanked.

Capture the anchor at landing time instead (auto_refresh::capture_anchor,
the capture half of relocate_view), so whatever the user selected last
is what gets restored. Also switch the commit-detail stale check from
index to hash compare: a reload landing mid-load may shift the commit's
index when it re-anchors, and an index compare would discard the
in-flight detail and leave the pane stuck on Loading.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ail times

- Detail times older than 24h render as yyyy-MM-dd HH:mm:ss in the
  system timezone instead of "N months ago" (relative kept within 24h).
- Author / committer emails move out of the always-visible text (bot
  emails are long enough to wrap the line): hovering the row opens a
  floating card with the full "name <email>", whose text is
  drag-selectable and copied with Cmd/Ctrl+C like the rest of the
  detail. The row's hover-out delay leaves time to move the mouse into
  the card, which then keeps itself open.
- The detail metadata splits into three selection segments (subject,
  hash + body, identity card); their callbacks cross-clear the other
  handles so a stale highlight can't linger once one segment owns the
  selection.
- CommitDetail gains committer_email (git show format %ce).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	app/src/settings/init.rs
#	crates/warp_features/src/lib.rs
The commit detail's three drag-selectable segments (subject, hash+body,
identity card) shared one selected-text slot that every SelectableArea
overwrote unconditionally. Two failures resulted, both surfacing as
"text highlights but Cmd/Ctrl+C copies nothing":

- Every area that saw a mouse-up reported into the slot, so a segment
  reporting an empty selection wiped the text another segment had just
  captured.
- A mouse-down's empty Some("") dispatched FocusPanel, whose focus_self
  re-render rebuilt the area with a None origin; a fast drag reached
  mouse-up before the next paint, so the owner failed to materialize its
  selection and cleared its own slot.

Tag the slot with the owning segment's index: only the owner clears it,
and an empty payload (None or Some("")) now touches neither focus nor
the shared text. Non-empty selections take ownership and re-bootstrap
focus only when newly owned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed external-contributor Indicates that a PR has been opened by someone outside the Warp team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a Git Graph (commit DAG) panel with right-click git operations

2 participants