Repository Pack: portable, validated patch packages for Git working trees.
rpack is a small CLI tool for moving code changes between repositories as a single .rpack file. It does not create commits, branches, or otherwise modify Git history. It only validates and applies patches to the working tree.
rpack create -o change.rpack
rpack inspect change.rpack
rpack check change.rpack
rpack lint change.rpack
rpack diagnose change.rpack
rpack rebase change.rpack
rpack open change.rpack
rpack apply change.rpack
rpack undoPlain patch files are useful, but they are easy to lose context around. rpack wraps a Git patch with:
- a manifest
- SHA-256 checksums
- package metadata
- dry-run validation
- local apply history
- undo support
The result is still simple: after rpack apply, you review the working tree and decide whether to commit.
This repository is in early MVP stage. The current format is rpack-v1 and focuses on ordered Git patch packages for working trees.
Implemented:
.rpackas ZIPmanifest.json- one or more ordered
git diff --binarypatches create,inspect,check,diagnose,lint,rebase,apply,undo,historyopenfor guided package application- checksum verification
- clean working tree requirement by default
git apply --checkbefore apply- per-repository local state under Git metadata
- detailed package summary and per-patch/per-file diff stats in
inspect - Windows
.rpackfile association through the MSI installer - .NET global tool package metadata
- GitHub Actions CI
- runtime path prefix mapping for packages built from repository subdirectory snapshots
Not implemented yet:
- package signing
- binary file overlay mode
- NuGet publication
- GitHub release automation
Download the latest Windows MSI from the GitHub releases page:
https://github.com/ppotepa/rpack/releasesThe MSI installs rpack.exe, rpack-open.exe, associates .rpack files with
rpack, and adds the installation directory to PATH.
Open a new terminal after installation if rpack is not immediately found.
You can also build from source:
dotnet buildRun through dotnet run:
dotnet run --project src/Rpack.Cli -- create -o change.rpackBuild a local .NET tool package:
dotnet pack src/Rpack.Cli -c ReleaseBuild Windows MSI locally (both variants at once):
.\scripts\build-windows-msi.ps1 -Version 0.1.21This creates two installers:
rpack-<version>-win-x64-self-contained.msiincludes the .NET runtime and is best for clean Windows machines.rpack-<version>-win-x64-framework-dependent.msiis much smaller and requires the .NET 10 Desktop Runtime x64 to be installed.
Build only one variant when needed:
.\scripts\build-windows-msi.ps1 -Version 0.1.21 -Variant framework-dependent
.\scripts\build-windows-msi.ps1 -Version 0.1.21 -Variant self-containedCreate a package from current unstaged working tree changes:
rpack create -o change.rpackrpack create currently emits one aggregate patch entry. Packages created manually or by agents may include multiple ordered patch entries in Patches.
Set package metadata when useful:
rpack create -o change.rpack --id change-123 --title "Fix parser" --description "Parser and tests"Create a package from staged changes:
rpack create --staged -o change.rpackCreate a package from a revision range, still as a working tree patch:
rpack create --from HEAD~2 --to HEAD -o change.rpackrpack create writes one aggregate patch. To build a multi-patch package,
create the ZIP manually and declare every patch in manifest.json.
Each patch must be a Git diff that git apply can read, and patches must be
listed in the exact order they should be applied. Do not rely on filename
sorting; the Patches array is the source of truth.
For a patch series based on commits, create one diff per step:
mkdir -p package/patches
git diff --binary HEAD~2 HEAD~1 > package/patches/0001-core.patch
git diff --binary HEAD~1 HEAD > package/patches/0002-tests.patchFor independent working-tree areas, split by path:
mkdir -p package/patches
git diff --binary -- src/Rpack.Core > package/patches/0001-core.patch
git diff --binary -- tests > package/patches/0002-tests.patchCompute SHA-256 for each patch and put the lowercase value into the matching
manifest entry. The current reader expects JSON property names in the casing
shown here, such as Format, Mode, Patches, Path, Kind, and Sha256.
sha256sum package/patches/*.patchOn PowerShell:
Get-FileHash package\patches\*.patch -Algorithm SHA256"Patches": [
{
"Path": "patches/0001-core.patch",
"Kind": "git-diff",
"Sha256": "<lowercase-sha256-of-0001-core.patch>"
},
{
"Path": "patches/0002-tests.patch",
"Kind": "git-diff",
"Sha256": "<lowercase-sha256-of-0002-tests.patch>"
}
]Recommended archive layout:
change.rpack
|-- manifest.json
|-- patches/
| |-- 0001-core.patch
| `-- 0002-tests.patch
|-- checksums.sha256
`-- README.mdchecksums.sha256 is optional for the current reader, but useful for humans:
<lowercase-sha256-of-0001-core.patch> patches/0001-core.patch
<lowercase-sha256-of-0002-tests.patch> patches/0002-tests.patchPlace manifest.json at the archive root and ZIP the package contents, not the
parent directory:
(cd package && zip -r ../change.rpack manifest.json patches checksums.sha256 README.md)Before sharing a manual multi-patch package, verify it:
rpack inspect change.rpack
rpack check change.rpackRebase a package against another repository working tree:
rpack rebase change.rpack ./other-repo -o change-rebased.rpackrebase applies the package to a detached worktree at the target repository
HEAD, then writes a new working-tree patch package. The rebased package currently
contains one aggregate patch entry, even if the input package had multiple
ordered patches.
Inspect a package without applying it:
rpack inspect change.rpackinspect now shows a full patch breakdown:
- total file/line/hunk counts
- per-patch summaries
- per-file +/− lines, hunk counts, and category (Code/Tests/Docs/Scripts/Assets/Other)
Example:
Package summary:
- patches: 3
- files changed: 18
- lines added: 1240
- lines removed: 310
- total diff hunks: 86
- binary files: 0Then for each patch:
PATCH 004 — InkFrame
- DefaultBuildInkFrameStep.cs +280 / -90 h:8 Code
- ...
Subtotal: +420 -90 hunks:12Check whether a package applies to the current repository:
rpack check change.rpackDiagnose a failed check without modifying the working tree:
rpack diagnose change.rpackLint a package for generated output, common secret markers, local machine paths, and fragile patch quality:
rpack lint change.rpackApply a package to the current repository:
rpack apply change.rpackHandle existing-file add conflicts explicitly when needed:
rpack apply change.rpack --allow-existing-added-files modify
rpack apply change.rpack --resolve-added-file-conflicts as-modify
rpack apply change.rpack --allow-existing-added-files skip
rpack apply change.rpack --allow-existing-added-files overwriteBy default, rpack fails on added-file conflicts (abort). The same conflict
resolution modes are accepted by check, diagnose, rebase, and apply;
as-modify is accepted as an alias for modify.
Current text-file conflict behavior:
abortreports the conflict and stops.skipremoves that added-file block from the temporary patch when target content differs.modify,as-modify, andoverwriterewrite the added-file block as a modify patch against the existing target text file.
These modes do not overwrite binary or non-text added-file conflicts; those still fail when Git cannot apply them safely.
Open a package with a guided inspect/check/apply flow:
rpack open change.rpackopen first looks for a Git repository by walking upward from the .rpack
file location. Packages created by current rpack versions also include
Source.ProjectPath as a local repository hint. When present and valid,
rpack open and rpack-open.exe can use that path, so a package can be opened
from Downloads or Desktop without copying it into the target repository.
An explicit repo argument still wins over the manifest hint:
rpack open change.rpack ./repo
rpack open change.rpack --repo ./repoCheck or apply to an explicit repository:
rpack check change.rpack ./repo
rpack apply change.rpack ./repoApply a package whose patch paths are relative to a repository subdirectory snapshot:
rpack inspect change.rpack --path-prefix src
rpack check change.rpack ./repo --path-prefix src
rpack apply change.rpack ./repo --path-prefix srcUse this when a package contains paths such as aot/project/file.cs, but the
real Git-root path is src/aot/project/file.cs.
By default, rpack lets Git ignore whitespace-only differences in patch context
lines. This makes packages more robust to CRLF/LF and final-newline drift between
working trees while still applying the actual changed lines from the patch.
Use strict patch context only when exact whitespace context is important:
rpack check change.rpack --strict
rpack apply change.rpack --strict
rpack open change.rpack --strict--strict-whitespace is accepted as a more explicit alias. The older
--ignore-space-change flag is still accepted for compatibility, but it is now
the default behavior.
Undo the last applied package:
rpack undoShow local rpack history:
rpack historyPrint the installed version:
rpack --versionThe Windows MSI registers .rpack files with rpack-open.exe and installs the
rpack icon for the file association. Both rpack.exe and rpack-open.exe are
published with the same embedded icon on Windows builds.
When one or more packages are double-clicked, rpack opens a single batch window.
Additional .rpack files opened while that window is already running are added
to the same list instead of opening more windows.
During multi-select opens, helper processes wait for the first window to finish
starting and hand their package paths to that same window. This also applies to
Shift dirty-tree mode.
For each package, rpack:
- finds the target Git repository from the package location
- uses
Source.ProjectPathfrom the manifest when the package is outside a repository and the path exists locally - inspects the package
- verifies checksums
- runs
git apply --check - shows status, warnings, and detailed errors in the batch window
- captures hidden
gitcommand output in theConsole logtab - applies checked ready packages only after explicit confirmation
Normal double-click keeps the default safety model and requires a clean working
tree. The clicked .rpack file itself is ignored for this clean-tree check when
it is stored inside the target repository, so an untracked incoming package does
not block its own application.
Hold Shift while double-clicking to open the same package with dirty working tree allowed. This only enables the equivalent of:
rpack open change.rpack --allow-dirtyIt does not bypass checksum verification, dry-run apply, or strict base behavior
when --strict-base is used. The context menu also includes an extended
Shift-right-click action named Apply with rpack allowing dirty.
The Allow whitespace context match checkbox is enabled by default. Uncheck it
to force strict patch context matching for pending packages.
The batch window stops applying at the first failed package and keeps the raw Git
or package error available in the Status / errors tab for diagnosis. Git
processes launched by rpack-open.exe run without visible console windows; their
stdout, stderr, working directory, and exit codes are shown in the Console log
tab.
If you want a coding agent such as ChatGPT or Codex to return changes as an .rpack file, see LLM_AGENTS.md.
By default, rpack check and rpack apply require:
- valid
.rpackarchive - valid
manifest.json - matching SHA-256 checksums
- safe archive paths
- clean Git working tree based on real tracked diffs plus untracked files
- successful
git apply --checkwith whitespace-compatible context matching
rpack lint additionally scans package paths and patch content for risky
generated outputs, .rpack files, common secret markers, local machine paths,
large hunks, no-final-newline markers, and trailing whitespace. Lint does not
modify the target repository.
Lint errors include generated or unsafe package paths such as logs/,
artifacts/, release/, bin/, obj/, .env, .key, .pem, .pdb,
.exe, .dll, .rpack, .user, and .suo. Lint also warns on local path
markers such as D:\Git\, C:\Users\, and /home/.
Source commit mismatch is a warning by default. This is intentional: rpack is meant to apply patches to compatible working trees, even when Git history differs.
Use --strict-base when the target repository must be at the recorded source base commit:
rpack apply change.rpack --strict-baseUse --allow-dirty only when applying into a dirty working tree is intentional:
rpack apply change.rpack --allow-dirty
rpack open change.rpack --allow-dirtyrpack undo blocks extra dirty paths by default and allows them only with:
rpack undo --allow-dirty--path-prefix is applied only at check/apply time after package checksum
verification. It does not modify the .rpack file or its manifest.
The prefix must be a relative safe path. Absolute paths and prefixes containing
.. are rejected.
Default whitespace-compatible context matching changes only Git patch context matching. It does not skip checksum verification, clean-tree checks, base checks, or package path safety.
Use --strict or --strict-whitespace to require exact context whitespace.
When a package tries to add files that already exist in the target repository,
rpack compares the existing target file with the file content encoded in the
patch. If the content matches, allowing CRLF/LF differences, rpack safely skips
that added-file block and applies the remaining patch. If the content differs,
rpack check reports an added-file conflict with target/package byte counts,
target last-write time, and package creation time.
rpack deliberately does not choose a winner by file size or timestamp. A larger or newer file is useful diagnostic information, not a safe overwrite policy.
The MVP stores .rpack files as ZIP archives:
manifest.json
patches/
change.patch
checksums.sha256
README.mdOnly these archive parts are required by the current reader:
manifest.json- one or more patch files referenced by
manifest.json - a matching
Sha256value in each manifest patch entry
checksums.sha256 and package README.md are recommended for humans and future
tooling, but they are not currently required for validation.
Packages may contain multiple patch files. The Patches array in manifest.json
is ordered. rpack checks and applies patches in that order, and rpack undo
reverses the same patch list in reverse order.
Manifest validation rules:
Formatmust berpack-v1.Modemust beworking-tree-patch.Patchesmust contain at least one entry.- each patch entry must have non-empty
PathandSha256. - each patch
Kindmust begit-diff. - patch paths must be relative archive paths and must not contain
... - every declared patch path must exist in the archive.
- the patch bytes must match the declared SHA-256 value.
Minimal valid manifest:
{
"Format": "rpack-v1",
"Mode": "working-tree-patch",
"RequiresCleanTree": true,
"Patches": [
{
"Path": "patches/change.patch",
"Kind": "git-diff",
"Sha256": "<lowercase-sha256-of-patches/change.patch>"
}
],
"Validation": []
}Example manifest:
{
"Format": "rpack-v1",
"Id": "rpack-20260606154000",
"Title": "Repository patch package",
"Description": "",
"CreatedAtUtc": "2026-06-06T15:40:00Z",
"BaseCommit": "abc123",
"Source": {
"Repository": "example",
"ProjectPath": "D:\\Git\\example",
"BaseCommit": "abc123",
"HeadCommit": "abc123"
},
"RequiresCleanTree": true,
"Mode": "working-tree-patch",
"Patches": [
{
"Path": "patches/0001-core.patch",
"Kind": "git-diff",
"Sha256": "..."
},
{
"Path": "patches/0002-tests.patch",
"Kind": "git-diff",
"Sha256": "..."
}
],
"Validation": []
}Validation must be an array of objects, not an array of strings. Use an empty
array when no package-level validation commands are declared. When validation is
included, each item uses the Name, Command, and Optional fields:
"Validation": [
{
"Name": "Build",
"Command": "dotnet build",
"Optional": false
}
]In the current MVP, Validation entries are package metadata only. rpack
validates that the manifest shape is correct, but it does not execute these
commands yet.
Because rpack is intended to be installed and run from PATH, state is stored per target repository under Git's metadata path for rpack:
.git/rpack/
├─ apply-log.json
└─ applied/
└─ <apply-id>/
├─ patches/
│ ├─ 0001-core.patch
│ └─ 0002-tests.patch
└─ manifest.jsonrpack undo uses the stored manifest and patch files. It runs git apply --reverse --check for each patch before reverting them in reverse order.
Build:
dotnet buildTest:
dotnet testProject layout:
src/
Rpack.Cli/
Rpack.Core/
Rpack.Open/
tests/
Rpack.Tests/See ROADMAP.md.
See CHANGELOG.md.
MIT. See LICENSE.