Repository navigation
Releases: tkittich/git-remote-seafile
Release list
v0.9.7 - Hardened Upload Retries, Recycled PID Reclaim, and Unicode CLI Polish
What's Changed in v0.9.7
This release hardens upload reliability, fast-reclaims stale locks from recycled local PIDs, consolidates SQLite schema reading, and polishes Unicode path presentation in snapshot vaults. Nothing in how a snapshot or a push is stored changes.
Fixed
- Upload retries in
client.pyrefresh the upload link. On transient connection failures and 5xx errors, rewindable uploads obtain a fresh upload link before retrying, preventing immediateHTTP 400 Bad Requestfailures on Seafile fileservers that consume tokens on interrupted connections. seafile_doctor.py errorsprinted nothing with default--days 0. The summary and error-group formatting logic was indented insideif args.days:, producing blank output when run with the default CLI setting.- Stale remote locks from recycled local PIDs are fast-reclaimed. A local lock ticket records process creation timestamps on Windows and Linux;
_is_pid_aliveverifies that an active PID matches the ticket's start time, reclaiming abandoned locks immediately even if the operating system reused the PID. - Doctor SQLite reads survive concurrent client syncing. Reading
repo.dbretries up to 3 times on torn WAL snapshots. - Snapshot tool outputs clean UTF-8 paths on Windows. Staging repositories and restores pin
core.quotepath = false, preventing multi-byte UTF-8 filenames (e.g., Thai characters) from being octal-escaped in Git command outputs.
Changed
- Consolidated
repo.dbSQLite schema reading (T-4). Factoredread_repo_propertiesintosqlite_read.pywith shared retry logic for torn live WAL databases, eliminating schema drift acrosssafety.pyandtools/seafile_doctor.py. - HTTP connection pool scaled. Configured HTTP client session with
pool_connections=64andpool_maxsize=64to prevent pool exhaustion warnings during parallel payload transfers. - Remediation hints on HTTP 401/403. When resolving repository IDs fails due to authentication or permission errors, client outputs an actionable suggestion pointing to
git-remote-seafile check-authand desktop client login status. - Diff stat truncation. Passed
--stat-count=35in snapshot diff summaries to prevent large file listings from flooding console logs. - Standardized notification prefixes. Unified user-facing notifications across snapshot tools and CLI with title-case prefixes (
Warning:,Note:,Notice:,Hint:). - Hardened lock test fixture hostname. Defaulted
_ticketmachine name in test fixtures toforeign-<hostname>so CI hosts namednodeAcannot accidentally flip foreign-ticket assertions.
The full list is in CHANGELOG.md.
Full Changelog: v0.9.6...v0.9.7
v0.9.6 - Long Paths No Longer Break a Push on Windows
What's Changed in v0.9.6
A push of a repository holding long non-ASCII paths failed on Windows with
git pack-objects failed: fatal: expected object ID, got garbage. Found on a real vault whose Thai
file names run to 400 bytes. Nothing about what is stored changes.
Fixed
- A long path broke the push.
pack-objects --stdinon git for Windows reads each<id> <path>
line into a 302-byte buffer and takes the overflow for the next line. The path is only a hint for
delta compression, so a line over 300 bytes is now sent as the bare object id; the pack holds the
same objects. - A path containing U+0085, U+2028 or U+2029 was cut in two by the object listing
(str.splitlines()splits on them). It is now split on\nonly.
Not changed
- What is stored, the pack layout and every setting.
The full list is in CHANGELOG.md.
Full Changelog: v0.9.5...v0.9.6
v0.9.5 - A Server Under a Path, a Torn Database Said Once, and a Test Runner That Does Not Hang
What's Changed in v0.9.5
A Seafile served under a path (https://corp.example.com/seafile/) now works. That was the one thing a
seafile:// URL could not express, and the one open item that blocked a whole class of users. The path
belongs to the server, so it is set with SEAFILE_SERVER, the credentials file, or the desktop client's
account; the no-code alternative (give the server a host name of its own at the proxy) is documented
beside it. Checked against a real Seafile server (CE 13.0.28, scratch library) through a local proxy that
serves it only under /seafile: push, clone and lock-status, 75 requests, none outside the prefix. The
suite is 1101 tests, also run on Linux (Debian under WSL).
Added
- A server under a path via
SEAFILE_SERVER, the credentials file'sserver, or the desktop
client's account URL. API calls and relative transfer links go under it; the token stays scoped to
host and port, never to the path.desktop-urlprints the short form and names the setting for such a
server. USER_GUIDE 7.1, with the proxy alternative.
Fixed
- A synced-folder check that could not read the desktop client's
repo.dbsaid nothing. A copy
torn by a concurrent write read as "no libraries", so the guard did not run. The read is retried and,
if the file is there and still unreadable, one warning names it. - A restore that failed after the clone left a partial tree and the retry stopped with
"destination is not empty". The failure now says where it was left and that it must be deleted first.
Changed (development)
- The parallel test runner kills a target after
--timeout(default 1800 s) and names it, stops
every worker's process tree on Ctrl-C, closes a log whose spawn failed, and cannot turn a finished
run into a traceback because Windows held a temporary directory. - Six suite gaps closed: credential branches,
cmd_fetchfed what git sends, the "empty" pack
outcome, a deterministic concurrency assertion, a gc test that checks the mirror it computed, and a
stale cross-reference.
Not changed
- A
seafile://URL still cannot carry a context path: the segment after the host is the library.
The full list is in CHANGELOG.md.
Full Changelog: v0.9.4...v0.9.5
v0.9.4 - Read It Back: `gc` Proves the New Pack by Content, and a Snapshot Checks Its Payload Objects After the Push
What's Changed in v0.9.4
The remaining items of the 0.9.3 review round, all of them about trusting what a server says. Checked
on the stub, on Linux (Debian under WSL), and against a real Seafile server (CE 13.0.28, scratch
library): the opt-in real-server tier, a payload snapshot with restore and verify, and a full gc
followed by a clone and fsck. The suite is 1079 tests.
Changed
gcand the tail merge read the new pack back and compare SHA-256 before deleting anything. The
listing only proves a size, and a same-length substitution (a padded partial write, a damaged
block) passed it and led to the originals being deleted. The check costs one more download of the
new pack under the lock already held, about 0.015 s per MiB.- A snapshot run lists its payload objects once more after the push. A
--payload-deleteon
another machine can remove an object between the pre-push check and the ref landing. The object is
uploaded again when the source file is as it was; otherwise the run exits 1 naming it. - A ref deletion is read back. A server that answers 200 without deleting is an error now, not
ok.
Tests
- The unreadable-directory guard and the forced English git messages are covered on Windows too (the
earlier test needed POSIX permissions and was skipped there).
Not changed, on purpose
- Bounding the server's
Dateoffset was rejected: a client clock that is wrong is exactly what the
offset exists to absorb.
The full list is in CHANGELOG.md.
Full Changelog: v0.9.3...v0.9.4
v0.9.3 - The Third Review Round: `gc` Re-reads Refs Before Deleting, the Windows Hash Cache Sees In-Place Edits, and a Release Pre-flight
What's Changed in v0.9.3
Three independent reviews (GLM-5.3, Gemini, DeepSeek) went over 0.9.2. Every finding was reproduced
or refuted before anything changed; the fixes carry tests that fail without them. The suite is 1074
tests, also run on Linux (Debian under WSL), and the directory-listing question was measured against a
real Seafile server (CE 13.0.28, scratch library).
The two that mattered
- Full
gcdeleted the old packs without re-reading the refs. A writer that ignored the lock and
pushed whilegcwas repacking had its pack deleted and its ref left naming nothing. The refs are
read again after the upload is verified; a change deletes nothing and says so. - On Windows the payload hash cache could vouch for a changed file.
st_ctimeis the creation
time there and never moves, so a same-size in-place edit with the modification time put back was
trusted and the old hash recorded. The key now holds NTFS's change time. The first run on Windows
hashes every large payload file once more.
Fixed
- A reaped-but-present expired lock ticket was rewritten fresh by
renew(), putting a stalled holder
back ahead of the peer that took over. It isLEASE_LOSTwhen a live peer is behind it. get_file_bytesenforced its cap only against a declaredContent-Length; a chunked body was
buffered whole. It is bounded as it is read.- A remote ref file whose content was an option (
--stdin) reachedgit merge-base's argv. - Deleting a nested ref could remove a sibling created a moment earlier (the empty-parent prune).
--payload-gc --payload-deletere-reads the refs every 25 deletions or 10 s and stops on a change.- A file name that is not UTF-8 aborted an
--excluderun; a name with a newline or a leading"
made restore verification refuse; a vault lock that could not be written leaked its descriptor and
left an empty lock file;--code-remotewith--no-pushwas silently ignored. - Git's English messages are now forced (
LC_MESSAGES=C) where the tool reads them. - A failed branch-delete rollback was reported as "(deleted)".
- Plain
unlockdeleted a live local holder's ticket; it now refuses.
Decided
- A lock-ignoring writer's lost last-writer-wins race is not rescued by a
gcgrace period; it
stays documented. Every writer must use the helper. - The default conversion of an existing vault to payload mode is announced, and now repeated as the
last line of the run on stdout and stderr.
Added and removed
tools/release_check.pyanswers before the tag exists what the release job answers after it.- The warning for the retired
seafile.locksettle/seafile.gcmaxmibkeys is gone. - Documents named tests that did not exist; a guard now fails on any
test_...a document names that
is not defined. DESIGN and USER_GUIDE now say what the code does about the ref read-back, the
size-only pack proof, a token over plain HTTP, Trap 2's direction and LFS storage.
The full list is in CHANGELOG.md.
Full Changelog: v0.9.2...v0.9.3
v0.9.2 - Times Survive a Restore, `--force-with-lease` Works, and a Directory the Tool Cannot Read No Longer Vanishes From the Snapshot
What's Changed in v0.9.2
Two more rounds of independent review went over the snapshot tool: Opus twice on the code (the second
time on code written for this release), and GLM-5.3 and Gemini on the specification. Every finding that
reproduced is fixed with a test that fails without the fix. The suite is 1049 tests; the tool and helper
changes were also run on Linux (Debian under WSL) and against a real Seafile server (CE 13.0.28) in a
scratch library.
The one that mattered most
- A directory the tool could not read was silently missing from the snapshot.
git addprints a
warning for a directory it cannot open and exits 0, so everything under it was absent and the run
reported success. The run now stops and names the directory;--excludeleaves it out on purpose.
Added
- Modification times of git-stored files are restored. A snapshot that pushes to a
seafile://remote
records them in.seafile-payload/mtimes.jsonand--restoreputs them back (payload files already
carried theirs).--record-mtimes/--no-record-mtimes; an explicit choice is remembered; a run with
nothing typed follows the last snapshot. Touching a file without changing it still makes no new snapshot. git push --force-with-leaseis supported: a push may rewrite history over exactly the value you
saw, and is refused asstale infoif the remote moved. Updates, deletions and--dry-run.- A fetch no longer advertises half a repository. A refs directory that its parent listed and that then
answers 404 is retried once and then an error. - macOS network mounts are recognised by the clock probe.
Fixed in the snapshot tool
- A restore overwrote and then removed a real git file named
<payload file>.part, and certified the tree. - A manifest path through a link to
.git(tool/hooks/pre-commit) passed the "inside the destination"
check; a hostile manifest could fill the disk before its sizes were compared. - A share whose clock ran 2 to 5 s behind kept a stale hash (the noise floor was wider than the racy
window); a share that could not be probed fell back to the machine's clock; a same-size edit that put the
old mtime back was trusted. The cache key now holds the device and the inode change time, unprobed
shares and other devices get a one-hour margin, and whole-second mtimes are trusted (twice the margin)
instead of being re-read on every run of a FAT or HFS+ volume. --no-embedded-reposafter a capturing run wrote a gitlink naming the vault's own commit; a repository
inside a repository crashed the run; one with no commit failed every run; a junction that is itself a
repository was followed out of the tree.--payload-gcdecided from a listing that could have skipped a branch (so its objects looked unused),
and from a decision minutes old. The listing is strict and the refs are read again before the first
deletion.- A pid recycled by an unrelated process blocked every later snapshot on Windows.
- The weekly report copied the whole vault into the temporary directory and ran on a vault's first run.
- A manifest path using a code point HFS+ ignores was accepted; flags that would be ignored are refused.
Documentation
- Corrected: the source is "never written to" (a probe file on a network share), objects are "never
overwritten", a payload vault can snapshot offline (it cannot),--payload-verifycovers every snapshot
(the newest only), and 0.9.1's claim that the conflict-copy warning missed dotted refs (measured on a
real server: it did not). - Stated: tags must be pushed to be seen by retention; the manifest is one line; a 0.8.8 machine restores a
payload vault's git tree only, without a warning.
Upgrading from 0.9.1
- A directory that cannot be read now stops the run: a scheduled job that had been skipping one will fail
until it is made readable or excluded. --payload-gcstops, deleting nothing, if a ref cannot be read or a push landed while it decided.- A vault gets
.seafile-payload/mtimes.jsonon its next pushing run (about 60 bytes a file; about 1.5 MB
compressed at 100,000 files, uploaded whole each time anything changes).--no-record-mtimesopts out. - No file format changed; 0.9.1 vaults and remotes are read as before.
Known gaps
- The times record is one blob, rewritten whole when any file changes (sharding it is a format change).
- A nested repository's history, and directory times, are not captured.
- macOS (NFD names, HFS+,
utimeon symlinks) is covered by reasoning and CI, not by a Mac here.
The full list, with measurements, is in CHANGELOG.md.
Full Changelog: v0.9.1...v0.9.2
v0.9.1 - A Review Release: `gc` and `push` Can No Longer Lose a Ref Silently, Tokens Stay Where They Belong, and Nested Repositories Are Captured
What's Changed in v0.9.1
Three independent reviews (Opus, GLM-5.3, Gemini) went over the Git remote helper. Every finding that
reproduced is fixed here, each with a test that fails without the fix. The snapshot tool changes below
were not part of those reviews. The full suite is 987 tests; the helper changes were also run on Linux
(Debian under WSL) and against a real Seafile server (CE 13.0.28) in a scratch library.
The two that could lose data
gcdeleted the commits of a branch it did not see. The compactor mirrored each remote ref as a
loose file in a scratch repository, so refs whose names collide on the local filesystem (Featureand
featureon Windows and macOS,NUL, a trailing dot) became one file; the branch that lost had its
objects pruned and its old packs deleted, andgcexited 0. A ref whose read came back 404 was dropped
the same way. The mirror is now onepacked-refsfile, git must read back exactly the refs written,
and an unreadable ref stopsgcwith nothing deleted.- A push could write a ref that names objects the remote does not have. The pack left out what the
refs read before the lock reached; a branch deleted (andgcrun) in that window took its commits
away whilemainwas written pointing at them. The refs are read again under the lock, the pack
directory is listed after the upload and checked at size, and the lease is verified before every ref.
Credentials
- A returned transfer link could choose who received the Seafile token (
http://other\@api-host/:
the origin check and the connection parsed it differently). It is compared with the connection's parser,
and an ambiguous authority is refused. - Transfer tokens no longer appear in connection-error text, and a URL password is masked in the four
messages that printed it.
The smaller ones
- A remote named
gc,testorunlockran that subcommand (git fetch gcran a compaction). - A remote pack named as the uppercase twin of a local one overwrote it on Windows; a backslash in a
seafile://path bypassed the working-tree collision guard. - Windows scratch directories (
grs-push-*) were never removed and were counted as removed. - A landed push could be reported
remote rejected; a lock that could not be renewed warned on every
16 KiB block; the conflict-copy warning missed refs with a dot;--dry-runsaidokto deleting a
ref that is not there; ref, HEAD and lock files were read with no size bound.
Snapshot tool
- Nested git repositories are captured as their files (ignored files too,
--excludeand payload
mode apply); their.gitis not.--no-embedded-reposkeeps the old commit-id-only record. - The payload hash cache's racy rule uses the share's own clock on a network filesystem (a probe file
measures it; a local tree is never written to). A same-size edit within one timestamp granule on a share
whose clock ran behind used to keep its stale hash. - Long Windows paths:
core.longpathson the vault and on a restored clone, extended-length paths for
restored payload files. - A report-only
--payload-gcsummary after a run that pushes, at most every--payload-report-days
(default 7,0off). It never deletes. - Manifest growth at 100,000 files is measured: about 10 kB per snapshot at 0.1% changed, 110 kB at 1%.
Upgrading from 0.9.0
gcis stricter: it stops, deleting nothing, when a ref or a refs directory cannot be read. Re-run it.- A push makes two more requests (refs under the lock, a pack directory listing).
- A vault whose source holds a nested git repository gets its files on the next snapshot; add
--no-embedded-reposto keep the old record. - Pack names must be lowercase and a
seafile://path may not contain a backslash. - Set the library's history to a few days (USER_GUIDE 8.0.1).
- No file format changed; 0.9.0 vaults and remotes are read as before.
Known gaps
--force-with-leaseis not supported (documented; it was rejected as non-fast-forward without a reason).- macOS network mounts are not detected by the clock probe;
--payload-rehashis the audit. - The nested repository's history, and git-tracked files' timestamps, are not restored.
The full list, with measurements, is in CHANGELOG.md.
Full Changelog: v0.9.0...v0.9.1
v0.9.0 - Payload Mode: a 131 GiB Tree Through Seafile Without Going Through Git, and the Snapshot Tool Installs with the Package
What's Changed in v0.9.0
The snapshot tool (git seafile-snapshot) can now carry a tree that is mostly large,
immutable data. Files above a threshold, or that do not compress, bypass git: each is
hashed and stored on the server under its SHA-256, and the snapshot is a manifest.
A 131 GiB tree (633 payload files, 124.6 GB) was snapshotted in 38 minutes against a
real Seafile server and restored byte-exact, verified file by file.
Upgrading from 0.8.8
- The snapshot tool ships in the wheel:
git-seafile-snapshot(sogit seafile-snapshot)
andgit-remote-seafile snapshot.python tools/seafile_snapshot.pystill works from a checkout. - Payload mode is on by default (
auto: files of 64 MiB or more, or 4 MiB or more that do not compress) for snapshots that push to aseafile://remote. A tree with no such file is snapshotted exactly as in 0.8.8.--no-payloadturns it off and the vault remembers that. - An existing vault with large files in git changes on its next run: they leave the git tree and are uploaded as payload objects (one full pass; stored twice on the server, since the old blobs stay in the history). Older snapshots restore as before; newer ones need the payload, and 0.8.8 cannot restore them completely. Run once with
--no-payloadto keep a vault as it was.
Added
- Payload mode (
--payload-min-size [SIZE|auto]): large files stored by hash under
<path>/payload/objects/, a manifest in the vault commit, parallel uploads, a
per-machine hash cache, and a verified--restore. The setting is remembered by the vault. --payload-gc(two-phase retention,--payload-keep Nfor expiry),--payload-verify
(audit by download and hash),--payload-view(a browsable directory of real names
by server-side copy),--payload-rehash.--no-payload, a.seafile-payload/README.txtin every payload vault, and a size line for the
payload store after an upload.- Safeguards (PAYLOAD.md D13): each snapshot commit records the tool's version and a run warns when it
builds on a newer tool's snapshot;--payload-deleteneeds--vault(or--payload-no-vault) and stops
with--payload-accept-losswhen the remote's history was rewritten or--payload-keepwould expire
snapshots (a tag is a pin); a restore that would not fit the disk is refused before downloading; an
upload over the quota or into a library this machine syncs is warned about first;--payload-verify
leaves a marker that the gc report shows. - The specification, with two rounds of independent review, is
PAYLOAD.md.
Changed
SeafileClient.delete_entryforgets the directories under a deleted path.--helpgroups the payload options and states each default.
Fixed
--restore --ref <rev>failed against any remote whose HEAD is the vault branch.
The full list, with measurements, is in CHANGELOG.md.
Full Changelog: v0.8.8...v0.9.0
v0.8.8 - Measured Against a Real Server: Size Caps, Chunked Uploads, and One Fewer Moving Part
What's Changed in v0.8.8
This release started as a simplification pass and became a size release when the changes were finally
measured against a real Seafile server (CE 13.0.28). Three things the stub could not have told us:
- A single request of 2 GiB is reset (1.75 GiB is accepted), so an uncapped 4 GiB tail merge failed at
its upload and a single 20 GB raw-data pack could never have been pushed. - The same server assembles a 5 GiB file from
Content-Rangechunks and serves it back
byte-identical. - Git for Windows with
core.autocrlf=truesilently truncates a 4 GiB+ blob atgit add(5 GiB came out
as 1 GiB) and stallsgit cloneof one. The helper cannot prevent the first; it now warns before the
second.
Upgrading from 0.8.7 or earlier
seafile.autogcis on by default;git config seafile.autogc falserestores tip-only behaviour.- Usage errors exit 2 on stderr (they were 1 on stdout).
seafile.locksettleandseafile.gcmaxmibare retired (reported once, then ignored);
seafile.gcmaxpackmibis now the largest pack the helper writes.- Clients older than 0.8.4 must not share a remote with this version.
Added
- Pack size cap and chunked upload. Every pack is capped at
seafile.gcmaxpackmib(1 GiB); files of
1 GiB or more upload in 128 MiBContent-Rangechunks, checked against the listing afterwards. git-remote-seafile doctor [url]- login, how the URL is interpreted, safety guards, a write probe.gc --tail- run the automatic tail merge by hand.- A "Known Limitations and What They Mean for You" section in the User Guide.
- A real-server test tier (opt-in) and a differential test of ref-name validation against
git.
Changed
- Automatic tail merge defaults on; budget is 2x the cap; packs at least half the cap are frozen.
- One output and exit-code convention for every subcommand;
cli.mainis a dispatch table. cmd_pushis a sequence of named steps shared with--dry-run.- Concurrent pushers wait by queue progress, not wall time; a waiting ticket is not re-uploaded each poll.
- Push-time
pack-objectsno longer delta-searches big blobs (50 s to 5 s on the measured payload). - The snapshot tool's source is cohesive parts built into one file; its watchdog is thread-free.
- Snapshot tool: the raw-payload profile starts at a sample ratio of 0.5 (was 0.9), so mixed data stages at 125-200 MiB/s instead of ~45 (131 GiB: ~50 minutes before, ~20 now); a free-space guard and a temp-pack sweep protect large runs.
Fixed
- Encrypted libraries are refused clearly; ref-name validation matches
git check-ref-format. - Conflict copies are named when they appear;
HEADread failures cannot half-advertise refs;
deleting HEAD's branch says how to repair it; uploads wait for the server's reply; stderr is UTF-8. - Snapshot tool: git failures are not "diverged", settings are remembered only after success,
--restorefinds the remote's only branch.
The full list, with measurements, is in CHANGELOG.md.
Full Changelog: v0.8.7...v0.8.8
v0.8.7 - The Fail-Loud Pass: a Bounded Subprocess, Three Client Lies, and the Two Halves of D6
What's Changed in v0.8.7
A patch release: every change in it is a defect rather than a capability. The theme is the one the
fail-closed principle has always stated and the code did not always follow — a check that cannot
answer must not be read as an answer, and a command that never finished must not be read as one that
did.
Three of the four groups below are that sentence in different clothes. Nothing bounded a subprocess, so
a wedged git repack held the remote lock past its own expiry while another client lawfully took over.
A download compared nothing against its Content-Length, so a truncated packfile was reported as a
success. A dir_exists() answered "does not exist" for every non-200, including the ones that mean "I
could not tell". And the shipped source cited twenty-three documents that are not in the repository, so
no reader could follow a single one of them.
The fourth is the two halves of D6 that the v0.8.6 pass deliberately left undone — and the reason it
left them is the reason they were worth doing properly: a type check that is red on arrival and a
coverage report nothing produces are both worse than nothing, because they look like enforcement.
Changed
-
Every
gitinvocation is bounded (H-24). The tree had timeouts on its HTTP calls and none at all
on its twenty-fivesubprocesssites.git_util.run_git,gc's runner and the snapshot tool's
runner now bound every call and fail loudly, naming the command and the limit.The bound is a constant rather than a configuration key, on purpose: a repository must not be able to
raise the ceiling that protects its own concurrency guarantee. The failure it prevents is specific —
agcwhosegit repacknever returns holds the remote lease open past expiry, at which point
another client lawfully takes over the lock this one still believes it holds.filter_existing_objectsis the one deliberate exception: a timeout there warns on stderr and answers
"assume no objects are present", because the cost of a false negative is a re-upload rather than a
lost object. -
dir_exists()no longer answers "does not exist" for a transient error (H-7). It returnedFalse
for every non-200, whilelist_dir()beside it raised for anything that was not 200 or 404 — the
two disagreed about the same state, and the ref-pruning path acted on the optimistic reading. Only an
explicit 404 is aFalsenow. The pruning loop already caught the raise and stopped;mkdir_pnow
aborts with the client's own message instead of proceeding to a creation it could not confirm. -
.gitignoregained.coverage.*. Parallel mode — whichpyproject.tomlturns on for the whole
suite, so that a worker cannot overwrite the driver's data — writes one data file per process, and the
plain.coveragerule matched none of them.
Fixed
-
A truncated download is a failure, not a success (A12.10).
download_file_toread
Content-Lengthinto the progress callback and never compared it against what it had written, so a
short body returnedTrue. A packfile that is quietly incomplete is a corrupt object store, found
later and far from the cause; a mismatch now raises. -
A server time that was never learned is announced (H-8).
get_server_time()fell back to the
local clock whenever noDateheader had been seen, which rested the lock's ordering on a clock the
protocol does not trust. It still falls back — a server that sends no date must not make the lock
unusable — but it now says so once, on stderr. -
The shipped source stops citing documents a reader cannot open (C8). Twenty-three comments across
seven files namedREVIEW.*.md,archive/…,(N-16)and(Qwen …); every one of them lives in the
gitignoredarchive/, so none could be followed from a checkout. The reasoning is inlined, and an
identifier survives only where it names a fact about the review rather than standing in for the
explanation.
Added
-
A
typecheckCI job (D6 residual). mypy 2.4.0, pinned, run bare so that the scope and the
strictness come from[tool.mypy]inpyproject.tomlrather than from the workflow — a localmypy
and CI cannot disagree about what is checked. The package andtools/are clean.tests/is deliberately out of scope: it is a namespace package, so mypy resolves each module under
two names and stops before checking anything, and its test doubles areMagicMocks whose attributes
no annotation can describe. A check that has to exclude a third of what it sees is not one people
trust.warn_unused_ignoresis on, so an ignore that has stopped being necessary fails the job
instead of absorbing the next real error on its line. -
A
coverageCI job (D6 residual). It runs the whole suite undercoverageand publishes the
per-file table to the job summary, because a workflow log needs admin rights to download and a job
summary does not.[tool.coverage.*]holds the configuration, and two settings there are load-bearing rather than
tidiness:parallelkeeps each process's data in its own file instead of letting the last one
overwrite it, andpatch = ["subprocess"]installs a startup hook and puts the setting in the
environment — the hook lives in site-packages and the variable is inherited throughgit, so the
helper is measured as well as the workers. Without both, the report would describe the driver process
alone: a figure that looks like coverage and is not.There is deliberately no threshold. A
fail_underchosen to make today's build green is one that
gets raised for the wrong reason or lowered for a worse one; what the job is for is that a drop is
visible in a review. At this release it reads 91% of the package (2944 statements, 264 missed) —
a dated observation, not a target. The report is also a measurement rather than a promise: it counts
the paths a stub exercises, and
DESIGN.md §4.3.1 says plainly
that none of this has been validated against a real Seafile instance.
Tests & Tooling
Twelve new tests in four classes cover the changes above: the three client.py defects, and the
subprocess timeout in git_util, in gc and in the snapshot tool. Suite: 681 → 693 tests / 134 → 138
targets. ROADMAP.md is re-targeted: the six rows a patch release could carry are closed and recorded
in §2, and every row that is left still names v0.9.0, v1.0.0 or won't do.
Full Changelog: v0.8.6...v0.8.7