Git Graph
approvedby 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.
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.
gitinstalled. If it is not on your PATH, set the executable in the plugin settings.- git 2.13 or newer (
--absolute-git-diris used to resolve the repository's.gitdirectory).
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
gitexecutable (the one from the Git executable setting) withchild_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
.gitdirectory withfs.existsSyncandfs.watchso 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
| Setting | Default | Meaning |
|---|---|---|
| Git executable | git | Command or path used to run git. |
| Refs to show | Auto | Auto: current branch, its upstream, default remote branch. All: every branch and tag. |
| Commits per page | 200 | Loaded at once; more load as you scroll. |
| Show working tree changes | on | Row above the newest commit with the uncommitted change count for files inside the vault; click it for the file list. |
| Date format | Relative | "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.
| Gate | Command | What fails it |
|---|---|---|
| Typecheck | npm run typecheck | A type error anywhere under src/**, tests/**, harness/** or the root configs (strict, noUncheckedIndexedAccess). |
| Build | npm run build | typecheck, or the vite build itself, failing. |
| oxlint | npm run lint:oxlint | Any oxlint correctness/suspicious finding. |
| eslint | npm run lint:eslint | Any 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. |
| deadcode | npm run deadcode | fallow finding an unused export, file, component prop, or dependency. |
| LOC | npm run loc | Any 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 thresholds | npm run test:coverage | v8 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). |
| Tests | npm run test | Any vitest failure (dom and node projects). |
| Test build | npm run test-build | typecheck, 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.