git-tree-state
v0.1.1
Published
Incremental git file status for a repository
Downloads
248
Readme
git-tree-state
Incremental git file status for a repository. Performs one full git status snapshot at startup, then keeps a cached per-file map in sync via filesystem events and scoped porcelain refreshes.
Install
npm install git-tree-stateOne-shot APIs
import { getRepoGitState, getFileGitState } from 'git-tree-state';
const allDirty = await getRepoGitState('/path/to/repo');
const oneFile = await getFileGitState('/path/to/repo', 'src/app.ts');Watcher API
Filesystem events are dirty hints only. The watcher debounces changed paths, runs scoped git status --porcelain=v2 -z -- <paths>, compares against the cache, and emits change only when the computed git state actually changes.
Git metadata (index, HEAD, refs) is watched separately, so commands like git add, git commit, git reset, and checkout also trigger semantic updates.
import { createGitStateWatcher } from 'git-tree-state';
const watcher = await createGitStateWatcher('/path/to/repo', {
debounceMs: 100,
ignored: ['**/dist/**'],
});
console.log(watcher.getState());
const unsubscribe = watcher.on('change', (changes) => {
for (const change of changes) {
console.log(change.path, change.previous, '->', change.current);
}
});
// Force a resync if watcher events may have been dropped:
await watcher.refresh();
await watcher.close();
unsubscribe();Performance model
| Operation | Cost |
|-----------|------|
| Startup | One full git status --porcelain=v2 -z |
| File edit | Debounced batch of scoped status calls for dirty paths only |
| Git metadata change (git add, commit, reset, checkout) | Debounced full status refresh |
| Listener | Fires only on semantic GitState transitions (including dirty → clean) |
Use refresh() to force a resync after missed watcher events or external operations you do not trust the watcher to catch.
Types
type GitState = {
status:
| 'added'
| 'modified'
| 'deleted'
| 'renamed'
| 'copied'
| 'unknown';
staged: boolean;
unstaged: boolean;
path: string;
oldPath?: string;
};
type GitStateChange = {
path: string;
previous?: GitState;
current?: GitState;
};Simulation harness
For scenario-style tests and local inspection, use the test-support simulation harness in src/test-support/simulator.ts. It runs real git and filesystem operations against createGitStateWatcher() and renders a terminal tree with per-file git state badges.
npm test -- src/simulation.test.ts
npm run simulateSet GIT_TREE_STATE_SIM_PRINT=1 to print tree snapshots after each step (npm run simulate enables this automatically).
Development
npm install
npm test
npm run build
npm run simulateCI and release
CI
Every push and pull request to main runs .github/workflows/ci.yml: npm ci, build, and test on Node 20 and 22.
Release (manual staged publish)
Releases are triggered manually via .github/workflows/release.yml using npm staged publishing.
1. Start the workflow
In GitHub: Actions → Release → Run workflow, enter the version (e.g. 1.2.3 or 1.2.3-beta.1 — no v prefix).
The workflow will:
- Run tests on Node 22
- Set
package.jsonversion to the input you provided - Build the package
- Run
npm stage publish(uploads to npm’s staging queue; not installable yet)
2. Approve on npm
A maintainer with 2FA must approve before the version goes live:
- npmjs.com → package → Staged Packages → Approve, or
npm stage approve git-tree-state@<version>
Prerequisites
| Requirement | Notes |
|-------------|--------|
| Trusted publishing | Configure on npm for this repo/workflow; use stage-only so CI can npm stage publish but not npm publish |
| npm CLI ≥ 11.15.0 | Release job upgrades npm globally before staging |
| Node ≥ 22.14.0 | Release job uses Node 22 |
| Package on registry | Staging requires an existing package; publish 0.1.0 manually once if needed |
| 2FA on npm | Required for the approval step (CI cannot approve) |
Provenance
The release workflow sets id-token: write for OIDC. With trusted publishing configured on npm, provenance attestations are generated automatically when staging — no NPM_TOKEN or --provenance flag in the workflow.
