Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Git Patcher

Isolated multi-project git diff/patch generator and applier — usable as a CLI (npx git-patcher) or as a local web UI (React + shadcn/ui).

Features

Core (CLI + UI + API)

  • Project isolation — every registered repo gets its own storage namespace under ~/.git-patcher/projects/<id>/ (patch history, metadata). Nothing from one project ever leaks into another, even if two repos share a folder name.
  • Diff/patch generation — generate a unified diff from the whole working tree, staged-only, unstaged-only, or a hand-picked set of files (including untracked/new files), against any base ref.
  • Patch application — apply any generated or saved patch to a project, with:
    • --check dry-run (verify it applies cleanly without touching files)
    • --reverse (undo an already-applied patch)
    • automatic 3-way merge fallback on conflicts, with a per-file pre-flight divergence check
  • Patch history — save named patches per project, list them, view their contents, re-apply them later, or delete them. Patches can be exported/imported as portable, checksummed, optionally GPG-signed JSON bundles, or shared as a GitHub gist.
  • Commit history & blame — paginated git log, commit-to-commit diffing, and git blame at HEAD.
  • Stash — create, list, diff, apply/pop, and drop stashes.
  • Hunk-level index staging — stage/unstage a patch's changes directly against the index (a git add -p equivalent), reusing the same hunk machinery as "save as patch".
  • Amend/fixup — apply a patch straight into the last commit instead of leaving it uncommitted, with a message override and a guard against amending a commit that's already pushed upstream.
  • Patch dependency ordering — mark a saved patch as depending on another; check whether dependencies are satisfied, and topologically sort a batch into a safe apply order (detecting cycles).
  • Webhook notifications — configure a per-project URL that gets a POST whenever a patch is applied. Best-effort/fire-and-forget: a broken webhook never blocks an apply.
  • GPG patch signing — optionally detached-sign a bundle's checksum on export, and verify the signature on import. Signing is additive: unsigned and older-format bundles still import normally.

Web UI + API only (no CLI command yet — cross-project or visually-oriented features that don't map as cleanly onto a single-repo terminal invocation)

  • Blame-aware conflict resolution — a toggleable git blame gutter inside the visual conflict resolver.
  • Multi-repo patches — generate and save one logical patch spanning several registered projects at once, and apply it to all of them together.
  • Cross-project search — search saved-patch names, descriptions, tags, and touched file paths across every registered project at once.
  • Diff-of-diffs — compare two saved patches against each other to see how a fix evolved between iterations.
  • Rule-based commit message suggestion — infers a Conventional-Commits-style subject line from a patch's changed files and stats. Fully offline/deterministic — no LLM call, no network dependency.
  • Line-ending / .gitattributes awareness — advisory (non-blocking) warnings when a patch's added lines mix CRLF/LF, or conflict with a .gitattributes text/eol rule, or introduce CRLF where no rule exists at all.

Interface

  • Web UI — project switcher, a directory browser to register new projects, a checkbox file picker, a syntax-highlighted diff viewer, and one-click apply/save/download, plus everything above.
  • CLI — everything in "Core" above is scriptable: project, status, diff, apply, amend, patches, stash, log, log-diff, blame, stage, watch, ui.

Quick start

As a CLI

npx git-patcher project add /path/to/your/repo --name "My Project"
npx git-patcher status
npx git-patcher diff --all --save my-patch
npx git-patcher patches list
npx git-patcher apply my-patch
npx git-patcher apply my-patch --reverse   # undo it

Every command accepts an optional project argument (an id or a directory path) so you don't have to cd into the repo first:

npx git-patcher status /path/to/other/repo

As a web UI

npx git-patcher ui

This starts a local server (default port 5177) and opens your browser. From there you can register projects, pick files, preview diffs, and apply/save patches visually.

CLI reference

git-patcher project add [dir] [--name <name>]     Register a repo as an isolated project
git-patcher project list                          List all registered projects
git-patcher project rename <idOrDir> <newName>     Rename a project
git-patcher project remove <idOrDir> [--keep-storage]
git-patcher project webhook <idOrDir> [url]        Set (or, with no url, clear) the apply-notification webhook

git-patcher status [idOrDir]                      Show working tree status

git-patcher diff [idOrDir]
  --all | --staged | --unstaged                   Scope of the diff
  -f, --files <files...>                           Only these files
  -i, --interactive                                Pick files interactively (checkbox prompt)
  -o, --output <file>                              Write patch to a file
  --save [name]                                    Save into the project's patch history
  -c, --context <lines>                            Context lines (default 3)

git-patcher watch [idOrDir]                        Continuously regenerate a live patch file on change
  -o, --output <file>                              Patch file to keep up to date (default live.patch)
  -c, --context <lines>                            Context lines (default 3)
  -b, --base <ref>                                 Diff against this ref instead of HEAD
  -d, --debounce <ms>                               Debounce window (default 400)

git-patcher apply <patchFileOrId> [idOrDir]
  --check                                          Dry run, don't modify files
  --reverse                                         Reverse-apply (undo)
  --no-3way                                        Disable 3-way merge fallback

git-patcher amend <patchFileOrId> [idOrDir]        Apply a patch and fold it into the last commit
  -m, --message <message>                          Replace the commit message (default: keep existing)
  --force                                          Allow amending a commit that looks already pushed

git-patcher stage <patchFile> [idOrDir]            Stage a patch file's changes against the index
  --unstage                                        Unstage instead of stage

git-patcher log [idOrDir]                          Show commit history
  -n, --limit <count>                              Number of commits to show (default 20)
  -s, --skip <count>                               Pagination offset (default 0)
  -f, --file <path>                                Only commits touching this file

git-patcher log-diff <shaA> <shaB> [idOrDir]       Diff between two commits (shaA = before, shaB = after)
  -c, --context <lines>                            Context lines (default 3)

git-patcher blame <file> [idOrDir]                 Show who last touched each line, at HEAD
  --from <line> --to <line>                        Restrict to a line range

git-patcher stash list [idOrDir]
git-patcher stash push [idOrDir]                   Stash the working tree (or selected files)
  -m, --message <message>
  --staged                                         Stash only unstaged changes (keep the index)
  -f, --files <files...>                           Stash only these files
  --no-untracked                                   Do not include untracked files
git-patcher stash show <index> [idOrDir]           Print a stash's diff
git-patcher stash apply <index> [idOrDir] [--pop]  Apply a stash (optionally drop it after)
git-patcher stash pop <index> [idOrDir]            Apply and drop a stash
git-patcher stash drop <index> [idOrDir]           Delete a stash without applying it

git-patcher patches list [idOrDir]
git-patcher patches show <patchId> [idOrDir]
git-patcher patches rm <patchId> [idOrDir]
git-patcher patches depends-on <patchId> [ids...] [--project <idOrDir>] [--clear]
                                                    Mark (or clear) a patch's declared dependencies
git-patcher patches check-deps <patchId> [idOrDir] Check whether declared dependencies are applied
git-patcher patches apply-order <patchIds...> [--project <idOrDir>]
                                                    Order patch ids so dependencies come first
git-patcher patches export <patchId> [idOrDir]     Export a saved patch as a portable JSON bundle
  -o, --output <file>                              Write to a file instead of stdout
  --sign                                           GPG-sign the bundle checksum (needs gpg on PATH)
  --key-id <keyId>                                 GPG key to sign with (defaults to your default key)
git-patcher patches import <bundleFile> [idOrDir]  Import a bundle, verifying checksum + signature

git-patcher ui [--port <port>] [--no-open]         Launch the web UI

<patchFileOrId> in apply accepts either a path to a .patch file on disk or a saved patch's id from patches list.

How project isolation works

Each registered project is keyed by a SHA-256 hash of its resolved absolute path. That hash becomes a folder name under ~/.git-patcher/projects/<hash>/, containing:

~/.git-patcher/
  registry.json              # id -> { name, path, webhookUrl, timestamps }
  projects/
    <hash>/
      meta.json
      history.json            # saved patch metadata (name, scope, stats, dependsOn, applied status)
      patches/
        <patchId>.patch        # the actual patch text
  multi-repo-patches/
    index.json                 # multi-repo patch metadata (spans several project ids at once)
    <id>-<projectId>.patch      # one patch file per project in the bundle

Removing a project deletes its storage folder (unless --keep-storage is passed). The actual git repository on disk is never touched by registration/removal — only the tracking metadata is. Multi-repo patches live outside any single project's folder, since they intentionally span more than one.

Optional external dependency: GPG

Patch-bundle signing shells out to a gpg binary on PATH — via --sign/--key-id on patches export in the CLI, or ?sign=true&keyId= on the bundle-export route in the API/UI. It's entirely optional: git-patcher patches export works without it (just produces an unsigned bundle), GET /api/system/gpg-available reports whether signing is usable, and nothing else in the tool depends on it.

Repository layout (for contributors)

packages/
  core/     Pure Node engine: project registry, git diff/apply, patch manager (no UI deps)
  server/   Express API wrapping core, serves the built UI in production
  cli/      Commander-based CLI; vendors core+server+built UI for standalone npm publishing
  ui/       React 19 + Vite + Tailwind v4 + shadcn-style components

Development

npm install          # installs all workspaces
npm run dev          # runs the API server (5177) and Vite dev server (5173) concurrently

The Vite dev server proxies /api/* to http://localhost:5177, so the UI talks to a live server with HMR.

Building for production / publishing the CLI

npm run bundle       # builds the UI, then vendors core+server+ui/dist into packages/cli/vendor
cd packages/cli
npm publish          # `prepack` re-runs the bundle step automatically

The published git-patcher package is fully self-contained — it does not depend on @git-patcher/core or @git-patcher/server as npm packages; those are vendored directly into packages/cli/vendor at pack time so npx git-patcher works with zero workspace resolution.

API

The local server exposes a REST API under /api, consumed by the web UI. This is the full surface as it exists today.

Projects

GET/POST/PATCH/DELETE /api/projects[/:id]
PATCH  /api/projects/:id/webhook              { webhookUrl }  — set/clear the apply-notification webhook
GET    /api/projects/:id/status
GET    /api/projects/:id/refs

Diff / apply

POST   /api/projects/:id/diff                 { scope, filePaths, contextLines, base, save, name }
POST   /api/projects/:id/apply                { patchText, check, reverse, threeWay }
POST   /api/projects/:id/preflight            { patchText } — per-file divergence check
GET    /api/projects/:id/saved-raw?...

Saved patches

GET/DELETE  /api/projects/:id/saved[/:patchId]
PATCH       /api/projects/:id/saved/:patchId              { name, description, tags, dependsOn }
POST        /api/projects/:id/saved/:patchId/apply        { check, reverse, threeWay } → { result, dependencyWarning }
GET         /api/projects/:id/saved/:patchId/base-compatibility
GET         /api/projects/:id/saved/:patchId/bundle?sign=true&keyId=<id>   — export a signed/unsigned JSON bundle
POST        /api/projects/:id/import-bundle                { bundle } → { saved, verifiedSignature }

Commit history & blame

GET  /api/projects/:id/log?skip=&limit=&filePath=
GET  /api/projects/:id/log/:shaA/diff/:shaB?contextLines=
GET  /api/projects/:id/blame?filePath=&from=&to=

Stash

GET/POST     /api/projects/:id/stash                        { message, scope, filePaths, includeUntracked }
GET          /api/projects/:id/stash/:index/diff
POST         /api/projects/:id/stash/:index/apply            { pop }
DELETE       /api/projects/:id/stash/:index

Hunk staging & amend

POST /api/projects/:id/stage-hunks      { patchText }
POST /api/projects/:id/unstage-hunks    { patchText }
POST /api/projects/:id/amend            { patchText, message, force }

Patch dependencies & comparison

GET  /api/projects/:id/saved/:patchId/dependencies
POST /api/projects/:id/saved/apply-order                    { patchIds } → { order }  (400 on a cycle)
GET  /api/projects/:id/saved/:patchIdA/diff-against/:patchIdB

Commit messages & line endings

POST /api/projects/:id/suggest-message         { patchId } or { stats } → { suggestion }  (rule-based, no LLM)
POST /api/projects/:id/line-ending-check        { patchText } → { warnings }  (advisory only)

Multi-repo patches (cross-project, top-level)

POST         /api/multi-repo/diff               { requests: [{ projectId, scope, filePaths, contextLines, base }] }
POST/GET     /api/multi-repo[/:id]
POST         /api/multi-repo/:id/apply           { check, reverse, threeWay } → { results }
DELETE       /api/multi-repo/:id

Cross-project search & system

GET /api/search?q=<query>                        — searches every registered project's saved-patch history
GET /api/system/gpg-available                     — whether a usable gpg binary is on PATH
GET /api/fs/browse?path=<dir>                      — directory listing for the "add project" folder picker

Conflicts, gist export, and other endpoints

The conflict-resolution and GitHub gist-export routes existed before this reference was written and are unchanged; see packages/server/src/routes/*.js for the full source of truth if a route above looks stale — this list is best-effort documentation, not a generated spec.

License

MIT

About

Isolated multi-project git diff/patch generator and applier — usable as a **CLI** (`npx git-patcher`) or as a local **web UI** (React + shadcn/ui).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages