Patchy logo


npm package license build status


# Patchy 🩹 A CLI for generating and applying patches to git repositories. ## Why Patchy? For long-lived git forks with no plans to merge upstream: - A **git fork** stores your changes as commits. - **Patches** store them as `.diff` Patch files are a clean way to store long-lived changes - human-readable, easy to review and version. But managing them can be cumbersome. Patchy makes managing patches easy: make changes to a clone → save them as patches → reapply anytime. ## How it works `patchy init` sets up your project: ``` ├── patchy.json ← config (source: github.com/org/repo, base: v2.1.0) ├── clones/ │ └── upstream-repo/ ← a clone of the source repo - your working copy └── patches/ ← empty for now - your patches live here ``` The workflow: 1. Make changes directly in `clones/upstream-repo/` 2. Run `patchy generate` to generate patches: ``` patches/ └── 001-feature-name/ ├── src/file.ts.diff ← edits to existing files └── src/newFile.ts ← new files (no .diff suffix) ``` 3. Run `patchy apply` to reapply your patches to `clones/upstream-repo/` anytime See the [example walkthrough](./docs/example.md) for a step-by-step guide. ## Getting started ### Installation ```sh curl -fsSL https://raw.githubusercontent.com/richardgill/patchy/main/install | bash # follow instructions patchy ``` Or via npm: ```sh npm install -g patchy-cli patchy ``` Or use directly without installing: ```sh npx patchy-cli@latest ``` ### Initialize Patchy Run this command to initialize Patchy in your project folder: ```sh patchy init ``` ## `patchy.json` reference ```jsonc { // Git URL or local file path to clone from. "source_repo": "https://github.com/example/repo.git", // Override: --source-repo | env: PATCHY_SOURCE_REPO // Directory containing patch files. "patches_dir": "./patches/", // Override: --patches-dir | env: PATCHY_PATCHES_DIR // Default directory for cloning repos. "clones_dir": "./clones/", // Override: --clones-dir | env: PATCHY_CLONES_DIR // Path to repo you're generating patches from or applying patches to. // Can be relative to clones_dir: / or absolute. "target_repo": "repo", // Override: --target-repo | env: PATCHY_TARGET_REPO // Patch set to generate into (subdirectory of patches_dir). // If not set, prompts interactively or errors in non-interactive mode. "patch_set": "001-security-fixes", // Override: --patch-set | env: PATCHY_PATCH_SET // Git SHA or tag to use as the base for patches. "base_revision": "abc123def", // Override: --base-revision | env: PATCHY_BASE_REVISION // Remote branch to track for updates (e.g., "main"). Used by `patchy base` to find new commits/tags. "upstream_branch": "main" // Override: --upstream-branch | env: PATCHY_UPSTREAM_BRANCH } ``` Precedence: CLI flags > Environment variables > `patchy.json` `patchy.json` uses jsonc, so comments are allowed. ## Patch file layout Patches are stored in the `patches/` directory (customizable via [`patches_dir`](#patchyjson-reference)): ``` ./ ├── patches/ │ └── 001-first-patch-set/ │ ├── path/to/existingFile.txt.diff │ └── path/to/newFile.txt ├── clones/ │ └── repo-clone-1/ │ ├── path/to/existingFile.txt (modified) │ └── path/to/newFile.txt (added) └── patchy.json ``` Patches are grouped into **patch sets** for organizing related changes. Patch sets have numeric prefixes (e.g., `001-auth`, `002-ui`) and are applied in order. Within each patch set files follow the same folder structure as in the `source_repo`. **Two types of patch files:** - **`.diff` files** — For modified existing files (generated via `git diff HEAD`) - **Plain files** — For newly added files (copied verbatim for easier inspection and editing) `patchy generate` automatically removes stale files in `patches/` that no longer correspond to changes in `target_repo`. ## Hooks Patch sets can include executable scripts that run before and after patches are applied: ``` patches/ └── 001-add-feature/ ├── patchy-pre-apply # runs before patches ├── src/file.ts.diff ├── src/new-file.ts └── patchy-post-apply # runs after patches ``` Patch sets support scripts without diffs, enabling pure automation steps. ### Hook execution - Hooks run with `cwd` set to `target_repo` - Environment variables: `PATCHY_TARGET_REPO`, `PATCHY_PATCH_SET`, `PATCHY_PATCHES_DIR`, `PATCHY_PATCH_SET_DIR`, `PATCHY_BASE_REVISION` - Non-zero exit aborts `patchy apply` - Hooks must be executable (`chmod +x`) ### Custom hook prefix ```jsonc { "hook_prefix": "my-prefix-" // Override: --hook-prefix | env: PATCHY_HOOK_PREFIX } ``` With prefix `my-prefix-`, hooks are named `my-prefix-pre-apply` and `my-prefix-post-apply`. ## Commands ### `patchy generate` Generate `.diff` files and new files into `./patches//` based on current `git diff` in `target_repo`. ```sh patchy generate [--patch-set] [--target-repo] [--patches-dir] [--dry-run] ``` If `--patch-set` is not provided (and not set via env/config), prompts to select an existing patch set or create a new one. Note: `patchy generate` is destructive and will remove any unneeded files in the patch set directory. --- ### `patchy apply` Apply patch files from `patches/` into `target_repo`. Patch sets are applied in alphabetical order. ```sh patchy apply [--only ] [--until ] [--auto-commit=] [--on-conflict=] [--target-repo] [--patches-dir] [--dry-run] ``` | Flag | Description | |------|-------------| | `--only ` | Apply only the specified patch set | | `--until ` | Apply patch sets up to and including the specified one | | `--auto-commit=` | Control auto-commit behavior (see below) | | `--on-conflict=` | How to handle patches that fail to apply (see below) | #### Conflict handling When a patch doesn't apply cleanly (e.g., the upstream file changed), patchy can insert git-style conflict markers for manual resolution. | Mode | Behavior | |------|----------| | `markers` (default) | Insert conflict markers and continue | | `error` | Fail immediately (previous behavior) | **Resolving conflicts:** When conflicts occur, patchy outputs instructions: ``` ✗ Applied patches with conflicts to clones/my-repo To resolve: 1. Edit files in clones/my-repo to resolve conflicts (remove conflict markers) 2. Run: patchy generate --patch-set 001-fix 3. Commit the updated patches ``` Conflict markers look like standard git merge conflicts: ``` <<<<<<< current const value = 999; ======= const value = 42; >>>>>>> file.ts.diff ``` Edit the file to keep the correct code, remove the marker lines, then regenerate the patch. #### Auto-commit behavior Each patch set creates a single commit with message `Apply patch set: `. The `--auto-commit` flag controls when commits happen: | Mode | Behavior | |------|----------| | `interactive` (default) | Commits all patch sets automatically, prompts for the last one | | `all` | Commits every patch set immediately after applying | | `skip-last` | Commits all except the last patch set | | `off` | No commits are made | **Notes:** - In non-interactive environments (e.g., CI), `interactive` mode auto-commits everything - Commits are skipped if any patch in the set fails to apply - `--dry-run` skips all commits --- ### `patchy repo reset` Hard reset the Git working tree of `target_repo` to `base_revision`. Discards all local changes and patch commits. ```sh patchy repo reset [--base-revision] [--target-repo] ``` --- ### `patchy repo clone` Clone a repository into a subdirectory of `clones_dir` and checkout `base_revision`. The target directory is derived from the repo name. ```sh patchy repo clone [--source-repo] [--clones-dir] [--base-revision] [--yes] ``` Use `--yes` to skip confirmation prompts and automatically update `patchy.json` with the new target directory. --- ### `patchy base [revision]` View or update the `base_revision` in config. ```sh patchy base # Interactive patchy base abc123def # Set base_revision to the specified SHA or tag ``` --- ### `patchy prime` Prints a prompt you can include in your `AGENTS.md` / `CLAUDE.md`. Tell your agent to run: ```sh patchy prime ``` Or include it directly: ```sh patchy prime >> CLAUDE.md ``` Outputs a brief description of Patchy, key paths, and essential commands to help AI coding agents understand your project's patch workflow. --- ### `patchy config get ` Output a single config value (raw, no label). Useful for shell scripts. ```sh patchy config get target_repo_path # /home/user/project/clones/my-repo patchy config get patch_set # 001-feature patchy config get verbose # false ``` **Available keys:** | Key | Description | |-----|-------------| | `source_repo` | Git URL or local file path | | `target_repo` | Repository name or path | | `clones_dir` | Directory for clones | | `patches_dir` | Directory for patches | | `patch_set` | Current patch set name | | `base_revision` | Base SHA or tag | | `upstream_branch` | Remote branch to track | | `hook_prefix` | Hook script prefix | | `verbose` | Verbose mode ("true"/"false") | | `clones_dir_path` | Absolute path to clones directory | | `target_repo_path` | Absolute path to target repository | | `patches_dir_path` | Absolute path to patches directory | | `patch_set_path` | Absolute path to current patch set | - Unknown keys exit with code 1 - Unset raw keys exit with code 1 - Unset computed keys (e.g., `patch_set_path` when `patch_set` is not set) output an empty line --- ### `patchy config list` Output all config values as aligned key-value pairs. ```sh patchy config list # source_repo https://github.com/example/repo.git # target_repo my-repo # clones_dir ./clones # patches_dir ./patches # patch_set 001-feature # verbose false # clones_dir_path /home/user/project/clones # target_repo_path /home/user/project/clones/my-repo # ... ``` Only defined values are shown. Computed path values are resolved to absolute paths. ## License MIT