Skip to content

feat: improve git integration - #119

Open
kaminskypavel wants to merge 2 commits into
AgentSystemLabs:mainfrom
kaminskypavel:feat/git-source-control
Open

kaminskypavel wants to merge 2 commits into
AgentSystemLabs:mainfrom
kaminskypavel:feat/git-source-control

Conversation

@kaminskypavel

@kaminskypavel kaminskypavel commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

The g modal: a Source control sidebar with the CHANGES section listing 11 modified files above a GRAPH of 469 commits with colored lanes and remote branch refs, a merge commit unfolded into its files, and the commit's message and stat in the right pane

g turns 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

  • A commit graph under your changes: g
    • the newest 500 commits of HEAD, every local branch and every remote-tracking one
    • drawn the way git log --graph draws them, each lane in its own color, refs colored by kind (HEAD, local, remote, tag)
    • a clean checkout opens on HEAD's commit instead of flashing no changes in main
  • Push and pull at a glance: ↑ and ↓
    • ↑ marks a commit HEAD's upstream lacks (a push would send it), ↓ one HEAD lacks (a pull would bring it)
    • the header counts both, as in ▾ GRAPH (5) ↑3 ↓1
  • Commits open in place: Enter, → or a click
    • the commit unfolds into its files right under it, each diffed against the commit's first parent
    • ← or Enter folds it again, and the same keys fold a whole section on its header
    • the right pane reads the commit under the cursor: message, author, dates and --stat

🔍 Read diffs like an editor

  • Side by side: Ctrl+s
    • old file left, new file right, line numbers on both, a removed line facing what replaced it
    • Ctrl+s flips to the unified diff and back, remembered across opens
    • a diff pane narrower than 90 columns shows the unified diff either way
  • One list, one filter: ↑/↓, then type
    • the cursor walks from the changed files straight into the commits, across both headers
    • typing narrows both sections at once: files by path, commits by hash, ref or subject
  • What stayed the same
    • reviewed ✓ marks (Ctrl+r) and the directory tree (Ctrl+t) behave as before
    • a pull request's diff (from / or v) lists its FILES with no graph
    • nothing in the sidebar writes to the repository

🧭 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 changed
Loading

⚠️ Risk

Verdict: 🟢 Low risk: a TUI-only change to one modal that runs read-only git in the user's own checkout, off the event loop.

Level Why
🔒 Security & production Low No new surface: the TUI already shells out to git here; the new calls (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.
⚡ Performance Low All reads run on the blocking pool through BACKGROUND READS. git log --graph --date-order -n 500 over 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 behind Arcs.
🧩 Fit with the codebase Low Follows the DIFF VIEWER's own patterns: tickets and Answer variants, the diff cache, list_hit hit-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 revert undoes it all. The UI state gains diff_unified and diff_sidebar_width, which an older build ignores; it stops writing diff_files_width, so after a revert the file list opens once at its default width.

🔧 Technical overview

  • Mechanism. DiffView gains log: Option<GitLog> and a place (ChangesHeader, Changes, GraphHeader, Graph). side_len / side_row / side_select lay 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 is git log --graph --date-order --decorate=full -n 500 HEAD --branches --remotes split into commits and connector lines, with rev-list --left-right HEAD...@{upstream} marking sides; unfolded commits read git show --name-status -M --diff-merges=first-parent. Side by side is git_diff::split_rows, computed once per diff in show_diff; each draw decides whether it fits (MIN_SPLIT_W), and shown_rows keeps scrolling on the layout drawn.
  • Files. 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_for takes 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.rs and event_loop/activate.rs: keys, mouse, the open path, the new answers, persistence (diff_probe removed); crates/nebula-tui/src/view_jobs.rs: Answer::Log and Answer::CommitFiles.
  • Not done. Staging, committing, fetch, pull and push stay out: this PR only reads. The first cut put the history behind a Ctrl+l mode that replaced the file list; one stacked sidebar, as in VS Code, keeps both in view and needs no mode. --all was left out because stash, notes and PR refs clutter the graph.
  • Gate. make ci: cargo fmt --check clean; clippy adds no warning (the ones it prints are already on main); cargo test --workspace 1708 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 on main without this change.

📝 Notes

🤖 Generated with Claude Code

kaminskypavel and others added 2 commits October 3, 2026 14:24
…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 kaminskypavel changed the title g reads like VS Code's source control: changed files over a commit graph of local and remote branches, commits unfold into their files, diffs side by side feat: improve git integration Oct 3, 2026
@kaminskypavel
kaminskypavel marked this pull request as ready for review October 3, 2026 11:30
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