Skip to main content

Cut and paste Git history safely using bundles and git-filter-repo, with strong dry-run previews

Project description

git-clipboard

Convenience CLI wrappers to cut and paste Git history using git-filter-repo and bundles.

  • git-cut: produce a portable bundle containing only selected paths and their full history.
  • git-paste: import that bundle into another repository, preserving history and optionally merging.

The heavy lifting is done by git-filter-repo; these commands make common flows feel like a simple clipboard.

Minimal clipboard flow

Copy a couple of paths (with full history) from one repo and paste them into another — no extra flags.

# In a source repo
git-cut fileA dirB

# In a target repo
git-paste

Example output (abbreviated):

/abs/path/.git-clipboard/clip-20250101-120000.bundle
/abs/path/.git-clipboard/clip-20250101-120000.json
Imported branch: clip/clip-20250101-120000
{
	"action": "merge-preview",
	"target": "main",
	"source": "clip/clip-20250101-120000",
	"no_ff": false,
	"squash": false,
	"conflicts": false,
	"allow_unrelated_histories": false,
	"auto_allow_unrelated_histories": false,
	"trailers": false,
	"note": null
}
Auto-merge now? [y/N]: y
Merged clip/clip-20250101-120000 into main

That’s it: cut some paths, paste them, confirm merge if it’s clean. If a merge might conflict (or histories are unrelated), you’ll see a preview and no auto-merge is attempted.

Install

  • Requirements: git, git-filter-repo
  • macOS: brew install git-filter-repo

Python package (pipx) install for convenience:

pipx install .
# Afterwards the commands git-cut, git-paste, git-clipboard are available on your PATH

git-cut

Create a bundle containing only the specified paths (files/folders) and their history. The source repo is never modified.

Usage

git-cut [PATH ...] [--repo REPO] [--to-subdir DIR] [--out-dir DIR] [--name NAME] [--force]

Key options

  • -r/--repo: path to the source repository (default: current dir)
  • -t/--to-subdir: re-root the content into a subdirectory inside the clip
  • -o/--out-dir: where to write the .bundle and .json (default: ./.git-clipboard)
  • -n/--name: base filename for the outputs (default: clip-YYYYmmdd-HHMMSS)
  • -f/--force: overwrite existing outputs
  • -d/--dry-run: print a JSON plan without creating output files

Outputs

  • NAME.bundle: a git bundle with all refs from the filtered repo
  • NAME.json: metadata capturing paths, subdir, source remotes, and default branch
  • The last clip pointer is also written to ~/.git-clipboard/last for easy pasting without specifying a path.

git-paste

Import a previously created bundle into a target repository. By default it creates a new branch from the bundle and you can choose to merge it.

Usage

git-paste [BUNDLE] [-m META.json] [-r REPO] [-a NAME] [--ref REF] [--list-refs|-L] [-b BRANCH] [--merge|-M|--squash|-s|--rebase|-R] [--no-ff|-F] [--message|-j MSG] [--dry-run|-d] [--allow-unrelated-histories|-U] [--prompt-merge|-p] [--trailers|-T]

Default behavior

  • Creates a branch clip/<bundle-base-name> from the bundle's first head
  • If --merge/--squash/--rebase is given, merges into the current branch (or --branch)
  • Cleans up a temporary remote used for fetching from the bundle

Ref selection

  • Use --ref to pick a specific ref from the bundle (e.g., --ref main or --ref refs/heads/main).
  • If omitted, git-paste tries the metadata default_branch from the clip. If not available, it falls back to the first head in the bundle.

List refs in a bundle

  • Use --list-refs (-L) to print all refs in the bundle as JSON and exit.
  • If a metadata file is available, default_ref will be included based on the clip’s default_branch.

Example:

git-paste ./clips/clip.bundle --list-refs
# {
#   "action": "list-refs",
#   "bundle": "/abs/path/clips/clip.bundle",
#   "refs": [
#     {"sha": "abc123", "ref": "refs/heads/main"}
#   ],
#   "default_ref": "refs/heads/main"
# }

Clipboard default and obvious mode

  • If BUNDLE is omitted, git-paste looks up the last clip pointer at ~/.git-clipboard/last written by git-cut, and uses that bundle.
  • If you pass no merge flags, git-paste runs a quick merge preview and, if clean, prompts whether to auto-merge the imported branch into the current (or --branch) target. If conflicts are likely or unknown, it won’t auto-merge.

Example (clipboard + obvious mode):

# In source repo
git-cut path/to/subtree --out-dir ../clips --to-subdir imported

# In target repo, just paste with no args. If clean, confirm to auto-merge.
git-paste

Trailers (provenance in commit messages)

  • Use --trailers (-T) to append clip metadata as trailers to merge or squash commit messages.
  • Trailers include: Clip-Bundle, Clip-Source (if available), Clip-Paths, Clip-Subdir, Clip-Created-At, Clip-Ref (imported ref), and Clip-Head (SHA of imported branch head).
  • Behavior:
    • If you pass --message, trailers are added as an extra paragraph.
    • If you don’t pass --message, for non-squash merges the default merge message is preserved and we amend to append trailers.
    • For squash merges, trailers are appended to the squash commit message.

Example:

git-paste ../clips/clip.bundle --merge --allow-unrelated-histories --message "Import clip" --trailers
# Commit message ends with:
# Clip-Bundle: clip.bundle
# Clip-Source: git@github.com:me/repo.git
# Clip-Paths: proj/a
# Clip-Subdir: imported
# Clip-Created-At: 2025-01-01T12:00:00Z
# Clip-Ref: refs/heads/main
# Clip-Head: abcdef1234567890...

Notes for dry-run

  • With --dry-run, paste clones the target repo into a temporary directory, simulates the import and prints a JSON summary, and previews merge conflicts using git merge-tree when possible. The real repo is not modified.
  • Use --allow-unrelated-histories when you later perform a real merge of unrelated histories (often required for fresh repos).
  • Use --prompt-merge to preview conflicts and, if clean, interactively confirm an automatic merge.

Dry-run JSON fields

  • import-branch: action, as_branch, source_ref, remote, head, source_summary

    • source_summary now includes: commit_count, top_level_paths (+ totals), file_count, total_size_bytes, largest_files[{path,size}]
  • merge-preview: action, target, source, no_ff, squash, conflicts, allow_unrelated_histories, auto_allow_unrelated_histories, trailers, source_summary, diff_summary, note

    • diff_summary: range, files_changed, insertions, deletions, changes_sample (up to 50 items; includes rename tuples)
    • head: SHA of the imported branch tip
    • source_summary: quick provenance of the imported branch
    • commit_count: integer
    • top_level_paths: up to 50 entries from the branch root
    • top_level_paths_total: total entries
    • top_level_paths_truncated: true if truncated
  • merge-preview includes:

    • target, source, no_ff, squash, conflicts
    • allow_unrelated_histories, auto_allow_unrelated_histories, trailers
    • source_summary: same shape as above
    • note: reason when merge-base is unknown

Notes and assumptions

  • Assumes git-filter-repo is installed and available as either git filter-repo or git-filter-repo.
  • git-cut clones your repository into a temp directory, filters there, and never touches your working copy.
  • We bundle --all refs after filtering to maximize portability; paste selects the first head by default.
  • If you used --to-subdir when cutting, the directory structure is already remapped inside the bundle; paste doesn’t need to move files.
  • Merge strategies in paste are standard Git merge/rebase flows; resolve conflicts as usual if they arise.

Examples

Cut history of two folders and paste into another repo under a new branch, then merge:

# from source repo
git-cut dotfiles/.config nvim/ --to-subdir configs --out-dir ../clips

# in target repo
git-paste ../clips/clip-20250101-120000.bundle --dry-run --merge
# If clean, perform the merge (often with unrelated histories allowed):
git-paste ../clips/clip-20250101-120000.bundle --merge --allow-unrelated-histories --message "Import configs"

Try it

Quick smoke test and demo:

# Run the end-to-end test; it prints JSON previews and ends with "E2E OK"
bash ./e2e.sh

Tests

Run the end-to-end test script (creates temporary repos, cuts, dry-runs paste, then imports and merges):

bash ./e2e.sh

Troubleshooting

  • If you see git: 'filter-repo' is not a git command, install git-filter-repo.
  • Bundles list heads: git bundle list-heads path/to.bundle.
  • You can delete the generated branch and retry paste safely; the origin repo is never modified by these tools.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

git_clipboard-0.2.2.tar.gz (14.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

git_clipboard-0.2.2-py3-none-any.whl (15.0 kB view details)

Uploaded Python 3

File details

Details for the file git_clipboard-0.2.2.tar.gz.

File metadata

  • Download URL: git_clipboard-0.2.2.tar.gz
  • Upload date:
  • Size: 14.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for git_clipboard-0.2.2.tar.gz
Algorithm Hash digest
SHA256 4529f91be61adea3782bb9d5de8d18ec09ae98ba89d0abe9f1d175ec40812324
MD5 99bf26fcde57ed8cb5652789eeee3a85
BLAKE2b-256 e7e9c59cf1ef7bc7b1489125182256141489f4b8f79173008ce59b884ccb0d5c

See more details on using hashes here.

File details

Details for the file git_clipboard-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: git_clipboard-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 15.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for git_clipboard-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 2cd5f21e4ed62ca6d87f867057946ae71716d9678578d7928dfb64fd73cb7975
MD5 0b3f7fb1a1b6ef3f34f7c43339e85e46
BLAKE2b-256 5ff19dde8e49d38fc5de8488741bad2fae5373ad44f50becb17ec7cce7c78bc2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page