cli.sgit.ai / Builds / sgit-ai 0.26.6: commit some paths, commit some hunks, push with uncommitted changes
sgit-ai 0.26.6
┌─ sgit-ai 0.26.6 ── dev build ── 28 Oct 2026, 11:47 UTC ─────────────────────────
│ tag v0.26.6 = 036e33b5 (dev) · PyPI package == tag · a plain install gets this
│ new commit <paths> · commit -p · --list-hunks / --hunk · push when dirty
│ tests 5,144 unit+security ✓ · 18/18 end-to-end ✓ · 100 Git differentials ✓
│ open BR-1 (high, every build) · BR-2, BR-3 (0.26.5) · R3-1
│ maturity #######--- good: use it; push at most 100 commits at a time
└──────────────────────────────────────────────────────────────────────────────────
$ pip install sgit-ai==0.26.6 # or: sgit update, which takes the newest build
$ sgit version
sgit-ai v0.26.6
What is new
This build is Git-parity item 2.3, from design 03. With 0.26.5's line merge, it closes the two gaps that mattered most in daily use.
Commit only some paths
$ sgit commit -m "fix the intro" docs/intro.md src/ # only these; everything else stays uncommitted
$ sgit commit "fix the intro" -- docs/intro.md # the same, message first
$ sgit commit -m "docs" "*.md" # * crosses / as in Git
$ sgit commit -m "rm" old.txt --json # a deleted file, named, is committed as a deletion
- What it commits: HEAD's tree with only the named paths taken from the working copy, as
git commit <pathspec>does. The output lists what stays uncommitted. - Folders, files and patterns:
- a folder takes every change under it;
- a path is relative to the current folder;
- unlike Git, naming a new file commits it, since sgit has no
add. - Refused, with nothing written:
- a pattern that matches nothing refuses the commit;
- a path commit during a merge is refused, as in Git.
--jsonprints one object:committed(withpathsandleft_uncommitted),nothing_to_commit, orrefused.
Commit some hunks of a file
$ sgit commit -p # interactive: y n a d s j k q ? (s splits a hunk)
$ sgit commit --list-hunks --json notes.md # agents: every hunk with an id
$ sgit commit -m "first fix" --hunk 3f9c… --hunk 81ab…
- Ids come from the hunk's content and base, not its position. An id listed before the file or HEAD changed is refused (exit 2) and nothing is written.
- The working copy is never written. The hunks not committed show as uncommitted changes.
- The same hunks as Git: headers match
git diff -U3, and any subset gives the bytesgit apply --cachedgives. - Committed whole, as one hunk: binary files, new files, deleted files and files over 8 MiB.
sgit push with uncommitted changes
- It pushes your commits and says how many changes stay local. It used to refuse, or offer to commit them.
- The implicit pull still protects the working copy:
- an edit that the incoming commits do not touch is kept;
- one they would change refuses the push before anything is written.
A behaviour change for scripts
sgit commit "fix typo"still commits everything.- With
-m, other words are now paths, as in Git.sgit commit -m x yused to ignorey; now it fails ifyis not a file. - Two words without
-mis a usage error that shows both correct forms.
Compatibility
- Nothing changes in the vault format, and there is no new gate. A commit made with paths or hunks is an ordinary commit.
- Checked: 0.26.0 and 0.26.3 clone, pull and push against a vault where 0.26.6 committed by path and by hunk. Both read the same trees, and a file left uncommitted by 0.26.6 stays local.
Review by cli.build
Verdict: good.
- Every claim of this build held, including the two checked against Git as the oracle.
- Use it, including for agents.
- One high finding is not this build's own: it is in every build tested, 0.26.0 to 0.26.6 (BR-1).
- Until it is fixed, push at least once every 100 commits.
┌─ checks on the PyPI package 0.26.6 ───────────────────────────────────────────────
│ ✓ C1 commit <paths> vs `git add -A -- <paths>; git commit` 40 random cases, 0 diff
│ ✓ C2 hunk subsets vs `git apply --cached`, headers vs -U3 60 cases, 0 diff
│ ✓ C3 a stale hunk id is refused; HEAD and objects unchanged
│ ✓ C4 push with an uncommitted edit: commits pushed, edit kept
│ ✓ C5 incoming commit would change an edited file: push refused, edit kept
│ ✓ everything from 0.26.1–0.26.5 (G1–G2, M1–M5, L1–L2, X1–X4) still holds
│ ✓ cross-version with 0.26.0 and 0.26.3, both directions
│ ✗ P1 one push of 150 commits: fresh clone's fsck fails ── BR-1, below
│ ✗ R3-1 unchanged: 64 bad offsets after compaction of a 0.25.0 vault
└──────────────────────────────────────────────────────────────────────────────────────
BR-1 (high, every build since at least 0.26.0): a push of more than 100 commits leaves a gap on the host
What happens.
Vault__Sync__Pushcollects the commits to upload withVault__Fetch.fetch_commit_chain(…, limit=100).- Push calls it from two places (
Vault__Sync__Push.py:252and:666) without passing a limit, so a push of 101 or more new commits uploads only the newest 100, with their trees and blobs. - The ref moves to the new head. The older commits exist only in the pushing clone.
- The gap never fills. The next push stops at the remote head.
one push of N commits, then one more push; a fresh clone walks the history and runs fsck
✓ N=100 walks 102 of 102 commits fsck ok
✓ N=101 walks 103 of 103 commits fsck ok (100 new + the later push)
✗ N=102 walks 101 of 104 commits fsck: missing 1
✗ N=151 walks 101 of 153 commits fsck: missing 1 (normal and pack vaults alike)
What it breaks:
- a fresh clone's
check fsck; history logpast the gap;- every merge whose base lies behind the gap. A teammate with their own commits who pulls gets a merge against an empty base: every file both sides touched conflicts, and the pushing side's change is not applied. My first deep-fork test failed this way at 101 commits, before I separated the two causes. 0.26.3's merge-base fix is real (M4 passes when pushes are at most 50 commits).
- If the pushing clone is deleted, those commits are lost.
How likely: an agent that commits often and pushes rarely, a long offline session, or sgit write in a loop.
For the CLI team's agent:
- Drop the cap for push. Walk with the commit graph from 0.26.3 (
Vault__Commit_Graph), stopping at the remote head's ancestors, with no limit. The stop set is capped at 100 too. - Before moving the ref, check that every uploaded commit's parents are either in the upload set or already on the host.
- Give an existing gap a repair path. The pushing clone still holds the commits, so
sgit push(orcheck fsck --repairon that clone) could upload the missing ancestors. - The repro is
push_gap.py:python -I push_gap.py 100 101 150, withPACKS=1for a pack vault.
Also noted
- The
-mchange is right, and Git's rule. Say it in the release notes for 0.27.0: a script that passed extra words after-mnow fails loudly instead of committing everything. - Not tested here:
-pinteractively (it needs a terminal), and hooks (not built).
Scripts: check_build.py, push_gap.py, xstep.py. Results: results/check_0.26.6.json, results/suite-v0.26.6.txt. All builds: the builds page.
This page as markdown · its source, admin/content/builds/0-26-6.md