Add a Git Graph (commit DAG) panel with right-click git operations#12043
Open
0xBB2B wants to merge 61 commits into
Open
Add a Git Graph (commit DAG) panel with right-click git operations#120430xBB2B wants to merge 61 commits into
0xBB2B wants to merge 61 commits into
Conversation
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>
|
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 |
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>
|
This feature and a Git status into Project explorer panel, It is the only thing that Warp lacks to be perfect. |
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>
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 → Gittoggles. Inspired by vscode-git-graph.On top of the read-only browsing surface, a separately flag-gated layer adds right-click git operations:
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 --prunefor 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:
FeatureFlag::GitGraph(Dogfood) + cargo featuregit_graph+ theshow_git_graphsetting.FeatureFlag::GitGraphWrite(Dogfood).Spec:
specs/GH12041/product.md(numbered behavior invariants) +tech.md, both updated to as-built.Linked Issue
Closes #12041.
Testing
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).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)../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.