firnpack 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- firnpack-1.0.0/LICENSE +21 -0
- firnpack-1.0.0/PKG-INFO +192 -0
- firnpack-1.0.0/README.md +167 -0
- firnpack-1.0.0/firnpack.egg-info/PKG-INFO +192 -0
- firnpack-1.0.0/firnpack.egg-info/SOURCES.txt +19 -0
- firnpack-1.0.0/firnpack.egg-info/dependency_links.txt +1 -0
- firnpack-1.0.0/firnpack.egg-info/entry_points.txt +3 -0
- firnpack-1.0.0/firnpack.egg-info/requires.txt +4 -0
- firnpack-1.0.0/firnpack.egg-info/top_level.txt +1 -0
- firnpack-1.0.0/firnpack.py +3169 -0
- firnpack-1.0.0/pyproject.toml +47 -0
- firnpack-1.0.0/setup.cfg +4 -0
- firnpack-1.0.0/tests/test_docs.py +105 -0
- firnpack-1.0.0/tests/test_e2e.py +856 -0
- firnpack-1.0.0/tests/test_fuzz_compact.py +130 -0
- firnpack-1.0.0/tests/test_guards.py +100 -0
- firnpack-1.0.0/tests/test_m1.py +890 -0
- firnpack-1.0.0/tests/test_m2_branch.py +257 -0
- firnpack-1.0.0/tests/test_m2_merge.py +401 -0
- firnpack-1.0.0/tests/test_m2_safety.py +651 -0
- firnpack-1.0.0/tests/test_regressions.py +193 -0
firnpack-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fabrizio Hernandez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
firnpack-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: firnpack
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: a glacier for your files — every commit a single packed layer of history.
|
|
5
|
+
Author: Fabrizio Hernandez
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: version control,vcs,backup,single-user,archive
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
16
|
+
Classifier: Topic :: System :: Archiving :: Backup
|
|
17
|
+
Classifier: Topic :: Software Development :: Version Control
|
|
18
|
+
Requires-Python: >=3.13.16
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
23
|
+
Requires-Dist: ruff>=0.8; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# firnpack
|
|
27
|
+
|
|
28
|
+
> a glacier for your files — every commit a single packed layer of history.
|
|
29
|
+
|
|
30
|
+
[](https://github.com/fabzter/firnpack/actions/workflows/ci.yml)
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
**firnpack** is version control for people who don't want version control
|
|
34
|
+
machinery. If you work on small projects, on one computer, and you've ever
|
|
35
|
+
wished you could just *save* a version and get back to it later — without
|
|
36
|
+
staging areas, remotes, or a `.git` folder that grows thousands of tiny files
|
|
37
|
+
on a tired hard drive — firnpack is for you.
|
|
38
|
+
|
|
39
|
+
Every commit is one file: a compressed, checksummed, immutable archive of your
|
|
40
|
+
whole project, stacked on the ones before it. A repository with 100 commits
|
|
41
|
+
holds 102 files. That's the whole design.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
Requires Python 3.13.16 or newer. From a clone:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
pip install .
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
which registers both `firnpack` and the shorter `firn` command. (Or run the
|
|
52
|
+
single module directly: `python firnpack.py …` — the whole engine is one
|
|
53
|
+
file.)
|
|
54
|
+
|
|
55
|
+
## Quickstart
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
cd my-project
|
|
59
|
+
firnpack init
|
|
60
|
+
firnpack commit # packs everything — that's it
|
|
61
|
+
# ...work...
|
|
62
|
+
firnpack commit # a new layer
|
|
63
|
+
firnpack status # what changed since the last pack
|
|
64
|
+
firnpack history # every saved version, newest first
|
|
65
|
+
firnpack diff # what exactly changed, line by line
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
That's the whole daily loop: five commands, no staging, no jargon you have
|
|
69
|
+
to look up. Misspelled a commit message? `firnpack amend -m "fixed"` replaces
|
|
70
|
+
the head layer without adding a new one.
|
|
71
|
+
|
|
72
|
+
## Commands
|
|
73
|
+
|
|
74
|
+
| Command | What it does |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `firnpack init [PATH]` | create a repository in this folder (or PATH) |
|
|
77
|
+
| `firnpack commit [-m MSG]` | pack the working tree into a new layer |
|
|
78
|
+
| `firnpack status [--full] [--ignored]` | what changed since the last pack |
|
|
79
|
+
| `firnpack history [-n N] [--all] [BRANCH]` | saved versions, newest first |
|
|
80
|
+
| `firnpack diff` | line-by-line changes vs the last pack |
|
|
81
|
+
| `firnpack branch NAME [FROM]` | create a branch (at HEAD, or at FROM) |
|
|
82
|
+
| `firnpack switch REF` | move to a branch, or pin to a layer id |
|
|
83
|
+
| `firnpack list-branches` | branches and their heads |
|
|
84
|
+
| `firnpack rename-branch OLD NEW` | rename a branch |
|
|
85
|
+
| `firnpack delete-branch NAME` | delete a branch (warns if heads orphan) |
|
|
86
|
+
| `firnpack merge BRANCH [--abort]` | merge a branch in; abort a pending one |
|
|
87
|
+
| `firnpack amend [-m MSG]` | replace the head layer (same parents) |
|
|
88
|
+
| `firnpack restore REF [--to DIR]` | bring a layer back over the worktree |
|
|
89
|
+
| `firnpack export REF --to DIR` | extract a layer to a fresh directory |
|
|
90
|
+
| `firnpack verify` | re-hash every layer; check database consistency |
|
|
91
|
+
| `firnpack compact --keep N [--dry-run] [--yes] [--archive DIR]` | squash old layers, reclaim disk |
|
|
92
|
+
|
|
93
|
+
Every command accepts `--json` for machine-readable output — firnpack is
|
|
94
|
+
designed to be operated by people *and* by agents; see
|
|
95
|
+
[`docs/agent-contract.md`](docs/agent-contract.md) for the normative
|
|
96
|
+
contract.
|
|
97
|
+
|
|
98
|
+
## Branching and merging
|
|
99
|
+
|
|
100
|
+
Branches are names for heads; nothing else. `branch` creates one (staying
|
|
101
|
+
where you are), `switch` moves you — to a branch, or to a pinned layer id
|
|
102
|
+
when you hand it one. `merge` is a plain 3-way merge: clean trees merge into
|
|
103
|
+
one layer that remembers both parents; conflicting trees leave git-style
|
|
104
|
+
markers in the files and a durable "merge in progress" state that `status`
|
|
105
|
+
reports and `merge --abort` clears. A merge never clobbers uncommitted work:
|
|
106
|
+
with drift in the worktree it refuses instead.
|
|
107
|
+
|
|
108
|
+
## Undo, rescue, explore
|
|
109
|
+
|
|
110
|
+
- **`amend`** replaces the head layer — fix a message or a file without
|
|
111
|
+
stacking another version on top. The replaced layer stays on disk until a
|
|
112
|
+
`compact` collects it.
|
|
113
|
+
- **`restore REF`** brings any layer back over the worktree. If you have
|
|
114
|
+
uncommitted work, firnpack **rescues it first** as its own layer and tells
|
|
115
|
+
you — restore never destroys drift.
|
|
116
|
+
- **`export REF --to DIR`** extracts a layer into a fresh directory and
|
|
117
|
+
touches nothing else: the zero-risk way to look at an old version.
|
|
118
|
+
|
|
119
|
+
## Checking the glacier
|
|
120
|
+
|
|
121
|
+
`firnpack verify` re-hashes every layer against its own filename, cross-checks
|
|
122
|
+
the database, and reports dangling references or orphan files. It is the
|
|
123
|
+
answer to "is my history still sound?" after a power cut, a disk scare, or a
|
|
124
|
+
copy between machines. If the database itself is lost or corrupt,
|
|
125
|
+
`verify --rebuild-db` reconstructs it from the layers themselves — and says
|
|
126
|
+
plainly which branch names could not be recovered.
|
|
127
|
+
|
|
128
|
+
## History management: compact
|
|
129
|
+
|
|
130
|
+
Whole-tree layers are the honest price of a glacier you can verify one file
|
|
131
|
+
at a time. When history grows past comfort, `compact --keep N` squashes
|
|
132
|
+
everything below the newest N layers into one foundation layer and reclaims
|
|
133
|
+
the disk.
|
|
134
|
+
|
|
135
|
+
It is **never automatic and never silent**:
|
|
136
|
+
|
|
137
|
+
- `--keep N` is required every time — there is no default to trip over;
|
|
138
|
+
- `--dry-run` prints the full plan (what drops, what rewrites, what gets
|
|
139
|
+
collected, peak disk needed) and changes nothing;
|
|
140
|
+
- real runs require `--yes`;
|
|
141
|
+
- `--archive DIR` exports every layer whose content is about to disappear
|
|
142
|
+
into a zip before anything is deleted;
|
|
143
|
+
- branches that point into the squashed range refuse the compact by name;
|
|
144
|
+
branches that sit on rewritten layers follow the rewrite, and the plan and
|
|
145
|
+
the output say exactly which branch moved where.
|
|
146
|
+
|
|
147
|
+
A reminder line after `commit` tells you when the glacier crosses 50 layers.
|
|
148
|
+
Nothing is ever compacted behind your back.
|
|
149
|
+
|
|
150
|
+
## Ignoring junk
|
|
151
|
+
|
|
152
|
+
firnpack reads the standard `.gitignore` file at your project root, so the
|
|
153
|
+
ignore rules you already have keep working. If you have none, `init` seeds
|
|
154
|
+
one line for you: `~$*` — the hidden lock files office tools leave behind.
|
|
155
|
+
Excluded files are never silently hidden: `status` counts them, and
|
|
156
|
+
`status --ignored` lists every one.
|
|
157
|
+
|
|
158
|
+
## Safety, in plain words
|
|
159
|
+
|
|
160
|
+
- A commit either happens completely or not at all. A power cut mid-pack
|
|
161
|
+
leaves your repository exactly as it was.
|
|
162
|
+
- Every layer carries its own checksum, and the layer's *name* is that
|
|
163
|
+
checksum — tampering or disk rot is detectable by re-reading.
|
|
164
|
+
- Extraction of a layer is guarded: a corrupt or hostile archive can never
|
|
165
|
+
write outside your project folder, no matter what paths it claims to
|
|
166
|
+
contain.
|
|
167
|
+
- No command destroys uncommitted work: merge, switch and restore all refuse
|
|
168
|
+
on drift, or rescue it first and say so.
|
|
169
|
+
- History rewrites (compact) require explicit permission, report everything
|
|
170
|
+
they move, and archive what they delete.
|
|
171
|
+
|
|
172
|
+
## Documentation
|
|
173
|
+
|
|
174
|
+
- [`docs/usage.md`](docs/usage.md) — every command: examples, exit codes,
|
|
175
|
+
JSON shapes
|
|
176
|
+
- [`docs/agent-contract.md`](docs/agent-contract.md) — the normative contract
|
|
177
|
+
for automated operators
|
|
178
|
+
- [`docs/storage.md`](docs/storage.md) — layer format, database schema,
|
|
179
|
+
recovery playbook
|
|
180
|
+
- [`docs/decisions.md`](docs/decisions.md) — the decision log
|
|
181
|
+
- [`SPEC.md`](SPEC.md) — the full specification; [`research/`](research/) —
|
|
182
|
+
the research behind it
|
|
183
|
+
|
|
184
|
+
## Status
|
|
185
|
+
|
|
186
|
+
v1.0 candidate: the full command surface above is implemented and tested
|
|
187
|
+
(unit, property and end-to-end CLI suites, CI across Linux, Windows and
|
|
188
|
+
macOS). PyPI publication follows the v1.0.0 tag.
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT — see [LICENSE](LICENSE).
|
firnpack-1.0.0/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# firnpack
|
|
2
|
+
|
|
3
|
+
> a glacier for your files — every commit a single packed layer of history.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/fabzter/firnpack/actions/workflows/ci.yml)
|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
**firnpack** is version control for people who don't want version control
|
|
9
|
+
machinery. If you work on small projects, on one computer, and you've ever
|
|
10
|
+
wished you could just *save* a version and get back to it later — without
|
|
11
|
+
staging areas, remotes, or a `.git` folder that grows thousands of tiny files
|
|
12
|
+
on a tired hard drive — firnpack is for you.
|
|
13
|
+
|
|
14
|
+
Every commit is one file: a compressed, checksummed, immutable archive of your
|
|
15
|
+
whole project, stacked on the ones before it. A repository with 100 commits
|
|
16
|
+
holds 102 files. That's the whole design.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
Requires Python 3.13.16 or newer. From a clone:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
pip install .
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
which registers both `firnpack` and the shorter `firn` command. (Or run the
|
|
27
|
+
single module directly: `python firnpack.py …` — the whole engine is one
|
|
28
|
+
file.)
|
|
29
|
+
|
|
30
|
+
## Quickstart
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
cd my-project
|
|
34
|
+
firnpack init
|
|
35
|
+
firnpack commit # packs everything — that's it
|
|
36
|
+
# ...work...
|
|
37
|
+
firnpack commit # a new layer
|
|
38
|
+
firnpack status # what changed since the last pack
|
|
39
|
+
firnpack history # every saved version, newest first
|
|
40
|
+
firnpack diff # what exactly changed, line by line
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
That's the whole daily loop: five commands, no staging, no jargon you have
|
|
44
|
+
to look up. Misspelled a commit message? `firnpack amend -m "fixed"` replaces
|
|
45
|
+
the head layer without adding a new one.
|
|
46
|
+
|
|
47
|
+
## Commands
|
|
48
|
+
|
|
49
|
+
| Command | What it does |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `firnpack init [PATH]` | create a repository in this folder (or PATH) |
|
|
52
|
+
| `firnpack commit [-m MSG]` | pack the working tree into a new layer |
|
|
53
|
+
| `firnpack status [--full] [--ignored]` | what changed since the last pack |
|
|
54
|
+
| `firnpack history [-n N] [--all] [BRANCH]` | saved versions, newest first |
|
|
55
|
+
| `firnpack diff` | line-by-line changes vs the last pack |
|
|
56
|
+
| `firnpack branch NAME [FROM]` | create a branch (at HEAD, or at FROM) |
|
|
57
|
+
| `firnpack switch REF` | move to a branch, or pin to a layer id |
|
|
58
|
+
| `firnpack list-branches` | branches and their heads |
|
|
59
|
+
| `firnpack rename-branch OLD NEW` | rename a branch |
|
|
60
|
+
| `firnpack delete-branch NAME` | delete a branch (warns if heads orphan) |
|
|
61
|
+
| `firnpack merge BRANCH [--abort]` | merge a branch in; abort a pending one |
|
|
62
|
+
| `firnpack amend [-m MSG]` | replace the head layer (same parents) |
|
|
63
|
+
| `firnpack restore REF [--to DIR]` | bring a layer back over the worktree |
|
|
64
|
+
| `firnpack export REF --to DIR` | extract a layer to a fresh directory |
|
|
65
|
+
| `firnpack verify` | re-hash every layer; check database consistency |
|
|
66
|
+
| `firnpack compact --keep N [--dry-run] [--yes] [--archive DIR]` | squash old layers, reclaim disk |
|
|
67
|
+
|
|
68
|
+
Every command accepts `--json` for machine-readable output — firnpack is
|
|
69
|
+
designed to be operated by people *and* by agents; see
|
|
70
|
+
[`docs/agent-contract.md`](docs/agent-contract.md) for the normative
|
|
71
|
+
contract.
|
|
72
|
+
|
|
73
|
+
## Branching and merging
|
|
74
|
+
|
|
75
|
+
Branches are names for heads; nothing else. `branch` creates one (staying
|
|
76
|
+
where you are), `switch` moves you — to a branch, or to a pinned layer id
|
|
77
|
+
when you hand it one. `merge` is a plain 3-way merge: clean trees merge into
|
|
78
|
+
one layer that remembers both parents; conflicting trees leave git-style
|
|
79
|
+
markers in the files and a durable "merge in progress" state that `status`
|
|
80
|
+
reports and `merge --abort` clears. A merge never clobbers uncommitted work:
|
|
81
|
+
with drift in the worktree it refuses instead.
|
|
82
|
+
|
|
83
|
+
## Undo, rescue, explore
|
|
84
|
+
|
|
85
|
+
- **`amend`** replaces the head layer — fix a message or a file without
|
|
86
|
+
stacking another version on top. The replaced layer stays on disk until a
|
|
87
|
+
`compact` collects it.
|
|
88
|
+
- **`restore REF`** brings any layer back over the worktree. If you have
|
|
89
|
+
uncommitted work, firnpack **rescues it first** as its own layer and tells
|
|
90
|
+
you — restore never destroys drift.
|
|
91
|
+
- **`export REF --to DIR`** extracts a layer into a fresh directory and
|
|
92
|
+
touches nothing else: the zero-risk way to look at an old version.
|
|
93
|
+
|
|
94
|
+
## Checking the glacier
|
|
95
|
+
|
|
96
|
+
`firnpack verify` re-hashes every layer against its own filename, cross-checks
|
|
97
|
+
the database, and reports dangling references or orphan files. It is the
|
|
98
|
+
answer to "is my history still sound?" after a power cut, a disk scare, or a
|
|
99
|
+
copy between machines. If the database itself is lost or corrupt,
|
|
100
|
+
`verify --rebuild-db` reconstructs it from the layers themselves — and says
|
|
101
|
+
plainly which branch names could not be recovered.
|
|
102
|
+
|
|
103
|
+
## History management: compact
|
|
104
|
+
|
|
105
|
+
Whole-tree layers are the honest price of a glacier you can verify one file
|
|
106
|
+
at a time. When history grows past comfort, `compact --keep N` squashes
|
|
107
|
+
everything below the newest N layers into one foundation layer and reclaims
|
|
108
|
+
the disk.
|
|
109
|
+
|
|
110
|
+
It is **never automatic and never silent**:
|
|
111
|
+
|
|
112
|
+
- `--keep N` is required every time — there is no default to trip over;
|
|
113
|
+
- `--dry-run` prints the full plan (what drops, what rewrites, what gets
|
|
114
|
+
collected, peak disk needed) and changes nothing;
|
|
115
|
+
- real runs require `--yes`;
|
|
116
|
+
- `--archive DIR` exports every layer whose content is about to disappear
|
|
117
|
+
into a zip before anything is deleted;
|
|
118
|
+
- branches that point into the squashed range refuse the compact by name;
|
|
119
|
+
branches that sit on rewritten layers follow the rewrite, and the plan and
|
|
120
|
+
the output say exactly which branch moved where.
|
|
121
|
+
|
|
122
|
+
A reminder line after `commit` tells you when the glacier crosses 50 layers.
|
|
123
|
+
Nothing is ever compacted behind your back.
|
|
124
|
+
|
|
125
|
+
## Ignoring junk
|
|
126
|
+
|
|
127
|
+
firnpack reads the standard `.gitignore` file at your project root, so the
|
|
128
|
+
ignore rules you already have keep working. If you have none, `init` seeds
|
|
129
|
+
one line for you: `~$*` — the hidden lock files office tools leave behind.
|
|
130
|
+
Excluded files are never silently hidden: `status` counts them, and
|
|
131
|
+
`status --ignored` lists every one.
|
|
132
|
+
|
|
133
|
+
## Safety, in plain words
|
|
134
|
+
|
|
135
|
+
- A commit either happens completely or not at all. A power cut mid-pack
|
|
136
|
+
leaves your repository exactly as it was.
|
|
137
|
+
- Every layer carries its own checksum, and the layer's *name* is that
|
|
138
|
+
checksum — tampering or disk rot is detectable by re-reading.
|
|
139
|
+
- Extraction of a layer is guarded: a corrupt or hostile archive can never
|
|
140
|
+
write outside your project folder, no matter what paths it claims to
|
|
141
|
+
contain.
|
|
142
|
+
- No command destroys uncommitted work: merge, switch and restore all refuse
|
|
143
|
+
on drift, or rescue it first and say so.
|
|
144
|
+
- History rewrites (compact) require explicit permission, report everything
|
|
145
|
+
they move, and archive what they delete.
|
|
146
|
+
|
|
147
|
+
## Documentation
|
|
148
|
+
|
|
149
|
+
- [`docs/usage.md`](docs/usage.md) — every command: examples, exit codes,
|
|
150
|
+
JSON shapes
|
|
151
|
+
- [`docs/agent-contract.md`](docs/agent-contract.md) — the normative contract
|
|
152
|
+
for automated operators
|
|
153
|
+
- [`docs/storage.md`](docs/storage.md) — layer format, database schema,
|
|
154
|
+
recovery playbook
|
|
155
|
+
- [`docs/decisions.md`](docs/decisions.md) — the decision log
|
|
156
|
+
- [`SPEC.md`](SPEC.md) — the full specification; [`research/`](research/) —
|
|
157
|
+
the research behind it
|
|
158
|
+
|
|
159
|
+
## Status
|
|
160
|
+
|
|
161
|
+
v1.0 candidate: the full command surface above is implemented and tested
|
|
162
|
+
(unit, property and end-to-end CLI suites, CI across Linux, Windows and
|
|
163
|
+
macOS). PyPI publication follows the v1.0.0 tag.
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: firnpack
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: a glacier for your files — every commit a single packed layer of history.
|
|
5
|
+
Author: Fabrizio Hernandez
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: version control,vcs,backup,single-user,archive
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
16
|
+
Classifier: Topic :: System :: Archiving :: Backup
|
|
17
|
+
Classifier: Topic :: Software Development :: Version Control
|
|
18
|
+
Requires-Python: >=3.13.16
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
23
|
+
Requires-Dist: ruff>=0.8; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# firnpack
|
|
27
|
+
|
|
28
|
+
> a glacier for your files — every commit a single packed layer of history.
|
|
29
|
+
|
|
30
|
+
[](https://github.com/fabzter/firnpack/actions/workflows/ci.yml)
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
**firnpack** is version control for people who don't want version control
|
|
34
|
+
machinery. If you work on small projects, on one computer, and you've ever
|
|
35
|
+
wished you could just *save* a version and get back to it later — without
|
|
36
|
+
staging areas, remotes, or a `.git` folder that grows thousands of tiny files
|
|
37
|
+
on a tired hard drive — firnpack is for you.
|
|
38
|
+
|
|
39
|
+
Every commit is one file: a compressed, checksummed, immutable archive of your
|
|
40
|
+
whole project, stacked on the ones before it. A repository with 100 commits
|
|
41
|
+
holds 102 files. That's the whole design.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
Requires Python 3.13.16 or newer. From a clone:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
pip install .
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
which registers both `firnpack` and the shorter `firn` command. (Or run the
|
|
52
|
+
single module directly: `python firnpack.py …` — the whole engine is one
|
|
53
|
+
file.)
|
|
54
|
+
|
|
55
|
+
## Quickstart
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
cd my-project
|
|
59
|
+
firnpack init
|
|
60
|
+
firnpack commit # packs everything — that's it
|
|
61
|
+
# ...work...
|
|
62
|
+
firnpack commit # a new layer
|
|
63
|
+
firnpack status # what changed since the last pack
|
|
64
|
+
firnpack history # every saved version, newest first
|
|
65
|
+
firnpack diff # what exactly changed, line by line
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
That's the whole daily loop: five commands, no staging, no jargon you have
|
|
69
|
+
to look up. Misspelled a commit message? `firnpack amend -m "fixed"` replaces
|
|
70
|
+
the head layer without adding a new one.
|
|
71
|
+
|
|
72
|
+
## Commands
|
|
73
|
+
|
|
74
|
+
| Command | What it does |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `firnpack init [PATH]` | create a repository in this folder (or PATH) |
|
|
77
|
+
| `firnpack commit [-m MSG]` | pack the working tree into a new layer |
|
|
78
|
+
| `firnpack status [--full] [--ignored]` | what changed since the last pack |
|
|
79
|
+
| `firnpack history [-n N] [--all] [BRANCH]` | saved versions, newest first |
|
|
80
|
+
| `firnpack diff` | line-by-line changes vs the last pack |
|
|
81
|
+
| `firnpack branch NAME [FROM]` | create a branch (at HEAD, or at FROM) |
|
|
82
|
+
| `firnpack switch REF` | move to a branch, or pin to a layer id |
|
|
83
|
+
| `firnpack list-branches` | branches and their heads |
|
|
84
|
+
| `firnpack rename-branch OLD NEW` | rename a branch |
|
|
85
|
+
| `firnpack delete-branch NAME` | delete a branch (warns if heads orphan) |
|
|
86
|
+
| `firnpack merge BRANCH [--abort]` | merge a branch in; abort a pending one |
|
|
87
|
+
| `firnpack amend [-m MSG]` | replace the head layer (same parents) |
|
|
88
|
+
| `firnpack restore REF [--to DIR]` | bring a layer back over the worktree |
|
|
89
|
+
| `firnpack export REF --to DIR` | extract a layer to a fresh directory |
|
|
90
|
+
| `firnpack verify` | re-hash every layer; check database consistency |
|
|
91
|
+
| `firnpack compact --keep N [--dry-run] [--yes] [--archive DIR]` | squash old layers, reclaim disk |
|
|
92
|
+
|
|
93
|
+
Every command accepts `--json` for machine-readable output — firnpack is
|
|
94
|
+
designed to be operated by people *and* by agents; see
|
|
95
|
+
[`docs/agent-contract.md`](docs/agent-contract.md) for the normative
|
|
96
|
+
contract.
|
|
97
|
+
|
|
98
|
+
## Branching and merging
|
|
99
|
+
|
|
100
|
+
Branches are names for heads; nothing else. `branch` creates one (staying
|
|
101
|
+
where you are), `switch` moves you — to a branch, or to a pinned layer id
|
|
102
|
+
when you hand it one. `merge` is a plain 3-way merge: clean trees merge into
|
|
103
|
+
one layer that remembers both parents; conflicting trees leave git-style
|
|
104
|
+
markers in the files and a durable "merge in progress" state that `status`
|
|
105
|
+
reports and `merge --abort` clears. A merge never clobbers uncommitted work:
|
|
106
|
+
with drift in the worktree it refuses instead.
|
|
107
|
+
|
|
108
|
+
## Undo, rescue, explore
|
|
109
|
+
|
|
110
|
+
- **`amend`** replaces the head layer — fix a message or a file without
|
|
111
|
+
stacking another version on top. The replaced layer stays on disk until a
|
|
112
|
+
`compact` collects it.
|
|
113
|
+
- **`restore REF`** brings any layer back over the worktree. If you have
|
|
114
|
+
uncommitted work, firnpack **rescues it first** as its own layer and tells
|
|
115
|
+
you — restore never destroys drift.
|
|
116
|
+
- **`export REF --to DIR`** extracts a layer into a fresh directory and
|
|
117
|
+
touches nothing else: the zero-risk way to look at an old version.
|
|
118
|
+
|
|
119
|
+
## Checking the glacier
|
|
120
|
+
|
|
121
|
+
`firnpack verify` re-hashes every layer against its own filename, cross-checks
|
|
122
|
+
the database, and reports dangling references or orphan files. It is the
|
|
123
|
+
answer to "is my history still sound?" after a power cut, a disk scare, or a
|
|
124
|
+
copy between machines. If the database itself is lost or corrupt,
|
|
125
|
+
`verify --rebuild-db` reconstructs it from the layers themselves — and says
|
|
126
|
+
plainly which branch names could not be recovered.
|
|
127
|
+
|
|
128
|
+
## History management: compact
|
|
129
|
+
|
|
130
|
+
Whole-tree layers are the honest price of a glacier you can verify one file
|
|
131
|
+
at a time. When history grows past comfort, `compact --keep N` squashes
|
|
132
|
+
everything below the newest N layers into one foundation layer and reclaims
|
|
133
|
+
the disk.
|
|
134
|
+
|
|
135
|
+
It is **never automatic and never silent**:
|
|
136
|
+
|
|
137
|
+
- `--keep N` is required every time — there is no default to trip over;
|
|
138
|
+
- `--dry-run` prints the full plan (what drops, what rewrites, what gets
|
|
139
|
+
collected, peak disk needed) and changes nothing;
|
|
140
|
+
- real runs require `--yes`;
|
|
141
|
+
- `--archive DIR` exports every layer whose content is about to disappear
|
|
142
|
+
into a zip before anything is deleted;
|
|
143
|
+
- branches that point into the squashed range refuse the compact by name;
|
|
144
|
+
branches that sit on rewritten layers follow the rewrite, and the plan and
|
|
145
|
+
the output say exactly which branch moved where.
|
|
146
|
+
|
|
147
|
+
A reminder line after `commit` tells you when the glacier crosses 50 layers.
|
|
148
|
+
Nothing is ever compacted behind your back.
|
|
149
|
+
|
|
150
|
+
## Ignoring junk
|
|
151
|
+
|
|
152
|
+
firnpack reads the standard `.gitignore` file at your project root, so the
|
|
153
|
+
ignore rules you already have keep working. If you have none, `init` seeds
|
|
154
|
+
one line for you: `~$*` — the hidden lock files office tools leave behind.
|
|
155
|
+
Excluded files are never silently hidden: `status` counts them, and
|
|
156
|
+
`status --ignored` lists every one.
|
|
157
|
+
|
|
158
|
+
## Safety, in plain words
|
|
159
|
+
|
|
160
|
+
- A commit either happens completely or not at all. A power cut mid-pack
|
|
161
|
+
leaves your repository exactly as it was.
|
|
162
|
+
- Every layer carries its own checksum, and the layer's *name* is that
|
|
163
|
+
checksum — tampering or disk rot is detectable by re-reading.
|
|
164
|
+
- Extraction of a layer is guarded: a corrupt or hostile archive can never
|
|
165
|
+
write outside your project folder, no matter what paths it claims to
|
|
166
|
+
contain.
|
|
167
|
+
- No command destroys uncommitted work: merge, switch and restore all refuse
|
|
168
|
+
on drift, or rescue it first and say so.
|
|
169
|
+
- History rewrites (compact) require explicit permission, report everything
|
|
170
|
+
they move, and archive what they delete.
|
|
171
|
+
|
|
172
|
+
## Documentation
|
|
173
|
+
|
|
174
|
+
- [`docs/usage.md`](docs/usage.md) — every command: examples, exit codes,
|
|
175
|
+
JSON shapes
|
|
176
|
+
- [`docs/agent-contract.md`](docs/agent-contract.md) — the normative contract
|
|
177
|
+
for automated operators
|
|
178
|
+
- [`docs/storage.md`](docs/storage.md) — layer format, database schema,
|
|
179
|
+
recovery playbook
|
|
180
|
+
- [`docs/decisions.md`](docs/decisions.md) — the decision log
|
|
181
|
+
- [`SPEC.md`](SPEC.md) — the full specification; [`research/`](research/) —
|
|
182
|
+
the research behind it
|
|
183
|
+
|
|
184
|
+
## Status
|
|
185
|
+
|
|
186
|
+
v1.0 candidate: the full command surface above is implemented and tested
|
|
187
|
+
(unit, property and end-to-end CLI suites, CI across Linux, Windows and
|
|
188
|
+
macOS). PyPI publication follows the v1.0.0 tag.
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
firnpack.py
|
|
4
|
+
pyproject.toml
|
|
5
|
+
firnpack.egg-info/PKG-INFO
|
|
6
|
+
firnpack.egg-info/SOURCES.txt
|
|
7
|
+
firnpack.egg-info/dependency_links.txt
|
|
8
|
+
firnpack.egg-info/entry_points.txt
|
|
9
|
+
firnpack.egg-info/requires.txt
|
|
10
|
+
firnpack.egg-info/top_level.txt
|
|
11
|
+
tests/test_docs.py
|
|
12
|
+
tests/test_e2e.py
|
|
13
|
+
tests/test_fuzz_compact.py
|
|
14
|
+
tests/test_guards.py
|
|
15
|
+
tests/test_m1.py
|
|
16
|
+
tests/test_m2_branch.py
|
|
17
|
+
tests/test_m2_merge.py
|
|
18
|
+
tests/test_m2_safety.py
|
|
19
|
+
tests/test_regressions.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
firnpack
|