Skip to content

Add a read-only Git Graph tab to projects - #99

Open
vivi7 wants to merge 6 commits into
doctly:mainfrom
vivi7:feat/git-graph-tab
Open

vivi7 wants to merge 6 commits into
doctly:mainfrom
vivi7:feat/git-graph-tab

Conversation

@vivi7

@vivi7 vivi7 commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds a read-only Git Graph tab to projects, right after Git. It shows the commit graph of the attached repositories with ref labels, commit details and diffs.

Depends on #98. The commit-details file list reuses its buildGitChangeTree/gitTreeRows, so #98's two commits show up here until it merges. I'll rebase onto main then.

Following your review, this PR is now read-only. Nothing in it changes a repository. The settings drawer, avatars, the committed config file and every mutating action are gone. The actions can follow in a separate PR, or as a plugin once that system is ready.

What's included

  • Graph: a lane layout for merges, octopus merges, multiple roots, stashes and the Uncommitted Changes row. Nodes are drawn at each table row's measured position, so they stay aligned with the rows, including when details open inline.
  • Ref labels: branches, remote branches (combined with the local label when they match), tags, HEAD and stashes.
  • Filters: a Branches/Tags dropdown with search, A→Z / Z→A, and Select All / Deselect All / Invert Selection, plus a Show Remote toggle.
  • Commit details: click a commit to open its details inline. Ctrl/Cmd-click a second commit to compare them. The file list uses the Git tab's tree from Add list/tree view toggle to the Git tab's Changes list #98, and clicking a file opens it in the existing diff viewer in read-only mode.
  • Table and navigation: find, resizable columns that can be hidden from the header's right-click menu, Load More with automatic loading on scroll, and auto refresh when the repository changes.
  • Context menus: they only copy (hash, subject, ref names, file paths) or view (diff, file at a revision).

Review points addressed

Security

  • Every attribute value is now escaped with escapeAttr, including data-gg-file-path. Every text value goes through escapeHtml.
  • The renderer tests load the real escapeHtml/escapeAttr from utils.js. They push ", ', <, > and & through file paths, branch, remote and tag names, stash messages and commit subjects, and check the markup can't be broken out of.
  • The dialogs you flagged are no longer part of this PR.

Bugs

  • Loading more commits, from Load More or on scroll, keeps the scroll position.
  • Shortcuts only act while the Git Graph tab is the one showing. The check now uses the active project and tab, not body.isConnected.
  • Cmd/Ctrl+Arrow keys are left to the app. Plain ArrowUp/ArrowDown move the selection while details are open.
  • The Cmd+R shortcut and its tooltip are gone, and no shortcut clashes with the app's own.
  • The repository watcher is bound once and resolves the current tab body when it fires, so auto refresh keeps working after switching tabs.
  • Double-click checkout and native alert/confirm are gone along with the actions.

Polish

  • No horizontal scrollbar: columns shrink with ellipsis, and the last column's resize handle no longer overflows.
  • Icons use the app's stroke conventions, and the details Tree/List toggle uses the existing .pane-seg control.

Overlap with #98: gitGraphBuildFileTree/gitGraphCompactFolders are removed in favour of #98's tree.

Changes to existing files

All additive:

  • git.js: read helpers only.
  • projects.js, main.js, preload.js: five read endpoints and a change event, each checking the folder is attached.
  • The tab registration and a tag icon.
  • escapeAttr in utils.js.
  • An opt-in readOnly option for createUnifiedMergeViewer.

Size: about 5k lines, of which ~3.2k are code and ~1.9k tests.

Testing

  • npm test: 523/523. The new tests cover the layout, the git read helpers against real temporary repositories, the attached-folder check on every endpoint, escaping with the real functions, the scroll and shortcut scoping, and watcher re-binding.
  • End-to-end: I drove the real app (isolated data directory) against a fixture repository with 431 rows and hostile names: a file a"b<i>c&.txt, branch feat/a&b, tag v1'x, a stash message <b>"q"</b> and a subject <img src=x onerror=alert(1)>". I checked that:
    • every menu is read-only;
    • hostile names render as text;
    • scroll holds on load;
    • shortcuts don't fire from other tabs, and auto refresh works after switching back;
    • there is no horizontal overflow at 1400px and 1000px;
    • nodes stay aligned with their rows;
    • there are no console errors.

A List/Tree switch in the Changes heading. List keeps today's flat view
and stays the default; Tree groups each status group's files into
collapsible folders, with single-child folder chains compacted into one
row (like VS Code's Source Control "View as Tree").

The choice is remembered in localStorage. Switching views or folding a
folder repaints only the file list, leaving the selection and diff pane
untouched, and keeps keyboard focus on the toggled folder.
@abasiri

abasiri commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Thanks so much for this, @vivi7! The graph itself looks really nice. It's clean, easy to read, and a real step up for seeing what's happening in a repo. It's clear a lot of care went into it, and the main-process side is well built too: every new IPC endpoint checks that the folder is attached, actions come from a fixed table, arguments are validated, and nothing runs through a shell.

We can't merge it quite as it stands, though. Here's what we found.

1. Security: repo content can inject HTML into the renderer. The renderer can run git actions through window.api, so this needs fixing before anything lands:

  • project-git-graph-view.js:1290 puts file paths into data-gg-file-path="…" with escapeHtml, which doesn't escape quotes. A committed file name can break out of the attribute. Please use escapeAttr for every attribute value.
  • Several dialog bodies insert names without escaping: git-graph-dialogs.js:661 (remote branch), :748 (tag name), :852 (stash message) and :1184 (file path). Tag and branch names can contain < and arrive with a fetch.
  • The dialog tests stub escapeHtml/escapeAttr as identity functions, so they can't catch this. Please test with the real functions, and push " and < through paths, ref names and commit subjects.

2. Scope. About 13k lines, with 35 actions that change the repo, a settings system, avatar fetching and a trust model, is a lot for us to take on and maintain. Please split it the way you offered: a read-only graph first (layout, rendering, commit details, and diffs in the existing viewer), without the settings drawer, avatars or the committed config file. Mutating actions can follow in a separate PR.

3. Bugs

  • When you scroll to the end and more commits load, the view jumps back to the top.
  • Keyboard shortcuts stay active after you leave the tab. The keydown guard checks body.isConnected, which stays true when the project view is only hidden (project-git-graph-view.js:1483).
  • Cmd+Up/Down is already the app's shortcut for switching sessions, so both actions run.
  • Cmd+R is taken by the app, so the "Refresh (Ctrl/Cmd+R)" tooltip is wrong.
  • Auto-refresh and progress updates stop after the first tab switch. gitGraphBindWatcher and gitGraphBindProgress bind once and keep the first body, which the next render replaces (:1612-1638).
  • Fetch/push/pull errors never show: state.error is set, then cleared by the reload that runs straight after (:815, :434).
  • Double-clicking a commit checks it out (detached HEAD) with no confirmation. Please also use in-app dialogs instead of native alert/confirm (git-graph-menus.js:60,76, git-graph-dialogs.js:98, git-graph-settings.js:35).

4. Visual polish. The settings tabs, the icons, and the horizontal scrollbar that's always showing need some work to match the rest of the app.

5. Overlap with #98. #98 adds a list/tree view to the Git tab with its own tree builder (buildGitChangeTree). Please reuse it for the commit-details file tree instead of adding a second one (gitGraphBuildFileTree / gitGraphCompactFolders).

Ali is also working on a plugin system, and Git Graph would fit really well there. We'll share more once it's ready.

— Claude, on behalf of Ali

… visible

- Tree is now the default view; a stored 'list' choice is kept.
- Tree rows show the tree node's own name, so a file name containing a backslash isn't cut at it.
- Staged / Unstaged / Staged + unstaged shows on each tree row instead of only in the tooltip.
- Collapsing the folder that holds the selected file highlights that folder row.
Log with parents, refs, stashes and file contents at a revision, all read-only and run through execFile with an argv array.
Builds the graph payload (paging, stashes, uncommitted changes), commit details, comparisons and file contents for diffs, and watches the repository for changes. Every endpoint goes through projects.js and only accepts folders attached to the project; nothing here changes a repository.
A pure function that assigns commits to lanes and computes edges for merges, octopus merges, multiple roots, stashes and the uncommitted row.
A tab right after Git with the commit graph, ref labels, branch and tag filters, find, commit details and comparisons. Files in a commit use the Git tab's tree and open in the existing diff viewer, read-only. Context menus only copy or view.

Values from the repository are escaped for their context (escapeAttr for attributes), shortcuts only act while the tab is visible and avoid the app's own, and loading more commits keeps the scroll position.
@vivi7
vivi7 force-pushed the feat/git-graph-tab branch from 88d0e86 to bb8544a Compare September 30, 2026 12:11
@vivi7 vivi7 changed the title Add a Git Graph tab to projects Add a read-only Git Graph tab to projects Sep 30, 2026
@vivi7

vivi7 commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review! I've reworked this PR as a read-only graph on top of #98, reusing its tree for the commit-details file list. Settings, avatars, the committed config and every mutating action are gone. The security, bug and polish points are addressed as listed in the updated description, with tests using the real escape functions. The actions are ready for a follow-up PR, or a plugin once that lands. Happy to adjust anything.

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.

2 participants