Releases: rustutils/git-lfs
Release v0.7.0
Added
- Object files committed into the local LFS store now respect
core.sharedRepository(group/true/1→ 0o770/0o660;
all/world/everybody/2→ 0o775/0o664; octal values such as
0660→ that mode; unset /false/umask→ fall through to the
process umask). Directories under.git/lfs/(objects/,tmp/,
incomplete/) get the matching mode with read bits copied to
execute bits. Thetempfilecrate creates files at 0o600
unconditionally; we chmod after persisting so umask-respecting
shells get the same mode they'd get fromgititself. Wired through
Clean / Smudge / FilterProcess / pull / fetch / checkout. Lands
t-umask(4/4). git lfs clone --recursive(and--recurse-submodules) now runs
git submodule foreach --recursive 'git lfs pull'after the
top-level pull, so LFS content materializes in every nested
submodule (git ≥ 2.9 forwards the disabled smudge filter to
submodules, leaving them with pointer text otherwise). Lands
t-clone::clone with submodules.http.cookieFilesupport: reqwest cookie jar populated from the
configured Netscape-format file, attached to every transfer. Lets
the LFS client traverse load balancers / proxies that gate access
on a session cookie. URL-scoped overrides (http.<url>.cookieFile)
win over global. Landst-clone::clone (HTTP server/proxy require cookies).- Action-URL expiration check before upload/download. The transfer
queue now compares the batch response'sexpires_in(preferred when
non-zero) /expires_atagainstnow + 5sand fails the object
withaction "<rel>" expiredrather than driving an already-stale
URL into the wire. Landst-expired(6/6). The matching
t-push::push (retry with expired actions)still fails because that
test requires the upstream retry-then-rebatch flow we haven't
implemented yet. git lfs merge-driver, the LFS-aware Git merge driver. Wired into
Git via[merge "lfs"] driver = git lfs merge-driver --ancestor %O --current %A --other %B --marker-size %L --output %A. Each of
--ancestor/--current/--otheris smudged into a tempfile
(fetching the object on demand if needed), the three plus a fresh
%Dtempfile are substituted into--program(default
git merge-file --stdout --marker-size=%L %A %O %B >%D), and the
merged result is cleaned back into a pointer at--output.
Non-zero merge program exit propagates as conflicts. Lands
t-merge-driver(6/6).git lfs logs(andlogs last/logs show <name>/logs clear/
logs boomtown). Manages the crash-log directory under
.git/lfs/logs/;boomtownis the deliberate-failure self-test that
writes a sample log and exits 2. Landst-logs.git lfs ls-files --deletedwalks the ref's full history (matching
scan_pointers's reachability semantics) so pointers reachable from
prior commits but absent at HEAD still surface. Lands
t-ls-files::reference with --deleted.git lfs ls-files --all <ref>now errors withCannot use --all with explicit referencerather than silently ignoring the positional.
Landst-ls-files::--all with argument(s).url.<base>.pushInsteadOfis honored for upload + verify action URLs
whenlfs.transfer.enablehrefrewrite=true. Falls back to plain
insteadOfwhen no push-direction alias matches, so the existing
download behavior is preserved. Newgit_lfs_git::aliases::load_push_aliases
andTransferConfig::upload_url_rewritercarry the push-direction
rewrite separately from the download rewriter. Landst-push::push with invalid pushInsteadof.
Fixed
-
creds: gate thecreds: git credential <sub> (...)trace line on
GIT_TRACEso it stays silent when tracing is off. Matches upstream's
tracerx.Printfbehavior. Landst-lock::lock multiple files, whose
grep -v CREDS errlogassertion was tripping on the always-on lowercase
line. Restorest-lockto 17/17. -
git lfs smudge <path>now honorslfs.fetchinclude/
lfs.fetchexclude. When the path doesn't pass the filter, the
pointer bytes pass through verbatim instead of triggering a
download — matchinggit lfs filter-processand upstream's
command_smudge.go. Landst-smudge::smudge include/exclude. -
Temp-file cleanup at command startup now walks the full
.git/lfs/tmp/tree rather than onlytmp/objects/. Files
matching<64-hex>-...whose object is already complete are
removed unconditionally; other files older than an hour are pruned,
with subdirectories younger than an hour exempted (hard-linked
cross-repo temp files can look stale but still be in use). Mirrors
upstream'sfs/cleanup.go. Landst-tempfile. -
git lfs tracklisting now expands[attr]NAMEmacros from
top-level.gitattributes,.git/info/attributes, and the user
attributes file (core.attributesfile, default
$XDG_CONFIG_HOME/git/attributes). Patterns like*.dat lfsare
recognized as LFS-tracked when[attr]lfs filter=lfs ...is in
scope, sogit lfs track '*.dat'correctly reports
"*.dat" already supported. Subdirectory[attr]NAMEdeclarations
are ignored (git itself rejects them as "not allowed:
dir/.gitattributes:N"). Landst-attributes(4/4). -
git lfs trackblocklist check now matches upstream'sgit ls-files --ignored --cached -z -x <pattern>+ basename-prefix logic rather
than textually globbing the pattern against.gitattributesetc.
Patterns that would match a forbidden file but where no such file
is currently tracked (e.g.**/*in a fresh repo) now go through
cleanly. Existing rejection ofgit lfs track .gitattributes,.git*,
*still works because those patterns hit committed.gitattributes.
Landst-ls-files::list/stat files with escaped runes in path before commit(which sets up viagit lfs track '**/*'). -
git lfs ls-files(no args) now scans the index in addition to the
tree at HEAD, so freshly-staged-but-uncommitted pointers show up.
Falls back to the empty tree when HEAD doesn't exist yet, matching
upstream'sgit.EmptyTree()path. The*/-"is the working-tree
file present?" check now joins against the repo root so invocations
from a subdirectory report the correct marker. Landst-ls-files
tests 3–5, 8–9, 27–31 (10 tests). -
git lfs ls-files --jsonnow uses single-space indentation and ends
with a trailing newline, matching upstream's
json.Encoder{SetIndent("", " "), Encode()}. -
git lfs push --all(and any push path with locally-missing pointers
the server already holds) now reportsUploading LFS objects: 100% (N/N)instead of(M/N)whenM < Nbecause the missing-locally
objects are already on the remote. They count as already-successful
in both the object count and byte total. Landst-pushtests 9–12
(4 tests). -
api:BatchResponse.objects[].sizedeserialization now rejects
negative values withinvalid size (got: -N), matching upstream's
wording. Previously serde's defaultu64decoder bailed with a
generic type error. Landst-push::push (with invalid object size). -
git lfs pushexits with code 2 when any per-object upload fails
(previously: code 1). Matches upstream's "push aborted" semantics
and is whatt-push::push with invalid pushInsteadofgreps for.
Release v0.6.0
Fixed
api:ApiError::StatusDisplay now surfaces the server's body
messageverbatim when present, falling back to
Authorization error: <url>(401/403) only when the body is empty.
Previously the body was dropped for 401/403, hiding messages like
Expected ref "refs/heads/other", got "refs/heads/main"that
t-pre-push/t-fetch-refspec/t-push/t-credentialsall
grep for. Lands 5 tests (3 near-misses across 3 suites, plus 2
bonus from suites that shared the same root cause).creds:HelperChain::fillnow skips helpers that error and
continues to the next, matching upstream'sCredentialHelpers.Fill
(creds/creds.go:502). Previously a failed askpass program
short-circuited the chain beforegit credentialgot a turn,
so a missingGIT_ASKPASSwould lock the user out instead of
falling through to the configured credential helper.creds: emitcreds: failed to find GIT_ASKPASS command: <prog>
when the askpass executable isn't onPATH, and
creds: git credential <sub> (<proto>, <host>, <path>)on every
git credentialinvocation. Both match upstream'stracerx.Printf
format atcreds/creds.go:284/:328. Lands
t-credentials-no-prompt::askpass: push with bad askpass.git lfs fetch <ref>...now scans only the HEAD-state of each
named ref instead of walking its full history. Historical /
deleted-from-HEAD pointers still get fetched via--allor
--recent. Matches upstream'sfetchRefvsfetchRefssplit
and is a prerequisite for the upcoming--recentsemantics.
Added
-
migrate info --fixupnow does the real per-tree attribute walk:
list every blob at the selected ref, build a freshAttrSetfrom
that tree's.gitattributesfiles (root + nested), and count any
non-attrs, non-symlink, non-pointer blob whose path is LFS-tracked
per the attrs. Mirrors upstream's
commands/command_migrate_info.go::BlobFnfixup branch. Lands
t-migrate-infotests 37-41 (the--fixupcluster) → suite is
now full pass 50/50. Per-commit attribute resolution across multi-
commit history is deferred; the vendored fixup fixtures are all
single-commit so the simplification doesn't affect any test today. -
transfer: Range-resume on interrupted downloads. The basic
download adapter writes through<lfs_dir>/incomplete/<oid>.part
and, when a prior attempt left a non-empty partial, sends
Range: bytes=<offset>-<size-1>on the next attempt. Three status
paths land:- 206 Partial Content → append to the partial (
xfer: server accepted resume download request). - 416 Requested Range Not Satisfiable → delete the partial and
recurse withoutRange:(xfer: server rejected resume … re-downloading from start). - 200 OK to a Range request → server ignored the header; treat
as a fresh download (truncate + write).
Partials whose size meets or exceeds the expected object size are
treated as invalid (would producebytes=N-(N-1)) and dropped
before any request.GIT_CURL_VERBOSEnow emits curl-style
request/response headers on the storage GET so tests can grep
Range:,Content-Range:,206 Partial Content,416 Requested Range Not Satisfiable. Mirrors upstream'stq/basic_download.go.
Landst-batch-storage-retriestests 3-5. - 206 Partial Content → append to the partial (
-
store:incomplete_dir()/incomplete_path(oid)/
commit_partial(oid, path)API for the resumable-download adapter.
Hash mismatch error message changed toexpected OID {expected}, got {actual}so the upstream test suite can grep for the
substring. -
cli: fetch failures now emiterror: failed to fetch some objects, matching upstream'scommands/command_fetch.go::Exit
format. Previously emittedone or more objects failed to download. -
transfer: batch endpoint retries on 429 / 5xx, honoring
Retry-Afterwhen the server pinned a wait time.Transfer::run
now routes through abatch_with_retryhelper that retries the
batch the same way per-object transfers retry, emitting
tq: sending batch of size Non every attempt and one
tq: enqueue retry #N after <secs>s for "<oid>" (size: M): <err>
per object in the batch on each retry — that's what
t-batch-retries-ratelimit.shgreps for, since upstream's
transfer queue routes each object throughenqueueRetryat the
batch layer. TheRetry-Afterheader now also surfaces on
ApiError::Statusvia the newretry_after()accessor. Lands
t-batch-retries-ratelimit(5 tests). -
transfer:Retry-Afterheader parsing on storage-action 429 / 5xx
responses. When the server pins a wait time we sleep for exactly
that long instead of falling back to exponential backoff, mirroring
upstream'serrors.NewRetriableLaterErrorgate.
git_lfs_api::parse_retry_afteris the shared helper (delta-seconds
only today; RFC 1123 deferred until a test forces it). Lands
t-batch-storage-retries-ratelimit(5 tests). -
transfer:with_retryemits upstream-matching GIT_TRACE
breadcrumbs per retry —tq: retrying object <oid> after <secs>s
(Retry-After path) ortq: retrying object <oid>: <err>(exponential
path), plustq: enqueue retry #N after <secs>s for "<oid>" (size: N): <err>.
Landst-batch-storage-retriestests 1-2 (storage 5xx exponential
retries). -
transfer: action-URL error format for fatal 5xx now prefixes
Fatal error:to match upstream'sNewFatalErrorwrap — the
t-batch-storage-retriesgreps for the exact string. 4xx and the
non-fatal 5xx (501/507/509) keep the existingLFS:prefix that
t-pull/t-pushgrep on. -
transfer: defaultmax_attemptsbumped from 3 to 9 (= 8 retries),
matching upstream'sdefaultMaxRetries = 8. Rate-limit windows
(the test server uses 10s) outlast our previous 2-retry budget;
the new budget covers ~25s of cumulative exponential backoff. -
creds:NetrcCredentialHelperreads$HOME/.netrc(or_netrc
on Windows) at construction and slots into the helper chain ahead
of the cache. Hosts covered by netrc don't have to round-trip
throughgit credential fill. Parser is permissive — recognized
keywords aremachine/default/login/password;
unknown tokens are silently skipped so other tools' annotations
don't break the parse. Matches upstream's
creds/netrc.go::netrcCredentialHelper, including the trace
format (netrc: git credential fill/approve/reject (…)with
Go's%qquoting) the shell tests grep on. -
api::Client: preemptive fill on subsequent requests after the
first successful auth cycle. Re-walks the helper chain on every
request once we've cached creds for the endpoint, so trace-emitting
helpers (notably netrc) log afillline per authenticated
request — matches upstream'ssetRequestAuthflow under
access=basic. Lands the two main netrc tests int-credentials.sh
(credentials from netrc,credentials from netrc with unknown keyword) plus one of twot-credentials-no-prompt.shtests. -
git/cli:http.extraHeader/http.<url>.extraHeader(multi-
value, longest-prefix match) are now installed as default headers on
the reqwest client backing the LFS API and transfer adapter. Same
knob proxies and enterprise gateways use to inject Authorization or
bookkeeping headers without going throughgit credential. Header
names are case-canonicalized by reqwest (matches upstream's
textproto.CanonicalMIMEHeaderKey), soAUTHORIZATION:and
Authorization:map to the same header.GIT_CURL_VERBOSEechoes
the values in the request dump sot-extra-header.sh's curl-style
greps line up. -
transfer: basic upload adapter now sniffs the first 512 bytes of
each object and setsContent-Typeaccordingly (matches upstream's
tq/basic_upload.go::setContentTypeFor). Sniffing covers gzip
(1f 8b→application/x-gzip) today; broader coverage extends
the table when a new test demands it.lfs.<url>.contenttype=false
(withlfs.contenttypefallback) skips detection and sends
application/octet-stream— useful when a CDN rejects sniffed
types. On HTTP 422 from the action upload, the adapter emits
upstream's three-line stderr nudge pointing at the disable knob.
Lands all oft-extra-header.sh(4 tests) andt-content-type.sh
(3 tests). -
cli/prune:--verify-remote(-c) sends every prunable OID
through a download-direction batch and refuses to delete anything
the server can't serve back — protects against accidentally
pruning the only remaining copy of a not-yet-replicated object.
--verify-unreachableextends the check to orphan objects (those
not reachable from any commit) too; without it, orphans pass
through silently and are still pruned, matching upstream's
pruneGetVerifiedPrunableObjectsdecision matrix.
--when-unverified={halt|continue}controls what happens when
some are missing —halt(default) refuses the prune and lists
the OIDs;continuedrops them from the delete set and prunes
the rest.--no-verify-remote/--no-verify-unreachable
override the correspondinglfs.pruneverifyremotealways/
lfs.pruneverifyunreachablealwaysconfig keys for one
invocation. Status line now readsX local objects, Y retained, Z verified with remote, W not on remote, done.Closes
t-prune.shtests 6 (prune verify) and 8 (prune unreachable)
—t-prune.shis now 18/18. -
cli/fetcher:check_server_can_download(specs)companion to
the existingcheck_server_has— sends a download-direction
batch and returns the OIDs the server admits to having. Used by
prune --verify-remote; the existing upload-direction helper
still serves push's "skip not-on-remote" gate. -
creds/api/cli: SSH-mediated auth via thegit-lfs-authenticate
command, the missing piece for SSH-only forge deployments. New
creds::SshAuthClientspawns
ssh [-p <port>] <user>@<host> git-lfs-authenticate <path> <op>,
parses the JSON re...
Release v0.5.0
Changed
--helpoutput no longer renders rustdoc backticks literally. The
doc-comment markdown convention now produces clean terminal text
(backticks stripped), bold inline-code in man pages, and proper
links in the mdbook docs (gitignore(5),git-lfs-config(5),
etc. resolve to git-scm.com or the corresponding internal page).git-lfs-smudge(1)gains ENVIRONMENT and KNOWN BUGS sections;
git-lfs-checkout(1)gets the upstream-faithful DESCRIPTION
(conflict mode, partial-clone /GIT_ATTR_SOURCEinteraction,
bare-repo behavior) and an EXAMPLES section.git-lfs-fetch(1)gets the upstream-faithful DESCRIPTION,
per-flag wording, and dedicated DEFAULT REMOTE / DEFAULT REFS /
INCLUDE AND EXCLUDE / EXAMPLES / SEE ALSO sections. Adds
-a/-p/-d/-jshort aliases for--all/--prune/
--dry-run/--jsonto match upstream. The--recent
flag and thelfs.fetchrecent*configuration are still
unimplemented and the docs say so explicitly.git-lfs-pull(1)gets the upstream-faithful DESCRIPTION
(with a short pointer to git-lfs-checkout(1) for the
partial-clone / bare-repo behavior, since the same prose
already lives there) plus DEFAULT REMOTE, INCLUDE AND
EXCLUDE, and SEE ALSO sections.git-lfs-push(1)gets the upstream-faithful DESCRIPTION,
per-flag wording (including the "behavior differs from
git lfs fetch --all" warning on--all), and a SEE
ALSO section. Adds-d/-a/-oshort aliases for
--dry-run/--all/--object-idto match upstream.git-lfs-install(1)andgit-lfs-uninstall(1)get
upstream-faithful DESCRIPTIONs and per-flag wording,
plus SEE ALSO sections. Adds-w(--worktree) on
both and-s(--skip-smudge) on install for parity.
--manualis not yet supported on install — use
git lfs update --manualinstead.git-lfs-track(1)andgit-lfs-untrack(1)get
upstream-faithful DESCRIPTIONs, per-flag wording, and
EXAMPLES + SEE ALSO sections. Adds-d(--dry-run)
and-j(--json) short aliases on track for parity.git-lfs-lock(1),git-lfs-locks(1), and
git-lfs-unlock(1)get upstream-faithful DESCRIPTIONs
and per-flag wording, plus SEE ALSO sections. Our
--ref(refspec) flag is documented as an extension
over upstream's CLI on each command.--cachedon
locksis not yet implemented.git-lfs-status(1),git-lfs-ls-files(1),
git-lfs-prune(1), andgit-lfs-fsck(1)get
upstream-faithful DESCRIPTIONs and per-flag wording,
plus SEE ALSO sections. Each page honestly notes
unimplemented upstream features:ls-filesskips
--include/--exclude/--deletedand the two-ref
diff form;pruneskips the--force/--recent/
--verify-remotefamily and the recent-files /
stash / worktree retention rules;fsckskips the
<a>..<b>range form andlfs.fetchexcludehonor.git-lfs-clean(1),git-lfs-filter-process(1),
git-lfs-clone(1),git-lfs-pointer(1),
git-lfs-version(1),git-lfs-env(1),
git-lfs-ext(1), andgit-lfs-update(1)get
upstream-faithful descriptions and per-flag wording.
Adds-s(--skip) on filter-process and-m/-f
(--manual/--force) on update for parity with
upstream's short aliases. Thegit-lfs-clone(1)page
notes thatgit lfs cloneno longer offers a
meaningful speedup over plaingit clone(which
parallelizes the smudge filter on modern Git).git-lfs-pre-push(1),git-lfs-post-checkout(1),
git-lfs-post-commit(1), andgit-lfs-post-merge(1)
get upstream-faithful descriptions and SEE ALSO
sections. Adds-d(--dry-run) on pre-push for
parity. The post-* hook docstrings previously claimed
"no-op stub"; corrected to reflect that all three
now wire into the lockable read-only enforcement
(the post-commit page notes our gap vs. upstream's
HEAD-only optimization).git-lfs-migrate(1)and its three subcommands
(import,export,info) get upstream-faithful
descriptions and per-flag wording. The migrate parent
page also gets INCLUDE AND EXCLUDE (with the
migrate-specific glob semantics that differ from
gitignore form), INCLUDE AND EXCLUDE REFERENCES
(with the ASCII commit-graph diagram), EXAMPLES (8
representative invocations across all three modes),
and SEE ALSO sections. Man pages for commands with
subcommands now include a SUBCOMMANDS section listing
them.- xtask now recurses into nested subcommands, generating
a man page and mdbook page for each one — the migrate
subcommands now have their own pages
(git-lfs-migrate-import(1)etc.), so the SUBCOMMANDS
cross-references ongit-lfs-migrate(1)resolve to
real pages instead of broken ones. - Every man page and mdbook page now ends with a REPORTING BUGS
section pointing at the project issue tracker and clarifying
that this is the Rust port (so reports don't end up on the
upstream Go project's tracker by mistake). Sourced from a
singlecli/man/reporting_bugs.md. The groff converter
learned.UR/.UEfor markdown links, so "issue tracker"
renders as a clickable link in OSC-8-capable terminals and
falls back to "issue tracker ⟨URL⟩" everywhere else
(portable across groff and mandoc).
Added
- Release packaging via
just package. Cross-compilesgit-lfsfor
linux-musl, darwin, and windows-gnullvm (x86_64 + aarch64 each)
using cargo-zigbuild, and produces per-target tarballs (zips on
windows) undertarget/dist/. Linux musl targets additionally
build.deband.rpmpackages via cargo-deb / cargo-generate-rpm,
namedgit-lfs-rsto avoid colliding with the upstreamgit-lfs
package; the binary still installs as/usr/bin/git-lfsso
git lfs <command>works after install. A source tarball
(git-lfs-X.Y.Z.tar.zst) ships alongside, combininggit archive HEADwith the generated man pages so downstream packagers can
build without our xtask. - Release binaries are now stripped, ThinLTO-optimized, single-codegen-
unit, andpanic = "abort"(workspace[profile.release]). Smaller
and faster than the default release profile. Stripping happens during
rustc rather than via cargo-deb's host-binutils strip, which was
failing on cross-arch musl binaries in CI. - GitLab CI pipeline (
lint → test → package → release → deploy).
Pushes to master run lint and test; semver-tagged commits
additionally build all packaging artifacts and publish a GitLab
release with notes pulled from the matchingCHANGELOG.md
section. Package job is also exposed as a manual button on master
and merge requests so packaging can be verified without cutting a
tag.
Fixed
git lfs ls-files --debugnow terminates each pointer block
with a trailing blank line, matching upstream's output and the
vendoredt-ls-filestest expectations.- Batch responses that use the deprecated
_linksfield name
(instead ofactions) now deserialize correctly. Older LFS
servers in the wild still emit this form. git lfs ls-filesoutside a git repository now printsNot in a Git repository.to stdout and exits 128, matching the other
commands.git lfs ls-files -- --allnow hints at the likely intended
git lfs ls-files --all --instead of silently scanning HEAD
for a ref named--all.git lfs migrate info --unit=<unit>now formats every row's
byte count as a fractional count of the requested unit
(b,kb,mb,gb,tb,pb) instead of being silently
ignored. Bare unit suffixes (--unit=kb) are accepted as
shorthand for--unit=1kb.git lfs smudgehonorslfs.skipdownloaderrors/
GIT_LFS_SKIP_DOWNLOAD_ERRORS. When the local store doesn't
have an object and the fetch fails, the smudge filter now
passes the original pointer text through to the working tree
instead of failing the checkout.