git-clipboard


Namegit-clipboard JSON
Version 0.2.1 PyPI version JSON
download
home_pageNone
SummaryCut and paste Git history safely using bundles and git-filter-repo, with strong dry-run previews
upload_time2025-08-30 20:54:17
maintainerNone
docs_urlNone
authorvv111y
requires_python>=3.8
licenseMIT
keywords bundle git git-filter-repo history subtree
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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 try to make common flows one-liners.

## Install

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

Python package (pipx) install for convenience:

```bash
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

```bash
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

```bash
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:

```bash
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):

```bash
# 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:

```bash
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:

```bash
# 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:

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

Minimal clipboard flow:

```bash
# In a source repo
git-cut some/path --to-subdir imported

# In a target repo
git-paste   # uses the last clip; if clean, confirm to auto-merge
```

## Tests

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

```bash
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

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "git-clipboard",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "bundle, git, git-filter-repo, history, subtree",
    "author": "vv111y",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/cf/d1/6d2a513430bf13d7acab505aba944d619ea9c1a2d0a5877c0c5527e05a27/git_clipboard-0.2.1.tar.gz",
    "platform": null,
    "description": "# git-clipboard\n\nConvenience CLI wrappers to cut and paste Git history using git-filter-repo and bundles.\n\n- git-cut: produce a portable bundle containing only selected paths and their full history.\n- git-paste: import that bundle into another repository, preserving history and optionally merging.\n\nThe heavy lifting is done by git-filter-repo; these commands try to make common flows one-liners.\n\n## Install\n\n- Requirements: git, git-filter-repo\n- macOS: `brew install git-filter-repo`\n\nPython package (pipx) install for convenience:\n\n```bash\npipx install .\n# Afterwards the commands git-cut, git-paste, git-clipboard are available on your PATH\n```\n\n## git-cut\n\nCreate a bundle containing only the specified paths (files/folders) and their history. The source repo is never modified.\n\nUsage\n\n```bash\ngit-cut [PATH ...] [--repo REPO] [--to-subdir DIR] [--out-dir DIR] [--name NAME] [--force]\n```\n\n### Key options\n\n- -r/--repo: path to the source repository (default: current dir)\n- -t/--to-subdir: re-root the content into a subdirectory inside the clip\n- -o/--out-dir: where to write the .bundle and .json (default: ./.git-clipboard)\n- -n/--name: base filename for the outputs (default: clip-YYYYmmdd-HHMMSS)\n- -f/--force: overwrite existing outputs\n- -d/--dry-run: print a JSON plan without creating output files\n\n### Outputs\n\n- NAME.bundle: a git bundle with all refs from the filtered repo\n- NAME.json: metadata capturing paths, subdir, source remotes, and default branch\n- The last clip pointer is also written to ~/.git-clipboard/last for easy pasting without specifying a path.\n\n## git-paste\n\nImport 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.\n\nUsage\n\n```bash\ngit-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]\n```\n\n### Default behavior\n\n- Creates a branch `clip/<bundle-base-name>` from the bundle's first head\n- If --merge/--squash/--rebase is given, merges into the current branch (or --branch)\n- Cleans up a temporary remote used for fetching from the bundle\n\n### Ref selection\n\n- Use `--ref` to pick a specific ref from the bundle (e.g., `--ref main` or `--ref refs/heads/main`).\n- 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.\n\n### List refs in a bundle\n\n- Use `--list-refs` (`-L`) to print all refs in the bundle as JSON and exit.\n- If a metadata file is available, `default_ref` will be included based on the clip\u2019s `default_branch`.\n\nExample:\n\n```bash\ngit-paste ./clips/clip.bundle --list-refs\n# {\n#   \"action\": \"list-refs\",\n#   \"bundle\": \"/abs/path/clips/clip.bundle\",\n#   \"refs\": [\n#     {\"sha\": \"abc123\", \"ref\": \"refs/heads/main\"}\n#   ],\n#   \"default_ref\": \"refs/heads/main\"\n# }\n```\n\n### Clipboard default and obvious mode\n\n- If BUNDLE is omitted, git-paste looks up the last clip pointer at `~/.git-clipboard/last` written by git-cut, and uses that bundle.\n- 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\u2019t auto-merge.\n\nExample (clipboard + obvious mode):\n\n```bash\n# In source repo\ngit-cut path/to/subtree --out-dir ../clips --to-subdir imported\n\n# In target repo, just paste with no args. If clean, confirm to auto-merge.\ngit-paste\n```\n\n### Trailers (provenance in commit messages)\n\n- Use `--trailers` (`-T`) to append clip metadata as trailers to merge or squash commit messages.\n- 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).\n- Behavior:\n\t- If you pass `--message`, trailers are added as an extra paragraph.\n\t- If you don\u2019t pass `--message`, for non-squash merges the default merge message is preserved and we amend to append trailers.\n\t- For squash merges, trailers are appended to the squash commit message.\n\nExample:\n\n```bash\ngit-paste ../clips/clip.bundle --merge --allow-unrelated-histories --message \"Import clip\" --trailers\n# Commit message ends with:\n# Clip-Bundle: clip.bundle\n# Clip-Source: git@github.com:me/repo.git\n# Clip-Paths: proj/a\n# Clip-Subdir: imported\n# Clip-Created-At: 2025-01-01T12:00:00Z\n# Clip-Ref: refs/heads/main\n# Clip-Head: abcdef1234567890...\n```\n\n### Notes for dry-run\n\n- 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.\n- Use `--allow-unrelated-histories` when you later perform a real merge of unrelated histories (often required for fresh repos).\n- Use `--prompt-merge` to preview conflicts and, if clean, interactively confirm an automatic merge.\n\n### Dry-run JSON fields\n\n- import-branch: action, as_branch, source_ref, remote, head, source_summary\n\t- source_summary now includes: commit_count, top_level_paths (+ totals), file_count, total_size_bytes, largest_files[{path,size}]\n- merge-preview: action, target, source, no_ff, squash, conflicts, allow_unrelated_histories, auto_allow_unrelated_histories, trailers, source_summary, diff_summary, note\n\t- diff_summary: range, files_changed, insertions, deletions, changes_sample (up to 50 items; includes rename tuples)\n\t- head: SHA of the imported branch tip\n\t- source_summary: quick provenance of the imported branch\n\t- commit_count: integer\n\t- top_level_paths: up to 50 entries from the branch root\n\t- top_level_paths_total: total entries\n\t- top_level_paths_truncated: true if truncated\n\n- merge-preview includes:\n\t- target, source, no_ff, squash, conflicts\n\t- allow_unrelated_histories, auto_allow_unrelated_histories, trailers\n\t- source_summary: same shape as above\n\t- note: reason when merge-base is unknown\n\n## Notes and assumptions\n\n- Assumes git-filter-repo is installed and available as either `git filter-repo` or `git-filter-repo`.\n- `git-cut` clones your repository into a temp directory, filters there, and never touches your working copy.\n- We bundle `--all` refs after filtering to maximize portability; paste selects the first head by default.\n- If you used `--to-subdir` when cutting, the directory structure is already remapped inside the bundle; paste doesn\u2019t need to move files.\n- Merge strategies in paste are standard Git merge/rebase flows; resolve conflicts as usual if they arise.\n\n## Examples\n\nCut history of two folders and paste into another repo under a new branch, then merge:\n\n```bash\n# from source repo\ngit-cut dotfiles/.config nvim/ --to-subdir configs --out-dir ../clips\n\n# in target repo\ngit-paste ../clips/clip-20250101-120000.bundle --dry-run --merge\n# If clean, perform the merge (often with unrelated histories allowed):\ngit-paste ../clips/clip-20250101-120000.bundle --merge --allow-unrelated-histories --message \"Import configs\"\n```\n\n## Try it\n\nQuick smoke test and demo:\n\n```bash\n# Run the end-to-end test; it prints JSON previews and ends with \"E2E OK\"\nbash ./e2e.sh\n```\n\nMinimal clipboard flow:\n\n```bash\n# In a source repo\ngit-cut some/path --to-subdir imported\n\n# In a target repo\ngit-paste   # uses the last clip; if clean, confirm to auto-merge\n```\n\n## Tests\n\nRun the end-to-end test script (creates temporary repos, cuts, dry-runs paste, then imports and merges):\n\n```bash\nbash ./e2e.sh\n```\n\n## Troubleshooting\n\n- If you see `git: 'filter-repo' is not a git command`, install git-filter-repo.\n- Bundles list heads: `git bundle list-heads path/to.bundle`.\n- You can delete the generated branch and retry paste safely; the origin repo is never modified by these tools.\n\n## License\n\nMIT\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Cut and paste Git history safely using bundles and git-filter-repo, with strong dry-run previews",
    "version": "0.2.1",
    "project_urls": {
        "Homepage": "https://github.com/vv111y/git-clipboard"
    },
    "split_keywords": [
        "bundle",
        " git",
        " git-filter-repo",
        " history",
        " subtree"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "89a35b66c3b667a7cb171c0cc2f9917c5ac2231174be47ef378eeae046ee9c7e",
                "md5": "021f7ec7f2975a4bba7b99f4ef5754e8",
                "sha256": "681c36fa563e5565212a20689c14b94f82f42b799792d74583aeba31bdc2b060"
            },
            "downloads": -1,
            "filename": "git_clipboard-0.2.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "021f7ec7f2975a4bba7b99f4ef5754e8",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 14756,
            "upload_time": "2025-08-30T20:54:15",
            "upload_time_iso_8601": "2025-08-30T20:54:15.896741Z",
            "url": "https://files.pythonhosted.org/packages/89/a3/5b66c3b667a7cb171c0cc2f9917c5ac2231174be47ef378eeae046ee9c7e/git_clipboard-0.2.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "cfd16d2a513430bf13d7acab505aba944d619ea9c1a2d0a5877c0c5527e05a27",
                "md5": "6a26192526b987f00448ea6e8568c699",
                "sha256": "9e1d2ad699db8d8b7c96ec7e40a2dc6878f8f8c5810fe52328543f0a43c2bd82"
            },
            "downloads": -1,
            "filename": "git_clipboard-0.2.1.tar.gz",
            "has_sig": false,
            "md5_digest": "6a26192526b987f00448ea6e8568c699",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 13883,
            "upload_time": "2025-08-30T20:54:17",
            "upload_time_iso_8601": "2025-08-30T20:54:17.586667Z",
            "url": "https://files.pythonhosted.org/packages/cf/d1/6d2a513430bf13d7acab505aba944d619ea9c1a2d0a5877c0c5527e05a27/git_clipboard-0.2.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-30 20:54:17",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "vv111y",
    "github_project": "git-clipboard",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "git-clipboard"
}
        
Elapsed time: 0.60993s