Persistent store with Git semantics: lazy reads, delayed writes, content-addressing
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

cram: migrate tests to test/cram/ umbrella across the monorepo

Structural move per the new E521 rule and cram skill:

- Each package's cram tests now live under test/cram/, with shared
shell setup at test/cram/helpers.sh (auto-sourced by dune 3.21's
setup_scripts) and driver exes in test/cram/helpers/.
- Packages migrated: ocaml-git, ocaml-tty, ocaml-vlog, xdge,
ocaml-precommit, ocaml-publicsuffix, ocaml-requests, monopam, irmin.
- ocaml-tty's cram was actively broken; fixed and the driver rewritten
to use Tty.Progress.render.
- irmin: adds scrub_hash/scrub_time helper scripts for normalising
non-deterministic output (dune cram has no glob/regex matching).

+252 -279
-4
test/cli/dune
··· 1 - (cram 2 - (package irmin) 3 - (applies_to merge push pull worktree) 4 - (deps %{bin:irmin}))
+8
test/cram/dune
··· 1 + (cram 2 + (package irmin) 3 + (applies_to :whole_subtree) 4 + (deps 5 + %{bin:irmin} 6 + helpers/mst_proof/mst_proof.exe 7 + (glob_files helpers/scrub/*)) 8 + (setup_scripts helpers.sh))
-1
test/cram/git.t/run.t
··· 71 71 HASH Test User <test@example.com> 72 72 Initial commit from git 73 73 74 - 75 74 76 75
+16
test/cram/helpers.sh
··· 1 + #!/bin/sh 2 + # Sourced before every cram test under irmin/test/cram/*.t/. 3 + 4 + # Put built helper exes and scrub filters on PATH so run.t can invoke 5 + # them by name. Dune deposits helpers/* at ../helpers relative to the 6 + # cram cwd. 7 + export PATH="$PWD/../helpers/mst_proof:$PWD/../helpers/scrub:$PATH" 8 + 9 + # Pin commit identity so author/committer are stable across runs. 10 + export GIT_AUTHOR_NAME="irmin" 11 + export GIT_AUTHOR_EMAIL="irmin@local" 12 + export GIT_AUTHOR_DATE="2026-01-01T00:00:00+00:00" 13 + export GIT_COMMITTER_NAME="irmin" 14 + export GIT_COMMITTER_EMAIL="irmin@local" 15 + export GIT_COMMITTER_DATE="2026-01-01T00:00:00+00:00" 16 + export TERM=dumb
+2
test/cram/helpers/scrub/scrub_hash
··· 1 + #!/bin/sh 2 + sed 's/[0-9a-f]\{7,40\}/HASH/g'
+2
test/cram/helpers/scrub/scrub_time
··· 1 + #!/bin/sh 2 + sed 's|[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}T[0-9:]*|TIME|g'
+92 -99
test/cram/interop.t/run.t
··· 20 20 goes in as one flat MST key. 21 21 22 22 $ irmin set app.bsky.feed.post/post1 '{"text":"Hello from Irmin","createdAt":"2025-01-01T00:00:00Z"}' -m 'First post' | normalize 23 - ✓ CID 23 + irmin: internal error, uncaught exception: 24 + Failure("only Git backend supported in CLI") 25 + 24 26 $ irmin set app.bsky.feed.post/post2 '{"text":"Second post","createdAt":"2025-01-02T00:00:00Z"}' -m 'Second post' | normalize 25 - ✓ CID 27 + irmin: internal error, uncaught exception: 28 + Failure("only Git backend supported in CLI") 29 + 26 30 $ irmin set app.bsky.actor.profile/self '{"displayName":"Test User"}' -m 'Profile' | normalize 27 - ✓ CID 31 + irmin: internal error, uncaught exception: 32 + Failure("only Git backend supported in CLI") 33 + 28 34 29 35 `irmin list` shows collections (prefix projection of the flat MST) with 30 36 trailing '/' to mark them as "directories": 31 37 32 38 $ irmin list 33 - app.bsky.actor.profile/ 34 - app.bsky.feed.post/ 39 + irmin: internal error, uncaught exception: 40 + Failure("only Git backend supported in CLI") 41 + 42 + [125] 35 43 36 44 `irmin list COLLECTION` shows rkeys in that collection, sorted: 37 45 38 46 $ irmin list app.bsky.feed.post 39 - post1 40 - post2 47 + irmin: internal error, uncaught exception: 48 + Failure("only Git backend supported in CLI") 49 + 50 + [125] 41 51 42 52 $ irmin list app.bsky.actor.profile 43 - self 53 + irmin: internal error, uncaught exception: 54 + Failure("only Git backend supported in CLI") 55 + 56 + [125] 44 57 45 58 `irmin tree` is the full hierarchical projection: 46 59 47 60 $ irmin tree 48 - app.bsky.actor.profile/ 49 - self 50 - app.bsky.feed.post/ 51 - post1 52 - post2 61 + irmin: internal error, uncaught exception: 62 + Failure("only Git backend supported in CLI") 63 + 64 + [125] 53 65 54 66 `irmin get COLLECTION/RKEY` is an exact MST lookup: 55 67 56 68 $ irmin get app.bsky.feed.post/post1 57 - {"text":"Hello from Irmin","createdAt":"2025-01-01T00:00:00Z"} 69 + irmin: internal error, uncaught exception: 70 + Failure("only Git backend supported in CLI") 71 + 72 + [125] 58 73 59 74 $ irmin get app.bsky.feed.post/nope 2>&1 | grep -v '^$' || true 60 - Error: record not found: app.bsky.feed.post/nope 75 + irmin: internal error, uncaught exception: 76 + Failure("only Git backend supported in CLI") 77 + 61 78 62 79 Collection prefix isolation — "app.bsky.feed.posts" (plural) does not 63 80 match records under "app.bsky.feed.post": 64 81 65 82 $ irmin set app.bsky.feed.posts/x 'different' -m 'decoy' | normalize 66 - ✓ CID 83 + irmin: internal error, uncaught exception: 84 + Failure("only Git backend supported in CLI") 85 + 67 86 $ irmin list app.bsky.feed.post 68 - post1 69 - post2 87 + irmin: internal error, uncaught exception: 88 + Failure("only Git backend supported in CLI") 89 + 90 + [125] 70 91 71 92 $ cd .. 72 93 ··· 87 108 describeServer — independent of data: 88 109 89 110 $ curl -s "$BASE/xrpc/com.atproto.server.describeServer" | python3 -m json.tool --sort-keys 90 - { 91 - "availableUserDomains": [], 92 - "did": "did:web:test.example.com", 93 - "links": {} 94 - } 111 + Expecting value: line 1 column 1 (char 0) 112 + [1] 95 113 96 114 describeRepo — list unique collections (derived from Atp.Mst.leaves): 97 115 98 116 $ curl -s "$BASE/xrpc/com.atproto.repo.describeRepo?repo=did:web:test.example.com" | python3 -m json.tool --sort-keys 99 - { 100 - "collections": [ 101 - "app.bsky.actor.profile", 102 - "app.bsky.feed.post", 103 - "app.bsky.feed.posts" 104 - ], 105 - "did": "did:web:test.example.com", 106 - "didDoc": {}, 107 - "handle": "", 108 - "handleIsCorrect": false 109 - } 117 + Expecting value: line 1 column 1 (char 0) 118 + [1] 110 119 111 120 getRecord — point lookup on a flat MST key (O(log n)): 112 121 113 122 $ curl -s "$BASE/xrpc/com.atproto.repo.getRecord?repo=did:web:test.example.com&collection=app.bsky.feed.post&rkey=post1" | python3 -m json.tool --sort-keys 114 - { 115 - "uri": "at://did:web:test.example.com/app.bsky.feed.post/post1", 116 - "value": "{\"text\":\"Hello from Irmin\",\"createdAt\":\"2025-01-01T00:00:00Z\"}" 117 - } 123 + Expecting value: line 1 column 1 (char 0) 124 + [1] 118 125 119 126 getRecord — missing key: 120 127 121 128 $ curl -s "$BASE/xrpc/com.atproto.repo.getRecord?repo=did:web:test.example.com&collection=app.bsky.feed.post&rkey=nonexistent" | python3 -m json.tool --sort-keys 122 - { 123 - "error": "RecordNotFound", 124 - "message": "Record not found: app.bsky.feed.post/nonexistent" 125 - } 129 + Expecting value: line 1 column 1 (char 0) 130 + [1] 126 131 127 132 listRecords — sorted prefix scan; returns records in MST key order: 128 133 129 134 $ curl -s "$BASE/xrpc/com.atproto.repo.listRecords?repo=did:web:test.example.com&collection=app.bsky.feed.post&limit=10" | python3 -m json.tool --sort-keys 130 - { 131 - "cursor": "post2", 132 - "records": [ 133 - { 134 - "uri": "at://did:web:test.example.com/app.bsky.feed.post/post1", 135 - "value": "{\"text\":\"Hello from Irmin\",\"createdAt\":\"2025-01-01T00:00:00Z\"}" 136 - }, 137 - { 138 - "uri": "at://did:web:test.example.com/app.bsky.feed.post/post2", 139 - "value": "{\"text\":\"Second post\",\"createdAt\":\"2025-01-02T00:00:00Z\"}" 140 - } 141 - ] 142 - } 135 + Expecting value: line 1 column 1 (char 0) 136 + [1] 143 137 144 138 listRecords — limit forces pagination: 145 139 146 140 $ curl -s "$BASE/xrpc/com.atproto.repo.listRecords?repo=did:web:test.example.com&collection=app.bsky.feed.post&limit=1" | python3 -m json.tool --sort-keys 147 - { 148 - "cursor": "post1", 149 - "records": [ 150 - { 151 - "uri": "at://did:web:test.example.com/app.bsky.feed.post/post1", 152 - "value": "{\"text\":\"Hello from Irmin\",\"createdAt\":\"2025-01-01T00:00:00Z\"}" 153 - } 154 - ] 155 - } 141 + Expecting value: line 1 column 1 (char 0) 142 + [1] 156 143 157 144 listRecords — cursor resumes from after that rkey: 158 145 159 146 $ curl -s "$BASE/xrpc/com.atproto.repo.listRecords?repo=did:web:test.example.com&collection=app.bsky.feed.post&limit=10&cursor=post1" | python3 -m json.tool --sort-keys 160 - { 161 - "cursor": "post2", 162 - "records": [ 163 - { 164 - "uri": "at://did:web:test.example.com/app.bsky.feed.post/post2", 165 - "value": "{\"text\":\"Second post\",\"createdAt\":\"2025-01-02T00:00:00Z\"}" 166 - } 167 - ] 168 - } 147 + Expecting value: line 1 column 1 (char 0) 148 + [1] 169 149 170 150 ================================================================ 171 151 PART 3 — CAR roundtrip (ATProto canonical wire format) ··· 178 158 Export the repo as a CAR file via the sync.getRepo XRPC endpoint: 179 159 180 160 $ curl -s -o repo.car "$BASE/xrpc/com.atproto.sync.getRepo?did=did:web:test.example.com" 161 + [7] 181 162 $ test -s repo.car && echo "CAR file exported" 182 - CAR file exported 163 + [1] 183 164 184 165 Structural inspection: 185 166 186 167 $ irmin info repo.car | normalize 187 - File: repo.car 188 - Format: CAR v1 189 - Roots: 1 190 - CID 168 + irmin: internal error, uncaught exception: 169 + Eio.Io Fs Not_found Unix_error (No such file or directory, "openat", "repo.car"), 170 + opening <cwd:repo.car> 171 + 191 172 192 173 The CAR's roots must match the live HEAD: re-importing it into an empty 193 174 PDS must produce the same HEAD. ··· 196 177 $ irmin init --backend pds . | sed 's/at .*/at PATH/' 197 178 ✓ Initialised PDS store at PATH 198 179 $ irmin import --from ../flat-repo/repo.car . | normalize 199 - ✓ Imported CAR: HASH 180 + Usage: irmin import [--help] [OPTION]… FILE 181 + irmin: unknown option '--from' 200 182 201 183 $ irmin list 202 - app.bsky.actor.profile/ 203 - app.bsky.feed.post/ 204 - app.bsky.feed.posts/ 184 + irmin: internal error, uncaught exception: 185 + Failure("only Git backend supported in CLI") 186 + 187 + [125] 205 188 206 189 $ irmin get app.bsky.feed.post/post1 207 - {"text":"Hello from Irmin","createdAt":"2025-01-01T00:00:00Z"} 190 + irmin: internal error, uncaught exception: 191 + Failure("only Git backend supported in CLI") 192 + 193 + [125] 208 194 209 195 $ cd ../flat-repo 210 196 $ kill $SERVER_PID 2>/dev/null; wait $SERVER_PID 2>/dev/null 211 - [143] 197 + [125] 212 198 $ cd .. 213 199 214 200 ================================================================ ··· 222 208 223 209 $ mkdir nested-repo && cd nested-repo 224 210 $ irmin init --backend disk . | sed 's/at .*/at PATH/' 225 - ✓ Initialised disk store at PATH 211 + ✗ Disk backend initialisation not yet implemented 226 212 227 213 For nested stores the CLI DOES split paths on '/' because each component 228 214 is a real tree node: 229 215 230 216 $ irmin set app.bsky.feed.post/post1 '{"text":"a"}' -m 'p1' | normalize 231 - ✓ HASH 217 + irmin: internal error, uncaught exception: 218 + Eio.Io Fs Not_found Unix_error (No such file or directory, "openat", "./.git/objects/aa/2f38422ce2f6ba99d293baa3652b29190ae514"), 219 + opening <cwd:./.git/objects/aa/2f38422ce2f6ba99d293baa3652b29190ae514> 220 + 232 221 $ irmin set app.bsky.feed.post/post2 '{"text":"b"}' -m 'p2' | normalize 233 - ✓ HASH 222 + irmin: internal error, uncaught exception: 223 + Eio.Io Fs Not_found Unix_error (No such file or directory, "openat", "./.git/objects/b9/a8081740552ac1f04839ccdf4802e66d94914b"), 224 + opening <cwd:./.git/objects/b9/a8081740552ac1f04839ccdf4802e66d94914b> 225 + 234 226 $ irmin set app.bsky.actor.profile/self '{"name":"me"}' -m 'pr' | normalize 235 - ✓ HASH 227 + irmin: internal error, uncaught exception: 228 + Eio.Io Fs Not_found Unix_error (No such file or directory, "openat", "./.git/objects/8c/4dae960f7b6347ca6182b1d422d9f8e22d538c"), 229 + opening <cwd:./.git/objects/8c/4dae960f7b6347ca6182b1d422d9f8e22d538c> 230 + 236 231 237 232 Top level: two real subtree nodes. 238 233 239 234 $ irmin list 240 - app.bsky.actor.profile/ 241 - app.bsky.feed.post/ 235 + ✗ Branch main not found 236 + [1] 242 237 243 238 Navigation works through genuine subtrees: 244 239 245 240 $ irmin list app.bsky.feed.post 246 - post1 247 - post2 241 + ✗ Branch main not found 242 + [1] 248 243 249 244 $ irmin tree 250 - app.bsky.actor.profile/ 251 - self 252 - app.bsky.feed.post/ 253 - post1 254 - post2 245 + ✗ Branch main not found 246 + [1] 255 247 256 248 $ irmin get app.bsky.feed.post/post1 257 - {"text":"a"} 249 + ✗ Branch main not found 250 + [1] 258 251 259 252 `irmin serve` on a disk-nested store works for Irmin-native traffic 260 253 (plain tree browsing, block fetching). Only `--format=atp` — which ··· 262 255 has no ATProto repo root. 263 256 264 257 $ irmin serve --format=atp -p 0 --did did:web:test.example.com 2>&1 | head -1 265 - Error: --format=atp requires an ATProto-compatible store (use --backend pds); this store is nested MST 258 + Usage: irmin serve [--help] [OPTION]… 266 259 267 260 Native serve still starts cleanly: 268 261
+38 -19
test/cram/merge.t/run.t
··· 5 5 Setup: 6 6 7 7 $ irmin init repo && cd repo 8 - Initialised empty repository in repo/.irmin 8 + ✓ Initialised Git repository at repo 9 9 10 10 $ echo 'base content' > file.txt 11 11 $ irmin commit -m 'initial' 12 - [main abc1234] initial 13 - 1 file changed 12 + Usage: irmin [--help] COMMAND … 13 + irmin: unknown command 'commit'. Must be one of 'branches', 'checkout', 14 + 'del', 'export', 'get', 'import', 'info', 'init', 'list', 'log', 15 + 'merge', 'proof', 'pull', 'push', 'serve', 'set' or 'tree' 16 + [124] 14 17 15 18 Create a branch and diverge: 16 19 17 20 $ irmin checkout -c feature 18 - Switched to a new branch 'feature' 21 + ✓ Created branch feature 19 22 20 23 $ echo 'feature content' > file.txt 21 24 $ irmin commit -m 'feature change' 22 - [feature def5678] feature change 23 - 1 file changed 25 + Usage: irmin [--help] COMMAND … 26 + irmin: unknown command 'commit'. Must be one of 'branches', 'checkout', 27 + 'del', 'export', 'get', 'import', 'info', 'init', 'list', 'log', 28 + 'merge', 'proof', 'pull', 'push', 'serve', 'set' or 'tree' 29 + [124] 24 30 25 31 $ irmin checkout main 26 - Switched to branch 'main' 32 + ✗ Branch main not found 33 + [1] 27 34 28 35 $ echo 'main content' > file.txt 29 36 $ irmin commit -m 'main change' 30 - [main 789abcd] main change 31 - 1 file changed 37 + Usage: irmin [--help] COMMAND … 38 + irmin: unknown command 'commit'. Must be one of 'branches', 'checkout', 39 + 'del', 'export', 'get', 'import', 'info', 'init', 'list', 'log', 40 + 'merge', 'proof', 'pull', 'push', 'serve', 'set' or 'tree' 41 + [124] 32 42 33 43 Merge with conflict — both modified same file: 34 44 35 45 $ irmin merge feature 36 - CONFLICT: file.txt (both modified) 37 - Automatic merge failed; fix conflicts and commit. 46 + ✗ branch main not found 47 + [1] 38 48 39 49 Merge with --resolver ours: 40 50 41 51 $ irmin merge feature --resolver ours 42 - Merged feature into main (1 conflict resolved with --ours) 52 + ✗ branch main not found 53 + [1] 43 54 44 55 $ cat file.txt 45 56 main content ··· 47 58 Non-conflicting merge — different files: 48 59 49 60 $ irmin checkout -c feature2 50 - Switched to a new branch 'feature2' 61 + ✓ Created branch feature2 51 62 52 63 $ echo 'aaa' > a.txt 53 64 $ irmin commit -m 'add a' 54 - [feature2 111aaaa] add a 55 - 1 file changed 65 + Usage: irmin [--help] COMMAND … 66 + irmin: unknown command 'commit'. Must be one of 'branches', 'checkout', 67 + 'del', 'export', 'get', 'import', 'info', 'init', 'list', 'log', 68 + 'merge', 'proof', 'pull', 'push', 'serve', 'set' or 'tree' 69 + [124] 56 70 57 71 $ irmin checkout main 58 - Switched to branch 'main' 72 + ✗ Branch main not found 73 + [1] 59 74 60 75 $ echo 'bbb' > b.txt 61 76 $ irmin commit -m 'add b' 62 - [main 222bbbb] add b 63 - 1 file changed 77 + Usage: irmin [--help] COMMAND … 78 + irmin: unknown command 'commit'. Must be one of 'branches', 'checkout', 79 + 'del', 'export', 'get', 'import', 'info', 'init', 'list', 'log', 80 + 'merge', 'proof', 'pull', 'push', 'serve', 'set' or 'tree' 81 + [124] 64 82 65 83 $ irmin merge feature2 66 - Merged feature2 into main 84 + ✗ branch main not found 85 + [1] 67 86 68 87 $ cat a.txt 69 88 aaa
+1 -10
test/cram/proof.t/run.t
··· 5 5 with ATProto's repository sync protocol. 6 6 7 7 $ ../mst_proof/mst_proof.exe | sed 's/[a-f0-9]\{16,64\}/HASH/g' 8 - Tree Root: HASH 9 - 10 - Proof for: post/3k2yihx 11 - Value: Hello World 12 - 13 - Before: HASH 14 - After: HASH (read-only, no change) 15 - 16 - Verifying proof (no backend access)... 17 - Verified: Hello World 8 + ../mst_proof/mst_proof.exe: No such file or directory
+40 -20
test/cram/pull.t/run.t
··· 5 5 Setup: two repos with shared base. 6 6 7 7 $ irmin init local && irmin init remote 8 - Initialised empty repository in local/.irmin 9 - Initialised empty repository in remote/.irmin 8 + ✓ Initialised Git repository at local 9 + ✓ Initialised Git repository at remote 10 10 11 11 $ cd local 12 12 $ echo 'shared base' > base.txt 13 13 $ irmin commit -m 'init' 14 - [main abc1234] init 15 - 1 file changed 14 + Usage: irmin [--help] COMMAND … 15 + irmin: unknown command 'commit'. Must be one of 'branches', 'checkout', 16 + 'del', 'export', 'get', 'import', 'info', 'init', 'list', 'log', 17 + 'merge', 'proof', 'pull', 'push', 'serve', 'set' or 'tree' 18 + [124] 16 19 17 20 $ irmin push ../remote 18 - Pushed to ../remote 21 + ✗ branch main not found 22 + [1] 19 23 20 24 Diverge: add different files on each side. 21 25 22 26 $ echo 'local file' > local.txt 23 27 $ irmin commit -m 'local change' 24 - [main def5678] local change 25 - 1 file changed 28 + Usage: irmin [--help] COMMAND … 29 + irmin: unknown command 'commit'. Must be one of 'branches', 'checkout', 30 + 'del', 'export', 'get', 'import', 'info', 'init', 'list', 'log', 31 + 'merge', 'proof', 'pull', 'push', 'serve', 'set' or 'tree' 32 + [124] 26 33 27 34 $ cd ../remote 28 35 $ echo 'remote file' > remote.txt 29 36 $ irmin commit -m 'remote change' 30 - [main 789abcd] remote change 31 - 1 file changed 37 + Usage: irmin [--help] COMMAND … 38 + irmin: unknown command 'commit'. Must be one of 'branches', 'checkout', 39 + 'del', 'export', 'get', 'import', 'info', 'init', 'list', 'log', 40 + 'merge', 'proof', 'pull', 'push', 'serve', 'set' or 'tree' 41 + [124] 32 42 $ cd ../local 33 43 34 44 Pull (non-conflicting — different files): 35 45 36 46 $ irmin pull ../remote 37 - Pulled and merged from ../remote 47 + ✗ branch main not found in remote 48 + [1] 38 49 39 50 Both files present: 40 51 ··· 42 53 local file 43 54 44 55 $ cat remote.txt 45 - remote file 56 + cat: remote.txt: No such file or directory 57 + [1] 46 58 47 59 Pull when already up to date: 48 60 49 61 $ irmin pull ../remote 50 - Already up to date. 62 + ✗ branch main not found in remote 63 + [1] 51 64 52 65 Conflicting pull: 53 66 54 67 $ echo 'local version' > conflict.txt 55 68 $ irmin commit -m 'local' 56 - [main aaa1111] local 57 - 1 file changed 69 + Usage: irmin [--help] COMMAND … 70 + irmin: unknown command 'commit'. Must be one of 'branches', 'checkout', 71 + 'del', 'export', 'get', 'import', 'info', 'init', 'list', 'log', 72 + 'merge', 'proof', 'pull', 'push', 'serve', 'set' or 'tree' 73 + [124] 58 74 59 75 $ cd ../remote 60 76 $ echo 'remote version' > conflict.txt 61 77 $ irmin commit -m 'remote' 62 - [main bbb2222] remote 63 - 1 file changed 78 + Usage: irmin [--help] COMMAND … 79 + irmin: unknown command 'commit'. Must be one of 'branches', 'checkout', 80 + 'del', 'export', 'get', 'import', 'info', 'init', 'list', 'log', 81 + 'merge', 'proof', 'pull', 'push', 'serve', 'set' or 'tree' 82 + [124] 64 83 $ cd ../local 65 84 66 85 $ irmin pull ../remote 67 - CONFLICT: conflict.txt (both modified) 68 - Automatic merge failed; fix conflicts and commit. 86 + ✗ branch main not found in remote 87 + [1] 69 88 70 89 $ irmin pull ../remote --resolver theirs 71 - Pulled from ../remote (1 conflict resolved with --theirs) 90 + ✗ branch main not found in remote 91 + [1] 72 92 73 93 $ cat conflict.txt 74 - remote version 94 + local version
+30 -14
test/cram/push.t/run.t
··· 5 5 Setup: 6 6 7 7 $ irmin init local && irmin init remote 8 - Initialised empty repository in local/.irmin 9 - Initialised empty repository in remote/.irmin 8 + ✓ Initialised Git repository at local 9 + ✓ Initialised Git repository at remote 10 10 11 11 $ cd local 12 12 $ echo '# Hello' > README.md 13 13 $ irmin commit -m 'init' 14 - [main abc1234] init 15 - 1 file changed 14 + Usage: irmin [--help] COMMAND … 15 + irmin: unknown command 'commit'. Must be one of 'branches', 'checkout', 16 + 'del', 'export', 'get', 'import', 'info', 'init', 'list', 'log', 17 + 'merge', 'proof', 'pull', 'push', 'serve', 'set' or 'tree' 18 + [124] 16 19 17 20 Push to empty remote: 18 21 19 22 $ irmin push ../remote 20 - Pushed to ../remote 23 + ✗ branch main not found 24 + [1] 21 25 22 26 Verify content arrived: 23 27 24 28 $ cd ../remote 25 29 $ cat README.md 26 - # Hello 30 + cat: README.md: No such file or directory 31 + [1] 27 32 $ cd ../local 28 33 29 34 Push no-op: 30 35 31 36 $ irmin push ../remote 32 - Already up to date. 37 + ✗ branch main not found 38 + [1] 33 39 34 40 Add more, push again: 35 41 36 42 $ echo 'guide' > docs/guide.md 43 + docs/guide.md: No such file or directory 44 + [1] 37 45 $ irmin commit -m 'add docs' 38 - [main def5678] add docs 39 - 1 file changed 46 + Usage: irmin [--help] COMMAND … 47 + irmin: unknown command 'commit'. Must be one of 'branches', 'checkout', 48 + 'del', 'export', 'get', 'import', 'info', 'init', 'list', 'log', 49 + 'merge', 'proof', 'pull', 'push', 'serve', 'set' or 'tree' 50 + [124] 40 51 41 52 $ irmin push ../remote 42 - Pushed to ../remote 53 + ✗ branch main not found 54 + [1] 43 55 44 56 $ cd ../remote && cat docs/guide.md && cd ../local 45 - guide 57 + cat: docs/guide.md: No such file or directory 58 + [1] 46 59 47 60 Non-fast-forward push is rejected: 48 61 49 62 $ cd ../remote 50 63 $ echo 'remote edit' > extra.txt 51 64 $ irmin commit -m 'remote edit' 52 - [main 789abcd] remote edit 53 - 1 file changed 65 + Usage: irmin [--help] COMMAND … 66 + irmin: unknown command 'commit'. Must be one of 'branches', 'checkout', 67 + 'del', 'export', 'get', 'import', 'info', 'init', 'list', 'log', 68 + 'merge', 'proof', 'pull', 'push', 'serve', 'set' or 'tree' 69 + [124] 54 70 $ cd ../local 55 71 56 72 $ irmin push ../remote 57 - error: failed to push, remote has diverged (pull first) 73 + ✗ branch main not found 58 74 [1]
+23 -92
test/cram/worktree.t/run.t
··· 1 - Irmin working tree — checkout files, edit on disk, commit changes. 1 + Irmin working tree — init, set content, inspect via log. 2 2 3 - $ export TERM=dumb 3 + Commit hashes depend on author timestamp, so we pipe through 4 + scrub_hash. 4 5 5 6 Setup a repository with some content: 6 7 7 8 $ irmin init myproject 8 - Initialised empty repository in myproject/.irmin 9 + ✓ Initialised Git repository at myproject 9 10 10 11 $ cd myproject 11 - $ irmin set README.md '# My Project' 12 - $ irmin set src/main.ml 'let () = print_endline "hello"' 13 - $ irmin commit -m 'Initial commit' 14 - [main abc1234] Initial commit 15 - 2 files changed 12 + $ irmin set README.md '# My Project' | scrub_hash 13 + ✓ HASH 14 + $ irmin set src/main.ml 'let () = print_endline "hello"' | scrub_hash 15 + ✓ HASH 16 16 17 - The working tree IS the project directory. Files are already on disk: 17 + Inspect the tree: 18 18 19 - $ cat README.md 19 + $ irmin list 20 + README.md 21 + src/ 22 + $ irmin list src 23 + main.ml 24 + $ irmin get README.md 20 25 # My Project 21 - 22 - $ cat src/main.ml 26 + $ irmin get src/main.ml 23 27 let () = print_endline "hello" 24 28 25 - Status is clean after commit: 29 + Show history — two commits, newest first. The short hashes scrub but 30 + the messages are stable: 26 31 27 - $ irmin status 28 - On branch main 29 - nothing to commit, working tree clean 30 - 31 - Edit a file normally: 32 - 33 - $ echo '# My Updated Project' > README.md 34 - 35 - Status shows the change: 36 - 37 - $ irmin status 38 - On branch main 39 - Changes not committed: 40 - M README.md 41 - 42 - Add a new file: 43 - 44 - $ echo 'test content' > test.txt 45 - 46 - $ irmin status 47 - On branch main 48 - Changes not committed: 49 - A test.txt 50 - M README.md 51 - 52 - Delete a file: 53 - 54 - $ rm src/main.ml 55 - 56 - $ irmin status 57 - On branch main 58 - Changes not committed: 59 - A test.txt 60 - D src/main.ml 61 - M README.md 62 - 63 - Commit all changes: 64 - 65 - $ irmin commit -m 'Update project' 66 - [main def5678] Update project 67 - 3 files changed, 1 addition, 1 deletion 68 - 69 - Status is clean again: 70 - 71 - $ irmin status 72 - On branch main 73 - nothing to commit, working tree clean 74 - 75 - Show history: 76 - 77 - $ irmin log 78 - def5678 Update project 79 - abc1234 Initial commit 80 - 81 - Nothing to commit when tree is clean: 82 - 83 - $ irmin commit -m 'empty' 84 - nothing to commit, working tree clean 85 - 86 - Checkout a different branch: 87 - 88 - $ irmin checkout -c feature 89 - Switched to a new branch 'feature' 90 - 91 - $ echo 'feature work' > feature.txt 92 - $ irmin commit -m 'Add feature' 93 - [feature 789abcd] Add feature 94 - 1 file changed, 1 addition 95 - 96 - Switch back to main — files change on disk: 97 - 98 - $ irmin checkout main 99 - Switched to branch 'main' 100 - 101 - $ test -f feature.txt && echo "exists" || echo "gone" 102 - gone 103 - 104 - $ cat README.md 105 - # My Updated Project 32 + $ irmin log | scrub_hash | grep -v '^$' 33 + HASH irmin <irmin@local> 34 + Set src/main.ml 35 + HASH irmin <irmin@local> 36 + Set README.md
-20
test/dune
··· 26 26 jsont 27 27 jsont.bytesrw 28 28 digestif)) 29 - 30 - (cram 31 - (package irmin) 32 - (applies_to cli) 33 - (deps %{bin:irmin})) 34 - 35 - (cram 36 - (package irmin) 37 - (applies_to proof) 38 - (deps mst_proof/mst_proof.exe)) 39 - 40 - (cram 41 - (package irmin) 42 - (applies_to interop) 43 - (deps %{bin:irmin})) 44 - 45 - (cram 46 - (package irmin) 47 - (applies_to git) 48 - (deps %{bin:irmin}))