Manage custom changes to cloned git repositories without maintaining forks.
patch-repo captures your uncommitted changes as .patch files, stores them locally, and re-applies them after pulling latest upstream. Think patch-package, but for entire repositories.
brew tap cchitsiang/tap
brew install patch-repogo install github.com/cchitsiang/patch-repo@latestgit clone https://github.com/cchitsiang/patch-repo.git
cd patch-repo
go build -o patch-repo .# Clone a repo you want to customize
git clone https://github.com/someone/cool-project.git
cd cool-project
# Initialize patch-repo (creates .patch-repo/, updates global gitignore)
patch-repo init
# Make your changes (don't commit them)
vim src/config.go
# Save changes as a named patch
patch-repo create fix-auth-timeout
# Later, pull latest upstream and re-apply your patches
patch-repo syncSets up .patch-repo/ in the current git repo and adds it to your global gitignore so patches are never committed upstream.
Captures all uncommitted changes (tracked and untracked files) as a sequenced patch file.
$ patch-repo create fix-auth-timeout
Created 001-fix-auth-timeout.patch
Files: src/config.goName must be lowercase alphanumeric and hyphens only.
Applies all patches in sequence order. Uses 3-way merge when a patch doesn't apply cleanly, leaving conflict markers instead of refusing entirely.
$ patch-repo apply
Applied 001-fix-auth-timeout.patch
Applied 002-add-logging.patch
All 2 patches applied successfullyShows all stored patches with affected files.
$ patch-repo list
001 | fix-auth-timeout | src/config.go
002 | add-logging | src/server.go, src/middleware.goDeletes a patch by name or sequence number. Remaining patches are renumbered to stay contiguous.
$ patch-repo remove 1
Removed 001-fix-auth-timeout.patch
Remaining patches:
001-add-logging.patchShows the full diff contents of stored patches. Without arguments, shows all patches. With an argument, shows a specific patch by name or number.
$ patch-repo diff
=== 001-fix-auth-timeout.patch ===
diff --git a/src/config.go b/src/config.go
...
$ patch-repo diff 1
=== 001-fix-auth-timeout.patch ===
...Clears all existing patches and creates one fresh patch from all current uncommitted changes. Useful after resolving conflicts or when patches have gotten messy.
$ patch-repo refresh my-customizations
Cleared 3 existing patches
Created 001-my-customizations.patch
Files: src/config.go, src/server.goDiscards all uncommitted changes, pulls latest from upstream, and re-applies all patches. Uses 3-way merge for conflict resolution. Prompts for confirmation before proceeding.
$ patch-repo sync
This will discard all uncommitted changes and pull latest. Continue? [y/N] y
Discarding uncommitted changes...
Pulling latest...
Applying patches...
Applied 001-fix-auth-timeout.patch
Sync complete: pulled latest and applied 1 patches- Patches are standard
git diffoutput stored in.patch-repo/inside the repo root - Files are named
NNN-<name>.patch(e.g.001-fix-auth-timeout.patch) .patch-repo/is added to your global gitignore oninit, so patches never pollute the upstream repo- All git operations use the
gitbinary on your PATH
If upstream changes conflict with a patch during apply or sync, 3-way merge creates standard conflict markers in the affected files:
$ patch-repo apply
Applied 001-fix-auth-timeout.patch with CONFLICTS in:
- src/config.go
Some patches have conflicts. Resolve them, then recreate the patch:
1. Edit the conflicted files (look for <<<<<<< markers)
2. patch-repo remove <name>
3. patch-repo create <name>Or use refresh to start clean:
# Resolve the conflict markers in your editor, then:
patch-repo refresh my-customizations- Go 1.21+ (for building)
giton PATH
MIT