feat: improve git integration - #119
Open
kaminskypavel wants to merge 2 commits into
Open
kaminskypavel wants to merge 2 commits into
kaminskypavel wants to merge 2 commits into
Conversation
…t graph of every local and remote branch, a commit unfolds into its files, and diffs show side by side - The DIFF VIEWER's left side is a SOURCE CONTROL sidebar: CHANGES (the changed files, flat or tree, reviewed marks as before) over GRAPH, the newest 500 commits of HEAD, every local branch and every remote-tracking one, drawn as `git log --graph` draws them with each lane in its own color, each commit with its refs, author and age. - `↑` marks a commit HEAD's upstream lacks (a push would send it), `↓` one HEAD lacks (a pull would bring it); the GRAPH header counts both. - `↑`/`↓` walk straight from the files into the commits. The right pane reads the cursor's row: a commit's `git show --stat`, a header's `git status --short --branch`. - `Enter`, `→` or a click unfolds a commit into its files right under it, each diffed against the commit's first parent; `←` or `Enter` folds it, and the same keys fold a whole section on its header. - One filter narrows both sections (commits by hash, ref or subject). - Diffs show side by side, numbered, a removed line facing what replaced it. `Ctrl+s` flips to the unified diff and back, remembered across opens (`diff_unified` in the UI state); a pane under 90 columns shows the unified one either way. - A clean checkout opens on HEAD's commit instead of flashing "no changes", which retires the clean-badge probe (`diff_probe`). - The sidebar opens at a third of the modal until dragged. Its width is stored under a new key, so a width saved for the old bare file list does not squeeze the graph. - Reading only: nothing in the sidebar writes to the repository. A pull request's diff keeps its file list (headed FILES) with no graph. - docs/keys.md and README. Tests: g_with_clean_repo_opens_on_heads_commit, the_cursor_walks_from_the_changes_into_the_graph, split_rows_pairs_removals_with_the_additions_after_them, git_log's parse_log_splits_commits_from_the_lines_between_them, parse_refs_names_each_kind_and_drops_a_remotes_head, parse_name_status_z_reads_renames_old_then_new, the_cursor_starts_on_head_and_steps_over_graph_lines, the_filter_narrows_to_commits_and_clearing_keeps_the_cursor, a_fresh_read_keeps_the_cursor_on_its_commit, an_unfolded_commit_lists_its_files_under_it, read_log_lists_local_and_remote_commits_against_the_upstream, read_log_on_an_unborn_head_lists_no_commits; tui_git_diff_modal follows the new headers and the clean checkout's graph. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kaminskypavel
marked this pull request as ready for review
October 3, 2026 11:30
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.
gturns from a list of changed files into a source control view shaped like VS Code's: the changes sit over a commit graph of HEAD and every local and remote branch, any commit unfolds into its files in place, and diffs read side by side. Everything stays read-only, and the reviewed marks, the tree and pull request diffs work as they did.Contents
✨ What you get
🌳 See the whole history
ggit log --graphdraws them, each lane in its own color, refs colored by kind (HEAD, local, remote, tag)no changes in main↑and↓↑marks a commit HEAD's upstream lacks (a push would send it),↓one HEAD lacks (a pull would bring it)▾ GRAPH (5) ↑3 ↓1Enter,→or a click←orEnterfolds it again, and the same keys fold a whole section on its header--stat🔍 Read diffs like an editor
Ctrl+sCtrl+sflips to the unified diff and back, remembered across opens↑/↓, then typeCtrl+r) and the directory tree (Ctrl+t) behave as before/orv) lists its FILES with no graph🧭 How it flows
flowchart LR U([User]) -->|"g"| V["DiffView sidebar"] V -->|"BACKGROUND READ"| S["git status: CHANGES"] V -->|"BACKGROUND READ"| L["git log --graph + rev-list: GRAPH"] V -->|"cursor row"| P{"right pane"} P -->|"changed file"| D1["git diff HEAD"] P -->|"commit"| D2["git show --stat"] P -->|"commit's file"| D3["git show sha -- path"] P -->|"header"| D4["git status --short --branch"] V -->|"Enter / → on a commit"| F["git show --name-status: files unfold"] D1 --> X["split_rows: side by side"] D3 --> X classDef changed fill:#fde68a,stroke:#b45309,color:#111 class L,D2,D3,D4,F,X changedVerdict: 🟢 Low risk: a TUI-only change to one modal that runs read-only git in the user's own checkout, off the event loop.
log,show,rev-list,status) are read-only, take an argument vector (no shell), and every path goes after--. The DAEMON and the protocol are untouched.git log --graph --date-order -n 500over every ref walks the history to sort it, which can take a second on a very large repository; the header shows(…)until it lands and nothing waits on it. The per-frame clone holds the commits behindArcs.Answervariants, the diff cache,list_hithit-testing, the tree's fold keys. The one new idea is a row space over two lists (side_select), which keeps every existing list method intact underneath.Rollback:
git revertundoes it all. The UI state gainsdiff_unifiedanddiff_sidebar_width, which an older build ignores; it stops writingdiff_files_width, so after a revert the file list opens once at its default width.🔧 Technical overview
DiffViewgainslog: Option<GitLog>and aplace(ChangesHeader,Changes,GraphHeader,Graph).side_len/side_row/side_selectlay one row space over the changed-file list and the graph, so↑/↓,Ctrl+d/Ctrl+u, the wheel and clicks cross sections without the list code changing. The graph isgit log --graph --date-order --decorate=full -n 500 HEAD --branches --remotessplit into commits and connector lines, withrev-list --left-right HEAD...@{upstream}marking sides; unfolded commits readgit show --name-status -M --diff-merges=first-parent. Side by side isgit_diff::split_rows, computed once per diff inshow_diff; each draw decides whether it fits (MIN_SPLIT_W), andshown_rowskeeps scrolling on the layout drawn.crates/nebula-tui/src/git_log.rs: new, the graph's read, parse, fold and pane loaders;crates/nebula-tui/src/app.rs:DiffView's place, sidebar mapping, split state and the two UI state keys;crates/nebula-tui/src/git_diff.rs:diff_fortakes a commit,split_rows, a listing that settles the cursor instead of closing on a clean checkout;crates/nebula-tui/src/ui.rs: sidebar rows, lane glyphs and colors, the split draw;crates/nebula-tui/src/event_loop.rsandevent_loop/activate.rs: keys, mouse, the open path, the new answers, persistence (diff_proberemoved);crates/nebula-tui/src/view_jobs.rs:Answer::LogandAnswer::CommitFiles.Ctrl+lmode that replaced the file list; one stacked sidebar, as in VS Code, keeps both in view and needs no mode.--allwas left out because stash, notes and PR refs clutter the graph.make ci:cargo fmt --checkclean;clippyadds no warning (the ones it prints are already onmain);cargo test --workspace1708 pass, 2 fail (tui_drag_past_the_pane_top_autoscrolls_and_copies_the_run,nebula_open_from_inside_a_session_raises_the_file_tabs), and both fail the same way onmainwithout this change.📝 Notes
mainat v0.42.0 (0f98b95), independent of Move a session to another worktree or project with m or a drag, and it carries on the same conversation there #118; no conflicts.nebula killneeded.🤖 Generated with Claude Code