sgit-ai 0.26.8
┌─ sgit-ai 0.26.8 ── dev build ── 28 Oct 2026, 13:34 UTC ─────────────────────────
│ tag v0.26.8 = bc904c1b (dev) · PyPI package == tag · a plain install gets this
│ new history blame · grep · history log -S/-G · renames in show/diff/log · log --follow
│ change history log --json: a rename is in files_renamed, not files_added + files_deleted
│ tests 5,436 unit+security ✓ · 18/18 end-to-end ✓ · 0.20.0–0.26.0 vaults open ✓
│ vs git blame 25/25 · renames 90/90 · grep 120/120 · log -S/-G 40/40 (git --no-renames)
│ open BR-1 (high, every build) · BR-5 (low) · BR-6, BR-7 (differences) · R3-1
│ maturity ########-- good: use it; read BR-5 if a script tests grep's exit code
└──────────────────────────────────────────────────────────────────────────────────
$ pip install sgit-ai==0.26.8 # or: sgit update, which takes the newest build
$ sgit version
sgit-ai v0.26.8
What is new
This build is design 04 of the Git-parity roadmap: reading history the way Git does. It adds no vault format and no gate.
Blame
$ sgit history blame -L 10,+5 src/app.py # who last changed each line
$ sgit history blame HEAD~3 docs/guide.md # as of a revision
$ sgit history blame --porcelain notes.md # Git's machine format
$ sgit history blame --json notes.md # history_blame_v1
- Each line is attributed to the commit that last changed it, through renames and merges.
- Options as in Git:
-L N,M,N,+K,N,-K,/regex/,+K(repeatable),-w,--first-parent,-s,--abbrev,--porcelain,--line-porcelain. Also--json,-e(the signing key id in place of the branch name) and--text(a binary file, line by line). - The author is the commit's branch name, dates are UTC, and a boundary (the root, a shallow clone's edge) is marked
^.
Search
$ sgit grep -n -i todo # the working copy (an uncommitted edit is what is searched)
$ sgit grep -n todo HEAD~5 -- '*.py' # a revision, decrypted in memory: HEAD~5:path:line:text
$ sgit history log -S needle --oneline # commits where the count of "needle" changes
$ sgit history log -G 'def \w+' --file src/ -n 5 # commits whose diff adds or removes a matching line
sgit greptakes Git's options (-i -w -v -n -c -l -L -o -q -h -H -I -a -F -e -A -B -C -m) and exit codes: 0 a match, 1 none, 2 an error.grepis a new top-level command;sgit file grepis the same.- The pattern is a Python regular expression (see BR-6); it goes through the same guard as
--grep: at most 1,000 characters, valid, and no repetition of a repetition. - A path from the vault is quoted as Git quotes it (
"jos\303\251.md"), so a file name cannot forge an output line. log -Sand-Gcombine with--file,--grep,--since,--until,--author,-n,--onelineand--stat;--pickaxe-regexand-ias in Git. They stop at the-n-th match.
Renames, found as Git finds them
history show,history diffandhistory log(--files,--patch,--json) reportR096 docs/guide.md -> manual/guide-v2.md, and in a patch Git'ssimilarity index,rename from,rename toheader. On by default.- The detection is a port of Git's: exact, then same basename, then a similarity matrix.
- Options as in Git:
-M[<n>](-M90,-M90%,-M0.9),--no-renames,-l/--rename-limit. sgit history log --file <path> --followcontinues past a rename.
Faster, and a patch that is git diff's
history show,history diffandhistory log --patchopen only the folders whose ids differ and decrypt only the changed files. The CLI team measured a 3,000-file vault with one change at 439 ms and 6,000 decrypted blobs before, and 7 ms and 2 after.- The patch text is
git diff's, byte for byte, including\ No newline at end of file. The old output glued the last line of a file with no final newline to the next+line. - A re-encrypted file whose bytes did not change is no longer listed as modified.
Changes for scripts and agents
| What | Before | Now |
|---|---|---|
history log --json, history show --json, history diff --json | a renamed file is in files_added and files_deleted | it is in files_renamed (old_path, new_path, score, exact, lines_added, lines_removed); --no-renames restores the old lists |
| patch text | the old format | git diff's |
history log --stat | a rename shown as R old -> new | the same, preferring a same-basename source, as Git does |
- A script that reads
files_addedto find new files now misses a renamed file. That is correct, and it will be noticed only if the script is run on a history that has a rename.
Compatibility
- No change to the vault format, and no new gate. Everything here reads; nothing writes a new kind of object.
- Checked on old vaults made by 0.20.0, 0.22.0, 0.24.0 and 0.26.0 (pack mode too, from 0.24.0), each opened by 0.26.8:
- the old working copy: status, fsck, a commit and a push;
- a fresh clone: fsck clean, every commit's signature verified, the same tree;
- the old client pulls what 0.26.8 pushed, and its fsck is clean.
- Blame (through a rename), grep and
-Sgave the right answers on the vaults of 0.20.0 and 0.24.0 (pack mode).
Review by cli.build
Verdict: good.
- The new commands matched Git, used as the oracle, on every case I built, and where they differ the difference is a documented design choice (BR-6, BR-7).
- The hostile inputs I tried were refused or quoted: a pattern that backtracks without end, a quote or non-ASCII characters in a name (a newline in a name is rewritten to
_by the vault, andsgit lsprints it raw, see below), a path outside the vault, a binary file. - One finding changes a script's exit code (BR-5). One high finding is not this build's own (BR-1).
┌─ checks on the PyPI package 0.26.8 ───────────────────────────────────────────────
│ ✓ package == tag v0.26.8 (only the version file differs); wheel == sdist
│ ✓ 5,436 unit+security tests pass (38 skipped); 4 serve tests fail only when two suites run at once
│ ✓ everything from 0.26.1–0.26.7 (G1–G2, M1–M5, L1–L2, X1–X4, C1–C5) still holds
│ ✓ H1 blame, 25 files over random histories with edits, moves, a rename: every line's commit equals git blame --porcelain
│ ✓ H2 grep: 104 working-copy runs (13 option sets) and 16 revision runs: output and exit code equal git grep (-E syntax)
│ ✓ H3 log -S / -G / -i -S / --pickaxe-regex: 40 runs: the commits selected equal git log --no-renames
│ ✓ H4 rename detection -M50: 90 consecutive pairs: scores and paths equal git diff -M50 --name-status
│ ✓ H5 hostile input: nested repetition, 1,000 and 1,001 characters, a quote and non-ASCII in a name, ../ pathspec, a binary file
│ ✓ H6 vaults of 0.20.0 · 0.22.0 · 0.24.0 · 0.26.0, plain and pack: open, commit, push; old client pulls
│ ✗ P1 one push of 150 commits: fresh clone's fsck fails ── BR-1, unchanged from 0.26.6
│ ✗ R3-1 not re-run; no change in the code it concerns
└──────────────────────────────────────────────────────────────────────────────────────
BR-5 (low): sgit grep with a pattern of 1,001 characters or more exits 1 with a traceback message
What happens.
history log --greprefuses a long pattern cleanly:error: --grep pattern is 1001 characters; the limit is 1000.sgit grepdoes not reach that check. The pattern becomes aSafe_Str__Grep_Patternfirst, and its own length limit fails witherror: TypeError in "grep" — … value exceeds maximum length of 1000 characters (was 1001).- The exit code is 1, which for
grepmeans "no match", not "error" (2).
Why it matters: a script that treats 1 as "nothing found" will treat an oversized pattern as a clean result. 1,000 characters is a long pattern, so this is rare.
For the CLI team's agent: check the length (and run the guard) before the pattern is turned into the safe type, or catch the type error in CLI__Grep and exit 2 with the guard's message. The repro: sgit grep -c "$(printf 'y%.0s' $(seq 1 1001))"; echo $?.
BR-6 (difference from Git, to document): the pattern is a Python expression, and Git's default is a basic one
git grepreads a pattern as a basic regular expression:a\|bis an alternation anda|bis literal.sgit grepreads it likegit grep -E:a|bis an alternation anda\|bis literal.- A Git user's
sgit grep 'alpha\|gamma'finds nothing and exits 1.sgit grep -E 'alpha|gamma'andsgit grep 'alpha|gamma'behave as in Git with-E. - The CHANGELOG says
-Eand-Pare accepted and the pattern is a Python regular expression; it does not say that the default differs from Git. Say it in the release notes, and in sgit and Git. - In my differential the 104 working-copy runs used patterns that mean the same in both dialects; the one run that did not (
ne+dle) is where this showed.
BR-7 (difference from Git, to document): log -S and -G do not follow renames
- Git's
log -Sruns after rename detection, whichgit logturns on by default. A commit that only renames (or renames and edits) a file holding the string does not change its count, so it is not listed. sgit history log -Slists it: a rename counts as a delete and an add. Its answers equalgit log --no-renames -Son all 40 runs, and differ from default Git, which has rename detection on, in 16 of the 40 runs.- This is a choice, not a bug, and the CHANGELOG's "exactly git log's" is true of
--no-renames. Rename detection is on by default forshow,diffandlog, so a reader will expect-Sto follow it. Either follow renames in the pickaxe, or say so.
Also noted
history blameon a 200,000-line file took 24 s (two commits, 3.7 MB, one edit).grep -cover the same vault took 1.4 s,log -S0.9 s,history show HEAD0.8 s. The CLI team's own figures (0.44 s on a vault of 67,742 files) use small files. That is one point; I did not measure how it grows.sgit lsprints a path with a newline in it raw, so the name can forge a line. It does in 0.26.6 too. The newVault__Path_Quoteis used by grep and not byls; one call would fix it.blame -L 0,1says "file has only 3 lines" when the range, not the file, is wrong. A CR in a line is shown as?.history blame HEAD~9on a vault with fewer commits says "has 0 parent(s), not 1", which does not say the revision is out of range.- The serve tests share ports, so two suites at once fail four of them; run alone they pass (9 of 9).
- Not tested here:
-pand the interactive paths, a merge-heavy history forblame --first-parent, and a vault on a real host (everything ran on the in-memory host).
What changed since 0.26.6, for the release notes of 0.27.0
A list the CLI team can paste into the release notes, from this and the three builds before it:
sgit commit -m "msg" y:yis a path now; it was ignored (0.26.6).history log/show/diff --json: renames are infiles_renamed(0.26.8).- Patch text is
git diff's (0.26.8). - Rename detection is on by default in
show,diffandlog(0.26.8);--no-renamesturns it off. sgit greppatterns read likegit grep -E, not Git's default (0.26.8).sgit pushpushes with uncommitted changes (0.26.6).sgit updatetakes the newest build;--stabletakes the newest release (0.26.3).- User signatures can be checked, and none can be made yet (0.26.7).
Scripts: diff_history.py (NORENAMES=1 for the -S comparison), legacy_vault.py, push_gap.py, check_build.py. Results: results/check_0.26.8.json, results/suite-v0.26.8.txt, results/diff_history_seeds_1-8_*.txt, results/legacy_vaults_0.20.0_to_0.26.0.txt. All builds: the builds page.
This page as markdown · its source, admin/content/builds/0-26-8.md