Git Graph

approved

by Luis Mendez

VS Code's source control graph as a side pane: lanes, refs, and commit details for the vault's git repository. - This plugin has not been manually reviewed by Obsidian staff.

122 downloadsUpdated 13d agoMIT

Git Graph for Obsidian

VS Code's Source Control graph as an Obsidian side pane. If your vault is a git repository (or sits inside one), the pane shows the commit graph: colored lanes, commit subjects, authors, dates, branch/tag/remote badges, the current HEAD, and a row for uncommitted changes. Click a commit to see its full message and changed files.

Read-only: the plugin never runs a git command that modifies the repository. Click a file in an expanded commit to open it in the editor (files outside the vault or no longer present show a notice). Click the changes row to see the uncommitted files; click a file to open it. Deleted files are shown struck through and cannot be opened.

Requirements

  • Obsidian 1.13 or newer, desktop only.
  • git installed. If it is not on your PATH, set the executable in the plugin settings.
  • git 2.13 or newer (--absolute-git-dir is used to resolve the repository's .git directory).

What the plugin touches

Obsidian's community-plugin review flags any plugin that uses Node's fs module or runs external programs. This one does both, for exactly these purposes, and nothing else:

  • Runs the git executable (the one from the Git executable setting) with child_process.execFile — never through a shell — to read the repository: rev-parse, log, for-each-ref, status, show, diff-tree, symbolic-ref. No command writes to the repository or the working tree; the plugin has no commit, checkout, fetch or push.
  • Reads the repository's .git directory with fs.existsSync and fs.watch so the pane refreshes when a commit, branch or checkout happens. For a vault that is a folder inside a larger repository, that directory sits outside the vault. The plugin never writes there.
  • Resolves real paths (fs.realpathSync.native) of the vault folder and the repository root so a vault opened through a symlink or junction matches the paths git reports.

Files are opened in the editor through Obsidian's vault API only. The plugin makes no network requests and collects no telemetry.

Usage

Open the pane from the ribbon icon, or from the command palette: Git Graph: Open. Git Graph: Refresh re-reads the repository on demand; the pane also refreshes on its own when the repository's git state changes (commits, branches, checkouts). Working-tree edits (unstaged/staged file changes) refresh the changes row within about half a second while the pane is open; a git-state change still refreshes the whole graph. When the vault is a folder inside a larger repository, the row counts only files under the vault. The history icon in the pane header narrows the list to just the commits that touched the file open in the active editor, as a flat list with the graph lanes hidden; switching the active file re-filters it, and clicking the icon again returns to the full graph. Renames are followed, including one made in Obsidian and not yet committed. File history obeys the Refs to show setting like the rest of the pane, so on Auto a commit that touched the file on an unmerged branch is not listed until you switch to All.

Settings

SettingDefaultMeaning
Git executablegitCommand or path used to run git.
Refs to showAutoAuto: current branch, its upstream, default remote branch. All: every branch and tag.
Commits per page200Loaded at once; more load as you scroll.
Show working tree changesonRow above the newest commit with the uncommitted change count for files inside the vault; click it for the file list.
Date formatRelative"3 days ago" or YYYY-MM-DD HH:mm.

Development

npm install
npm run test-build   # builds and installs into .obsidian/plugins/git-graph/ — open this repo as a vault
npm run check        # build + lint + dead-code check + LOC backstop + coverage: the pre-commit gate

npm run check runs, in order: build (typecheck + vite build), lint (oxlint then eslint), deadcode (fallow), loc (the LOC report and per-file backstop), then test:coverage (vitest run --coverage, which also runs every test). tsconfig.json includes src/**, tests/**, harness/** and the root configs, so npm run typecheck (vue-tsc --noEmit) type-checks all of them; build and test-build call it instead of inlining vue-tsc.

GateCommandWhat fails it
Typechecknpm run typecheckA type error anywhere under src/**, tests/**, harness/** or the root configs (strict, noUncheckedIndexedAccess).
Buildnpm run buildtypecheck, or the vite build itself, failing.
oxlintnpm run lint:oxlintAny oxlint correctness/suspicious finding.
eslintnpm run lint:eslintAny obsidianmd/typescript-eslint/vue rule, at --max-warnings 0 — including the size/complexity limits: src/** caps max-lines (300), max-lines-per-function (80), complexity (12), max-depth (4), max-params (5), max-nested-callbacks (4); tests/**, harness/** and scripts/** cap max-lines (500), complexity (15), max-depth (4) (no per-function line limit — a describe body is one function). A handful of pre-existing functions exceed these by a small, documented margin; each has a file-scoped override in eslint.config.mjs with a // TODO(quality) comment explaining why an extraction isn't trivial.
deadcodenpm run deadcodefallow finding an unused export, file, component prop, or dependency.
LOCnpm run locAny tracked file (src/, tests/, scripts/, harness/, styles/) exceeding 400 code lines — a backstop for file types eslint's size rules don't cover (.css, .mjs configs). Also prints a per-directory and top-10-files LOC table; --json for machine-readable output.
Coverage thresholdsnpm run test:coveragev8 coverage over src/**/*.{ts,vue} dropping below thresholds: statements 90%, branches 80%, functions 90%, lines 90% (rounded down from the measured baseline, floored at 80/80/80/70).
Testsnpm run testAny vitest failure (dom and node projects).
Test buildnpm run test-buildtypecheck, the dev-mode vite build, or scripts/test-build.mjs (installs into .obsidian/plugins/git-graph/) failing.

Styles

All CSS lives in styles/ as partials, listed in load order by styles/index.css; no .vue file carries a <style> block (tests/build/no-sfc-styles.test.ts enforces that). There is no stylesheet at the repository root — scripts/styles-assemble.mjs concatenates the partials and the git-graph-styles Vite plugin writes the result to dist/styles.css, which is what test-build installs and what a release ships. Edit a partial, never a build output.

The concatenation exists because Obsidian injects a plugin's stylesheet as one blob, so an @import surviving into it would resolve against the app rather than the plugin folder. The browser harness is the exception: it imports styles/index.css and lets Vite resolve the imports itself, so it needs no build step.

Lane colors can be changed by a CSS snippet overriding --git-graph-lane-0--git-graph-lane-7.

CI runs check's gates plus an npm audit job on every push to main and every pull request; the release workflow refuses to publish a commit that job has not passed. See RELEASING.md for cutting a release and CHANGELOG.md for what each one shipped.

Browser harness

harness/ is a standalone Vite app that mounts the real view tree against in-memory fixture repositories, so the pane can be opened, driven and screenshotted in Chromium without installing the plugin into Obsidian — useful for AI agents and for reviewing UI changes. npm run harness serves it on http://localhost:5174, npm run screenshot writes a PNG per scenario and theme into screenshots/, and npm run test:e2e runs the Playwright smoke tests (run npm run harness:install once first to download Chromium). These are deliberately not part of npm run check; run test:e2e before merging a change to src/view/** or styles/**. See harness/README.md for the scenarios, the URL parameters and the window.__harness API.

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.