Skip to content

Releases: rustutils/git-lfs

Release v0.7.0

13 May 16:46
bc20f4e

Choose a tag to compare

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. The tempfile crate creates files at 0o600
    unconditionally; we chmod after persisting so umask-respecting
    shells get the same mode they'd get from git itself. 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.cookieFile support: 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. Lands t-clone::clone (HTTP server/proxy require cookies).
  • Action-URL expiration check before upload/download. The transfer
    queue now compares the batch response's expires_in (preferred when
    non-zero) / expires_at against now + 5s and fails the object
    with action "<rel>" expired rather than driving an already-stale
    URL into the wire. Lands t-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 / --other is smudged into a tempfile
    (fetching the object on demand if needed), the three plus a fresh
    %D tempfile 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 (and logs last / logs show <name> / logs clear /
    logs boomtown). Manages the crash-log directory under
    .git/lfs/logs/; boomtown is the deliberate-failure self-test that
    writes a sample log and exits 2. Lands t-logs.
  • git lfs ls-files --deleted walks 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 with Cannot use --all with explicit reference rather than silently ignoring the positional.
    Lands t-ls-files::--all with argument(s).
  • url.<base>.pushInsteadOf is honored for upload + verify action URLs
    when lfs.transfer.enablehrefrewrite=true. Falls back to plain
    insteadOf when no push-direction alias matches, so the existing
    download behavior is preserved. New git_lfs_git::aliases::load_push_aliases
    and TransferConfig::upload_url_rewriter carry the push-direction
    rewrite separately from the download rewriter. Lands t-push::push with invalid pushInsteadof.

Fixed

  • creds: gate the creds: git credential <sub> (...) trace line on
    GIT_TRACE so it stays silent when tracing is off. Matches upstream's
    tracerx.Printf behavior. Lands t-lock::lock multiple files, whose
    grep -v CREDS errlog assertion was tripping on the always-on lowercase
    line. Restores t-lock to 17/17.

  • git lfs smudge <path> now honors lfs.fetchinclude /
    lfs.fetchexclude. When the path doesn't pass the filter, the
    pointer bytes pass through verbatim instead of triggering a
    download — matching git lfs filter-process and upstream's
    command_smudge.go. Lands t-smudge::smudge include/exclude.

  • Temp-file cleanup at command startup now walks the full
    .git/lfs/tmp/ tree rather than only tmp/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's fs/cleanup.go. Lands t-tempfile.

  • git lfs track listing now expands [attr]NAME macros from
    top-level .gitattributes, .git/info/attributes, and the user
    attributes file (core.attributesfile, default
    $XDG_CONFIG_HOME/git/attributes). Patterns like *.dat lfs are
    recognized as LFS-tracked when [attr]lfs filter=lfs ... is in
    scope, so git lfs track '*.dat' correctly reports
    "*.dat" already supported. Subdirectory [attr]NAME declarations
    are ignored (git itself rejects them as "not allowed:
    dir/.gitattributes:N"). Lands t-attributes (4/4).

  • git lfs track blocklist check now matches upstream's git ls-files --ignored --cached -z -x <pattern> + basename-prefix logic rather
    than textually globbing the pattern against .gitattributes etc.
    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 of git lfs track .gitattributes, .git*,
    * still works because those patterns hit committed .gitattributes.
    Lands t-ls-files::list/stat files with escaped runes in path before commit (which sets up via git 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's git.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. Lands t-ls-files
    tests 3–5, 8–9, 27–31 (10 tests).

  • git lfs ls-files --json now 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 reports Uploading LFS objects: 100% (N/N) instead of (M/N) when M < N because the missing-locally
    objects are already on the remote. They count as already-successful
    in both the object count and byte total. Lands t-push tests 9–12
    (4 tests).

  • api: BatchResponse.objects[].size deserialization now rejects
    negative values with invalid size (got: -N), matching upstream's
    wording. Previously serde's default u64 decoder bailed with a
    generic type error. Lands t-push::push (with invalid object size).

  • git lfs push exits with code 2 when any per-object upload fails
    (previously: code 1). Matches upstream's "push aborted" semantics
    and is what t-push::push with invalid pushInsteadof greps for.

Release v0.6.0

13 May 01:00
9da141f

Choose a tag to compare

Fixed

  • api: ApiError::Status Display now surfaces the server's body
    message verbatim 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-credentials all
    grep for. Lands 5 tests (3 near-misses across 3 suites, plus 2
    bonus from suites that shared the same root cause).
  • creds: HelperChain::fill now skips helpers that error and
    continues to the next, matching upstream's CredentialHelpers.Fill
    (creds/creds.go:502). Previously a failed askpass program
    short-circuited the chain before git credential got a turn,
    so a missing GIT_ASKPASS would lock the user out instead of
    falling through to the configured credential helper.
  • creds: emit creds: failed to find GIT_ASKPASS command: <prog>
    when the askpass executable isn't on PATH, and
    creds: git credential <sub> (<proto>, <host>, <path>) on every
    git credential invocation. Both match upstream's tracerx.Printf
    format at creds/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 --all or
    --recent. Matches upstream's fetchRef vs fetchRefs split
    and is a prerequisite for the upcoming --recent semantics.

Added

  • migrate info --fixup now does the real per-tree attribute walk:
    list every blob at the selected ref, build a fresh AttrSet from
    that tree's .gitattributes files (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::BlobFn fixup branch. Lands
    t-migrate-info tests 37-41 (the --fixup cluster) → 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 without Range: (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 produce bytes=N-(N-1)) and dropped
    before any request. GIT_CURL_VERBOSE now 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's tq/basic_download.go.
    Lands t-batch-storage-retries tests 3-5.

  • store: incomplete_dir() / incomplete_path(oid) /
    commit_partial(oid, path) API for the resumable-download adapter.
    Hash mismatch error message changed to expected OID {expected}, got {actual} so the upstream test suite can grep for the
    substring.

  • cli: fetch failures now emit error: failed to fetch some objects, matching upstream's commands/command_fetch.go::Exit
    format. Previously emitted one or more objects failed to download.

  • transfer: batch endpoint retries on 429 / 5xx, honoring
    Retry-After when the server pinned a wait time. Transfer::run
    now routes through a batch_with_retry helper that retries the
    batch the same way per-object transfers retry, emitting
    tq: sending batch of size N on 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.sh greps for, since upstream's
    transfer queue routes each object through enqueueRetry at the
    batch layer. The Retry-After header now also surfaces on
    ApiError::Status via the new retry_after() accessor. Lands
    t-batch-retries-ratelimit (5 tests).

  • transfer: Retry-After header 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's errors.NewRetriableLaterError gate.
    git_lfs_api::parse_retry_after is 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_retry emits upstream-matching GIT_TRACE
    breadcrumbs per retry — tq: retrying object <oid> after <secs>s
    (Retry-After path) or tq: retrying object <oid>: <err> (exponential
    path), plus tq: enqueue retry #N after <secs>s for "<oid>" (size: N): <err>.
    Lands t-batch-storage-retries tests 1-2 (storage 5xx exponential
    retries).

  • transfer: action-URL error format for fatal 5xx now prefixes
    Fatal error: to match upstream's NewFatalError wrap — the
    t-batch-storage-retries greps for the exact string. 4xx and the
    non-fatal 5xx (501/507/509) keep the existing LFS: prefix that
    t-pull / t-push grep on.

  • transfer: default max_attempts bumped from 3 to 9 (= 8 retries),
    matching upstream's defaultMaxRetries = 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: NetrcCredentialHelper reads $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
    through git credential fill. Parser is permissive — recognized
    keywords are machine / 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 %q quoting) 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 a fill line per authenticated
    request — matches upstream's setRequestAuth flow under
    access=basic. Lands the two main netrc tests in t-credentials.sh
    (credentials from netrc, credentials from netrc with unknown keyword) plus one of two t-credentials-no-prompt.sh tests.

  • 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 through git credential. Header
    names are case-canonicalized by reqwest (matches upstream's
    textproto.CanonicalMIMEHeaderKey), so AUTHORIZATION: and
    Authorization: map to the same header. GIT_CURL_VERBOSE echoes
    the values in the request dump so t-extra-header.sh's curl-style
    greps line up.

  • transfer: basic upload adapter now sniffs the first 512 bytes of
    each object and sets Content-Type accordingly (matches upstream's
    tq/basic_upload.go::setContentTypeFor). Sniffing covers gzip
    (1f 8bapplication/x-gzip) today; broader coverage extends
    the table when a new test demands it. lfs.<url>.contenttype=false
    (with lfs.contenttype fallback) 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 of t-extra-header.sh (4 tests) and t-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-unreachable extends 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
    pruneGetVerifiedPrunableObjects decision matrix.
    --when-unverified={halt|continue} controls what happens when
    some are missing — halt (default) refuses the prune and lists
    the OIDs; continue drops them from the delete set and prunes
    the rest. --no-verify-remote / --no-verify-unreachable
    override the corresponding lfs.pruneverifyremotealways /
    lfs.pruneverifyunreachablealways config keys for one
    invocation. Status line now reads X local objects, Y retained, Z verified with remote, W not on remote, done. Closes
    t-prune.sh tests 6 (prune verify) and 8 (prune unreachable)
    t-prune.sh is now 18/18.

  • cli/fetcher: check_server_can_download(specs) companion to
    the existing check_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 the git-lfs-authenticate
    command, the missing piece for SSH-only forge deployments. New
    creds::SshAuthClient spawns
    ssh [-p <port>] <user>@<host> git-lfs-authenticate <path> <op>,
    parses the JSON re...

Read more

Release v0.5.0

03 May 19:22
f1dfabc

Choose a tag to compare

Changed

  • --help output 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_SOURCE interaction,
    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/-j short aliases for --all/--prune/
    --dry-run/--json to match upstream. The --recent
    flag and the lfs.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/-o short aliases for
    --dry-run/--all/--object-id to match upstream.
  • git-lfs-install(1) and git-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.
    --manual is not yet supported on install — use
    git lfs update --manual instead.
  • git-lfs-track(1) and git-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. --cached on
    locks is not yet implemented.
  • git-lfs-status(1), git-lfs-ls-files(1),
    git-lfs-prune(1), and git-lfs-fsck(1) get
    upstream-faithful DESCRIPTIONs and per-flag wording,
    plus SEE ALSO sections. Each page honestly notes
    unimplemented upstream features: ls-files skips
    --include/--exclude/--deleted and the two-ref
    diff form; prune skips the --force/--recent/
    --verify-remote family and the recent-files /
    stash / worktree retention rules; fsck skips the
    <a>..<b> range form and lfs.fetchexclude honor.
  • 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), and git-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. The git-lfs-clone(1) page
    notes that git lfs clone no longer offers a
    meaningful speedup over plain git clone (which
    parallelizes the smudge filter on modern Git).
  • git-lfs-pre-push(1), git-lfs-post-checkout(1),
    git-lfs-post-commit(1), and git-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 on git-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
    single cli/man/reporting_bugs.md. The groff converter
    learned .UR/.UE for 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-compiles git-lfs for
    linux-musl, darwin, and windows-gnullvm (x86_64 + aarch64 each)
    using cargo-zigbuild, and produces per-target tarballs (zips on
    windows) under target/dist/. Linux musl targets additionally
    build .deb and .rpm packages via cargo-deb / cargo-generate-rpm,
    named git-lfs-rs to avoid colliding with the upstream git-lfs
    package; the binary still installs as /usr/bin/git-lfs so
    git lfs <command> works after install. A source tarball
    (git-lfs-X.Y.Z.tar.zst) ships alongside, combining git archive HEAD with the generated man pages so downstream packagers can
    build without our xtask.
  • Release binaries are now stripped, ThinLTO-optimized, single-codegen-
    unit, and panic = "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 matching CHANGELOG.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 --debug now terminates each pointer block
    with a trailing blank line, matching upstream's output and the
    vendored t-ls-files test expectations.
  • Batch responses that use the deprecated _links field name
    (instead of actions) now deserialize correctly. Older LFS
    servers in the wild still emit this form.
  • git lfs ls-files outside a git repository now prints Not in a Git repository. to stdout and exits 128, matching the other
    commands.
  • git lfs ls-files -- --all now 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 smudge honors lfs.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.