# gitcrumbs
_A tiny CLI tool that captures **durable working states** of your Git repo._
It makes a snapshot **only when changes persist** (e.g., after you pause to test, or step away), and lets you **list**, **diff**, and **restore** those states without interfering with Git.
- **Zero friction**: works with your existing Git workflow.
- **Lightweight**: uses Git’s object store and a small SQLite DB under `.git/gitcrumbs/`.
- **Safe**: won’t change commits or refs.
---
## Why would I want this?
Sometimes you tweak code, run tests, tweak again… and only some of those transient edits “stick.” `gitcrumbs` records **stable** working trees so you can jump back to the exact files you had when something actually worked, even if you never committed.
Think of it as **temporary but reliable breadcrumbs** between commits.
---
## Install
The CLI is published as `gitcrumbs` on PyPI. You can install it system-wide and use it in any repo.
**Recommended (isolated CLIs):**
```bash
pipx install gitcrumbs
```
**Standard pip:**
```bash
python -m pip install gitcrumbs
```
> Requires Python 3.9+ and `git` on your PATH.
---
## Quick Start (2 minutes)
```bash
cd /path/to/your/repo
# 1) Prepare state storage under .git/gitcrumbs/
gitcrumbs init
# 2) Start the tracker: it snapshots only when you make changes and they last (dwell) for some time. Ctrl-C to stop
gitcrumbs track
# or
gitcrumbs track --interval 30 --dwell 90
# 3) See what has been captured
gitcrumbs timeline
# 4) Jump to an earlier/later working state
gitcrumbs previous # alias: gitcrumbs p
gitcrumbs next # alias: gitcrumbs n
# 5) Optional manual snapshot at any time
gitcrumbs snapshot
```
---
## Core ideas
- **Durable change**: a change that remains after a short wait (`--dwell`, e.g., 90s). Flapping edits don’t create noise.
- **Snapshot**: a record of file states (tracked + untracked) at a moment in time, stored in `.git/gitcrumbs/gitcrumbs.db`.
- **Anchoring on restore**: when you restore an old snapshot, `gitcrumbs` **does not** create a new one immediately. It **waits** until you make a new durable change, then records a fresh snapshot that’s linked back to the one you restored from.
---
## Everyday workflows
### 1) “I had it working 10 minutes ago…”
```bash
gitcrumbs timeline
gitcrumbs previous # step back one snapshot
# run tests...
# If it works, you can diff or continue from here
```
### 2) Continuous capture while you work
```bash
gitcrumbs track --interval 15 --dwell 60
# Let this run in another terminal tab or tmux pane
```
### 3) Compare two states
```bash
gitcrumbs diff 12 15
# Shows added/deleted/modified files between snapshots #12 and #15
```
### 4) Restore an older snapshot
```bash
gitcrumbs restore 2
```
### 5) Remove gitcrumbs from a repo
```bash
gitcrumbs remove --dry-run # see what would be deleted under .git/gitcrumbs
gitcrumbs remove --yes # removes gitcrumbs from the current repo
```
---
## Commands (cheat sheet)
```text
gitcrumbs init
Initialize .git/gitcrumbs/ (SQLite DB + config).
gitcrumbs track [--interval N] [--dwell M]
Continuous tracker: snapshot only when the state changes and stays changed for M seconds.
gitcrumbs snapshot
Create a snapshot right now.
gitcrumbs timeline
Show all snapshots with timestamps, branch, and a short summary.
gitcrumbs status
Show tracker status and the current cursor (baseline snapshot id).
gitcrumbs diff A B
Summarize differences between snapshots A and B (added/deleted/modified paths).
gitcrumbs restore ID [--purge/--no-purge]
Restore working files to snapshot ID. Default: --purge (remove extra files).
gitcrumbs next [--purge/--no-purge] # alias: n
Restore to the next snapshot after the current cursor (defaults to --purge).
gitcrumbs previous [--purge/--no-purge] # alias: p
Restore to the previous snapshot before the current cursor (defaults to --purge).
gitcrumbs remove [--dry-run] [--yes|-y]
Delete .git/gitcrumbs (DB and metadata). Safe: does not affect Git commits/branches.
```
---
## How it works (a bit deeper)
- Uses Git plumbing commands (`ls-files`, `diff-files`, `hash-object`, `cat-file`) to compare the **real** working tree to the index/HEAD.
- For tracked files, `gitcrumbs` fingerprints by **content hash**, not mtime/size (so restores don’t cause false deltas).
- For untracked files, it records metadata + hashed content.
- Snapshots live in SQLite; file bytes live in Git’s object store (efficient, deduplicated).
- The tracker writes a tiny `tracker_state.json` so it knows when to anchor after restore and when to create the next snapshot.
---
## Safety & edge cases
- Works **before first commit** (unborn `HEAD`) and in **detached HEAD**.
- Pauses during **merge/rebase** or when `.git/index.lock` exists.
- On restore, if the saved branch no longer exists, it falls back to **detached** at the recorded commit.
- Default restore is **purge on** for predictability; append `--no-purge` to keep extra files.
---
## Troubleshooting
- “`gitcrumbs` not found after installation” → If you used `pip install --user`, ensure `~/.local/bin` is on your PATH; or prefer `pipx`.
- “No snapshots yet” → Start the tracker or run `gitcrumbs snapshot` manually.
- “Snapshot created right after restore” → That should not happen with the anchoring logic. If it does, please open an issue with your `timeline` and steps.
---
## Contributing
Issues and PRs are welcome. If you hit an edge case, share a minimal repro.
---
## License
MIT
Raw data
{
"_id": null,
"home_page": null,
"name": "gitcrumbs",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "git, cli, developer-tools, productivity, snapshots, version-control",
"author": null,
"author_email": "Ertelek <contact.ertelek@yahoo.com>",
"download_url": "https://files.pythonhosted.org/packages/f5/24/7f7c087b03fd5cf8cd6fc99d6ec0db294c05755a99574d09242c7198f3bd/gitcrumbs-0.1.1.tar.gz",
"platform": null,
"description": "# gitcrumbs\n\n_A tiny CLI tool that captures **durable working states** of your Git repo._ \nIt makes a snapshot **only when changes persist** (e.g., after you pause to test, or step away), and lets you **list**, **diff**, and **restore** those states without interfering with Git.\n\n- **Zero friction**: works with your existing Git workflow. \n- **Lightweight**: uses Git\u2019s object store and a small SQLite DB under `.git/gitcrumbs/`. \n- **Safe**: won\u2019t change commits or refs.\n\n---\n\n## Why would I want this?\n\nSometimes you tweak code, run tests, tweak again\u2026 and only some of those transient edits \u201cstick.\u201d `gitcrumbs` records **stable** working trees so you can jump back to the exact files you had when something actually worked, even if you never committed.\n\nThink of it as **temporary but reliable breadcrumbs** between commits.\n\n---\n\n## Install\n\nThe CLI is published as `gitcrumbs` on PyPI. You can install it system-wide and use it in any repo.\n\n**Recommended (isolated CLIs):**\n```bash\npipx install gitcrumbs\n```\n\n**Standard pip:**\n```bash\npython -m pip install gitcrumbs\n```\n\n> Requires Python 3.9+ and `git` on your PATH.\n\n---\n\n## Quick Start (2 minutes)\n\n```bash\ncd /path/to/your/repo\n\n# 1) Prepare state storage under .git/gitcrumbs/\ngitcrumbs init\n\n# 2) Start the tracker: it snapshots only when you make changes and they last (dwell) for some time. Ctrl-C to stop\ngitcrumbs track\n# or\ngitcrumbs track --interval 30 --dwell 90\n\n# 3) See what has been captured\ngitcrumbs timeline\n\n# 4) Jump to an earlier/later working state\ngitcrumbs previous # alias: gitcrumbs p\ngitcrumbs next # alias: gitcrumbs n\n\n# 5) Optional manual snapshot at any time\ngitcrumbs snapshot\n```\n\n---\n\n## Core ideas\n\n- **Durable change**: a change that remains after a short wait (`--dwell`, e.g., 90s). Flapping edits don\u2019t create noise. \n- **Snapshot**: a record of file states (tracked + untracked) at a moment in time, stored in `.git/gitcrumbs/gitcrumbs.db`. \n- **Anchoring on restore**: when you restore an old snapshot, `gitcrumbs` **does not** create a new one immediately. It **waits** until you make a new durable change, then records a fresh snapshot that\u2019s linked back to the one you restored from.\n\n---\n\n## Everyday workflows\n\n### 1) \u201cI had it working 10 minutes ago\u2026\u201d\n```bash\ngitcrumbs timeline\ngitcrumbs previous # step back one snapshot\n# run tests...\n# If it works, you can diff or continue from here\n```\n\n### 2) Continuous capture while you work\n```bash\ngitcrumbs track --interval 15 --dwell 60\n# Let this run in another terminal tab or tmux pane\n```\n\n### 3) Compare two states\n```bash\ngitcrumbs diff 12 15\n# Shows added/deleted/modified files between snapshots #12 and #15\n```\n\n### 4) Restore an older snapshot\n```bash\ngitcrumbs restore 2\n```\n\n### 5) Remove gitcrumbs from a repo\n```bash\ngitcrumbs remove --dry-run # see what would be deleted under .git/gitcrumbs\ngitcrumbs remove --yes # removes gitcrumbs from the current repo\n```\n\n---\n\n## Commands (cheat sheet)\n\n```text\ngitcrumbs init\n Initialize .git/gitcrumbs/ (SQLite DB + config).\n\ngitcrumbs track [--interval N] [--dwell M]\n Continuous tracker: snapshot only when the state changes and stays changed for M seconds.\n\ngitcrumbs snapshot\n Create a snapshot right now.\n\ngitcrumbs timeline\n Show all snapshots with timestamps, branch, and a short summary.\n\ngitcrumbs status\n Show tracker status and the current cursor (baseline snapshot id).\n\ngitcrumbs diff A B\n Summarize differences between snapshots A and B (added/deleted/modified paths).\n\ngitcrumbs restore ID [--purge/--no-purge]\n Restore working files to snapshot ID. Default: --purge (remove extra files).\n\ngitcrumbs next [--purge/--no-purge] # alias: n\n Restore to the next snapshot after the current cursor (defaults to --purge).\n\ngitcrumbs previous [--purge/--no-purge] # alias: p\n Restore to the previous snapshot before the current cursor (defaults to --purge).\n\ngitcrumbs remove [--dry-run] [--yes|-y]\n Delete .git/gitcrumbs (DB and metadata). Safe: does not affect Git commits/branches.\n```\n\n---\n\n## How it works (a bit deeper)\n\n- Uses Git plumbing commands (`ls-files`, `diff-files`, `hash-object`, `cat-file`) to compare the **real** working tree to the index/HEAD. \n- For tracked files, `gitcrumbs` fingerprints by **content hash**, not mtime/size (so restores don\u2019t cause false deltas). \n- For untracked files, it records metadata + hashed content. \n- Snapshots live in SQLite; file bytes live in Git\u2019s object store (efficient, deduplicated). \n- The tracker writes a tiny `tracker_state.json` so it knows when to anchor after restore and when to create the next snapshot.\n\n---\n\n## Safety & edge cases\n\n- Works **before first commit** (unborn `HEAD`) and in **detached HEAD**. \n- Pauses during **merge/rebase** or when `.git/index.lock` exists. \n- On restore, if the saved branch no longer exists, it falls back to **detached** at the recorded commit. \n- Default restore is **purge on** for predictability; append `--no-purge` to keep extra files.\n\n---\n\n## Troubleshooting\n\n- \u201c`gitcrumbs` not found after installation\u201d \u2192 If you used `pip install --user`, ensure `~/.local/bin` is on your PATH; or prefer `pipx`. \n- \u201cNo snapshots yet\u201d \u2192 Start the tracker or run `gitcrumbs snapshot` manually. \n- \u201cSnapshot created right after restore\u201d \u2192 That should not happen with the anchoring logic. If it does, please open an issue with your `timeline` and steps.\n\n---\n\n## Contributing\n\nIssues and PRs are welcome. If you hit an edge case, share a minimal repro.\n\n---\n\n## License\n\nMIT\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Track durable working-tree states for Git repos: snapshot, diff, restore, and auto-track stable changes.",
"version": "0.1.1",
"project_urls": {
"Homepage": "https://github.com/ertelek/gitcrumbs",
"Issues": "https://github.com/ertelek/gitcrumbs/issues",
"Repository": "https://github.com/ertelek/gitcrumbs"
},
"split_keywords": [
"git",
" cli",
" developer-tools",
" productivity",
" snapshots",
" version-control"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "7787bbe5862b19d704d023f4fb341024ffd494e14282e450d2e3b0d507c6ede7",
"md5": "03a88930afaee04dc21c38544907b1a5",
"sha256": "d7f1d1bfa06ac7c3212fc61e0e50487120fd86ead08de2d5ec0f8b1f2ac77d8d"
},
"downloads": -1,
"filename": "gitcrumbs-0.1.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "03a88930afaee04dc21c38544907b1a5",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 12182,
"upload_time": "2025-10-19T10:04:29",
"upload_time_iso_8601": "2025-10-19T10:04:29.908749Z",
"url": "https://files.pythonhosted.org/packages/77/87/bbe5862b19d704d023f4fb341024ffd494e14282e450d2e3b0d507c6ede7/gitcrumbs-0.1.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "f5247f7c087b03fd5cf8cd6fc99d6ec0db294c05755a99574d09242c7198f3bd",
"md5": "08cc9e48f521f566006dd4282c184c26",
"sha256": "58285c3ab26ca81d19227c8b4456c7044a8bc70322009225848382d29a9c51bf"
},
"downloads": -1,
"filename": "gitcrumbs-0.1.1.tar.gz",
"has_sig": false,
"md5_digest": "08cc9e48f521f566006dd4282c184c26",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 14043,
"upload_time": "2025-10-19T10:04:31",
"upload_time_iso_8601": "2025-10-19T10:04:31.314228Z",
"url": "https://files.pythonhosted.org/packages/f5/24/7f7c087b03fd5cf8cd6fc99d6ec0db294c05755a99574d09242c7198f3bd/gitcrumbs-0.1.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-19 10:04:31",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "ertelek",
"github_project": "gitcrumbs",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "gitcrumbs"
}