Merge branch 'ty/patch-ids-document-lazy-eval'

In-code comment update to record a design decision to allow lazy
computation of patch IDs.

* ty/patch-ids-document-lazy-eval:
  patch-ids: document intentional const-casting in patch_id_neq()
diff --git a/.editorconfig b/.editorconfig
index 2d3929b..6e4eaa8 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -4,7 +4,7 @@
 
 # The settings for C (*.c and *.h) files are mirrored in .clang-format.  Keep
 # them in sync.
-[{*.{c,h,sh,bash,perl,pl,pm,txt,adoc},config.mak.*,Makefile}]
+[{*.{c,h,sh,bash,perl,pl,pm,txt,adoc},config.mak.*,Makefile,templates/hooks/*.sample}]
 indent_style = tab
 tab_width = 8
 
diff --git a/.gitattributes b/.gitattributes
index 3258314..556322b 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,13 +1,13 @@
-* whitespace=!indent,trail,space
-*.[ch] whitespace=indent,trail,space diff=cpp
-*.sh whitespace=indent,trail,space text eol=lf
+* whitespace=trail,space
+*.[ch] whitespace=indent,trail,space,incomplete diff=cpp
+*.sh whitespace=indent,trail,space,incomplete text eol=lf
 *.perl text eol=lf diff=perl
 *.pl text eof=lf diff=perl
 *.pm text eol=lf diff=perl
 *.py text eol=lf diff=python
 *.bat text eol=crlf
 CODE_OF_CONDUCT.md -whitespace
-/Documentation/**/*.adoc text eol=lf
+/Documentation/**/*.adoc text eol=lf whitespace=trail,space,incomplete
 /command-list.txt text eol=lf
 /GIT-VERSION-GEN text eol=lf
 /mergetools/* text eol=lf
@@ -17,3 +17,5 @@
 /Documentation/gitk.adoc conflict-marker-size=32
 /Documentation/user-manual.adoc conflict-marker-size=32
 /t/t????-*.sh conflict-marker-size=32
+/t/unit-tests/clar/test/expected/* whitespace=-blank-at-eof
+/templates/hooks/*.sample whitespace=indent,trail,space,incomplete text eol=lf
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index c8755e3..9304212 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -10,7 +10,8 @@
 
 Please read ["A note from the maintainer"](https://git.kernel.org/pub/scm/git/git.git/plain/MaintNotes?h=todo)
 to learn how the Git project is managed, and how you can work with it.
-In addition, we highly recommend you to read [our submission guidelines](../Documentation/SubmittingPatches).
+In addition, we highly recommend you to read
+[our submission guidelines](https://git-scm.com/docs/SubmittingPatches).
 
 If you prefer video, then [this talk](https://www.youtube.com/watch?v=Q7i_qQW__q4&feature=youtu.be&t=6m4s)
 might be useful to you as the presenter walks you through the contribution
diff --git a/.github/workflows/l10n.yml b/.github/workflows/l10n.yml
index e2c3dbd..95e5513 100644
--- a/.github/workflows/l10n.yml
+++ b/.github/workflows/l10n.yml
@@ -63,7 +63,7 @@
             origin \
             ${{ github.ref }} \
             $args
-      - uses: actions/setup-go@v5
+      - uses: actions/setup-go@v6
         with:
           go-version: '>=1.16'
           cache: false
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 816d5a3..826f2f5 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -150,7 +150,7 @@
     - uses: git-for-windows/setup-git-for-windows-sdk@v1
     - name: test
       shell: bash
-      run: . /etc/profile && ci/run-test-slice.sh ${{matrix.nr}} 10
+      run: . /etc/profile && ci/run-test-slice.sh $((${{matrix.nr}} + 1)) 10
     - name: print test failures
       if: failure() && env.FAILED_TEST_ARTIFACTS != ''
       shell: bash
@@ -237,7 +237,7 @@
       shell: bash
       env:
         NO_SVN_TESTS: 1
-      run: . /etc/profile && ci/run-test-slice.sh ${{matrix.nr}} 10
+      run: . /etc/profile && ci/run-test-slice.sh $((${{matrix.nr}} + 1)) 10
     - name: print test failures
       if: failure() && env.FAILED_TEST_ARTIFACTS != ''
       shell: bash
@@ -297,8 +297,18 @@
         name: windows-meson-artifacts
         path: build
     - name: Test
-      shell: pwsh
-      run: meson test -C build --no-rebuild --print-errorlogs --slice "$(1+${{ matrix.nr }})/10"
+      shell: bash
+      run: ci/run-test-slice-meson.sh build $((${{matrix.nr}} + 1)) 10
+    - name: print test failures
+      if: failure() && env.FAILED_TEST_ARTIFACTS != ''
+      shell: bash
+      run: ci/print-test-failures.sh
+    - name: Upload failed tests' directories
+      if: failure() && env.FAILED_TEST_ARTIFACTS != ''
+      uses: actions/upload-artifact@v4
+      with:
+        name: failed-tests-windows-meson-${{ matrix.nr }}
+        path: ${{env.FAILED_TEST_ARTIFACTS}}
 
   regular:
     name: ${{matrix.vector.jobname}} (${{matrix.vector.pool}})
@@ -433,7 +443,7 @@
     - run: ci/install-dependencies.sh
     - run: useradd builder --create-home
     - run: chown -R builder .
-    - run: sudo --preserve-env --set-home --user=builder ci/run-build-and-tests.sh
+    - run: chmod a+w $GITHUB_ENV && sudo --preserve-env --set-home --user=builder ci/run-build-and-tests.sh
     - name: print test failures
       if: failure() && env.FAILED_TEST_ARTIFACTS != ''
       run: sudo --preserve-env --set-home --user=builder ci/print-test-failures.sh
diff --git a/.gitignore b/.gitignore
index 78a45cb..24635cf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -79,6 +79,7 @@
 /git-grep
 /git-hash-object
 /git-help
+/git-history
 /git-hook
 /git-http-backend
 /git-http-fetch
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b419a84..83ec786 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -101,13 +101,13 @@
   parallel:
     matrix:
       - jobname: osx-clang
-        image: macos-14-xcode-15
+        image: macos-15-xcode-16
         CC: clang
       - jobname: osx-reftable
-        image: macos-14-xcode-15
+        image: macos-15-xcode-16
         CC: clang
       - jobname: osx-meson
-        image: macos-14-xcode-15
+        image: macos-15-xcode-16
         CC: clang
   artifacts:
     paths:
@@ -157,6 +157,8 @@
   parallel: 10
 
 .msvc-meson:
+  variables:
+    TEST_OUTPUT_DIRECTORY: "C:/Git-Test"
   tags:
     - saas-windows-medium-amd64
   before_script:
@@ -164,12 +166,13 @@
     - choco install -y git meson ninja rust-ms
     - Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1
     - refreshenv
+    - New-Item -Path $env:TEST_OUTPUT_DIRECTORY -ItemType Directory
 
 build:msvc-meson:
   extends: .msvc-meson
   stage: build
   script:
-    - meson setup build --vsenv -Dperl=disabled -Dbackend_max_links=1 -Dcredential_helpers=wincred
+    - meson setup build --vsenv -Dperl=disabled -Dbackend_max_links=1 -Dcredential_helpers=wincred -Dtest_output_directory="$TEST_OUTPUT_DIRECTORY"
     - meson compile -C build
   artifacts:
     paths:
@@ -183,11 +186,21 @@
     - job: "build:msvc-meson"
       artifacts: true
   script:
-    - meson test -C build --no-rebuild --print-errorlogs --slice $Env:CI_NODE_INDEX/$Env:CI_NODE_TOTAL
+    - |
+      & "C:/Program Files/Git/usr/bin/bash.exe" -l -c 'ci/run-test-slice-meson.sh build $CI_NODE_INDEX $CI_NODE_TOTAL'
+  after_script:
+    - |
+      if ($env:CI_JOB_STATUS -ne "success") {
+        & "C:/Program Files/Git/usr/bin/bash.exe" -l -c 'ci/print-test-failures.sh'
+        Move-Item -Path "$env:TEST_OUTPUT_DIRECTORY/failed-test-artifacts" -Destination t/
+      }
   parallel: 10
   artifacts:
+    paths:
+      - t/failed-test-artifacts
     reports:
       junit: build/meson-logs/testlog.junit.xml
+    when: on_failure
 
 test:fuzz-smoke-tests:
   image: ubuntu:latest
diff --git a/.mailmap b/.mailmap
index 7b31981..c2e3939 100644
--- a/.mailmap
+++ b/.mailmap
@@ -107,6 +107,9 @@
 Jay Soffian <jaysoffian@gmail.com> <jaysoffian+git@gmail.com>
 Jean-Noël Avila <jn.avila@free.fr> Jean-Noel Avila
 Jean-Noël Avila <jn.avila@free.fr> Jean-Noël AVILA
+Jean-Noël Avila <jn.avila@free.fr> Jean-Noel Avila <jean-noel.avila@scantech.fr>
+Jean-Noël Avila <jn.avila@free.fr> Jean-Noël AVILA <avila.jn@gmail.com>
+Jean-Noël Avila <jn.avila@free.fr> Jean-Noël Avila via GitGitGadget <gitgitgadget@gmail.com>
 Jeff King <peff@peff.net> <peff@github.com>
 Jeff Muizelaar <jmuizelaar@mozilla.com> <jeff@infidigm.net>
 Jens Axboe <axboe@kernel.dk> <axboe@suse.de>
@@ -140,8 +143,8 @@
 Kaartic Sivaraam <kaartic.sivaraam@gmail.com> <kaarticsivaraam91196@gmail.com>
 Karl Wiberg <kha@treskal.com> Karl  Hasselström
 Karl Wiberg <kha@treskal.com> <kha@yoghurt.hemma.treskal.com>
-Karsten Blees <blees@dcon.de> <karsten.blees@dcon.de>
-Karsten Blees <blees@dcon.de> <karsten.blees@gmail.com>
+Karsten Blees <karsten.blees@gmail.com> <karsten.blees@dcon.de>
+Karsten Blees <karsten.blees@gmail.com> <blees@dcon.de>
 Kay Sievers <kay.sievers@vrfy.org> <kay.sievers@suse.de>
 Kay Sievers <kay.sievers@vrfy.org> <kay@mam.(none)>
 Kazuki Saitoh <ksaitoh560@gmail.com> kazuki saitoh <ksaitoh560@gmail.com>
@@ -221,11 +224,14 @@
 Peter Krefting <peter@softwolves.pp.se> <peter@svarten.intern.softwolves.pp.se>
 Petr Baudis <pasky@ucw.cz> <pasky@suse.cz>
 Petr Baudis <pasky@ucw.cz> <xpasky@machine>
-Phil Hord <hordp@cisco.com> <phil.hord@gmail.com>
+Phil Hord <phil.hord@gmail.com> <hordp@cisco.com>
+Phil Hord <phil.hord@gmail.com> <phord@purestorage.com>
 Philip Jägenstedt <philip@foolip.org> <philip.jagenstedt@gmail.com>
 Philip Oakley <philipoakley@iee.email> <philipoakley@iee.org> # secondary <philipoakley@dunelm.org.uk>
 Philipp A. Hartmann <pah@qo.cx> <ph@sorgh.de>
 Philippe Bruhat <book@cpan.org>
+Phillip Wood <phillip.wood@dunelm.org.uk> <phillip.wood123@gmail.com>
+Phillip Wood <phillip.wood@dunelm.org.uk> <phillip.wood@talktalk.net>
 Ralf Thielow <ralf.thielow@gmail.com> <ralf.thielow@googlemail.com>
 Ramsay Jones <ramsay@ramsayjones.plus.com> <ramsay@ramsay1.demon.co.uk>
 Ramkumar Ramachandra <r@artagnon.com> <artagnon@gmail.com>
@@ -277,6 +283,7 @@
 Thomas Rast <tr@thomasrast.ch> <trast@student.ethz.ch>
 Thomas Rast <tr@thomasrast.ch> <trast@inf.ethz.ch>
 Thomas Rast <tr@thomasrast.ch> <trast@google.com>
+Tian Yuchen <cat@malon.dev> <a3205153416@gmail.com>
 Timo Hirvonen <tihirvon@gmail.com> <tihirvon@ee.oulu.fi>
 Toby Allsopp <Toby.Allsopp@navman.co.nz> <toby.allsopp@navman.co.nz>
 Tom Grennan <tmgrennan@gmail.com> <tgrennan@redback.com>
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
index df72fe0..b867075 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -33,6 +33,16 @@
    achieve and why the changes were necessary (more on this in the
    accompanying SubmittingPatches document).
 
+ - A label "NEEDSWORK:" followed by a description of the things to
+   be done is a way to leave in-code comments to document design
+   decisions yet to be made. 80% of the work to resolve a NEEDSWORK
+   comment is to decide if it still makes sense to do so, since the
+   situation around the codebase may have changed since the comment
+   was written.  It can be a very valid change to remove an existing
+   NEEDSWORK comment without doing anything else, with the commit log
+   message describing a good argument why it does not make sense to do
+   the thing the NEEDSWORK comment mentioned.
+
 Make your code readable and sensible, and don't try to be clever.
 
 As for more concrete guidelines, just imitate the existing code
@@ -430,6 +440,8 @@
 	 */
 	_("Here is a translatable string explained by the above.");
 
+   We do not use // comments.
+
  - Double negation is often harder to understand than no negation
    at all.
 
@@ -656,6 +668,19 @@
    unsigned other_field:1;
    unsigned field_with_longer_name:1;
 
+ - Array names should be named in the singular form if the individual items are
+   subject of use. E.g.:
+
+         char *dog[] = ...;
+         walk_dog(dog[0]);
+         walk_dog(dog[1]);
+
+   Cases where the array is employed as a whole rather than as its unit parts,
+   the plural forms is preferable. E.g:
+
+         char *dogs[] = ...;
+         walk_all_dogs(dogs);
+
 For Perl programs:
 
  - Most of the C guidelines above apply.
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 04e9e10..2699f0b 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -53,6 +53,7 @@
 MAN7_TXT += gitcore-tutorial.adoc
 MAN7_TXT += gitcredentials.adoc
 MAN7_TXT += gitcvs-migration.adoc
+MAN7_TXT += gitdatamodel.adoc
 MAN7_TXT += gitdiffcore.adoc
 MAN7_TXT += giteveryday.adoc
 MAN7_TXT += gitfaq.adoc
@@ -142,6 +143,7 @@
 TECH_DOCS += technical/sparse-checkout
 TECH_DOCS += technical/sparse-index
 TECH_DOCS += technical/trivial-merge
+TECH_DOCS += technical/unambiguous-types
 TECH_DOCS += technical/unit-tests
 SP_ARTICLES += $(TECH_DOCS)
 SP_ARTICLES += technical/api-index
diff --git a/Documentation/MyFirstContribution.adoc b/Documentation/MyFirstContribution.adoc
index f186dfb..b9fdefc 100644
--- a/Documentation/MyFirstContribution.adoc
+++ b/Documentation/MyFirstContribution.adoc
@@ -331,7 +331,8 @@
 for you, try `cd Documentation/ && ../bin-wrappers/git psuh`). That's not so
 helpful. So what other context can we get?
 
-Add a line to `#include "config.h"` and `#include "repository.h"`.
+Add a line to `#include "config.h"`, `#include "repository.h"` and
+`#include "environment.h"`.
 Then, add the following bits to the function body:
 function body:
 
@@ -351,7 +352,7 @@
 apply standard precedence rules. `repo_config_get_string_tmp()` will look up
 a specific key ("user.name") and give you the value. There are a number of
 single-key lookup functions like this one; you can see them all (and more info
-about how to use `repo_config()`) in `Documentation/technical/api-config.adoc`.
+about how to use `repo_config()`) in `config.h`.
 
 You should see that the name printed matches the one you see when you run:
 
@@ -429,6 +430,7 @@
 ----
 #include "commit.h"
 #include "pretty.h"
+#include "strbuf.h"
 ----
 
 Then, add the following lines within your implementation of `cmd_psuh()` near
@@ -503,8 +505,8 @@
 
 SYNOPSIS
 --------
-[verse]
-'git-psuh [<arg>...]'
+[synopsis]
+git psuh [<arg>...]
 
 DESCRIPTION
 -----------
@@ -726,9 +728,10 @@
 ----
 
 NOTE: You can also do this with `make test` or use any testing harness which can
-speak TAP. `prove` can run concurrently. `shuffle` randomizes the order the
-tests are run in, which makes them resilient against unwanted inter-test
-dependencies. `prove` also makes the output nicer.
+speak TAP. `prove` can run concurrently. `-j$(nproc)` runs tests using all
+available CPUs in parallel, but the job count can be adjusted as needed.
+`shuffle` randomizes the order the tests are run in, which makes them resilient
+against unwanted inter-test dependencies. `prove` also makes the output nicer.
 
 Go ahead and commit this change, as well.
 
diff --git a/Documentation/RelNotes/2.53.0.adoc b/Documentation/RelNotes/2.53.0.adoc
new file mode 100644
index 0000000..2668c2e
--- /dev/null
+++ b/Documentation/RelNotes/2.53.0.adoc
@@ -0,0 +1,336 @@
+Git v2.53 Release Notes
+=======================
+
+UI, Workflows & Features
+------------------------
+
+ * "git maintenance" command learned "is-needed" subcommand to tell if
+   it is necessary to perform various maintenance tasks.
+
+ * "git replay" (experimental) learned to perform ref updates itself
+   in a transaction by default, instead of emitting where each refs
+   should point at and leaving the actual update to another command.
+
+ * "git blame" learns "--diff-algorithm=<algo>" option.
+
+ * "git repo info" learned "--all" option.
+
+ * Both "git apply" and "git diff" learn a new whitespace error class,
+   "incomplete-line".
+
+ * Add a new manual that describes the data model.
+
+ * "git fast-import" learns "--signed-commits=strip-if-invalid" option
+   to drop invalid cryptographic signature from objects.
+
+ * The use of "revision" (a connected set of commits) has been
+   clarified in the "git replay" documentation.
+
+ * A help message from "git branch" now mentions "git help" instead of
+   "man" when suggesting to read some documentation.
+
+ * "git repo struct" learned to take "-z" as a synonym to "--format=nul".
+
+ * More object database related information are shown in "git repo
+   structure" output.
+
+ * Improve the error message when a bad argument is given to the
+   `--onto` option of "git replay".  Test coverage of "git replay" has
+   been improved.
+
+ * The iconv library on macOS fails to correctly handle stateful
+   ISO/IEC 2022:1994 encoded strings.  Work it around instead of
+   replacing it wholesale from homebrew.
+
+ * Upstream symbolic link support on Windows from Git-for-Windows.
+
+
+Performance, Internal Implementation, Development Support etc.
+--------------------------------------------------------------
+
+ * The list of packfiles used in a running Git process is moved from
+   the packed_git structure into the packfile store.
+
+ * Some ref backend storage can hold not just the object name of an
+   annotated tag, but the object name of the object the tag points at.
+   The code to handle this information has been streamlined.
+
+ * As "git diff --quiet" only cares about the existence of any
+   changes, disable rename/copy detection to skip more expensive
+   processing whose result will be discarded anyway.
+
+ * A part of code paths that deals with loose objects has been cleaned
+   up.
+
+ * "make strip" has been taught to strip "scalar" as well as "git".
+
+ * Dockerized jobs at the GitHub Actions CI have been taught to show
+   more details of failed tests.
+
+ * Code refactoring around object database sources.
+
+ * Halve the memory consumed by artificial filepairs created during
+   "git diff --find-copies-harder", also making the operation run
+   faster.
+
+ * The "git_istream" abstraction has been revamped to make it easier
+   to interface with pluggable object database design.
+
+ * Rewrite the only use of "mktemp()" that is subject to TOCTOU race
+   and Stop using the insecure "mktemp()" function.
+   (merge 10bba537c4 rs/ban-mktemp later to maint).
+
+ * In-code comment update to clarify that single-letter options are
+   outside of the scope of command line completion script.
+   (merge dc8a00fafe jc/completion-no-single-letter-options later to maint).
+
+ * MEMZERO_ARRAY() helper is introduced to avoid clearing only the
+   first N bytes of an N-element array whose elements are larger than
+   a byte.
+
+ * "git diff-files -R --find-copies-harder" has been taught to use
+   the potential copy sources from the index correctly.
+
+ * Require C99 style flexible array member support from all platforms.
+
+ * The code path that enumerates promisor objects have been optimized
+   to skip pointlessly parsing blob objects.
+
+ * Prepare test suite for Git for Windows that supports symbolic
+   links.
+
+ * Import newer version of "clar", unit testing framework.
+   (merge 84071a6dea ps/clar-integers later to maint).
+
+ * The packfile_store data structure is moved from object store to odb
+   source.
+
+ * The object-info API has been cleaned up.
+
+ * Further preparation to upstream symbolic link support on Windows.
+
+ * Remove implicit reliance on the_repository global in the APIs
+   around tree objects and make it explicit which repository to work
+   in.
+
+ * "git bugreport" and "git version --build-options" learned to
+   include use of 'gettext' feature, to make it easier to diagnose
+   problems around l10n.
+
+ * Dscho observed that SVN tests are taking too much time in CI leak
+   checking tasks, but most time is spent not in our code but in libsvn
+   code (which happen to be written in Perl), whose leaks have little
+   value to discover for us.  Skip SVN, P4, and CVS tests in the leak
+   checking tasks.
+   (merge 047bd7dfe3 js/ci-leak-skip-svn later to maint).
+
+
+Fixes since v2.52
+-----------------
+
+ * Ever since we added whitespace rules for this project, we misspelt
+   an entry, which has been corrected.
+   (merge 358e94dc70 jc/gitattributes-whitespace-no-indent-fix later to maint).
+
+ * The code to expand attribute macros has been rewritten to avoid
+   recursion to avoid running out of stack space in an uncontrolled
+   way.
+   (merge 42ed046866 jk/attr-macroexpand-wo-recursion later to maint).
+
+ * Adding a repository that uses a different hash function is a no-no,
+   but "git submodule add" did not prevent it, which has been corrected.
+   (merge 6fe288bfbc bc/submodule-force-same-hash later to maint).
+
+ * An earlier check added to osx keychain credential helper to avoid
+   storing the credential itself supplied was overeager and rejected
+   credential material supplied by other helper backends that it would
+   have wanted to store, which has been corrected.
+   (merge 4580bcd235 kn/osxkeychain-idempotent-store-fix later to maint).
+
+ * The "git repo structure" subcommand tried to align its output but
+   mixed up byte count and display column width, which has been
+   corrected.
+   (merge 7a03a10a3a jx/repo-struct-utf8width-fix later to maint).
+
+ * Yet another corner case fix around renames in the "ort" merge
+   strategy.
+   (merge a562d90a35 en/ort-rename-another-fix later to maint).
+
+ * Test leakfix.
+   (merge 14b561e768 jk/test-mktemp-leakfix later to maint).
+
+ * Update a version of action used at the GitHub Actions CI.
+   (merge cd99203f86 js/ci-github-setup-go-update later to maint).
+
+ * The "return errno = EFOO, -1" construct, which is heavily used in
+   compat/mingw.c and triggers warnings under "-Wcomma", has been
+   rewritten to avoid the warnings.
+   (merge af3919816f js/mingw-assign-comma-fix later to maint).
+
+ * Makefile based build have recently been updated to build a
+   libgit.a that also has reftable and xdiff objects; CMake based
+   build procedure has been updated to match.
+   (merge b0d5c88cca js/cmake-libgit-fix later to maint).
+
+ * Under-allocation fix.
+   (merge d22a488482 js/wincred-get-credential-alloc-fix later to maint).
+
+ * "git worktree list" attempts to show paths to worktrees while
+   aligning them, but miscounted display columns for the paths when
+   non-ASCII characters were involved, which has been corrected.
+   (merge 08dfa59835 pw/worktree-list-display-width-fix later to maint).
+
+ * "Windows+meson" job at the GitHub Actions CI was hard to debug, as
+   it did not show and save failed test artifacts, which has been
+   corrected.
+   (merge 17bd1108ea jk/ci-windows-meson-test-fix later to maint).
+
+ * Emulation code clean-up.
+   (merge 2367c6bcd6 gf/win32-pthread-cond-wait-err later to maint).
+
+ * Various issues detected by Asan have been corrected.
+   (merge a031b6181a jk/asan-bonanza later to maint).
+
+ * "git config get --path" segfaulted on an ":(optional)path" that
+   does not exist, which has been corrected.
+   (merge 0bd16856ff jc/optional-path later to maint).
+
+ * The "--committer-date-is-author-date" option of "git am/rebase" is
+   a misguided one.  The documentation is updated to discourage its
+   use.
+   (merge fbf3d0669f kh/doc-committer-date-is-author-date later to maint).
+
+ * The option help text given by "git config unset -h" described
+   the "--all" option to "replace", not "unset", multiple variables,
+   which has been corrected.
+   (merge 18bf67b753 rs/config-unset-opthelp-fix later to maint).
+
+ * The error message given by "git config set", when the variable
+   being updated has more than one values defined, used old style "git
+   config" syntax with an incorrect option in its hint, both of which
+   have been corrected.
+   (merge df963f0df4 rs/config-set-multi-error-message-fix later to maint).
+
+ * "git replay" forgot to omit the "gpgsig-sha256" extended header
+   from the resulting commit the same way it omits "gpgsig", which has
+   been corrected.
+   (merge 9f3a115087 pw/replay-exclude-gpgsig-fix later to maint).
+
+ * A few tests have been updated to work under the shell compatible
+   mode of zsh.
+   (merge a92f243a94 bc/zsh-testsuite later to maint).
+
+ * The way patience diff finds LCS has been optimized.
+   (merge c7e3b8085b yc/xdiff-patience-optim later to maint).
+
+ * Recent optimization to "last-modified" command introduced use of
+   uninitialized block of memory, which has been corrected.
+   (merge fe4e60759b tc/last-modified-active-paths-optimization later to maint).
+
+ * "git last-modified" used to mishandle "--" to mark the beginning of
+   pathspec, which has been corrected.
+   (merge 05491b90ce js/last-modified-with-sparse-checkouts later to maint).
+
+ * Emulation code clean-up.
+   (merge 42aa7603aa gf/win32-pthread-cond-init later to maint).
+
+ * "git submodule add" to add a submodule under <name> segfaulted,
+   when a submodule.<name>.something is already in .gitmodules file
+   without defining where its submodule.<name>.path is, which has been
+   corrected.
+   (merge dd8e8c786e jc/submodule-add later to maint).
+
+ * "git fetch" that involves fetching tags, when a tag being fetched
+   needs to overwrite existing one, failed to fetch other tags, which
+   has been corrected.
+   (merge b7b17ec8a6 kn/fix-fetch-backfill-tag-with-batched-ref-updates later to maint).
+
+ * Document "rev-list --filter-provided-objects" better.
+   (merge 6d8dc99478 jt/doc-rev-list-filter-provided-objects later to maint).
+
+ * Even when there is no changes in the packfile and no need to
+   recompute bitmaps, "git repack" recomputed and updated the MIDX
+   file, which has been corrected.
+   (merge 6ce9d558ce ps/repack-avoid-noop-midx-rewrite later to maint).
+
+ * Update HTTP tests to adjust for changes in curl 8.18.0
+   (merge 17f4b01da7 jk/test-curl-updates later to maint).
+
+ * Workaround the "iconv" shipped as part of macOS, which is broken
+   handling stateful ISO/IEC 2022 encoded strings.
+   (merge cee341e9dd rs/macos-iconv-workaround later to maint).
+
+ * Running "git diff" with "--name-only" and other options that allows
+   us not to look at the blob contents, while objects that are lazily
+   fetched from a promisor remote, caused use-after-free, which has
+   been corrected.
+
+ * The ort merge machinery hit an assertion failure in a history with
+   criss-cross merges renamed a directory and a non-directory, which
+   has been corrected.
+   (merge 979ee83e8a en/ort-recursive-d-f-conflict-fix later to maint).
+
+ * Diagnose invalid bundle-URI that lack the URI entry, instead of
+   crashing.
+   (merge 7796c14a1a sb/bundle-uri-without-uri later to maint).
+
+ * Mailmap update for Karsten
+   (merge e97678c4ef js/mailmap-karsten-blees later to maint).
+
+ * Perf-test fixes.
+   (merge 79d301c767 jk/t-perf-fixes later to maint).
+
+ * Fix for a performance regression in "git cat-file".
+   (merge 9e8b448dd8 jk/cat-file-avoid-bitmap-when-unneeded later to maint).
+
+ * Update a FAQ entry on synching two separate repositories using the
+   "git stash export/import" recently introduced.
+   (merge 02fc44a989 bc/doc-stash-import-export later to maint).
+
+ * "git fsck" used inconsistent set of refs to show a confused
+   warning, which has been corrected.
+
+ * Some error messages from the http transport layer lacked the
+   terminating newline, which has been corrected.
+   (merge a8227ae8d5 kt/http-backend-errors later to maint).
+
+ * "git repack --geometric" did not work with promisor packs, which
+   has been corrected.
+
+ * The logic that avoids reusing MIDX files with a wrong checksum was
+   broken, which has been corrected.
+
+ * Other code cleanup, docfix, build fix, etc.
+   (merge 46207a54cc qj/doc-http-bad-want-response later to maint).
+   (merge df90eccd93 kh/doc-commit-extra-references later to maint).
+   (merge f18aa68861 rs/xmkstemp-simplify later to maint).
+   (merge fddba8f737 ja/doc-synopsis-style later to maint).
+   (merge 22ce0cb639 en/xdiff-cleanup-2 later to maint).
+   (merge 8ef7355a8f je/doc-pull later to maint).
+   (merge 48176f953f jc/capability-leak later to maint).
+   (merge 8cbbdc92f7 kh/doc-pre-commit-fix later to maint).
+   (merge d4bc39a4d9 mh/doc-config-gui-gcwarning later to maint).
+   (merge 41d425008a kh/doc-send-email-paragraph-fix later to maint).
+   (merge d4b732899e jc/macports-darwinports later to maint).
+   (merge bab391761d kj/pull-options-decl-cleanup later to maint).
+   (merge 007b8994d4 rs/t4014-git-version-string-fix later to maint).
+   (merge 4ce170c522 ds/doc-scalar-config later to maint).
+   (merge a0c813951a jc/doc-commit-signoff-config later to maint).
+   (merge 8ee262985a ja/doc-misc-fixes later to maint).
+   (merge 1722c2244b mh/doc-core-attributesfile later to maint).
+   (merge c469ca26c5 dk/ci-rust-fix later to maint).
+   (merge 12f0be0857 gf/clear-path-cache-cleanup later to maint).
+   (merge 949df6ed6b js/test-func-comment-fix later to maint).
+   (merge 93f894c001 bc/checkout-error-message-fix later to maint).
+   (merge abf05d856f rs/show-branch-prio-queue later to maint).
+   (merge 06188ea5f3 rs/parse-config-expiry-simplify later to maint).
+   (merge 861dbb1586 dd/t5403-modernise later to maint).
+   (merge acffc5e9e5 ja/doc-synopsis-style-more later to maint).
+   (merge 6c5c7e7071 ac/t1420-use-more-direct-check later to maint).
+   (merge 2ac93bfcbc ds/builtin-doc-update later to maint).
+   (merge 3f051fc9c9 kh/doc-patch-id later to maint).
+   (merge 555c8464e5 je/doc-reset later to maint).
+   (merge 220f888d7e ps/t1410-cleanup later to maint).
+   (merge 5814b04c02 ps/config-doc-get-urlmatch-fix later to maint).
+   (merge 5ae594f30b sb/doc-update-ref-markup-fix later to maint).
+   (merge bc8556d066 ty/t1005-test-path-is-helpers later to maint).
diff --git a/Documentation/RelNotes/2.54.0.adoc b/Documentation/RelNotes/2.54.0.adoc
new file mode 100644
index 0000000..005a98e
--- /dev/null
+++ b/Documentation/RelNotes/2.54.0.adoc
@@ -0,0 +1,340 @@
+Git v2.54 Release Notes
+=======================
+
+UI, Workflows & Features
+------------------------
+
+ * "git add -p" and friends note what the current status of the hunk
+   being shown is.
+
+ * "git history" history rewriting (experimental) command has been
+   added.
+
+ * "git replay" is taught to drop commits that become empty (not the
+   ones that are empty in the original).
+
+ * The help text and the documentation for the "--expire" option of
+   "git worktree [list|prune]" have been improved.
+
+ * When "git show-index" is run outside a repository, it silently
+   defaults to SHA-1; the tool now warns when this happens.
+
+ * "git merge-file" can be run outside a repository, but it ignored
+   all configuration, even the per-user ones.  The command now uses
+   available configuration files to find its customization.
+
+ * "auto filter" logic for large-object promisor remote.
+
+ * "git rev-list" and friends learn "--maximal-only" to show only the
+   commits that are not reachable by other commits.
+
+ * Command line completion (in contrib/) update for
+   "stash import/export".
+
+ * "git repo info" learns "--keys" action to list known keys.
+
+ * Extend the alias configuration syntax to allow aliases using
+   characters outside ASCII alphanumeric (plus '-').
+
+ * A signature on a commit that was GPG signed long time ago ought to
+   be still valid after the key that was used to sign it has expired,
+   but we showed them in alarming red.
+
+ * "git subtree split --prefix=P <commit>" now checks the prefix P
+   against the tree of the (potentially quite different from the
+   current working tree) given commit.
+
+ * "git add -p" learned a new mode that allows the user to revisit a
+   file that was already dealt with.
+
+ * Allow the directory in which reference backends store their data to
+   be specified.
+
+ * "gitweb" has been taught to be mobile friendly.
+
+ * "git apply --directory=./un/../normalized/path" now normalizes the
+   given path before using it.
+
+ * "git maintenance" starts using the "geometric" strategy by default.
+
+ * "git config list" is taught to show the values interpreted for
+   specific type with "--type=<X>" option.
+
+ * "git add <submodule>" has been taught to honor
+   submodule.<name>.ignore that is set to "all" (and requires "git add
+   -f" to override it).
+
+ * Hook commands are now allowed to be defined (possibly centrally)
+   in the configuration files, and run multiple of them for the same
+   hook event.
+
+ * The way end-users can add their own "git <cmd>" subcommand by
+   storing "git-<cmd>" in a directory on their $PATH has not been
+   documented clearly, which has been corrected.
+
+ * "git send-email" learns to pass hostname/port to Authen::SASL
+   module.
+
+ * "git send-email" learns to support use of client-side certificates.
+
+ * "git send-email" has learned to be a bit more careful when it
+   accepts charset to use from the end-user, to avoid 'y' (mistaken
+   'yes' when expecting a charset like 'UTF-8') and other nonsense.
+
+ * "git status" learned to show comparison between the current branch
+   and various other branches listed on status.compareBranches
+   configuration.
+
+
+Performance, Internal Implementation, Development Support etc.
+--------------------------------------------------------------
+
+ * Avoid local submodule repository directory paths overlapping with
+   each other by encoding submodule names before using them as path
+   components.
+
+ * The string_list API gains a new helper, string_list_sort_u(), and
+   new unit tests to extend coverage.
+
+ * Improve set-up time of a perf test.
+
+ * ISO C23 redefines strchr and friends that tradiotionally took
+   a const pointer and returned a non-const pointer derived from it to
+   preserve constness (i.e., if you ask for a substring in a const
+   string, you get a const pointer to the substring).  Update code
+   paths that used non-const pointer to receive their results that did
+   not have to be non-const to adjust.
+
+ * Rename three functions around the commit_list data structure.
+
+ * Transaction to create objects (or not) is currently tied to the
+   repository, but in the future a repository can have multiple object
+   sources, which may have different transaction mechanisms.  Make the
+   odb transaction API per object source.
+
+ * "git merge-ours" is taught to work better in a sparse checkout.
+
+ * Allow recording process ID of the process that holds the lock next
+   to a lockfile for diagnosis.
+
+ * Reduce dependency on the_repository of xdiff-interface layer.
+
+ * Code clean-up to use the commit_stack API.
+
+ * "git diff --anchored=<text>" has been optimized.
+
+ * A CodingGuidelines update.
+
+ * Add process ancestry data to trace2 on macOS to match what we
+   already do on Linux and Windows.  Also adjust the way Windows
+   implementation reports this information to match the other two.
+
+ * A handful of places used refs_for_each_ref_in() API incorrectly,
+   which has been corrected.
+
+ * Some tests assumed "iconv" is available without honoring ICONV
+   prerequisite, which has been corrected.
+
+ * Revamp object enumeration API around odb.
+
+ * Additional tests were introduced to see the interaction with netrc
+   auth with auth failure on the http transport.
+
+ * A couple of bugs in use of flag bits around odb API has been
+   corrected, and the flag bits reordered.
+
+ * Plumb gitk/git-gui build and install procedure in meson based
+   builds.
+
+ * The code to accept shallow "git push" has been optimized.
+
+ * Simplify build procedure for oxskeychain (in contrib/).
+
+ * Fix dependency screw-up in meson-based builds.
+
+ * Wean the mailmap code off of the_repository dependency.
+
+ * API clean-up for the worktree subsystem.
+
+ * The last uses of the_repository in "tree-diff.c" have been
+   eradicated.
+
+ * Clean-up the code around "git repo info" command.
+
+ * Mark the merge-ort codebase to prevent more uses of the_repository
+   from getting added.
+
+ * The core.attributesfile is intended to be set per repository, but
+   were kept track of by a single global variable in-core, which has
+   been corrected by moving it to per-repository data structure.
+
+ * Use the hook API to replace ad-hoc invocation of hook scripts via
+   the run_command() API.
+
+ * Code refactoring around refs-for-each-* API functions.
+
+ * The parse-options API learned to notice an options[] array with
+   duplicated long options.
+   (merge 237e520d81 rs/parse-options-duplicated-long-options later to maint).
+
+ * The code to maintain mapping between object names in multiple hash
+   functions is being added, written in Rust.
+
+ * A bit of OIDmap API enhancement and cleanup.
+
+ * Move gitlab CI from macOS 14 images that are being deprecated.
+
+ * The object source API is getting restructured to allow plugging new
+   backends.
+
+
+Fixes since v2.53
+-----------------
+
+ * HTTP transport failed to authenticate in some code paths, which has
+   been corrected.
+   (merge ed0f7a62f7 ap/http-probe-rpc-use-auth later to maint).
+
+ * The computation of column width made by "git diff --stat" was
+   confused when pathnames contain non-ASCII characters.
+   (merge 04f5d95ef7 lp/diff-stat-utf8-display-width-fix later to maint).
+
+ * The "-z" and "--max-depth" documentation (and implementation of
+   "-z") in the "git last-modified" command have been updated.
+   (merge 9dcc09bed1 tc/last-modified-options-cleanup later to maint).
+
+ * A handful of code paths that started using batched ref update API
+   (after Git 2.51 or so) lost detailed error output, which have been
+   corrected.
+   (merge eff9299eac kn/ref-batch-output-error-reporting-fix later to maint).
+
+ * "git blame --ignore-revs=... --color-lines" did not account for
+   ignored revisions passing blame to the same commit an adjacent line
+   gets blamed for.
+   (merge d519082d4e rs/blame-ignore-colors-fix later to maint).
+
+ * Coccinelle rules update.
+   (merge 60614838a4 tc/memzero-array later to maint).
+
+ * Giving "git last-modified" a tree (not a commit-ish) died an
+   uncontrolled death, which has been corrected.
+   (merge 525ef52301 tc/last-modified-not-a-tree later to maint).
+
+ * Test contrib/ things in CI to catch breakages before they enter the
+   "next" branch.
+   (merge c591c3ceff jc/ci-test-contrib-too later to maint).
+
+ * A handful of documentation pages have been modernized to use the
+   "synopsis" style.
+   (merge a34d1d53a6 ja/doc-synopsis-style-even-more later to maint).
+
+ * Small clean-up of xdiff library to remove unnecessary data
+   duplication.
+   (merge 5086213bd2 pw/xdiff-cleanups later to maint).
+
+ * Update sample commit-msg hook to complain when a log message has
+   material mailinfo considers the end of log message in the middle.
+   (merge 83804c361b pw/commit-msg-sample-hook later to maint).
+
+ * "git pack-objects --stdin-packs" with "--exclude-promisor-objects"
+   fetched objects that are promised, which was not wanted.  This has
+   been fixed.
+   (merge f4eff7116d ps/pack-concat-wo-backfill later to maint).
+
+ * "git switch <name>", in an attempt to create a local branch <name>
+   after a remote tracking branch of the same name gave an advise
+   message to disambiguate using "git checkout", which has been
+   updated to use "git switch".
+   (merge 12fee11f21 jc/checkout-switch-restore later to maint).
+
+ * It does not make much sense to apply the "incomplete-line"
+   whitespace rule to symbolic links, whose contents almost always
+   lack the final newline.  "git apply" and "git diff" are now taught
+   to exclude them for a change to symbolic links.
+   (merge 6a41481c6d jc/whitespace-incomplete-line later to maint).
+
+ * "git format-patch --from=<me>" did not honor the command line
+   option when writing out the cover letter, which has been corrected.
+
+ * Update build precedure for mergetool documentation in meson-based builds.
+   (merge 58e4eeeeb5 pw/meson-doc-mergetool later to maint).
+
+ * An earlier attempt to optimize "git subtree" discarded too much
+   relevant histories, which has been corrected.
+
+ * A prefetch call can be triggered to access a stale diff_queue entry
+   after diffcore-break breaks a filepair into two and freed the
+   original entry that is no longer used, leading to a segfault, which
+   has been corrected.
+   (merge 2d88ab078d hy/diff-lazy-fetch-with-break-fix later to maint).
+
+ * "git fetch --deepen" that tries to go beyond merged branch used to
+   get confused where the updated shallow points are, which has been
+   corrected.
+   (merge 3ef68ff40e sp/shallow-deepen-relative-fix later to maint).
+
+ * "fsck" iterates over packfiles and its access to pack data caused
+   the list to be permuted, which caused it to loop forever; the code
+   to access pack data by "fsck" has been updated to avoid this.
+   (merge 13eb65d366 ps/fsck-stream-from-the-right-object-instance later to maint).
+
+ * "git log --graph --stat" did not count the display width of colored
+   graph part of its own output correctly, which has been corrected.
+   (merge 064b869efc lp/diff-stat-utf8-display-width-fix later to maint).
+
+ * The configuration variable format.noprefix did not behave as a
+   proper boolean variable, which has now been fixed and documented.
+   (merge ea3a62c40e kh/format-patch-noprefix-is-boolean later to maint).
+
+ * CI fix.
+   (merge eb35167dd4 ps/ci-reduce-gitlab-envsize later to maint).
+
+ * "git diff --no-index --find-object=<object-name>" outside a
+   repository of course wouldn't be able to find the object and died
+   while parsing the command line.  The command is made to die in a
+   bit more user-friendly way.
+   (merge b0ddc7947c mm/diff-no-index-find-object later to maint).
+
+ * Fix typo-induced breakages in fsmonitor-watchman sample hook.
+   (merge 41366e4677 pt/fsmonitor-watchman-sample-fix later to maint).
+
+ * "git for-each-repo" started from a secondary worktree did not work
+   as expected, which has been corrected.
+   (merge e87493b9b4 ds/for-each-repo-w-worktree later to maint).
+ * Other code cleanup, docfix, build fix, etc.
+   (merge d79fff4a11 jk/remote-tracking-ref-leakfix later to maint).
+   (merge 7a747f972d dd/t5403-modernise later to maint).
+   (merge 81021871ea sp/myfirstcontribution-include-update later to maint).
+   (merge 49223593fd ac/sparse-checkout-string-list-cleanup later to maint).
+   (merge a824421d36 sp/t5500-cleanup later to maint).
+   (merge df1c5d7ed7 kh/doc-shortlog-fix later to maint).
+   (merge 2d45507f15 am/doc-github-contributiong-link-to-submittingpatches later to maint).
+   (merge 68060b9262 hs/t9160-test-paths later to maint).
+   (merge 486386c687 cs/subtree-reftable-testfix later to maint).
+   (merge 0728012c53 jc/diff-highlight-main-master-testfix later to maint).
+   (merge 831989ef38 mc/doc-send-email-signed-off-by-cc later to maint).
+   (merge c44b3f3203 sd/doc-my1c-api-config-reference-fix later to maint).
+   (merge 6c21e53bad rs/version-wo-the-repository later to maint).
+   (merge 10c68d2577 rs/clean-includes later to maint).
+   (merge 168d575719 bk/t2003-modernise later to maint).
+   (merge 6bfef81c9a kh/doc-rerere-options-xref later to maint).
+   (merge aaf3cc3d8d sd/t7003-test-path-is-helpers later to maint).
+   (merge 2668b6bdc4 jc/doc-rerere-update later to maint).
+   (merge 2f99f50f2d jc/doc-cg-c-comment later to maint).
+   (merge a454cdca42 kh/doc-am-format-sendmail later to maint).
+   (merge 8b0061b5c5 jk/ref-filter-lrstrip-optim later to maint).
+   (merge 5133837392 ps/ci-gitlab-msvc-updates later to maint).
+   (merge 143e84958c db/doc-fetch-jobs-auto later to maint).
+   (merge 0678e01f02 ap/use-test-seq-f-more later to maint).
+   (merge 96286f14b0 ty/symlinks-use-unsigned-for-bitset later to maint).
+   (merge b10e0cb1f3 kh/doc-am-xref later to maint).
+   (merge ed84bc1c0d kh/doc-patch-id-4 later to maint).
+   (merge 7451864bfa sc/pack-redundant-leakfix later to maint).
+   (merge f87593ab1a cx/fetch-display-ubfix later to maint).
+   (merge a66c8c7f91 jk/repo-structure-cleanup later to maint).
+   (merge 5ee8782f87 ss/test-that-that-typofix later to maint).
+   (merge f31b322008 fp/t3310-test-path-is-helpers later to maint).
+   (merge b22ed4c4f9 kj/path-micro-code-cleanup later to maint).
+   (merge a56fa1ca05 lp/doc-gitprotocol-pack-fixes later to maint).
+   (merge 0d6bb8b541 ss/t3700-modernize later to maint).
+   (merge 63c00a677b ss/t9123-setup-inside-test-expect-success later to maint).
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index e270ccb..359f5fb 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -37,11 +37,24 @@
 they have no obligation to help you (i.e. you ask them for help,
 you don't demand).  +git log -p {litdd} _$area_you_are_modifying_+ would
 help you find out who they are.
++
+It is also a good idea to check whether your topic has been discussed
+previously on the mailing list, or whether similar work is already in
+progress.  Prior discussions may contain useful context, design
+considerations, or earlier attempts at solving the same problem. Being
+aware of such discussions can help you avoid duplicating work and may
+allow you to coordinate with other contributors working in the same
+area.
 
 . You get comments and suggestions for improvements.  You may even get
   them in an "on top of your change" patch form.  You are expected to
   respond to them with "Reply-All" on the mailing list, while taking
   them into account while preparing an updated set of patches.
++
+It is often beneficial to allow some time for reviewers to provide
+feedback before sending a new version, rather than sending an updated
+series immediately after receiving a review. This helps collect broader
+input and avoids unnecessary churn from many rapid iterations.
 
 . Polish, refine, and re-send your patches to the list and to the people
   who spent their time to improve your patch.  Go back to step (2).
diff --git a/Documentation/asciidoc.conf.in b/Documentation/asciidoc.conf.in
index ff9ea0a..31b883a 100644
--- a/Documentation/asciidoc.conf.in
+++ b/Documentation/asciidoc.conf.in
@@ -81,12 +81,18 @@
 
 ifdef::backend-docbook[]
 ifdef::doctype-manpage[]
+[blockdef-open]
+synopsis-style=template="verseparagraph",filter="sed 's!&#8230;\\(\\]\\|$\\)!<phrase>\\0</phrase>!g;s!\\([\\[ |()]\\|^\\|\\]\\|&gt;\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|&#8230;\\)!\\1<literal>\\2</literal>!g;s!&lt;[-a-zA-Z0-9.]\\+&gt;!<emphasis>\\0</emphasis>!g'"
+
 [paradef-default]
 synopsis-style=template="verseparagraph",filter="sed 's!&#8230;\\(\\]\\|$\\)!<phrase>\\0</phrase>!g;s!\\([\\[ |()]\\|^\\|\\]\\|&gt;\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|&#8230;\\)!\\1<literal>\\2</literal>!g;s!&lt;[-a-zA-Z0-9.]\\+&gt;!<emphasis>\\0</emphasis>!g'"
 endif::doctype-manpage[]
 endif::backend-docbook[]
 
 ifdef::backend-xhtml11[]
+[blockdef-open]
+synopsis-style=template="verseparagraph",filter="sed 's!&#8230;\\(\\]\\|$\\)!<span>\\0</span>!g;s!\\([\\[ |()]\\|^\\|\\]\\|&gt;\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|&#8230;\\)!\\1<code>\\2</code>!g;s!&lt;[-a-zA-Z0-9.]\\+&gt;!<em>\\0</em>!g'"
+
 [paradef-default]
 synopsis-style=template="verseparagraph",filter="sed 's!&#8230;\\(\\]\\|$\\)!<span>\\0</span>!g;s!\\([\\[ |()]\\|^\\|\\]\\|&gt;\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|&#8230;\\)!\\1<code>\\2</code>!g;s!&lt;[-a-zA-Z0-9.]\\+&gt;!<em>\\0</em>!g'"
 endif::backend-xhtml11[]
diff --git a/Documentation/blame-options.adoc b/Documentation/blame-options.adoc
index 1fb948f..1ae1222 100644
--- a/Documentation/blame-options.adoc
+++ b/Documentation/blame-options.adoc
@@ -1,105 +1,105 @@
--b::
+`-b`::
 	Show blank SHA-1 for boundary commits.  This can also
 	be controlled via the `blame.blankBoundary` config option.
 
---root::
+`--root`::
 	Do not treat root commits as boundaries.  This can also be
 	controlled via the `blame.showRoot` config option.
 
---show-stats::
+`--show-stats`::
 	Include additional statistics at the end of blame output.
 
--L <start>,<end>::
--L :<funcname>::
-	Annotate only the line range given by '<start>,<end>',
-	or by the function name regex '<funcname>'.
+`-L <start>,<end>`::
+`-L :<funcname>`::
+	Annotate only the line range given by `<start>,<end>`,
+	or by the function name regex _<funcname>_.
 	May be specified multiple times. Overlapping ranges are allowed.
 +
-'<start>' and '<end>' are optional. `-L <start>` or `-L <start>,` spans from
-'<start>' to end of file. `-L ,<end>` spans from start of file to '<end>'.
+_<start>_ and _<end>_ are optional. `-L <start>` or `-L <start>,` spans from
+_<start>_ to end of file. `-L ,<end>` spans from start of file to _<end>_.
 +
 include::line-range-format.adoc[]
 
--l::
+`-l`::
 	Show long rev (Default: off).
 
--t::
+`-t`::
 	Show raw timestamp (Default: off).
 
--S <revs-file>::
-	Use revisions from revs-file instead of calling linkgit:git-rev-list[1].
+`-S <revs-file>`::
+	Use revisions from _<revs-file>_ instead of calling
+	linkgit:git-rev-list[1].
 
---reverse <rev>..<rev>::
+`--reverse <start>..<end>`::
 	Walk history forward instead of backward. Instead of showing
 	the revision in which a line appeared, this shows the last
 	revision in which a line has existed. This requires a range of
-	revision like START..END where the path to blame exists in
-	START.  `git blame --reverse START` is taken as `git blame
-	--reverse START..HEAD` for convenience.
+	revision like `<start>..<end>` where the path to blame exists in
+	_<start>_.  `git blame --reverse <start>` is taken as `git blame
+	--reverse <start>..HEAD` for convenience.
 
---first-parent::
+`--first-parent`::
 	Follow only the first parent commit upon seeing a merge
 	commit. This option can be used to determine when a line
 	was introduced to a particular integration branch, rather
 	than when it was introduced to the history overall.
 
--p::
---porcelain::
+`-p`::
+`--porcelain`::
 	Show in a format designed for machine consumption.
 
---line-porcelain::
+`--line-porcelain`::
 	Show the porcelain format, but output commit information for
 	each line, not just the first time a commit is referenced.
-	Implies --porcelain.
+	Implies `--porcelain`.
 
---incremental::
+`--incremental`::
 	Show the result incrementally in a format designed for
 	machine consumption.
 
---encoding=<encoding>::
-	Specifies the encoding used to output author names
+`--encoding=<encoding>`::
+	Specify the encoding used to output author names
 	and commit summaries. Setting it to `none` makes blame
 	output unconverted data. For more information see the
 	discussion about encoding in the linkgit:git-log[1]
 	manual page.
 
---contents <file>::
-	Annotate using the contents from the named file, starting from <rev>
-	if it is specified, and HEAD otherwise. You may specify '-' to make
+`--contents <file>`::
+	Annotate using the contents from _<file>_, starting from _<rev>_
+	if it is specified, and `HEAD` otherwise. You may specify `-` to make
 	the command read from the standard input for the file contents.
 
---date <format>::
-	Specifies the format used to output dates. If --date is not
-	provided, the value of the blame.date config variable is
-	used. If the blame.date config variable is also not set, the
+`--date <format>`::
+	Specify the format used to output dates. If `--date` is not
+	provided, the value of the `blame.date` config variable is
+	used. If the `blame.date` config variable is also not set, the
 	iso format is used. For supported values, see the discussion
-	of the --date option at linkgit:git-log[1].
+	of the `--date` option at linkgit:git-log[1].
 
---progress::
---no-progress::
-	Progress status is reported on the standard error stream
-	by default when it is attached to a terminal. This flag
-	enables progress reporting even if not attached to a
-	terminal. Can't use `--progress` together with `--porcelain`
-	or `--incremental`.
+`--progress`::
+`--no-progress`::
+	Enable progress reporting on the standard error stream even if
+	not attached to a terminal. By default, progress status is
+	reported only when it is attached. You can't use `--progress`
+	together with `--porcelain` or `--incremental`.
 
--M[<num>]::
+`-M[<num>]`::
 	Detect moved or copied lines within a file. When a commit
 	moves or copies a block of lines (e.g. the original file
-	has A and then B, and the commit changes it to B and then
-	A), the traditional 'blame' algorithm notices only half of
+	has _A_ and then _B_, and the commit changes it to _B_ and then
+	_A_), the traditional `blame` algorithm notices only half of
 	the movement and typically blames the lines that were moved
-	up (i.e. B) to the parent and assigns blame to the lines that
-	were moved down (i.e. A) to the child commit.  With this
+	up (i.e. _B_) to the parent and assigns blame to the lines that
+	were moved down (i.e. _A_) to the child commit.  With this
 	option, both groups of lines are blamed on the parent by
 	running extra passes of inspection.
 +
-<num> is optional but it is the lower bound on the number of
+_<num>_ is optional, but it is the lower bound on the number of
 alphanumeric characters that Git must detect as moving/copying
 within a file for it to associate those lines with the parent
 commit. The default value is 20.
 
--C[<num>]::
+`-C[<num>]`::
 	In addition to `-M`, detect lines moved or copied from other
 	files that were modified in the same commit.  This is
 	useful when you reorganize your program and move code
@@ -109,14 +109,14 @@
 	option is given three times, the command additionally
 	looks for copies from other files in any commit.
 +
-<num> is optional but it is the lower bound on the number of
+_<num>_ is optional, but it is the lower bound on the number of
 alphanumeric characters that Git must detect as moving/copying
 between files for it to associate those lines with the parent
 commit. And the default value is 40. If there are more than one
-`-C` options given, the <num> argument of the last `-C` will
+`-C` options given, the _<num>_ argument of the last `-C` will
 take effect.
 
---ignore-rev <rev>::
+`--ignore-rev <rev>`::
 	Ignore changes made by the revision when assigning blame, as if the
 	change never happened.  Lines that were changed or added by an ignored
 	commit will be blamed on the previous commit that changed that line or
@@ -126,26 +126,26 @@
 	another commit will be marked with a `?` in the blame output.  If the
 	`blame.markUnblamableLines` config option is set, then those lines touched
 	by an ignored commit that we could not attribute to another revision are
-	marked with a '*'. In the porcelain modes, we print 'ignored' and
-	'unblamable' on a newline respectively.
+	marked with a `*`. In the porcelain modes, we print `ignored` and
+	`unblamable` on a newline respectively.
 
---ignore-revs-file <file>::
-	Ignore revisions listed in `file`, which must be in the same format as an
+`--ignore-revs-file <file>`::
+	Ignore revisions listed in _<file>_, which must be in the same format as an
 	`fsck.skipList`.  This option may be repeated, and these files will be
 	processed after any files specified with the `blame.ignoreRevsFile` config
 	option.  An empty file name, `""`, will clear the list of revs from
 	previously processed files.
 
---color-lines::
+`--color-lines`::
 	Color line annotations in the default format differently if they come from
 	the same commit as the preceding line. This makes it easier to distinguish
 	code blocks introduced by different commits. The color defaults to cyan and
 	can be adjusted using the `color.blame.repeatedLines` config option.
 
---color-by-age::
-	Color line annotations depending on the age of the line in the default format.
-	The `color.blame.highlightRecent` config option controls what color is used for
-	each range of age.
+`--color-by-age`::
+	Color line annotations depending on the age of the line in
+	the default format.  The `color.blame.highlightRecent` config
+	option controls what color is used for each range of age.
 
--h::
+`-h`::
 	Show help message.
diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc
index 80ce17d..dc6ca0e 100644
--- a/Documentation/config/alias.adoc
+++ b/Documentation/config/alias.adoc
@@ -1,12 +1,47 @@
 alias.*::
-	Command aliases for the linkgit:git[1] command wrapper - e.g.
-	after defining `alias.last = cat-file commit HEAD`, the invocation
-	`git last` is equivalent to `git cat-file commit HEAD`. To avoid
-	confusion and troubles with script usage, aliases that
-	hide existing Git commands are ignored except for deprecated
-	commands.  Arguments are split by
-	spaces, the usual shell quoting and escaping are supported.
-	A quote pair or a backslash can be used to quote them.
+alias.*.command::
+	Command aliases for the linkgit:git[1] command wrapper. Aliases
+	can be defined using two syntaxes:
++
+--
+1. Without a subsection, e.g., `[alias] co = checkout`. The alias
+   name ("co" in this example) is
+   limited to ASCII alphanumeric characters and `-`,
+   and is matched case-insensitively.
+2. With a subsection, e.g., `[alias "co"] command = checkout`. The
+   alias name can contain any characters (except for newlines and NUL bytes),
+   including UTF-8, and is matched case-sensitively as raw bytes.
+   You define the action of the alias in the `command`.
+--
++
+Examples:
++
+----
+# Without subsection (ASCII alphanumeric and dash only)
+[alias]
+    co = checkout
+    st = status
+
+# With subsection (allows any characters, including UTF-8)
+[alias "hämta"]
+    command = fetch
+[alias "rätta till"]
+    command = commit --amend
+----
++
+With a Git alias defined, e.g.,
++
+    $ git config --global alias.last "cat-file commit HEAD"
+    # Which is equivalent to
+    $ git config --global alias.last.command "cat-file commit HEAD"
++
+`git last` is equivalent to `git cat-file commit HEAD`.
++
+To avoid confusion and troubles with script usage, aliases that
+hide existing Git commands are ignored except for deprecated
+commands.  Arguments are split by
+spaces, the usual shell quoting and escaping are supported.
+A quote pair or a backslash can be used to quote them.
 +
 Note that the first word of an alias does not necessarily have to be a
 command. It can be a command-line option that will be passed into the
diff --git a/Documentation/config/am.adoc b/Documentation/config/am.adoc
index 5bcad2e..e9561e1 100644
--- a/Documentation/config/am.adoc
+++ b/Documentation/config/am.adoc
@@ -1,14 +1,20 @@
 am.keepcr::
-	If true, git-am will call git-mailsplit for patches in mbox format
-	with parameter `--keep-cr`. In this case git-mailsplit will
+	If true, linkgit:git-am[1] will call linkgit:git-mailsplit[1]
+	for patches in mbox format with parameter `--keep-cr`. In this
+	case linkgit:git-mailsplit[1] will
 	not remove `\r` from lines ending with `\r\n`. Can be overridden
 	by giving `--no-keep-cr` from the command line.
-	See linkgit:git-am[1], linkgit:git-mailsplit[1].
 
 am.threeWay::
-	By default, `git am` will fail if the patch does not apply cleanly. When
-	set to true, this setting tells `git am` to fall back on 3-way merge if
-	the patch records the identity of blobs it is supposed to apply to and
-	we have those blobs available locally (equivalent to giving the `--3way`
-	option from the command line). Defaults to `false`.
-	See linkgit:git-am[1].
+	By default, linkgit:git-am[1] will fail if the patch does not
+	apply cleanly. When set to true, this setting tells
+	linkgit:git-am[1] to fall back on 3-way merge if the patch
+	records the identity of blobs it is supposed to apply to and we
+	have those blobs available locally (equivalent to giving the
+	`--3way` option from the command line). Defaults to `false`.
+
+am.messageId::
+	Add a `Message-ID` trailer based on the email header to the
+	commit when using linkgit:git-am[1] (see
+	linkgit:git-interpret-trailers[1]). See also the `--message-id`
+	and `--no-message-id` options.
diff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc
index 11efad1..a0ebf03 100644
--- a/Documentation/config/core.adoc
+++ b/Documentation/config/core.adoc
@@ -348,6 +348,17 @@
 read-only snapshot of the same index to a location different from the
 repository's usual working tree).
 
+core.lockfilePid::
+	If true, Git will create a PID file alongside lock files. When a
+	lock acquisition fails and a PID file exists, Git can provide
+	additional diagnostic information about the process holding the
+	lock, including whether it is still running. Defaults to `false`.
++
+The PID file is named by inserting `~pid` before the `.lock` suffix.
+For example, if the lock file is `index.lock`, the PID file will be
+`index~pid.lock`. The file contains a single line in the format
+`pid <value>` followed by a newline.
+
 core.logAllRefUpdates::
 	Enable the reflog. Updates to a ref <ref> is logged to the file
 	"`$GIT_DIR/logs/<ref>`", by appending the new and old
@@ -492,10 +503,9 @@
 	command-line argument and write the password on its STDOUT.
 
 core.attributesFile::
-	In addition to `.gitattributes` (per-directory) and
-	`.git/info/attributes`, Git looks into this file for attributes
-	(see linkgit:gitattributes[5]). Path expansions are made the same
-	way as for `core.excludesFile`. Its default value is
+	Specifies the pathname to the file that contains attributes (see
+	linkgit:gitattributes[5]), in addition to `.gitattributes` (per-directory)
+	and `.git/info/attributes`. Its default value is
 	`$XDG_CONFIG_HOME/git/attributes`. If `$XDG_CONFIG_HOME` is either not
 	set or empty, `$HOME/.config/git/attributes` is used instead.
 
@@ -629,6 +639,8 @@
   part of the line terminator, i.e. with it, `trailing-space`
   does not trigger if the character before such a carriage-return
   is not a whitespace (not enabled by default).
+* `incomplete-line` treats the last line of a file that is missing the
+  newline at the end as an error (not enabled by default).
 * `tabwidth=<n>` tells how many character positions a tab occupies; this
   is relevant for `indent-with-non-tab` and when Git fixes `tab-in-indent`
   errors. The default tab width is 8. Allowed values are 1 to 63.
diff --git a/Documentation/config/extensions.adoc b/Documentation/config/extensions.adoc
index 5324566..be6678b 100644
--- a/Documentation/config/extensions.adoc
+++ b/Documentation/config/extensions.adoc
@@ -57,10 +57,24 @@
 `core.repositoryFormatVersion` setting.
 
 refStorage:::
-	Specify the ref storage format to use. The acceptable values are:
+	Specify the ref storage format and a corresponding payload. The value
+	can be either a format name or a URI:
 +
 --
+* A format name alone (e.g., `reftable` or `files`).
+
+* A URI format `<format>://<payload>` explicitly specifies both the
+  format and payload (e.g., `reftable:///foo/bar`).
+
+Supported format names are:
+
 include::../ref-storage-format.adoc[]
+
+The payload is passed directly to the reference backend. For the files and
+reftable backends, this must be a filesystem path where the references will
+be stored. Defaulting to the commondir when no payload is provided. Relative
+paths are resolved relative to the `$GIT_DIR`. Future backends may support
+other payload schemes, e.g., postgres://127.0.0.1:5432?database=myrepo.
 --
 +
 Note that this setting should only be set by linkgit:git-init[1] or
@@ -73,6 +87,35 @@
 	repaired with either the `--relative-paths` option or with the
 	`worktree.useRelativePaths` config set to `true`.
 
+submodulePathConfig:::
+	This extension is for the minority of users who:
++
+--
+* Encounter errors like `refusing to create ... in another submodule's git dir`
+  due to a number of reasons, like case-insensitive filesystem conflicts when
+  creating modules named `foo` and `Foo`.
+* Require more flexible submodule layouts, for example due to nested names like
+  `foo`, `foo/bar` and `foo/baz` not supported by the default gitdir mechanism
+  which uses `.git/modules/<plain-name>` locations, causing further conflicts.
+--
++
+When `extensions.submodulePathConfig` is enabled, the `submodule.<name>.gitdir`
+config becomes the single source of truth for all submodule gitdir paths and is
+automatically set for all new submodules both during clone and init operations.
++
+Git will error out if a module does not have a corresponding
+`submodule.<name>.gitdir` set.
++
+Existing (pre-extension) submodules need to be migrated by adding the missing
+config entries. This can be done manually, e.g. for each submodule:
+`git config submodule.<name>.gitdir .git/modules/<name>`, or via the
+`git submodule--helper migrate-gitdir-configs` command which iterates over all
+submodules and attempts to migrate them.
++
+The extension can be enabled automatically for new repositories by setting
+`init.defaultSubmodulePathConfig` to `true`, for example by running
+`git config --global init.defaultSubmodulePathConfig true`.
+
 worktreeConfig:::
 	If enabled, then worktrees will load config settings from the
 	`$GIT_DIR/config.worktree` file in addition to the
diff --git a/Documentation/config/fetch.adoc b/Documentation/config/fetch.adoc
index d7dc461..cd40db0 100644
--- a/Documentation/config/fetch.adoc
+++ b/Documentation/config/fetch.adoc
@@ -1,32 +1,32 @@
-fetch.recurseSubmodules::
+`fetch.recurseSubmodules`::
 	This option controls whether `git fetch` (and the underlying fetch
 	in `git pull`) will recursively fetch into populated submodules.
-	This option can be set either to a boolean value or to 'on-demand'.
+	This option can be set either to a boolean value or to `on-demand`.
 	Setting it to a boolean changes the behavior of fetch and pull to
 	recurse unconditionally into submodules when set to true or to not
-	recurse at all when set to false. When set to 'on-demand', fetch and
+	recurse at all when set to false. When set to `on-demand`, fetch and
 	pull will only recurse into a populated submodule when its
 	superproject retrieves a commit that updates the submodule's
 	reference.
-	Defaults to 'on-demand', or to the value of 'submodule.recurse' if set.
+	Defaults to `on-demand`, or to the value of `submodule.recurse` if set.
 
-fetch.fsckObjects::
+`fetch.fsckObjects`::
 	If it is set to true, git-fetch-pack will check all fetched
 	objects. See `transfer.fsckObjects` for what's
-	checked. Defaults to false. If not set, the value of
+	checked. Defaults to `false`. If not set, the value of
 	`transfer.fsckObjects` is used instead.
 
-fetch.fsck.<msg-id>::
+`fetch.fsck.<msg-id>`::
 	Acts like `fsck.<msg-id>`, but is used by
 	linkgit:git-fetch-pack[1] instead of linkgit:git-fsck[1]. See
 	the `fsck.<msg-id>` documentation for details.
 
-fetch.fsck.skipList::
+`fetch.fsck.skipList`::
 	Acts like `fsck.skipList`, but is used by
 	linkgit:git-fetch-pack[1] instead of linkgit:git-fsck[1]. See
 	the `fsck.skipList` documentation for details.
 
-fetch.unpackLimit::
+`fetch.unpackLimit`::
 	If the number of objects fetched over the Git native
 	transfer is below this
 	limit, then the objects will be unpacked into loose object
@@ -37,12 +37,12 @@
 	especially on slow filesystems.  If not set, the value of
 	`transfer.unpackLimit` is used instead.
 
-fetch.prune::
+`fetch.prune`::
 	If true, fetch will automatically behave as if the `--prune`
 	option was given on the command line.  See also `remote.<name>.prune`
 	and the PRUNING section of linkgit:git-fetch[1].
 
-fetch.pruneTags::
+`fetch.pruneTags`::
 	If true, fetch will automatically behave as if the
 	`refs/tags/*:refs/tags/*` refspec was provided when pruning,
 	if not set already. This allows for setting both this option
@@ -50,41 +50,41 @@
 	refs. See also `remote.<name>.pruneTags` and the PRUNING
 	section of linkgit:git-fetch[1].
 
-fetch.all::
+`fetch.all`::
 	If true, fetch will attempt to update all available remotes.
 	This behavior can be overridden by passing `--no-all` or by
 	explicitly specifying one or more remote(s) to fetch from.
-	Defaults to false.
+	Defaults to `false`.
 
-fetch.output::
+`fetch.output`::
 	Control how ref update status is printed. Valid values are
 	`full` and `compact`. Default value is `full`. See the
 	OUTPUT section in linkgit:git-fetch[1] for details.
 
-fetch.negotiationAlgorithm::
+`fetch.negotiationAlgorithm`::
 	Control how information about the commits in the local repository
 	is sent when negotiating the contents of the packfile to be sent by
-	the server.  Set to "consecutive" to use an algorithm that walks
-	over consecutive commits checking each one.  Set to "skipping" to
+	the server.  Set to `consecutive` to use an algorithm that walks
+	over consecutive commits checking each one.  Set to `skipping` to
 	use an algorithm that skips commits in an effort to converge
 	faster, but may result in a larger-than-necessary packfile; or set
-	to "noop" to not send any information at all, which will almost
+	to `noop` to not send any information at all, which will almost
 	certainly result in a larger-than-necessary packfile, but will skip
-	the negotiation step.  Set to "default" to override settings made
+	the negotiation step.  Set to `default` to override settings made
 	previously and use the default behaviour.  The default is normally
-	"consecutive", but if `feature.experimental` is true, then the
-	default is "skipping".  Unknown values will cause 'git fetch' to
+	`consecutive`, but if `feature.experimental` is `true`, then the
+	default is `skipping`.  Unknown values will cause `git fetch` to
 	error out.
 +
 See also the `--negotiate-only` and `--negotiation-tip` options to
 linkgit:git-fetch[1].
 
-fetch.showForcedUpdates::
-	Set to false to enable `--no-show-forced-updates` in
+`fetch.showForcedUpdates`::
+	Set to `false` to enable `--no-show-forced-updates` in
 	linkgit:git-fetch[1] and linkgit:git-pull[1] commands.
-	Defaults to true.
+	Defaults to `true`.
 
-fetch.parallel::
+`fetch.parallel`::
 	Specifies the maximal number of fetch operations to be run in parallel
 	at a time (submodules, or remotes when the `--multiple` option of
 	linkgit:git-fetch[1] is in effect).
@@ -94,16 +94,16 @@
 For submodules, this setting can be overridden using the `submodule.fetchJobs`
 config setting.
 
-fetch.writeCommitGraph::
+`fetch.writeCommitGraph`::
 	Set to true to write a commit-graph after every `git fetch` command
 	that downloads a pack-file from a remote. Using the `--split` option,
 	most executions will create a very small commit-graph file on top of
 	the existing commit-graph file(s). Occasionally, these files will
 	merge and the write may take longer. Having an updated commit-graph
 	file helps performance of many Git commands, including `git merge-base`,
-	`git push -f`, and `git log --graph`. Defaults to false.
+	`git push -f`, and `git log --graph`. Defaults to `false`.
 
-fetch.bundleURI::
+`fetch.bundleURI`::
 	This value stores a URI for downloading Git object data from a bundle
 	URI before performing an incremental fetch from the origin Git server.
 	This is similar to how the `--bundle-uri` option behaves in
@@ -115,9 +115,9 @@
 value, then remove that `fetch.bundleCreationToken` value before fetching from
 the new bundle URI.
 
-fetch.bundleCreationToken::
+`fetch.bundleCreationToken`::
 	When using `fetch.bundleURI` to fetch incrementally from a bundle
-	list that uses the "creationToken" heuristic, this config value
+	list that uses the "`creationToken`" heuristic, this config value
 	stores the maximum `creationToken` value of the downloaded bundles.
 	This value is used to prevent downloading bundles in the future
 	if the advertised `creationToken` is not strictly larger than this
diff --git a/Documentation/config/gui.adoc b/Documentation/config/gui.adoc
index 171be77..1565c0a 100644
--- a/Documentation/config/gui.adoc
+++ b/Documentation/config/gui.adoc
@@ -55,3 +55,8 @@
 	linkgit:gitk[1] for the selected commit, when the `Show History
 	Context` menu item is invoked from 'git gui blame'. If this
 	variable is set to zero, the whole history is shown.
+
+gui.GCWarning::
+	Determines whether linkgit:git-gui[1] should prompt for garbage
+	collection when git detects a large number of loose objects in
+	the repository. The default value is "true".
diff --git a/Documentation/config/hook.adoc b/Documentation/config/hook.adoc
new file mode 100644
index 0000000..64e845a
--- /dev/null
+++ b/Documentation/config/hook.adoc
@@ -0,0 +1,24 @@
+hook.<name>.command::
+	The command to execute for `hook.<name>`. `<name>` is a unique
+	"friendly" name that identifies this hook. (The hook events that
+	trigger the command are configured with `hook.<name>.event`.) The
+	value can be an executable path or a shell oneliner. If more than
+	one value is specified for the same `<name>`, only the last value
+	parsed is used. See linkgit:git-hook[1].
+
+hook.<name>.event::
+	The hook events that trigger `hook.<name>`. The value is the name
+	of a hook event, like "pre-commit" or "update". (See
+	linkgit:githooks[5] for a complete list of hook events.) On the
+	specified event, the associated `hook.<name>.command` is executed.
+	This is a multi-valued key. To run `hook.<name>` on multiple
+	events, specify the key more than once. An empty value resets
+	the list of events, clearing any previously defined events for
+	`hook.<name>`. See linkgit:git-hook[1].
+
+hook.<name>.enabled::
+	Whether the hook `hook.<name>` is enabled. Defaults to `true`.
+	Set to `false` to disable the hook without removing its
+	configuration. This is particularly useful when a hook is defined
+	in a system or global config file and needs to be disabled for a
+	specific repository. See linkgit:git-hook[1].
diff --git a/Documentation/config/init.adoc b/Documentation/config/init.adoc
index e45b2a8..7b4abda 100644
--- a/Documentation/config/init.adoc
+++ b/Documentation/config/init.adoc
@@ -18,3 +18,9 @@
 	See `--ref-format=` in linkgit:git-init[1]. Both the command line
 	option and the `GIT_DEFAULT_REF_FORMAT` environment variable take
 	precedence over this config.
+
+init.defaultSubmodulePathConfig::
+	A boolean that specifies if `git init` and `git clone` should
+	automatically set `extensions.submodulePathConfig` to `true`. This
+	allows all new repositories to automatically use the submodule path
+	extension. Defaults to `false` when unset.
diff --git a/Documentation/config/maintenance.adoc b/Documentation/config/maintenance.adoc
index d0c38f0..b578856 100644
--- a/Documentation/config/maintenance.adoc
+++ b/Documentation/config/maintenance.adoc
@@ -30,8 +30,7 @@
 +
 * `none`: This strategy implies no tasks are run at all. This is the default
   strategy for scheduled maintenance.
-* `gc`: This strategy runs the `gc` task. This is the default strategy for
-  manual maintenance.
+* `gc`: This strategy runs the `gc` task.
 * `geometric`: This strategy performs geometric repacking of packfiles and
   keeps auxiliary data structures up-to-date. The strategy expires data in the
   reflog and removes worktrees that cannot be located anymore. When the
@@ -40,7 +39,8 @@
   are already part of a cruft pack will be expired.
 +
 This repacking strategy is a full replacement for the `gc` strategy and is
-recommended for large repositories.
+recommended for large repositories. This is the default strategy for manual
+maintenance.
 * `incremental`: This setting optimizes for performing small maintenance
   activities that do not delete any data. This does not schedule the `gc`
   task, but runs the `prefetch` and `commit-graph` tasks hourly, the
diff --git a/Documentation/config/pack.adoc b/Documentation/config/pack.adoc
index 75402d5..fa997c8 100644
--- a/Documentation/config/pack.adoc
+++ b/Documentation/config/pack.adoc
@@ -160,12 +160,13 @@
 	processes. See linkgit:git-pack-objects[1] for full details.
 
 pack.preferBitmapTips::
+	Specifies a ref hierarchy (e.g., "refs/heads/"); can be
+	given multiple times to specify more than one hierarchies.
 	When selecting which commits will receive bitmaps, prefer a
-	commit at the tip of any reference that is a suffix of any value
-	of this configuration over any other commits in the "selection
-	window".
+	commit at the tip of a reference that is contained in any of
+	the configured hierarchies.
 +
-Note that setting this configuration to `refs/foo` does not mean that
+Note that setting this configuration to `refs/foo/` does not mean that
 the commits at the tips of `refs/foo/bar` and `refs/foo/baz` will
 necessarily be selected. This is because commits are selected for
 bitmaps from within a series of windows of variable length.
diff --git a/Documentation/config/promisor.adoc b/Documentation/config/promisor.adoc
index 93e5e0d..b0fa43b 100644
--- a/Documentation/config/promisor.adoc
+++ b/Documentation/config/promisor.adoc
@@ -89,3 +89,36 @@
 `promisor.acceptFromServer` config variable is not set to "None". If
 set to "None", this config variable has no effect. See
 linkgit:gitprotocol-v2[5].
+
+promisor.storeFields::
+	A comma or space separated list of additional remote related
+	field names. If a client accepts an advertised remote, the
+	client will store the values associated with these field names
+	taken from the remote advertisement into its configuration,
+	and then reload its remote configuration. Currently,
+	"partialCloneFilter" and "token" are the only supported field
+	names.
++
+For example if a server advertises "partialCloneFilter=blob:limit=20k"
+for remote "foo", and that remote is accepted, then "blob:limit=20k"
+will be stored for the "remote.foo.partialCloneFilter" configuration
+variable.
++
+If the new field value from an advertised remote is the same as the
+existing field value for that remote on the client side, then no
+change is made to the client configuration though.
++
+When a new value is stored, a message is printed to standard error to
+let users know about this.
++
+Note that for security reasons, if the remote is not already
+configured on the client side, nothing will be stored for that
+remote. In any case, no new remote will be created and no URL will be
+stored.
++
+Before storing a partial clone filter, it's parsed to check it's
+valid. If it's not, a warning is emitted and it's not stored.
++
+Before storing a token, a check is performed to ensure it contains no
+control character. If the check fails, a warning is emitted and it's
+not stored.
diff --git a/Documentation/config/push.adoc b/Documentation/config/push.adoc
index 0acbbea..d9112b2 100644
--- a/Documentation/config/push.adoc
+++ b/Documentation/config/push.adoc
@@ -1,15 +1,15 @@
-push.autoSetupRemote::
-	If set to "true" assume `--set-upstream` on default push when no
+`push.autoSetupRemote`::
+	If set to `true` assume `--set-upstream` on default push when no
 	upstream tracking exists for the current branch; this option
-	takes effect with push.default options 'simple', 'upstream',
-	and 'current'. It is useful if by default you want new branches
+	takes effect with `push.default` options `simple`, `upstream`,
+	and `current`. It is useful if by default you want new branches
 	to be pushed to the default remote (like the behavior of
-	'push.default=current') and you also want the upstream tracking
+	`push.default=current`) and you also want the upstream tracking
 	to be set. Workflows most likely to benefit from this option are
-	'simple' central workflows where all branches are expected to
+	`simple` central workflows where all branches are expected to
 	have the same name on the remote.
 
-push.default::
+`push.default`::
 	Defines the action `git push` should take if no refspec is
 	given (whether from the command-line, config, or elsewhere).
 	Different values are well-suited for
@@ -18,24 +18,28 @@
 	`upstream` is probably what you want.  Possible values are:
 +
 --
+`nothing`;;
+do not push anything (error out) unless a refspec is
+given. This is primarily meant for people who want to
+avoid mistakes by always being explicit.
 
-* `nothing` - do not push anything (error out) unless a refspec is
-  given. This is primarily meant for people who want to
-  avoid mistakes by always being explicit.
+`current`;;
+push the current branch to update a branch with the same
+name on the receiving end.  Works in both central and non-central
+workflows.
 
-* `current` - push the current branch to update a branch with the same
-  name on the receiving end.  Works in both central and non-central
-  workflows.
+`upstream`;;
+push the current branch back to the branch whose
+changes are usually integrated into the current branch (which is
+called `@{upstream}`).  This mode only makes sense if you are
+pushing to the same repository you would normally pull from
+(i.e. central workflow).
 
-* `upstream` - push the current branch back to the branch whose
-  changes are usually integrated into the current branch (which is
-  called `@{upstream}`).  This mode only makes sense if you are
-  pushing to the same repository you would normally pull from
-  (i.e. central workflow).
+`tracking`;;
+this is a deprecated synonym for `upstream`.
 
-* `tracking` - This is a deprecated synonym for `upstream`.
-
-* `simple` - push the current branch with the same name on the remote.
+`simple`;;
+push the current branch with the same name on the remote.
 +
 If you are working on a centralized workflow (pushing to the same repository you
 pull from, which is typically `origin`), then you need to configure an upstream
@@ -44,16 +48,17 @@
 This mode is the default since Git 2.0, and is the safest option suited for
 beginners.
 
-* `matching` - push all branches having the same name on both ends.
-  This makes the repository you are pushing to remember the set of
-  branches that will be pushed out (e.g. if you always push 'maint'
-  and 'master' there and no other branches, the repository you push
-  to will have these two branches, and your local 'maint' and
-  'master' will be pushed there).
+`matching`;;
+push all branches having the same name on both ends.
+This makes the repository you are pushing to remember the set of
+branches that will be pushed out (e.g. if you always push `maint`
+and `master` there and no other branches, the repository you push
+to will have these two branches, and your local `maint` and
+`master` will be pushed there).
 +
 To use this mode effectively, you have to make sure _all_ the
 branches you would push out are ready to be pushed out before
-running 'git push', as the whole point of this mode is to allow you
+running `git push`, as the whole point of this mode is to allow you
 to push all of the branches in one go.  If you usually finish work
 on only one branch and push out the result, while other branches are
 unfinished, this mode is not for you.  Also this mode is not
@@ -66,24 +71,24 @@
 
 --
 
-push.followTags::
+`push.followTags`::
 	If set to true, enable `--follow-tags` option by default.  You
 	may override this configuration at time of push by specifying
 	`--no-follow-tags`.
 
-push.gpgSign::
-	May be set to a boolean value, or the string 'if-asked'. A true
+`push.gpgSign`::
+	May be set to a boolean value, or the string `if-asked`. A true
 	value causes all pushes to be GPG signed, as if `--signed` is
-	passed to linkgit:git-push[1]. The string 'if-asked' causes
+	passed to linkgit:git-push[1]. The string `if-asked` causes
 	pushes to be signed if the server supports it, as if
-	`--signed=if-asked` is passed to 'git push'. A false value may
+	`--signed=if-asked` is passed to `git push`. A false value may
 	override a value from a lower-priority config file. An explicit
 	command-line flag always overrides this config option.
 
-push.pushOption::
+`push.pushOption`::
 	When no `--push-option=<option>` argument is given from the
-	command line, `git push` behaves as if each <value> of
-	this variable is given as `--push-option=<value>`.
+	command line, `git push` behaves as if each _<option>_ of
+	this variable is given as `--push-option=<option>`.
 +
 This is a multi-valued variable, and an empty value can be used in a
 higher priority configuration file (e.g. `.git/config` in a
@@ -109,26 +114,26 @@
 
 ----
 
-push.recurseSubmodules::
-	May be "check", "on-demand", "only", or "no", with the same behavior
-	as that of "push --recurse-submodules".
-	If not set, 'no' is used by default, unless 'submodule.recurse' is
-	set (in which case a 'true' value means 'on-demand').
+`push.recurseSubmodules`::
+	May be `check`, `on-demand`, `only`, or `no`, with the same behavior
+	as that of `push --recurse-submodules`.
+	If not set, `no` is used by default, unless `submodule.recurse` is
+	set (in which case a `true` value means `on-demand`).
 
-push.useForceIfIncludes::
-	If set to "true", it is equivalent to specifying
+`push.useForceIfIncludes`::
+	If set to `true`, it is equivalent to specifying
 	`--force-if-includes` as an option to linkgit:git-push[1]
 	in the command line. Adding `--no-force-if-includes` at the
 	time of push overrides this configuration setting.
 
-push.negotiate::
-	If set to "true", attempt to reduce the size of the packfile
+`push.negotiate`::
+	If set to `true`, attempt to reduce the size of the packfile
 	sent by rounds of negotiation in which the client and the
-	server attempt to find commits in common. If "false", Git will
+	server attempt to find commits in common. If `false`, Git will
 	rely solely on the server's ref advertisement to find commits
 	in common.
 
-push.useBitmaps::
-	If set to "false", disable use of bitmaps for "git push" even if
-	`pack.useBitmaps` is "true", without preventing other git operations
-	from using bitmaps. Default is true.
+`push.useBitmaps`::
+	If set to `false`, disable use of bitmaps for `git push` even if
+	`pack.useBitmaps` is `true`, without preventing other git operations
+	from using bitmaps. Default is `true`.
diff --git a/Documentation/config/replay.adoc b/Documentation/config/replay.adoc
new file mode 100644
index 0000000..7d549d2
--- /dev/null
+++ b/Documentation/config/replay.adoc
@@ -0,0 +1,11 @@
+replay.refAction::
+	Specifies the default mode for handling reference updates in
+	`git replay`. The value can be:
++
+--
+	* `update`: Update refs directly using an atomic transaction (default behavior).
+	* `print`: Output update-ref commands for pipeline use.
+--
++
+This setting can be overridden with the `--ref-action` command-line option.
+When not configured, `git replay` defaults to `update` mode.
diff --git a/Documentation/config/sendemail.adoc b/Documentation/config/sendemail.adoc
index 90164c7..6560ecc 100644
--- a/Documentation/config/sendemail.adoc
+++ b/Documentation/config/sendemail.adoc
@@ -12,6 +12,22 @@
 	Path to ca-certificates (either a directory or a single file).
 	Set it to an empty string to disable certificate verification.
 
+sendemail.smtpSSLClientCert::
+	Path to the client certificate file to present if requested by the
+	server. This is required when the server is set up to verify client
+	certificates. If the corresponding private key is not included in the
+	file, it must be supplied using `sendemail.smtpSSLClientKey` or the
+	`--smtp-ssl-client-key` option.
+
+sendemail.smtpSSLClientKey::
+	Path to the client private key file that corresponds to the client
+	certificate. To avoid misconfiguration, this configuration must be used
+	in conjunction with `sendemail.smtpSSLClientKey` or the
+	`--smtp-ssl-client-cert` option. If the client key is included in the
+	client certificate, the choice of private key depends on the format of
+	the certificate. Visit https://metacpan.org/pod/IO::Socket::SSL for more
+	details.
+
 sendemail.<identity>.*::
 	Identity-specific versions of the `sendemail.*` parameters
 	found below, taking precedence over those when this
diff --git a/Documentation/config/status.adoc b/Documentation/config/status.adoc
index 8caf90f..b5dd85b 100644
--- a/Documentation/config/status.adoc
+++ b/Documentation/config/status.adoc
@@ -17,6 +17,31 @@
 	`--no-ahead-behind` by default in linkgit:git-status[1] for
 	non-porcelain status formats.  Defaults to true.
 
+status.compareBranches::
+	A space-separated list of branch comparison specifiers to use in
+	linkgit:git-status[1]. Currently, only `@{upstream}` and `@{push}`
+	are supported. They are interpreted as `branch@{upstream}` and
+	`branch@{push}` for the current branch.
++
+If not set, the default behavior is equivalent to `@{upstream}`, which
+compares against the configured upstream tracking branch.
++
+The entries are shown in the order they appear in the configuration.
+Duplicate entries that resolve to the same ref are suppressed after
+their first occurrence, so `@{push} @{upstream} @{push}` shows at
+most two comparisons.  When `@{upstream}` and `@{push}` resolve to
+the same remote-tracking branch, only one comparison is shown.
++
+Example:
++
+----
+[status]
+	compareBranches = @{upstream} @{push}
+----
++
+This would show comparisons against both the configured upstream and push
+tracking branches for the current branch.
+
 status.displayCommentPrefix::
 	If set to true, linkgit:git-status[1] will insert a comment
 	prefix before each output line (starting with
diff --git a/Documentation/config/submodule.adoc b/Documentation/config/submodule.adoc
index 0672d99..8dacb85 100644
--- a/Documentation/config/submodule.adoc
+++ b/Documentation/config/submodule.adoc
@@ -32,15 +32,16 @@
 
 submodule.<name>.ignore::
 	Defines under what circumstances "git status" and the diff family show
-	a submodule as modified. When set to "all", it will never be considered
-	modified (but it will nonetheless show up in the output of status and
-	commit when it has been staged), "dirty" will ignore all changes
-	to the submodule's work tree and
+	a submodule as modified.
+	When set to "all" will never consider the submodule modified. It can
+	nevertheless be staged using the option --force and it will then show up
+	in the output of status.
+	When set to "dirty" will ignore all changes to the submodule's work tree and
 	takes only differences between the HEAD of the submodule and the commit
 	recorded in the superproject into account. "untracked" will additionally
 	let submodules with modified tracked files in their work tree show up.
-	Using "none" (the default when this option is not set) also shows
-	submodules that have untracked files in their work tree as changed.
+	When set to "none"(default) It also show submodules as changed if they have
+	untracked files in their work tree.
 	This setting overrides any setting made in .gitmodules for this submodule,
 	both settings can be overridden on the command line by using the
 	"--ignore-submodules" option. The 'git submodule' commands are not
@@ -52,6 +53,13 @@
 	submodule.active config option. See linkgit:gitsubmodules[7] for
 	details.
 
+submodule.<name>.gitdir::
+	This sets the gitdir path for submodule <name>. This configuration is
+	respected when `extensions.submodulePathConfig` is enabled, otherwise it
+	has no effect. When enabled, this config becomes the single source of
+	truth for submodule gitdir paths and Git will error if it is missing.
+	See linkgit:git-config[1] for details.
+
 submodule.active::
 	A repeated field which contains a pathspec used to match against a
 	submodule's path to determine if the submodule is of interest to git
diff --git a/Documentation/diff-algorithm-option.adoc b/Documentation/diff-algorithm-option.adoc
new file mode 100644
index 0000000..8e3a0b6
--- /dev/null
+++ b/Documentation/diff-algorithm-option.adoc
@@ -0,0 +1,20 @@
+`--diff-algorithm=(patience|minimal|histogram|myers)`::
+	Choose a diff algorithm. The variants are as follows:
++
+--
+   `default`;;
+   `myers`;;
+	The basic greedy diff algorithm. Currently, this is the default.
+   `minimal`;;
+	Spend extra time to make sure the smallest possible diff is
+	produced.
+   `patience`;;
+	Use "patience diff" algorithm when generating patches.
+   `histogram`;;
+	This algorithm extends the patience algorithm to "support
+	low-occurrence common elements".
+--
++
+For instance, if you configured the `diff.algorithm` variable to a
+non-default value and want to use the default one, then you
+have to use `--diff-algorithm=default` option.
diff --git a/Documentation/diff-options.adoc b/Documentation/diff-options.adoc
index ae31520..fcfcdf0 100644
--- a/Documentation/diff-options.adoc
+++ b/Documentation/diff-options.adoc
@@ -197,26 +197,7 @@
 appearing as a deletion or addition in the output. It uses the "patience
 diff" algorithm internally.
 
-`--diff-algorithm=(patience|minimal|histogram|myers)`::
-	Choose a diff algorithm. The variants are as follows:
-+
---
-   `default`;;
-   `myers`;;
-	The basic greedy diff algorithm. Currently, this is the default.
-   `minimal`;;
-	Spend extra time to make sure the smallest possible diff is
-	produced.
-   `patience`;;
-	Use "patience diff" algorithm when generating patches.
-   `histogram`;;
-	This algorithm extends the patience algorithm to "support
-	low-occurrence common elements".
---
-+
-For instance, if you configured the `diff.algorithm` variable to a
-non-default value and want to use the default one, then you
-have to use `--diff-algorithm=default` option.
+include::diff-algorithm-option.adoc[]
 
 `--stat[=<width>[,<name-width>[,<count>]]]`::
 	Generate a diffstat. By default, as much space as necessary
@@ -878,10 +859,18 @@
 	Do not show any source or destination prefix.
 
 `--default-prefix`::
+ifdef::git-format-patch[]
+	Use the default source and destination prefixes ("a/" and "b/").
+	This overrides configuration variables such as `format.noprefix`,
+	`diff.srcPrefix`, `diff.dstPrefix`, and `diff.mnemonicPrefix`
+	(see linkgit:git-config[1]).
+endif::git-format-patch[]
+ifndef::git-format-patch[]
 	Use the default source and destination prefixes ("a/" and "b/").
 	This overrides configuration variables such as `diff.noprefix`,
 	`diff.srcPrefix`, `diff.dstPrefix`, and `diff.mnemonicPrefix`
 	(see linkgit:git-config[1]).
+endif::git-format-patch[]
 
 `--line-prefix=<prefix>`::
 	Prepend an additional _<prefix>_ to every line of output.
diff --git a/Documentation/fetch-options.adoc b/Documentation/fetch-options.adoc
index ad1e1f4..81a9d7f 100644
--- a/Documentation/fetch-options.adoc
+++ b/Documentation/fetch-options.adoc
@@ -1,41 +1,41 @@
---all::
---no-all::
+`--all`::
+`--no-all`::
 	Fetch all remotes, except for the ones that has the
 	`remote.<name>.skipFetchAll` configuration variable set.
 	This overrides the configuration variable `fetch.all`.
 
--a::
---append::
+`-a`::
+`--append`::
 	Append ref names and object names of fetched refs to the
 	existing contents of `.git/FETCH_HEAD`.  Without this
 	option old data in `.git/FETCH_HEAD` will be overwritten.
 
---atomic::
+`--atomic`::
 	Use an atomic transaction to update local refs. Either all refs are
 	updated, or on error, no refs are updated.
 
---depth=<depth>::
+`--depth=<depth>`::
 	Limit fetching to the specified number of commits from the tip of
 	each remote branch history. If fetching to a 'shallow' repository
 	created by `git clone` with `--depth=<depth>` option (see
 	linkgit:git-clone[1]), deepen or shorten the history to the specified
 	number of commits. Tags for the deepened commits are not fetched.
 
---deepen=<depth>::
-	Similar to --depth, except it specifies the number of commits
+`--deepen=<depth>`::
+	Similar to `--depth`, except it specifies the number of commits
 	from the current shallow boundary instead of from the tip of
 	each remote branch history.
 
---shallow-since=<date>::
+`--shallow-since=<date>`::
 	Deepen or shorten the history of a shallow repository to
-	include all reachable commits after <date>.
+	include all reachable commits after _<date>_.
 
---shallow-exclude=<ref>::
+`--shallow-exclude=<ref>`::
 	Deepen or shorten the history of a shallow repository to
 	exclude commits reachable from a specified remote branch or tag.
 	This option can be specified multiple times.
 
---unshallow::
+`--unshallow`::
 	If the source repository is complete, convert a shallow
 	repository to a complete one, removing all the limitations
 	imposed by shallow repositories.
@@ -43,13 +43,13 @@
 If the source repository is shallow, fetch as much as possible so that
 the current repository has the same history as the source repository.
 
---update-shallow::
+`--update-shallow`::
 	By default when fetching from a shallow repository,
 	`git fetch` refuses refs that require updating
-	.git/shallow. This option updates .git/shallow and accepts such
+	`.git/shallow`. This option updates `.git/shallow` and accepts such
 	refs.
 
---negotiation-tip=<commit|glob>::
+`--negotiation-tip=(<commit>|<glob>)`::
 	By default, Git will report, to the server, commits reachable
 	from all local refs to find common commits in an attempt to
 	reduce the size of the to-be-received packfile. If specified,
@@ -69,28 +69,47 @@
 configuration variables documented in linkgit:git-config[1], and the
 `--negotiate-only` option below.
 
---negotiate-only::
+`--negotiate-only`::
 	Do not fetch anything from the server, and instead print the
-	ancestors of the provided `--negotiation-tip=*` arguments,
+	ancestors of the provided `--negotiation-tip=` arguments,
 	which we have in common with the server.
 +
-This is incompatible with `--recurse-submodules=[yes|on-demand]`.
+This is incompatible with `--recurse-submodules=(yes|on-demand)`.
 Internally this is used to implement the `push.negotiate` option, see
 linkgit:git-config[1].
 
---dry-run::
+`--dry-run`::
 	Show what would be done, without making any changes.
 
---porcelain::
+`--porcelain`::
 	Print the output to standard output in an easy-to-parse format for
 	scripts. See section OUTPUT in linkgit:git-fetch[1] for details.
 +
-This is incompatible with `--recurse-submodules=[yes|on-demand]` and takes
+This is incompatible with `--recurse-submodules=(yes|on-demand)` and takes
 precedence over the `fetch.output` config option.
 
+`--filter=<filter-spec>`::
+	Use the partial clone feature and request that the server sends
+	a subset of reachable objects according to a given object filter.
+	When using `--filter`, the supplied _<filter-spec>_ is used for
+	the partial fetch.
++
+If `--filter=auto` is used, the filter specification is determined
+automatically by combining the filter specifications advertised by
+the server for the promisor remotes that the client accepts (see
+linkgit:gitprotocol-v2[5] and the `promisor.acceptFromServer`
+configuration option in linkgit:git-config[1]).
++
+For details on all other available filter specifications, see the
+`--filter=<filter-spec>` option in linkgit:git-rev-list[1].
++
+For example, `--filter=blob:none` will filter out all blobs (file
+contents) until needed by Git. Also, `--filter=blob:limit=<size>` will
+filter out all blobs of size at least _<size>_.
+
 ifndef::git-pull[]
---write-fetch-head::
---no-write-fetch-head::
+`--write-fetch-head`::
+`--no-write-fetch-head`::
 	Write the list of remote refs fetched in the `FETCH_HEAD`
 	file directly under `$GIT_DIR`.  This is the default.
 	Passing `--no-write-fetch-head` from the command line tells
@@ -98,64 +117,65 @@
 	file is never written.
 endif::git-pull[]
 
--f::
---force::
-	When 'git fetch' is used with `<src>:<dst>` refspec, it may
-	refuse to update the local branch as discussed
+`-f`::
+`--force`::
 ifdef::git-pull[]
-	in the `<refspec>` part of the linkgit:git-fetch[1]
-	documentation.
+When `git fetch` is used with `<src>:<dst>` refspec, it may
+refuse to update the local branch as discussed
+in the _<refspec>_ part of the linkgit:git-fetch[1]
+documentation.
 endif::git-pull[]
 ifndef::git-pull[]
-	in the `<refspec>` part below.
+When `git fetch` is used with `<src>:<dst>` refspec, it may
+refuse to update the local branch as discussed in the _<refspec>_ part below.
 endif::git-pull[]
-	This option overrides that check.
+This option overrides that check.
 
--k::
---keep::
+`-k`::
+`--keep`::
 	Keep downloaded pack.
 
 ifndef::git-pull[]
---multiple::
-	Allow several <repository> and <group> arguments to be
-	specified. No <refspec>s may be specified.
+`--multiple`::
+	Allow several _<repository>_ and _<group>_ arguments to be
+	specified. No __<refspec>__s may be specified.
 
---auto-maintenance::
---no-auto-maintenance::
---auto-gc::
---no-auto-gc::
+`--auto-maintenance`::
+`--no-auto-maintenance`::
+`--auto-gc`::
+`--no-auto-gc`::
 	Run `git maintenance run --auto` at the end to perform automatic
-	repository maintenance if needed. (`--[no-]auto-gc` is a synonym.)
+	repository maintenance if needed.
 	This is enabled by default.
 
---write-commit-graph::
---no-write-commit-graph::
+`--write-commit-graph`::
+`--no-write-commit-graph`::
 	Write a commit-graph after fetching. This overrides the config
 	setting `fetch.writeCommitGraph`.
 endif::git-pull[]
 
---prefetch::
+`--prefetch`::
 	Modify the configured refspec to place all refs into the
 	`refs/prefetch/` namespace. See the `prefetch` task in
 	linkgit:git-maintenance[1].
 
--p::
---prune::
+`-p`::
+`--prune`::
 	Before fetching, remove any remote-tracking references that no
 	longer exist on the remote.  Tags are not subject to pruning
 	if they are fetched only because of the default tag
-	auto-following or due to a --tags option.  However, if tags
+	auto-following or due to a `--tags` option.  However, if tags
 	are fetched due to an explicit refspec (either on the command
 	line or in the remote configuration, for example if the remote
-	was cloned with the --mirror option), then they are also
+	was cloned with the `--mirror` option), then they are also
 	subject to pruning. Supplying `--prune-tags` is a shorthand for
 	providing the tag refspec.
 ifndef::git-pull[]
 +
 See the PRUNING section below for more details.
 
--P::
---prune-tags::
+`-P`::
+`--prune-tags`::
 	Before fetching, remove any local tags that no longer exist on
 	the remote if `--prune` is enabled. This option should be used
 	more carefully, unlike `--prune` it will remove any local
@@ -168,17 +188,17 @@
 endif::git-pull[]
 
 ifndef::git-pull[]
--n::
+`-n`::
 endif::git-pull[]
---no-tags::
+`--no-tags`::
 	By default, tags that point at objects that are downloaded
 	from the remote repository are fetched and stored locally.
 	This option disables this automatic tag following. The default
-	behavior for a remote may be specified with the remote.<name>.tagOpt
+	behavior for a remote may be specified with the `remote.<name>.tagOpt`
 	setting. See linkgit:git-config[1].
 
 ifndef::git-pull[]
---refetch::
+`--refetch`::
 	Instead of negotiating with the server to avoid transferring commits and
 	associated objects that are already present locally, this option fetches
 	all objects as a fresh clone would. Use this to reapply a partial clone
@@ -187,29 +207,29 @@
 	object database pack consolidation to remove any duplicate objects.
 endif::git-pull[]
 
---refmap=<refspec>::
+`--refmap=<refspec>`::
 	When fetching refs listed on the command line, use the
 	specified refspec (can be given more than once) to map the
 	refs to remote-tracking branches, instead of the values of
-	`remote.*.fetch` configuration variables for the remote
-	repository.  Providing an empty `<refspec>` to the
+	`remote.<name>.fetch` configuration variables for the remote
+	repository.  Providing an empty _<refspec>_ to the
 	`--refmap` option causes Git to ignore the configured
 	refspecs and rely entirely on the refspecs supplied as
 	command-line arguments. See section on "Configured Remote-tracking
 	Branches" for details.
 
--t::
---tags::
+`-t`::
+`--tags`::
 	Fetch all tags from the remote (i.e., fetch remote tags
 	`refs/tags/*` into local tags with the same name), in addition
 	to whatever else would otherwise be fetched.  Using this
-	option alone does not subject tags to pruning, even if --prune
+	option alone does not subject tags to pruning, even if `--prune`
 	is used (though tags may be pruned anyway if they are also the
 	destination of an explicit refspec; see `--prune`).
 
 ifndef::git-pull[]
---recurse-submodules[=(yes|on-demand|no)]::
-	This option controls if and under what conditions new commits of
+`--recurse-submodules[=(yes|on-demand|no)]`::
+	Control if and under what conditions new commits of
 	submodules should be fetched too. When recursing through submodules,
 	`git fetch` always attempts to fetch "changed" submodules, that is, a
 	submodule that has commits that are referenced by a newly fetched
@@ -219,19 +239,21 @@
 	adds a new submodule, that submodule cannot be fetched until it is
 	cloned e.g. by `git submodule update`.
 +
-When set to 'on-demand', only changed submodules are fetched. When set
-to 'yes', all populated submodules are fetched and submodules that are
-both unpopulated and changed are fetched. When set to 'no', submodules
+When set to `on-demand`, only changed submodules are fetched. When set
+to `yes`, all populated submodules are fetched and submodules that are
+both unpopulated and changed are fetched. When set to `no`, submodules
 are never fetched.
 +
 When unspecified, this uses the value of `fetch.recurseSubmodules` if it
-is set (see linkgit:git-config[1]), defaulting to 'on-demand' if unset.
-When this option is used without any value, it defaults to 'yes'.
+is set (see linkgit:git-config[1]), defaulting to `on-demand` if unset.
+When this option is used without any value, it defaults to `yes`.
 endif::git-pull[]
 
--j::
---jobs=<n>::
-	Number of parallel children to be used for all forms of fetching.
+`-j <n>`::
+`--jobs=<n>`::
+	Parallelize all forms of fetching up to _<n>_ jobs at a time.
++
+A value of 0 will use some reasonable default.
 +
 If the `--multiple` option was specified, the different remotes will be fetched
 in parallel. If multiple submodules are fetched, they will be fetched in
@@ -242,12 +264,12 @@
 default fetches are performed sequentially, not in parallel.
 
 ifndef::git-pull[]
---no-recurse-submodules::
+`--no-recurse-submodules`::
 	Disable recursive fetching of submodules (this has the same effect as
 	using the `--recurse-submodules=no` option).
 endif::git-pull[]
 
---set-upstream::
+`--set-upstream`::
 	If the remote is fetched successfully, add upstream
 	(tracking) reference, used by argument-less
 	linkgit:git-pull[1] and other commands. For more information,
@@ -255,57 +277,57 @@
 	linkgit:git-config[1].
 
 ifndef::git-pull[]
---submodule-prefix=<path>::
-	Prepend <path> to paths printed in informative messages
+`--submodule-prefix=<path>`::
+	Prepend _<path>_ to paths printed in informative messages
 	such as "Fetching submodule foo".  This option is used
 	internally when recursing over submodules.
 
---recurse-submodules-default=[yes|on-demand]::
+`--recurse-submodules-default=(yes|on-demand)`::
 	This option is used internally to temporarily provide a
-	non-negative default value for the --recurse-submodules
+	non-negative default value for the `--recurse-submodules`
 	option.  All other methods of configuring fetch's submodule
 	recursion (such as settings in linkgit:gitmodules[5] and
 	linkgit:git-config[1]) override this option, as does
-	specifying --[no-]recurse-submodules directly.
+	specifying `--[no-]recurse-submodules` directly.
 
--u::
---update-head-ok::
-	By default 'git fetch' refuses to update the head which
+`-u`::
+`--update-head-ok`::
+	By default `git fetch` refuses to update the head which
 	corresponds to the current branch.  This flag disables the
-	check.  This is purely for the internal use for 'git pull'
-	to communicate with 'git fetch', and unless you are
+	check.  This is purely for the internal use for `git pull`
+	to communicate with `git fetch`, and unless you are
 	implementing your own Porcelain you are not supposed to
 	use it.
 endif::git-pull[]
 
---upload-pack <upload-pack>::
+`--upload-pack <upload-pack>`::
 	When given, and the repository to fetch from is handled
-	by 'git fetch-pack', `--exec=<upload-pack>` is passed to
+	by `git fetch-pack`, `--exec=<upload-pack>` is passed to
 	the command to specify non-default path for the command
 	run on the other end.
 
 ifndef::git-pull[]
--q::
---quiet::
-	Pass --quiet to git-fetch-pack and silence any other internally
+`-q`::
+`--quiet`::
+	Pass `--quiet` to `git-fetch-pack` and silence any other internally
 	used git commands. Progress is not reported to the standard error
 	stream.
 
--v::
---verbose::
+`-v`::
+`--verbose`::
 	Be verbose.
 endif::git-pull[]
 
---progress::
+`--progress`::
 	Progress status is reported on the standard error stream
-	by default when it is attached to a terminal, unless -q
+	by default when it is attached to a terminal, unless `-q`
 	is specified. This flag forces progress status even if the
 	standard error stream is not directed to a terminal.
 
--o <option>::
---server-option=<option>::
+`-o <option>`::
+`--server-option=<option>`::
 	Transmit the given string to the server when communicating using
-	protocol version 2.  The given string must not contain a NUL or LF
+	protocol version 2.  The given string must not contain a _NUL_ or _LF_
 	character.  The server's handling of server options, including
 	unknown ones, is server-specific.
 	When multiple `--server-option=<option>` are given, they are all
@@ -314,23 +336,23 @@
 	the values of configuration variable `remote.<name>.serverOption`
 	are used instead.
 
---show-forced-updates::
+`--show-forced-updates`::
 	By default, git checks if a branch is force-updated during
-	fetch. This can be disabled through fetch.showForcedUpdates, but
-	the --show-forced-updates option guarantees this check occurs.
+	fetch. This can be disabled through `fetch.showForcedUpdates`, but
+	the `--show-forced-updates` option guarantees this check occurs.
 	See linkgit:git-config[1].
 
---no-show-forced-updates::
+`--no-show-forced-updates`::
 	By default, git checks if a branch is force-updated during
-	fetch. Pass --no-show-forced-updates or set fetch.showForcedUpdates
+	fetch. Pass `--no-show-forced-updates` or set `fetch.showForcedUpdates`
 	to false to skip this check for performance reasons. If used during
-	'git-pull' the --ff-only option will still check for forced updates
+	`git-pull` the `--ff-only` option will still check for forced updates
 	before attempting a fast-forward update. See linkgit:git-config[1].
 
--4::
---ipv4::
+`-4`::
+`--ipv4`::
 	Use IPv4 addresses only, ignoring IPv6 addresses.
 
--6::
---ipv6::
+`-6`::
+`--ipv6`::
 	Use IPv6 addresses only, ignoring IPv4 addresses.
diff --git a/Documentation/for-each-ref-options.adoc b/Documentation/for-each-ref-options.adoc
index f13efb5..54e2fa9 100644
--- a/Documentation/for-each-ref-options.adoc
+++ b/Documentation/for-each-ref-options.adoc
@@ -30,8 +30,8 @@
 
 `--color[=<when>]`::
 	Respect any colors specified in the `--format` option. The
-	_<when__ field must be one of `always`, `never`, or `auto` (if
-	`<when>` is absent, behave as if `always` was given).
+	_<when>_ field must be one of `always`, `never`, or `auto` (if
+	_<when>_ is absent, behave as if `always` was given).
 
 `--shell`::
 `--perl`::
diff --git a/Documentation/format-patch-caveats.adoc b/Documentation/format-patch-caveats.adoc
new file mode 100644
index 0000000..807a65b
--- /dev/null
+++ b/Documentation/format-patch-caveats.adoc
@@ -0,0 +1,33 @@
+The output from linkgit:git-format-patch[1] can lead to a different
+commit message when applied with linkgit:git-am[1]. The patch that is
+applied may also be different from the one that was generated, or patch
+application may fail outright.
+ifdef::git-am[]
+See the <<discussion,DISCUSSION>> section above for the syntactic rules.
+endif::git-am[]
+
+ifndef::git-am[]
+include::format-patch-end-of-commit-message.adoc[]
+endif::git-am[]
+
+Note that this is especially problematic for unindented diffs that occur
+in the commit message; the diff in the commit message might get applied
+along with the patch section, or the patch application machinery might
+trip up because the patch target doesn't apply. This could for example
+be caused by a diff in a Markdown code block.
+
+The solution for this is to indent the diff or other text that could
+cause problems.
+
+This loss of fidelity might be simple to notice if you are applying
+patches directly from a mailbox. However, changes originating from Git
+could be applied in bulk, in which case this would be much harder to
+notice. This could for example be a Linux distribution which uses patch
+files to apply changes on top of the commits from the upstream
+repositories. This goes to show that this behavior does not only impact
+email workflows.
+
+Given these limitations, one might be tempted to use a general-purpose
+utility like patch(1) instead. However, patch(1) will not only look for
+unindented diffs (like linkgit:git-am[1]) but will try to apply indented
+diffs as well.
diff --git a/Documentation/format-patch-end-of-commit-message.adoc b/Documentation/format-patch-end-of-commit-message.adoc
new file mode 100644
index 0000000..ec1ef79
--- /dev/null
+++ b/Documentation/format-patch-end-of-commit-message.adoc
@@ -0,0 +1,8 @@
+Any line that is of the form:
+
+* three-dashes and end-of-line, or
+* a line that begins with "diff -", or
+* a line that begins with "Index: "
+
+is taken as the beginning of a patch, and the commit log message
+is terminated before the first occurrence of such a line.
diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc
index acac968..6a4db3a 100644
--- a/Documentation/fsck-msgids.adoc
+++ b/Documentation/fsck-msgids.adoc
@@ -13,6 +13,9 @@
 `badGpgsig`::
 	(ERROR) A tag contains a bad (truncated) signature (e.g., `gpgsig`) header.
 
+`badHeadTarget`::
+	(ERROR) The `HEAD` ref is a symref that does not refer to a branch.
+
 `badHeaderContinuation`::
 	(ERROR) A continuation header (such as for `gpgsig`) is unexpectedly truncated.
 
@@ -41,6 +44,9 @@
 `badRefName`::
 	(ERROR) A ref has an invalid format.
 
+`badRefOid`::
+	(ERROR) A ref points to an invalid object ID.
+
 `badReferentName`::
 	(ERROR) The referent name of a symref is invalid.
 
diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc
index 6192dae..941135d 100644
--- a/Documentation/git-add.adoc
+++ b/Documentation/git-add.adoc
@@ -75,7 +75,10 @@
 
 `-f`::
 `--force`::
-	Allow adding otherwise ignored files.
+	Allow adding otherwise ignored files. The option is also used when
+	`submodule.<name>.ignore=all` is set, but you want to stage an
+	update of the submodule. The `path` to the submodule must be explicitly
+	specified.
 
 `--sparse`::
 	Allow updating index entries outside of the sparse-checkout cone.
diff --git a/Documentation/git-am.adoc b/Documentation/git-am.adoc
index b23b4fb..384e0cd 100644
--- a/Documentation/git-am.adoc
+++ b/Documentation/git-am.adoc
@@ -9,7 +9,7 @@
 SYNOPSIS
 --------
 [verse]
-'git am' [--signoff] [--keep] [--[no-]keep-cr] [--[no-]utf8] [--no-verify]
+'git am' [--signoff] [--keep] [--[no-]keep-cr] [--[no-]utf8] [--[no-]verify]
 	 [--[no-]3way] [--interactive] [--committer-date-is-author-date]
 	 [--ignore-date] [--ignore-space-change | --ignore-whitespace]
 	 [--whitespace=<action>] [-C<n>] [-p<n>] [--directory=<dir>]
@@ -37,20 +37,21 @@
 
 -s::
 --signoff::
-	Add a `Signed-off-by` trailer to the commit message, using
-	the committer identity of yourself.
-	See the signoff option in linkgit:git-commit[1] for more information.
+	Add a `Signed-off-by` trailer to the commit message (see
+	linkgit:git-interpret-trailers[1]), using the committer identity
+	of yourself.  See the signoff option in linkgit:git-commit[1]
+	for more information.
 
 -k::
 --keep::
-	Pass `-k` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
+	Pass `-k` flag to linkgit:git-mailinfo[1].
 
 --keep-non-patch::
-	Pass `-b` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
+	Pass `-b` flag to linkgit:git-mailinfo[1].
 
 --keep-cr::
 --no-keep-cr::
-	With `--keep-cr`, call 'git mailsplit' (see linkgit:git-mailsplit[1])
+	With `--keep-cr`, call linkgit:git-mailsplit[1]
 	with the same option, to prevent it from stripping CR at the end of
 	lines. `am.keepcr` configuration variable can be used to specify the
 	default behaviour.  `--no-keep-cr` is useful to override `am.keepcr`.
@@ -65,7 +66,7 @@
 	Ignore scissors lines (see linkgit:git-mailinfo[1]).
 
 --quoted-cr=<action>::
-	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
+	This flag will be passed down to linkgit:git-mailinfo[1].
 
 --empty=(drop|keep|stop)::
 	How to handle an e-mail message lacking a patch:
@@ -83,10 +84,11 @@
 
 -m::
 --message-id::
-	Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
-	so that the Message-ID header is added to the commit message.
-	The `am.messageid` configuration variable can be used to specify
-	the default behaviour.
+	Pass the `-m` flag to linkgit:git-mailinfo[1], so that the
+	`Message-ID` header is added as a trailer (see
+	linkgit:git-interpret-trailers[1]).  The `am.messageid`
+	configuration variable can be used to specify the default
+	behaviour.
 
 --no-message-id::
 	Do not add the Message-ID header to the commit message.
@@ -98,7 +100,7 @@
 
 -u::
 --utf8::
-	Pass `-u` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
+	Pass `-u` flag to linkgit:git-mailinfo[1].
 	The proposed commit log message taken from the e-mail
 	is re-coded into UTF-8 encoding (configuration variable
 	`i18n.commitEncoding` can be used to specify the project's
@@ -108,8 +110,7 @@
 default.   You can use `--no-utf8` to override this.
 
 --no-utf8::
-	Pass `-n` flag to 'git mailinfo' (see
-	linkgit:git-mailinfo[1]).
+	Pass `-n` flag to linkgit:git-mailinfo[1].
 
 -3::
 --3way::
@@ -132,9 +133,8 @@
 --exclude=<path>::
 --include=<path>::
 --reject::
-	These flags are passed to the 'git apply' (see linkgit:git-apply[1])
-	program that applies
-	the patch.
+	These flags are passed to the linkgit:git-apply[1] program that
+	applies the patch.
 +
 Valid <action> for the `--whitespace` option are:
 `nowarn`, `warn`, `fix`, `error`, and `error-all`.
@@ -150,11 +150,14 @@
 --interactive::
 	Run interactively.
 
+--verify::
 -n::
 --no-verify::
-	By default, the pre-applypatch and applypatch-msg hooks are run.
-	When any of `--no-verify` or `-n` is given, these are bypassed.
-	See also linkgit:githooks[5].
+	Run the `pre-applypatch` and `applypatch-msg` hooks. This is the
+	default. Skip these hooks with `-n` or `--no-verify`. See also
+	linkgit:githooks[5].
++
+Note that `post-applypatch` cannot be skipped.
 
 --committer-date-is-author-date::
 	By default the command records the date from the e-mail
@@ -162,6 +165,13 @@
 	commit creation as the committer date. This allows the
 	user to lie about the committer date by using the same
 	value as the author date.
++
+WARNING: The history walking machinery assumes that commits have
+non-decreasing commit timestamps. You should consider if you really need
+to use this option. Then you should only use this option to override the
+committer date when applying commits on top of a base which commit is
+older (in terms of the commit date) than the oldest patch you are
+applying.
 
 --ignore-date::
 	By default the command records the date from the e-mail
@@ -198,7 +208,8 @@
 	to the screen before exiting.  This overrides the
 	standard message informing you to use `--continue`
 	or `--skip` to handle the failure.  This is solely
-	for internal use between 'git rebase' and 'git am'.
+	for internal use between linkgit:git-rebase[1] and
+	linkgit:git-am[1].
 
 --abort::
 	Restore the original branch and abort the patching operation.
@@ -216,7 +227,7 @@
 	failure again.
 
 --show-current-patch[=(diff|raw)]::
-	Show the message at which `git am` has stopped due to
+	Show the message at which linkgit:git-am[1] has stopped due to
 	conflicts.  If `raw` is specified, show the raw contents of
 	the e-mail message; if `diff`, show the diff portion only.
 	Defaults to `raw`.
@@ -226,6 +237,7 @@
 	create an empty commit with the contents of the e-mail message
 	as its log message.
 
+[[discussion]]
 DISCUSSION
 ----------
 
@@ -245,16 +257,13 @@
 line is automatically stripped.
 
 The patch is expected to be inline, directly following the
-message.  Any line that is of the form:
+message.
+include::format-patch-end-of-commit-message.adoc[]
 
-* three-dashes and end-of-line, or
-* a line that begins with "diff -", or
-* a line that begins with "Index: "
+This means that the contents of the commit message can inadvertently
+interrupt the processing (see the <<caveats,CAVEATS>> section below).
 
-is taken as the beginning of a patch, and the commit log message
-is terminated before the first occurrence of such a line.
-
-When initially invoking `git am`, you give it the names of the mailboxes
+When initially invoking linkgit:git-am[1], you give it the names of the mailboxes
 to process.  Upon seeing the first patch that does not apply, it
 aborts in the middle.  You can recover from this in one of two ways:
 
@@ -272,16 +281,25 @@
 
 Before any patches are applied, ORIG_HEAD is set to the tip of the
 current branch.  This is useful if you have problems with multiple
-commits, like running 'git am' on the wrong branch or an error in the
-commits that is more easily fixed by changing the mailbox (e.g.
+commits, like running linkgit:git-am[1] on the wrong branch or an error
+in the commits that is more easily fixed by changing the mailbox (e.g.
 errors in the "From:" lines).
 
+[[caveats]]
+CAVEATS
+-------
+
+:git-am: 1
+include::format-patch-caveats.adoc[]
+
 HOOKS
 -----
 This command can run `applypatch-msg`, `pre-applypatch`,
 and `post-applypatch` hooks.  See linkgit:githooks[5] for more
 information.
 
+See the `--verify`/`-n`/`--no-verify` options.
+
 CONFIGURATION
 -------------
 
diff --git a/Documentation/git-blame.adoc b/Documentation/git-blame.adoc
index e438d28..8808009 100644
--- a/Documentation/git-blame.adoc
+++ b/Documentation/git-blame.adoc
@@ -7,12 +7,12 @@
 
 SYNOPSIS
 --------
-[verse]
-'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
-	    [-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
-	    [--ignore-rev <rev>] [--ignore-revs-file <file>]
-	    [--color-lines] [--color-by-age] [--progress] [--abbrev=<n>]
-	    [ --contents <file> ] [<rev> | --reverse <rev>..<rev>] [--] <file>
+[synopsis]
+git blame [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
+	  [-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
+	  [--ignore-rev <rev>] [--ignore-revs-file <file>]
+	  [--color-lines] [--color-by-age] [--progress] [--abbrev=<n>]
+	  [ --contents <file> ] [<rev> | --reverse <rev>..<rev>] [--] <file>
 
 DESCRIPTION
 -----------
@@ -30,7 +30,7 @@
 `-C` and `-M` options.
 
 The report does not tell you anything about lines which have been deleted or
-replaced; you need to use a tool such as 'git diff' or the "pickaxe"
+replaced; you need to use a tool such as `git diff` or the "pickaxe"
 interface briefly mentioned in the following paragraph.
 
 Apart from supporting file annotation, Git also supports searching the
@@ -50,45 +50,47 @@
 -------
 include::blame-options.adoc[]
 
--c::
+`-c`::
 	Use the same output mode as linkgit:git-annotate[1] (Default: off).
 
---score-debug::
+`--score-debug`::
 	Include debugging information related to the movement of
 	lines between files (see `-C`) and lines moved within a
 	file (see `-M`).  The first number listed is the score.
 	This is the number of alphanumeric characters detected
 	as having been moved between or within files.  This must be above
-	a certain threshold for 'git blame' to consider those lines
+	a certain threshold for `git blame` to consider those lines
 	of code to have been moved.
 
--f::
---show-name::
+`-f`::
+`--show-name`::
 	Show the filename in the original commit.  By default
 	the filename is shown if there is any line that came from a
 	file with a different name, due to rename detection.
 
--n::
---show-number::
+`-n`::
+`--show-number`::
 	Show the line number in the original commit (Default: off).
 
--s::
+`-s`::
 	Suppress the author name and timestamp from the output.
 
--e::
---show-email::
+`-e`::
+`--show-email`::
 	Show the author email instead of the author name (Default: off).
 	This can also be controlled via the `blame.showEmail` config
 	option.
 
--w::
+`-w`::
 	Ignore whitespace when comparing the parent's version and
 	the child's to find where the lines came from.
 
---abbrev=<n>::
-	Instead of using the default 7+1 hexadecimal digits as the
-	abbreviated object name, use <m>+1 digits, where <m> is at
-	least <n> but ensures the commit object names are unique.
+include::diff-algorithm-option.adoc[]
+
+`--abbrev=<n>`::
+	Instead of using the default _7+1_ hexadecimal digits as the
+	abbreviated object name, use _<m>+1_ digits, where _<m>_ is at
+	least _<n>_ but ensures the commit object names are unique.
 	Note that 1 column
 	is used for a caret to mark the boundary commit.
 
@@ -122,21 +124,21 @@
 This header line is followed by the following information
 at least once for each commit:
 
-- the author name ("author"), email ("author-mail"), time
-  ("author-time"), and time zone ("author-tz"); similarly
+- the author name (`author`), email (`author-mail`), time
+  (`author-time`), and time zone (`author-tz`); similarly
   for committer.
 - the filename in the commit that the line is attributed to.
-- the first line of the commit log message ("summary").
+- the first line of the commit log message (`summary`).
 
 The contents of the actual line are output after the above
-header, prefixed by a TAB. This is to allow adding more
+header, prefixed by a _TAB_. This is to allow adding more
 header elements later.
 
 The porcelain format generally suppresses commit information that has
 already been seen. For example, two lines that are blamed to the same
 commit will both be shown, but the details for that commit will be shown
 only once. Information which is specific to individual lines will not be
-grouped together, like revs to be marked 'ignored' or 'unblamable'. This
+grouped together, like revs to be marked `ignored` or `unblamable`. This
 is more efficient, but may require more state be kept by the reader. The
 `--line-porcelain` option can be used to output full commit information
 for each line, allowing simpler (but less efficient) usage like:
@@ -150,7 +152,7 @@
 SPECIFYING RANGES
 -----------------
 
-Unlike 'git blame' and 'git annotate' in older versions of git, the extent
+Unlike `git blame` and `git annotate` in older versions of git, the extent
 of the annotation can be limited to both line ranges and revision
 ranges. The `-L` option, which limits annotation to a range of lines, may be
 specified multiple times.
@@ -171,7 +173,7 @@
 
 When you are not interested in changes older than version
 v2.6.18, or changes older than 3 weeks, you can use revision
-range specifiers similar to 'git rev-list':
+range specifiers similar to `git rev-list`:
 
 	git blame v2.6.18.. -- foo
 	git blame --since=3.weeks -- foo
@@ -210,8 +212,9 @@
 annotated.
 
 . Each blame entry always starts with a line of:
-
-	<40-byte-hex-sha1> <sourceline> <resultline> <num-lines>
++
+[synopsis]
+<40-byte-hex-sha1> <sourceline> <resultline> <num-lines>
 +
 Line numbers count from 1.
 
@@ -222,16 +225,17 @@
 
 . Unlike the Porcelain format, the filename information is always
   given and terminates the entry:
-
-	"filename" <whitespace-quoted-filename-goes-here>
++
+[synopsis]
+filename <whitespace-quoted-filename-goes-here>
 +
 and thus it is really quite easy to parse for some line- and word-oriented
 parser (which should be quite natural for most scripting languages).
 +
 [NOTE]
 For people who do parsing: to make it more robust, just ignore any
-lines between the first and last one ("<sha1>" and "filename" lines)
-where you do not recognize the tag words (or care about that particular
+lines between the first and last one (_<40-byte-hex-sha1>_ and `filename`
+lines) where you do not recognize the tag words (or care about that particular
 one) at the beginning of the "extended information" lines. That way, if
 there is ever added information (like the commit encoding or extended
 commit commentary), a blame viewer will not care.
diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc
index 6f281b2..43ccf47 100644
--- a/Documentation/git-checkout.adoc
+++ b/Documentation/git-checkout.adoc
@@ -509,7 +509,7 @@
 -----------------------
 
 When you run `git checkout <something>`, Git tries to guess whether
-`<something>` is intended to be a branch, a commit, or a set of file(s),
+_<something>_ is intended to be a branch, a commit, or a set of file(s),
 and then either switches to that branch or commit, or restores the
 specified files.
 
diff --git a/Documentation/git-clone.adoc b/Documentation/git-clone.adoc
index 57cdfb7..b6e1f8a 100644
--- a/Documentation/git-clone.adoc
+++ b/Documentation/git-clone.adoc
@@ -84,7 +84,7 @@
 	with the source repository.  The resulting repository
 	starts out without any object of its own.
 +
-*NOTE*: this is a possibly dangerous operation; do *not* use
+NOTE: this is a possibly dangerous operation; do *not* use
 it unless you understand what it does. If you clone your
 repository using this option and then delete branches (or use any
 other Git command that makes any existing commit unreferenced) in the
@@ -104,7 +104,8 @@
 its source repository, you can simply run `git repack -a` to copy all
 objects from the source repository into a pack in the cloned repository.
 
-`--reference[-if-able] <repository>`::
+`--reference=<repository>`::
+`--reference-if-able=<repository>`::
 	If the reference _<repository>_ is on the local machine,
 	automatically setup `.git/objects/info/alternates` to
 	obtain objects from the reference _<repository>_.  Using
@@ -115,7 +116,7 @@
 	directory is skipped with a warning instead of aborting
 	the clone.
 +
-*NOTE*: see the NOTE for the `--shared` option, and also the
+NOTE: see the NOTE for the `--shared` option, and also the
 `--dissociate` option.
 
 `--dissociate`::
@@ -140,27 +141,28 @@
 	to the standard error stream.
 
 `--progress`::
-	Progress status is reported on the standard error stream
-	by default when it is attached to a terminal, unless `--quiet`
+	Report progress status on the standard error stream
+	by default when attached to a terminal, unless `--quiet`
 	is specified. This flag forces progress status even if the
 	standard error stream is not directed to a terminal.
 
 `--server-option=<option>`::
 	Transmit the given string to the server when communicating using
-	protocol version 2.  The given string must not contain a NUL or LF
+	protocol version 2.  The given string must not contain a _NUL_ or _LF_
 	character.  The server's handling of server options, including
 	unknown ones, is server-specific.
 	When multiple `--server-option=<option>` are given, they are all
 	sent to the other side in the order listed on the command line.
-	When no ++--server-option=++__<option>__ is given from the command
+	When no `--server-option=<option>` is given from the command
 	line, the values of configuration variable `remote.<name>.serverOption`
 	are used instead.
 
 `-n`::
 `--no-checkout`::
-	No checkout of `HEAD` is performed after the clone is complete.
+	Do not checkout `HEAD` after the clone is complete.
 
-`--`[`no-`]`reject-shallow`::
+`--no-reject-shallow`::
+`--reject-shallow`::
 	Fail if the source repository is a shallow repository.
 	The `clone.rejectShallow` configuration variable can be used to
 	specify the default.
@@ -187,11 +189,26 @@
 	Use the partial clone feature and request that the server sends
 	a subset of reachable objects according to a given object filter.
 	When using `--filter`, the supplied _<filter-spec>_ is used for
-	the partial clone filter. For example, `--filter=blob:none` will
-	filter out all blobs (file contents) until needed by Git. Also,
-	`--filter=blob:limit=<size>` will filter out all blobs of size
-	at least _<size>_. For more details on filter specifications, see
-	the `--filter` option in linkgit:git-rev-list[1].
+	the partial clone filter.
++
+If `--filter=auto` is used the filter specification is determined
+automatically through the 'promisor-remote' protocol (see
+linkgit:gitprotocol-v2[5]) by combining the filter specifications
+advertised by the server for the promisor remotes that the client
+accepts (see the `promisor.acceptFromServer` configuration option in
+linkgit:git-config[1]). This allows the server to suggest the optimal
+filter for the available promisor remotes.
++
+As with other filter specifications, the "auto" value is persisted in
+the configuration. This ensures that future fetches will continue to
+adapt to the server's current recommendation.
++
+For details on all other available filter specifications, see the
+`--filter=<filter-spec>` option in linkgit:git-rev-list[1].
++
+For example, `--filter=blob:none` will filter out all blobs (file
+contents) until needed by Git. Also, `--filter=blob:limit=<size>` will
+filter out all blobs of size at least _<size>_.
 
 `--also-filter-submodules`::
 	Also apply the partial clone filter to any submodules in the repository.
@@ -206,18 +223,17 @@
 	that all these refs are overwritten by a `git remote update` in the
 	target repository.
 
-`-o` _<name>_::
-`--origin` _<name>_::
+`-o<name>`::
+`--origin=<name>`::
 	Instead of using the remote name `origin` to keep track of the upstream
 	repository, use _<name>_.  Overrides `clone.defaultRemoteName` from the
 	config.
 
-`-b` _<name>_::
-`--branch` _<name>_::
-	Instead of pointing the newly created `HEAD` to the branch pointed
-	to by the cloned repository's `HEAD`, point to _<name>_ branch
-	instead. In a non-bare repository, this is the branch that will
-	be checked out.
+`-b<name>`::
+`--branch=<name>`::
+	Point the newly created `HEAD` to _<name>_ branch instead of the branch
+	pointed to by the cloned repository's `HEAD`. In a non-bare repository,
+	this is the branch that will be checked out.
 	`--branch` can also take tags and detaches the `HEAD` at that commit
 	in the resulting repository.
 
@@ -230,18 +246,17 @@
 	name.
 	This option is incompatible with `--branch` and `--mirror`.
 
-`-u` _<upload-pack>_::
-`--upload-pack` _<upload-pack>_::
-	When given, and the repository to clone from is accessed
-	via ssh, this specifies a non-default path for the command
-	run on the other end.
+`-u<upload-pack>`::
+`--upload-pack=<upload-pack>`::
+	Specify a non-default path for the command run on the other end when the
+	repository to clone from is accessed via ssh.
 
 `--template=<template-directory>`::
 	Specify the directory from which templates will be used;
 	(See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].)
 
-`-c` `<key>=<value>`::
-`--config` `<key>=<value>`::
+`-c<key>=<value>`::
+`--config=<key>=<value>`::
 	Set a configuration variable in the newly-created repository;
 	this takes effect immediately after the repository is
 	initialized, but before the remote history is fetched or any
@@ -257,7 +272,7 @@
 `remote.<name>.mirror` and `remote.<name>.tagOpt`.  Use the
 corresponding `--mirror` and `--no-tags` options instead.
 
-`--depth <depth>`::
+`--depth=<depth>`::
 	Create a 'shallow' clone with a history truncated to the
 	specified number of commits. Implies `--single-branch` unless
 	`--no-single-branch` is given to fetch the histories near the
@@ -339,8 +354,8 @@
 +
 include::ref-storage-format.adoc[]
 
-`-j` _<n>_::
-`--jobs` _<n>_::
+`-j<n>`::
+`--jobs=<n>`::
 	The number of submodules fetched at the same time.
 	Defaults to the `submodule.fetchJobs` option.
 
diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc
index 54c207a..8329c10 100644
--- a/Documentation/git-commit.adoc
+++ b/Documentation/git-commit.adoc
@@ -146,7 +146,8 @@
 	linkgit:git-status[1] for details. Implies `--dry-run`.
 
 `--branch`::
-	Show the branch and tracking info even in short-format.
+	Show the branch and tracking info even in short-format. See
+	linkgit:git-status[1] for details.
 
 `--porcelain`::
 	When doing a dry-run, give the output in a porcelain-ready
@@ -154,12 +155,13 @@
 	`--dry-run`.
 
 `--long`::
-	When doing a dry-run, give the output in the long-format.
-	Implies `--dry-run`.
+	When doing a dry-run, give the output in the long-format. This
+	is the default output of linkgit:git-status[1]. Implies
+	`--dry-run`.
 
 `-z`::
 `--null`::
-	When showing `short` or `porcelain` status output, print the
+	When showing `short` or `porcelain` linkgit:git-status[1] output, print the
 	filename verbatim and terminate the entries with _NUL_, instead of _LF_.
 	If no format is given, implies the `--porcelain` output format.
 	Without the `-z` option, filenames with "unusual" characters are
diff --git a/Documentation/git-config.adoc b/Documentation/git-config.adoc
index cc054fa..5300dd4 100644
--- a/Documentation/git-config.adoc
+++ b/Documentation/git-config.adoc
@@ -240,6 +240,9 @@
   that the given value is canonicalize-able as an ANSI color, but it is written
   as-is.
 +
+If the command is in `list` mode, then the `--type <type>` argument will apply
+to each listed config value. If the value does not successfully parse in that
+format, then it will be omitted from the list.
 
 --bool::
 --int::
@@ -332,7 +335,7 @@
 	Replaced by `git config get --all --show-names --regexp <name-regexp>`.
 
 --get-urlmatch <name> <URL>::
-	Replaced by `git config get --all --show-names --url=<URL> <name>`.
+	Replaced by `git config get --url=<URL> <name>`.
 
 --get-color <name> [<default>]::
 	Replaced by `git config get --type=color [--default=<default>] <name>`.
diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc
index b74179a..479c408 100644
--- a/Documentation/git-fast-import.adoc
+++ b/Documentation/git-fast-import.adoc
@@ -66,15 +66,26 @@
 remote-helpers that use the `import` capability, as they are
 already trusted to run their own code.
 
---signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::
-	Specify how to handle signed tags.  Behaves in the same way
-	as the same option in linkgit:git-fast-export[1], except that
-	default is 'verbatim' (instead of 'abort').
+`--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)`::
+	Specify how to handle signed tags. Behaves in the same way as
+	the `--signed-commits=<mode>` below, except that the
+	`strip-if-invalid` mode is not yet supported. Like for signed
+	commits, the default mode is `verbatim`.
 
---signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::
-	Specify how to handle signed commits.  Behaves in the same way
-	as the same option in linkgit:git-fast-export[1], except that
-	default is 'verbatim' (instead of 'abort').
+`--signed-commits=<mode>`::
+	Specify how to handle signed commits. The following <mode>s
+	are supported:
++
+* `verbatim`, which is the default, will silently import commit
+  signatures.
+* `warn-verbatim` will import them, but will display a warning.
+* `abort` will make this program die when encountering a signed
+  commit.
+* `strip` will silently make the commits unsigned.
+* `warn-strip` will make them unsigned, but will display a warning.
+* `strip-if-invalid` will check signatures and, if they are invalid,
+  will strip them and display a warning. The validation is performed
+  in the same way as linkgit:git-verify-commit[1] does it.
 
 Options for Frontends
 ~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/git-fetch.adoc b/Documentation/git-fetch.adoc
index 16f5d9d..db03541 100644
--- a/Documentation/git-fetch.adoc
+++ b/Documentation/git-fetch.adoc
@@ -8,11 +8,11 @@
 
 SYNOPSIS
 --------
-[verse]
-'git fetch' [<options>] [<repository> [<refspec>...]]
-'git fetch' [<options>] <group>
-'git fetch' --multiple [<options>] [(<repository> | <group>)...]
-'git fetch' --all [<options>]
+[synopsis]
+git fetch [<options>] [<repository> [<refspec>...]]
+git fetch [<options>] <group>
+git fetch --multiple [<options>] [(<repository>|<group>)...]
+git fetch --all [<options>]
 
 
 DESCRIPTION
@@ -20,19 +20,19 @@
 Fetch branches and/or tags (collectively, "refs") from one or more
 other repositories, along with the objects necessary to complete their
 histories.  Remote-tracking branches are updated (see the description
-of <refspec> below for ways to control this behavior).
+of _<refspec>_ below for ways to control this behavior).
 
 By default, any tag that points into the histories being fetched is
 also fetched; the effect is to fetch tags that
 point at branches that you are interested in.  This default behavior
-can be changed by using the --tags or --no-tags options or by
-configuring remote.<name>.tagOpt.  By using a refspec that fetches tags
+can be changed by using the `--tags` or `--no-tags` options or by
+configuring `remote.<name>.tagOpt`.  By using a refspec that fetches tags
 explicitly, you can fetch tags that do not point into branches you
 are interested in as well.
 
-'git fetch' can fetch from either a single named repository or URL,
-or from several repositories at once if <group> is given and
-there is a remotes.<group> entry in the configuration file.
+`git fetch` can fetch from either a single named repository or URL,
+or from several repositories at once if _<group>_ is given and
+there is a `remotes.<group>` entry in the configuration file.
 (See linkgit:git-config[1]).
 
 When no remote is specified, by default the `origin` remote will be used,
@@ -48,15 +48,15 @@
 
 include::pull-fetch-param.adoc[]
 
---stdin::
+`--stdin`::
 	Read refspecs, one per line, from stdin in addition to those provided
-	as arguments. The "tag <name>" format is not supported.
+	as arguments. The "tag _<name>_" format is not supported.
 
 include::urls-remotes.adoc[]
 
-
-CONFIGURED REMOTE-TRACKING BRANCHES[[CRTB]]
--------------------------------------------
+[[CRTB]]
+CONFIGURED REMOTE-TRACKING BRANCHES
+-----------------------------------
 
 You often interact with the same remote repository by
 regularly and repeatedly fetching from it.  In order to keep track
@@ -84,13 +84,13 @@
 
 * When `git fetch` is run with explicit branches and/or tags
   to fetch on the command line, e.g. `git fetch origin master`, the
-  <refspec>s given on the command line determine what are to be
+  _<refspec>s_ given on the command line determine what are to be
   fetched (e.g. `master` in the example,
   which is a short-hand for `master:`, which in turn means
-  "fetch the 'master' branch but I do not explicitly say what
+  "fetch the `master` branch but I do not explicitly say what
   remote-tracking branch to update with it from the command line"),
   and the example command will
-  fetch _only_ the 'master' branch.  The `remote.<repository>.fetch`
+  fetch _only_ the `master` branch.  The `remote.<repository>.fetch`
   values determine which
   remote-tracking branch, if any, is updated.  When used in this
   way, the `remote.<repository>.fetch` values do not have any
@@ -144,9 +144,9 @@
 exist on the remote.
 
 This might not be what you expect, i.e. you want to prune remote
-`<name>`, but also explicitly fetch tags from it, so when you fetch
+_<name>_, but also explicitly fetch tags from it, so when you fetch
 from it you delete all your local tags, most of which may not have
-come from the `<name>` remote in the first place.
+come from the _<name>_ remote in the first place.
 
 So be careful when using this with a refspec like
 `refs/tags/*:refs/tags/*`, or any other refspec which might map
@@ -213,11 +213,11 @@
 <flag> <old-object-id> <new-object-id> <local-reference>
 -------------------------------
 
-The status of up-to-date refs is shown only if the --verbose option is
+The status of up-to-date refs is shown only if the `--verbose` option is
 used.
 
 In compact output mode, specified with configuration variable
-fetch.output, if either entire `<from>` or `<to>` is found in the
+fetch.output, if either entire _<from>_ or _<to>_ is found in the
 other string, it will be substituted with `*` in the other string. For
 example, `master -> origin/master` becomes `master -> origin/*`.
 
@@ -303,7 +303,7 @@
 
 BUGS
 ----
-Using --recurse-submodules can only fetch new commits in submodules that are
+Using `--recurse-submodules` can only fetch new commits in submodules that are
 present locally e.g. in `$GIT_DIR/modules/`. If the upstream adds a new
 submodule, that submodule cannot be fetched until it is cloned e.g. by `git
 submodule update`. This is expected to be fixed in a future Git version.
diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc
index 9a7807c..3614600 100644
--- a/Documentation/git-format-patch.adoc
+++ b/Documentation/git-format-patch.adoc
@@ -282,11 +282,12 @@
 
 --from::
 --from=<ident>::
-	Use `ident` in the `From:` header of each commit email. If the
-	author ident of the commit is not textually identical to the
-	provided `ident`, place a `From:` header in the body of the
-	message with the original author. If no `ident` is given, use
-	the committer ident.
+	Use `ident` in the `From:` header of each email. In case of a
+	commit email, if the author ident of the commit is not textually
+	identical to the provided `ident`, place a `From:` header in the
+	body of the message with the original author. If no `ident` is
+	given, or if the option is not passed at all, use the ident of
+	the current committer.
 +
 Note that this option is only useful if you are actually sending the
 emails and want to identify yourself as the sender, but retain the
@@ -798,6 +799,10 @@
 include enough information for the receiving end to reproduce the same
 merge commit.
 
+=== PATCH APPLICATION
+
+include::format-patch-caveats.adoc[]
+
 SEE ALSO
 --------
 linkgit:git-am[1], linkgit:git-send-email[1]
diff --git a/Documentation/git-history.adoc b/Documentation/git-history.adoc
new file mode 100644
index 0000000..cc019de
--- /dev/null
+++ b/Documentation/git-history.adoc
@@ -0,0 +1,77 @@
+git-history(1)
+==============
+
+NAME
+----
+git-history - EXPERIMENTAL: Rewrite history
+
+SYNOPSIS
+--------
+[synopsis]
+git history reword <commit> [--dry-run] [--update-refs=(branches|head)]
+
+DESCRIPTION
+-----------
+
+Rewrite history by rearranging or modifying specific commits in the
+history.
+
+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
+
+This command is related to linkgit:git-rebase[1] in that both commands can be
+used to rewrite history. There are a couple of major differences though:
+
+* linkgit:git-history[1] can work in a bare repository as it does not need to
+  touch either the index or the worktree.
+* linkgit:git-history[1] does not execute any linkgit:githooks[5] at the
+  current point in time. This may change in the future.
+* linkgit:git-history[1] by default updates all branches that are descendants
+  of the original commit to point to the rewritten commit.
+
+Overall, linkgit:git-history[1] aims to provide a more opinionated way to modify
+your commit history that is simpler to use compared to linkgit:git-rebase[1] in
+general.
+
+Use linkgit:git-rebase[1] if you want to reapply a range of commits onto a
+different base, or interactive rebases if you want to edit a range of commits
+at once.
+
+LIMITATIONS
+-----------
+
+This command does not (yet) work with histories that contain merges. You
+should use linkgit:git-rebase[1] with the `--rebase-merges` flag instead.
+
+Furthermore, the command does not support operations that can result in merge
+conflicts. This limitation is by design as history rewrites are not intended to
+be stateful operations. The limitation can be lifted once (if) Git learns about
+first-class conflicts.
+
+COMMANDS
+--------
+
+The following commands are available to rewrite history in different ways:
+
+`reword <commit>`::
+	Rewrite the commit message of the specified commit. All the other
+	details of this commit remain unchanged. This command will spawn an
+	editor with the current message of that commit.
+
+OPTIONS
+-------
+
+`--dry-run`::
+	Do not update any references, but instead print any ref updates in a
+	format that can be consumed by linkgit:git-update-ref[1]. Necessary new
+	objects will be written into the repository, so applying these printed
+	ref updates is generally safe.
+
+`--update-refs=(branches|head)`::
+	Control which references will be updated by the command, if any. With
+	`branches`, all local branches that point to commits which are
+	descendants of the original commit will be rewritten. With `head`, only
+	the current `HEAD` reference will be rewritten. Defaults to `branches`.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-hook.adoc b/Documentation/git-hook.adoc
index f6cc72d..12d2701 100644
--- a/Documentation/git-hook.adoc
+++ b/Documentation/git-hook.adoc
@@ -9,6 +9,7 @@
 --------
 [verse]
 'git hook' run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]
+'git hook' list [-z] <hook-name>
 
 DESCRIPTION
 -----------
@@ -16,18 +17,107 @@
 A command interface for running git hooks (see linkgit:githooks[5]),
 for use by other scripted git commands.
 
+This command parses the default configuration files for sets of configs like
+so:
+
+  [hook "linter"]
+    event = pre-commit
+    command = ~/bin/linter --cpp20
+
+In this example, `[hook "linter"]` represents one script - `~/bin/linter
+--cpp20` - which can be shared by many repos, and even by many hook events, if
+appropriate.
+
+To add an unrelated hook which runs on a different event, for example a
+spell-checker for your commit messages, you would write a configuration like so:
+
+  [hook "linter"]
+    event = pre-commit
+    command = ~/bin/linter --cpp20
+  [hook "spellcheck"]
+    event = commit-msg
+    command = ~/bin/spellchecker
+
+With this config, when you run 'git commit', first `~/bin/linter --cpp20` will
+have a chance to check your files to be committed (during the `pre-commit` hook
+event`), and then `~/bin/spellchecker` will have a chance to check your commit
+message (during the `commit-msg` hook event).
+
+Commands are run in the order Git encounters their associated
+`hook.<name>.event` configs during the configuration parse (see
+linkgit:git-config[1]). Although multiple `hook.linter.event` configs can be
+added, only one `hook.linter.command` event is valid - Git uses "last-one-wins"
+to determine which command to run.
+
+So if you wanted your linter to run when you commit as well as when you push,
+you would configure it like so:
+
+  [hook "linter"]
+    event = pre-commit
+    event = pre-push
+    command = ~/bin/linter --cpp20
+
+With this config, `~/bin/linter --cpp20` would be run by Git before a commit is
+generated (during `pre-commit`) as well as before a push is performed (during
+`pre-push`).
+
+And if you wanted to run your linter as well as a secret-leak detector during
+only the "pre-commit" hook event, you would configure it instead like so:
+
+  [hook "linter"]
+    event = pre-commit
+    command = ~/bin/linter --cpp20
+  [hook "no-leaks"]
+    event = pre-commit
+    command = ~/bin/leak-detector
+
+With this config, before a commit is generated (during `pre-commit`), Git would
+first start `~/bin/linter --cpp20` and second start `~/bin/leak-detector`. It
+would evaluate the output of each when deciding whether to proceed with the
+commit.
+
+For a full list of hook events which you can set your `hook.<name>.event` to,
+and how hooks are invoked during those events, see linkgit:githooks[5].
+
+Git will ignore any `hook.<name>.event` that specifies an event it doesn't
+recognize. This is intended so that tools which wrap Git can use the hook
+infrastructure to run their own hooks; see "WRAPPERS" for more guidance.
+
+In general, when instructions suggest adding a script to
+`.git/hooks/<hook-event>`, you can specify it in the config instead by running:
+
+----
+git config set hook.<some-name>.command <path-to-script>
+git config set --append hook.<some-name>.event <hook-event>
+----
+
+This way you can share the script between multiple repos. That is, `cp
+~/my-script.sh ~/project/.git/hooks/pre-commit` would become:
+
+----
+git config set hook.my-script.command ~/my-script.sh
+git config set --append hook.my-script.event pre-commit
+----
+
 SUBCOMMANDS
 -----------
 
 run::
-	Run the `<hook-name>` hook. See linkgit:githooks[5] for
-	supported hook names.
+	Runs hooks configured for `<hook-name>`, in the order they are
+	discovered during the config parse. The default `<hook-name>` from
+	the hookdir is run last. See linkgit:githooks[5] for supported
+	hook names.
 +
 
 Any positional arguments to the hook should be passed after a
 mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
 linkgit:githooks[5] for arguments hooks might expect (if any).
 
+list [-z]::
+	Print a list of hooks which will be run on `<hook-name>` event. If no
+	hooks are configured for that event, print a warning and return 1.
+	Use `-z` to terminate output lines with NUL instead of newlines.
+
 OPTIONS
 -------
 
@@ -41,6 +131,49 @@
 	tools that want to do a blind one-shot run of a hook that may
 	or may not be present.
 
+-z::
+	Terminate "list" output lines with NUL instead of newlines.
+
+WRAPPERS
+--------
+
+`git hook run` has been designed to make it easy for tools which wrap Git to
+configure and execute hooks using the Git hook infrastructure. It is possible to
+provide arguments and stdin via the command line, as well as specifying parallel
+or series execution if the user has provided multiple hooks.
+
+Assuming your wrapper wants to support a hook named "mywrapper-start-tests", you
+can have your users specify their hooks like so:
+
+  [hook "setup-test-dashboard"]
+    event = mywrapper-start-tests
+    command = ~/mywrapper/setup-dashboard.py --tap
+
+Then, in your 'mywrapper' tool, you can invoke any users' configured hooks by
+running:
+
+----
+git hook run mywrapper-start-tests \
+  # providing something to stdin
+  --stdin some-tempfile-123 \
+  # execute hooks in serial
+  # plus some arguments of your own...
+  -- \
+  --testname bar \
+  baz
+----
+
+Take care to name your wrapper's hook events in a way which is unlikely to
+overlap with Git's native hooks (see linkgit:githooks[5]) - a hook event named
+`mywrappertool-validate-commit` is much less likely to be added to native Git
+than a hook event named `validate-commit`. If Git begins to use a hook event
+named the same thing as your wrapper hook, it may invoke your users' hooks in
+unintended and unsupported ways.
+
+CONFIGURATION
+-------------
+include::config/hook.adoc[]
+
 SEE ALSO
 --------
 linkgit:githooks[5]
diff --git a/Documentation/git-last-modified.adoc b/Documentation/git-last-modified.adoc
index 602843e..d7d16fc 100644
--- a/Documentation/git-last-modified.adoc
+++ b/Documentation/git-last-modified.adoc
@@ -9,7 +9,8 @@
 SYNOPSIS
 --------
 [synopsis]
-git last-modified [--recursive] [--show-trees] [<revision-range>] [[--] <path>...]
+git last-modified [--recursive] [--show-trees] [--max-depth=<depth>] [-z]
+		  [<revision-range>] [[--] <pathspec>...]
 
 DESCRIPTION
 -----------
@@ -24,13 +25,23 @@
 
 `-r`::
 `--recursive`::
-	Instead of showing tree entries, step into subtrees and show all entries
-	inside them recursively.
+	Recursively traverse into all subtrees. By default, the command only
+	shows tree entries matching the `<pathspec>`. With this option, it
+	descends into subtrees and displays all entries within them.
+	Equivalent to `--max-depth=-1`.
 
 `-t`::
 `--show-trees`::
-	Show tree entries even when recursing into them. It has no effect
-	without `--recursive`.
+	Show tree entries even when recursing into them.
+
+`--max-depth=<depth>`::
+	For each pathspec given on the command line, traverse at most `<depth>`
+	levels into subtrees. A negative value means no limit.
+	The default is 0, which shows all paths matching the pathspec
+	without descending into subtrees.
+
+`-z`::
+	Terminate each line with a _NUL_ character rather than a newline.
 
 `<revision-range>`::
 	Only traverse commits in the specified revision range. When no
@@ -39,10 +50,26 @@
 	spell `<revision-range>`, see the 'Specifying Ranges' section of
 	linkgit:gitrevisions[7].
 
-`[--] <path>...`::
-	For each _<path>_ given, the commit which last modified it is returned.
-	Without an optional path parameter, all files and subdirectories
-	in path traversal the are included in the output.
+`[--] <pathspec>...`::
+	Show the commit that last modified each path matching _<pathspec>_.
+	If no _<pathspec>_ is given, all files and subdirectories are included.
+	See linkgit:gitglossary[7] for details on pathspec syntax.
+
+OUTPUT
+------
+
+The output is in the format:
+
+------------
+ <oid> TAB <path> LF
+------------
+
+If a path contains any special characters, the path is C-style quoted. To
+avoid quoting, pass option `-z` to terminate each line with a NUL.
+
+------------
+ <oid> TAB <path> NUL
+------------
 
 SEE ALSO
 --------
diff --git a/Documentation/git-maintenance.adoc b/Documentation/git-maintenance.adoc
index 540b5cf..bda616f 100644
--- a/Documentation/git-maintenance.adoc
+++ b/Documentation/git-maintenance.adoc
@@ -12,6 +12,7 @@
 'git maintenance' run [<options>]
 'git maintenance' start [--scheduler=<scheduler>]
 'git maintenance' (stop|register|unregister) [<options>]
+'git maintenance' is-needed [<options>]
 
 
 DESCRIPTION
@@ -84,6 +85,16 @@
 is not already registered. Use the `--force` option to return success even
 when the current repository is not registered.
 
+is-needed::
+    Check whether maintenance needs to be run without actually running it.
+    Exits with a 0 status code if maintenance needs to be run, 1 otherwise.
+    Ideally used with the '--auto' flag.
++
+If one or more `--task` options are specified, then those tasks are checked
+in that order. Otherwise, the tasks are determined by which
+`maintenance.<task>.enabled` config options are true. By default, only
+`maintenance.gc.enabled` is true.
+
 TASKS
 -----
 
@@ -183,6 +194,8 @@
 	in the `gc.auto` config setting, or when the number of pack-files
 	exceeds the `gc.autoPackLimit` config setting. Not compatible with
 	the `--schedule` option.
+	When combined with the `is-needed` subcommand, check if the required
+	thresholds are met without actually running maintenance.
 
 --schedule::
 	When combined with the `run` subcommand, run maintenance tasks
diff --git a/Documentation/git-merge-file.adoc b/Documentation/git-merge-file.adoc
index 71915a0..9dc5d8a 100644
--- a/Documentation/git-merge-file.adoc
+++ b/Documentation/git-merge-file.adoc
@@ -85,6 +85,9 @@
 
 --zdiff3::
 	Show conflicts in "zdiff3" style.
++
+The `--diff3` and `--zdiff3` options default to the value of the
+`merge.conflictStyle` configuration variable (see linkgit:git-config[1]).
 
 --ours::
 --theirs::
diff --git a/Documentation/git-patch-id.adoc b/Documentation/git-patch-id.adoc
index 92a1af3..0585999 100644
--- a/Documentation/git-patch-id.adoc
+++ b/Documentation/git-patch-id.adoc
@@ -3,7 +3,7 @@
 
 NAME
 ----
-git-patch-id - Compute unique ID for a patch
+git-patch-id - Compute unique IDs for patches
 
 SYNOPSIS
 --------
@@ -12,7 +12,7 @@
 
 DESCRIPTION
 -----------
-Read a patch from the standard input and compute the patch ID for it.
+Read patches from standard input and compute the patch IDs.
 
 A "patch ID" is nothing but a sum of SHA-1 of the file diffs associated with a
 patch, with line numbers ignored.  As such, it's "reasonably stable", but at
@@ -21,18 +21,19 @@
 
 The main usecase for this command is to look for likely duplicate commits.
 
-When dealing with `git diff-tree` output, it takes advantage of
+When dealing with `git diff-tree --patch` output, it takes advantage of
 the fact that the patch is prefixed with the object name of the
 commit, and outputs two 40-byte hexadecimal strings.  The first
 string is the patch ID, and the second string is the commit ID.
-This can be used to make a mapping from patch ID to commit ID.
+This can be used to make a mapping from patch ID to commit ID for a
+set or range of commits.
 
 OPTIONS
 -------
 
 `--verbatim`::
-	Calculate the patch-id of the input as it is given, do not strip
-	any whitespace.
+	Calculate the patch ID of the input as it is given, do not strip
+	any whitespace. Implies `--stable` and forbids `--unstable`.
 +
 This is the default if `patchid.verbatim` is `true`.
 
@@ -45,28 +46,72 @@
   with two different settings for `-O<orderfile>` result in the same
   patch ID signature, thereby allowing the computed result to be used
   as a key to index some meta-information about the change between
-  the two trees;
+  the two trees.
 
-- Result is different from the value produced by git 1.9 and older
+- The result is different from the value produced by Git 1.9 and older
   or produced when an "unstable" hash (see `--unstable` below) is
   configured - even when used on a diff output taken without any use
   of `-O<orderfile>`, thereby making existing databases storing such
-  "unstable" or historical patch-ids unusable.
+  "unstable" or historical patch IDs unusable.
 
-- All whitespace within the patch is ignored and does not affect the id.
+- All whitespace within the patch is ignored and does not affect the ID.
 --
 +
 This is the default if `patchid.stable` is set to `true`.
 
 `--unstable`::
 	Use an "unstable" hash as the patch ID. With this option,
-	the result produced is compatible with the patch-id value produced
-	by git 1.9 and older and whitespace is ignored.  Users with pre-existing
-	databases storing patch-ids produced by git 1.9 and older (who do not deal
+	the result produced is compatible with the patch ID value produced
+	by Git 1.9 and older and whitespace is ignored.  Users with pre-existing
+	databases storing patch IDs produced by Git 1.9 and older (who do not deal
 	with reordered patches) may want to use this option.
 +
 This is the default.
 
+EXAMPLES
+--------
+
+linkgit:git-cherry[1] shows what commits from a branch have patch ID
+equivalent commits in some upstream branch. But it only tells you
+whether such a commit exists or not. What if you wanted to know the
+relevant commits in the upstream? We can use this command to make a
+mapping between your branch and the upstream branch:
+
+----
+#!/bin/sh
+
+upstream="$1"
+branch="$2"
+test -z "$branch" && branch=HEAD
+limit="$3"
+if test -n "$limit"
+then
+    tail_opts="$limit".."$upstream"
+else
+    since=$(git log --format=%aI "$upstream".."$branch" | tail -1)
+    tail_opts=--since="$since"' '"$upstream"
+fi
+for_branch=$(mktemp)
+for_upstream=$(mktemp)
+
+git rev-list --no-merges "$upstream".."$branch" |
+    git diff-tree --patch --stdin |
+    git patch-id  --stable | sort >"$for_branch"
+git rev-list --no-merges $tail_opts |
+    git diff-tree --patch --stdin |
+    git patch-id  --stable | sort >"$for_upstream"
+join -a1 "$for_branch" "$for_upstream" | cut -d' ' -f2,3
+rm "$for_branch"
+rm "$for_upstream"
+----
+
+Now the first column shows the commit from your branch and the second
+column shows the patch ID equivalent commit, if it exists.
+
+SEE ALSO
+--------
+linkgit:git-cherry[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Documentation/git-pull.adoc b/Documentation/git-pull.adoc
index cd3bbc9..88f4fd3 100644
--- a/Documentation/git-pull.adoc
+++ b/Documentation/git-pull.adoc
@@ -8,8 +8,8 @@
 
 SYNOPSIS
 --------
-[verse]
-'git pull' [<options>] [<repository> [<refspec>...]]
+[synopsis]
+git pull [<options>] [<repository> [<refspec>...]]
 
 
 DESCRIPTION
@@ -37,13 +37,13 @@
 or `pull.ff` with your preferred behaviour.
 
 If there's a merge conflict during the merge or rebase that you don't
-want to handle, you can safely abort it with `git merge --abort` or `git
---rebase abort`.
+want to handle, you can safely abort it with `git merge --abort` or
+`git rebase --abort`.
 
 OPTIONS
 -------
 
-<repository>::
+_<repository>_::
 	The "remote" repository to pull from.  This can be either
 	a URL (see the section <<URLS,GIT URLS>> below) or the name
 	of a remote (see the section <<REMOTES,REMOTES>> below).
@@ -52,29 +52,29 @@
 See <<UPSTREAM-BRANCHES,UPSTREAM BRANCHES>> below for more on how to
 configure upstreams.
 
-<refspec>::
+_<refspec>_::
 	Which branch or other reference(s) to fetch and integrate into the
 	current branch, for example `main` in `git pull origin main`.
 	Defaults to the configured upstream for the current branch.
 +
 This can be a branch, tag, or other collection of reference(s).
-See <<fetch-refspec,<refspec>>> below under "Options related to fetching"
+See <<fetch-refspec,_<refspec>_>> below under "Options related to fetching"
 for the full syntax, and <<DEFAULT-BEHAVIOUR,DEFAULT BEHAVIOUR>> below
 for how `git pull` uses this argument to determine which remote branch
 to integrate.
 
--q::
---quiet::
+`-q`::
+`--quiet`::
 	This is passed to both underlying git-fetch to squelch reporting of
 	during transfer, and underlying git-merge to squelch output during
 	merging.
 
--v::
---verbose::
-	Pass --verbose to git-fetch and git-merge.
+`-v`::
+`--verbose`::
+	Pass `--verbose` to git-fetch and git-merge.
 
---recurse-submodules[=(yes|on-demand|no)]::
---no-recurse-submodules::
+`--recurse-submodules[=(yes|on-demand|no)]`::
+`--no-recurse-submodules`::
 	This option controls if new commits of populated submodules should
 	be fetched, and if the working trees of active submodules should be
 	updated, too (see linkgit:git-fetch[1], linkgit:git-config[1] and
@@ -91,21 +91,20 @@
 
 include::merge-options.adoc[]
 
--r::
---rebase[=(false|true|merges|interactive)]::
-	When true, rebase the current branch on top of the upstream
+`-r`::
+`--rebase[=(true|merges|false|interactive)]`::
+`true`;; rebase the current branch on top of the upstream
 	branch after fetching. If there is a remote-tracking branch
 	corresponding to the upstream branch and the upstream branch
 	was rebased since last fetched, the rebase uses that information
-	to avoid rebasing non-local changes.
-+
-When set to `merges`, rebase using `git rebase --rebase-merges` so that
+	to avoid rebasing non-local changes. This is the default.
+
+`merges`;; rebase using `git rebase --rebase-merges` so that
 the local merge commits are included in the rebase (see
 linkgit:git-rebase[1] for details).
-+
-When false, merge the upstream branch into the current branch.
-+
-When `interactive`, enable the interactive mode of rebase.
+`false`;; merge the upstream branch into the current branch.
+`interactive`;; enable the interactive mode of rebase.
+
 +
 See `pull.rebase`, `branch.<name>.rebase` and `branch.autoSetupRebase` in
 linkgit:git-config[1] if you want to make `git pull` always use
@@ -117,8 +116,8 @@
 published that history already.  Do *not* use this option
 unless you have read linkgit:git-rebase[1] carefully.
 
---no-rebase::
-	This is shorthand for --rebase=false.
+`--no-rebase`::
+	This is shorthand for `--rebase=false`.
 
 Options related to fetching
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -178,7 +177,7 @@
 rules apply:
 
 . If `branch.<name>.merge` configuration for the current
-  branch `<name>` exists, that is the name of the branch at the
+  branch _<name>_ exists, that is the name of the branch at the
   remote site that is merged.
 
 . If the refspec is a globbing one, nothing is merged.
@@ -198,9 +197,9 @@
 $ git pull origin
 ------------------------------------------------
 +
-Normally the branch merged in is the HEAD of the remote repository,
-but the choice is determined by the branch.<name>.remote and
-branch.<name>.merge options; see linkgit:git-config[1] for details.
+Normally the branch merged in is the `HEAD` of the remote repository,
+but the choice is determined by the `branch.<name>.remote` and
+`branch.<name>.merge` options; see linkgit:git-config[1] for details.
 
 * Merge into the current branch the remote branch `next`:
 +
@@ -208,7 +207,7 @@
 $ git pull origin next
 ------------------------------------------------
 +
-This leaves a copy of `next` temporarily in FETCH_HEAD, and
+This leaves a copy of `next` temporarily in `FETCH_HEAD`, and
 updates the remote-tracking branch `origin/next`.
 The same can be done by invoking fetch and merge:
 +
@@ -219,14 +218,14 @@
 
 
 If you tried a pull which resulted in complex conflicts and
-would want to start over, you can recover with 'git reset'.
+would want to start over, you can recover with `git reset`.
 
 
 include::transfer-data-leaks.adoc[]
 
 BUGS
 ----
-Using --recurse-submodules can only fetch new commits in already checked
+Using `--recurse-submodules` can only fetch new commits in already checked
 out submodules right now. When e.g. upstream added a new submodule in the
 just fetched commits of the superproject the submodule itself cannot be
 fetched, making it impossible to check out that submodule later without
diff --git a/Documentation/git-push.adoc b/Documentation/git-push.adoc
index 864b0d0..e5ba3a6 100644
--- a/Documentation/git-push.adoc
+++ b/Documentation/git-push.adoc
@@ -8,13 +8,13 @@
 
 SYNOPSIS
 --------
-[verse]
-'git push' [--all | --branches | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
-	   [--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-q | --quiet] [-v | --verbose]
-	   [-u | --set-upstream] [-o <string> | --push-option=<string>]
-	   [--[no-]signed|--signed=(true|false|if-asked)]
-	   [--force-with-lease[=<refname>[:<expect>]] [--force-if-includes]]
-	   [--no-verify] [<repository> [<refspec>...]]
+[synopsis]
+git push [--all | --branches | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
+	 [--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-q | --quiet] [-v | --verbose]
+	 [-u | --set-upstream] [-o <string> | --push-option=<string>]
+	 [--[no-]signed | --signed=(true|false|if-asked)]
+	 [--force-with-lease[=<refname>[:<expect>]] [--force-if-includes]]
+	 [--no-verify] [<repository> [<refspec>...]]
 
 DESCRIPTION
 -----------
@@ -35,7 +35,7 @@
 
 1. The `<refspec>` argument(s) (for example `main` in `git push origin main`)
    or the `--all`, `--mirror`, or `--tags` options
-2. The `remote.*.push` configuration for the repository being pushed to
+2. The `remote.<name>.push` configuration for the repository being pushed to
 3. The `push.default` configuration. The default is `push.default=simple`,
    which will push to a branch with the same name as the current branch.
    See the <<CONFIGURATION,CONFIGURATION>> section below for more on `push.default`.
@@ -49,25 +49,25 @@
 every time you push into it, by setting up 'hooks' there.  See
 documentation for linkgit:git-receive-pack[1].
 
-
-OPTIONS[[OPTIONS]]
-------------------
-<repository>::
+[[OPTIONS]]
+OPTIONS
+-------
+_<repository>_::
 	The "remote" repository that is the destination of a push
 	operation.  This parameter can be either a URL
 	(see the section <<URLS,GIT URLS>> below) or the name
 	of a remote (see the section <<REMOTES,REMOTES>> below).
 
-<refspec>...::
+`<refspec>...`::
 	Specify what destination ref to update with what source object.
 +
-The format for a refspec is [+]<src>[:<dst>], for example `main`,
+The format for a refspec is `[+]<src>[:<dst>]`, for example `main`,
 `main:other`, or `HEAD^:refs/heads/main`.
 +
-The `<src>` is often the name of the local branch to push, but it can be
+The _<src>_ is often the name of the local branch to push, but it can be
 any arbitrary "SHA-1 expression" (see linkgit:gitrevisions[7]).
 +
-The `<dst>` determines what ref to update on the remote side. It must be the
+The _<dst>_ determines what ref to update on the remote side. It must be the
 name of a branch, tag, or other ref, not an arbitrary expression.
 +
 The `+` is optional and does the same thing as `--force`.
@@ -78,23 +78,23 @@
 `main:other`). Here are the rules for how refspecs are expanded,
 as well as various other special refspec forms:
 +
- *  `<src>` without a `:<dst>` means to update the same ref as the
-    `<src>`, unless the `remote.<repository>.push` configuration specifies a
-    different <dst>. For example, if `main` is a branch, then the refspec
+ *  _<src>_ without a `:<dst>` means to update the same ref as the
+    _<src>_, unless the `remote.<repository>.push` configuration specifies a
+    different _<dst>_. For example, if `main` is a branch, then the refspec
     `main` expands to `main:refs/heads/main`.
- *  If `<dst>` unambiguously refers to a ref on the <repository> remote,
+ *  If _<dst>_ unambiguously refers to a ref on the <repository> remote,
     then expand it to that ref. For example, if `v1.0` is a tag on the
     remote, then `HEAD:v1.0` expands to `HEAD:refs/tags/v1.0`.
- *  If `<src>` resolves to a ref starting with `refs/heads/` or `refs/tags/`,
+ *  If _<src>_ resolves to a ref starting with `refs/heads/` or `refs/tags/`,
     then prepend that to <dst>. For example, if `main` is a branch, then
     `main:other` expands to `main:refs/heads/other`
  *  The special refspec `:` (or `+:` to allow non-fast-forward updates)
     directs Git to push "matching" branches: for every branch that exists on
     the local side, the remote side is updated if a branch of the same name
     already exists on the remote side.
- *  <src> may contain a * to indicate a simple pattern match.
+ *  _<src>_ may contain a `*` to indicate a simple pattern match.
     This works like a glob that matches any ref matching the pattern.
-    There must be only one * in both the `<src>` and `<dst>`.
+    There must be only one `*` in both the `<src>` and `<dst>`.
     It will map refs to the destination by replacing the * with the
     contents matched from the source. For example, `refs/heads/*:refs/heads/*`
     will push all branches.
@@ -102,11 +102,11 @@
     This specifies refs to exclude. A ref will be considered to
     match if it matches at least one positive refspec, and does not
     match any negative refspec. Negative refspecs can be pattern refspecs.
-    They must only contain a `<src>`.
+    They must only contain a _<src>_.
     Fully spelled out hex object names are also not supported.
     For example, `git push origin 'refs/heads/*' '^refs/heads/dev-*'`
     will push all branches except for those starting with `dev-`
- *  If `<src>` is empty, it deletes the `<dst>` ref from the remote
+ *  If _<src>_ is empty, it deletes the _<dst>_ ref from the remote
     repository. For example, `git push origin :dev` will
     delete the `dev` branch.
  *  `tag <tag>` expands to `refs/tags/<tag>:refs/tags/<tag>`.
@@ -121,12 +121,12 @@
 
 Not all updates are allowed: see PUSH RULES below for the details.
 
---all::
---branches::
+`--all`::
+`--branches`::
 	Push all branches (i.e. refs under `refs/heads/`); cannot be
 	used with other <refspec>.
 
---prune::
+`--prune`::
 	Remove remote branches that don't have a local counterpart. For example
 	a remote branch `tmp` will be removed if a local branch with the same
 	name doesn't exist any more. This also respects refspecs, e.g.
@@ -134,7 +134,7 @@
 	make sure that remote `refs/tmp/foo` will be removed if `refs/heads/foo`
 	doesn't exist.
 
---mirror::
+`--mirror`::
 	Instead of naming each ref to push, specifies that all
 	refs under `refs/` (which includes but is not
 	limited to `refs/heads/`, `refs/remotes/`, and `refs/tags/`)
@@ -145,26 +145,26 @@
 	if the configuration option `remote.<remote>.mirror` is
 	set.
 
--n::
---dry-run::
+`-n`::
+`--dry-run`::
 	Do everything except actually send the updates.
 
---porcelain::
+`--porcelain`::
 	Produce machine-readable output.  The output status line for each ref
 	will be tab-separated and sent to stdout instead of stderr.  The full
 	symbolic names of the refs will be given.
 
--d::
---delete::
+`-d`::
+`--delete`::
 	All listed refs are deleted from the remote repository. This is
 	the same as prefixing all refs with a colon.
 
---tags::
+`--tags`::
 	All refs under `refs/tags` are pushed, in
 	addition to refspecs explicitly listed on the command
 	line.
 
---follow-tags::
+`--follow-tags`::
 	Push all the refs that would be pushed without this option,
 	and also push annotated tags in `refs/tags` that are missing
 	from the remote but are pointing at commit-ish that are
@@ -172,29 +172,34 @@
 	with configuration variable `push.followTags`.  For more
 	information, see `push.followTags` in linkgit:git-config[1].
 
---signed::
---no-signed::
---signed=(true|false|if-asked)::
+`--signed`::
+`--no-signed`::
+`--signed=(true|false|if-asked)`::
 	GPG-sign the push request to update refs on the receiving
 	side, to allow it to be checked by the hooks and/or be
-	logged.  If `false` or `--no-signed`, no signing will be
-	attempted.  If `true` or `--signed`, the push will fail if the
-	server does not support signed pushes.  If set to `if-asked`,
-	sign if and only if the server supports signed pushes.  The push
-	will also fail if the actual call to `gpg --sign` fails.  See
-	linkgit:git-receive-pack[1] for the details on the receiving end.
+	logged. Possible values are:
+`false`;;
+`--no-signed`;;
+no signing will be attempted.
+`true`;;
+`--signed`;;
+the push will fail if the server does not support signed pushes.
+`if-asked`;;
+sign if and only if the server supports signed pushes.  The push
+will also fail if the actual call to `gpg --sign` fails.  See
+linkgit:git-receive-pack[1] for the details on the receiving end.
 
---atomic::
---no-atomic::
+`--atomic`::
+`--no-atomic`::
 	Use an atomic transaction on the remote side if available.
 	Either all refs are updated, or on error, no refs are updated.
 	If the server does not support atomic pushes the push will fail.
 
--o <option>::
---push-option=<option>::
+`-o <option>`::
+`--push-option=<option>`::
 	Transmit the given string to the server, which passes them to
 	the pre-receive as well as the post-receive hook. The given string
-	must not contain a NUL or LF character.
+	must not contain a _NUL_ or _LF_ character.
 	When multiple `--push-option=<option>` are given, they are
 	all sent to the other side in the order listed on the
 	command line.
@@ -202,22 +207,22 @@
 	line, the values of configuration variable `push.pushOption`
 	are used instead.
 
---receive-pack=<git-receive-pack>::
---exec=<git-receive-pack>::
+`--receive-pack=<git-receive-pack>`::
+`--exec=<git-receive-pack>`::
 	Path to the 'git-receive-pack' program on the remote
 	end.  Sometimes useful when pushing to a remote
 	repository over ssh, and you do not have the program in
-	a directory on the default $PATH.
+	a directory on the default `$PATH`.
 
---force-with-lease::
---no-force-with-lease::
---force-with-lease=<refname>::
---force-with-lease=<refname>:<expect>::
-	Usually, "git push" refuses to update a remote ref that is
+`--force-with-lease`::
+`--no-force-with-lease`::
+`--force-with-lease=<refname>`::
+`--force-with-lease=<refname>:<expect>`::
+	Usually, `git push` refuses to update a remote ref that is
 	not an ancestor of the local ref used to overwrite it.
 +
 This option overrides this restriction if the current value of the
-remote ref is the expected value.  "git push" fails otherwise.
+remote ref is the expected value.  `git push` fails otherwise.
 +
 Imagine that you have to rebase what you have already published.
 You will have to bypass the "must fast-forward" rule in order to
@@ -239,16 +244,16 @@
 for them.
 +
 `--force-with-lease=<refname>`, without specifying the expected value, will
-protect the named ref (alone), if it is going to be updated, by
+protect _<refname>_ (alone), if it is going to be updated, by
 requiring its current value to be the same as the remote-tracking
 branch we have for it.
 +
-`--force-with-lease=<refname>:<expect>` will protect the named ref (alone),
+`--force-with-lease=<refname>:<expect>` will protect _<refname>_ (alone),
 if it is going to be updated, by requiring its current value to be
-the same as the specified value `<expect>` (which is allowed to be
+the same as the specified value _<expect>_ (which is allowed to be
 different from the remote-tracking branch we have for the refname,
 or we do not even have to have such a remote-tracking branch when
-this form is used).  If `<expect>` is the empty string, then the named ref
+this form is used).  If _<expect>_ is the empty string, then the named ref
 must not already exist.
 +
 Note that all forms other than `--force-with-lease=<refname>:<expect>`
@@ -256,7 +261,7 @@
 still experimental and their semantics may change as we gain experience
 with this feature.
 +
-"--no-force-with-lease" will cancel all the previous --force-with-lease on the
+`--no-force-with-lease` will cancel all the previous `--force-with-lease` on the
 command line.
 +
 A general note on safety: supplying this option without an expected
@@ -276,23 +281,29 @@
 background for you a way to mitigate this is to simply set up another
 remote:
 +
-	git remote add origin-push $(git config remote.origin.url)
-	git fetch origin-push
+----
+git remote add origin-push $(git config remote.origin.url)
+git fetch origin-push
+----
 +
 Now when the background process runs `git fetch origin` the references
 on `origin-push` won't be updated, and thus commands like:
 +
-	git push --force-with-lease origin-push
+----
+git push --force-with-lease origin-push
+----
 +
 Will fail unless you manually run `git fetch origin-push`. This method
 is of course entirely defeated by something that runs `git fetch
 --all`, in that case you'd need to either disable it or do something
 more tedious like:
 +
-	git fetch              # update 'master' from remote
-	git tag base master    # mark our base point
-	git rebase -i master   # rewrite some commits
-	git push --force-with-lease=master:base master:master
+----
+git fetch              # update 'master' from remote
+git tag base master    # mark our base point
+git rebase -i master   # rewrite some commits
+git push --force-with-lease=master:base master:master
+----
 +
 I.e. create a `base` tag for versions of the upstream code that you've
 seen and are willing to overwrite, then rewrite history, and finally
@@ -308,26 +319,26 @@
 implicitly updated in the background are integrated locally before
 allowing a forced update.
 
--f::
---force::
+`-f`::
+`--force`::
 	Usually, `git push` will refuse to update a branch that is not an
 	ancestor of the commit being pushed.
 +
 This flag disables that check, the other safety checks in PUSH RULES
-below, and the checks in --force-with-lease. It can cause the remote
+below, and the checks in `--force-with-lease`. It can cause the remote
 repository to lose commits; use it with care.
 +
 Note that `--force` applies to all the refs that are pushed, hence
 using it with `push.default` set to `matching` or with multiple push
-destinations configured with `remote.*.push` may overwrite refs
+destinations configured with `remote.<name>.push` may overwrite refs
 other than the current branch (including local refs that are
 strictly behind their remote counterpart).  To force a push to only
 one branch, use a `+` in front of the refspec to push (e.g `git push
 origin +master` to force a push to the `master` branch). See the
 `<refspec>...` section above for details.
 
---force-if-includes::
---no-force-if-includes::
+`--force-if-includes`::
+`--no-force-if-includes`::
 	Force an update only if the tip of the remote-tracking ref
 	has been integrated locally.
 +
@@ -343,72 +354,78 @@
 +
 Specifying `--no-force-if-includes` disables this behavior.
 
---repo=<repository>::
-	This option is equivalent to the <repository> argument. If both
+`--repo=<repository>`::
+	This option is equivalent to the _<repository>_ argument. If both
 	are specified, the command-line argument takes precedence.
 
--u::
---set-upstream::
+`-u`::
+`--set-upstream`::
 	For every branch that is up to date or successfully pushed, add
 	upstream (tracking) reference, used by argument-less
 	linkgit:git-pull[1] and other commands. For more information,
 	see `branch.<name>.merge` in linkgit:git-config[1].
 
---thin::
---no-thin::
+`--thin`::
+`--no-thin`::
 	These options are passed to linkgit:git-send-pack[1]. A thin transfer
 	significantly reduces the amount of sent data when the sender and
 	receiver share many of the same objects in common. The default is
 	`--thin`.
 
--q::
---quiet::
+`-q`::
+`--quiet`::
 	Suppress all output, including the listing of updated refs,
 	unless an error occurs. Progress is not reported to the standard
 	error stream.
 
--v::
---verbose::
+`-v`::
+`--verbose`::
 	Run verbosely.
 
---progress::
+`--progress`::
 	Progress status is reported on the standard error stream
-	by default when it is attached to a terminal, unless -q
+	by default when it is attached to a terminal, unless `-q`
 	is specified. This flag forces progress status even if the
 	standard error stream is not directed to a terminal.
 
---no-recurse-submodules::
---recurse-submodules=check|on-demand|only|no::
+`--no-recurse-submodules`::
+`--recurse-submodules=(check|on-demand|only|no)`::
 	May be used to make sure all submodule commits used by the
 	revisions to be pushed are available on a remote-tracking branch.
-	If 'check' is used Git will verify that all submodule commits that
+	Possible values are:
+`check`;;
+        Git will verify that all submodule commits that
 	changed in the revisions to be pushed are available on at least one
 	remote of the submodule. If any commits are missing the push will
-	be aborted and exit with non-zero status. If 'on-demand' is used
+	be aborted and exit with non-zero status.
+`on-demand`;;
 	all submodules that changed in the revisions to be pushed will be
-	pushed. If on-demand was not able to push all necessary revisions it will
-	also be aborted and exit with non-zero status. If 'only' is used all
-	submodules will be pushed while the superproject is left
-	unpushed. A value of 'no' or using `--no-recurse-submodules` can be used
-	to override the push.recurseSubmodules configuration variable when no
-	submodule recursion is required.
+	pushed. If `on-demand` was not able to push all necessary revisions it will
+	also be aborted and exit with non-zero status.
+`only`;;
+	all submodules will be pushed while the superproject is left
+	unpushed.
+`no`;;
+	override the `push.recurseSubmodules` configuration variable when no
+	submodule recursion is required. Similar to using `--no-recurse-submodules`.
+
 +
-When using 'on-demand' or 'only', if a submodule has a
-"push.recurseSubmodules={on-demand,only}" or "submodule.recurse" configuration,
-further recursion will occur. In this case, "only" is treated as "on-demand".
+When using `on-demand` or `only`, if a submodule has a
+`push.recurseSubmodules=(on-demand|only)` or `submodule.recurse` configuration,
+further recursion will occur. In this case, `only` is treated as `on-demand`.
 
---verify::
---no-verify::
+`--verify`::
+`--no-verify`::
 	Toggle the pre-push hook (see linkgit:githooks[5]).  The
-	default is --verify, giving the hook a chance to prevent the
-	push.  With --no-verify, the hook is bypassed completely.
+	default is `--verify`, giving the hook a chance to prevent the
+	push.  With `--no-verify`, the hook is bypassed completely.
 
--4::
---ipv4::
+`-4`::
+`--ipv4`::
 	Use IPv4 addresses only, ignoring IPv6 addresses.
 
--6::
---ipv6::
+`-6`::
+`--ipv6`::
 	Use IPv6 addresses only, ignoring IPv4 addresses.
 
 include::urls-remotes.adoc[]
@@ -427,16 +444,16 @@
  <flag> <summary> <from> -> <to> (<reason>)
 -------------------------------
 
-If --porcelain is used, then each line of the output is of the form:
+If `--porcelain` is used, then each line of the output is of the form:
 
 -------------------------------
  <flag> \t <from>:<to> \t <summary> (<reason>)
 -------------------------------
 
-The status of up-to-date refs is shown only if --porcelain or --verbose
+The status of up-to-date refs is shown only if `--porcelain` or `--verbose`
 option is used.
 
-flag::
+_<flag>_::
 	A single character indicating the status of the ref:
 (space);; for a successfully pushed fast-forward;
 `+`;; for a successful forced update;
@@ -445,7 +462,7 @@
 `!`;; for a ref that was rejected or failed to push; and
 `=`;; for a ref that was up to date and did not need pushing.
 
-summary::
+_<summary>_::
 	For a successfully pushed ref, the summary shows the old and new
 	values of the ref in a form suitable for using as an argument to
 	`git log` (this is `<old>..<new>` in most cases, and
@@ -586,7 +603,7 @@
 push will be accepted.
 
 Alternatively, you can rebase your change between X and B on top of A,
-with "git pull --rebase", and push the result back.  The rebase will
+with `git pull --rebase`, and push the result back.  The rebase will
 create a new commit D that builds the change between X and B on top of
 A.
 
@@ -604,12 +621,12 @@
 There is another common situation where you may encounter non-fast-forward
 rejection when you try to push, and it is possible even when you are
 pushing into a repository nobody else pushes into. After you push commit
-A yourself (in the first picture in this section), replace it with "git
-commit --amend" to produce commit B, and you try to push it out, because
+A yourself (in the first picture in this section), replace it with `git
+commit --amend` to produce commit B, and you try to push it out, because
 forgot that you have pushed A out already. In such a case, and only if
 you are certain that nobody in the meantime fetched your earlier commit A
-(and started building on top of it), you can run "git push --force" to
-overwrite it. In other words, "git push --force" is a method reserved for
+(and started building on top of it), you can run `git push --force` to
+overwrite it. In other words, `git push --force` is a method reserved for
 a case where you do mean to lose history.
 
 
@@ -627,18 +644,18 @@
 	variable) if it has the same name as the current branch, and
 	errors out without pushing otherwise.
 +
-The default behavior of this command when no <refspec> is given can be
+The default behavior of this command when no _<refspec>_ is given can be
 configured by setting the `push` option of the remote, or the `push.default`
 configuration variable.
 +
 For example, to default to pushing only the current branch to `origin`
-use `git config remote.origin.push HEAD`.  Any valid <refspec> (like
+use `git config remote.origin.push HEAD`.  Any valid _<refspec>_ (like
 the ones in the examples below) can be configured as the default for
 `git push origin`.
 
 `git push origin :`::
 	Push "matching" branches to `origin`. See
-	<refspec> in the <<OPTIONS,OPTIONS>> section above for a
+	_<refspec>_ in the <<OPTIONS,OPTIONS>> section above for a
 	description of "matching" branches.
 
 `git push origin master`::
diff --git a/Documentation/git-rebase.adoc b/Documentation/git-rebase.adoc
index 005caf6..e177808 100644
--- a/Documentation/git-rebase.adoc
+++ b/Documentation/git-rebase.adoc
@@ -87,7 +87,7 @@
 point to that commit at the end of the rebase if other commands that change
 `ORIG_HEAD` (like `git reset`) are used during the rebase. The previous branch
 tip, however, is accessible using the reflog of the current branch (i.e. `@{1}`,
-see linkgit:gitrevisions[7].
+see linkgit:gitrevisions[7]).
 
 TRANSPLANTING A TOPIC BRANCH WITH --ONTO
 ----------------------------------------
@@ -474,6 +474,13 @@
 	Instead of using the current time as the committer date, use
 	the author date of the commit being rebased as the committer
 	date. This option implies `--force-rebase`.
++
+WARNING: The history walking machinery assumes that commits have
+non-decreasing commit timestamps. You should consider if you really need
+to use this option. Then you should only use this option to override the
+committer date when rebasing commits on top of a base which commit is
+older (in terms of the commit date) than the oldest commit you are
+applying (in terms of the author date).
 
 --ignore-date::
 --reset-author-date::
diff --git a/Documentation/git-remote.adoc b/Documentation/git-remote.adoc
index 932a5c3..eaae30a 100644
--- a/Documentation/git-remote.adoc
+++ b/Documentation/git-remote.adoc
@@ -8,20 +8,20 @@
 
 SYNOPSIS
 --------
-[verse]
-'git remote' [-v | --verbose]
-'git remote add' [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=(fetch|push)] <name> <URL>
-'git remote rename' [--[no-]progress] <old> <new>
-'git remote remove' <name>
-'git remote set-head' <name> (-a | --auto | -d | --delete | <branch>)
-'git remote set-branches' [--add] <name> <branch>...
-'git remote get-url' [--push] [--all] <name>
-'git remote set-url' [--push] <name> <newurl> [<oldurl>]
-'git remote set-url --add' [--push] <name> <newurl>
-'git remote set-url --delete' [--push] <name> <URL>
-'git remote' [-v | --verbose] 'show' [-n] <name>...
-'git remote prune' [-n | --dry-run] <name>...
-'git remote' [-v | --verbose] 'update' [-p | --prune] [(<group> | <remote>)...]
+[synopsis]
+git remote [-v | --verbose]
+git remote add [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=(fetch|push)] <name> <URL>
+git remote rename [--[no-]progress] <old> <new>
+git remote remove <name>
+git remote set-head <name> (-a | --auto | -d | --delete | <branch>)
+git remote set-branches [--add] <name> <branch>...
+git remote get-url [--push] [--all] <name>
+git remote set-url [--push] <name> <newurl> [<oldurl>]
+git remote set-url --add [--push] <name> <newurl>
+git remote set-url --delete [--push] <name> <URL>
+git remote [-v | --verbose] show [-n] <name>...
+git remote prune [-n | --dry-run] <name>...
+git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]
 
 DESCRIPTION
 -----------
@@ -32,8 +32,8 @@
 OPTIONS
 -------
 
--v::
---verbose::
+`-v`::
+`--verbose`::
 	Be a little more verbose and show remote url after name.
 	For promisor remotes, also show which filters (`blob:none` etc.)
 	are configured.
@@ -43,14 +43,14 @@
 COMMANDS
 --------
 
-With no arguments, shows a list of existing remotes.  Several
+With no arguments, show a list of existing remotes.  Several
 subcommands are available to perform operations on the remotes.
 
-'add'::
+`add`::
 
-Add a remote named <name> for the repository at
-<URL>.  The command `git fetch <name>` can then be used to create and
-update remote-tracking branches <name>/<branch>.
+Add a remote named _<name>_ for the repository at
+_<URL>_.  The command `git fetch <name>` can then be used to create and
+update remote-tracking branches `<name>/<branch>`.
 +
 With `-f` option, `git fetch <name>` is run immediately after
 the remote information is set up.
@@ -66,40 +66,40 @@
 +
 With `-t <branch>` option, instead of the default glob
 refspec for the remote to track all branches under
-the `refs/remotes/<name>/` namespace, a refspec to track only `<branch>`
+the `refs/remotes/<name>/` namespace, a refspec to track only _<branch>_
 is created.  You can give more than one `-t <branch>` to track
 multiple branches without grabbing all branches.
 +
 With `-m <master>` option, a symbolic-ref `refs/remotes/<name>/HEAD` is set
-up to point at remote's `<master>` branch. See also the set-head command.
+up to point at remote's _<master>_ branch. See also the set-head command.
 +
 When a fetch mirror is created with `--mirror=fetch`, the refs will not
-be stored in the 'refs/remotes/' namespace, but rather everything in
-'refs/' on the remote will be directly mirrored into 'refs/' in the
+be stored in the `refs/remotes/` namespace, but rather everything in
+`refs/` on the remote will be directly mirrored into `refs/` in the
 local repository. This option only makes sense in bare repositories,
 because a fetch would overwrite any local commits.
 +
 When a push mirror is created with `--mirror=push`, then `git push`
 will always behave as if `--mirror` was passed.
 
-'rename'::
+`rename`::
 
-Rename the remote named <old> to <new>. All remote-tracking branches and
+Rename the remote named _<old>_ to _<new>_. All remote-tracking branches and
 configuration settings for the remote are updated.
 +
-In case <old> and <new> are the same, and <old> is a file under
+In case _<old>_ and _<new>_ are the same, and _<old>_ is a file under
 `$GIT_DIR/remotes` or `$GIT_DIR/branches`, the remote is converted to
 the configuration file format.
 
-'remove'::
-'rm'::
+`remove`::
+`rm`::
 
-Remove the remote named <name>. All remote-tracking branches and
+Remove the remote named _<name>_. All remote-tracking branches and
 configuration settings for the remote are removed.
 
-'set-head'::
+`set-head`::
 
-Sets or deletes the default branch (i.e. the target of the
+Set or delete the default branch (i.e. the target of the
 symbolic-ref `refs/remotes/<name>/HEAD`) for
 the named remote. Having a default branch for a remote is not required,
 but allows the name of the remote to be specified in lieu of a specific
@@ -116,15 +116,15 @@
 only work if `refs/remotes/origin/next` already exists; if not it must be
 fetched first.
 +
-Use `<branch>` to set the symbolic-ref `refs/remotes/<name>/HEAD` explicitly. e.g., `git
+Use _<branch>_ to set the symbolic-ref `refs/remotes/<name>/HEAD` explicitly. e.g., `git
 remote set-head origin master` will set the symbolic-ref `refs/remotes/origin/HEAD` to
 `refs/remotes/origin/master`. This will only work if
 `refs/remotes/origin/master` already exists; if not it must be fetched first.
 +
 
-'set-branches'::
+`set-branches`::
 
-Changes the list of branches tracked by the named remote.
+Change the list of branches tracked by the named remote.
 This can be used to track a subset of the available remote branches
 after the initial setup for a remote.
 +
@@ -134,7 +134,7 @@
 With `--add`, instead of replacing the list of currently tracked
 branches, adds to that list.
 
-'get-url'::
+`get-url`::
 
 Retrieves the URLs for a remote. Configurations for `insteadOf` and
 `pushInsteadOf` are expanded here. By default, only the first URL is listed.
@@ -143,18 +143,18 @@
 +
 With `--all`, all URLs for the remote will be listed.
 
-'set-url'::
+`set-url`::
 
-Changes URLs for the remote. Sets first URL for remote <name> that matches
-regex <oldurl> (first URL if no <oldurl> is given) to <newurl>. If
-<oldurl> doesn't match any URL, an error occurs and nothing is changed.
+Change URLs for the remote. Sets first URL for remote _<name>_ that matches
+regex _<oldurl>_ (first URL if no _<oldurl>_ is given) to _<newurl>_. If
+_<oldurl>_ doesn't match any URL, an error occurs and nothing is changed.
 +
 With `--push`, push URLs are manipulated instead of fetch URLs.
 +
 With `--add`, instead of changing existing URLs, new URL is added.
 +
 With `--delete`, instead of changing existing URLs, all URLs matching
-regex <URL> are deleted for remote <name>.  Trying to delete all
+regex _<URL>_ are deleted for remote _<name>_.  Trying to delete all
 non-push URLs is an error.
 +
 Note that the push URL and the fetch URL, even though they can
@@ -165,17 +165,17 @@
 your publishing repository), use two separate remotes.
 
 
-'show'::
+`show`::
 
-Gives some information about the remote <name>.
+Give some information about the remote _<name>_.
 +
 With `-n` option, the remote heads are not queried first with
 `git ls-remote <name>`; cached information is used instead.
 
-'prune'::
+`prune`::
 
-Deletes stale references associated with <name>. By default, stale
-remote-tracking branches under <name> are deleted, but depending on
+Delete stale references associated with _<name>_. By default, stale
+remote-tracking branches under _<name>_ are deleted, but depending on
 global configuration and the configuration of the remote we might even
 prune local tags that haven't been pushed there. Equivalent to `git
 fetch --prune <name>`, except that no new references will be fetched.
@@ -186,13 +186,13 @@
 With `--dry-run` option, report what branches would be pruned, but do not
 actually prune them.
 
-'update'::
+`update`::
 
 Fetch updates for remotes or remote groups in the repository as defined by
 `remotes.<group>`. If neither group nor remote is specified on the command line,
-the configuration parameter remotes.default will be used; if
-remotes.default is not defined, all remotes which do not have the
-configuration parameter `remote.<name>.skipDefaultUpdate` set to true will
+the configuration parameter `remotes.default` will be used; if
+`remotes.default` is not defined, all remotes which do not have the
+configuration parameter `remote.<name>.skipDefaultUpdate` set to `true` will
 be updated.  (See linkgit:git-config[1]).
 +
 With `--prune` option, run pruning against all the remotes that are updated.
@@ -210,7 +210,7 @@
 
 On success, the exit status is `0`.
 
-When subcommands such as 'add', 'rename', and 'remove' can't find the
+When subcommands such as `add`, `rename`, and `remove` can't find the
 remote in question, the exit status is `2`. When the remote already
 exists, the exit status is `3`.
 
@@ -247,7 +247,7 @@
 ...
 ------------
 
-* Imitate 'git clone' but track only selected branches
+* Imitate `git clone` but track only selected branches
 +
 ------------
 $ mkdir project.git
diff --git a/Documentation/git-repack.adoc b/Documentation/git-repack.adoc
index d12c498..673ce91 100644
--- a/Documentation/git-repack.adoc
+++ b/Documentation/git-repack.adoc
@@ -77,14 +77,14 @@
 	Only useful with `--cruft -d`.
 
 --max-cruft-size=<n>::
-	Overrides `--max-pack-size` for cruft packs. Inherits the value of
+	Override `--max-pack-size` for cruft packs. Inherits the value of
 	`--max-pack-size` (if any) by default. See the documentation for
 	`--max-pack-size` for more details.
 
 --combine-cruft-below-size=<n>::
 	When generating cruft packs without pruning, only repack
-	existing cruft packs whose size is strictly less than `<n>`,
-	where `<n>` represents a number of bytes, which can optionally
+	existing cruft packs whose size is strictly less than `<n>`
+	bytes, which can optionally
 	be suffixed with "k", "m", or "g". Cruft packs whose size is
 	greater than or equal to `<n>` are left as-is and not repacked.
 	Useful when you want to avoid repacking large cruft pack(s) in
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index 0b12bf8..8d696ce 100644
--- a/Documentation/git-replay.adoc
+++ b/Documentation/git-replay.adoc
@@ -9,16 +9,17 @@
 SYNOPSIS
 --------
 [verse]
-(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>...
+(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch>) [--ref-action[=<mode>]] <revision-range>
 
 DESCRIPTION
 -----------
 
-Takes ranges of commits and replays them onto a new location. Leaves
-the working tree and the index untouched, and updates no references.
-The output of this command is meant to be used as input to
-`git update-ref --stdin`, which would update the relevant branches
-(see the OUTPUT section below).
+Takes a range of commits and replays them onto a new location. Leaves
+the working tree and the index untouched. By default, updates the
+relevant references using an atomic transaction (all refs update or
+none). Use `--ref-action=print` to avoid automatic ref updates and
+instead get update commands that can be piped to `git update-ref --stdin`
+(see the <<output,OUTPUT>> section below).
 
 THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
 
@@ -29,33 +30,54 @@
 	Starting point at which to create the new commits.  May be any
 	valid commit, and not just an existing branch name.
 +
-When `--onto` is specified, the update-ref command(s) in the output will
-update the branch(es) in the revision range to point at the new
-commits, similar to the way how `git rebase --update-refs` updates
-multiple branches in the affected range.
+When `--onto` is specified, the branch(es) in the revision range will be
+updated to point at the new commits, similar to the way `git rebase --update-refs`
+updates multiple branches in the affected range.
 
 --advance <branch>::
 	Starting point at which to create the new commits; must be a
 	branch name.
 +
-When `--advance` is specified, the update-ref command(s) in the output
-will update the branch passed as an argument to `--advance` to point at
-the new commits (in other words, this mimics a cherry-pick operation).
+The history is replayed on top of the <branch> and <branch> is updated to
+point at the tip of the resulting history. This is different from `--onto`,
+which uses the target only as a starting point without updating it.
+
+--contained::
+	Update all branches that point at commits in
+	<revision-range>. Requires `--onto`.
+
+--ref-action[=<mode>]::
+	Control how references are updated. The mode can be:
++
+--
+	* `update` (default): Update refs directly using an atomic transaction.
+	  All refs are updated or none are (all-or-nothing behavior).
+	* `print`: Output update-ref commands for pipeline use. This is the
+	  traditional behavior where output can be piped to `git update-ref --stdin`.
+--
++
+The default mode can be configured via the `replay.refAction` configuration variable.
 
 <revision-range>::
-	Range of commits to replay. More than one <revision-range> can
-	be passed, but in `--advance <branch>` mode, they should have
-	a single tip, so that it's clear where <branch> should point
-	to. See "Specifying Ranges" in linkgit:git-rev-parse[1] and the
-	"Commit Limiting" options below.
+	Range of commits to replay; see "Specifying Ranges" in
+	linkgit:git-rev-parse[1]. In `--advance <branch>` mode, the
+	range should have a single tip, so that it's clear to which tip the
+	advanced <branch> should point. Any commits in the range whose
+	changes are already present in the branch the commits are being
+	replayed onto will be dropped.
 
+:git-replay: 1
 include::rev-list-options.adoc[]
 
+[[output]]
 OUTPUT
 ------
 
-When there are no conflicts, the output of this command is usable as
-input to `git update-ref --stdin`.  It is of the form:
+By default, or with `--ref-action=update`, this command produces no output on
+success, as refs are updated directly using an atomic transaction.
+
+When using `--ref-action=print`, the output is usable as input to
+`git update-ref --stdin`. It is of the form:
 
 	update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}
 	update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}
@@ -66,6 +88,10 @@
 number of refs updated is always one, but for `--onto`, it can be one
 or more (rebasing multiple branches simultaneously is supported).
 
+There is no stderr output on conflicts; see the <<exit-status,EXIT
+STATUS>> section below.
+
+[[exit-status]]
 EXIT STATUS
 -----------
 
@@ -81,6 +107,14 @@
 
 ------------
 $ git replay --onto target origin/main..mybranch
+------------
+
+The refs are updated atomically and no output is produced on success.
+
+To see what would be updated without actually updating:
+
+------------
+$ git replay --ref-action=print --onto target origin/main..mybranch
 update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}
 ------------
 
@@ -88,33 +122,29 @@
 
 ------------
 $ git replay --advance target origin/main..mybranch
-update refs/heads/target ${NEW_target_HASH} ${OLD_target_HASH}
 ------------
 
 Note that the first two examples replay the exact same commits and on
 top of the exact same new base, they only differ in that the first
-provides instructions to make mybranch point at the new commits and
-the second provides instructions to make target point at them.
+updates mybranch to point at the new commits and the second updates
+target to point at them.
 
 What if you have a stack of branches, one depending upon another, and
 you'd really like to rebase the whole set?
 
 ------------
 $ git replay --contained --onto origin/main origin/main..tipbranch
-update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}
-update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}
-update refs/heads/tipbranch ${NEW_tipbranch_HASH} ${OLD_tipbranch_HASH}
 ------------
 
+All three branches (`branch1`, `branch2`, and `tipbranch`) are updated
+atomically.
+
 When calling `git replay`, one does not need to specify a range of
 commits to replay using the syntax `A..B`; any range expression will
 do:
 
 ------------
 $ git replay --onto origin/main ^base branch1 branch2 branch3
-update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}
-update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}
-update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}
 ------------
 
 This will simultaneously rebase `branch1`, `branch2`, and `branch3`,
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index ce43cb1..42262c1 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -8,8 +8,9 @@
 SYNOPSIS
 --------
 [synopsis]
-git repo info [--format=(keyvalue|nul)] [-z] [<key>...]
-git repo structure [--format=(table|keyvalue|nul)]
+git repo info [--format=(lines|nul) | -z] [--all | <key>...]
+git repo info --keys [--format=(lines|nul) | -z]
+git repo structure [--format=(table|lines|nul) | -z]
 
 DESCRIPTION
 -----------
@@ -19,38 +20,51 @@
 
 COMMANDS
 --------
-`info [--format=(keyvalue|nul)] [-z] [<key>...]`::
+`info [--format=(lines|nul) | -z] [--all | <key>...]`::
 	Retrieve metadata-related information about the current repository. Only
 	the requested data will be returned based on their keys (see "INFO KEYS"
 	section below).
 +
 The values are returned in the same order in which their respective keys were
-requested.
+requested. The `--all` flag requests the values for all the available keys.
 +
 The output format can be chosen through the flag `--format`. Two formats are
 supported:
 +
-`keyvalue`:::
-	output key-value pairs one per line using the `=` character as
+
+`lines`:::
+	Output key-value pairs one per line using the `=` character as
 	the delimiter between the key and the value. Values containing "unusual"
 	characters are quoted as explained for the configuration variable
 	`core.quotePath` (see linkgit:git-config[1]). This is the default.
 
 `nul`:::
-	similar to `keyvalue`, but using a newline character as the delimiter
-	between the key and the value and using a NUL character after each value.
+	Similar to `lines`, but using a newline character as the delimiter
+	between the key and the value and using a _NUL_ character after each value.
 	This format is better suited for being parsed by another applications than
-	`keyvalue`. Unlike in the `keyvalue` format, the values are never quoted.
+	`lines`. Unlike in the `lines` format, the values are never quoted.
 +
 `-z` is an alias for `--format=nul`.
 
-`structure [--format=(table|keyvalue|nul)]`::
+`info --keys [--format=(lines|nul) | -z]`::
+	List all the available keys, one per line. The output format can be chosen
+	through the flag `--format`. The following formats are supported:
++
+`lines`:::
+	Output the keys one per line. This is the default.
+
+`nul`:::
+	Similar to `lines`, but using a _NUL_ character after each value.
+
+`structure [--format=(table|lines|nul) | -z]`::
 	Retrieve statistics about the current repository structure. The
 	following kinds of information are reported:
 +
 * Reference counts categorized by type
 * Reachable object counts categorized by type
-
+* Total inflated size of reachable objects by type
+* Total disk size of reachable objects by type
+* Largest reachable objects in the repository by type
 +
 The output format can be chosen through the flag `--format`. Three formats are
 supported:
@@ -60,18 +74,20 @@
 	change and is not intended for machine parsing. This is the default
 	format.
 
-`keyvalue`:::
+`lines`:::
 	Each line of output contains a key-value pair for a repository stat.
 	The '=' character is used to delimit between the key and the value.
 	Values containing "unusual" characters are quoted as explained for the
 	configuration variable `core.quotePath` (see linkgit:git-config[1]).
 
 `nul`:::
-	Similar to `keyvalue`, but uses a NUL character to delimit between
+	Similar to `lines`, but uses a _NUL_ character to delimit between
 	key-value pairs instead of a newline. Also uses a newline character as
 	the delimiter between the key and value instead of '='. Unlike the
-	`keyvalue` format, values containing "unusual" characters are never
+	`lines` format, values containing "unusual" characters are never
 	quoted.
++
+`-z` is an alias for `--format=nul`.
 
 INFO KEYS
 ---------
diff --git a/Documentation/git-rerere.adoc b/Documentation/git-rerere.adoc
index 992b469..99f967b 100644
--- a/Documentation/git-rerere.adoc
+++ b/Documentation/git-rerere.adoc
@@ -32,7 +32,7 @@
 --------
 
 Normally, 'git rerere' is run without arguments or user-intervention.
-However, it has several commands that allow it to interact with
+However, it has several commands that allow users to interact with
 its working state.
 
 'clear'::
@@ -44,7 +44,7 @@
 'forget' <pathspec>::
 
 Reset the conflict resolutions which rerere has recorded for the current
-conflict in <pathspec>.
+conflict in paths that match <pathspec>.
 
 'diff'::
 
diff --git a/Documentation/git-reset.adoc b/Documentation/git-reset.adoc
index 3b9ba9a..5023b50 100644
--- a/Documentation/git-reset.adoc
+++ b/Documentation/git-reset.adoc
@@ -3,86 +3,67 @@
 
 NAME
 ----
-git-reset - Reset current HEAD to the specified state
+git-reset - Set `HEAD` or the index to a known state
 
 SYNOPSIS
 --------
 [synopsis]
+git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
 git reset [-q] [<tree-ish>] [--] <pathspec>...
 git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]
 git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
-git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
 
 DESCRIPTION
 -----------
-In the first three forms, copy entries from _<tree-ish>_ to the index.
-In the last form, set the current branch head (`HEAD`) to _<commit>_,
-optionally modifying index and working tree to match.
-The _<tree-ish>_/_<commit>_ defaults to `HEAD` in all forms.
+`git reset` does either of the following:
 
-`git reset [-q] [<tree-ish>] [--] <pathspec>...`::
-`git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]`::
-	These forms reset the index entries for all paths that match the
-	_<pathspec>_ to their state at _<tree-ish>_.  (It does not affect
-	the working tree or the current branch.)
-+
-This means that `git reset <pathspec>` is the opposite of `git add
-<pathspec>`. This command is equivalent to
-`git restore [--source=<tree-ish>] --staged <pathspec>...`.
-+
-After running `git reset <pathspec>` to update the index entry, you can
-use linkgit:git-restore[1] to check the contents out of the index to
-the working tree. Alternatively, using linkgit:git-restore[1]
-and specifying a commit with `--source`, you
-can copy the contents of a path out of a commit to the index and to the
-working tree in one go.
-
-`git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>...]`::
-	Interactively select hunks in the difference between the index
-	and _<tree-ish>_ (defaults to `HEAD`).  The chosen hunks are applied
-	in reverse to the index.
-+
-This means that `git reset -p` is the opposite of `git add -p`, i.e.
-you can use it to selectively reset hunks. See the "Interactive Mode"
-section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
+1. `git reset [<mode>] <commit>` changes which commit `HEAD` points to. This
+   makes it possible to undo various Git operations, for example commit, merge,
+   rebase, and pull.
+2. When you specify files or directories or pass `--patch`, `git reset` updates
+   the staged version of the specified files.
 
 `git reset [<mode>] [<commit>]`::
-	This form resets the current branch head to _<commit>_ and
-	possibly updates the index (resetting it to the tree of _<commit>_) and
-	the working tree depending on _<mode>_. Before the operation, `ORIG_HEAD`
-	is set to the tip of the current branch. If _<mode>_ is omitted,
-	defaults to `--mixed`. The _<mode>_ must be one of the following:
+	Set the current branch head (`HEAD`) to point at _<commit>_.
+	Depending on _<mode>_, also update the working directory and/or index
+	to match the contents of _<commit>_.
+	_<commit>_ defaults to `HEAD`.
+	Before the operation, `ORIG_HEAD` is set to the tip of the current branch.
 +
---
-`--soft`::
-	Does not touch the index file or the working tree at all (but
-	resets the head to _<commit>_, just like all modes do). This leaves
-	all your changed files "Changes to be committed", as `git status`
-	would put it.
+The _<mode>_ must be one of the following (default `--mixed`):
++
 
+--
 `--mixed`::
-	Resets the index but not the working tree (i.e., the changed files
-	are preserved but not marked for commit) and reports what has not
-	been updated. This is the default action.
+	Leave your working directory unchanged.
+	Update the index to match the new `HEAD`, so nothing will be staged.
 +
-If `-N` is specified, removed paths are marked as intent-to-add (see
+If `-N` is specified, mark removed paths as intent-to-add (see
 linkgit:git-add[1]).
 
+`--soft`::
+	Leave your working tree files and the index unchanged.
+	For example, if you have no staged changes, you can use
+	`git reset --soft HEAD~5; git commit`
+	to combine the last 5 commits into 1 commit. This works even with
+	changes in the working tree, which are left untouched, but such usage
+	can lead to confusion.
+
 `--hard`::
-	Resets the index and working tree. Any changes to tracked files in the
-	working tree since _<commit>_ are discarded.  Any untracked files or
-	directories in the way of writing any tracked files are simply deleted.
+	Overwrite all files and directories with the version from _<commit>_,
+	and may overwrite untracked files. Tracked files not in _<commit>_ are
+	removed so that the working tree matches _<commit>_.
+	Update the index to match the new `HEAD`, so nothing will be staged.
 
 `--merge`::
-	Resets the index and updates the files in the working tree that are
-	different between _<commit>_ and `HEAD`, but keeps those which are
+	Reset the index and update the files in the working tree that are
+	different between _<commit>_ and `HEAD`, but keep those which are
 	different between the index and working tree (i.e. which have changes
 	which have not been added).
+	Mainly exists to reset unmerged index entries, like those left behind by
+	`git am -3` or `git switch -m` in certain situations.
 	If a file that is different between _<commit>_ and the index has
 	unstaged changes, reset is aborted.
-+
-In other words, `--merge` does something like a `git read-tree -u -m <commit>`,
-but carries forward unmerged index entries.
 
 `--keep`::
 	Resets index entries and updates files in the working tree that are
@@ -98,6 +79,28 @@
 	the submodules' `HEAD` to be detached at that commit.
 --
 
+`git reset [-q] [<tree-ish>] [--] <pathspec>...`::
+`git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]`::
+	For all specified files or directories, set the staged version to
+	the version from the given commit or tree (which defaults to `HEAD`).
++
+This means that `git reset <pathspec>` is the opposite of `git add
+<pathspec>`: it unstages all changes to the specified file(s) or
+directories. This is equivalent to `git restore --staged <pathspec>...`.
++
+In this mode, `git reset` updates only the index (without updating the `HEAD` or
+working tree files). If you want to update the files as well as the index
+entries, use linkgit:git-restore[1].
+
+`git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>...]`::
+	Interactively select changes from the difference between the index
+	and the specified commit or tree (which defaults to `HEAD`).
+	The index is modified using the chosen changes.
++
+This means that `git reset -p` is the opposite of `git add -p`, i.e.
+you can use it to selectively unstage changes. See the "Interactive Mode"
+section of linkgit:git-add[1] to learn how to use the `--patch` option.
+
 See "Reset, restore and revert" in linkgit:git[1] for the differences
 between the three commands.
 
diff --git a/Documentation/git-send-email.adoc b/Documentation/git-send-email.adoc
index 263b977..dea3b86 100644
--- a/Documentation/git-send-email.adoc
+++ b/Documentation/git-send-email.adoc
@@ -208,7 +208,7 @@
 	for your own case. Default is the value of `sendemail.smtpEncryption`.
 
 --smtp-domain=<FQDN>::
-	Specifies the Fully Qualified Domain Name (FQDN) used in the
+	Specify the Fully Qualified Domain Name (FQDN) used in the
 	HELO/EHLO command to the SMTP server.  Some servers require the
 	FQDN to match your IP address.  If not set, `git send-email` attempts
 	to determine your FQDN automatically.  Default is the value of
@@ -245,7 +245,7 @@
 	Disable SMTP authentication. Short hand for `--smtp-auth=none`.
 
 --smtp-server=<host>::
-	If set, specifies the outgoing SMTP server to use (e.g.
+	Specify the outgoing SMTP server to use (e.g.
 	`smtp.example.com` or a raw IP address).  If unspecified, and if
 	`--sendmail-cmd` is also unspecified, the default is to search
 	for `sendmail` in `/usr/sbin`, `/usr/lib` and `$PATH` if such a
@@ -258,7 +258,7 @@
 instead.
 
 --smtp-server-port=<port>::
-	Specifies a port different from the default port (SMTP
+	Specify a port different from the default port (SMTP
 	servers typically listen to smtp port 25, but may also listen to
 	submission port 587, or the common SSL smtp port 465);
 	symbolic port names (e.g. `submission` instead of 587)
@@ -266,7 +266,7 @@
 	`sendemail.smtpServerPort` configuration variable.
 
 --smtp-server-option=<option>::
-	If set, specifies the outgoing SMTP server option to use.
+	Specify the outgoing SMTP server option to use.
 	Default value can be specified by the `sendemail.smtpServerOption`
 	configuration option.
 +
@@ -277,7 +277,7 @@
 --smtp-ssl::
 	Legacy alias for `--smtp-encryption ssl`.
 
---smtp-ssl-cert-path::
+--smtp-ssl-cert-path <path>::
 	Path to a store of trusted CA certificates for SMTP SSL/TLS
 	certificate validation (either a directory that has been processed
 	by `c_rehash`, or a single file containing one or more PEM format
@@ -290,6 +290,25 @@
 	variable, if set, or the backing SSL library's compiled-in default
 	otherwise (which should be the best choice on most platforms).
 
+--smtp-ssl-client-cert <path>::
+	Path to the client certificate file to present if requested by the
+	server. This option is required when the server is set up to verify
+	client certificates. If the corresponding private key is not included in
+	the file, it must be supplied using the `sendemail.smtpSSLClientKey`
+	configuration variable or the `--smtp-ssl-client-key` option. Defaults
+	to the value of the `sendemail.smtpSSLClientCert` configuration
+	variable, if set.
+
+--smtp-ssl-client-key <path>::
+	Path to the client private key file that corresponds to the client
+	certificate. To avoid misconfiguration, this option must be used in
+	conjunction with the `sendemail.smtpSSLClientKey` configuration variable
+	or the `--smtp-ssl-client-cert` option. If the client key is included in
+	the client certificate, the choice of private key depends on the format
+	of the certificate. Visit https://metacpan.org/pod/IO::Socket::SSL for
+	more details. Defaults to the value of the `sendemail.smtpSSLClientKey`
+	configuration variable, if set.
+
 --smtp-user=<user>::
 	Username for SMTP-AUTH. Default is the value of `sendemail.smtpUser`;
 	if a username is not specified (with `--smtp-user` or `sendemail.smtpUser`),
@@ -321,7 +340,6 @@
 	If disabled with `--no-use-imap-only`, the emails will be sent like usual.
 	Disabled by default, but the `sendemail.useImapOnly` configuration
 	variable can be used to enable it.
-
 +
 This feature requires setting up `git imap-send`. See linkgit:git-imap-send[1]
 for instructions.
@@ -347,11 +365,11 @@
 --no-to::
 --no-cc::
 --no-bcc::
-	Clears any list of `To:`, `Cc:`, `Bcc:` addresses previously
+	Clear any list of `To:`, `Cc:`, `Bcc:` addresses previously
 	set via config.
 
 --no-identity::
-	Clears the previously read value of `sendemail.identity` set
+	Clear the previously read value of `sendemail.identity` set
 	via config, if any.
 
 --to-cmd=<command>::
@@ -436,7 +454,7 @@
 +
 Default is the value of `sendemail.suppressCc` configuration value; if
 that is unspecified, default to `self` if `--suppress-from` is
-specified, as well as `body` if `--no-signed-off-cc` is specified.
+specified, as well as `body` if `--no-signed-off-by-cc` is specified.
 
 --suppress-from::
 --no-suppress-from::
@@ -510,12 +528,12 @@
 	Currently, validation means the following:
 +
 --
-		*	Invoke the sendemail-validate hook if present (see linkgit:githooks[5]).
-		*	Warn of patches that contain lines longer than
-			998 characters unless a suitable transfer encoding
-			(`auto`, `base64`, or `quoted-printable`) is used;
-			this is due to SMTP limits as described by
-			https://www.ietf.org/rfc/rfc5322.txt.
+* Invoke the sendemail-validate hook if present (see linkgit:githooks[5]).
+* Warn of patches that contain lines longer than
+  998 characters unless a suitable transfer encoding
+  (`auto`, `base64`, or `quoted-printable`) is used;
+  this is due to SMTP limits as described by
+  https://www.ietf.org/rfc/rfc5322.txt.
 --
 +
 Default is the value of `sendemail.validate`; if this is not set,
@@ -693,6 +711,11 @@
 	- https://github.com/AdityaGarg8/git-credential-email[git-msgraph]
 	  (cross platform client that can send emails using the Microsoft Graph API)
 
+CAVEATS
+-------
+
+include::format-patch-caveats.adoc[]
+
 SEE ALSO
 --------
 linkgit:git-format-patch[1], linkgit:git-imap-send[1], mbox(5)
diff --git a/Documentation/git-shortlog.adoc b/Documentation/git-shortlog.adoc
index aa92800..a11b57c 100644
--- a/Documentation/git-shortlog.adoc
+++ b/Documentation/git-shortlog.adoc
@@ -64,9 +64,6 @@
    example, if your project uses `Reviewed-by` trailers, you might want
    to see who has been reviewing with
    `git shortlog -ns --group=trailer:reviewed-by`.
- - `format:<format>`, any string accepted by the `--format` option of
-   'git log'. (See the "PRETTY FORMATS" section of
-   linkgit:git-log[1].)
 +
 Note that commits that do not include the trailer will not be counted.
 Likewise, commits with multiple trailers (e.g., multiple signoffs) may
@@ -77,6 +74,10 @@
 identity. If successful, the mailmap is applied and the email is omitted
 unless the `--email` option is specified. If the value cannot be parsed
 as an identity, it will be taken literally and completely.
+
+ - `format:<format>`, any string accepted by the `--format` option of
+   'git log'. (See the "PRETTY FORMATS" section of
+   linkgit:git-log[1].)
 --
 +
 If `--group` is specified multiple times, commits are counted under each
diff --git a/Documentation/git-show.adoc b/Documentation/git-show.adoc
index 51044c8..3b180e8 100644
--- a/Documentation/git-show.adoc
+++ b/Documentation/git-show.adoc
@@ -8,8 +8,8 @@
 
 SYNOPSIS
 --------
-[verse]
-'git show' [<options>] [<object>...]
+[synopsis]
+git show [<options>] [<object>...]
 
 DESCRIPTION
 -----------
@@ -17,16 +17,16 @@
 
 For commits it shows the log message and textual diff. It also
 presents the merge commit in a special format as produced by
-'git diff-tree --cc'.
+`git diff-tree --cc`.
 
 For tags, it shows the tag message and the referenced objects.
 
-For trees, it shows the names (equivalent to 'git ls-tree'
-with --name-only).
+For trees, it shows the names (equivalent to `git ls-tree`
+with `--name-only`).
 
 For plain blobs, it shows the plain contents.
 
-Some options that 'git log' command understands can be used to
+Some options that `git log` command understands can be used to
 control how the changes the commit introduces are shown.
 
 This manual page describes only the most frequently used options.
@@ -34,8 +34,8 @@
 
 OPTIONS
 -------
-<object>...::
-	The names of objects to show (defaults to 'HEAD').
+`<object>...`::
+	The names of objects to show (defaults to `HEAD`).
 	For a more complete list of ways to spell object names, see
 	"SPECIFYING REVISIONS" section in linkgit:gitrevisions[7].
 
diff --git a/Documentation/git-stage.adoc b/Documentation/git-stage.adoc
index 2f6aaa7..753a817 100644
--- a/Documentation/git-stage.adoc
+++ b/Documentation/git-stage.adoc
@@ -8,8 +8,8 @@
 
 SYNOPSIS
 --------
-[verse]
-'git stage' <arg>...
+[synopsis]
+git stage <arg>...
 
 
 DESCRIPTION
diff --git a/Documentation/git-status.adoc b/Documentation/git-status.adoc
index 9a37688..9acca52 100644
--- a/Documentation/git-status.adoc
+++ b/Documentation/git-status.adoc
@@ -8,8 +8,9 @@
 
 SYNOPSIS
 --------
-[verse]
-'git status' [<options>] [--] [<pathspec>...]
+
+[synopsis]
+git status [<options>] [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -18,57 +19,57 @@
 tree and the index file, and paths in the working tree that are not
 tracked by Git (and are not ignored by linkgit:gitignore[5]). The first
 are what you _would_ commit by running `git commit`; the second and
-third are what you _could_ commit by running 'git add' before running
+third are what you _could_ commit by running `git add` before running
 `git commit`.
 
 OPTIONS
 -------
 
--s::
---short::
+`-s`::
+`--short`::
 	Give the output in the short-format.
 
--b::
---branch::
+`-b`::
+`--branch`::
 	Show the branch and tracking info even in short-format.
 
---show-stash::
+`--show-stash`::
 	Show the number of entries currently stashed away.
 
---porcelain[=<version>]::
+`--porcelain[=<version>]`::
 	Give the output in an easy-to-parse format for scripts.
 	This is similar to the short output, but will remain stable
 	across Git versions and regardless of user configuration. See
 	below for details.
 +
-The version parameter is used to specify the format version.
-This is optional and defaults to the original version 'v1' format.
+The _<version>_ parameter is used to specify the format version.
+This is optional and defaults to the original version `v1` format.
 
---long::
+`--long`::
 	Give the output in the long-format. This is the default.
 
--v::
---verbose::
+`-v`::
+`--verbose`::
 	In addition to the names of files that have been changed, also
 	show the textual changes that are staged to be committed
 	(i.e., like the output of `git diff --cached`). If `-v` is specified
 	twice, then also show the changes in the working tree that
 	have not yet been staged (i.e., like the output of `git diff`).
 
--u[<mode>]::
---untracked-files[=<mode>]::
+`-u[<mode>]`::
+`--untracked-files[=<mode>]`::
 	Show untracked files.
 +
 --
 The mode parameter is used to specify the handling of untracked files.
-It is optional: it defaults to 'all', and if specified, it must be
+It is optional: it defaults to `all`, and if specified, it must be
 stuck to the option (e.g. `-uno`, but not `-u no`).
 
 The possible options are:
 
-	- 'no'     - Show no untracked files.
-	- 'normal' - Shows untracked files and directories.
-	- 'all'    - Also shows individual files in untracked directories.
+`no`:: Show no untracked files.
+`normal`:: Show untracked files and directories.
+`all`:: Also show individual files in untracked directories.
 
 When `-u` option is not used, untracked files and directories are
 shown (i.e. the same as specifying `normal`), to help you avoid
@@ -82,76 +83,78 @@
 All usual spellings for Boolean value `true` are taken as `normal`
 and `false` as `no`.
 
-The default can be changed using the status.showUntrackedFiles
+The default can be changed using the `status.showUntrackedFiles`
 configuration variable documented in linkgit:git-config[1].
 --
 
---ignore-submodules[=<when>]::
-	Ignore changes to submodules when looking for changes. <when> can be
-	either "none", "untracked", "dirty" or "all", which is the default.
-	Using "none" will consider the submodule modified when it either contains
+`--ignore-submodules[=<when>]`::
+	Ignore changes to submodules when looking for changes. _<when>_ can be
+	either `none`, `untracked`, `dirty` or `all`, which is the default.
+`none`;; will consider the submodule modified when it either contains
 	untracked or modified files or its HEAD differs from the commit recorded
 	in the superproject and can be used to override any settings of the
-	'ignore' option in linkgit:git-config[1] or linkgit:gitmodules[5]. When
-	"untracked" is used submodules are not considered dirty when they only
+	`ignore` option in linkgit:git-config[1] or linkgit:gitmodules[5].
+`untracked`;; submodules are not considered dirty when they only
 	contain untracked content (but they are still scanned for modified
-	content). Using "dirty" ignores all changes to the work tree of submodules,
+	content).
+`dirty`;; ignore all changes to the work tree of submodules,
 	only changes to the commits stored in the superproject are shown (this was
-	the behavior before 1.7.0). Using "all" hides all changes to submodules
+	the behavior before 1.7.0).
+`all`;; hide all changes to submodules
 	(and suppresses the output of submodule summaries when the config option
 	`status.submoduleSummary` is set).
 
---ignored[=<mode>]::
+`--ignored[=<mode>]`::
 	Show ignored files as well.
 +
 --
 The mode parameter is used to specify the handling of ignored files.
-It is optional: it defaults to 'traditional'.
+It is optional: it defaults to `traditional`.
 
 The possible options are:
 
-	- 'traditional' - Shows ignored files and directories, unless
-			  --untracked-files=all is specified, in which case
-			  individual files in ignored directories are
-			  displayed.
-	- 'no'	        - Show no ignored files.
-	- 'matching'    - Shows ignored files and directories matching an
-			  ignore pattern.
-
-When 'matching' mode is specified, paths that explicitly match an
+`traditional`:: Show ignored files and directories, unless
+`--untracked-files=all` is specified, in which case
+ individual files in ignored directories are
+ displayed.
+`no`:: Show no ignored files.
+`matching`:: Show ignored files and directories matching an
+ignore pattern.
++
+Paths that explicitly match an
 ignored pattern are shown. If a directory matches an ignore pattern,
 then it is shown, but not paths contained in the ignored directory. If
 a directory does not match an ignore pattern, but all contents are
 ignored, then the directory is not shown, but all contents are shown.
 --
 
--z::
-	Terminate entries with NUL, instead of LF.  This implies
+`-z`::
+	Terminate entries with _NUL_, instead of _LF_.  This implies
 	the `--porcelain=v1` output format if no other format is given.
 
---column[=<options>]::
---no-column::
+`--column[=<options>]`::
+`--no-column`::
 	Display untracked files in columns. See configuration variable
 	`column.status` for option syntax. `--column` and `--no-column`
-	without options are equivalent to 'always' and 'never'
+	without options are equivalent to `always` and `never`
 	respectively.
 
---ahead-behind::
---no-ahead-behind::
+`--ahead-behind`::
+`--no-ahead-behind`::
 	Display or do not display detailed ahead/behind counts for the
-	branch relative to its upstream branch.  Defaults to true.
+	branch relative to its upstream branch.  Defaults to `true`.
 
---renames::
---no-renames::
+`--renames`::
+`--no-renames`::
 	Turn on/off rename detection regardless of user configuration.
 	See also linkgit:git-diff[1] `--no-renames`.
 
---find-renames[=<n>]::
+`--find-renames[=<n>]`::
 	Turn on rename detection, optionally setting the similarity
 	threshold.
 	See also linkgit:git-diff[1] `--find-renames`.
 
-<pathspec>...::
+`<pathspec>...`::
 	See the 'pathspec' entry in linkgit:gitglossary[7].
 
 OUTPUT
@@ -173,12 +176,12 @@
 In the short-format, the status of each path is shown as one of these
 forms
 
-	XY PATH
-	XY ORIG_PATH -> PATH
+	<xy> <path>
+	<xy> <orig-path> -> <path>
 
-where `ORIG_PATH` is where the renamed/copied contents came
-from. `ORIG_PATH` is only shown when the entry is renamed or
-copied. The `XY` is a two-letter status code.
+where _<orig-path>_ is where the renamed/copied contents came
+from. _<orig-path>_ is only shown when the entry is renamed or
+copied. The _<xy>_ is a two-letter status code `XY`.
 
 The fields (including the `->`) are separated from each other by a
 single space. If a filename contains whitespace or other nonprintable
@@ -187,7 +190,7 @@
 interior special characters backslash-escaped.
 
 There are three different types of states that are shown using this format, and
-each one uses the `XY` syntax differently:
+each one uses the _<xy>_ syntax differently:
 
 * When a merge is occurring and the merge was successful, or outside of a merge
 	situation, `X` shows the status of the index and `Y` shows the status of the
@@ -207,60 +210,59 @@
 these characters are used for `X` and `Y` fields for the first two sections that
 show tracked paths:
 
-* ' ' = unmodified
-* 'M' = modified
-* 'T' = file type changed (regular file, symbolic link or submodule)
-* 'A' = added
-* 'D' = deleted
-* 'R' = renamed
-* 'C' = copied (if config option status.renames is set to "copies")
-* 'U' = updated but unmerged
+' ':: unmodified
+`M`:: modified
+`T`:: file type changed (regular file, symbolic link or submodule)
+`A`:: added
+`D`:: deleted
+`R`:: renamed
+`C`:: copied (if config option status.renames is set to "copies")
+`U`:: updated but unmerged
 
-....
-X          Y     Meaning
--------------------------------------------------
-	 [AMD]   not updated
-M        [ MTD]  updated in index
-T        [ MTD]  type changed in index
-A        [ MTD]  added to index
-D                deleted from index
-R        [ MTD]  renamed in index
-C        [ MTD]  copied in index
-[MTARC]          index and work tree matches
-[ MTARC]    M    work tree changed since index
-[ MTARC]    T    type changed in work tree since index
-[ MTARC]    D    deleted in work tree
-	    R    renamed in work tree
-	    C    copied in work tree
--------------------------------------------------
-D           D    unmerged, both deleted
-A           U    unmerged, added by us
-U           D    unmerged, deleted by them
-U           A    unmerged, added by them
-D           U    unmerged, deleted by us
-A           A    unmerged, both added
-U           U    unmerged, both modified
--------------------------------------------------
-?           ?    untracked
-!           !    ignored
--------------------------------------------------
-....
+[cols="^1m,^1m,<2",options="header"]
+|===
+|X        |  Y     |Meaning
+|         |[AMD]   |not updated
+|M        |[ MTD]  |updated in index
+|T        |[ MTD]  |type changed in index
+|A        |[ MTD]  |added to index
+|D        |        |deleted from index
+|R        |[ MTD]  |renamed in index
+|C        |[ MTD]  |copied in index
+|[MTARC]  |        |index and work tree matches
+|[ MTARC] |M       |work tree changed since index
+|[ MTARC] |T       |type changed in work tree since index
+|[ MTARC] |D       |deleted in work tree
+|         |R       |renamed in work tree
+|         |C       |copied in work tree
+|D        |D       |unmerged, both deleted
+|A        |U       |unmerged, added by us
+|U        |D       |unmerged, deleted by them
+|U        |A       |unmerged, added by them
+|D        |U       |unmerged, deleted by us
+|A        |A       |unmerged, both added
+|U        |U       |unmerged, both modified
+|?        |?       |untracked
+|!        |!       |ignored
+|===
 
 Submodules have more state and instead report
 
-* 'M' = the submodule has a different HEAD than recorded in the index
-* 'm' = the submodule has modified content
-* '?' = the submodule has untracked files
+`M`:: the submodule has a different HEAD than recorded in the index
+`m`:: the submodule has modified content
+`?`:: the submodule has untracked files
 
 This is since modified content or untracked files in a submodule cannot be added
 via `git add` in the superproject to prepare a commit.
 
-'m' and '?' are applied recursively. For example if a nested submodule
-in a submodule contains an untracked file, this is reported as '?' as well.
+`m` and `?` are applied recursively. For example if a nested submodule
+in a submodule contains an untracked file, this is reported as `?` as well.
 
-If -b is used the short-format status is preceded by a line
+If `-b` is used the short-format status is preceded by a line
 
-    ## branchname tracking info
+[synopsis]
+{empty}## <branchname> <tracking-info>
+
 
 Porcelain Format Version 1
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -271,16 +273,16 @@
 The description of the short format above also describes the porcelain
 format, with a few exceptions:
 
-1. The user's color.status configuration is not respected; color will
+1. The user's `color.status` configuration is not respected; color will
    always be off.
 
-2. The user's status.relativePaths configuration is not respected; paths
+2. The user's `status.relativePaths` configuration is not respected; paths
    shown will always be relative to the repository root.
 
-There is also an alternate -z format recommended for machine parsing. In
+There is also an alternate `-z` format recommended for machine parsing. In
 that format, the status field is the same, but some other things
-change.  First, the '\->' is omitted from rename entries and the field
-order is reversed (e.g 'from \-> to' becomes 'to from'). Second, a NUL
+change.  First, the `->` is omitted from rename entries and the field
+order is reversed (e.g `from -> to` becomes `to from`). Second, a _NUL_
 (ASCII 0) follows each filename, replacing space as a field separator
 and the terminating newline (but a space still separates the status
 field from the first filename).  Third, filenames containing special
@@ -296,7 +298,7 @@
 the worktree and changed items.  Version 2 also defines an extensible
 set of easy to parse optional headers.
 
-Header lines start with "#" and are added in response to specific
+Header lines start with `#` and are added in response to specific
 command line arguments.  Parsers should ignore headers they
 don't recognize.
 
@@ -306,16 +308,15 @@
 If `--branch` is given, a series of header lines are printed with
 information about the current branch.
 
-....
-Line                                     Notes
-------------------------------------------------------------
-# branch.oid <commit> | (initial)        Current commit.
-# branch.head <branch> | (detached)      Current branch.
-# branch.upstream <upstream-branch>      If upstream is set.
-# branch.ab +<ahead> -<behind>           If upstream is set and
-					 the commit is present.
-------------------------------------------------------------
-....
+[cols="<1,<1",options="header"]
+|===
+|Line                                     |Notes
+|`# branch.oid <commit> \| (initial)`     |Current commit.
+|`# branch.head <branch> \| (detached)`   |Current branch.
+|`# branch.upstream <upstream-branch>`    |If upstream is set.
+|`# branch.ab +<ahead> -<behind>`         |If upstream is set and
+					  the commit is present.
+|===
 
 Stash Information
 ^^^^^^^^^^^^^^^^^
@@ -336,66 +337,73 @@
 
 Ordinary changed entries have the following format:
 
-    1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>
+[synopsis]
+1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>
 
 Renamed or copied entries have the following format:
 
-    2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><sep><origPath>
+[synopsis]
+2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><sep><origPath>
 
-....
-Field       Meaning
---------------------------------------------------------
-<XY>        A 2 character field containing the staged and
-	    unstaged XY values described in the short format,
-	    with unchanged indicated by a "." rather than
-	    a space.
-<sub>       A 4 character field describing the submodule state.
-	    "N..." when the entry is not a submodule.
-	    "S<c><m><u>" when the entry is a submodule.
-	    <c> is "C" if the commit changed; otherwise ".".
-	    <m> is "M" if it has tracked changes; otherwise ".".
-	    <u> is "U" if there are untracked changes; otherwise ".".
-<mH>        The octal file mode in HEAD.
-<mI>        The octal file mode in the index.
-<mW>        The octal file mode in the worktree.
-<hH>        The object name in HEAD.
-<hI>        The object name in the index.
-<X><score>  The rename or copy score (denoting the percentage
-	    of similarity between the source and target of the
-	    move or copy). For example "R100" or "C75".
-<path>      The pathname.  In a renamed/copied entry, this
-	    is the target path.
-<sep>       When the `-z` option is used, the 2 pathnames are separated
-	    with a NUL (ASCII 0x00) byte; otherwise, a tab (ASCII 0x09)
-	    byte separates them.
-<origPath>  The pathname in the commit at HEAD or in the index.
-	    This is only present in a renamed/copied entry, and
-	    tells where the renamed/copied contents came from.
---------------------------------------------------------
-....
+[cols="<1,<1a",options="header"]
+|===
+|Field       | Meaning
+
+|_<XY>_
+|A 2 character field containing the staged and
+unstaged XY values described in the short format,
+with unchanged indicated by a "." rather than
+a space.
+|_<sub>_
+|A 4 character field describing the submodule state.
+"N..." when the entry is not a submodule.
+`S<c><m><u>` when the entry is a submodule.
+
+* _<c>_ is "C" if the commit changed; otherwise ".".
+* _<m>_ is "M" if it has tracked changes; otherwise ".".
+* _<u>_ is "U" if there are untracked changes; otherwise ".".
+|_<mH>_       |The octal file mode in HEAD.
+|_<mI>_       |The octal file mode in the index.
+|_<mW>_       |The octal file mode in the worktree.
+|_<hH>_       |The object name in HEAD.
+|_<hI>_       |The object name in the index.
+|_<X><score>_ |The rename or copy score (denoting the percentage
+of similarity between the source and target of the
+move or copy). For example "R100" or "C75".
+|_<path>_
+|The pathname.  In a renamed/copied entry, this is the target path.
+|_<sep>_
+|When the `-z` option is used, the 2 pathnames are separated
+with a _NUL_ (ASCII 0x00) byte; otherwise, a _TAB_ (ASCII 0x09)
+byte separates them.
+|_<origPath>_
+|The pathname in the commit at HEAD or in the index.
+This is only present in a renamed/copied entry, and
+tells where the renamed/copied contents came from.
+|===
 
 Unmerged entries have the following format; the first character is
 a "u" to distinguish from ordinary changed entries.
 
-    u <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>
+[synopsis]
+u <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>
 
-....
-Field       Meaning
---------------------------------------------------------
-<XY>        A 2 character field describing the conflict type
+[cols="<1,<1a",options="header"]
+|===
+|Field       |Meaning
+|_<XY>_      |A 2 character field describing the conflict type
 	    as described in the short format.
-<sub>       A 4 character field describing the submodule state
+|_<sub>_     |A 4 character field describing the submodule state
 	    as described above.
-<m1>        The octal file mode in stage 1.
-<m2>        The octal file mode in stage 2.
-<m3>        The octal file mode in stage 3.
-<mW>        The octal file mode in the worktree.
-<h1>        The object name in stage 1.
-<h2>        The object name in stage 2.
-<h3>        The object name in stage 3.
-<path>      The pathname.
---------------------------------------------------------
-....
+|_<m1>_      |The octal file mode in stage 1.
+|_<m2>_      |The octal file mode in stage 2.
+|_<m3>_      |The octal file mode in stage 3.
+|_<mW>_      |The octal file mode in the worktree.
+|_<h1>_      |The object name in stage 1.
+|_<h2>_      |The object name in stage 2.
+|_<h3>_      |The object name in stage 3.
+|_<path>_    |The pathname.
+|===
 
 Other Items
 ^^^^^^^^^^^
@@ -416,7 +424,7 @@
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 When the `-z` option is given, pathnames are printed as is and
-without any quoting and lines are terminated with a NUL (ASCII 0x00)
+without any quoting and lines are terminated with a _NUL_ (ASCII 0x00)
 byte.
 
 Without the `-z` option, pathnames with "unusual" characters are
@@ -439,11 +447,11 @@
 If `status.submoduleSummary` is set to a non zero number or true (identical
 to -1 or an unlimited number), the submodule summary will be enabled for
 the long format and a summary of commits for modified submodules will be
-shown (see --summary-limit option of linkgit:git-submodule[1]). Please note
+shown (see `--summary-limit` option of linkgit:git-submodule[1]). Please note
 that the summary output from the status command will be suppressed for all
-submodules when `diff.ignoreSubmodules` is set to 'all' or only for those
+submodules when `diff.ignoreSubmodules` is set to `all` or only for those
 submodules where `submodule.<name>.ignore=all`. To also view the summary for
-ignored submodules you can either use the --ignore-submodules=dirty command
+ignored submodules you can either use the `--ignore-submodules=dirty` command
 line option or the 'git submodule summary' command, which shows a similar
 output but does not honor these settings.
 
@@ -484,7 +492,7 @@
 	setting this variable to `false` disables the warning message
 	given when enumerating untracked files takes more than 2
 	seconds.  In a large project, it may take longer and the user
-	may have already accepted the trade off (e.g. using "-uno" may
+	may have already accepted the trade off (e.g. using `-uno` may
 	not be an acceptable option for the user), in which case, there
 	is no point issuing the warning message, and in such a case,
 	disabling the warning may be the best.
diff --git a/Documentation/git-submodule.adoc b/Documentation/git-submodule.adoc
index 95beaee..722d827 100644
--- a/Documentation/git-submodule.adoc
+++ b/Documentation/git-submodule.adoc
@@ -8,19 +8,19 @@
 
 SYNOPSIS
 --------
-[verse]
-'git submodule' [--quiet] [--cached]
-'git submodule' [--quiet] add [<options>] [--] <repository> [<path>]
-'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]
-'git submodule' [--quiet] init [--] [<path>...]
-'git submodule' [--quiet] deinit [-f|--force] (--all|[--] <path>...)
-'git submodule' [--quiet] update [<options>] [--] [<path>...]
-'git submodule' [--quiet] set-branch [<options>] [--] <path>
-'git submodule' [--quiet] set-url [--] <path> <newurl>
-'git submodule' [--quiet] summary [<options>] [--] [<path>...]
-'git submodule' [--quiet] foreach [--recursive] <command>
-'git submodule' [--quiet] sync [--recursive] [--] [<path>...]
-'git submodule' [--quiet] absorbgitdirs [--] [<path>...]
+[synopsis]
+git submodule [--quiet] [--cached]
+git submodule [--quiet] add [<options>] [--] <repository> [<path>]
+git submodule [--quiet] status [--cached] [--recursive] [--] [<path>...]
+git submodule [--quiet] init [--] [<path>...]
+git submodule [--quiet] deinit [-f|--force] (--all|[--] <path>...)
+git submodule [--quiet] update [<options>] [--] [<path>...]
+git submodule [--quiet] set-branch [<options>] [--] <path>
+git submodule [--quiet] set-url [--] <path> <newurl>
+git submodule [--quiet] summary [<options>] [--] [<path>...]
+git submodule [--quiet] foreach [--recursive] <command>
+git submodule [--quiet] sync [--recursive] [--] [<path>...]
+git submodule [--quiet] absorbgitdirs [--] [<path>...]
 
 
 DESCRIPTION
@@ -34,34 +34,34 @@
 With no arguments, shows the status of existing submodules.  Several
 subcommands are available to perform operations on the submodules.
 
-add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--ref-format <format>] [--depth <depth>] [--] <repository> [<path>]::
+`add [-b <branch>] [-f | --force] [--name <name>] [--reference <repository>] [--ref-format <format>] [--depth <depth>] [--] <repository> [<path>]`::
 	Add the given repository as a submodule at the given path
 	to the changeset to be committed next to the current
 	project: the current project is termed the "superproject".
 +
-<repository> is the URL of the new submodule's origin repository.
-This may be either an absolute URL, or (if it begins with ./
-or ../), the location relative to the superproject's default remote
-repository (Please note that to specify a repository 'foo.git'
-which is located right next to a superproject 'bar.git', you'll
+_<repository>_ is the URL of the new submodule's `origin` repository.
+This may be either an absolute URL, or (if it begins with `./`
+or `../`), the location relative to the superproject's default remote
+repository (Please note that to specify a repository `foo.git`
+which is located right next to a superproject `bar.git`, you'll
 have to use `../foo.git` instead of `./foo.git` - as one might expect
 when following the rules for relative URLs - because the evaluation
 of relative URLs in Git is identical to that of relative directories).
 +
 The default remote is the remote of the remote-tracking branch
 of the current branch. If no such remote-tracking branch exists or
-the HEAD is detached, "origin" is assumed to be the default remote.
+the `HEAD` is detached, `origin` is assumed to be the default remote.
 If the superproject doesn't have a default remote configured
 the superproject is its own authoritative upstream and the current
 working directory is used instead.
 +
-The optional argument <path> is the relative location for the cloned
-submodule to exist in the superproject. If <path> is not given, the
-canonical part of the source repository is used ("repo" for
-"/path/to/repo.git" and "foo" for "host.xz:foo/.git"). If <path>
+The optional argument _<path>_ is the relative location for the cloned
+submodule to exist in the superproject. If _<path>_ is not given, the
+canonical part of the source repository is used (`repo` for
+`/path/to/repo.git` and `foo` for `host.xz:foo/.git`). If _<path>_
 exists and is already a valid Git repository, then it is staged
-for commit without cloning. The <path> is also used as the submodule's
-logical name in its configuration entries unless `--name` is used
+for commit without cloning. The _<path>_ is also used as the submodule's
+logical name in its configuration entries unless `--name <name>` is used
 to specify a logical name.
 +
 The given URL is recorded into `.gitmodules` for use by subsequent users
@@ -75,10 +75,10 @@
 If `--ref-format <format>`  is specified, the ref storage format of newly
 cloned submodules will be set accordingly.
 
-status [--cached] [--recursive] [--] [<path>...]::
+`status [--cached] [--recursive] [--] [<path>...]`::
 	Show the status of the submodules. This will print the SHA-1 of the
 	currently checked out commit for each submodule, along with the
-	submodule path and the output of 'git describe' for the
+	submodule path and the output of linkgit:git-describe[1] for the
 	SHA-1. Each SHA-1 will possibly be prefixed with `-` if the submodule is
 	not initialized, `+` if the currently checked out submodule commit
 	does not match the SHA-1 found in the index of the containing
@@ -91,11 +91,11 @@
 submodules, and show their status as well.
 +
 If you are only interested in changes of the currently initialized
-submodules with respect to the commit recorded in the index or the HEAD,
+submodules with respect to the commit recorded in the index or the `HEAD`,
 linkgit:git-status[1] and linkgit:git-diff[1] will provide that information
 too (and can also report changes to a submodule's work tree).
 
-init [--] [<path>...]::
+`init [--] [<path>...]`::
 	Initialize the submodules recorded in the index (which were
 	added and committed elsewhere) by setting `submodule.$name.url`
 	in `.git/config`, using the same setting from `.gitmodules` as
@@ -103,7 +103,7 @@
 	the default remote. If there is no default remote, the current
 	repository will be assumed to be upstream.
 +
-Optional <path> arguments limit which submodules will be initialized.
+Optional _<path>_ arguments limit which submodules will be initialized.
 If no path is specified and submodule.active has been configured, submodules
 configured to be active will be initialized, otherwise all submodules are
 initialized.
@@ -116,12 +116,12 @@
 You can then customize the submodule clone URLs in `.git/config`
 for your local setup and proceed to `git submodule update`;
 you can also just use `git submodule update --init` without
-the explicit 'init' step if you do not intend to customize
+the explicit `init` step if you do not intend to customize
 any submodule locations.
 +
 See the add subcommand for the definition of default remote.
 
-deinit [-f|--force] (--all|[--] <path>...)::
+`deinit [-f | --force] (--all|[--] <path>...)`::
 	Unregister the given submodules, i.e. remove the whole
 	`submodule.$name` section from .git/config together with their work
 	tree. Further calls to `git submodule update`, `git submodule foreach`
@@ -139,7 +139,7 @@
 that use linkgit:git-rm[1] instead. See linkgit:gitsubmodules[7] for removal
 options.
 
-update [--init] [--remote] [-N|--no-fetch] [--[no-]recommend-shallow] [-f|--force] [--checkout|--rebase|--merge] [--reference <repository>] [--ref-format <format>] [--depth <depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--filter <filter-spec>] [--] [<path>...]::
+`update [--init] [--remote] [-N | --no-fetch] [--[no-]recommend-shallow] [-f | --force] [--checkout | --rebase | --merge] [--reference=<repository>] [--ref-format=<format>] [--depth=<depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--filter=<filter-spec>] [--] [<path>...]`::
 +
 --
 Update the registered submodules to match what the superproject
@@ -148,38 +148,38 @@
 the submodules. The "updating" can be done in several ways depending
 on command line options and the value of `submodule.<name>.update`
 configuration variable. The command line option takes precedence over
-the configuration variable. If neither is given, a 'checkout' is performed.
+the configuration variable. If neither is given, a `checkout` is performed.
 (note: what is in `.gitmodules` file is irrelevant at this point;
 see `git submodule init` above for how `.gitmodules` is used).
-The 'update' procedures supported both from the command line as well as
+The `update` procedures supported both from the command line as well as
 through the `submodule.<name>.update` configuration are:
 
-	checkout;; the commit recorded in the superproject will be
-	    checked out in the submodule on a detached HEAD.
+`checkout`;; the commit recorded in the superproject will be
+checked out in the submodule on a detached `HEAD`.
 +
 If `--force` is specified, the submodule will be checked out (using
 `git checkout --force`), even if the commit specified
 in the index of the containing repository already matches the commit
 checked out in the submodule.
 
-	rebase;; the current branch of the submodule will be rebased
-	    onto the commit recorded in the superproject.
+`rebase`;; the current branch of the submodule will be rebased
+onto the commit recorded in the superproject.
 
-	merge;; the commit recorded in the superproject will be merged
-	    into the current branch in the submodule.
+`merge`;; the commit recorded in the superproject will be merged
+into the current branch in the submodule.
 
 The following update procedures have additional limitations:
 
-	custom command;; mechanism for running arbitrary commands with the
-	    commit ID as an argument. Specifically, if the
-	    `submodule.<name>.update` configuration variable is set to
-	    `!custom command`, the object name of the commit recorded in the
-	    superproject for the submodule is appended to the `custom command`
-	    string and executed. Note that this mechanism is not supported in
-	    the `.gitmodules` file or on the command line.
+`!<custom-command>`;; mechanism for running arbitrary commands with the
+commit ID as an argument. Specifically, if the
+`submodule.<name>.update` configuration variable is set to
+`!<custom-command>`, the object name of the commit recorded in the
+superproject for the submodule is appended to the _<custom-command>_
+string and executed. Note that this mechanism is not supported in
+the `.gitmodules` file or on the command line.
 
-	none;; the submodule is not updated. This update procedure is not
-	    allowed on the command line.
+`none`;; the submodule is not updated. This update procedure is not
+allowed on the command line.
 
 If the submodule is not yet initialized, and you just want to use the
 setting as stored in `.gitmodules`, you can automatically initialize the
@@ -195,20 +195,20 @@
 applied to the submodule. See linkgit:git-rev-list[1] for details on filter
 specifications.
 --
-set-branch (-b|--branch) <branch> [--] <path>::
-set-branch (-d|--default) [--] <path>::
-	Sets the default remote tracking branch for the submodule. The
+`set-branch (-b|--branch) <branch> [--] <path>`::
+`set-branch (-d|--default) [--] <path>`::
+	Set the default remote tracking branch for the submodule. The
 	`--branch` option allows the remote branch to be specified. The
-	`--default` option removes the submodule.<name>.branch configuration
-	key, which causes the tracking branch to default to the remote 'HEAD'.
+	`--default` option removes the `submodule.<name>.branch` configuration
+	key, which causes the tracking branch to default to the remote `HEAD`.
 
-set-url [--] <path> <newurl>::
-	Sets the URL of the specified submodule to <newurl>. Then, it will
+`set-url [--] <path> <newurl>`::
+	Set the URL of the specified submodule to _<newurl>_. Then, it will
 	automatically synchronize the submodule's new remote URL
 	configuration.
 
-summary [--cached|--files] [(-n|--summary-limit) <n>] [commit] [--] [<path>...]::
-	Show commit summary between the given commit (defaults to HEAD) and
+`summary [--cached | --files] [(-n|--summary-limit) <n>] [commit] [--] [<path>...]`::
+	Show commit summary between the given commit (defaults to `HEAD`) and
 	working tree/index. For a submodule in question, a series of commits
 	in the submodule between the given super project commit and the
 	index or working tree (switched by `--cached`) are shown. If the option
@@ -220,27 +220,31 @@
 Using the `--submodule=log` option with linkgit:git-diff[1] will provide that
 information too.
 
-foreach [--recursive] <command>::
-	Evaluates an arbitrary shell command in each checked out submodule.
-	The command has access to the variables $name, $sm_path, $displaypath,
-	$sha1 and $toplevel:
-	$name is the name of the relevant submodule section in `.gitmodules`,
-	$sm_path is the path of the submodule as recorded in the immediate
-	superproject, $displaypath contains the relative path from the
-	current working directory to the submodules root directory,
-	$sha1 is the commit as recorded in the immediate
-	superproject, and $toplevel is the absolute path to the top-level
-	of the immediate superproject.
-	Note that to avoid conflicts with '$PATH' on Windows, the '$path'
-	variable is now a deprecated synonym of '$sm_path' variable.
-	Any submodules defined in the superproject but not checked out are
-	ignored by this command. Unless given `--quiet`, foreach prints the name
-	of each submodule before evaluating the command.
-	If `--recursive` is given, submodules are traversed recursively (i.e.
-	the given shell command is evaluated in nested submodules as well).
-	A non-zero return from the command in any submodule causes
-	the processing to terminate. This can be overridden by adding '|| :'
-	to the end of the command.
+`foreach [--recursive] <command>`::
+	Evaluate an arbitrary shell _<command>_ in each checked out submodule.
+	The command has access to the variables `$name`, `$sm_path`, `$displaypath`,
+	`$sha1` and `$toplevel`:
++
+--
+`$name`;; the name of the relevant submodule section in `.gitmodules`
+`$sm_path`;; the path of the submodule as recorded in the immediate
+	superproject
+`$displaypath`;; the relative path from the
+	current working directory to the submodules root directory
+`$sha1`;; the commit as recorded in the immediate superproject
+`$toplevel`;; the absolute path to the top-level of the immediate superproject.
+--
++
+Note that to avoid conflicts with `$PATH` on Windows, the `$path`
+variable is now a deprecated synonym of `$sm_path` variable.
+Any submodules defined in the superproject but not checked out are
+ignored by this command. Unless given `--quiet`, foreach prints the name
+of each submodule before evaluating the command.
+If `--recursive` is given, submodules are traversed recursively (i.e.
+the given shell command is evaluated in nested submodules as well).
+A non-zero return from the command in any submodule causes
+the processing to terminate. This can be overridden by adding ++||:++
+to the end of the command.
 +
 As an example, the command below will show the path and currently
 checked out commit for each submodule:
@@ -249,26 +253,26 @@
 git submodule foreach 'echo $sm_path `git rev-parse HEAD`'
 --------------
 
-sync [--recursive] [--] [<path>...]::
-	Synchronizes submodules' remote URL configuration setting
+`sync [--recursive] [--] [<path>...]`::
+	Synchronize submodules' remote URL configuration setting
 	to the value specified in `.gitmodules`. It will only affect those
-	submodules which already have a URL entry in .git/config (that is the
+	submodules which already have a URL entry in `.git/config` (that is the
 	case when they are initialized or freshly added). This is useful when
 	submodule URLs change upstream and you need to update your local
 	repositories accordingly.
 +
 `git submodule sync` synchronizes all submodules while
-`git submodule sync -- A` synchronizes submodule "A" only.
+`git submodule sync -- A` synchronizes submodule `A` only.
 +
 If `--recursive` is specified, this command will recurse into the
 registered submodules, and sync any nested submodules within.
 
-absorbgitdirs::
+`absorbgitdirs`::
 	If a git directory of a submodule is inside the submodule,
 	move the git directory of the submodule into its superproject's
 	`$GIT_DIR/modules` path and then connect the git directory and
 	its working directory by setting the `core.worktree` and adding
-	a .git file pointing to the git directory embedded in the
+	a `.git` file pointing to the git directory embedded in the
 	superprojects git directory.
 +
 A repository that was cloned independently and later added as a submodule or
@@ -279,72 +283,70 @@
 
 OPTIONS
 -------
--q::
---quiet::
+`-q`::
+`--quiet`::
 	Only print error messages.
 
---progress::
-	This option is only valid for add and update commands.
-	Progress status is reported on the standard error stream
-	by default when it is attached to a terminal, unless -q
+`--progress`::
+	Report progress status on the standard error stream
+	by default when it is attached to a terminal, unless `-q`
 	is specified. This flag forces progress status even if the
-	standard error stream is not directed to a terminal.
+	standard error stream is not directed to a terminal. It is
+	only valid for `add` and `update` commands.
 
---all::
-	This option is only valid for the deinit command. Unregister all
-	submodules in the working tree.
+`--all`::
+	Unregister all submodules in the working tree. This option is only
+	valid for the `deinit` command.
 
--b <branch>::
---branch <branch>::
+`-b<branch>`::
+`--branch=<branch>`::
 	Branch of repository to add as submodule.
 	The name of the branch is recorded as `submodule.<name>.branch` in
 	`.gitmodules` for `update --remote`.  A special value of `.` is used to
 	indicate that the name of the branch in the submodule should be the
 	same name as the current branch in the current repository.  If the
-	option is not specified, it defaults to the remote 'HEAD'.
+	option is not specified, it defaults to the remote `HEAD`.
 
--f::
---force::
-	This option is only valid for add, deinit and update commands.
-	When running add, allow adding an otherwise ignored submodule path.
-	This option is also used to bypass a check that the submodule's name
-	is not already in use. By default, 'git submodule add' will fail if
-	the proposed name (which is derived from the path) is already registered
-	for another submodule in the repository. Using '--force' allows the command
-	to proceed by automatically generating a unique name by appending a number
-	to the conflicting name (e.g., if a submodule named 'child' exists, it will
-	try 'child1', and so on).
-	When running deinit the submodule working trees will be removed even
-	if they contain local changes.
-	When running update (only effective with the checkout procedure),
-	throw away local changes in submodules when switching to a
-	different commit; and always run a checkout operation in the
-	submodule, even if the commit listed in the index of the
-	containing repository matches the commit checked out in the
-	submodule.
+`-f`::
+`--force`::
+	Force the command to proceed, even if it would otherwise fail.
+	This option is only valid for `add`, `deinit` and `update` commands.
+`add`;; allow adding an otherwise ignored submodule path.
+This option is also used to bypass a check that the submodule's name
+is not already in use. By default, `git submodule add` will fail if
+the proposed name (which is derived from the path) is already registered
+for another submodule in the repository. Using `--force` allows the command
+to proceed by automatically generating a unique name by appending a number
+to the conflicting name (e.g., if a submodule named 'child' exists, it will
+try 'child1', and so on).
+`deinit`;; the submodule working trees will be removed even
+if they contain local changes.
+`update`;; (only effective with the checkout procedure),
+throw away local changes in submodules when switching to a
+different commit; and always run a checkout operation in the
+submodule, even if the commit listed in the index of the
+containing repository matches the commit checked out in the
+submodule.
 
---cached::
-	This option is only valid for status and summary commands.  These
-	commands typically use the commit found in the submodule HEAD, but
-	with this option, the commit stored in the index is used instead.
+`--cached`::
+	Use the index to determine the commit instead of the `HEAD`.
+	This option is only valid for `status` and `summary` commands.
 
---files::
-	This option is only valid for the summary command. This command
-	compares the commit in the index with that in the submodule HEAD
-	when this option is used.
+`--files`::
+	Make the `summary` command compare the commit in the index with that in
+	the submodule `HEAD`.
 
--n::
---summary-limit::
-	This option is only valid for the summary command.
-	Limit the summary size (number of commits shown in total).
+`-n<n>`::
+`--summary-limit=<n>`::
+	Limit the `summary` size (number of commits shown in total) to _<n>_.
 	Giving 0 will disable the summary; a negative number means unlimited
 	(the default). This limit only applies to modified submodules. The
 	size is always limited to 1 for added/deleted/typechanged submodules.
 
---remote::
-	This option is only valid for the update command.  Instead of using
-	the superproject's recorded SHA-1 to update the submodule, use the
-	status of the submodule's remote-tracking branch.  The remote used
+`--remote`::
+	Instead of using the superproject's recorded SHA-1 to update the
+	submodule, use the status of the submodule's remote-tracking branch.
+	This option is only valid for the `update` command. The remote used
 	is branch's remote (`branch.<name>.remote`), defaulting to `origin`.
 	The remote branch used defaults to the remote `HEAD`, but the branch
 	name may be overridden by setting the `submodule.<name>.branch`
@@ -363,7 +365,7 @@
 --remote --no-fetch`.
 +
 Use this option to integrate changes from the upstream subproject with
-your submodule's current HEAD.  Alternatively, you can run `git pull`
+your submodule's current `HEAD`.  Alternatively, you can run `git pull`
 from the submodule, which is equivalent except for the remote branch
 name: `update --remote` uses the default upstream repository and
 `submodule.<name>.branch`, while `git pull` uses the submodule's
@@ -372,105 +374,106 @@
 `branch.<name>.merge` if you want a more native feel while working in
 the submodule itself.
 
--N::
---no-fetch::
-	This option is only valid for the update command.
+`-N`::
+`--no-fetch`::
 	Don't fetch new objects from the remote site.
+	This option is only valid for the `update` command.
 
---checkout::
-	This option is only valid for the update command.
-	Checkout the commit recorded in the superproject on a detached HEAD
-	in the submodule. This is the default behavior, the main use of
-	this option is to override `submodule.$name.update` when set to
+`--checkout`::
+	Checkout the commit recorded in the superproject on a detached `HEAD`
+	in the submodule. This option is only valid for the `update` command.
+	This is the default behavior, the main use of
+	this option is to override `submodule.<name>.update` when set to
 	a value other than `checkout`.
-	If the key `submodule.$name.update` is either not explicitly set or
+	If the key `submodule.<name>.update` is either not explicitly set or
 	set to `checkout`, this option is implicit.
 
---merge::
-	This option is only valid for the update command.
+`--merge`::
 	Merge the commit recorded in the superproject into the current branch
-	of the submodule. If this option is given, the submodule's HEAD will
+	of the submodule. This option is only valid for the `update` command.
+	If this option is given, the submodule's `HEAD` will
 	not be detached. If a merge failure prevents this process, you will
 	have to resolve the resulting conflicts within the submodule with the
 	usual conflict resolution tools.
-	If the key `submodule.$name.update` is set to `merge`, this option is
+	If the key `submodule.<name>.update` is set to `merge`, this option is
 	implicit.
 
---rebase::
-	This option is only valid for the update command.
-	Rebase the current branch onto the commit recorded in the
-	superproject. If this option is given, the submodule's HEAD will not
+`--rebase`::
+	Rebase the current branch onto the commit recorded in the superproject.
+	This option is only valid for the `update` command. The submodule's `HEAD` will not
 	be detached. If a merge failure prevents this process, you will have
 	to resolve these failures with linkgit:git-rebase[1].
-	If the key `submodule.$name.update` is set to `rebase`, this option is
+	If the key `submodule.<name>.update` is set to `rebase`, this option is
 	implicit.
 
---init::
-	This option is only valid for the update command.
-	Initialize all submodules for which "git submodule init" has not been
-	called so far before updating.
+`--init`::
+	Initialize all submodules for which `git submodule init` has not been
+	called so far before updating. 	This option is only valid for the `update`
+	command.
 
---name::
-	This option is only valid for the add command. It sets the submodule's
-	name to the given string instead of defaulting to its path. The name
-	must be valid as a directory name and may not end with a '/'.
 
---reference <repository>::
-	This option is only valid for add and update commands.  These
-	commands sometimes need to clone a remote repository. In this case,
+`--name=<name>`::
+	Set the submodule's name to the given string instead of defaulting to its path. _<name>_
+	must be valid as a directory name and may not end with a `/`.
+
+`--reference=<repository>`::
+	Pass the local _<repository>_ as a reference when cloning the submodule.
+	This option is only valid for `add` and `update` commands.
+	These commands sometimes need to clone a remote repository. In this case,
 	this option will be passed to the linkgit:git-clone[1] command.
 +
-*NOTE*: Do *not* use this option unless you have read the note
+NOTE: Do *not* use this option unless you have read the note
 for linkgit:git-clone[1]'s `--reference`, `--shared`, and `--dissociate`
 options carefully.
 
---dissociate::
-	This option is only valid for add and update commands.  These
-	commands sometimes need to clone a remote repository. In this case,
+`--dissociate`::
+	After using a reference repository to clone from, do not rely on it anymore.
+	This option is only valid for `add` and `update` commands.
+	These commands sometimes need to clone a remote repository. In this case,
 	this option will be passed to the linkgit:git-clone[1] command.
 +
-*NOTE*: see the NOTE for the `--reference` option.
+NOTE: See the NOTE above for the `--reference` option.
 
---recursive::
-	This option is only valid for foreach, update, status and sync commands.
-	Traverse submodules recursively. The operation is performed not
+`--recursive`::
+	Traverse submodules recursively. This option is only valid for `foreach`,
+	`update`, `status` and `sync` commands. The operation is performed not
 	only in the submodules of the current repo, but also
 	in any nested submodules inside those submodules (and so on).
 
---depth::
-	This option is valid for add and update commands. Create a 'shallow'
-	clone with a history truncated to the specified number of revisions.
-	See linkgit:git-clone[1]
+`--depth=<depth>`::
+	Create a 'shallow' clone with a history truncated to the _<depth>_ revisions.
+	This option is valid for `add` and `update` commands. See linkgit:git-clone[1]
 
---recommend-shallow::
---no-recommend-shallow::
-	This option is only valid for the update command.
+`--recommend-shallow`::
+`--no-recommend-shallow`::
+	Recommend or not shallow cloning of submodules.
+	This option is only valid for the `update` command.
 	The initial clone of a submodule will use the recommended
 	`submodule.<name>.shallow` as provided by the `.gitmodules` file
 	by default. To ignore the suggestions use `--no-recommend-shallow`.
 
--j <n>::
---jobs <n>::
-	This option is only valid for the update command.
-	Clone new submodules in parallel with as many jobs.
+`-j<n>`::
+`--jobs=<n>`::
+	Clone new submodules in parallel with _<n>_ jobs.
+	This option is only valid for the `update` command.
 	Defaults to the `submodule.fetchJobs` option.
 
---single-branch::
---no-single-branch::
-	This option is only valid for the update command.
-	Clone only one branch during update: HEAD or one specified by --branch.
+`--single-branch`::
+`--no-single-branch`::
+	Clone only one branch during update: `HEAD` or one specified by `--branch`.
+	This option is only valid for the `update` command.
 
-<path>...::
+`<path>...`::
 	Paths to submodule(s). When specified this will restrict the command
 	to only operate on the submodules found at the specified paths.
-	(This argument is required with add).
+	(This argument is required with `add`).
 
 FILES
 -----
 When initializing submodules, a `.gitmodules` file in the top-level directory
-of the containing repository is used to find the url of each submodule.
+of the containing repository is used to find the URL of each submodule.
 This file should be formatted in the same way as `$GIT_DIR/config`. The key
-to each submodule url is "submodule.$name.url".  See linkgit:gitmodules[5]
+to each submodule URL is `submodule.<name>.url`.  See linkgit:gitmodules[5]
 for details.
 
 SEE ALSO
diff --git a/Documentation/git-update-ref.adoc b/Documentation/git-update-ref.adoc
index 9310ce9..37a5019 100644
--- a/Documentation/git-update-ref.adoc
+++ b/Documentation/git-update-ref.adoc
@@ -119,7 +119,7 @@
 	Verify <ref> against <old-oid> but do not change it.  If
 	<old-oid> is zero or missing, the ref must not exist.
 
-symref-create:
+symref-create::
 	Create symbolic ref <ref> with <new-target> after verifying that
 	it does not exist.
 
diff --git a/Documentation/git-worktree.adoc b/Documentation/git-worktree.adoc
index f272f79..fbf8426 100644
--- a/Documentation/git-worktree.adoc
+++ b/Documentation/git-worktree.adoc
@@ -104,7 +104,7 @@
 passed to the command. In the event the repository has a remote and
 `--guess-remote` is used, but no remote or local branches exist, then the
 command fails with a warning reminding the user to fetch from their remote
-first (or override by using `-f/--force`).
+first (or override by using `-f`/`--force`).
 
 `list`::
 
@@ -131,7 +131,13 @@
 
 `prune`::
 
-Prune worktree information in `$GIT_DIR/worktrees`.
+Remove worktree information in `$GIT_DIR/worktrees` for worktrees
+whose working trees are missing.  Useful after manually removing
+a working tree that is no longer needed (but use "git worktree
+remove" next time you want to do so).  Also, if you _moved_ a
+working tree elsewhere causing the worktree information to become
+dangling, see "git worktree repair" to reconnect the worktree to
+the new working tree location.
 
 `remove`::
 
@@ -271,7 +277,7 @@
 With `list`, output additional information about worktrees (see below).
 
 `--expire <time>`::
-	With `prune`, only expire unused worktrees older than _<time>_.
+	With `prune`, only prune missing worktrees if older than _<time>_.
 +
 With `list`, annotate missing worktrees as prunable if they are older than
 _<time>_.
diff --git a/Documentation/git.adoc b/Documentation/git.adoc
index ce099e7..8a5cdd3 100644
--- a/Documentation/git.adoc
+++ b/Documentation/git.adoc
@@ -235,7 +235,10 @@
 ------------
 
 We divide Git into high level ("porcelain") commands and low level
-("plumbing") commands.
+("plumbing") commands.  For defining command aliases, see
+linkgit:git-config[1] and look for descriptions of `alias.*`.
+For installing custom "git" subcommands, see the description for
+the 'PATH' environment variable in this manual.
 
 High-level commands (porcelain)
 -------------------------------
@@ -487,6 +490,14 @@
 	`$HOMEDRIVE$HOMEPATH` if both `$HOMEDRIVE` and `$HOMEPATH` exist;
 	otherwise `$USERPROFILE` if `$USERPROFILE` exists.
 
+`PATH`::
+	When a user runs 'git <command>' that is not part of the core Git programs
+	(installed in GIT_EXEC_PATH), 'git-<command>' that is runnable by the user
+	in a directory on `$PATH` is invoked. Argument passed after the command
+	name are passed as-is to the program. To execute `git <foo>`, `git` finds
+	command `<foo>` (either a core Git program found in 'GIT_EXEC_PATH', or a
+	custom one in a directory on 'PATH'), before trying `foo` as an alias.
+
 The Git Repository
 ~~~~~~~~~~~~~~~~~~
 These environment variables apply to 'all' core Git commands. Nb: it
@@ -584,6 +595,11 @@
 	repositories will be set to this value. The default is "files".
 	See `--ref-format` in linkgit:git-init[1].
 
+`GIT_REFERENCE_BACKEND`::
+    Specify which reference backend to be used along with its URI.
+    See `extensions.refStorage` option in linkgit:git-config[1] for more
+    details. Overrides the config variable when used.
+
 Git Commits
 ~~~~~~~~~~~
 `GIT_AUTHOR_NAME`::
diff --git a/Documentation/gitdatamodel.adoc b/Documentation/gitdatamodel.adoc
new file mode 100644
index 0000000..dcfdff0
--- /dev/null
+++ b/Documentation/gitdatamodel.adoc
@@ -0,0 +1,305 @@
+gitdatamodel(7)
+===============
+
+NAME
+----
+gitdatamodel - Git's core data model
+
+SYNOPSIS
+--------
+gitdatamodel
+
+DESCRIPTION
+-----------
+
+It's not necessary to understand Git's data model to use Git, but it's
+very helpful when reading Git's documentation so that you know what it
+means when the documentation says "object", "reference" or "index".
+
+Git's core operations use 4 kinds of data:
+
+1. <<objects,Objects>>: commits, trees, blobs, and tag objects
+2. <<references,References>>: branches, tags,
+   remote-tracking branches, etc
+3. <<index,The index>>, also known as the staging area
+4. <<reflogs,Reflogs>>: logs of changes to references ("ref log")
+
+[[objects]]
+OBJECTS
+-------
+
+All of the commits and files in a Git repository are stored as "Git objects".
+Git objects never change after they're created, and every object has an ID,
+like `1b61de420a21a2f1aaef93e38ecd0e45e8bc9f0a`.
+
+This means that if you have an object's ID, you can always recover its
+exact contents as long as the object hasn't been deleted.
+
+Every object has:
+
+[[object-id]]
+1. an *ID* (aka "object name"), which is a cryptographic hash of its
+  type and contents.
+  It's fast to look up a Git object using its ID.
+  This is usually represented in hexadecimal, like
+  `1b61de420a21a2f1aaef93e38ecd0e45e8bc9f0a`.
+2. a *type*. There are 4 types of objects:
+   <<commit,commits>>, <<tree,trees>>, <<blob,blobs>>,
+   and <<tag-object,tag objects>>.
+3. *contents*. The structure of the contents depends on the type.
+
+Here's how each type of object is structured:
+
+[[commit]]
+commit::
+    A commit contains these required fields
+    (though there are other optional fields):
++
+1. The full directory structure of all the files in that version of the
+   repository and each file's contents, stored as the *<<tree,tree>>* ID
+   of the commit's top-level directory
+2. Its *parent commit ID(s)*. The first commit in a repository has 0 parents,
+  regular commits have 1 parent, merge commits have 2 or more parents
+3. An *author* and the time the commit was authored
+4. A *committer* and the time the commit was committed
+5. A *commit message*
++
+Here's how an example commit is stored:
++
+----
+tree 1b61de420a21a2f1aaef93e38ecd0e45e8bc9f0a
+parent 4ccb6d7b8869a86aae2e84c56523f8705b50c647
+author Maya <maya@example.com> 1759173425 -0400
+committer Maya <maya@example.com> 1759173425 -0400
+
+Add README
+----
++
+Like all other objects, commits can never be changed after they're created.
+For example, "amending" a commit with `git commit --amend` creates a new
+commit with the same parent.
++
+Git does not store the diff for a commit: when you ask Git to show
+the commit with linkgit:git-show[1], it calculates the diff from its
+parent on the fly.
+
+[[tree]]
+tree::
+    A tree is how Git represents a directory.
+    It can contain files or other trees (which are subdirectories).
+    It lists, for each item in the tree:
++
+1. The *filename*, for example `hello.py`
+2. The *file type*, which must be one of these five types:
+  - *regular file*
+  - *executable file*
+  - *symbolic link*
+  - *directory*
+  - *gitlink* (for use with submodules)
+3. The <<object-id,*object ID*>> with the contents of the file, directory,
+   or gitlink.
++
+For example, this is how a tree containing one directory (`src`) and one file
+(`README.md`) is stored:
++
+----
+100644 blob 8728a858d9d21a8c78488c8b4e70e531b659141f README.md
+040000 tree 89b1d2e0495f66d6929f4ff76ff1bb07fc41947d src
+----
+
+NOTE: In the output above, Git displays the file type of each tree entry
+using a format that's loosely modelled on Unix file modes (`100644` is
+"regular file", `100755` is "executable file", `120000` is "symbolic
+link", `040000` is "directory", and `160000` is "gitlink"). It also
+displays the object's type: `blob` for files and symlinks, `tree` for
+directories, and `commit` for gitlinks.
+
+[[blob]]
+blob::
+    A blob object contains a file's contents.
++
+When you make a commit, Git stores the full contents of each file that
+you changed as a blob.
+For example, if you have a commit that changes 2 files in a repository
+with 1000 files, that commit will create 2 new blobs, and use the
+previous blob ID for the other 998 files.
+This means that commits can use relatively little disk space even in a
+very large repository.
+
+[[tag-object]]
+tag object::
+    Tag objects contain these required fields
+    (though there are other optional fields):
++
+1. The *ID* of the object it references
+2. The *type* of the object it references
+3. The *tagger* and tag date
+4. A *tag message*, similar to a commit message
+
+Here's how an example tag object is stored:
+
+----
+object 750b4ead9c87ceb3ddb7a390e6c7074521797fb3
+type commit
+tag v1.0.0
+tagger Maya <maya@example.com> 1759927359 -0400
+
+Release version 1.0.0
+----
+
+NOTE: All of the examples in this section were generated with
+`git cat-file -p <object-id>`.
+
+[[references]]
+REFERENCES
+----------
+
+References are a way to give a name to a commit.
+It's easier to remember "the changes I'm working on are on the `turtle`
+branch" than "the changes are in commit bb69721404348e".
+Git often uses "ref" as shorthand for "reference".
+
+References can either refer to:
+
+1. An object ID, usually a <<commit,commit>> ID
+2. Another reference. This is called a "symbolic reference"
+
+References are stored in a hierarchy, and Git handles references
+differently based on where they are in the hierarchy.
+Most references are under `refs/`. Here are the main types:
+
+[[branch]]
+branches: `refs/heads/<name>`::
+    A branch refers to a commit ID.
+    That commit is the latest commit on the branch.
++
+To get the history of commits on a branch, Git will start at the commit
+ID the branch references, and then look at the commit's parent(s),
+the parent's parent, etc.
+
+[[tag]]
+tags: `refs/tags/<name>`::
+    A tag refers to a commit ID, tag object ID, or other object ID.
+    There are two types of tags:
+    1. "Annotated tags", which reference a <<tag-object,tag object>> ID
+       which contains a tag message
+    2. "Lightweight tags", which reference a commit, blob, or tree ID
+       directly
++
+Even though branches and tags both refer to a commit ID, Git
+treats them very differently.
+Branches are expected to change over time: when you make a commit, Git
+will update your <<HEAD,current branch>> to point to the new commit.
+Tags are usually not changed after they're created.
+
+[[HEAD]]
+HEAD: `HEAD`::
+    `HEAD` is where Git stores your current <<branch,branch>>,
+    if there is a current branch. `HEAD` can either be:
++
+1. A symbolic reference to your current branch, for example `ref:
+   refs/heads/main` if your current branch is `main`.
+2. A direct reference to a commit ID. In this case there is no current branch.
+   This is called "detached HEAD state", see the DETACHED HEAD section
+   of linkgit:git-checkout[1] for more.
+
+[[remote-tracking-branch]]
+remote-tracking branches: `refs/remotes/<remote>/<branch>`::
+    A remote-tracking branch refers to a commit ID.
+    It's how Git stores the last-known state of a branch in a remote
+    repository. `git fetch` updates remote-tracking branches. When
+    `git status` says "you're up to date with origin/main", it's looking at
+    this.
++
+`refs/remotes/<remote>/HEAD` is a symbolic reference to the remote's
+default branch. This is the branch that `git clone` checks out by default.
+
+[[other-refs]]
+Other references::
+    Git tools may create references anywhere under `refs/`.
+    For example, linkgit:git-stash[1], linkgit:git-bisect[1],
+    and linkgit:git-notes[1] all create their own references
+    in `refs/stash`, `refs/bisect`, etc.
+    Third-party Git tools may also create their own references.
++
+Git may also create references other than `HEAD` at the base of the
+hierarchy, like `ORIG_HEAD`.
+
+NOTE: Git may delete objects that aren't "reachable" from any reference
+or <<reflogs,reflog>>.
+An object is "reachable" if we can find it by following tags to whatever
+they tag, commits to their parents or trees, and trees to the trees or
+blobs that they contain.
+For example, if you amend a commit with `git commit --amend`,
+there will no longer be a branch that points at the old commit.
+The old commit is recorded in the current branch's <<reflogs,reflog>>,
+so it is still "reachable", but when the reflog entry expires it may
+become unreachable and get deleted.
+Reachable objects will never be deleted.
+
+[[index]]
+THE INDEX
+---------
+The index, also known as the "staging area", is a list of files and
+the contents of each file, stored as a <<blob,blob>>.
+You can add files to the index or update the contents of a file in the
+index with linkgit:git-add[1]. This is called "staging" the file for commit.
+
+Unlike a <<tree,tree>>, the index is a flat list of files.
+When you commit, Git converts the list of files in the index to a
+directory <<tree,tree>> and uses that tree in the new <<commit,commit>>.
+
+Each index entry has 4 fields:
+
+1. The *file type*, which must be one of:
+  - *regular file*
+  - *executable file*
+  - *symbolic link*
+  - *gitlink* (for use with submodules)
+2. The *<<blob,blob>>* ID of the file,
+   or (rarely) the *<<commit,commit>>* ID of the submodule
+3. The *stage number*, either 0, 1, 2, or 3. This is normally 0, but if
+   there's a merge conflict there can be multiple versions of the same
+   filename in the index.
+4. The *file path*, for example `src/hello.py`
+
+It's extremely uncommon to look at the index directly: normally you'd
+run `git status` to see a list of changes between the index and <<HEAD,HEAD>>.
+But you can use `git ls-files --stage` to see the index.
+Here's the output of `git ls-files --stage` in a repository with 2 files:
+
+----
+100644 8728a858d9d21a8c78488c8b4e70e531b659141f 0 README.md
+100644 665c637a360874ce43bf74018768a96d2d4d219a 0 src/hello.py
+----
+
+[[reflogs]]
+REFLOGS
+-------
+
+Every time a branch, remote-tracking branch, or HEAD is updated, Git
+updates a log called a "reflog" for that <<references,reference>>.
+This means that if you make a mistake and "lose" a commit, you can
+generally recover the commit ID by running `git reflog <reference>`.
+
+A reflog is a list of log entries. Each entry has:
+
+1. The *commit ID*
+2. *Timestamp* when the change was made
+3. *Log message*, for example `pull: Fast-forward`
+
+Reflogs only log changes made in your local repository.
+They are not shared with remotes.
+
+You can view a reflog with `git reflog <reference>`.
+For example, here's the reflog for a `main` branch which has changed twice:
+
+----
+$ git reflog main --date=iso --no-decorate
+750b4ea main@{2025-09-29 15:17:05 -0400}: commit: Add README
+4ccb6d7 main@{2025-09-29 15:16:48 -0400}: commit (initial): Initial commit
+----
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gitfaq.adoc b/Documentation/gitfaq.adoc
index f2917d1..f6c9b9d 100644
--- a/Documentation/gitfaq.adoc
+++ b/Documentation/gitfaq.adoc
@@ -83,6 +83,25 @@
 which quotes the filename with spaces and specifies the `--nofork` option to
 avoid backgrounding the process.
 
+[[sign-off]]
+Why not have `commit.signoff` and other configuration variables?::
+	Git intentionally does not (and will not) provide a
+	configuration variable, such as `commit.signoff`, to
+	automatically add `--signoff` by default.  The reason is to
+	protect the legal and intentional significance of a sign-off.
+	If there were more automated and widely publicized ways for
+	sign-offs to be appended, it would become easier for someone
+	to argue later that a "Signed-off-by" trailer was just added
+	out of habit or by automation, without the committer's full
+	awareness or intent to certify their agreement with the
+	Developer Certificate of Origin (DCO) or a similar statement.
+	This could undermine the sign-off’s credibility in legal or
+	contractual situations.
++
+There exists `format.signoff`, but that is a historical mistake, and
+it is not an excuse to add more mistakes of the same kind on top.
+
+
 Credentials
 -----------
 
@@ -214,14 +233,30 @@
 the other doesn't have.  This can result in important objects becoming
 unreferenced and possibly pruned by `git gc`, causing data loss.
 +
-Therefore, it's better to push your work to either the other system or a central
-server using the normal push and pull mechanism.  However, this doesn't always
-preserve important data, like stashes, so some people prefer to share a working
-tree across systems.
+Therefore, it's better to push your work to either the other system or a
+central server using the normal push and pull mechanism.  In Git 2.51, Git
+learned to import and export stashes, so it's possible to synchronize the state
+of the working tree by stashing it with `git stash`, then exporting either all
+stashes with `git stash export --to-ref refs/heads/stashes` (assuming you want
+to export to the `stashes` branch) or selecting stashes by adding their numbers
+to the end of that command.  It's also possible to include untracked files by
+using the `--include-untracked` argument when stashing the data in the first
+place, but be careful not to do this if any of these contain sensitive
+information.
 +
-If you do this, the recommended approach is to use `rsync -a --delete-after`
-(ideally with an encrypted connection such as with `ssh`) on the root of
-repository.  You should ensure several things when you do this:
+You can then push the `stashes` branch (or whatever branch you've exported to),
+fetch them to the local system (such as with `git fetch origin
++stashes:stashes`), and import the stashes on the other system with `git stash
+import stashes` (again, changing the name as necessary).  Applying the changes
+to the working tree can be done with `git stash pop` or `git stash apply`.
+This is the approach that is most robust and most likely to avoid unintended
+problems.
++
+Having said that, there are some cases where people nevertheless prefer to
+share a working tree across systems.  If you do this, the recommended approach
+is to use `rsync -a --delete-after` (ideally with an encrypted connection such
+as with `ssh`) on the root of repository.  You should ensure several things
+when you do this:
 +
 * If you have additional worktrees or a separate Git directory, they must be
   synced at the same time as the main working tree and repository.
@@ -232,10 +267,11 @@
   any sort are taking place on it, including background operations like `git
   gc` and operations invoked by your editor).
 +
-Be aware that even with these recommendations, syncing in this way has some risk
-since it bypasses Git's normal integrity checking for repositories, so having
-backups is advised.  You may also wish to do a `git fsck` to verify the
-integrity of your data on the destination system after syncing.
+Be aware that even with these recommendations, syncing working trees in this
+way has some risk since it bypasses Git's normal integrity checking for
+repositories, so having backups is advised.  You may also wish to do a `git
+fsck` to verify the integrity of your data on the destination system after
+syncing.
 
 Common Issues
 -------------
diff --git a/Documentation/gitformat-loose.adoc b/Documentation/gitformat-loose.adoc
index 9479936..b0b5697 100644
--- a/Documentation/gitformat-loose.adoc
+++ b/Documentation/gitformat-loose.adoc
@@ -10,6 +10,7 @@
 --------
 [verse]
 $GIT_DIR/objects/[0-9a-f][0-9a-f]/*
+$GIT_DIR/objects/object-map/map-*.map
 
 DESCRIPTION
 -----------
@@ -48,6 +49,83 @@
 Similarly, a blob containing the contents `abc` would have the uncompressed
 data of `blob 3\0abc`.
 
+== Loose object mapping
+
+When the `compatObjectFormat` option is used, Git needs to store a mapping
+between the repository's main algorithm and the compatibility algorithm for
+loose objects as well as some auxiliary information.
+
+The mapping consists of a set of files under `$GIT_DIR/objects/object-map`
+ending in `.map`.  The portion of the filename before the extension is that of
+the main hash checksum (that is, the one specified in
+`extensions.objectformat`) in hex format.
+
+`git gc` will repack existing entries into one file, removing any unnecessary
+objects, such as obsolete shallow entries or loose objects that have been
+packed.
+
+The file format is as follows.  All values are in network byte order and all
+4-byte and 8-byte values must be 4-byte aligned in the file, so the NUL padding
+may be required in some cases.  Git always uses the smallest number of NUL
+bytes (including zero) that is required for the padding in order to make
+writing files deterministic.
+
+- A header appears at the beginning and consists of the following:
+	* A 4-byte mapping signature: `LMAP`
+	* 4-byte version number: 1
+	* 4-byte length of the header section (including reserved entries but
+		excluding any NUL padding).
+	* 4-byte number of objects declared in this map file.
+	* 4-byte number of object formats declared in this map file.
+	* For each object format:
+		** 4-byte format identifier (e.g., `sha1` for SHA-1)
+		** 4-byte length in bytes of shortened object names (that is, prefixes of
+			 the full object names). This is the shortest possible length needed to
+			 make names in the shortened object name table unambiguous.
+		** 8-byte integer, recording where tables relating to this format
+		are stored in this index file, as an offset from the beginning.
+	* 8-byte offset to the trailer from the beginning of this file.
+	* The remainder of the header section is reserved for future use.
+		Readers must ignore unrecognized data here.
+- Zero or more NUL bytes.  These are used to improve the alignment of the
+	4-byte quantities below.
+- Tables for the first object format:
+	* A sorted table of shortened object names.  These are prefixes of the names
+		of all objects in this file, packed together to reduce the cache footprint
+		of the binary search for a specific object name.
+  * A sorted table of full object names.
+	* A table of 4-byte metadata values.
+- Zero or more NUL bytes.
+- Tables for subsequent object formats:
+	* A sorted table of shortened object names.  These are prefixes of the names
+		of all objects in this file, packed together without offset values to
+		reduce the cache footprint of the binary search for a specific object name.
+	* A table of full object names in the order specified by the first object format.
+	* A table of 4-byte values mapping object name order to the order of the
+		first object format. For an object in the table of sorted shortened object
+		names, the value at the corresponding index in this table is the index in
+		the previous table for that same object.
+	* Zero or more NUL bytes.
+- The trailer consists of the following:
+	* Hash checksum of all of the above using the main hash.
+
+The lower six bits of each metadata table contain a type field indicating the
+reason that this object is stored:
+
+0::
+	Reserved.
+1::
+	This object is stored as a loose object in the repository.
+2::
+	This object is a shallow entry.  The mapping refers to a shallow value
+	returned by a remote server.
+3::
+	This object is a submodule entry.  The mapping refers to the commit stored
+	representing a submodule.
+
+Other data may be stored in this field in the future.  Bits that are not used
+must be zero.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Documentation/githooks.adoc b/Documentation/githooks.adoc
index 0397dec..0565537 100644
--- a/Documentation/githooks.adoc
+++ b/Documentation/githooks.adoc
@@ -103,17 +103,14 @@
 making a commit.  Exiting with a non-zero status from this script
 causes the `git commit` command to abort before creating a commit.
 
-The default 'pre-commit' hook, when enabled, catches introduction
-of lines with trailing whitespaces and aborts the commit when
-such a line is found.
-
 All the `git commit` hooks are invoked with the environment
 variable `GIT_EDITOR=:` if the command will not bring up an editor
 to modify the commit message.
 
-The default 'pre-commit' hook, when enabled--and with the
-`hooks.allownonascii` config option unset or set to false--prevents
-the use of non-ASCII filenames.
+The default 'pre-commit' hook, when enabled, prevents the introduction
+of non-ASCII filenames and lines with trailing whitespace. The non-ASCII
+check can be turned off by setting the `hooks.allownonascii` config
+option to `true`.
 
 pre-merge-commit
 ~~~~~~~~~~~~~~~~
diff --git a/Documentation/gitmodules.adoc b/Documentation/gitmodules.adoc
index d9bec8b..3792da9 100644
--- a/Documentation/gitmodules.adoc
+++ b/Documentation/gitmodules.adoc
@@ -70,7 +70,10 @@
 --
 	all;; The submodule will never be considered modified (but will
 	    nonetheless show up in the output of status and commit when it has
-	    been staged).
+	    been staged). Add `(new commits)` can be overruled using the
+	    `git add --force <submodule.path>`.
+		The setting affects `status`, `update-index`, `diff` and `log`(due
+		to underlaying `diff`).
 
 	dirty;; All changes to the submodule's work tree will be ignored, only
 	    committed differences between the `HEAD` of the submodule and its
diff --git a/Documentation/gitprotocol-http.adoc b/Documentation/gitprotocol-http.adoc
index d024010..e2ef7f0 100644
--- a/Documentation/gitprotocol-http.adoc
+++ b/Documentation/gitprotocol-http.adoc
@@ -443,7 +443,8 @@
 TODO: Define error if no "want" lines are requested.
 
 If any "want" object is not reachable, send an error:
-TODO: Define error if an invalid "want" is requested.
+When a Git server receives an invalid or malformed `want` line, it
+responds with an error message that includes the offending object name.
 
 Create an empty list, `s_common`.
 
diff --git a/Documentation/gitprotocol-pack.adoc b/Documentation/gitprotocol-pack.adoc
index 837b691..633deec 100644
--- a/Documentation/gitprotocol-pack.adoc
+++ b/Documentation/gitprotocol-pack.adoc
@@ -47,7 +47,9 @@
 Transports
 ----------
 There are three transports over which the packfile protocol is
-initiated.  The Git transport is a simple, unauthenticated server that
+initiated.
+
+The Git transport is a simple, unauthenticated server that
 takes the command (almost always 'upload-pack', though Git
 servers can be configured to be globally writable, in which 'receive-
 pack' initiation is also allowed) with which the client wishes to
@@ -65,7 +67,7 @@
 ----------------
 
 The protocol provides a mechanism in which clients can send additional
-information in its first message to the server. These are called "Extra
+information in their first message to the server. These are called "Extra
 Parameters", and are supported by the Git, SSH, and HTTP protocols.
 
 Each Extra Parameter takes the form of `<key>=<value>` or `<key>`.
@@ -115,7 +117,7 @@
 SSH Transport
 -------------
 
-Initiating the upload-pack or receive-pack processes over SSH is
+Initiating the 'upload-pack' or 'receive-pack' processes over SSH is
 executing the binary on the server via SSH remote execution.
 It is basically equivalent to running this:
 
@@ -129,7 +131,7 @@
 
 In an ssh:// format URI, it's absolute in the URI, so the '/' after
 the host name (or port number) is sent as an argument, which is then
-read by the remote git-upload-pack exactly as is, so it's effectively
+read by the remote 'git-upload-pack' exactly as is, so it's effectively
 an absolute path in the remote filesystem.
 
        git clone ssh://user@example.com/project.git
@@ -161,7 +163,7 @@
 
 A few things to remember here:
 
-- The "command name" is spelled with dash (e.g. git-upload-pack), but
+- The "command name" is spelled with dash (e.g. 'git-upload-pack'), but
   this can be overridden by the client;
 
 - The repository path is always quoted with single quotes.
@@ -277,7 +279,7 @@
   filter-request    =  PKT-LINE("filter" SP filter-spec)
 ----
 
-Clients MUST send all the obj-ids it wants from the reference
+Clients MUST send all the obj-ids they want from the reference
 discovery phase as 'want' lines. Clients MUST send at least one
 'want' command in the request body. Clients MUST NOT mention an
 obj-id in a 'want' command which did not appear in the response
@@ -375,10 +377,10 @@
 
 Without either multi_ack or multi_ack_detailed:
 
- * upload-pack sends "ACK obj-id" on the first common object it finds.
+ * 'upload-pack' sends "ACK obj-id" on the first common object it finds.
    After that it says nothing until the client gives it a "done".
 
- * upload-pack sends "NAK" on a flush-pkt if no common object
+ * 'upload-pack' sends "NAK" on a flush-pkt if no common object
    has been found yet.  If one has been found, and thus an ACK
    was already sent, it's silent on the flush-pkt.
 
diff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc
index c7db103..f985cb4 100644
--- a/Documentation/gitprotocol-v2.adoc
+++ b/Documentation/gitprotocol-v2.adoc
@@ -812,10 +812,15 @@
 After these mandatory fields, the server MAY advertise the following
 optional fields in any order:
 
-`partialCloneFilter`:: The filter specification used by the remote.
+`partialCloneFilter`:: The filter specification for the remote. It
+corresponds to the "remote.<name>.partialCloneFilter" config setting.
 Clients can use this to determine if the remote's filtering strategy
-is compatible with their needs (e.g., checking if both use "blob:none").
-It corresponds to the "remote.<name>.partialCloneFilter" config setting.
+is compatible with their needs (e.g., checking if both use
+"blob:none"). Additionally they can use this through the
+`--filter=auto` option in linkgit:git-clone[1]. With that option, the
+filter specification of the clone will be automatically computed by
+combining the filter specifications of the promisor remotes the client
+accepts.
 
 `token`:: An authentication token that clients can use when
 connecting to the remote. It corresponds to the "remote.<name>.token"
@@ -826,9 +831,11 @@
 above. Clients MUST ignore fields they don't recognize to allow for
 future protocol extensions.
 
-For now, the client can only use information transmitted through these
-fields to decide if it accepts the advertised promisor remote. In the
-future that information might be used for other purposes though.
+The client can use information transmitted through these fields to
+decide if it accepts the advertised promisor remote. Also, the client
+can be configured to store the values of these fields or use them
+to automatically configure the repository (see "promisor.storeFields"
+in linkgit:git-config[1] and `--filter=auto` in linkgit:git-clone[1]).
 
 Field values MUST be urlencoded.
 
@@ -856,8 +863,9 @@
 On the server side, the "promisor.advertise" and "promisor.sendFields"
 configuration options can be used to control what it advertises. On
 the client side, the "promisor.acceptFromServer" configuration option
-can be used to control what it accepts. See the documentation of these
-configuration options for more information.
+can be used to control what it accepts, and the "promisor.storeFields"
+option, to control what it stores. See the documentation of these
+configuration options in linkgit:git-config[1] for more information.
 
 Note that in the future it would be nice if the "promisor-remote"
 protocol capability could be used by the server, when responding to
diff --git a/Documentation/glossary-content.adoc b/Documentation/glossary-content.adoc
index e423e47..20ba121 100644
--- a/Documentation/glossary-content.adoc
+++ b/Documentation/glossary-content.adoc
@@ -297,8 +297,8 @@
 	identified by its <<def_object_name,object name>>. The objects usually
 	live in `$GIT_DIR/objects/`.
 
-[[def_object_identifier]]object identifier (oid)::
-	Synonym for <<def_object_name,object name>>.
+[[def_object_identifier]]object identifier, object ID, oid::
+	Synonyms for <<def_object_name,object name>>.
 
 [[def_object_name]]object name::
 	The unique identifier of an <<def_object,object>>.  The
diff --git a/Documentation/lint-gitlink.perl b/Documentation/lint-gitlink.perl
index f183a18..a92e887 100755
--- a/Documentation/lint-gitlink.perl
+++ b/Documentation/lint-gitlink.perl
@@ -41,10 +41,11 @@
 @ARGV = $to_check;
 while (<>) {
 	my $line = $_;
+	next if $line =~ /^\s*(ifn?def|endif)::/;
 	while ($line =~ m/(.{,8})((git[-a-z]+|scalar)\[(\d)*\])/g) {
 	    my $pos = pos $line;
 	    my ($macro, $target, $page, $section) = ($1, $2, $3, $4);
-		if ( $macro ne "linkgit:" && $macro !~ "ifn?def::" && $macro ne "endif::" ) {
+		if ( $macro ne "linkgit:" ) {
 			report($pos, $line, $target, "linkgit: macro expected");
 		}
 	}
diff --git a/Documentation/merge-options.adoc b/Documentation/merge-options.adoc
index 9d43326..952cb85 100644
--- a/Documentation/merge-options.adoc
+++ b/Documentation/merge-options.adoc
@@ -56,7 +56,7 @@
 `--ff-only`::
 	Only update to the new history if there is no divergent local
 	history.  This is the default when no method for reconciling
-	divergent histories is provided (via the --rebase=* flags).
+	divergent histories is provided (via the `--rebase` flags).
 
 `--ff`::
 `--no-ff`::
diff --git a/Documentation/meson.build b/Documentation/meson.build
index c00c9fe..d6365b8 100644
--- a/Documentation/meson.build
+++ b/Documentation/meson.build
@@ -64,6 +64,7 @@
   'git-gui.adoc' : 1,
   'git-hash-object.adoc' : 1,
   'git-help.adoc' : 1,
+  'git-history.adoc' : 1,
   'git-hook.adoc' : 1,
   'git-http-backend.adoc' : 1,
   'git-http-fetch.adoc' : 1,
@@ -193,6 +194,7 @@
   'gitcore-tutorial.adoc' : 7,
   'gitcredentials.adoc' : 7,
   'gitcvs-migration.adoc' : 7,
+  'gitdatamodel.adoc' : 7,
   'gitdiffcore.adoc' : 7,
   'giteveryday.adoc' : 7,
   'gitfaq.adoc' : 7,
@@ -352,13 +354,10 @@
     command: [
       shell,
       '@INPUT@',
-      '..',
+      meson.project_source_root(),
       mode,
       '@OUTPUT@'
     ],
-    env: [
-      'MERGE_TOOLS_DIR=' + meson.project_source_root() / 'mergetools',
-    ],
     input: 'generate-mergetool-list.sh',
     output: 'mergetools-' + mode + '.adoc',
   )
diff --git a/Documentation/pretty-formats.adoc b/Documentation/pretty-formats.adoc
index 2121e8e..5405e57 100644
--- a/Documentation/pretty-formats.adoc
+++ b/Documentation/pretty-formats.adoc
@@ -18,54 +18,72 @@
 linkgit:git-config[1]). Here are the details of the
 built-in formats:
 
-* `oneline`
-
-	  <hash> <title-line>
+`oneline`::
++
+[synopsis]
+--
+<hash> <title-line>
+--
 +
 This is designed to be as compact as possible.
 
-* `short`
+`short`::
++
+[synopsis]
+--
+commit <hash>
+Author: <author>
 
-	  commit <hash>
-	  Author: <author>
+    <title-line>
+--
 
-	      <title-line>
+`medium`::
++
+[synopsis]
+--
+commit <hash>
+Author: <author>
+Date:   <author-date>
 
-* `medium`
+    <title-line>
 
-	  commit <hash>
-	  Author: <author>
-	  Date:   <author-date>
+    <full-commit-message>
+--
 
-	      <title-line>
+`full`::
++
+[synopsis]
+--
+commit <hash>
+Author: <author>
+Commit: <committer>
 
-	      <full-commit-message>
+    <title-line>
 
-* `full`
+    <full-commit-message>
+--
 
-	  commit <hash>
-	  Author: <author>
-	  Commit: <committer>
+`fuller`::
++
+[synopsis]
+--
+commit <hash>
+Author:     <author>
+AuthorDate: <author-date>
+Commit:     <committer>
+CommitDate: <committer-date>
 
-	      <title-line>
+     <title-line>
 
-	      <full-commit-message>
+     <full-commit-message>
+--
 
-* `fuller`
-
-	  commit <hash>
-	  Author:     <author>
-	  AuthorDate: <author-date>
-	  Commit:     <committer>
-	  CommitDate: <committer-date>
-
-	       <title-line>
-
-	       <full-commit-message>
-
-* `reference`
-
-	  <abbrev-hash> (<title-line>, <short-author-date>)
+`reference`::
++
+[synopsis]
+--
+<abbrev-hash> (<title-line>, <short-author-date>)
+--
 +
 This format is used to refer to another commit in a commit message and
 is the same as ++--pretty=\'format:%C(auto)%h (%s, %ad)'++.  By default,
@@ -74,23 +92,24 @@
 placeholders, its output is not affected by other options like
 `--decorate` and `--walk-reflogs`.
 
-* `email`
-
-	  From <hash> <date>
-	  From: <author>
-	  Date: <author-date>
-	  Subject: [PATCH] <title-line>
-
-	  <full-commit-message>
-
-* `mboxrd`
+`email`::
 +
+[synopsis]
+--
+From <hash> <date>
+From: <author>
+Date: <author-date>
+Subject: [PATCH] <title-line>
+
+<full-commit-message>
+--
+
+`mboxrd`::
 Like `email`, but lines in the commit message starting with "From "
 (preceded by zero or more ">") are quoted with ">" so they aren't
 confused as starting a new commit.
 
-* `raw`
-+
+`raw`::
 The `raw` format shows the entire commit exactly as
 stored in the commit object.  Notably, the hashes are
 displayed in full, regardless of whether `--abbrev` or
@@ -101,8 +120,7 @@
 `git log --raw`. To get full object names in a raw diff format,
 use `--no-abbrev`.
 
-* `format:<format-string>`
-+
+`format:<format-string>`::
 The `format:<format-string>` format allows you to specify which information
 you want to show. It works a little bit like printf format,
 with the notable exception that you get a newline with `%n`
@@ -120,13 +138,18 @@
 The placeholders are:
 
 - Placeholders that expand to a single literal character:
++
+--
 ++%n++:: newline
 ++%%++:: a raw ++%++
 ++%x00++:: ++%x++ followed by two hexadecimal digits is replaced with a
 	 byte with the hexadecimal digits' value (we will call this
 	 "literal formatting code" in the rest of this document).
+--
 
 - Placeholders that affect formatting of later placeholders:
++
+--
 ++%Cred++:: switch color to red
 ++%Cgreen++:: switch color to green
 ++%Cblue++:: switch color to blue
@@ -181,8 +204,11 @@
 ++%><|(++_<m>_++)++:: similar to ++%<(++_<n>_++)++, ++%<|(++_<m>_++)++
 			  respectively, but padding both sides
 			  (i.e. the text is centered)
+--
 
 - Placeholders that expand to information extracted from the commit:
++
+--
 +%H+:: commit hash
 +%h+:: abbreviated commit hash
 +%T+:: tree hash
@@ -233,20 +259,19 @@
 literal formatting codes. These must be used for commas (`%x2C`) and closing
 parentheses (`%x29`), due to their role in the option syntax.
 
-** `prefix=<value>`: Shown before the list of ref names.  Defaults to "{nbsp}++(++".
-** `suffix=<value>`: Shown after the list of ref names.  Defaults to "+)+".
-** `separator=<value>`: Shown between ref names.  Defaults to "+,+{nbsp}".
-** `pointer=<value>`: Shown between HEAD and the branch it points to, if any.
-		      Defaults to "{nbsp}++->++{nbsp}".
-** `tag=<value>`: Shown before tag names. Defaults to "`tag:`{nbsp}".
+`prefix=<value>`;; Shown before the list of ref names.  Defaults to "{nbsp}++(++".
+`suffix=<value>`;; Shown after the list of ref names.  Defaults to "+)+".
+`separator=<value>`;; Shown between ref names.  Defaults to "+,+{nbsp}".
+`pointer=<value>`;; Shown between HEAD and the branch it points to, if any.
+	      Defaults to "{nbsp}->{nbsp}".
+`tag=<value>`;; Shown before tag names. Defaults to "`tag:`{nbsp}".
 
 +
---
 For example, to produce decorations with no wrapping
 or tag annotations, and spaces as separators:
-
-++%(decorate:prefix=,suffix=,tag=,separator= )++
---
+---------------------
+    %(decorate:prefix=,suffix=,tag=,separator= )
+---------------------
 
 ++%(describe++`[:<option>,...]`++)++::
 human-readable name, like linkgit:git-describe[1]; empty string for
@@ -254,15 +279,15 @@
 zero or more comma-separated options.  Descriptions can be inconsistent when
 tags are added or removed at the same time.
 +
-** `tags[=<bool-value>]`: Instead of only considering annotated tags,
+`tags[=<bool-value>]`;; Instead of only considering annotated tags,
    consider lightweight tags as well.
-** `abbrev=<number>`: Instead of using the default number of hexadecimal digits
+`abbrev=<number>`;; Instead of using the default number of hexadecimal digits
    (which will vary according to the number of objects in the repository with a
-   default of 7) of the abbreviated object name, use <number> digits, or as many
+   default of 7) of the abbreviated object name, use _<number>_ digits, or as many
    digits as needed to form a unique object name.
-** `match=<pattern>`: Only consider tags matching the given
+`match=<pattern>`;; Only consider tags matching the given
    `glob(7)` _<pattern>_, excluding the `refs/tags/` prefix.
-** `exclude=<pattern>`: Do not consider tags matching the given
+`exclude=<pattern>`;; Do not consider tags matching the given
    `glob(7)` _<pattern>_, excluding the `refs/tags/` prefix.
 
 +%S+:: ref name given on the command line by which the commit was reached
@@ -311,7 +336,7 @@
 a colon and zero or more comma-separated options. If any option is provided
 multiple times, the last occurrence wins.
 +
-** `key=<key>`: only show trailers with specified <key>. Matching is done
+`key=<key>`;; only show trailers with specified <key>. Matching is done
    case-insensitively and trailing colon is optional. If option is
    given multiple times trailer lines matching any of the keys are
    shown. This option automatically enables the `only` option so that
@@ -319,21 +344,21 @@
    desired it can be disabled with `only=false`.  E.g.,
    +%(trailers:key=Reviewed-by)+ shows trailer lines with key
    `Reviewed-by`.
-** `only[=<bool>]`: select whether non-trailer lines from the trailer
+`only[=<bool>]`;; select whether non-trailer lines from the trailer
    block should be included.
-** `separator=<sep>`: specify the separator inserted between trailer
+ `separator=<sep>`;; specify the separator inserted between trailer
    lines. Defaults to a line feed character. The string <sep> may contain
    the literal formatting codes described above. To use comma as
    separator one must use `%x2C` as it would otherwise be parsed as
    next option. E.g., +%(trailers:key=Ticket,separator=%x2C )+
-   shows all trailer lines whose key is "Ticket" separated by a comma
+   shows all trailer lines whose key is `Ticket` separated by a comma
    and a space.
-** `unfold[=<bool>]`: make it behave as if interpret-trailer's `--unfold`
+`unfold[=<bool>]`;; make it behave as if interpret-trailer's `--unfold`
    option was given. E.g.,
    +%(trailers:only,unfold=true)+ unfolds and shows all trailer lines.
-** `keyonly[=<bool>]`: only show the key part of the trailer.
-** `valueonly[=<bool>]`: only show the value part of the trailer.
-** `key_value_separator=<sep>`: specify the separator inserted between
+`keyonly[=<bool>]`;; only show the key part of the trailer.
+`valueonly[=<bool>]`;; only show the value part of the trailer.
+`key_value_separator=<sep>`;; specify the separator inserted between
    the key and value of each trailer. Defaults to ": ". Otherwise it
    shares the same semantics as `separator=<sep>` above.
 
@@ -360,9 +385,9 @@
 If you add a `' '` (space) after +%+ of a placeholder, a space
 is inserted immediately before the expansion if and only if the
 placeholder expands to a non-empty string.
+--
 
-* `tformat:`
-+
+`tformat:`::
 The `tformat:` format works exactly like `format:`, except that it
 provides "terminator" semantics instead of "separator" semantics. In
 other words, each commit has the message terminator character (usually a
diff --git a/Documentation/pull-fetch-param.adoc b/Documentation/pull-fetch-param.adoc
index bb2cf6a..d903dc8 100644
--- a/Documentation/pull-fetch-param.adoc
+++ b/Documentation/pull-fetch-param.adoc
@@ -1,20 +1,20 @@
-<repository>::
+_<repository>_::
 	The "remote" repository that is the source of a fetch
 	or pull operation.  This parameter can be either a URL
 	(see the section <<URLS,GIT URLS>> below) or the name
 	of a remote (see the section <<REMOTES,REMOTES>> below).
 
 ifndef::git-pull[]
-<group>::
+_<group>_::
 	A name referring to a list of repositories as the value
-	of remotes.<group> in the configuration file.
+	of `remotes.<group>` in the configuration file.
 	(See linkgit:git-config[1]).
 endif::git-pull[]
 
 [[fetch-refspec]]
-<refspec>::
+_<refspec>_::
 	Specifies which refs to fetch and which local refs to update.
-	When no <refspec>s appear on the command line, the refs to fetch
+	When no __<refspec>__s appear on the command line, the refs to fetch
 	are read from `remote.<repository>.fetch` variables instead
 ifndef::git-pull[]
 	(see <<CRTB,CONFIGURED REMOTE-TRACKING BRANCHES>> below).
@@ -24,18 +24,18 @@
 	in linkgit:git-fetch[1]).
 endif::git-pull[]
 +
-The format of a <refspec> parameter is an optional plus
-`+`, followed by the source <src>, followed
-by a colon `:`, followed by the destination <dst>.
-The colon can be omitted when <dst> is empty.  <src> is
+The format of a _<refspec>_ parameter is an optional plus
+`+`, followed by the source _<src>_, followed
+by a colon `:`, followed by the destination _<dst>_.
+The colon can be omitted when _<dst>_ is empty.  _<src>_ is
 typically a ref, or a glob pattern with a single `*` that is used
 to match a set of refs, but it can also be a fully spelled hex object
 name.
 +
-A <refspec> may contain a `*` in its <src> to indicate a simple pattern
+A _<refspec>_ may contain a `*` in its _<src>_ to indicate a simple pattern
 match. Such a refspec functions like a glob that matches any ref with the
-pattern. A pattern <refspec> must have one and only one `*` in both the <src> and
-<dst>. It will map refs to the destination by replacing the `*` with the
+pattern. A pattern _<refspec>_ must have one and only one `*` in both the _<src>_ and
+_<dst>_. It will map refs to the destination by replacing the `*` with the
 contents matched from the source.
 +
 If a refspec is prefixed by `^`, it will be interpreted as a negative
@@ -45,14 +45,14 @@
 not match any negative refspec. Negative refspecs can be useful to restrict
 the scope of a pattern refspec so that it will not include specific refs.
 Negative refspecs can themselves be pattern refspecs. However, they may only
-contain a <src> and do not specify a <dst>. Fully spelled out hex object
+contain a _<src>_ and do not specify a _<dst>_. Fully spelled out hex object
 names are also not supported.
 +
 `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`;
 it requests fetching everything up to the given tag.
 +
-The remote ref that matches <src>
-is fetched, and if <dst> is not an empty string, an attempt
+The remote ref that matches _<src>_
+is fetched, and if _<dst>_ is not an empty string, an attempt
 is made to update the local ref that matches it.
 +
 Whether that update is allowed without `--force` depends on the ref
@@ -60,7 +60,7 @@
 whether the update is considered to be a fast-forward. Generally, the
 same rules apply for fetching as when pushing, see the `<refspec>...`
 section of linkgit:git-push[1] for what those are. Exceptions to those
-rules particular to 'git fetch' are noted below.
+rules particular to `git fetch` are noted below.
 +
 Until Git version 2.20, and unlike when pushing with
 linkgit:git-push[1], any updates to `refs/tags/*` would be accepted
@@ -101,19 +101,19 @@
 ifdef::git-pull[]
 +
 [NOTE]
-There is a difference between listing multiple <refspec>
-directly on 'git pull' command line and having multiple
+There is a difference between listing multiple _<refspec>_
+directly on `git pull` command line and having multiple
 `remote.<repository>.fetch` entries in your configuration
-for a <repository> and running a
-'git pull' command without any explicit <refspec> parameters.
-<refspec>s listed explicitly on the command line are always
+for a _<repository>_ and running a
+`git pull` command without any explicit _<refspec>_ parameters.
+__<refspec>__s listed explicitly on the command line are always
 merged into the current branch after fetching.  In other words,
-if you list more than one remote ref, 'git pull' will create
+if you list more than one remote ref, `git pull` will create
 an Octopus merge.  On the other hand, if you do not list any
-explicit <refspec> parameter on the command line, 'git pull'
-will fetch all the <refspec>s it finds in the
+explicit _<refspec>_ parameter on the command line, `git pull`
+will fetch all the __<refspec>__s it finds in the
 `remote.<repository>.fetch` configuration and merge
-only the first <refspec> found into the current branch.
+only the first _<refspec>_ found into the current branch.
 This is because making an
 Octopus from remote refs is rarely done, while keeping track
 of multiple remote heads in one-go by fetching more than one
diff --git a/Documentation/ref-storage-format.adoc b/Documentation/ref-storage-format.adoc
index 14fff8a..6a8db47 100644
--- a/Documentation/ref-storage-format.adoc
+++ b/Documentation/ref-storage-format.adoc
@@ -1,3 +1,3 @@
-* `files` for loose files with packed-refs. This is the default.
-* `reftable` for the reftable format. This format is experimental and its
+`files`;; for loose files with packed-refs. This is the default.
+`reftable`;; for the reftable format. This format is experimental and its
   internals are subject to change.
diff --git a/Documentation/rerere-options.adoc b/Documentation/rerere-options.adoc
index b0b9201..4395fe0 100644
--- a/Documentation/rerere-options.adoc
+++ b/Documentation/rerere-options.adoc
@@ -4,6 +4,6 @@
 	the current conflict to update the files in the working
 	tree, allow it to also update the index with the result of
 	resolution.  `--no-rerere-autoupdate` is a good way to
-	double-check what `rerere` did and catch potential
+	double-check what linkgit:git-rerere[1] did and catch potential
 	mismerges, before committing the result to the index with a
-	separate `git add`.
+	separate linkgit:git-add[1].
diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc
index d9665d8..2d195a1 100644
--- a/Documentation/rev-list-options.adoc
+++ b/Documentation/rev-list-options.adoc
@@ -148,6 +148,10 @@
 	from the point where it diverged from the remote branch, given
 	that arbitrary merges can be valid topic branch changes.
 
+`--maximal-only`::
+	Restrict the output commits to be those that are not reachable
+	from any other commits in the revision range.
+
 `--not`::
 	Reverses the meaning of the '{caret}' prefix (or lack thereof)
 	for all following revision specifiers, up to the next `--not`.
@@ -983,7 +987,9 @@
 is the same as 'blob:limit=1024'.
 +
 The form `--filter=object:type=(tag|commit|tree|blob)` omits all objects
-which are not of the requested type.
+which are not of the requested type. Note that explicitly provided objects
+ignore filters and are always printed unless `--filter-provided-objects` is
+also specified.
 +
 The form `--filter=sparse:oid=<blob-ish>` uses a sparse-checkout
 specification contained in the blob (or blob-expression) _<blob-ish>_
@@ -1094,7 +1100,7 @@
 	Overrides a previous `--no-walk`.
 endif::git-shortlog[]
 
-ifndef::git-shortlog[]
+ifndef::git-shortlog,git-replay[]
 Commit Formatting
 ~~~~~~~~~~~~~~~~~
 
@@ -1263,4 +1269,4 @@
 	counts and print the count for equivalent commits separated
 	by a tab.
 endif::git-rev-list[]
-endif::git-shortlog[]
+endif::git-shortlog,git-replay[]
diff --git a/Documentation/scalar.adoc b/Documentation/scalar.adoc
index f81b283..5252fb1 100644
--- a/Documentation/scalar.adoc
+++ b/Documentation/scalar.adoc
@@ -197,6 +197,170 @@
 	This subcommand lets you delete an existing Scalar enlistment from your
 	local file system, unregistering the repository.
 
+RECOMMENDED CONFIG VALUES
+-------------------------
+
+As part of both `scalar clone` and `scalar register`, certain Git config
+values are set to optimize for large repositories or cross-platform support.
+These options are updated in new Git versions according to the best known
+advice for large repositories, and users can get the latest recommendations
+by running `scalar reconfigure [--all]`.
+
+This section lists justifications for the config values that are set in the
+latest version.
+
+am.keepCR=true::
+	This setting is important for cross-platform development across Windows
+	and non-Windows platforms and keeping carriage return (`\r`) characters
+	in certain workflows.
+
+commitGraph.changedPaths=true::
+	This setting helps the background maintenance steps that compute the
+	serialized commit-graph to also store changed-path Bloom filters. This
+	accelerates file history commands and allows users to automatically
+	benefit without running a foreground command.
+
+commitGraph.generationVersion=1::
+	While the preferred version is 2 for performance reasons, existing users
+	that had version 1 by default will need special care in upgrading to
+	version 2. This is likely to change in the future as the upgrade story
+	solidifies.
+
+core.autoCRLF=false::
+	This removes the transformation of worktree files to add CRLF line
+	endings when only LF line endings exist. This is removed for performance
+	reasons. Repositories that use tools that care about CRLF line endings
+	should commit the necessary files with those line endings instead.
+
+core.logAllRefUpdates=true::
+	This enables the reflog on all branches. While this is a performance
+	cost for large repositories, it is frequently an important data source
+	for users to get out of bad situations or to seek support from experts.
+
+core.safeCRLF=false::
+	Similar to `core.autoCRLF=false`, this disables checks around whether
+	the CRLF conversion is reversible. This is a performance improvement,
+	but can be dangerous if `core.autoCRLF` is reenabled by the user.
+
+credential.https://dev.azure.com.useHttpPath=true::
+	This setting enables the `credential.useHttpPath` feature only for web
+	URLs for Azure DevOps. This is important for users interacting with that
+	service using multiple organizations and thus multiple credential
+	tokens.
+
+feature.experimental=false::
+	This disables the "experimental" optimizations grouped under this
+	feature config. The expectation is that all valuable optimizations are
+	also set explicitly by Scalar config, and any differences are
+	intentional. Notable differences include several bitmap-related config
+	options which are disabled for client-focused Scalar repos.
+
+feature.manyFiles=false::
+	This disables the "many files" optimizations grouped under this feature
+	config. The expectation is that all valuable optimizations are also set
+	explicitly by Scalar config, and any differences are intentional.
+
+fetch.showForcedUpdates=false::
+	This disables the check at the end of `git fetch` that notifies the user
+	if the ref update was a forced update (one where the previous position
+	is not reachable from the latest position). This check can be very
+	expensive in large repositories, so is disabled and replaced with an
+	advice message. Set `advice.fetchShowForcedUpdates=false` to disable
+	this advice message.
+
+fetch.unpackLimit=1::
+	This setting prevents Git from unpacking packfiles into loose objects
+	as they are downloaded from the server. The default limit of 100 was
+	intended as a way to prevent performance issues from too many packfiles,
+	but Scalar uses background maintenance to group packfiles and cover them
+	with a multi-pack-index, removing this issue.
+
+fetch.writeCommitGraph=false::
+	This config setting was created to help users automatically update their
+	commit-graph files as they perform fetches. However, this takes time
+	from foreground fetches and pulls and Scalar uses background maintenance
+	for this function instead.
+
+gc.auto=0::
+	This disables automatic garbage collection, since Scalar uses background
+	maintenance to keep the repository data in good shape.
+
+gui.GCWarning=false::
+	Since Scalar disables garbage collection by setting `gc.auto=0`, the
+	`git-gui` tool may start to warn about this setting. Disable this
+	warning as Scalar's background maintenance configuration makes the
+	warning irrelevant.
+
+index.skipHash=true::
+	Disable computing the hash of the index contents as it is being written.
+	This assists with performance, especially for large index files.
+
+index.threads=true::
+	This tells Git to automatically detect how many threads it should use
+	when reading the index due to the default value of `core.preloadIndex`,
+	which enables parallel index reads. This explicit setting also enables
+	`index.recordOffsetTable=true` to speed up parallel index reads.
+
+index.version=4::
+	This index version adds compression to the path names, reducing the size
+	of the index in a significant way for large repos. This is an important
+	performance boost.
+
+log.excludeDecoration=refs/prefetch/*::
+	Since Scalar enables background maintenance with the `incremental`
+	strategy, this setting avoids polluting `git log` output with refs
+	stored by the background prefetch operations.
+
+merge.renames=true::
+	When computing merges in large repos, it is particularly important to
+	detect renames to maximize the potential for a result that will validate
+	correctly. Users performing merges locally are more likely to be doing
+	so because a server-side merge (via pull request or similar) resulted in
+	conflicts. While this is the default setting, it is set specifically to
+	override a potential change to `diff.renames` which a user may set for
+	performance reasons.
+
+merge.stat=false::
+	This disables a diff output after computing a merge. This improves
+	performance of `git merge` for large repos while reducing noisy output.
+
+pack.useBitmaps=false::
+	This disables the use of `.bitmap` files attached to packfiles. Bitmap
+	files are optimized for server-side use, not client-side use. Scalar
+	disables this to avoid some performance issues that can occur if a user
+	accidentally creates `.bitmap` files.
+
+pack.usePathWalk=true::
+	This enables the `--path-walk` option to `git pack-objects` by default.
+	This can accelerate the computation and compression of packfiles created
+	by `git push` and other repack operations.
+
+receive.autoGC=false::
+	Similar to `gc.auto`, this setting is disabled in preference of
+	background maintenance.
+
+status.aheadBehind=false::
+	This disables the ahead/behind calculation that would normally happen
+	during a `git status` command. This information is frequently ignored by
+	users but can be expensive to calculate in large repos that receive
+	thousands of commits per day. The calculation is replaced with an advice
+	message that can be disabled by disabling the `advice.statusAheadBehind`
+	config.
+
+The following settings are different based on which platform is in use:
+
+core.untrackedCache=(true|false)::
+	The untracked cache feature is important for performance benefits on
+	large repositories, but has demonstrated some bugs on Windows
+	filesystems. Thus, this is set for other platforms but disabled on
+	Windows.
+
+http.sslBackend=schannel::
+	On Windows, the `openssl` backend has some issues with certain types of
+	remote providers and certificate types. Override the default setting to
+	avoid these common problems.
+
+
 SEE ALSO
 --------
 linkgit:git-clone[1], linkgit:git-maintenance[1].
diff --git a/Documentation/signoff-option.adoc b/Documentation/signoff-option.adoc
index cddfb22..6fc2769 100644
--- a/Documentation/signoff-option.adoc
+++ b/Documentation/signoff-option.adoc
@@ -16,3 +16,7 @@
 +
 The `--no-signoff` option can be used to countermand an earlier `--signoff`
 option on the command line.
++
+Git does not (and will not) have a configuration variable to enable
+the `--signoff` command line option by default; see the
+`commit.signoff` entry in linkgit:gitfaq[7] for more details.
diff --git a/Documentation/technical/meson.build b/Documentation/technical/meson.build
index faff396..ec07088 100644
--- a/Documentation/technical/meson.build
+++ b/Documentation/technical/meson.build
@@ -32,6 +32,7 @@
   'sparse-checkout.adoc',
   'sparse-index.adoc',
   'trivial-merge.adoc',
+  'unambiguous-types.adoc',
   'unit-tests.adoc',
 ]
 
diff --git a/Documentation/technical/unambiguous-types.adoc b/Documentation/technical/unambiguous-types.adoc
new file mode 100644
index 0000000..9a49908
--- /dev/null
+++ b/Documentation/technical/unambiguous-types.adoc
@@ -0,0 +1,224 @@
+= Unambiguous types
+
+Most of these mappings are obvious, but there are some nuances and gotchas with
+Rust FFI (Foreign Function Interface).
+
+This document defines clear, one-to-one mappings between primitive types in C,
+Rust (and possible other languages in the future). Its purpose is to eliminate
+ambiguity in type widths, signedness, and binary representation across
+platforms and languages.
+
+For Git, the only header required to use these unambiguous types in C is
+`git-compat-util.h`.
+
+== Boolean types
+[cols="1,1", options="header"]
+|===
+| C Type | Rust Type
+| bool^1^       | bool
+|===
+
+== Integer types
+
+In C, `<stdint.h>` (or an equivalent) must be included.
+
+[cols="1,1", options="header"]
+|===
+| C Type | Rust Type
+| uint8_t    | u8
+| uint16_t   | u16
+| uint32_t   | u32
+| uint64_t   | u64
+
+| int8_t     | i8
+| int16_t    | i16
+| int32_t    | i32
+| int64_t    | i64
+|===
+
+== Floating-point types
+
+Rust requires IEEE-754 semantics.
+In C, that is typically true, but not guaranteed by the standard.
+
+[cols="1,1", options="header"]
+|===
+| C Type | Rust Type
+| float^2^      | f32
+| double^2^     | f64
+|===
+
+== Size types
+
+These types represent pointer-sized integers and are typically defined in
+`<stddef.h>` or an equivalent header.
+
+Size types should be used any time pointer arithmetic is performed e.g.
+indexing an array, describing the number of elements in memory, etc...
+
+[cols="1,1", options="header"]
+|===
+| C Type | Rust Type
+| size_t^3^     | usize
+| ptrdiff_t^3^  | isize
+|===
+
+== Character types
+
+This is where C and Rust don't have a clean one-to-one mapping.
+
+A C `char` and a Rust `u8` share the same bit width, so any C struct containing
+a `char` will have the same size as the corresponding Rust struct using `u8`.
+In that sense, such structs are safe to pass over the FFI boundary, because
+their fields will be laid out identically. However, beyond bit width, C `char`
+has additional semantics and platform-dependent behavior that can cause
+problems, as discussed below.
+
+The C language leaves the signedness of `char` implementation defined. Because
+our developer build enables -Wsign-compare, comparison of a value of `char`
+type with either signed or unsigned integers may trigger warnings from the
+compiler.
+
+Note: Rust's `char` type is an unsigned 32-bit integer that is used to describe
+Unicode code points.
+
+=== Notes
+^1^ This is only true if stdbool.h (or equivalent) is used. +
+^2^ C does not enforce IEEE-754 compatibility, but Rust expects it. If the
+platform/arch for C does not follow IEEE-754 then this equivalence does not
+hold. Also, it's assumed that `float` is 32 bits and `double` is 64, but
+there may be a strange platform/arch where even this isn't true. +
+^3^ C also defines uintptr_t, ssize_t and intptr_t, but these types are
+discouraged for FFI purposes. For functions like `read()` and `write()` ssize_t
+should be cast to a different, and unambiguous, type before being passed over
+the FFI boundary. +
+
+== Problems with std::ffi::c_* types in Rust
+TL;DR: In practice, Rust's `c_*` types aren't guaranteed to match C types for
+all possible C compilers, platforms, or architectures, because Rust only
+ensures correctness of C types on officially supported targets. These
+definitions have changed over time to match more targets which means that the
+c_* definitions will differ based on which Rust version Git chooses to use.
+
+Current list of safe, Rust side, FFI types in Git: +
+
+* `c_void`
+* `CStr`
+* `CString`
+
+Even then, they should be used sparingly, and only where the semantics match
+exactly.
+
+The std::os::raw::c_* directly inherits the problems of core::ffi, which
+changes over time and seems to make a best guess at the correct definition for
+a given platform/target. This probably isn't a problem for all other platforms
+that Rust supports currently, but can anyone say that Rust got it right for all
+C compilers of all platforms/targets?
+
+To give an example: c_long is defined in
+footnote:[https://doc.rust-lang.org/1.63.0/src/core/ffi/mod.rs.html#175-189[c_long in 1.63.0]]
+footnote:[https://doc.rust-lang.org/1.89.0/src/core/ffi/primitives.rs.html#135-151[c_long in 1.89.0]]
+
+=== Rust version 1.63.0
+
+```
+mod c_long_definition {
+    cfg_if! {
+        if #[cfg(all(target_pointer_width = "64", not(windows)))] {
+            pub type c_long = i64;
+            pub type NonZero_c_long = crate::num::NonZeroI64;
+            pub type c_ulong = u64;
+            pub type NonZero_c_ulong = crate::num::NonZeroU64;
+        } else {
+            // The minimal size of `long` in the C standard is 32 bits
+            pub type c_long = i32;
+            pub type NonZero_c_long = crate::num::NonZeroI32;
+            pub type c_ulong = u32;
+            pub type NonZero_c_ulong = crate::num::NonZeroU32;
+        }
+    }
+}
+```
+
+=== Rust version 1.89.0
+
+```
+mod c_long_definition {
+    crate::cfg_select! {
+        any(
+            all(target_pointer_width = "64", not(windows)),
+            // wasm32 Linux ABI uses 64-bit long
+            all(target_arch = "wasm32", target_os = "linux")
+        ) => {
+            pub(super) type c_long = i64;
+            pub(super) type c_ulong = u64;
+        }
+        _ => {
+            // The minimal size of `long` in the C standard is 32 bits
+            pub(super) type c_long = i32;
+            pub(super) type c_ulong = u32;
+        }
+    }
+}
+```
+
+Even for the cases where C types are correctly mapped to Rust types via
+std::ffi::c_* there are still problems. Let's take c_char for example. On some
+platforms it's u8 on others it's i8.
+
+=== Subtraction underflow in debug mode
+
+The following code will panic in debug on platforms that define c_char as u8,
+but won't if it's an i8.
+
+```
+let mut x: std::ffi::c_char = 0;
+x -= 1;
+```
+
+=== Inconsistent shift behavior
+
+`x` will be 0xC0 for platforms that use i8, but will be 0x40 where it's u8.
+
+```
+let mut x: std::ffi::c_char = 0x80;
+x >>= 1;
+```
+
+=== Equality fails to compile on some platforms
+
+The following will not compile on platforms that define c_char as i8, but will
+if it's u8. You can cast x e.g. `assert_eq!(x as u8, b'a');`, but then you get
+a warning on platforms that use u8 and a clean compilation where i8 is used.
+
+```
+let mut x: std::ffi::c_char = 0x61;
+assert_eq!(x, b'a');
+```
+
+== Enum types
+Rust enum types should not be used as FFI types. Rust enum types are more like
+C union types than C enum's. For something like:
+
+```
+#[repr(C, u8)]
+enum Fruit {
+    Apple,
+    Banana,
+    Cherry,
+}
+```
+
+It's easy enough to make sure the Rust enum matches what C would expect, but a
+more complex type like.
+
+```
+enum HashResult {
+    SHA1([u8; 20]),
+    SHA256([u8; 32]),
+}
+```
+
+The Rust compiler has to add a discriminant to the enum to distinguish between
+the variants. The width, location, and values for that discriminant is up to
+the Rust compiler and is not ABI stable.
diff --git a/Documentation/urls-remotes.adoc b/Documentation/urls-remotes.adoc
index 57b1646..6878bbe 100644
--- a/Documentation/urls-remotes.adoc
+++ b/Documentation/urls-remotes.adoc
@@ -4,7 +4,7 @@
 ------------------
 
 The name of one of the following can be used instead
-of a URL as `<repository>` argument:
+of a URL as _<repository>_ argument:
 
 * a remote in the Git configuration file: `$GIT_DIR/config`,
 * a file in the `$GIT_DIR/remotes` directory, or
@@ -32,8 +32,8 @@
 		fetch = <refspec>
 ------------
 
-The `<pushurl>` is used for pushes only. It is optional and defaults
-to `<URL>`. Pushing to a remote affects all defined pushurls or all
+The _<pushurl>_ is used for pushes only. It is optional and defaults
+to _<URL>_. Pushing to a remote affects all defined pushurls or all
 defined urls if no pushurls are defined. Fetch, however, will only
 fetch from the first defined url if multiple urls are defined.
 
@@ -54,8 +54,8 @@
 
 ------------
 
-`Push:` lines are used by 'git push' and
-`Pull:` lines are used by 'git pull' and 'git fetch'.
+`Push:` lines are used by `git push` and
+`Pull:` lines are used by `git pull` and `git fetch`.
 Multiple `Push:` and `Pull:` lines may
 be specified for additional branch mappings.
 
@@ -72,12 +72,12 @@
 	<URL>#<head>
 ------------
 
-`<URL>` is required; `#<head>` is optional.
+_<URL>_ is required; `#<head>` is optional.
 
 Depending on the operation, git will use one of the following
 refspecs, if you don't provide one on the command line.
-`<branch>` is the name of this file in `$GIT_DIR/branches` and
-`<head>` defaults to `master`.
+_<branch>_ is the name of this file in `$GIT_DIR/branches` and
+_<head>_ defaults to `master`.
 
 git fetch uses:
 
@@ -111,7 +111,7 @@
   'origin/main' have diverged, and have 2 and 3 different commits each
   respectively".
 
-The upstream is stored in `.git/config`, in the "remote" and "merge"
+The upstream is stored in `.git/config`, in the "`remote`" and "`merge`"
 fields. For example, if `main`'s upstream is `origin/main`:
 
 ------------
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 8d5bbf7..44240e0 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-DEF_VER=v2.52.0
+DEF_VER=v2.53.GIT
 
 LF='
 '
diff --git a/Makefile b/Makefile
index 7e0f77e..c619b54 100644
--- a/Makefile
+++ b/Makefile
@@ -95,11 +95,21 @@
 # and LDFLAGS appropriately.
 #
 # Define NO_DARWIN_PORTS if you are building on Darwin/Mac OS X,
-# have DarwinPorts installed in /opt/local, but don't want GIT to
+# have DarwinPorts (which is an old name for MacPorts) installed
+# in /opt/local, but don't want GIT to
 # link against any libraries installed there.  If defined you may
 # specify your own (or DarwinPort's) include directories and
 # library directories by defining CFLAGS and LDFLAGS appropriately.
 #
+# Define NO_HOMEBREW if you don't want to use gettext, libiconv and
+# msgfmt installed by Homebrew.
+#
+# Define HOMEBREW_PREFIX if you have Homebrew installed in a non-default
+# location on macOS or on Linux and want to use it.
+#
+# Define USE_HOMEBREW_LIBICONV to link against libiconv installed by
+# Homebrew, if present.
+#
 # Define NO_APPLE_COMMON_CRYPTO if you are building on Darwin/Mac OS X
 # and do not want to use Apple's CommonCrypto library.  This allows you
 # to provide your own OpenSSL library, for example from MacPorts.
@@ -332,6 +342,9 @@
 # If it isn't set, fallback to $LC_ALL, $LANG or use the first utf-8
 # locale returned by "locale -a".
 #
+# Define TEST_CONTRIB_TOO to make "make test" run tests in contrib/
+# directories.
+#
 # Define HAVE_CLOCK_GETTIME if your platform has clock_gettime.
 #
 # Define HAVE_CLOCK_MONOTONIC if your platform has CLOCK_MONOTONIC.
@@ -981,7 +994,7 @@
 SANITIZE_ADDRESS =
 
 # For the 'coccicheck' target
-SPATCH_INCLUDE_FLAGS = --all-includes
+SPATCH_INCLUDE_FLAGS = --all-includes $(addprefix -I ,compat ewah refs sha256 trace2 win32 xdiff)
 SPATCH_FLAGS =
 SPATCH_TEST_FLAGS =
 
@@ -1201,6 +1214,9 @@
 LIB_OBJS += object-name.o
 LIB_OBJS += object.o
 LIB_OBJS += odb.o
+LIB_OBJS += odb/source.o
+LIB_OBJS += odb/source-files.o
+LIB_OBJS += odb/streaming.o
 LIB_OBJS += oid-array.o
 LIB_OBJS += oidmap.o
 LIB_OBJS += oidset.o
@@ -1274,6 +1290,7 @@
 LIB_OBJS += repack-midx.o
 LIB_OBJS += repack-promisor.o
 LIB_OBJS += replace-object.o
+LIB_OBJS += replay.o
 LIB_OBJS += repo-settings.o
 LIB_OBJS += repository.o
 LIB_OBJS += rerere.o
@@ -1294,7 +1311,6 @@
 LIB_OBJS += stable-qsort.o
 LIB_OBJS += statinfo.o
 LIB_OBJS += strbuf.o
-LIB_OBJS += streaming.o
 LIB_OBJS += string-list.o
 LIB_OBJS += strmap.o
 LIB_OBJS += strvec.o
@@ -1407,6 +1423,7 @@
 BUILTIN_OBJS += builtin/grep.o
 BUILTIN_OBJS += builtin/hash-object.o
 BUILTIN_OBJS += builtin/help.o
+BUILTIN_OBJS += builtin/history.o
 BUILTIN_OBJS += builtin/hook.o
 BUILTIN_OBJS += builtin/index-pack.o
 BUILTIN_OBJS += builtin/init-db.o
@@ -1506,6 +1523,7 @@
 CLAR_TEST_SUITES += u-example-decorate
 CLAR_TEST_SUITES += u-hash
 CLAR_TEST_SUITES += u-hashmap
+CLAR_TEST_SUITES += u-list-objects-filter-options
 CLAR_TEST_SUITES += u-mem-pool
 CLAR_TEST_SUITES += u-oid-array
 CLAR_TEST_SUITES += u-oidmap
@@ -1525,6 +1543,7 @@
 CLAR_TEST_SUITES += u-strvec
 CLAR_TEST_SUITES += u-trailer
 CLAR_TEST_SUITES += u-urlmatch-normalization
+CLAR_TEST_SUITES += u-utf8-width
 CLAR_TEST_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
 CLAR_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(CLAR_TEST_SUITES))
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
@@ -1534,7 +1553,10 @@
 
 UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
 
+RUST_SOURCES += src/csum_file.rs
+RUST_SOURCES += src/hash.rs
 RUST_SOURCES += src/lib.rs
+RUST_SOURCES += src/loose.rs
 RUST_SOURCES += src/varint.rs
 
 GIT-VERSION-FILE: FORCE
@@ -1583,10 +1605,12 @@
 endif
 ifneq ($(filter leak,$(SANITIZERS)),)
 BASIC_CFLAGS += -O0
+NO_MMAP = CatchMapLeaks
 SANITIZE_LEAK = YesCompiledWithIt
 endif
 ifneq ($(filter address,$(SANITIZERS)),)
 NO_REGEX = NeededForASAN
+NO_MMAP = NeededForASAN
 SANITIZE_ADDRESS = YesCompiledWithIt
 endif
 endif
@@ -1675,11 +1699,21 @@
 			BASIC_CFLAGS += -I/sw/include
 			BASIC_LDFLAGS += -L/sw/lib
                 endif
+                ifeq ($(shell test -d /opt/sw/lib && echo y),y)
+			BASIC_CFLAGS += -I/opt/sw/include
+			BASIC_LDFLAGS += -L/opt/sw/lib
+			ifeq ($(shell test -e /opt/sw/lib/libiconv.dylib && echo y),y)
+				HAS_GOOD_LIBICONV = Yes
+			endif
+                endif
         endif
         ifndef NO_DARWIN_PORTS
                 ifeq ($(shell test -d /opt/local/lib && echo y),y)
 			BASIC_CFLAGS += -I/opt/local/include
 			BASIC_LDFLAGS += -L/opt/local/lib
+			ifeq ($(shell test -e /opt/local/lib/libiconv.dylib && echo y),y)
+				HAS_GOOD_LIBICONV = Yes
+			endif
                 endif
         endif
         ifndef NO_APPLE_COMMON_CRYPTO
@@ -1690,6 +1724,24 @@
 	PTHREAD_LIBS =
 endif
 
+ifndef NO_HOMEBREW
+ifdef HOMEBREW_PREFIX
+ifeq ($(shell test -d $(HOMEBREW_PREFIX)/opt/gettext && echo y),y)
+	BASIC_CFLAGS += -I$(HOMEBREW_PREFIX)/opt/gettext/include
+	BASIC_LDFLAGS += -L$(HOMEBREW_PREFIX)/opt/gettext/lib
+endif
+ifeq ($(shell test -x $(HOMEBREW_PREFIX)/opt/gettext/msgfmt && echo y),y)
+	MSGFMT = $(HOMEBREW_PREFIX)/opt/gettext/msgfmt
+endif
+ifdef USE_HOMEBREW_LIBICONV
+ifeq ($(shell test -d $(HOMEBREW_PREFIX)/opt/libiconv && echo y),y)
+	ICONVDIR ?= $(HOMEBREW_PREFIX)/opt/libiconv
+	HAS_GOOD_LIBICONV = Yes
+endif
+endif
+endif
+endif
+
 ifdef NO_LIBGEN_H
 	COMPAT_CFLAGS += -DNO_LIBGEN_H
 	COMPAT_OBJS += compat/basename.o
@@ -1830,6 +1882,11 @@
                 endif
 		EXTLIBS += $(ICONV_LINK) -liconv
         endif
+        ifdef NEEDS_GOOD_LIBICONV
+        ifndef HAS_GOOD_LIBICONV
+                BASIC_CFLAGS += -DICONV_RESTART_RESET
+        endif
+        endif
 endif
 ifdef ICONV_OMITS_BOM
 	BASIC_CFLAGS += -DICONV_OMITS_BOM
@@ -1917,7 +1974,6 @@
 endif
 ifdef NO_MKDTEMP
 	COMPAT_CFLAGS += -DNO_MKDTEMP
-	COMPAT_OBJS += compat/mkdtemp.o
 endif
 ifdef MKDIR_WO_TRAILING_SLASH
 	COMPAT_CFLAGS += -DMKDIR_WO_TRAILING_SLASH
@@ -2565,7 +2621,7 @@
 
 shell_compatibility_test: please_set_SHELL_PATH_to_a_more_modern_shell
 
-strip: $(PROGRAMS) git$X
+strip: $(PROGRAMS) git$X scalar$X
 	$(STRIP) $(STRIP_OPTS) $^
 
 ### Target-specific flags and dependencies
@@ -2638,9 +2694,10 @@
 	cp $< $@
 
 config-list.h: generate-configlist.sh
+	@mkdir -p .depend
+	$(QUIET_GEN)$(SHELL_PATH) ./generate-configlist.sh . $@ .depend/config-list.h.d
 
-config-list.h: Documentation/*config.adoc Documentation/config/*.adoc
-	$(QUIET_GEN)$(SHELL_PATH) ./generate-configlist.sh . $@
+-include .depend/config-list.h.d
 
 command-list.h: generate-cmdlist.sh command-list.txt
 
@@ -2830,6 +2887,10 @@
 dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
 dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
 
+ifeq ($(uname_S),Darwin)
+	dep_dirs += $(addsuffix .depend,$(sort $(dir contrib/credential/osxkeychain/git-credential-osxkeychain.o)))
+endif
+
 ifeq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
 $(dep_dirs):
 	@mkdir -p $@
@@ -2963,7 +3024,7 @@
 $(LIB_FILE): $(LIB_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
-$(RUST_LIB): Cargo.toml $(RUST_SOURCES)
+$(RUST_LIB): Cargo.toml $(RUST_SOURCES) $(LIB_FILE)
 	$(QUIET_CARGO)cargo build $(CARGO_ARGS)
 
 .PHONY: rust
@@ -3325,6 +3386,9 @@
 
 test: all
 	$(MAKE) -C t/ all
+ifdef TEST_CONTRIB_TOO
+	$(MAKE) -C contrib/ test
+endif
 
 perf: all
 	$(MAKE) -C t/perf/ all
@@ -3521,7 +3585,7 @@
 COCCICHECK_PATCH_MUST_BE_EMPTY_FILES = $(COCCICHECK_PATCHES_INTREE)
 endif
 coccicheck: $(COCCICHECK_PATCH_MUST_BE_EMPTY_FILES)
-	! grep -q ^ $(COCCICHECK_PATCH_MUST_BE_EMPTY_FILES) /dev/null
+	! grep ^ $(COCCICHECK_PATCH_MUST_BE_EMPTY_FILES) /dev/null
 
 # See contrib/coccinelle/README
 coccicheck-pending: coccicheck-test
@@ -4014,3 +4078,20 @@
 
 contrib/libgit-sys/libgitpub.a: $(LIBGIT_HIDDEN_EXPORT)
 	$(AR) $(ARFLAGS) $@ $^
+
+contrib/credential/osxkeychain/git-credential-osxkeychain: contrib/credential/osxkeychain/git-credential-osxkeychain.o $(LIB_FILE) GIT-LDFLAGS
+	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
+		$(filter %.o,$^) $(LIB_FILE) $(EXTLIBS) -framework Security -framework CoreFoundation
+
+contrib/credential/osxkeychain/git-credential-osxkeychain.o: contrib/credential/osxkeychain/git-credential-osxkeychain.c GIT-CFLAGS
+	$(QUIET_LINK)$(CC) -o $@ -c $(dep_args) $(compdb_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
+
+install-git-credential-osxkeychain: contrib/credential/osxkeychain/git-credential-osxkeychain
+	$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+	$(INSTALL) $(INSTALL_STRIP) $< '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+
+.PHONY: clean-git-credential-osxkeychain
+clean-git-credential-osxkeychain:
+	$(RM) \
+		contrib/credential/osxkeychain/git-credential-osxkeychain \
+		contrib/credential/osxkeychain/git-credential-osxkeychain.o
diff --git a/RelNotes b/RelNotes
index 6d16c00..84ef738 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.52.0.adoc
\ No newline at end of file
+Documentation/RelNotes/2.54.0.adoc
\ No newline at end of file
diff --git a/add-interactive.c b/add-interactive.c
index 68fc095..1580639 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -64,6 +64,7 @@ void init_add_i_state(struct add_i_state *s, struct repository *r,
 	s->r = r;
 	s->context = -1;
 	s->interhunkcontext = -1;
+	s->auto_advance = add_p_opt->auto_advance;
 
 	s->use_color_interactive = check_color_config(r, "color.interactive");
 
@@ -840,7 +841,7 @@ static int run_revert(struct add_i_state *s, const struct pathspec *ps,
 	if (is_initial)
 		oidcpy(&oid, s->r->hash_algo->empty_tree);
 	else {
-		tree = parse_tree_indirect(&oid);
+		tree = repo_parse_tree_indirect(s->r, &oid);
 		if (!tree) {
 			res = error(_("Could not parse HEAD^{tree}"));
 			goto finish_revert;
@@ -1017,6 +1018,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
 		struct add_p_opt add_p_opt = {
 			.context = s->context,
 			.interhunkcontext = s->interhunkcontext,
+			.auto_advance = s->auto_advance
 		};
 		struct strvec args = STRVEC_INIT;
 		struct pathspec ps_selected = { 0 };
diff --git a/add-interactive.h b/add-interactive.h
index da49502..7843397 100644
--- a/add-interactive.h
+++ b/add-interactive.h
@@ -6,9 +6,10 @@
 struct add_p_opt {
 	int context;
 	int interhunkcontext;
+	int auto_advance;
 };
 
-#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }
+#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1, .auto_advance = 1 }
 
 struct add_i_state {
 	struct repository *r;
@@ -29,6 +30,7 @@ struct add_i_state {
 	int use_single_key;
 	char *interactive_diff_filter, *interactive_diff_algorithm;
 	int context, interhunkcontext;
+	int auto_advance;
 };
 
 void init_add_i_state(struct add_i_state *s, struct repository *r,
diff --git a/add-patch.c b/add-patch.c
index 173a532..8c03f71 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -42,10 +42,10 @@ static struct patch_mode patch_mode_add = {
 	.apply_args = { "--cached", NULL },
 	.apply_check_args = { "--cached", NULL },
 	.prompt_mode = {
-		N_("Stage mode change [y,n,q,a,d%s,?]? "),
-		N_("Stage deletion [y,n,q,a,d%s,?]? "),
-		N_("Stage addition [y,n,q,a,d%s,?]? "),
-		N_("Stage this hunk [y,n,q,a,d%s,?]? ")
+		N_("Stage mode change%s [y,n,q,a,d%s,?]? "),
+		N_("Stage deletion%s [y,n,q,a,d%s,?]? "),
+		N_("Stage addition%s [y,n,q,a,d%s,?]? "),
+		N_("Stage this hunk%s [y,n,q,a,d%s,?]? ")
 	},
 	.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
 			     "will immediately be marked for staging."),
@@ -64,10 +64,10 @@ static struct patch_mode patch_mode_stash = {
 	.apply_args = { "--cached", NULL },
 	.apply_check_args = { "--cached", NULL },
 	.prompt_mode = {
-		N_("Stash mode change [y,n,q,a,d%s,?]? "),
-		N_("Stash deletion [y,n,q,a,d%s,?]? "),
-		N_("Stash addition [y,n,q,a,d%s,?]? "),
-		N_("Stash this hunk [y,n,q,a,d%s,?]? "),
+		N_("Stash mode change%s [y,n,q,a,d%s,?]? "),
+		N_("Stash deletion%s [y,n,q,a,d%s,?]? "),
+		N_("Stash addition%s [y,n,q,a,d%s,?]? "),
+		N_("Stash this hunk%s [y,n,q,a,d%s,?]? "),
 	},
 	.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
 			     "will immediately be marked for stashing."),
@@ -88,10 +88,10 @@ static struct patch_mode patch_mode_reset_head = {
 	.is_reverse = 1,
 	.index_only = 1,
 	.prompt_mode = {
-		N_("Unstage mode change [y,n,q,a,d%s,?]? "),
-		N_("Unstage deletion [y,n,q,a,d%s,?]? "),
-		N_("Unstage addition [y,n,q,a,d%s,?]? "),
-		N_("Unstage this hunk [y,n,q,a,d%s,?]? "),
+		N_("Unstage mode change%s [y,n,q,a,d%s,?]? "),
+		N_("Unstage deletion%s [y,n,q,a,d%s,?]? "),
+		N_("Unstage addition%s [y,n,q,a,d%s,?]? "),
+		N_("Unstage this hunk%s [y,n,q,a,d%s,?]? "),
 	},
 	.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
 			     "will immediately be marked for unstaging."),
@@ -111,10 +111,10 @@ static struct patch_mode patch_mode_reset_nothead = {
 	.apply_check_args = { "--cached", NULL },
 	.index_only = 1,
 	.prompt_mode = {
-		N_("Apply mode change to index [y,n,q,a,d%s,?]? "),
-		N_("Apply deletion to index [y,n,q,a,d%s,?]? "),
-		N_("Apply addition to index [y,n,q,a,d%s,?]? "),
-		N_("Apply this hunk to index [y,n,q,a,d%s,?]? "),
+		N_("Apply mode change to index%s [y,n,q,a,d%s,?]? "),
+		N_("Apply deletion to index%s [y,n,q,a,d%s,?]? "),
+		N_("Apply addition to index%s [y,n,q,a,d%s,?]? "),
+		N_("Apply this hunk to index%s [y,n,q,a,d%s,?]? "),
 	},
 	.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
 			     "will immediately be marked for applying."),
@@ -134,10 +134,10 @@ static struct patch_mode patch_mode_checkout_index = {
 	.apply_check_args = { "-R", NULL },
 	.is_reverse = 1,
 	.prompt_mode = {
-		N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
-		N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
-		N_("Discard addition from worktree [y,n,q,a,d%s,?]? "),
-		N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
+		N_("Discard mode change from worktree%s [y,n,q,a,d%s,?]? "),
+		N_("Discard deletion from worktree%s [y,n,q,a,d%s,?]? "),
+		N_("Discard addition from worktree%s [y,n,q,a,d%s,?]? "),
+		N_("Discard this hunk from worktree%s [y,n,q,a,d%s,?]? "),
 	},
 	.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
 			     "will immediately be marked for discarding."),
@@ -157,10 +157,10 @@ static struct patch_mode patch_mode_checkout_head = {
 	.apply_check_args = { "-R", NULL },
 	.is_reverse = 1,
 	.prompt_mode = {
-		N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
-		N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
-		N_("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
-		N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
+		N_("Discard mode change from index and worktree%s [y,n,q,a,d%s,?]? "),
+		N_("Discard deletion from index and worktree%s [y,n,q,a,d%s,?]? "),
+		N_("Discard addition from index and worktree%s [y,n,q,a,d%s,?]? "),
+		N_("Discard this hunk from index and worktree%s [y,n,q,a,d%s,?]? "),
 	},
 	.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
 			     "will immediately be marked for discarding."),
@@ -179,10 +179,10 @@ static struct patch_mode patch_mode_checkout_nothead = {
 	.apply_for_checkout = 1,
 	.apply_check_args = { NULL },
 	.prompt_mode = {
-		N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
-		N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
-		N_("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
-		N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
+		N_("Apply mode change to index and worktree%s [y,n,q,a,d%s,?]? "),
+		N_("Apply deletion to index and worktree%s [y,n,q,a,d%s,?]? "),
+		N_("Apply addition to index and worktree%s [y,n,q,a,d%s,?]? "),
+		N_("Apply this hunk to index and worktree%s [y,n,q,a,d%s,?]? "),
 	},
 	.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
 			     "will immediately be marked for applying."),
@@ -202,10 +202,10 @@ static struct patch_mode patch_mode_worktree_head = {
 	.apply_check_args = { "-R", NULL },
 	.is_reverse = 1,
 	.prompt_mode = {
-		N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
-		N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
-		N_("Discard addition from worktree [y,n,q,a,d%s,?]? "),
-		N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
+		N_("Discard mode change from worktree%s [y,n,q,a,d%s,?]? "),
+		N_("Discard deletion from worktree%s [y,n,q,a,d%s,?]? "),
+		N_("Discard addition from worktree%s [y,n,q,a,d%s,?]? "),
+		N_("Discard this hunk from worktree%s [y,n,q,a,d%s,?]? "),
 	},
 	.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
 			     "will immediately be marked for discarding."),
@@ -224,10 +224,10 @@ static struct patch_mode patch_mode_worktree_nothead = {
 	.apply_args = { NULL },
 	.apply_check_args = { NULL },
 	.prompt_mode = {
-		N_("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
-		N_("Apply deletion to worktree [y,n,q,a,d%s,?]? "),
-		N_("Apply addition to worktree [y,n,q,a,d%s,?]? "),
-		N_("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
+		N_("Apply mode change to worktree%s [y,n,q,a,d%s,?]? "),
+		N_("Apply deletion to worktree%s [y,n,q,a,d%s,?]? "),
+		N_("Apply addition to worktree%s [y,n,q,a,d%s,?]? "),
+		N_("Apply this hunk to worktree%s [y,n,q,a,d%s,?]? "),
 	},
 	.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
 			     "will immediately be marked for applying."),
@@ -342,7 +342,7 @@ static int parse_hunk_header(struct add_p_state *s, struct hunk *hunk)
 {
 	struct hunk_header *header = &hunk->header;
 	const char *line = s->plain.buf + hunk->start, *p = line;
-	char *eol = memchr(p, '\n', s->plain.len - hunk->start);
+	const char *eol = memchr(p, '\n', s->plain.len - hunk->start);
 
 	if (!eol)
 		eol = s->plain.buf + s->plain.len;
@@ -1418,7 +1418,44 @@ N_("j - go to the next undecided hunk, roll over at the bottom\n"
    "e - manually edit the current hunk\n"
    "p - print the current hunk\n"
    "P - print the current hunk using the pager\n"
-   "? - print help\n");
+   "> - go to the next file, roll over at the bottom\n"
+   "< - go to the previous file, roll over at the top\n"
+   "? - print help\n"
+   "HUNKS SUMMARY - Hunks: %d, USE: %d, SKIP: %d\n");
+
+static void apply_patch(struct add_p_state *s, struct file_diff *file_diff)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	size_t j;
+
+	/* Any hunk to be used? */
+	for (j = 0; j < file_diff->hunk_nr; j++)
+		if (file_diff->hunk[j].use == USE_HUNK)
+			break;
+
+	if (j < file_diff->hunk_nr ||
+		(!file_diff->hunk_nr && file_diff->head.use == USE_HUNK)) {
+		/* At least one hunk selected: apply */
+		strbuf_reset(&s->buf);
+		reassemble_patch(s, file_diff, 0, &s->buf);
+
+		discard_index(s->s.r->index);
+		if (s->mode->apply_for_checkout)
+			apply_for_checkout(s, &s->buf,
+					s->mode->is_reverse);
+		else {
+			setup_child_process(s, &cp, "apply", NULL);
+			strvec_pushv(&cp.args, s->mode->apply_args);
+			if (pipe_command(&cp, s->buf.buf, s->buf.len,
+					NULL, 0, NULL, 0))
+				error(_("'git apply' failed"));
+		}
+		if (repo_read_index(s->s.r) >= 0)
+			repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0,
+							1, NULL, NULL, NULL);
+	}
+
+}
 
 static size_t dec_mod(size_t a, size_t m)
 {
@@ -1441,25 +1478,27 @@ static bool get_first_undecided(const struct file_diff *file_diff, size_t *idx)
 	return false;
 }
 
-static int patch_update_file(struct add_p_state *s,
-			     struct file_diff *file_diff)
+static size_t patch_update_file(struct add_p_state *s, size_t idx)
 {
 	size_t hunk_index = 0;
 	ssize_t i, undecided_previous, undecided_next, rendered_hunk_index = -1;
 	struct hunk *hunk;
 	char ch;
-	struct child_process cp = CHILD_PROCESS_INIT;
-	int colored = !!s->colored.len, quit = 0, use_pager = 0;
+	int colored = !!s->colored.len, use_pager = 0;
 	enum prompt_mode_type prompt_mode_type;
+	int all_decided = 0;
+	struct file_diff *file_diff = s->file_diff + idx;
+	size_t patch_update_resp = idx;
 
 	/* Empty added files have no hunks */
 	if (!file_diff->hunk_nr && !file_diff->added)
-		return 0;
+		return patch_update_resp + 1;
 
 	strbuf_reset(&s->buf);
 	render_diff_header(s, file_diff, colored, &s->buf);
 	fputs(s->buf.buf, stdout);
 	for (;;) {
+		const char *hunk_use_decision = "";
 		enum {
 			ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0,
 			ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1,
@@ -1467,7 +1506,9 @@ static int patch_update_file(struct add_p_state *s,
 			ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3,
 			ALLOW_SEARCH_AND_GOTO = 1 << 4,
 			ALLOW_SPLIT = 1 << 5,
-			ALLOW_EDIT = 1 << 6
+			ALLOW_EDIT = 1 << 6,
+			ALLOW_GOTO_PREVIOUS_FILE = 1 << 7,
+			ALLOW_GOTO_NEXT_FILE = 1 << 8
 		} permitted = 0;
 
 		if (hunk_index >= file_diff->hunk_nr)
@@ -1498,9 +1539,14 @@ static int patch_update_file(struct add_p_state *s,
 
 		/* Everything decided? */
 		if (undecided_previous < 0 && undecided_next < 0 &&
-		    hunk->use != UNDECIDED_HUNK)
-			break;
-
+		    hunk->use != UNDECIDED_HUNK) {
+				if (!s->s.auto_advance)
+					all_decided = 1;
+				else {
+					patch_update_resp++;
+					break;
+				}
+		}
 		strbuf_reset(&s->buf);
 		if (file_diff->hunk_nr) {
 			if (rendered_hunk_index != hunk_index) {
@@ -1548,6 +1594,14 @@ static int patch_update_file(struct add_p_state *s,
 				permitted |= ALLOW_EDIT;
 				strbuf_addstr(&s->buf, ",e");
 			}
+			if (!s->s.auto_advance && s->file_diff_nr > 1) {
+				permitted |= ALLOW_GOTO_NEXT_FILE;
+				strbuf_addstr(&s->buf, ",>");
+			}
+			if (!s->s.auto_advance && s->file_diff_nr > 1) {
+				permitted |= ALLOW_GOTO_PREVIOUS_FILE;
+				strbuf_addstr(&s->buf, ",<");
+			}
 			strbuf_addstr(&s->buf, ",p,P");
 		}
 		if (file_diff->deleted)
@@ -1564,13 +1618,19 @@ static int patch_update_file(struct add_p_state *s,
 			      (uintmax_t)(file_diff->hunk_nr
 						? file_diff->hunk_nr
 						: 1));
+		if (hunk->use != UNDECIDED_HUNK) {
+			if (hunk->use == USE_HUNK)
+				hunk_use_decision = _(" (was: y)");
+			else
+				hunk_use_decision = _(" (was: n)");
+		}
 		printf(_(s->mode->prompt_mode[prompt_mode_type]),
-		       s->buf.buf);
+			hunk_use_decision, s->buf.buf);
 		if (*s->s.reset_color_interactive)
 			fputs(s->s.reset_color_interactive, stdout);
 		fflush(stdout);
 		if (read_single_character(s) == EOF) {
-			quit = 1;
+			patch_update_resp = s->file_diff_nr;
 			break;
 		}
 
@@ -1616,8 +1676,30 @@ static int patch_update_file(struct add_p_state *s,
 				hunk->use = SKIP_HUNK;
 			}
 		} else if (ch == 'q') {
-			quit = 1;
+			patch_update_resp = s->file_diff_nr;
 			break;
+		} else if (!s->s.auto_advance && s->answer.buf[0] == '>') {
+			if (permitted & ALLOW_GOTO_NEXT_FILE) {
+				if (patch_update_resp == s->file_diff_nr - 1)
+					patch_update_resp = 0;
+				else
+					patch_update_resp++;
+				break;
+			} else {
+				err(s, _("No next file"));
+				continue;
+			}
+		} else if (!s->s.auto_advance && s->answer.buf[0] == '<') {
+			if (permitted & ALLOW_GOTO_PREVIOUS_FILE) {
+				if (patch_update_resp == 0)
+					patch_update_resp = s->file_diff_nr - 1;
+				else
+					patch_update_resp--;
+				break;
+			} else {
+				err(s, _("No previous file"));
+				continue;
+			}
 		} else if (s->answer.buf[0] == 'K') {
 			if (permitted & ALLOW_GOTO_PREVIOUS_HUNK)
 				hunk_index = dec_mod(hunk_index,
@@ -1763,6 +1845,18 @@ static int patch_update_file(struct add_p_state *s,
 				 * commands shown in the prompt that are not
 				 * always available.
 				 */
+				if (all_decided && !strncmp(p, "HUNKS SUMMARY", 13)) {
+					int total = file_diff->hunk_nr, used = 0, skipped = 0;
+
+					for (i = 0; i < file_diff->hunk_nr; i++) {
+						if (file_diff->hunk[i].use == USE_HUNK)
+							used += 1;
+						if (file_diff->hunk[i].use == SKIP_HUNK)
+							skipped += 1;
+					}
+					color_fprintf_ln(stdout, s->s.help_color, _(p),
+							 total, used, skipped);
+				}
 				if (*p != '?' && !strchr(s->buf.buf, *p))
 					continue;
 
@@ -1775,35 +1869,11 @@ static int patch_update_file(struct add_p_state *s,
 		}
 	}
 
-	/* Any hunk to be used? */
-	for (i = 0; i < file_diff->hunk_nr; i++)
-		if (file_diff->hunk[i].use == USE_HUNK)
-			break;
-
-	if (i < file_diff->hunk_nr ||
-	    (!file_diff->hunk_nr && file_diff->head.use == USE_HUNK)) {
-		/* At least one hunk selected: apply */
-		strbuf_reset(&s->buf);
-		reassemble_patch(s, file_diff, 0, &s->buf);
-
-		discard_index(s->s.r->index);
-		if (s->mode->apply_for_checkout)
-			apply_for_checkout(s, &s->buf,
-					   s->mode->is_reverse);
-		else {
-			setup_child_process(s, &cp, "apply", NULL);
-			strvec_pushv(&cp.args, s->mode->apply_args);
-			if (pipe_command(&cp, s->buf.buf, s->buf.len,
-					 NULL, 0, NULL, 0))
-				error(_("'git apply' failed"));
-		}
-		if (repo_read_index(s->s.r) >= 0)
-			repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0,
-						     1, NULL, NULL, NULL);
-	}
+	if (s->s.auto_advance)
+		apply_patch(s, file_diff);
 
 	putchar('\n');
-	return quit;
+	return patch_update_resp;
 }
 
 int run_add_p(struct repository *r, enum add_p_mode mode,
@@ -1852,11 +1922,18 @@ int run_add_p(struct repository *r, enum add_p_mode mode,
 		return -1;
 	}
 
-	for (i = 0; i < s.file_diff_nr; i++)
-		if (s.file_diff[i].binary && !s.file_diff[i].hunk_nr)
+	for (i = 0; i < s.file_diff_nr;) {
+		if (s.file_diff[i].binary && !s.file_diff[i].hunk_nr) {
 			binary_count++;
-		else if (patch_update_file(&s, s.file_diff + i))
+			i++;
+			continue;
+		}
+		 if ((i = patch_update_file(&s, i)) == s.file_diff_nr)
 			break;
+    }
+	if (!s.s.auto_advance)
+		for (i = 0; i < s.file_diff_nr; i++)
+			apply_patch(&s, s.file_diff + i);
 
 	if (s.file_diff_nr == 0)
 		err(&s, _("No changes."));
diff --git a/alias.c b/alias.c
index 1a1a141..ec9833d 100644
--- a/alias.c
+++ b/alias.c
@@ -13,23 +13,57 @@ struct config_alias_data {
 	struct string_list *list;
 };
 
-static int config_alias_cb(const char *key, const char *value,
+static int config_alias_cb(const char *var, const char *value,
 			   const struct config_context *ctx UNUSED, void *d)
 {
 	struct config_alias_data *data = d;
-	const char *p;
+	const char *subsection, *key;
+	size_t subsection_len;
 
-	if (!skip_prefix(key, "alias.", &p))
+	if (parse_config_key(var, "alias", &subsection, &subsection_len,
+			     &key) < 0)
+		return 0;
+
+	/*
+	 * Two config syntaxes:
+	 * - alias.name = value   (without subsection, case-insensitive)
+	 * - [alias "name"]
+	 *       command = value  (with subsection, case-sensitive)
+	 */
+	/* Treat [alias ""] (empty subsection) the same as plain [alias]. */
+	if (subsection && !subsection_len)
+		subsection = NULL;
+
+	if (subsection && strcmp(key, "command"))
 		return 0;
 
 	if (data->alias) {
-		if (!strcasecmp(p, data->alias)) {
+		int match;
+
+		if (subsection)
+			match = (strlen(data->alias) == subsection_len &&
+				 !strncmp(data->alias, subsection,
+					  subsection_len));
+		else
+			match = !strcasecmp(data->alias, key);
+
+		if (match) {
 			FREE_AND_NULL(data->v);
 			return git_config_string(&data->v,
-						 key, value);
+						 var, value);
 		}
 	} else if (data->list) {
-		string_list_append(data->list, p);
+		struct string_list_item *item;
+
+		if (!value)
+			return config_error_nonbool(var);
+
+		if (subsection)
+			item = string_list_append_nodup(data->list,
+				xmemdupz(subsection, subsection_len));
+		else
+			item = string_list_append(data->list, key);
+		item->util = xstrdup(value);
 	}
 
 	return 0;
diff --git a/apply.c b/apply.c
index a2ceb3f..b6dd106 100644
--- a/apply.c
+++ b/apply.c
@@ -1640,6 +1640,14 @@ static void record_ws_error(struct apply_state *state,
 	    state->squelch_whitespace_errors < state->whitespace_error)
 		return;
 
+	/*
+	 * line[len] for an incomplete line points at the "\n" at the end
+	 * of patch input line, so "%.*s" would drop the last letter on line;
+	 * compensate for it.
+	 */
+	if (result & WS_INCOMPLETE_LINE)
+		len++;
+
 	err = whitespace_error_string(result);
 	if (state->apply_verbosity > verbosity_silent)
 		fprintf(stderr, "%s:%d: %s.\n%.*s\n",
@@ -1671,6 +1679,35 @@ static void check_old_for_crlf(struct patch *patch, const char *line, int len)
 
 
 /*
+ * Just saw a single line in a fragment.  If it is a part of this hunk
+ * that is a context " ", an added "+", or a removed "-" line, it may
+ * be followed by "\\ No newline..." to signal that the last "\n" on
+ * this line needs to be dropped.  Depending on locale settings when
+ * the patch was produced we don't know what this line would exactly
+ * say. The only thing we do know is that it begins with "\ ".
+ * Checking for 12 is just for sanity check; "\ No newline..." would
+ * be at least that long in any l10n.
+ *
+ * Return 0 if the line we saw is not followed by "\ No newline...",
+ * or length of that line.  The caller will use it to skip over the
+ * "\ No newline..." line.
+ */
+static int adjust_incomplete(const char *line, int len,
+			     unsigned long size)
+{
+	int nextlen;
+
+	if (*line != '\n' && *line != ' ' && *line != '+' && *line != '-')
+		return 0;
+	if (size - len < 12 || memcmp(line + len, "\\ ", 2))
+		return 0;
+	nextlen = linelen(line + len, size - len);
+	if (nextlen < 12)
+		return 0;
+	return nextlen;
+}
+
+/*
  * Parse a unified diff. Note that this really needs to parse each
  * fragment separately, since the only way to know the difference
  * between a "---" that is part of a patch, and a "---" that starts
@@ -1684,9 +1721,30 @@ static int parse_fragment(struct apply_state *state,
 {
 	int added, deleted;
 	int len = linelen(line, size), offset;
+	int skip_len = 0;
 	unsigned long oldlines, newlines;
 	unsigned long leading, trailing;
 
+	/* do not complain a symbolic link being an incomplete line */
+	if (patch->ws_rule & WS_INCOMPLETE_LINE) {
+		/*
+		 * We want to figure out if the postimage is a
+		 * symbolic link when applying the patch normally, or
+		 * if the preimage is a symbolic link when applying
+		 * the patch in reverse.  A normal patch only has
+		 * old_mode without new_mode.  If it changes the
+		 * filemode, new_mode has value, which is different
+		 * from old_mode.
+		 */
+		unsigned mode = (state->apply_in_reverse
+				 ? patch->old_mode
+				 : patch->new_mode
+				 ? patch->new_mode
+				 : patch->old_mode);
+		if (mode && S_ISLNK(mode))
+			patch->ws_rule &= ~WS_INCOMPLETE_LINE;
+	}
+
 	offset = parse_fragment_header(line, len, fragment);
 	if (offset < 0)
 		return -1;
@@ -1710,6 +1768,22 @@ static int parse_fragment(struct apply_state *state,
 		len = linelen(line, size);
 		if (!len || line[len-1] != '\n')
 			return -1;
+
+		/*
+		 * For an incomplete line, skip_len counts the bytes
+		 * on "\\ No newline..." marker line that comes next
+		 * to the current line.
+		 *
+		 * Reduce "len" to drop the newline at the end of
+		 * line[], but add one to "skip_len", which will be
+		 * added back to "len" for the next iteration, to
+		 * compensate.
+		 */
+		skip_len = adjust_incomplete(line, len, size);
+		if (skip_len) {
+			len--;
+			skip_len++;
+		}
 		switch (*line) {
 		default:
 			return -1;
@@ -1745,19 +1819,12 @@ static int parse_fragment(struct apply_state *state,
 			newlines--;
 			trailing = 0;
 			break;
+		}
 
-		/*
-		 * We allow "\ No newline at end of file". Depending
-		 * on locale settings when the patch was produced we
-		 * don't know what this line looks like. The only
-		 * thing we do know is that it begins with "\ ".
-		 * Checking for 12 is just for sanity check -- any
-		 * l10n of "\ No newline..." is at least that long.
-		 */
-		case '\\':
-			if (len < 12 || memcmp(line, "\\ ", 2))
-				return -1;
-			break;
+		/* eat the "\\ No newline..." as well, if exists */
+		if (skip_len) {
+			len += skip_len;
+			state->linenr++;
 		}
 	}
 	if (oldlines || newlines)
@@ -1768,14 +1835,6 @@ static int parse_fragment(struct apply_state *state,
 	fragment->leading = leading;
 	fragment->trailing = trailing;
 
-	/*
-	 * If a fragment ends with an incomplete line, we failed to include
-	 * it in the above loop because we hit oldlines == newlines == 0
-	 * before seeing it.
-	 */
-	if (12 < size && !memcmp(line, "\\ ", 2))
-		offset += linelen(line, size);
-
 	patch->lines_added += added;
 	patch->lines_deleted += deleted;
 
@@ -3529,9 +3588,9 @@ static int three_way_merge(struct apply_state *state,
 	else if (oideq(base, theirs) || oideq(ours, theirs))
 		return resolve_to(image, ours);
 
-	read_mmblob(&base_file, base);
-	read_mmblob(&our_file, ours);
-	read_mmblob(&their_file, theirs);
+	read_mmblob(&base_file, the_repository->objects, base);
+	read_mmblob(&our_file, the_repository->objects, ours);
+	read_mmblob(&their_file, the_repository->objects, theirs);
 	merge_opts.variant = state->merge_variant;
 	status = ll_merge(&result, path,
 			  &base_file, "base",
@@ -3779,7 +3838,7 @@ static int check_preimage(struct apply_state *state,
 		if (*ce && !(*ce)->ce_mode)
 			BUG("ce_mode == 0 for path '%s'", old_name);
 
-		if (trust_executable_bit)
+		if (trust_executable_bit || !S_ISREG(st->st_mode))
 			st_mode = ce_mode_from_stat(*ce, st->st_mode);
 		else if (*ce)
 			st_mode = (*ce)->ce_mode;
@@ -4105,7 +4164,7 @@ static int preimage_oid_in_gitlink_patch(struct patch *p, struct object_id *oid)
 	 */
 	struct fragment *hunk = p->fragments;
 	static const char heading[] = "-Subproject commit ";
-	char *preimage;
+	const char *preimage;
 
 	if (/* does the patch have only one hunk? */
 	    hunk && !hunk->next &&
@@ -4963,6 +5022,10 @@ static int apply_option_parse_directory(const struct option *opt,
 
 	strbuf_reset(&state->root);
 	strbuf_addstr(&state->root, arg);
+
+	if (strbuf_normalize_path(&state->root) < 0)
+		return error(_("unable to normalize directory: '%s'"), arg);
+
 	strbuf_complete(&state->root, '/');
 	return 0;
 }
diff --git a/archive-tar.c b/archive-tar.c
index 73b63dd..0fc70d1 100644
--- a/archive-tar.c
+++ b/archive-tar.c
@@ -12,8 +12,8 @@
 #include "tar.h"
 #include "archive.h"
 #include "odb.h"
+#include "odb/streaming.h"
 #include "strbuf.h"
-#include "streaming.h"
 #include "run-command.h"
 #include "write-or-die.h"
 
@@ -129,22 +129,20 @@ static void write_trailer(void)
  */
 static int stream_blocked(struct repository *r, const struct object_id *oid)
 {
-	struct git_istream *st;
-	enum object_type type;
-	unsigned long sz;
+	struct odb_read_stream *st;
 	char buf[BLOCKSIZE];
 	ssize_t readlen;
 
-	st = open_istream(r, oid, &type, &sz, NULL);
+	st = odb_read_stream_open(r->objects, oid, NULL);
 	if (!st)
 		return error(_("cannot stream blob %s"), oid_to_hex(oid));
 	for (;;) {
-		readlen = read_istream(st, buf, sizeof(buf));
+		readlen = odb_read_stream_read(st, buf, sizeof(buf));
 		if (readlen <= 0)
 			break;
 		do_write_blocked(buf, readlen);
 	}
-	close_istream(st);
+	odb_read_stream_close(st);
 	if (!readlen)
 		finish_record();
 	return readlen;
diff --git a/archive-zip.c b/archive-zip.c
index bea5bdd..97ea8d6 100644
--- a/archive-zip.c
+++ b/archive-zip.c
@@ -10,9 +10,9 @@
 #include "gettext.h"
 #include "git-zlib.h"
 #include "hex.h"
-#include "streaming.h"
 #include "utf8.h"
 #include "odb.h"
+#include "odb/streaming.h"
 #include "strbuf.h"
 #include "userdiff.h"
 #include "write-or-die.h"
@@ -309,7 +309,7 @@ static int write_zip_entry(struct archiver_args *args,
 	enum zip_method method;
 	unsigned char *out;
 	void *deflated = NULL;
-	struct git_istream *stream = NULL;
+	struct odb_read_stream *stream = NULL;
 	unsigned long flags = 0;
 	int is_binary = -1;
 	const char *path_without_prefix = path + args->baselen;
@@ -347,12 +347,11 @@ static int write_zip_entry(struct archiver_args *args,
 			method = ZIP_METHOD_DEFLATE;
 
 		if (!buffer) {
-			enum object_type type;
-			stream = open_istream(args->repo, oid, &type, &size,
-					      NULL);
+			stream = odb_read_stream_open(args->repo->objects, oid, NULL);
 			if (!stream)
 				return error(_("cannot stream blob %s"),
 					     oid_to_hex(oid));
+			size = stream->size;
 			flags |= ZIP_STREAM;
 			out = NULL;
 		} else {
@@ -429,7 +428,7 @@ static int write_zip_entry(struct archiver_args *args,
 		ssize_t readlen;
 
 		for (;;) {
-			readlen = read_istream(stream, buf, sizeof(buf));
+			readlen = odb_read_stream_read(stream, buf, sizeof(buf));
 			if (readlen <= 0)
 				break;
 			crc = crc32(crc, buf, readlen);
@@ -439,7 +438,7 @@ static int write_zip_entry(struct archiver_args *args,
 							    buf, readlen);
 			write_or_die(1, buf, readlen);
 		}
-		close_istream(stream);
+		odb_read_stream_close(stream);
 		if (readlen)
 			return readlen;
 
@@ -462,7 +461,7 @@ static int write_zip_entry(struct archiver_args *args,
 		zstream.avail_out = sizeof(compressed);
 
 		for (;;) {
-			readlen = read_istream(stream, buf, sizeof(buf));
+			readlen = odb_read_stream_read(stream, buf, sizeof(buf));
 			if (readlen <= 0)
 				break;
 			crc = crc32(crc, buf, readlen);
@@ -486,7 +485,7 @@ static int write_zip_entry(struct archiver_args *args,
 			}
 
 		}
-		close_istream(stream);
+		odb_read_stream_close(stream);
 		if (readlen)
 			return readlen;
 
diff --git a/archive.c b/archive.c
index 310672b..fcd474c 100644
--- a/archive.c
+++ b/archive.c
@@ -519,7 +519,7 @@ static void parse_treeish_arg(const char **argv,
 	if (ar_args->mtime_option)
 		archive_time = approxidate(ar_args->mtime_option);
 
-	tree = parse_tree_indirect(&oid);
+	tree = repo_parse_tree_indirect(the_repository, &oid);
 	if (!tree)
 		die(_("not a tree object: %s"), oid_to_hex(&oid));
 
diff --git a/attr.c b/attr.c
index d1daeb0..7536954 100644
--- a/attr.c
+++ b/attr.c
@@ -881,10 +881,11 @@ const char *git_attr_system_file(void)
 
 const char *git_attr_global_file(void)
 {
-	if (!git_attributes_file)
-		git_attributes_file = xdg_config_home("attributes");
+	struct repo_config_values *cfg = repo_config_values(the_repository);
+	if (!cfg->attributes_file)
+		cfg->attributes_file = xdg_config_home("attributes");
 
-	return git_attributes_file;
+	return cfg->attributes_file;
 }
 
 int git_attr_system_is_enabled(void)
@@ -1064,24 +1065,52 @@ static int path_matches(const char *pathname, int pathlen,
 			      pattern, prefix, pat->patternlen);
 }
 
-static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem);
+struct attr_state_queue {
+	const struct attr_state **items;
+	size_t alloc, nr;
+};
+
+static void attr_state_queue_push(struct attr_state_queue *t,
+				 const struct match_attr *a)
+{
+	for (size_t i = 0; i < a->num_attr; i++) {
+		ALLOC_GROW(t->items, t->nr + 1, t->alloc);
+		t->items[t->nr++] = &a->state[i];
+	}
+}
+
+static const struct attr_state *attr_state_queue_pop(struct attr_state_queue *t)
+{
+	return t->nr ? t->items[--t->nr] : NULL;
+}
+
+static void attr_state_queue_release(struct attr_state_queue *t)
+{
+	free(t->items);
+}
 
 static int fill_one(struct all_attrs_item *all_attrs,
 		    const struct match_attr *a, int rem)
 {
-	size_t i;
+	struct attr_state_queue todo = { 0 };
+	const struct attr_state *state;
 
-	for (i = a->num_attr; rem > 0 && i > 0; i--) {
-		const struct git_attr *attr = a->state[i - 1].attr;
+	attr_state_queue_push(&todo, a);
+	while (rem > 0 && (state = attr_state_queue_pop(&todo))) {
+		const struct git_attr *attr = state->attr;
 		const char **n = &(all_attrs[attr->attr_nr].value);
-		const char *v = a->state[i - 1].setto;
+		const char *v = state->setto;
 
 		if (*n == ATTR__UNKNOWN) {
+			const struct all_attrs_item *item =
+				&all_attrs[attr->attr_nr];
 			*n = v;
 			rem--;
-			rem = macroexpand_one(all_attrs, attr->attr_nr, rem);
+			if (item->macro && item->value == ATTR__TRUE)
+				attr_state_queue_push(&todo, item->macro);
 		}
 	}
+	attr_state_queue_release(&todo);
 	return rem;
 }
 
@@ -1106,16 +1135,6 @@ static int fill(const char *path, int pathlen, int basename_offset,
 	return rem;
 }
 
-static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem)
-{
-	const struct all_attrs_item *item = &all_attrs[nr];
-
-	if (item->macro && item->value == ATTR__TRUE)
-		return fill_one(all_attrs, item->macro, rem);
-	else
-		return rem;
-}
-
 /*
  * Marks the attributes which are macros based on the attribute stack.
  * This prevents having to search through the attribute stack each time
diff --git a/banned.h b/banned.h
index 44e76bd..2b934c8 100644
--- a/banned.h
+++ b/banned.h
@@ -41,4 +41,7 @@
 #undef asctime_r
 #define asctime_r(t, buf) BANNED(asctime_r)
 
+#undef mktemp
+#define mktemp(x) BANNED(mktemp)
+
 #endif /* BANNED_H */
diff --git a/bisect.c b/bisect.c
index a6dc76b..ef17a44 100644
--- a/bisect.c
+++ b/bisect.c
@@ -257,7 +257,7 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list, int n
 			p = p->next;
 	}
 	if (p) {
-		free_commit_list(p->next);
+		commit_list_free(p->next);
 		p->next = NULL;
 	}
 	strbuf_release(&buf);
@@ -438,7 +438,7 @@ void find_bisection(struct commit_list **commit_list, int *reaches,
 	if (best) {
 		if (!(bisect_flags & FIND_BISECTION_ALL)) {
 			list->item = best->item;
-			free_commit_list(list->next);
+			commit_list_free(list->next);
 			best = list;
 			best->next = NULL;
 		}
@@ -450,21 +450,20 @@ void find_bisection(struct commit_list **commit_list, int *reaches,
 	clear_commit_weight(&commit_weight);
 }
 
-static int register_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-			int flags UNUSED, void *cb_data UNUSED)
+static int register_ref(const struct reference *ref, void *cb_data UNUSED)
 {
 	struct strbuf good_prefix = STRBUF_INIT;
 	strbuf_addstr(&good_prefix, term_good);
 	strbuf_addstr(&good_prefix, "-");
 
-	if (!strcmp(refname, term_bad)) {
+	if (!strcmp(ref->name, term_bad)) {
 		free(current_bad_oid);
 		current_bad_oid = xmalloc(sizeof(*current_bad_oid));
-		oidcpy(current_bad_oid, oid);
-	} else if (starts_with(refname, good_prefix.buf)) {
-		oid_array_append(&good_revs, oid);
-	} else if (starts_with(refname, "skip-")) {
-		oid_array_append(&skipped_revs, oid);
+		oidcpy(current_bad_oid, ref->oid);
+	} else if (starts_with(ref->name, good_prefix.buf)) {
+		oid_array_append(&good_revs, ref->oid);
+	} else if (starts_with(ref->name, "skip-")) {
+		oid_array_append(&skipped_revs, ref->oid);
 	}
 
 	strbuf_release(&good_prefix);
@@ -474,8 +473,12 @@ static int register_ref(const char *refname, const char *referent UNUSED, const
 
 static int read_bisect_refs(void)
 {
-	return refs_for_each_ref_in(get_main_ref_store(the_repository),
-				    "refs/bisect/", register_ref, NULL);
+	struct refs_for_each_ref_options opts = {
+		.prefix = "refs/bisect/",
+		.trim_prefix = strlen("refs/bisect/"),
+	};
+	return refs_for_each_ref_ext(get_main_ref_store(the_repository),
+				     register_ref, NULL, &opts);
 }
 
 static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
@@ -560,8 +563,8 @@ struct commit_list *filter_skipped(struct commit_list *list,
 		} else {
 			if (!show_all) {
 				if (!skipped_first || !*skipped_first) {
-					free_commit_list(next);
-					free_commit_list(filtered);
+					commit_list_free(next);
+					commit_list_free(filtered);
 					return list;
 				}
 			} else if (skipped_first && !*skipped_first) {
@@ -880,7 +883,7 @@ static enum bisect_error check_merge_bases(size_t rev_nr, struct commit **rev, i
 		}
 	}
 
-	free_commit_list(result);
+	commit_list_free(result);
 	return res;
 }
 
@@ -1143,7 +1146,7 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix)
 
 	res = bisect_checkout(bisect_rev, no_checkout);
 cleanup:
-	free_commit_list(tried);
+	commit_list_free(tried);
 	release_revisions(&revs);
 	strvec_clear(&rev_argv);
 	return res;
@@ -1178,32 +1181,29 @@ int estimate_bisect_steps(int all)
 	return (e < 3 * x) ? n : n - 1;
 }
 
-static int mark_for_removal(const char *refname,
-			    const char *referent UNUSED,
-			    const struct object_id *oid UNUSED,
-			    int flag UNUSED, void *cb_data)
+static int mark_for_removal(const struct reference *ref, void *cb_data)
 {
 	struct string_list *refs = cb_data;
-	char *ref = xstrfmt("refs/bisect%s", refname);
-	string_list_append(refs, ref);
+	string_list_append(refs, ref->name);
 	return 0;
 }
 
 int bisect_clean_state(void)
 {
+	struct refs_for_each_ref_options opts = {
+		.prefix = "refs/bisect/",
+	};
 	int result = 0;
 
 	/* There may be some refs packed during bisection */
-	struct string_list refs_for_removal = STRING_LIST_INIT_NODUP;
-	refs_for_each_ref_in(get_main_ref_store(the_repository),
-			     "refs/bisect", mark_for_removal,
-			     (void *) &refs_for_removal);
-	string_list_append(&refs_for_removal, xstrdup("BISECT_HEAD"));
-	string_list_append(&refs_for_removal, xstrdup("BISECT_EXPECTED_REV"));
+	struct string_list refs_for_removal = STRING_LIST_INIT_DUP;
+	refs_for_each_ref_ext(get_main_ref_store(the_repository),
+			      mark_for_removal, &refs_for_removal, &opts);
+	string_list_append(&refs_for_removal, "BISECT_HEAD");
+	string_list_append(&refs_for_removal, "BISECT_EXPECTED_REV");
 	result = refs_delete_refs(get_main_ref_store(the_repository),
 				  "bisect: remove", &refs_for_removal,
 				  REF_NO_DEREF);
-	refs_for_removal.strdup_strings = 1;
 	string_list_clear(&refs_for_removal, 0);
 	unlink_or_warn(git_path_bisect_ancestors_ok());
 	unlink_or_warn(git_path_bisect_log());
diff --git a/blame.c b/blame.c
index cb0b083..a3c49d1 100644
--- a/blame.c
+++ b/blame.c
@@ -2368,7 +2368,7 @@ static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit
 		if (revs->first_parent_only &&
 		    commit->parents &&
 		    commit->parents->next) {
-			free_commit_list(commit->parents->next);
+			commit_list_free(commit->parents->next);
 			commit->parents->next = NULL;
 		}
 		return commit->parents;
diff --git a/bloom.c b/bloom.c
index 2d7b951..77a6fdd 100644
--- a/bloom.c
+++ b/bloom.c
@@ -354,7 +354,7 @@ static void init_truncated_large_filter(struct bloom_filter *filter,
 
 static int has_entries_with_high_bit(struct repository *r, struct tree *t)
 {
-	if (parse_tree(t))
+	if (repo_parse_tree(r, t))
 		return 1;
 
 	if (!(t->object.flags & VISITED)) {
diff --git a/branch.c b/branch.c
index 26be358..243db7d 100644
--- a/branch.c
+++ b/branch.c
@@ -375,7 +375,7 @@ int validate_branchname(const char *name, struct strbuf *ref)
 	if (check_branch_ref(ref, name)) {
 		int code = die_message(_("'%s' is not a valid branch name"), name);
 		advise_if_enabled(ADVICE_REF_SYNTAX,
-				  _("See `man git check-ref-format`"));
+				  _("See 'git help check-ref-format'"));
 		exit(code);
 	}
 
diff --git a/branch.h b/branch.h
index ec2f35f..3dc6e2a 100644
--- a/branch.h
+++ b/branch.h
@@ -15,8 +15,6 @@ enum branch_track {
 	BRANCH_TRACK_SIMPLE,
 };
 
-extern enum branch_track git_branch_track;
-
 /* Functions for acting on the information about branches. */
 
 /**
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..3724b3a
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,17 @@
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation: version 2 of the License, dated June 1991.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, see <https://www.gnu.org/licenses/>.
+
+fn main() {
+    println!("cargo:rustc-link-search=.");
+    println!("cargo:rustc-link-lib=git");
+    println!("cargo:rustc-link-lib=z");
+}
diff --git a/builtin.h b/builtin.h
index 1b35565..235c51f 100644
--- a/builtin.h
+++ b/builtin.h
@@ -17,7 +17,8 @@
  * . Define the implementation of the built-in command `foo` with
  *   signature:
  *
- *	int cmd_foo(int argc, const char **argv, const char *prefix);
+ *	int cmd_foo(int argc, const char **argv,
+ *		    const char *prefix, struct repository *repo);
  *
  * . Add the external declaration for the function to `builtin.h`.
  *
@@ -29,12 +30,14 @@
  * where options is the bitwise-or of:
  *
  * `RUN_SETUP`:
+ *
  *	If there is not a Git directory to work on, abort.  If there
  *	is a work tree, chdir to the top of it if the command was
  *	invoked in a subdirectory.  If there is no work tree, no
  *	chdir() is done.
  *
  * `RUN_SETUP_GENTLY`:
+ *
  *	If there is a Git directory, chdir as per RUN_SETUP, otherwise,
  *	don't chdir anywhere.
  *
@@ -57,6 +60,12 @@
  *	more informed decision, e.g., by ignoring `pager.<cmd>` for
  *	certain subcommands.
  *
+ * `NO_PARSEOPT`:
+ *
+ *	Most Git builtins use the parseopt library for parsing options.
+ *	This flag indicates that a custom parser is used and thus the
+ *	builtin would not appear in 'git --list-cmds=parseopt'.
+ *
  * . Add `builtin/foo.o` to `BUILTIN_OBJS` in `Makefile`.
  *
  * Additionally, if `foo` is a new command, there are 4 more things to do:
@@ -69,6 +78,21 @@
  *
  * . Add an entry for `/git-foo` to `.gitignore`.
  *
+ * As you work on implementing your builtin, be mindful that the
+ * following tests will check different aspects of the builtin's
+ * readiness and adherence to matching the documentation:
+ *
+ * * t0012-help.sh checks that the builtin can handle -h, which comes
+ *   automatically with the parseopt API.
+ *
+ * * t0450-txt-doc-vs-help.sh checks that the -h help output matches the
+ *   SYNOPSIS in the documentation for the builtin.
+ *
+ * * t1517-outside-repo.sh checks that the builtin can handle -h when
+ *   run outside of the context of a repository. Note that this test
+ *   requires that the usage has a space after the builtin name, so some
+ *   minimum description of options is required.
+ *
  *
  * How a built-in is called
  * ------------------------
@@ -172,6 +196,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix, struc
 int cmd_grep(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_hash_object(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_help(int argc, const char **argv, const char *prefix, struct repository *repo);
+int cmd_history(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_hook(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_index_pack(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_init_db(int argc, const char **argv, const char *prefix, struct repository *repo);
diff --git a/builtin/add.c b/builtin/add.c
index 3270979..0ee2169 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -256,6 +256,8 @@ static struct option builtin_add_options[] = {
 	OPT_GROUP(""),
 	OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")),
 	OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")),
+	OPT_BOOL(0, "auto-advance", &add_p_opt.auto_advance,
+		 N_("auto advance to the next file when selecting hunks interactively")),
 	OPT_DIFF_UNIFIED(&add_p_opt.context),
 	OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 	OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")),
@@ -418,6 +420,8 @@ int cmd_add(int argc,
 			die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch");
 		if (add_p_opt.interhunkcontext != -1)
 			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch");
+		if (!add_p_opt.auto_advance)
+			die(_("the option '%s' requires '%s'"), "--no-auto-advance", "--interactive/--patch");
 	}
 
 	if (edit_interactive) {
@@ -584,7 +588,7 @@ int cmd_add(int argc,
 	else
 		exit_status |= add_files_to_cache(repo, prefix,
 						  &pathspec, ps_matched,
-						  include_sparse, flags);
+						  include_sparse, flags, ignored_too);
 
 	if (take_worktree_changes && !add_renormalize && !ignore_add_errors &&
 	    report_path_error(ps_matched, &pathspec))
diff --git a/builtin/am.c b/builtin/am.c
index 277c2e7..e0c767e 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1726,7 +1726,7 @@ static void do_commit(const struct am_state *state)
 
 	run_hooks(the_repository, "post-applypatch");
 
-	free_commit_list(parents);
+	commit_list_free(parents);
 	strbuf_release(&sb);
 }
 
@@ -1998,7 +1998,7 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset)
 	struct unpack_trees_options opts;
 	struct tree_desc t[2];
 
-	if (parse_tree(head) || parse_tree(remote))
+	if (repo_parse_tree(the_repository, head) || repo_parse_tree(the_repository, remote))
 		return -1;
 
 	repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
@@ -2038,7 +2038,7 @@ static int merge_tree(struct tree *tree)
 	struct unpack_trees_options opts;
 	struct tree_desc t[1];
 
-	if (parse_tree(tree))
+	if (repo_parse_tree(the_repository, tree))
 		return -1;
 
 	repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
@@ -2071,11 +2071,11 @@ static int clean_index(const struct object_id *head, const struct object_id *rem
 	struct tree *head_tree, *remote_tree, *index_tree;
 	struct object_id index;
 
-	head_tree = parse_tree_indirect(head);
+	head_tree = repo_parse_tree_indirect(the_repository, head);
 	if (!head_tree)
 		return error(_("Could not parse object '%s'."), oid_to_hex(head));
 
-	remote_tree = parse_tree_indirect(remote);
+	remote_tree = repo_parse_tree_indirect(the_repository, remote);
 	if (!remote_tree)
 		return error(_("Could not parse object '%s'."), oid_to_hex(remote));
 
@@ -2089,7 +2089,7 @@ static int clean_index(const struct object_id *head, const struct object_id *rem
 				0, NULL))
 		return -1;
 
-	index_tree = parse_tree_indirect(&index);
+	index_tree = repo_parse_tree_indirect(the_repository, &index);
 	if (!index_tree)
 		return error(_("Could not parse object '%s'."), oid_to_hex(&index));
 
diff --git a/builtin/backfill.c b/builtin/backfill.c
index e80fc1b..e9a33e8 100644
--- a/builtin/backfill.c
+++ b/builtin/backfill.c
@@ -67,8 +67,7 @@ static int fill_missing_blobs(const char *path UNUSED,
 		return 0;
 
 	for (size_t i = 0; i < list->nr; i++) {
-		if (!odb_has_object(ctx->repo->objects, &list->oid[i],
-				    OBJECT_INFO_FOR_PREFETCH))
+		if (!odb_has_object(ctx->repo->objects, &list->oid[i], 0))
 			oid_array_append(&ctx->current_batch, &list->oid[i]);
 	}
 
@@ -129,6 +128,7 @@ int cmd_backfill(int argc, const char **argv, const char *prefix, struct reposit
 			 N_("Restrict the missing objects to the current sparse-checkout")),
 		OPT_END(),
 	};
+	struct repo_config_values *cfg = repo_config_values(the_repository);
 
 	show_usage_with_options_if_asked(argc, argv,
 					 builtin_backfill_usage, options);
@@ -139,7 +139,7 @@ int cmd_backfill(int argc, const char **argv, const char *prefix, struct reposit
 	repo_config(repo, git_default_config, NULL);
 
 	if (ctx.sparse < 0)
-		ctx.sparse = core_apply_sparse_checkout;
+		ctx.sparse = cfg->apply_sparse_checkout;
 
 	result = do_backfill(&ctx);
 	backfill_context_clear(&ctx);
diff --git a/builtin/bisect.c b/builtin/bisect.c
index ccff4e1..4520e58 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -363,10 +363,7 @@ static int check_and_set_terms(struct bisect_terms *terms, const char *cmd)
 	return 0;
 }
 
-static int inc_nr(const char *refname UNUSED,
-		  const char *referent UNUSED,
-		  const struct object_id *oid UNUSED,
-		  int flag UNUSED, void *cb_data)
+static int inc_nr(const struct reference *ref UNUSED, void *cb_data)
 {
 	unsigned int *nr = (unsigned int *)cb_data;
 	(*nr)++;
@@ -425,13 +422,17 @@ static void bisect_status(struct bisect_state *state,
 {
 	char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad);
 	char *good_glob = xstrfmt("%s-*", terms->term_good);
+	struct refs_for_each_ref_options opts = {
+		.pattern = good_glob,
+		.prefix = "refs/bisect/",
+		.trim_prefix = strlen("refs/bisect/"),
+	};
 
 	if (refs_ref_exists(get_main_ref_store(the_repository), bad_ref))
 		state->nr_bad = 1;
 
-	refs_for_each_glob_ref_in(get_main_ref_store(the_repository), inc_nr,
-				  good_glob, "refs/bisect/",
-				  (void *) &state->nr_good);
+	refs_for_each_ref_ext(get_main_ref_store(the_repository),
+			      inc_nr, &state->nr_good, &opts);
 
 	free(good_glob);
 	free(bad_ref);
@@ -554,18 +555,21 @@ static int bisect_append_log_quoted(const char **argv)
 	return res;
 }
 
-static int add_bisect_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-			  int flags UNUSED, void *cb)
+static int add_bisect_ref(const struct reference *ref, void *cb)
 {
 	struct add_bisect_ref_data *data = cb;
 
-	add_pending_oid(data->revs, refname, oid, data->object_flags);
+	add_pending_oid(data->revs, ref->name, ref->oid, data->object_flags);
 
 	return 0;
 }
 
 static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs)
 {
+	struct refs_for_each_ref_options opts = {
+		.prefix = "refs/bisect/",
+		.trim_prefix = strlen("refs/bisect/"),
+	};
 	int res = 0;
 	struct add_bisect_ref_data cb = { revs };
 	char *good = xstrfmt("%s-*", terms->term_good);
@@ -585,11 +589,16 @@ static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs)
 	reset_revision_walk();
 	repo_init_revisions(the_repository, revs, NULL);
 	setup_revisions(0, NULL, revs, NULL);
-	refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
-				  add_bisect_ref, bad, "refs/bisect/", &cb);
+
+	opts.pattern = bad;
+	refs_for_each_ref_ext(get_main_ref_store(the_repository),
+			      add_bisect_ref, &cb, &opts);
+
 	cb.object_flags = UNINTERESTING;
-	refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
-				  add_bisect_ref, good, "refs/bisect/", &cb);
+	opts.pattern = good;
+	refs_for_each_ref_ext(get_main_ref_store(the_repository),
+			      add_bisect_ref, &cb, &opts);
+
 	if (prepare_revision_walk(revs))
 		res = error(_("revision walk setup failed"));
 
@@ -1170,12 +1179,9 @@ static int bisect_visualize(struct bisect_terms *terms, int argc,
 	return run_command(&cmd);
 }
 
-static int get_first_good(const char *refname UNUSED,
-			  const char *referent UNUSED,
-			  const struct object_id *oid,
-			  int flag UNUSED, void *cb_data)
+static int get_first_good(const struct reference *ref, void *cb_data)
 {
-	oidcpy(cb_data, oid);
+	oidcpy(cb_data, ref->oid);
 	return 1;
 }
 
@@ -1198,10 +1204,14 @@ static int verify_good(const struct bisect_terms *terms, const char *command)
 	char *good_glob = xstrfmt("%s-*", terms->term_good);
 	int no_checkout = refs_ref_exists(get_main_ref_store(the_repository),
 					  "BISECT_HEAD");
+	struct refs_for_each_ref_options opts = {
+		.pattern = good_glob,
+		.prefix = "refs/bisect/",
+		.trim_prefix = strlen("refs/bisect/"),
+	};
 
-	refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
-				  get_first_good, good_glob, "refs/bisect/",
-				  &good_rev);
+	refs_for_each_ref_ext(get_main_ref_store(the_repository),
+			      get_first_good, &good_rev, &opts);
 	free(good_glob);
 
 	if (refs_read_ref(get_main_ref_store(the_repository), no_checkout ? "BISECT_HEAD" : "HEAD", &current_rev))
diff --git a/builtin/blame.c b/builtin/blame.c
index 2703820..f3a11ef 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -10,7 +10,6 @@
 #include "builtin.h"
 #include "config.h"
 #include "color.h"
-#include "builtin.h"
 #include "environment.h"
 #include "gettext.h"
 #include "hex.h"
@@ -454,7 +453,8 @@ static void determine_line_heat(struct commit_info *ci, const char **dest_color)
 	*dest_color = colorfield[i].col;
 }
 
-static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int opt)
+static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent,
+		       int opt, struct blame_entry *prev_ent)
 {
 	int cnt;
 	const char *cp;
@@ -485,7 +485,10 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
 			the_hash_algo->hexsz : (size_t) abbrev;
 
 		if (opt & OUTPUT_COLOR_LINE) {
-			if (cnt > 0) {
+			if (cnt > 0 ||
+			    (prev_ent &&
+			     oideq(&suspect->commit->object.oid,
+				   &prev_ent->suspect->commit->object.oid))) {
 				color = repeated_meta_color;
 				reset = GIT_COLOR_RESET;
 			} else  {
@@ -571,7 +574,7 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
 
 static void output(struct blame_scoreboard *sb, int option)
 {
-	struct blame_entry *ent;
+	struct blame_entry *ent, *prev_ent = NULL;
 
 	if (option & OUTPUT_PORCELAIN) {
 		for (ent = sb->ent; ent; ent = ent->next) {
@@ -593,7 +596,8 @@ static void output(struct blame_scoreboard *sb, int option)
 		if (option & OUTPUT_PORCELAIN)
 			emit_porcelain(sb, ent, option);
 		else {
-			emit_other(sb, ent, option);
+			emit_other(sb, ent, option, prev_ent);
+			prev_ent = ent;
 		}
 	}
 }
@@ -739,7 +743,8 @@ static int git_blame_config(const char *var, const char *value,
 		ret = git_config_pathname(&str, var, value);
 		if (ret)
 			return ret;
-		string_list_insert(&ignore_revs_file_list, str);
+		if (str)
+			string_list_insert(&ignore_revs_file_list, str);
 		free(str);
 		return 0;
 	}
@@ -779,6 +784,19 @@ static int git_blame_config(const char *var, const char *value,
 		}
 	}
 
+	if (!strcmp(var, "diff.algorithm")) {
+		long diff_algorithm;
+		if (!value)
+			return config_error_nonbool(var);
+		diff_algorithm = parse_algorithm_value(value);
+		if (diff_algorithm < 0)
+			return error(_("unknown value for config '%s': %s"),
+				     var, value);
+		xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK;
+		xdl_opts |= diff_algorithm;
+		return 0;
+	}
+
 	if (git_diff_heuristic_config(var, value, cb) < 0)
 		return -1;
 	if (userdiff_config(var, value) < 0)
@@ -824,6 +842,38 @@ static int blame_move_callback(const struct option *option, const char *arg, int
 	return 0;
 }
 
+static int blame_diff_algorithm_minimal(const struct option *option,
+					const char *arg, int unset)
+{
+	int *opt = option->value;
+
+	BUG_ON_OPT_ARG(arg);
+
+	*opt &= ~XDF_DIFF_ALGORITHM_MASK;
+	if (!unset)
+		*opt |= XDF_NEED_MINIMAL;
+
+	return 0;
+}
+
+static int blame_diff_algorithm_callback(const struct option *option,
+					 const char *arg, int unset)
+{
+	int *opt = option->value;
+	long value = parse_algorithm_value(arg);
+
+	BUG_ON_OPT_NEG(unset);
+
+	if (value < 0)
+		return error(_("option diff-algorithm accepts \"myers\", "
+			       "\"minimal\", \"patience\" and \"histogram\""));
+
+	*opt &= ~XDF_DIFF_ALGORITHM_MASK;
+	*opt |= value;
+
+	return 0;
+}
+
 static int is_a_rev(const char *name)
 {
 	struct object_id oid;
@@ -915,11 +965,16 @@ int cmd_blame(int argc,
 		OPT_BIT('s', NULL, &output_option, N_("suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR),
 		OPT_BIT('e', "show-email", &output_option, N_("show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL),
 		OPT_BIT('w', NULL, &xdl_opts, N_("ignore whitespace differences"), XDF_IGNORE_WHITESPACE),
+		OPT_CALLBACK_F(0, "diff-algorithm", &xdl_opts, N_("<algorithm>"),
+			       N_("choose a diff algorithm"),
+			       PARSE_OPT_NONEG, blame_diff_algorithm_callback),
 		OPT_STRING_LIST(0, "ignore-rev", &ignore_rev_list, N_("rev"), N_("ignore <rev> when blaming")),
 		OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("ignore revisions from <file>")),
 		OPT_BIT(0, "color-lines", &output_option, N_("color redundant metadata from previous line differently"), OUTPUT_COLOR_LINE),
 		OPT_BIT(0, "color-by-age", &output_option, N_("color lines by age"), OUTPUT_SHOW_AGE_WITH_COLOR),
-		OPT_BIT(0, "minimal", &xdl_opts, N_("spend extra cycles to find better match"), XDF_NEED_MINIMAL),
+		OPT_CALLBACK_F(0, "minimal", &xdl_opts, NULL,
+			       N_("spend extra cycles to find a better match"),
+			       PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, blame_diff_algorithm_minimal),
 		OPT_STRING('S', NULL, &revs_file, N_("file"), N_("use revisions from <file> instead of calling git-rev-list")),
 		OPT_STRING(0, "contents", &contents_from, N_("file"), N_("use <file>'s contents as the final image")),
 		OPT_CALLBACK_F('C', NULL, &opt, N_("score"), N_("find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback),
@@ -1197,7 +1252,7 @@ int cmd_blame(int argc,
 	sb.xdl_opts = xdl_opts;
 	sb.no_whole_file_rename = no_whole_file_rename;
 
-	read_mailmap(&mailmap);
+	read_mailmap(the_repository, &mailmap);
 
 	sb.found_guilty_entry = &found_guilty_entry;
 	sb.found_guilty_entry_data = &pi;
diff --git a/builtin/branch.c b/builtin/branch.c
index 9fcf04b..a1a4338 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -591,7 +591,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
 		else {
 			int code = die_message(_("invalid branch name: '%s'"), oldname);
 			advise_if_enabled(ADVICE_REF_SYNTAX,
-					  _("See `man git check-ref-format`"));
+					  _("See 'git help check-ref-format'"));
 			exit(code);
 		}
 	}
@@ -724,6 +724,7 @@ int cmd_branch(int argc,
 	static struct ref_sorting *sorting;
 	struct string_list sorting_options = STRING_LIST_INIT_DUP;
 	struct ref_format format = REF_FORMAT_INIT;
+	struct repo_config_values *cfg = repo_config_values(the_repository);
 	int ret;
 
 	struct option options[] = {
@@ -795,7 +796,7 @@ int cmd_branch(int argc,
 	if (!sorting_options.nr)
 		string_list_append(&sorting_options, "refname");
 
-	track = git_branch_track;
+	track = cfg->branch_track;
 
 	head = refs_resolve_refdup(get_main_ref_store(the_repository), "HEAD",
 				   0, &head_oid, NULL);
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 983ecec..b6f12f4 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -18,13 +18,13 @@
 #include "list-objects-filter-options.h"
 #include "parse-options.h"
 #include "userdiff.h"
-#include "streaming.h"
 #include "oid-array.h"
 #include "packfile.h"
 #include "pack-bitmap.h"
 #include "object-file.h"
 #include "object-name.h"
 #include "odb.h"
+#include "odb/streaming.h"
 #include "replace-object.h"
 #include "promisor-remote.h"
 #include "mailmap.h"
@@ -95,7 +95,7 @@ static int filter_object(const char *path, unsigned mode,
 
 static int stream_blob(const struct object_id *oid)
 {
-	if (stream_blob_to_fd(1, oid, NULL, 0))
+	if (odb_stream_blob_to_fd(the_repository->objects, 1, oid, NULL, 0))
 		die("unable to stream %s to stdout", oid_to_hex(oid));
 	return 0;
 }
@@ -487,8 +487,7 @@ static void batch_object_write(const char *obj_name,
 			data->info.sizep = &data->size;
 
 		if (pack)
-			ret = packed_object_info(the_repository, pack,
-						 offset, &data->info);
+			ret = packed_object_info(pack, offset, &data->info);
 		else
 			ret = odb_read_object_info_extended(the_repository->objects,
 							    &data->oid, &data->info,
@@ -807,11 +806,14 @@ struct for_each_object_payload {
 	void *payload;
 };
 
-static int batch_one_object_loose(const struct object_id *oid,
-				  const char *path UNUSED,
-				  void *_payload)
+static int batch_one_object_oi(const struct object_id *oid,
+			       struct object_info *oi,
+			       void *_payload)
 {
 	struct for_each_object_payload *payload = _payload;
+	if (oi && oi->whence == OI_PACKED)
+		return payload->callback(oid, oi->u.packed.pack, oi->u.packed.offset,
+					 payload->payload);
 	return payload->callback(oid, NULL, 0, payload->payload);
 }
 
@@ -846,12 +848,27 @@ static void batch_each_object(struct batch_options *opt,
 		.callback = callback,
 		.payload = _payload,
 	};
-	struct bitmap_index *bitmap = prepare_bitmap_git(the_repository);
+	struct bitmap_index *bitmap = NULL;
+	struct odb_source *source;
 
-	for_each_loose_object(the_repository->objects, batch_one_object_loose, &payload, 0);
+	/*
+	 * TODO: we still need to tap into implementation details of the object
+	 * database sources. Ideally, we should extend `odb_for_each_object()`
+	 * to handle object filters itself so that we can move the filtering
+	 * logic into the individual sources.
+	 */
+	odb_prepare_alternates(the_repository->objects);
+	for (source = the_repository->objects->sources; source; source = source->next) {
+		int ret = odb_source_loose_for_each_object(source, NULL, batch_one_object_oi,
+							   &payload, flags);
+		if (ret)
+			break;
+	}
 
-	if (bitmap && !for_each_bitmapped_object(bitmap, &opt->objects_filter,
-						 batch_one_object_bitmapped, &payload)) {
+	if (opt->objects_filter.choice != LOFC_DISABLED &&
+	    (bitmap = prepare_bitmap_git(the_repository)) &&
+	    !for_each_bitmapped_object(bitmap, &opt->objects_filter,
+				       batch_one_object_bitmapped, &payload)) {
 		struct packed_git *pack;
 
 		repo_for_each_pack(the_repository, pack) {
@@ -862,8 +879,15 @@ static void batch_each_object(struct batch_options *opt,
 						&payload, flags);
 		}
 	} else {
-		for_each_packed_object(the_repository, batch_one_object_packed,
-				       &payload, flags);
+		struct object_info oi = { 0 };
+
+		for (source = the_repository->objects->sources; source; source = source->next) {
+			struct odb_source_files *files = odb_source_files_downcast(source);
+			int ret = packfile_store_for_each_object(files->packed, &oi,
+								 batch_one_object_oi, &payload, flags);
+			if (ret)
+				break;
+		}
 	}
 
 	free_bitmap_index(bitmap);
@@ -923,7 +947,7 @@ static int batch_objects(struct batch_options *opt)
 			cb.seen = &seen;
 
 			batch_each_object(opt, batch_unordered_object,
-					  FOR_EACH_OBJECT_PACK_ORDER, &cb);
+					  ODB_FOR_EACH_OBJECT_PACK_ORDER, &cb);
 
 			oidset_clear(&seen);
 		} else {
@@ -1104,7 +1128,7 @@ int cmd_cat_file(int argc,
 	opt_epts = (opt == 'e' || opt == 'p' || opt == 't' || opt == 's');
 
 	if (use_mailmap)
-		read_mailmap(&mailmap);
+		read_mailmap(the_repository, &mailmap);
 
 	switch (batch.objects_filter.choice) {
 	case LOFC_DISABLED:
diff --git a/builtin/check-mailmap.c b/builtin/check-mailmap.c
index 9cc5c59..3f2a39c 100644
--- a/builtin/check-mailmap.c
+++ b/builtin/check-mailmap.c
@@ -63,9 +63,9 @@ int cmd_check_mailmap(int argc,
 	if (argc == 0 && !use_stdin)
 		die(_("no contacts specified"));
 
-	read_mailmap(&mailmap);
+	read_mailmap(the_repository, &mailmap);
 	if (mailmap_blob)
-		read_mailmap_blob(&mailmap, mailmap_blob);
+		read_mailmap_blob(the_repository, &mailmap, mailmap_blob);
 	if (mailmap_file)
 		read_mailmap_file(&mailmap, mailmap_file, 0);
 
diff --git a/builtin/checkout.c b/builtin/checkout.c
index f945347..1d1667f 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -43,26 +43,11 @@
 #include "parallel-checkout.h"
 #include "add-interactive.h"
 
-static const char * const checkout_usage[] = {
-	N_("git checkout [<options>] <branch>"),
-	N_("git checkout [<options>] [<branch>] -- <file>..."),
-	NULL,
-};
-
-static const char * const switch_branch_usage[] = {
-	N_("git switch [<options>] [<branch>]"),
-	NULL,
-};
-
-static const char * const restore_usage[] = {
-	N_("git restore [<options>] [--source=<branch>] <file>..."),
-	NULL,
-};
-
 struct checkout_opts {
 	int patch_mode;
 	int patch_context;
 	int patch_interhunk_context;
+	int auto_advance;
 	int quiet;
 	int merge;
 	int force;
@@ -111,6 +96,7 @@ struct checkout_opts {
 	.merge = -1, \
 	.patch_context = -1, \
 	.patch_interhunk_context = -1, \
+	.auto_advance = 1, \
 }
 
 struct branch_info {
@@ -294,9 +280,9 @@ static int checkout_merged(int pos, const struct checkout *state,
 	if (is_null_oid(&threeway[1]) || is_null_oid(&threeway[2]))
 		return error(_("path '%s' does not have necessary versions"), path);
 
-	read_mmblob(&ancestor, &threeway[0]);
-	read_mmblob(&ours, &threeway[1]);
-	read_mmblob(&theirs, &threeway[2]);
+	read_mmblob(&ancestor, the_repository->objects, &threeway[0]);
+	read_mmblob(&ours, the_repository->objects, &threeway[1]);
+	read_mmblob(&theirs, the_repository->objects, &threeway[2]);
 
 	repo_config_get_bool(the_repository, "merge.renormalize", &renormalize);
 	ll_opts.renormalize = renormalize;
@@ -549,6 +535,7 @@ static int checkout_paths(const struct checkout_opts *opts,
 		struct add_p_opt add_p_opt = {
 			.context = opts->patch_context,
 			.interhunkcontext = opts->patch_interhunk_context,
+			.auto_advance = opts->auto_advance
 		};
 		const char *rev = new_branch_info->name;
 		char rev_oid[GIT_MAX_HEXSZ + 1];
@@ -724,7 +711,7 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
 	init_checkout_metadata(&opts.meta, info->refname,
 			       info->commit ? &info->commit->object.oid : null_oid(the_hash_algo),
 			       NULL);
-	if (parse_tree(tree) < 0)
+	if (repo_parse_tree(the_repository, tree) < 0)
 		return 128;
 	init_tree_desc(&tree_desc, &tree->object.oid, tree->buffer, tree->size);
 	switch (unpack_trees(1, &tree_desc, &opts)) {
@@ -803,7 +790,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
 	if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
 		if (new_branch_info->commit)
 			BUG("'switch --orphan' should never accept a commit as starting point");
-		new_tree = parse_tree_indirect(the_hash_algo->empty_tree);
+		new_tree = repo_parse_tree_indirect(the_repository,
+						    the_hash_algo->empty_tree);
 		if (!new_tree)
 			BUG("unable to read empty tree");
 	} else {
@@ -841,14 +829,15 @@ static int merge_working_tree(const struct checkout_opts *opts,
 		old_commit_oid = old_branch_info->commit ?
 			&old_branch_info->commit->object.oid :
 			the_hash_algo->empty_tree;
-		tree = parse_tree_indirect(old_commit_oid);
+		tree = repo_parse_tree_indirect(the_repository,
+						old_commit_oid);
 		if (!tree)
 			die(_("unable to parse commit %s"),
 				oid_to_hex(old_commit_oid));
 
 		init_tree_desc(&trees[0], &tree->object.oid,
 			       tree->buffer, tree->size);
-		if (parse_tree(new_tree) < 0)
+		if (repo_parse_tree(the_repository, new_tree) < 0)
 			die(NULL);
 		tree = new_tree;
 		init_tree_desc(&trees[1], &tree->object.oid,
@@ -899,7 +888,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			 */
 
 			add_files_to_cache(the_repository, NULL, NULL, NULL, 0,
-					   0);
+					0, 0);
 			init_ui_merge_options(&o, the_repository);
 			o.verbosity = 0;
 			work = write_in_core_index_as_tree(the_repository);
@@ -1063,11 +1052,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 		report_tracking(new_branch_info);
 }
 
-static int add_pending_uninteresting_ref(const char *refname, const char *referent UNUSED,
-					 const struct object_id *oid,
-					 int flags UNUSED, void *cb_data)
+static int add_pending_uninteresting_ref(const struct reference *ref, void *cb_data)
 {
-	add_pending_oid(cb_data, refname, oid, UNINTERESTING);
+	add_pending_oid(cb_data, ref->name, ref->oid, UNINTERESTING);
 	return 0;
 }
 
@@ -1280,7 +1267,7 @@ static void setup_new_branch_info_and_source_tree(
 	new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
 	if (!new_branch_info->commit) {
 		/* not a commit */
-		*source_tree = parse_tree_indirect(rev);
+		*source_tree = repo_parse_tree_indirect(the_repository, rev);
 		if (!*source_tree)
 			die(_("unable to read tree (%s)"), oid_to_hex(rev));
 	} else {
@@ -1293,9 +1280,17 @@ static void setup_new_branch_info_and_source_tree(
 	}
 }
 
+
+enum checkout_command {
+	CHECKOUT_CHECKOUT = 1,
+	CHECKOUT_SWITCH = 2,
+	CHECKOUT_RESTORE = 3,
+};
+
 static char *parse_remote_branch(const char *arg,
 				 struct object_id *rev,
-				 int could_be_checkout_paths)
+				 int could_be_checkout_paths,
+				 enum checkout_command which_command)
 {
 	int num_matches = 0;
 	char *remote = unique_tracking_name(arg, rev, &num_matches);
@@ -1308,14 +1303,30 @@ static char *parse_remote_branch(const char *arg,
 
 	if (!remote && num_matches > 1) {
 	    if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) {
+		    const char *cmdname;
+
+		    switch (which_command) {
+		    case CHECKOUT_CHECKOUT:
+			    cmdname = "checkout";
+			    break;
+		    case CHECKOUT_SWITCH:
+			    cmdname = "switch";
+			    break;
+		    default:
+			    BUG("command <%d> should not reach parse_remote_branch",
+				which_command);
+			    break;
+		    }
+
 		    advise(_("If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
 			     "you can do so by fully qualifying the name with the --track option:\n"
 			     "\n"
-			     "    git checkout --track origin/<name>\n"
+			     "    git %s --track origin/<name>\n"
 			     "\n"
 			     "If you'd like to always have checkouts of an ambiguous <name> prefer\n"
 			     "one remote, e.g. the 'origin' remote, consider setting\n"
-			     "checkout.defaultRemote=origin in your config."));
+			     "checkout.defaultRemote=origin in your config."),
+			   cmdname);
 	    }
 
 	    die(_("'%s' matched multiple (%d) remote tracking branches"),
@@ -1327,6 +1338,7 @@ static char *parse_remote_branch(const char *arg,
 
 static int parse_branchname_arg(int argc, const char **argv,
 				int dwim_new_local_branch_ok,
+				enum checkout_command which_command,
 				struct branch_info *new_branch_info,
 				struct checkout_opts *opts,
 				struct object_id *rev)
@@ -1436,7 +1448,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 
 		if (recover_with_dwim) {
 			remote = parse_remote_branch(arg, rev,
-						     could_be_checkout_paths);
+						     could_be_checkout_paths,
+						     which_command);
 			if (remote) {
 				*new_branch = arg;
 				arg = remote;
@@ -1590,6 +1603,7 @@ static void die_if_switching_to_a_branch_in_use(struct checkout_opts *opts,
 static int checkout_branch(struct checkout_opts *opts,
 			   struct branch_info *new_branch_info)
 {
+	struct repo_config_values *cfg = repo_config_values(the_repository);
 	int noop_switch = (!new_branch_info->name &&
 			   !opts->new_branch &&
 			   !opts->force_detach);
@@ -1633,7 +1647,7 @@ static int checkout_branch(struct checkout_opts *opts,
 		if (opts->track != BRANCH_TRACK_UNSPECIFIED)
 			die(_("'%s' cannot be used with '%s'"), "--detach", "-t");
 	} else if (opts->track == BRANCH_TRACK_UNSPECIFIED)
-		opts->track = git_branch_track;
+		opts->track = cfg->branch_track;
 
 	if (new_branch_info->name && !new_branch_info->commit)
 		die(_("Cannot switch branch to a non-commit '%s'"),
@@ -1767,12 +1781,44 @@ static char cb_option = 'b';
 
 static int checkout_main(int argc, const char **argv, const char *prefix,
 			 struct checkout_opts *opts, struct option *options,
-			 const char * const usagestr[])
+			 enum checkout_command which_command)
 {
 	int parseopt_flags = 0;
 	struct branch_info new_branch_info = { 0 };
 	int ret;
 
+	static const char * const checkout_usage[] = {
+		N_("git checkout [<options>] <branch>"),
+		N_("git checkout [<options>] [<branch>] -- <file>..."),
+		NULL,
+	};
+
+	static const char * const switch_branch_usage[] = {
+		N_("git switch [<options>] [<branch>]"),
+		NULL,
+	};
+
+	static const char * const restore_usage[] = {
+		N_("git restore [<options>] [--source=<branch>] <file>..."),
+		NULL,
+	};
+
+	const char * const *usagestr;
+
+	switch (which_command) {
+	case CHECKOUT_CHECKOUT:
+		usagestr = checkout_usage;
+		break;
+	case CHECKOUT_SWITCH:
+		usagestr = switch_branch_usage;
+		break;
+	case CHECKOUT_RESTORE:
+		usagestr = restore_usage;
+		break;
+	default:
+		BUG("no such checkout variant %d", which_command);
+	}
+
 	opts->overwrite_ignore = 1;
 	opts->prefix = prefix;
 	opts->show_progress = -1;
@@ -1803,6 +1849,8 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
 		if (opts->patch_interhunk_context != -1)
 			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+		if (!opts->auto_advance)
+			die(_("the option '%s' requires '%s'"), "--no-auto-advance", "--patch");
 	}
 
 	if (opts->show_progress < 0) {
@@ -1893,7 +1941,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 			opts->dwim_new_local_branch &&
 			opts->track == BRANCH_TRACK_UNSPECIFIED &&
 			!opts->new_branch;
-		int n = parse_branchname_arg(argc, argv, dwim_ok,
+		int n = parse_branchname_arg(argc, argv, dwim_ok, which_command,
 					     &new_branch_info, opts, &rev);
 		argv += n;
 		argc -= n;
@@ -1901,7 +1949,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 		struct object_id rev;
 
 		if (repo_get_oid_mb(the_repository, opts->from_treeish, &rev))
-			die(_("could not resolve %s"), opts->from_treeish);
+			die(_("could not resolve '%s'"), opts->from_treeish);
 
 		setup_new_branch_info_and_source_tree(&new_branch_info,
 						      opts, &rev,
@@ -2001,6 +2049,8 @@ int cmd_checkout(int argc,
 		OPT_BOOL(0, "guess", &opts.dwim_new_local_branch,
 			 N_("second guess 'git checkout <no-such-branch>' (default)")),
 		OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")),
+		OPT_BOOL(0, "auto-advance", &opts.auto_advance,
+			 N_("auto advance to the next file when selecting hunks interactively")),
 		OPT_END()
 	};
 
@@ -2032,7 +2082,7 @@ int cmd_checkout(int argc,
 	options = add_checkout_path_options(&opts, options);
 
 	return checkout_main(argc, argv, prefix, &opts, options,
-			     checkout_usage);
+			     CHECKOUT_CHECKOUT);
 }
 
 int cmd_switch(int argc,
@@ -2071,7 +2121,7 @@ int cmd_switch(int argc,
 	cb_option = 'c';
 
 	return checkout_main(argc, argv, prefix, &opts, options,
-			     switch_branch_usage);
+			     CHECKOUT_SWITCH);
 }
 
 int cmd_restore(int argc,
@@ -2107,5 +2157,5 @@ int cmd_restore(int argc,
 	options = add_checkout_path_options(&opts, options);
 
 	return checkout_main(argc, argv, prefix, &opts, options,
-			     restore_usage);
+			     CHECKOUT_RESTORE);
 }
diff --git a/builtin/clone.c b/builtin/clone.c
index c990f39..fba3c9c 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -77,7 +77,6 @@ static struct string_list option_required_reference = STRING_LIST_INIT_NODUP;
 static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP;
 static int max_jobs = -1;
 static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP;
-static struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
 static int config_filter_submodules = -1;    /* unspecified */
 static int option_remote_submodules;
 
@@ -617,13 +616,15 @@ static int git_sparse_checkout_init(const char *repo)
 {
 	struct child_process cmd = CHILD_PROCESS_INIT;
 	int result = 0;
+	struct repo_config_values *cfg = repo_config_values(the_repository);
+
 	strvec_pushl(&cmd.args, "-C", repo, "sparse-checkout", "set", NULL);
 
 	/*
 	 * We must apply the setting in the current process
 	 * for the later checkout to use the sparse-checkout file.
 	 */
-	core_apply_sparse_checkout = 1;
+	cfg->apply_sparse_checkout = 1;
 
 	cmd.git_cmd = 1;
 	if (run_command(&cmd)) {
@@ -634,7 +635,9 @@ static int git_sparse_checkout_init(const char *repo)
 	return result;
 }
 
-static int checkout(int submodule_progress, int filter_submodules,
+static int checkout(int submodule_progress,
+		    struct list_objects_filter_options *filter_options,
+		    int filter_submodules,
 		    enum ref_storage_format ref_storage_format)
 {
 	struct object_id oid;
@@ -680,10 +683,10 @@ static int checkout(int submodule_progress, int filter_submodules,
 	opts.dst_index = the_repository->index;
 	init_checkout_metadata(&opts.meta, head, &oid, NULL);
 
-	tree = parse_tree_indirect(&oid);
+	tree = repo_parse_tree_indirect(the_repository, &oid);
 	if (!tree)
 		die(_("unable to parse commit %s"), oid_to_hex(&oid));
-	if (parse_tree(tree) < 0)
+	if (repo_parse_tree(the_repository, tree) < 0)
 		exit(128);
 	init_tree_desc(&t, &tree->object.oid, tree->buffer, tree->size);
 	if (unpack_trees(1, &t, &opts) < 0)
@@ -723,9 +726,9 @@ static int checkout(int submodule_progress, int filter_submodules,
 			strvec_pushf(&cmd.args, "--ref-format=%s",
 				     ref_storage_format_to_name(ref_storage_format));
 
-		if (filter_submodules && filter_options.choice)
+		if (filter_submodules && filter_options->choice)
 			strvec_pushf(&cmd.args, "--filter=%s",
-				     expand_list_objects_filter_spec(&filter_options));
+				     expand_list_objects_filter_spec(filter_options));
 
 		if (option_single_branch >= 0)
 			strvec_push(&cmd.args, option_single_branch ?
@@ -903,6 +906,7 @@ int cmd_clone(int argc,
 	enum transport_family family = TRANSPORT_FAMILY_ALL;
 	struct string_list option_config = STRING_LIST_INIT_DUP;
 	int option_dissociate = 0;
+	struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
 	int option_filter_submodules = -1; /* unspecified */
 	struct string_list server_options = STRING_LIST_INIT_NODUP;
 	const char *bundle_uri = NULL;
@@ -999,6 +1003,8 @@ int cmd_clone(int argc,
 		NULL
 	};
 
+	filter_options.allow_auto_filter = 1;
+
 	packet_trace_identity("clone");
 
 	repo_config(the_repository, git_clone_config, NULL);
@@ -1136,8 +1142,7 @@ int cmd_clone(int argc,
 		int val;
 
 		/* remove duplicates */
-		string_list_sort(&option_recurse_submodules);
-		string_list_remove_duplicates(&option_recurse_submodules, 0);
+		string_list_sort_u(&option_recurse_submodules, 0);
 
 		/*
 		 * NEEDSWORK: In a multi-working-tree world, this needs to be
@@ -1225,12 +1230,7 @@ int cmd_clone(int argc,
 	initialize_repository_version(GIT_HASH_UNKNOWN,
 				      the_repository->ref_storage_format, 1);
 
-	strbuf_addf(&buf, "%s/HEAD", git_dir);
-	write_file(buf.buf, "ref: refs/heads/.invalid");
-
-	strbuf_reset(&buf);
-	strbuf_addf(&buf, "%s/refs", git_dir);
-	safe_create_dir(the_repository, buf.buf, 1);
+	refs_create_refdir_stubs(the_repository, git_dir, NULL);
 
 	/*
 	 * additional config can be injected with -c, make sure it's included
@@ -1442,7 +1442,7 @@ int cmd_clone(int argc,
 	hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));
 	initialize_repository_version(hash_algo, the_repository->ref_storage_format, 1);
 	repo_set_hash_algo(the_repository, hash_algo);
-	create_reference_database(the_repository->ref_storage_format, NULL, 1);
+	create_reference_database(NULL, 1);
 
 	/*
 	 * Before fetching from the remote, download and install bundle
@@ -1617,7 +1617,7 @@ int cmd_clone(int argc,
 	transport_disconnect(transport);
 
 	if (option_dissociate) {
-		close_object_store(the_repository->objects);
+		odb_close(the_repository->objects);
 		dissociate_from_references();
 	}
 
@@ -1625,9 +1625,13 @@ int cmd_clone(int argc,
 		return 1;
 
 	junk_mode = JUNK_LEAVE_REPO;
-	err = checkout(submodule_progress, filter_submodules,
+	err = checkout(submodule_progress,
+		       &filter_options,
+		       filter_submodules,
 		       ref_storage_format);
 
+	list_objects_filter_release(&filter_options);
+
 	string_list_clear(&option_not, 0);
 	string_list_clear(&option_config, 0);
 	string_list_clear(&server_options, 0);
diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
index 5189e68..30535db 100644
--- a/builtin/commit-tree.c
+++ b/builtin/commit-tree.c
@@ -154,7 +154,7 @@ int cmd_commit_tree(int argc,
 	ret = 0;
 
 out:
-	free_commit_list(parents);
+	commit_list_free(parents);
 	strbuf_release(&buffer);
 	return ret;
 }
diff --git a/builtin/commit.c b/builtin/commit.c
index 0243f17..844bdcc 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -327,10 +327,11 @@ static void create_base_index(const struct commit *current_head)
 	opts.dst_index = the_repository->index;
 
 	opts.fn = oneway_merge;
-	tree = parse_tree_indirect(&current_head->object.oid);
+	tree = repo_parse_tree_indirect(the_repository,
+					&current_head->object.oid);
 	if (!tree)
 		die(_("failed to unpack HEAD tree object"));
-	if (parse_tree(tree) < 0)
+	if (repo_parse_tree(the_repository, tree) < 0)
 		exit(128);
 	init_tree_desc(&t, &tree->object.oid, tree->buffer, tree->size);
 	if (unpack_trees(1, &t, &opts))
@@ -455,7 +456,7 @@ static const char *prepare_index(const char **argv, const char *prefix,
 		repo_hold_locked_index(the_repository, &index_lock,
 				       LOCK_DIE_ON_ERROR);
 		add_files_to_cache(the_repository, also ? prefix : NULL,
-				   &pathspec, ps_matched, 0, 0);
+				   &pathspec, ps_matched, 0, 0, 0 );
 		if (!all && report_path_error(ps_matched, &pathspec))
 			exit(128);
 
@@ -815,7 +816,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 				  logfile);
 		hook_arg1 = "message";
 	} else if (use_message) {
-		char *buffer;
+		const char *buffer;
 		buffer = strstr(use_message_buffer, "\n\n");
 		if (buffer)
 			strbuf_addstr(&sb, skip_blank_lines(buffer + 2));
@@ -1154,7 +1155,7 @@ static const char *find_author_by_nickname(const char *name)
 	setup_revisions(ac, av, &revs, NULL);
 	revs.mailmap = xmalloc(sizeof(struct string_list));
 	string_list_init_nodup(revs.mailmap);
-	read_mailmap(revs.mailmap);
+	read_mailmap(the_repository, revs.mailmap);
 
 	if (prepare_revision_walk(&revs))
 		die(_("revision walk setup failed"));
@@ -1849,7 +1850,7 @@ int cmd_commit(int argc,
 	} else if (amend) {
 		if (!reflog_msg)
 			reflog_msg = "commit (amend)";
-		parents = copy_commit_list(current_head->parents);
+		parents = commit_list_copy(current_head->parents);
 	} else if (whence == FROM_MERGE) {
 		struct strbuf m = STRBUF_INIT;
 		FILE *fp;
@@ -1978,7 +1979,7 @@ int cmd_commit(int argc,
 
 cleanup:
 	free_commit_extra_headers(extra);
-	free_commit_list(parents);
+	commit_list_free(parents);
 	strbuf_release(&author_ident);
 	strbuf_release(&err);
 	strbuf_release(&sb);
diff --git a/builtin/config.c b/builtin/config.c
index 75852bd..7c4857b 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -3,6 +3,7 @@
 #include "abspath.h"
 #include "config.h"
 #include "color.h"
+#include "date.h"
 #include "editor.h"
 #include "environment.h"
 #include "gettext.h"
@@ -85,6 +86,17 @@ struct config_location_options {
 	.respect_includes_opt = -1, \
 }
 
+enum config_type {
+	TYPE_NONE = 0,
+	TYPE_BOOL,
+	TYPE_INT,
+	TYPE_BOOL_OR_INT,
+	TYPE_PATH,
+	TYPE_EXPIRY_DATE,
+	TYPE_COLOR,
+	TYPE_BOOL_OR_STR,
+};
+
 #define CONFIG_TYPE_OPTIONS(type) \
 	OPT_GROUP(N_("Type")), \
 	OPT_CALLBACK('t', "type", &type, N_("type"), N_("value is given this type"), option_parse_type), \
@@ -110,7 +122,7 @@ struct config_display_options {
 	int show_origin;
 	int show_scope;
 	int show_keys;
-	int type;
+	enum config_type type;
 	char *default_value;
 	/* Populated via `display_options_init()`. */
 	int term;
@@ -121,16 +133,9 @@ struct config_display_options {
 	.term = '\n', \
 	.delim = '=', \
 	.key_delim = ' ', \
+	.type = TYPE_NONE, \
 }
 
-#define TYPE_BOOL		1
-#define TYPE_INT		2
-#define TYPE_BOOL_OR_INT	3
-#define TYPE_PATH		4
-#define TYPE_EXPIRY_DATE	5
-#define TYPE_COLOR		6
-#define TYPE_BOOL_OR_STR	7
-
 #define OPT_CALLBACK_VALUE(s, l, v, h, i) { \
 	.type = OPTION_CALLBACK, \
 	.short_name = (s), \
@@ -231,95 +236,231 @@ static void show_config_scope(const struct config_display_options *opts,
 	strbuf_addch(buf, term);
 }
 
-static int show_all_config(const char *key_, const char *value_,
-			   const struct config_context *ctx,
-			   void *cb)
-{
-	const struct config_display_options *opts = cb;
-	const struct key_value_info *kvi = ctx->kvi;
-
-	if (opts->show_origin || opts->show_scope) {
-		struct strbuf buf = STRBUF_INIT;
-		if (opts->show_scope)
-			show_config_scope(opts, kvi, &buf);
-		if (opts->show_origin)
-			show_config_origin(opts, kvi, &buf);
-		/* Use fwrite as "buf" can contain \0's if "end_null" is set. */
-		fwrite(buf.buf, 1, buf.len, stdout);
-		strbuf_release(&buf);
-	}
-	if (!opts->omit_values && value_)
-		printf("%s%c%s%c", key_, opts->delim, value_, opts->term);
-	else
-		printf("%s%c", key_, opts->term);
-	return 0;
-}
-
 struct strbuf_list {
 	struct strbuf *items;
 	int nr;
 	int alloc;
 };
 
+static int format_config_int64(struct strbuf *buf,
+			       const char *key_,
+			       const char *value_,
+			       const struct key_value_info *kvi,
+			       int gently)
+{
+	int64_t v = 0;
+	if (gently) {
+		if (!git_parse_int64(value_, &v))
+			return -1;
+	} else {
+		/* may die() */
+		v = git_config_int64(key_, value_ ? value_ : "", kvi);
+	}
+
+	strbuf_addf(buf, "%"PRId64, v);
+	return 0;
+}
+
+static int format_config_bool(struct strbuf *buf,
+			      const char *key_,
+			      const char *value_,
+			      int gently)
+{
+	int v = 0;
+	if (gently) {
+		if ((v = git_parse_maybe_bool(value_)) < 0)
+			return -1;
+	} else {
+		/* may die() */
+		v = git_config_bool(key_, value_);
+	}
+
+	strbuf_addstr(buf, v ? "true" : "false");
+	return 0;
+}
+
+static int format_config_bool_or_int(struct strbuf *buf,
+				     const char *key_,
+				     const char *value_,
+				     const struct key_value_info *kvi,
+				     int gently)
+{
+	int v, is_bool = 0;
+
+	if (gently) {
+		v = git_parse_maybe_bool_text(value_);
+
+		if (v >= 0)
+			is_bool = 1;
+		else if (!git_parse_int(value_, &v))
+			return -1;
+	} else {
+		v = git_config_bool_or_int(key_, value_, kvi,
+					   &is_bool);
+	}
+
+	if (is_bool)
+		strbuf_addstr(buf, v ? "true" : "false");
+	else
+		strbuf_addf(buf, "%d", v);
+
+	return 0;
+}
+
+/* This mode is always gentle. */
+static int format_config_bool_or_str(struct strbuf *buf,
+				     const char *value_)
+{
+	int v = git_parse_maybe_bool(value_);
+	if (v < 0)
+		strbuf_addstr(buf, value_);
+	else
+		strbuf_addstr(buf, v ? "true" : "false");
+	return 0;
+}
+
+static int format_config_path(struct strbuf *buf,
+			      const char *key_,
+			      const char *value_,
+			      int gently)
+{
+	char *v;
+
+	if (git_config_pathname(&v, key_, value_) < 0)
+		return -1;
+
+	if (v)
+		strbuf_addstr(buf, v);
+	else
+		return gently ? -1 : 1; /* :(optional)no-such-file */
+
+	free(v);
+	return 0;
+}
+
+static int format_config_expiry_date(struct strbuf *buf,
+				     const char *key_,
+				     const char *value_,
+				     int quietly)
+{
+	timestamp_t t;
+	if (quietly) {
+		if (parse_expiry_date(value_, &t))
+			return -1;
+	} else if (git_config_expiry_date(&t, key_, value_) < 0) {
+		return -1;
+	}
+
+	strbuf_addf(buf, "%"PRItime, t);
+	return 0;
+}
+
+static int format_config_color(struct strbuf *buf,
+			       const char *key_,
+			       const char *value_,
+			       int gently)
+{
+	char v[COLOR_MAXLEN];
+
+	if (gently) {
+		if (color_parse_quietly(value_, v) < 0)
+			return -1;
+	} else if (git_config_color(v, key_, value_) < 0) {
+		return -1;
+	}
+
+	strbuf_addstr(buf, v);
+	return 0;
+}
+
+/*
+ * Format the configuration key-value pair (`key_`, `value_`) and
+ * append it into strbuf `buf`.  Returns a negative value on failure,
+ * 0 on success, 1 on a missing optional value (i.e., telling the
+ * caller to pretend that <key_,value_> did not exist).
+ *
+ * Note: 'gently' is currently ignored, but will be implemented in
+ * a future change.
+ */
 static int format_config(const struct config_display_options *opts,
 			 struct strbuf *buf, const char *key_,
-			 const char *value_, const struct key_value_info *kvi)
+			 const char *value_, const struct key_value_info *kvi,
+			 int gently)
 {
+	int res = 0;
 	if (opts->show_scope)
 		show_config_scope(opts, kvi, buf);
 	if (opts->show_origin)
 		show_config_origin(opts, kvi, buf);
 	if (opts->show_keys)
 		strbuf_addstr(buf, key_);
-	if (!opts->omit_values) {
-		if (opts->show_keys)
-			strbuf_addch(buf, opts->key_delim);
 
-		if (opts->type == TYPE_INT)
-			strbuf_addf(buf, "%"PRId64,
-				    git_config_int64(key_, value_ ? value_ : "", kvi));
-		else if (opts->type == TYPE_BOOL)
-			strbuf_addstr(buf, git_config_bool(key_, value_) ?
-				      "true" : "false");
-		else if (opts->type == TYPE_BOOL_OR_INT) {
-			int is_bool, v;
-			v = git_config_bool_or_int(key_, value_, kvi,
-						   &is_bool);
-			if (is_bool)
-				strbuf_addstr(buf, v ? "true" : "false");
-			else
-				strbuf_addf(buf, "%d", v);
-		} else if (opts->type == TYPE_BOOL_OR_STR) {
-			int v = git_parse_maybe_bool(value_);
-			if (v < 0)
-				strbuf_addstr(buf, value_);
-			else
-				strbuf_addstr(buf, v ? "true" : "false");
-		} else if (opts->type == TYPE_PATH) {
-			char *v;
-			if (git_config_pathname(&v, key_, value_) < 0)
-				return -1;
-			strbuf_addstr(buf, v);
-			free((char *)v);
-		} else if (opts->type == TYPE_EXPIRY_DATE) {
-			timestamp_t t;
-			if (git_config_expiry_date(&t, key_, value_) < 0)
-				return -1;
-			strbuf_addf(buf, "%"PRItime, t);
-		} else if (opts->type == TYPE_COLOR) {
-			char v[COLOR_MAXLEN];
-			if (git_config_color(v, key_, value_) < 0)
-				return -1;
-			strbuf_addstr(buf, v);
-		} else if (value_) {
+	if (opts->omit_values)
+		goto terminator;
+
+	if (opts->show_keys)
+		strbuf_addch(buf, opts->key_delim);
+
+	switch (opts->type) {
+	case TYPE_INT:
+		res = format_config_int64(buf, key_, value_, kvi, gently);
+		break;
+
+	case TYPE_BOOL:
+		res = format_config_bool(buf, key_, value_, gently);
+		break;
+
+	case TYPE_BOOL_OR_INT:
+		res = format_config_bool_or_int(buf, key_, value_, kvi, gently);
+		break;
+
+	case TYPE_BOOL_OR_STR:
+		res = format_config_bool_or_str(buf, value_);
+		break;
+
+	case TYPE_PATH:
+		res = format_config_path(buf, key_, value_, gently);
+		break;
+
+	case TYPE_EXPIRY_DATE:
+		res = format_config_expiry_date(buf, key_, value_, gently);
+		break;
+
+	case TYPE_COLOR:
+		res = format_config_color(buf, key_, value_, gently);
+		break;
+
+	case TYPE_NONE:
+		if (value_) {
 			strbuf_addstr(buf, value_);
 		} else {
 			/* Just show the key name; back out delimiter */
 			if (opts->show_keys)
 				strbuf_setlen(buf, buf->len - 1);
 		}
+		break;
+
+	default:
+		BUG("undefined type %d", opts->type);
 	}
+
+terminator:
 	strbuf_addch(buf, opts->term);
+	return res;
+}
+
+static int show_all_config(const char *key_, const char *value_,
+			   const struct config_context *ctx,
+			   void *cb)
+{
+	const struct config_display_options *opts = cb;
+	const struct key_value_info *kvi = ctx->kvi;
+	struct strbuf formatted = STRBUF_INIT;
+
+	if (format_config(opts, &formatted, key_, value_, kvi, 1) >= 0)
+		fwrite(formatted.buf, 1, formatted.len, stdout);
+
+	strbuf_release(&formatted);
 	return 0;
 }
 
@@ -344,6 +485,7 @@ static int collect_config(const char *key_, const char *value_,
 	struct collect_config_data *data = cb;
 	struct strbuf_list *values = data->values;
 	const struct key_value_info *kvi = ctx->kvi;
+	int status;
 
 	if (!(data->get_value_flags & GET_VALUE_KEY_REGEXP) &&
 	    strcmp(key_, data->key))
@@ -361,8 +503,15 @@ static int collect_config(const char *key_, const char *value_,
 	ALLOC_GROW(values->items, values->nr + 1, values->alloc);
 	strbuf_init(&values->items[values->nr], 0);
 
-	return format_config(data->display_opts, &values->items[values->nr++],
-			     key_, value_, kvi);
+	status = format_config(data->display_opts, &values->items[values->nr++],
+			       key_, value_, kvi, 0);
+	if (status < 0)
+		return status;
+	if (status) {
+		strbuf_release(&values->items[--values->nr]);
+		status = 0;
+	}
+	return status;
 }
 
 static int get_value(const struct config_location_options *opts,
@@ -438,15 +587,23 @@ static int get_value(const struct config_location_options *opts,
 	if (!values.nr && display_opts->default_value) {
 		struct key_value_info kvi = KVI_INIT;
 		struct strbuf *item;
+		int status;
 
 		kvi_from_param(&kvi);
 		ALLOC_GROW(values.items, values.nr + 1, values.alloc);
 		item = &values.items[values.nr++];
 		strbuf_init(item, 0);
-		if (format_config(display_opts, item, key_,
-				  display_opts->default_value, &kvi) < 0)
+
+		status = format_config(display_opts, item, key_,
+				       display_opts->default_value, &kvi, 0);
+		if (status < 0)
 			die(_("failed to format default config value: %s"),
 			    display_opts->default_value);
+		if (status) {
+			/* default was a missing optional value */
+			values.nr--;
+			strbuf_release(item);
+		}
 	}
 
 	ret = !values.nr;
@@ -714,11 +871,13 @@ static int get_urlmatch(const struct config_location_options *opts,
 	for_each_string_list_item(item, &values) {
 		struct urlmatch_current_candidate_value *matched = item->util;
 		struct strbuf buf = STRBUF_INIT;
+		int status;
 
-		format_config(&display_opts, &buf, item->string,
-			      matched->value_is_null ? NULL : matched->value.buf,
-			      &matched->kvi);
-		fwrite(buf.buf, 1, buf.len, stdout);
+		status = format_config(&display_opts, &buf, item->string,
+				       matched->value_is_null ? NULL : matched->value.buf,
+				       &matched->kvi, 0);
+		if (!status)
+			fwrite(buf.buf, 1, buf.len, stdout);
 		strbuf_release(&buf);
 
 		strbuf_release(&matched->value);
@@ -841,6 +1000,19 @@ static void display_options_init(struct config_display_options *opts)
 	}
 }
 
+static void display_options_init_list(struct config_display_options *opts)
+{
+	opts->show_keys = 1;
+
+	if (opts->end_nul) {
+		display_options_init(opts);
+	} else {
+		opts->term = '\n';
+		opts->delim = ' ';
+		opts->key_delim = '=';
+	}
+}
+
 static int cmd_config_list(int argc, const char **argv, const char *prefix,
 			   struct repository *repo UNUSED)
 {
@@ -859,7 +1031,7 @@ static int cmd_config_list(int argc, const char **argv, const char *prefix,
 	check_argc(argc, 0, 0);
 
 	location_options_init(&location_opts, prefix);
-	display_options_init(&display_opts);
+	display_options_init_list(&display_opts);
 
 	setup_auto_pager("config", 1);
 
@@ -985,7 +1157,7 @@ static int cmd_config_set(int argc, const char **argv, const char *prefix,
 						     argv[0], comment, value);
 		if (ret == CONFIG_NOTHING_SET)
 			error(_("cannot overwrite multiple values with a single value\n"
-			"       Use a regexp, --add or --replace-all to change %s."), argv[0]);
+			"       Use --value=<pattern>, --append or --all to change %s."), argv[0]);
 	}
 
 	location_options_release(&location_opts);
@@ -1003,8 +1175,8 @@ static int cmd_config_unset(int argc, const char **argv, const char *prefix,
 	struct option opts[] = {
 		CONFIG_LOCATION_OPTIONS(location_opts),
 		OPT_GROUP(N_("Filter")),
-		OPT_BIT(0, "all", &flags, N_("replace multi-valued config option with new value"), CONFIG_FLAGS_MULTI_REPLACE),
-		OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("show config with values matching the pattern")),
+		OPT_BIT(0, "all", &flags, N_("unset all multi-valued config options"), CONFIG_FLAGS_MULTI_REPLACE),
+		OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("unset multi-valued config options with matching values")),
 		OPT_BIT(0, "fixed-value", &flags, N_("use string equality when comparing values to value pattern"), CONFIG_FLAGS_FIXED_VALUE),
 		OPT_END(),
 	};
@@ -1290,6 +1462,7 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix)
 
 	if (actions == ACTION_LIST) {
 		check_argc(argc, 0, 0);
+		display_options_init_list(&display_opts);
 		if (config_with_options(show_all_config, &display_opts,
 					&location_opts.source, the_repository,
 					&location_opts.options) < 0) {
diff --git a/builtin/credential-store.c b/builtin/credential-store.c
index b74e06c..bc1453c 100644
--- a/builtin/credential-store.c
+++ b/builtin/credential-store.c
@@ -7,6 +7,7 @@
 #include "path.h"
 #include "string-list.h"
 #include "parse-options.h"
+#include "url.h"
 #include "write-or-die.h"
 
 static struct lock_file credential_lock;
@@ -76,12 +77,6 @@ static void rewrite_credential_file(const char *fn, struct credential *c,
 		die_errno("unable to write credential store");
 }
 
-static int is_rfc3986_unreserved(char ch)
-{
-	return isalnum(ch) ||
-		ch == '-' || ch == '_' || ch == '.' || ch == '~';
-}
-
 static int is_rfc3986_reserved_or_unreserved(char ch)
 {
 	if (is_rfc3986_unreserved(ch))
diff --git a/builtin/describe.c b/builtin/describe.c
index ffaf8d9..bffeed1 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -112,13 +112,13 @@ static int replace_name(struct commit_name *e,
 
 		if (!e->tag) {
 			t = lookup_tag(the_repository, &e->oid);
-			if (!t || parse_tag(t))
+			if (!t || parse_tag(the_repository, t))
 				return 1;
 			e->tag = t;
 		}
 
 		t = lookup_tag(the_repository, oid);
-		if (!t || parse_tag(t))
+		if (!t || parse_tag(the_repository, t))
 			return 0;
 		*tag = t;
 
@@ -154,20 +154,19 @@ static void add_to_known_names(const char *path,
 	}
 }
 
-static int get_name(const char *path, const char *referent UNUSED, const struct object_id *oid,
-		    int flag UNUSED, void *cb_data UNUSED)
+static int get_name(const struct reference *ref, void *cb_data UNUSED)
 {
 	int is_tag = 0;
 	struct object_id peeled;
 	int is_annotated, prio;
 	const char *path_to_match = NULL;
 
-	if (skip_prefix(path, "refs/tags/", &path_to_match)) {
+	if (skip_prefix(ref->name, "refs/tags/", &path_to_match)) {
 		is_tag = 1;
 	} else if (all) {
 		if ((exclude_patterns.nr || patterns.nr) &&
-		    !skip_prefix(path, "refs/heads/", &path_to_match) &&
-		    !skip_prefix(path, "refs/remotes/", &path_to_match)) {
+		    !skip_prefix(ref->name, "refs/heads/", &path_to_match) &&
+		    !skip_prefix(ref->name, "refs/remotes/", &path_to_match)) {
 			/* Only accept reference of known type if there are match/exclude patterns */
 			return 0;
 		}
@@ -209,10 +208,10 @@ static int get_name(const char *path, const char *referent UNUSED, const struct
 	}
 
 	/* Is it annotated? */
-	if (!peel_iterated_oid(the_repository, oid, &peeled)) {
-		is_annotated = !oideq(oid, &peeled);
+	if (!reference_get_peeled_oid(the_repository, ref, &peeled)) {
+		is_annotated = !oideq(ref->oid, &peeled);
 	} else {
-		oidcpy(&peeled, oid);
+		oidcpy(&peeled, ref->oid);
 		is_annotated = 0;
 	}
 
@@ -229,7 +228,8 @@ static int get_name(const char *path, const char *referent UNUSED, const struct
 	else
 		prio = 0;
 
-	add_to_known_names(all ? path + 5 : path + 10, &peeled, prio, oid);
+	add_to_known_names(all ? ref->name + 5 : ref->name + 10,
+			   &peeled, prio, ref->oid);
 	return 0;
 }
 
@@ -335,7 +335,7 @@ static void append_name(struct commit_name *n, struct strbuf *dst)
 {
 	if (n->prio == 2 && !n->tag) {
 		n->tag = lookup_tag(the_repository, &n->oid);
-		if (!n->tag || parse_tag(n->tag))
+		if (!n->tag || parse_tag(the_repository, n->tag))
 			die(_("annotated tag %s not available"), n->path);
 	}
 	if (n->tag && !n->name_checked) {
@@ -558,7 +558,7 @@ static void process_object(struct object *obj, const char *path, void *data)
 			describe_commit(pcd->current_commit, pcd->dst);
 			strbuf_addf(pcd->dst, ":%s", path);
 		}
-		free_commit_list(pcd->revs->commits);
+		commit_list_free(pcd->revs->commits);
 		pcd->revs->commits = NULL;
 	}
 }
@@ -641,6 +641,9 @@ int cmd_describe(int argc,
 		 const char *prefix,
 		 struct repository *repo UNUSED )
 {
+	struct refs_for_each_ref_options for_each_ref_opts = {
+		.flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+	};
 	int contains = 0;
 	struct option options[] = {
 		OPT_BOOL(0, "contains",   &contains, N_("find the tag that comes after the commit")),
@@ -738,8 +741,8 @@ int cmd_describe(int argc,
 	}
 
 	hashmap_init(&names, commit_name_neq, NULL, 0);
-	refs_for_each_rawref(get_main_ref_store(the_repository), get_name,
-			     NULL);
+	refs_for_each_ref_ext(get_main_ref_store(the_repository),
+			      get_name, NULL, &for_each_ref_opts);
 	if (!hashmap_get_size(&names) && !always)
 		die(_("No names found, cannot describe anything."));
 
diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c
index 49dd4d0..8b8f8b5 100644
--- a/builtin/diff-tree.c
+++ b/builtin/diff-tree.c
@@ -33,7 +33,7 @@ static int stdin_diff_commit(struct commit *commit, const char *p)
 		struct commit *parent = lookup_commit(the_repository, &oid);
 		if (!pptr) {
 			/* Free the real parent list */
-			free_commit_list(commit->parents);
+			commit_list_free(commit->parents);
 			commit->parents = NULL;
 			pptr = &(commit->parents);
 		}
@@ -52,7 +52,7 @@ static int stdin_diff_trees(struct tree *tree1, const char *p)
 	if (!isspace(*p++) || parse_oid_hex(p, &oid, &p) || *p)
 		return error("Need exactly two trees, separated by a space");
 	tree2 = lookup_tree(the_repository, &oid);
-	if (!tree2 || parse_tree(tree2))
+	if (!tree2 || repo_parse_tree(the_repository, tree2))
 		return -1;
 	printf("%s %s\n", oid_to_hex(&tree1->object.oid),
 			  oid_to_hex(&tree2->object.oid));
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index 0421360..0c5d238 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -797,10 +797,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
 	       (int)(committer_end - committer), committer);
 	if (signatures.nr) {
 		switch (signed_commit_mode) {
-		case SIGN_ABORT:
-			die(_("encountered signed commit %s; use "
-			      "--signed-commits=<mode> to handle it"),
-			    oid_to_hex(&commit->object.oid));
+		/* Exporting modes */
 		case SIGN_WARN_VERBATIM:
 			warning(_("exporting %"PRIuMAX" signature(s) for commit %s"),
 				(uintmax_t)signatures.nr, oid_to_hex(&commit->object.oid));
@@ -811,12 +808,25 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
 				print_signature(item->string, item->util);
 			}
 			break;
+
+		/* Stripping modes */
 		case SIGN_WARN_STRIP:
 			warning(_("stripping signature(s) from commit %s"),
 				oid_to_hex(&commit->object.oid));
 			/* fallthru */
 		case SIGN_STRIP:
 			break;
+
+		/* Aborting modes */
+		case SIGN_ABORT:
+			die(_("encountered signed commit %s; use "
+			      "--signed-commits=<mode> to handle it"),
+			    oid_to_hex(&commit->object.oid));
+		case SIGN_STRIP_IF_INVALID:
+			die(_("'strip-if-invalid' is not a valid mode for "
+			      "git fast-export with --signed-commits=<mode>"));
+		default:
+			BUG("invalid signed_commit_mode value %d", signed_commit_mode);
 		}
 		string_list_clear(&signatures, 0);
 	}
@@ -935,16 +945,15 @@ static void handle_tag(const char *name, struct tag *tag)
 		size_t sig_offset = parse_signed_buffer(message, message_size);
 		if (sig_offset < message_size)
 			switch (signed_tag_mode) {
-			case SIGN_ABORT:
-				die(_("encountered signed tag %s; use "
-				      "--signed-tags=<mode> to handle it"),
-				    oid_to_hex(&tag->object.oid));
+			/* Exporting modes */
 			case SIGN_WARN_VERBATIM:
 				warning(_("exporting signed tag %s"),
 					oid_to_hex(&tag->object.oid));
 				/* fallthru */
 			case SIGN_VERBATIM:
 				break;
+
+			/* Stripping modes */
 			case SIGN_WARN_STRIP:
 				warning(_("stripping signature from tag %s"),
 					oid_to_hex(&tag->object.oid));
@@ -952,6 +961,17 @@ static void handle_tag(const char *name, struct tag *tag)
 			case SIGN_STRIP:
 				message_size = sig_offset;
 				break;
+
+			/* Aborting modes */
+			case SIGN_ABORT:
+				die(_("encountered signed tag %s; use "
+				      "--signed-tags=<mode> to handle it"),
+				    oid_to_hex(&tag->object.oid));
+			case SIGN_STRIP_IF_INVALID:
+				die(_("'strip-if-invalid' is not a valid mode for "
+				      "git fast-export with --signed-tags=<mode>"));
+			default:
+				BUG("invalid signed_commit_mode value %d", signed_commit_mode);
 			}
 	}
 
@@ -1098,8 +1118,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)
 			free(full_name);
 	}
 
-	string_list_sort(&extra_refs);
-	string_list_remove_duplicates(&extra_refs, 0);
+	string_list_sort_u(&extra_refs, 0);
 }
 
 static void handle_tags_and_duplicates(struct string_list *extras)
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index 7c194e7..a41f951 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -875,6 +875,7 @@ static void end_packfile(void)
 	running = 1;
 	clear_delta_base_cache();
 	if (object_count) {
+		struct odb_source_files *files = odb_source_files_downcast(pack_data->repo->objects->sources);
 		struct packed_git *new_p;
 		struct object_id cur_pack_oid;
 		char *idx_name;
@@ -900,8 +901,7 @@ static void end_packfile(void)
 		idx_name = keep_pack(create_index());
 
 		/* Register the packfile with core git's machinery. */
-		new_p = packfile_store_load_pack(pack_data->repo->objects->packfiles,
-						 idx_name, 1);
+		new_p = packfile_store_load_pack(files->packed, idx_name, 1);
 		if (!new_p)
 			die(_("core Git rejected index %s"), idx_name);
 		all_packs[pack_id] = new_p;
@@ -955,7 +955,7 @@ static int store_object(
 	struct object_id *oidout,
 	uintmax_t mark)
 {
-	struct packfile_store *packs = the_repository->objects->packfiles;
+	struct odb_source *source;
 	void *out, *delta;
 	struct object_entry *e;
 	unsigned char hdr[96];
@@ -979,7 +979,13 @@ static int store_object(
 	if (e->idx.offset) {
 		duplicate_count_by_type[type]++;
 		return 1;
-	} else if (find_oid_pack(&oid, packfile_store_get_packs(packs))) {
+	}
+
+	for (source = the_repository->objects->sources; source; source = source->next) {
+		struct odb_source_files *files = odb_source_files_downcast(source);
+
+		if (!packfile_list_find_oid(packfile_store_get_packs(files->packed), &oid))
+			continue;
 		e->type = type;
 		e->pack_id = MAX_PACK_ID;
 		e->idx.offset = 1; /* just not zero! */
@@ -1096,10 +1102,10 @@ static void truncate_pack(struct hashfile_checkpoint *checkpoint)
 
 static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)
 {
-	struct packfile_store *packs = the_repository->objects->packfiles;
 	size_t in_sz = 64 * 1024, out_sz = 64 * 1024;
 	unsigned char *in_buf = xmalloc(in_sz);
 	unsigned char *out_buf = xmalloc(out_sz);
+	struct odb_source *source;
 	struct object_entry *e;
 	struct object_id oid;
 	unsigned long hdrlen;
@@ -1179,24 +1185,31 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)
 	if (e->idx.offset) {
 		duplicate_count_by_type[OBJ_BLOB]++;
 		truncate_pack(&checkpoint);
+		goto out;
+	}
 
-	} else if (find_oid_pack(&oid, packfile_store_get_packs(packs))) {
+	for (source = the_repository->objects->sources; source; source = source->next) {
+		struct odb_source_files *files = odb_source_files_downcast(source);
+
+		if (!packfile_list_find_oid(packfile_store_get_packs(files->packed), &oid))
+			continue;
 		e->type = OBJ_BLOB;
 		e->pack_id = MAX_PACK_ID;
 		e->idx.offset = 1; /* just not zero! */
 		duplicate_count_by_type[OBJ_BLOB]++;
 		truncate_pack(&checkpoint);
-
-	} else {
-		e->depth = 0;
-		e->type = OBJ_BLOB;
-		e->pack_id = pack_id;
-		e->idx.offset = offset;
-		e->idx.crc32 = crc32_end(pack_file);
-		object_count++;
-		object_count_by_type[OBJ_BLOB]++;
+		goto out;
 	}
 
+	e->depth = 0;
+	e->type = OBJ_BLOB;
+	e->pack_id = pack_id;
+	e->idx.offset = offset;
+	e->idx.crc32 = crc32_end(pack_file);
+	object_count++;
+	object_count_by_type[OBJ_BLOB]++;
+
+out:
 	free(in_buf);
 	free(out_buf);
 }
@@ -2772,7 +2785,7 @@ static void add_gpgsig_to_commit(struct strbuf *commit_data,
 {
 	struct string_list siglines = STRING_LIST_INIT_NODUP;
 
-	if (!sig->hash_algo)
+	if (!sig || !sig->hash_algo)
 		return;
 
 	strbuf_addstr(commit_data, header);
@@ -2815,6 +2828,57 @@ static void import_one_signature(struct signature_data *sig_sha1,
 		die(_("parse_one_signature() returned unknown hash algo"));
 }
 
+static void finalize_commit_buffer(struct strbuf *new_data,
+				   struct signature_data *sig_sha1,
+				   struct signature_data *sig_sha256,
+				   struct strbuf *msg)
+{
+	add_gpgsig_to_commit(new_data, "gpgsig ", sig_sha1);
+	add_gpgsig_to_commit(new_data, "gpgsig-sha256 ", sig_sha256);
+
+	strbuf_addch(new_data, '\n');
+	strbuf_addbuf(new_data, msg);
+}
+
+static void handle_strip_if_invalid(struct strbuf *new_data,
+				    struct signature_data *sig_sha1,
+				    struct signature_data *sig_sha256,
+				    struct strbuf *msg)
+{
+	struct strbuf tmp_buf = STRBUF_INIT;
+	struct signature_check signature_check = { 0 };
+	int ret;
+
+	/* Check signature in a temporary commit buffer */
+	strbuf_addbuf(&tmp_buf, new_data);
+	finalize_commit_buffer(&tmp_buf, sig_sha1, sig_sha256, msg);
+	ret = verify_commit_buffer(tmp_buf.buf, tmp_buf.len, &signature_check);
+
+	if (ret) {
+		const char *signer = signature_check.signer ?
+			signature_check.signer : _("unknown");
+		const char *subject;
+		int subject_len = find_commit_subject(msg->buf, &subject);
+
+		if (subject_len > 100)
+			warning(_("stripping invalid signature for commit '%.100s...'\n"
+				  "  allegedly by %s"), subject, signer);
+		else if (subject_len > 0)
+			warning(_("stripping invalid signature for commit '%.*s'\n"
+				  "  allegedly by %s"), subject_len, subject, signer);
+		else
+			warning(_("stripping invalid signature for commit\n"
+				  "  allegedly by %s"), signer);
+
+		finalize_commit_buffer(new_data, NULL, NULL, msg);
+	} else {
+		strbuf_swap(new_data, &tmp_buf);
+	}
+
+	signature_check_clear(&signature_check);
+	strbuf_release(&tmp_buf);
+}
+
 static void parse_new_commit(const char *arg)
 {
 	static struct strbuf msg = STRBUF_INIT;
@@ -2866,6 +2930,7 @@ static void parse_new_commit(const char *arg)
 			warning(_("importing a commit signature verbatim"));
 			/* fallthru */
 		case SIGN_VERBATIM:
+		case SIGN_STRIP_IF_INVALID:
 			import_one_signature(&sig_sha1, &sig_sha256, v);
 			break;
 
@@ -2950,11 +3015,12 @@ static void parse_new_commit(const char *arg)
 			"encoding %s\n",
 			encoding);
 
-	add_gpgsig_to_commit(&new_data, "gpgsig ", &sig_sha1);
-	add_gpgsig_to_commit(&new_data, "gpgsig-sha256 ", &sig_sha256);
+	if (signed_commit_mode == SIGN_STRIP_IF_INVALID &&
+	    (sig_sha1.hash_algo || sig_sha256.hash_algo))
+		handle_strip_if_invalid(&new_data, &sig_sha1, &sig_sha256, &msg);
+	else
+		finalize_commit_buffer(&new_data, &sig_sha1, &sig_sha256, &msg);
 
-	strbuf_addch(&new_data, '\n');
-	strbuf_addbuf(&new_data, &msg);
 	free(author);
 	free(committer);
 	free(encoding);
@@ -2975,9 +3041,6 @@ static void handle_tag_signature(struct strbuf *msg, const char *name)
 	switch (signed_tag_mode) {
 
 	/* First, modes that don't change anything */
-	case SIGN_ABORT:
-		die(_("encountered signed tag; use "
-		      "--signed-tags=<mode> to handle it"));
 	case SIGN_WARN_VERBATIM:
 		warning(_("importing a tag signature verbatim for tag '%s'"), name);
 		/* fallthru */
@@ -2994,7 +3057,13 @@ static void handle_tag_signature(struct strbuf *msg, const char *name)
 		strbuf_setlen(msg, sig_offset);
 		break;
 
-	/* Third, BUG */
+	/* Third, aborting modes */
+	case SIGN_ABORT:
+		die(_("encountered signed tag; use "
+		      "--signed-tags=<mode> to handle it"));
+	case SIGN_STRIP_IF_INVALID:
+		die(_("'strip-if-invalid' is not a valid mode for "
+		      "git fast-import with --signed-tags=<mode>"));
 	default:
 		BUG("invalid signed_tag_mode value %d from tag '%s'",
 		    signed_tag_mode, name);
diff --git a/builtin/fetch.c b/builtin/fetch.c
index c7ff348..8a36cf6 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -47,7 +47,7 @@
 static const char * const builtin_fetch_usage[] = {
 	N_("git fetch [<options>] [<repository> [<refspec>...]]"),
 	N_("git fetch [<options>] <group>"),
-	N_("git fetch --multiple [<options>] [(<repository> | <group>)...]"),
+	N_("git fetch --multiple [<options>] [(<repository>|<group>)...]"),
 	N_("git fetch --all [<options>]"),
 	NULL
 };
@@ -97,7 +97,6 @@ static struct strbuf default_rla = STRBUF_INIT;
 static struct transport *gtransport;
 static struct transport *gsecondary;
 static struct refspec refmap = REFSPEC_INIT_FETCH;
-static struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
 static struct string_list server_options = STRING_LIST_INIT_DUP;
 static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP;
 
@@ -289,13 +288,11 @@ static struct refname_hash_entry *refname_hash_add(struct hashmap *map,
 	return ent;
 }
 
-static int add_one_refname(const char *refname, const char *referent UNUSED,
-			   const struct object_id *oid,
-			   int flag UNUSED, void *cbdata)
+static int add_one_refname(const struct reference *ref, void *cbdata)
 {
 	struct hashmap *refname_map = cbdata;
 
-	(void) refname_hash_add(refname_map, refname, oid);
+	(void) refname_hash_add(refname_map, ref->name, ref->oid);
 	return 0;
 }
 
@@ -724,7 +721,7 @@ static void display_state_init(struct display_state *display_state, struct ref *
 		display_state->url = xstrdup("foreign");
 
 	display_state->url_len = strlen(display_state->url);
-	for (i = display_state->url_len - 1; display_state->url[i] == '/' && 0 <= i; i--)
+	for (i = display_state->url_len - 1; 0 <= i && display_state->url[i] == '/'; i--)
 		;
 	display_state->url_len = i + 1;
 	if (4 < i && !strncmp(".git", display_state->url + i - 3, 4))
@@ -863,12 +860,87 @@ static void display_ref_update(struct display_state *display_state, char code,
 	fputs(display_state->buf.buf, f);
 }
 
+struct ref_update_display_info {
+	bool failed;
+	char success_code;
+	char fail_code;
+	char *summary;
+	char *fail_detail;
+	char *success_detail;
+	char *ref;
+	char *remote;
+	struct object_id old_oid;
+	struct object_id new_oid;
+};
+
+struct ref_update_display_info_array {
+	struct ref_update_display_info *info;
+	size_t alloc, nr;
+};
+
+static struct ref_update_display_info *ref_update_display_info_append(
+					   struct ref_update_display_info_array *array,
+					   char success_code,
+					   char fail_code,
+					   const char *summary,
+					   const char *success_detail,
+					   const char *fail_detail,
+					   const char *ref,
+					   const char *remote,
+					   const struct object_id *old_oid,
+					   const struct object_id *new_oid)
+{
+	struct ref_update_display_info *info;
+
+	ALLOC_GROW(array->info, array->nr + 1, array->alloc);
+	info = &array->info[array->nr++];
+
+	info->failed = false;
+	info->success_code = success_code;
+	info->fail_code = fail_code;
+	info->summary = xstrdup(summary);
+	info->success_detail = xstrdup_or_null(success_detail);
+	info->fail_detail = xstrdup_or_null(fail_detail);
+	info->remote = xstrdup(remote);
+	info->ref = xstrdup(ref);
+
+	oidcpy(&info->old_oid, old_oid);
+	oidcpy(&info->new_oid, new_oid);
+
+	return info;
+}
+
+static void ref_update_display_info_set_failed(struct ref_update_display_info *info)
+{
+	info->failed = true;
+}
+
+static void ref_update_display_info_free(struct ref_update_display_info *info)
+{
+	free(info->summary);
+	free(info->success_detail);
+	free(info->fail_detail);
+	free(info->remote);
+	free(info->ref);
+}
+
+static void ref_update_display_info_display(struct ref_update_display_info *info,
+					    struct display_state *display_state,
+					    int summary_width)
+{
+	display_ref_update(display_state,
+			   info->failed ? info->fail_code : info->success_code,
+			   info->summary,
+			   info->failed ? info->fail_detail : info->success_detail,
+			   info->remote, info->ref, &info->old_oid,
+			   &info->new_oid, summary_width);
+}
+
 static int update_local_ref(struct ref *ref,
 			    struct ref_transaction *transaction,
-			    struct display_state *display_state,
 			    const struct ref *remote_ref,
-			    int summary_width,
-			    const struct fetch_config *config)
+			    const struct fetch_config *config,
+			    struct ref_update_display_info_array *display_array)
 {
 	struct commit *current = NULL, *updated;
 	int fast_forward = 0;
@@ -879,41 +951,56 @@ static int update_local_ref(struct ref *ref,
 
 	if (oideq(&ref->old_oid, &ref->new_oid)) {
 		if (verbosity > 0)
-			display_ref_update(display_state, '=', _("[up to date]"), NULL,
-					   remote_ref->name, ref->name,
-					   &ref->old_oid, &ref->new_oid, summary_width);
+			ref_update_display_info_append(display_array, '=', '=',
+						       _("[up to date]"), NULL,
+						       NULL, ref->name,
+						       remote_ref->name, &ref->old_oid,
+						       &ref->new_oid);
 		return 0;
 	}
 
 	if (!update_head_ok &&
 	    !is_null_oid(&ref->old_oid) &&
 	    branch_checked_out(ref->name)) {
+		struct ref_update_display_info *info;
 		/*
 		 * If this is the head, and it's not okay to update
 		 * the head, and the old value of the head isn't empty...
 		 */
-		display_ref_update(display_state, '!', _("[rejected]"),
-				   _("can't fetch into checked-out branch"),
-				   remote_ref->name, ref->name,
-				   &ref->old_oid, &ref->new_oid, summary_width);
+		info = ref_update_display_info_append(display_array, '!', '!',
+						      _("[rejected]"), NULL,
+						      _("can't fetch into checked-out branch"),
+						      ref->name, remote_ref->name,
+						      &ref->old_oid, &ref->new_oid);
+		ref_update_display_info_set_failed(info);
 		return 1;
 	}
 
 	if (!is_null_oid(&ref->old_oid) &&
 	    starts_with(ref->name, "refs/tags/")) {
+		struct ref_update_display_info *info;
+
 		if (force || ref->force) {
 			int r;
+
 			r = s_update_ref("updating tag", ref, transaction, 0);
-			display_ref_update(display_state, r ? '!' : 't', _("[tag update]"),
-					   r ? _("unable to update local ref") : NULL,
-					   remote_ref->name, ref->name,
-					   &ref->old_oid, &ref->new_oid, summary_width);
+
+			info = ref_update_display_info_append(display_array, 't', '!',
+							      _("[tag update]"), NULL,
+							      _("unable to update local ref"),
+							      ref->name, remote_ref->name,
+							      &ref->old_oid, &ref->new_oid);
+			if (r)
+				ref_update_display_info_set_failed(info);
+
 			return r;
 		} else {
-			display_ref_update(display_state, '!', _("[rejected]"),
-					   _("would clobber existing tag"),
-					   remote_ref->name, ref->name,
-					   &ref->old_oid, &ref->new_oid, summary_width);
+			info = ref_update_display_info_append(display_array, '!', '!',
+							      _("[rejected]"), NULL,
+							      _("would clobber existing tag"),
+							      ref->name, remote_ref->name,
+							      &ref->old_oid, &ref->new_oid);
+			ref_update_display_info_set_failed(info);
 			return 1;
 		}
 	}
@@ -923,6 +1010,7 @@ static int update_local_ref(struct ref *ref,
 	updated = lookup_commit_reference_gently(the_repository,
 						 &ref->new_oid, 1);
 	if (!current || !updated) {
+		struct ref_update_display_info *info;
 		const char *msg;
 		const char *what;
 		int r;
@@ -943,10 +1031,15 @@ static int update_local_ref(struct ref *ref,
 		}
 
 		r = s_update_ref(msg, ref, transaction, 0);
-		display_ref_update(display_state, r ? '!' : '*', what,
-				   r ? _("unable to update local ref") : NULL,
-				   remote_ref->name, ref->name,
-				   &ref->old_oid, &ref->new_oid, summary_width);
+
+		info = ref_update_display_info_append(display_array, '*', '!',
+						      what, NULL,
+						      _("unable to update local ref"),
+						      ref->name, remote_ref->name,
+						      &ref->old_oid, &ref->new_oid);
+		if (r)
+			ref_update_display_info_set_failed(info);
+
 		return r;
 	}
 
@@ -962,6 +1055,7 @@ static int update_local_ref(struct ref *ref,
 	}
 
 	if (fast_forward) {
+		struct ref_update_display_info *info;
 		struct strbuf quickref = STRBUF_INIT;
 		int r;
 
@@ -969,29 +1063,46 @@ static int update_local_ref(struct ref *ref,
 		strbuf_addstr(&quickref, "..");
 		strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV);
 		r = s_update_ref("fast-forward", ref, transaction, 1);
-		display_ref_update(display_state, r ? '!' : ' ', quickref.buf,
-				   r ? _("unable to update local ref") : NULL,
-				   remote_ref->name, ref->name,
-				   &ref->old_oid, &ref->new_oid, summary_width);
+
+		info = ref_update_display_info_append(display_array, ' ', '!',
+						      quickref.buf, NULL,
+						      _("unable to update local ref"),
+						      ref->name, remote_ref->name,
+						      &ref->old_oid, &ref->new_oid);
+		if (r)
+			ref_update_display_info_set_failed(info);
+
 		strbuf_release(&quickref);
 		return r;
 	} else if (force || ref->force) {
+		struct ref_update_display_info *info;
 		struct strbuf quickref = STRBUF_INIT;
 		int r;
+
 		strbuf_add_unique_abbrev(&quickref, &current->object.oid, DEFAULT_ABBREV);
 		strbuf_addstr(&quickref, "...");
 		strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV);
 		r = s_update_ref("forced-update", ref, transaction, 1);
-		display_ref_update(display_state, r ? '!' : '+', quickref.buf,
-				   r ? _("unable to update local ref") : _("forced update"),
-				   remote_ref->name, ref->name,
-				   &ref->old_oid, &ref->new_oid, summary_width);
+
+		info = ref_update_display_info_append(display_array, '+', '!',
+						      quickref.buf, _("forced update"),
+						      _("unable to update local ref"),
+						      ref->name, remote_ref->name,
+						      &ref->old_oid, &ref->new_oid);
+
+		if (r)
+			ref_update_display_info_set_failed(info);
+
 		strbuf_release(&quickref);
 		return r;
 	} else {
-		display_ref_update(display_state, '!', _("[rejected]"), _("non-fast-forward"),
-				   remote_ref->name, ref->name,
-				   &ref->old_oid, &ref->new_oid, summary_width);
+		struct ref_update_display_info *info;
+		info = ref_update_display_info_append(display_array, '!', '!',
+						      _("[rejected]"), NULL,
+						      _("non-fast-forward"),
+						      ref->name, remote_ref->name,
+						      &ref->old_oid, &ref->new_oid);
+		ref_update_display_info_set_failed(info);
 		return 1;
 	}
 }
@@ -1105,17 +1216,14 @@ static int store_updated_refs(struct display_state *display_state,
 			      int connectivity_checked,
 			      struct ref_transaction *transaction, struct ref *ref_map,
 			      struct fetch_head *fetch_head,
-			      const struct fetch_config *config)
+			      const struct fetch_config *config,
+			      struct ref_update_display_info_array *display_array)
 {
 	int rc = 0;
 	struct strbuf note = STRBUF_INIT;
 	const char *what, *kind;
 	struct ref *rm;
 	int want_status;
-	int summary_width = 0;
-
-	if (verbosity >= 0)
-		summary_width = transport_summary_width(ref_map);
 
 	if (!connectivity_checked) {
 		struct check_connected_options opt = CHECK_CONNECTED_INIT;
@@ -1220,8 +1328,8 @@ static int store_updated_refs(struct display_state *display_state,
 					  display_state->url_len);
 
 			if (ref) {
-				rc |= update_local_ref(ref, transaction, display_state,
-						       rm, summary_width, config);
+				rc |= update_local_ref(ref, transaction, rm,
+						       config, display_array);
 				free(ref);
 			} else if (write_fetch_head || dry_run) {
 				/*
@@ -1229,12 +1337,12 @@ static int store_updated_refs(struct display_state *display_state,
 				 * would be written to FETCH_HEAD, if --dry-run
 				 * is set).
 				 */
-				display_ref_update(display_state, '*',
-						   *kind ? kind : "branch", NULL,
-						   rm->name,
-						   "FETCH_HEAD",
-						   &rm->new_oid, &rm->old_oid,
-						   summary_width);
+
+				ref_update_display_info_append(display_array, '*', '*',
+							       *kind ? kind : "branch",
+							       NULL, NULL, "FETCH_HEAD",
+							       rm->name, &rm->new_oid,
+							       &rm->old_oid);
 			}
 		}
 	}
@@ -1302,7 +1410,8 @@ static int fetch_and_consume_refs(struct display_state *display_state,
 				  struct ref_transaction *transaction,
 				  struct ref *ref_map,
 				  struct fetch_head *fetch_head,
-				  const struct fetch_config *config)
+				  const struct fetch_config *config,
+				  struct ref_update_display_info_array *display_array)
 {
 	int connectivity_checked = 1;
 	int ret;
@@ -1324,7 +1433,8 @@ static int fetch_and_consume_refs(struct display_state *display_state,
 
 	trace2_region_enter("fetch", "consume_refs", the_repository);
 	ret = store_updated_refs(display_state, connectivity_checked,
-				 transaction, ref_map, fetch_head, config);
+				 transaction, ref_map, fetch_head, config,
+				 display_array);
 	trace2_region_leave("fetch", "consume_refs", the_repository);
 
 out:
@@ -1416,14 +1526,11 @@ static void set_option(struct transport *transport, const char *name, const char
 }
 
 
-static int add_oid(const char *refname UNUSED,
-		   const char *referent UNUSED,
-		   const struct object_id *oid,
-		   int flags UNUSED, void *cb_data)
+static int add_oid(const struct reference *ref, void *cb_data)
 {
 	struct oid_array *oids = cb_data;
 
-	oid_array_append(oids, oid);
+	oid_array_append(oids, ref->oid);
 	return 0;
 }
 
@@ -1434,6 +1541,9 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
 
 	for (i = 0; i < negotiation_tip.nr; i++) {
 		const char *s = negotiation_tip.items[i].string;
+		struct refs_for_each_ref_options opts = {
+			.pattern = s,
+		};
 		int old_nr;
 		if (!has_glob_specials(s)) {
 			struct object_id oid;
@@ -1445,8 +1555,8 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
 			continue;
 		}
 		old_nr = oids->nr;
-		refs_for_each_glob_ref(get_main_ref_store(the_repository),
-				       add_oid, s, oids);
+		refs_for_each_ref_ext(get_main_ref_store(the_repository),
+				      add_oid, oids, &opts);
 		if (old_nr == oids->nr)
 			warning("ignoring --negotiation-tip=%s because it does not match any refs",
 				s);
@@ -1454,7 +1564,8 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
 	smart_options->negotiation_tips = oids;
 }
 
-static struct transport *prepare_transport(struct remote *remote, int deepen)
+static struct transport *prepare_transport(struct remote *remote, int deepen,
+					   struct list_objects_filter_options *filter_options)
 {
 	struct transport *transport;
 
@@ -1478,9 +1589,9 @@ static struct transport *prepare_transport(struct remote *remote, int deepen)
 		set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
 	if (refetch)
 		set_option(transport, TRANS_OPT_REFETCH, "yes");
-	if (filter_options.choice) {
+	if (filter_options->choice) {
 		const char *spec =
-			expand_list_objects_filter_spec(&filter_options);
+			expand_list_objects_filter_spec(filter_options);
 		set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, spec);
 		set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
 	}
@@ -1498,7 +1609,9 @@ static int backfill_tags(struct display_state *display_state,
 			 struct ref_transaction *transaction,
 			 struct ref *ref_map,
 			 struct fetch_head *fetch_head,
-			 const struct fetch_config *config)
+			 const struct fetch_config *config,
+			 struct ref_update_display_info_array *display_array,
+			 struct list_objects_filter_options *filter_options)
 {
 	int retcode, cannot_reuse;
 
@@ -1512,7 +1625,7 @@ static int backfill_tags(struct display_state *display_state,
 	cannot_reuse = transport->cannot_reuse ||
 		deepen_since || deepen_not.nr;
 	if (cannot_reuse) {
-		gsecondary = prepare_transport(transport->remote, 0);
+		gsecondary = prepare_transport(transport->remote, 0, filter_options);
 		transport = gsecondary;
 	}
 
@@ -1520,7 +1633,7 @@ static int backfill_tags(struct display_state *display_state,
 	transport_set_option(transport, TRANS_OPT_DEPTH, "0");
 	transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL);
 	retcode = fetch_and_consume_refs(display_state, transport, transaction, ref_map,
-					 fetch_head, config);
+					 fetch_head, config, display_array);
 
 	if (gsecondary) {
 		transport_disconnect(gsecondary);
@@ -1646,6 +1759,7 @@ struct ref_rejection_data {
 	bool conflict_msg_shown;
 	bool case_sensitive_msg_shown;
 	const char *remote_name;
+	struct strmap *rejected_refs;
 };
 
 static void ref_transaction_rejection_handler(const char *refname,
@@ -1654,6 +1768,7 @@ static void ref_transaction_rejection_handler(const char *refname,
 					      const char *old_target UNUSED,
 					      const char *new_target UNUSED,
 					      enum ref_transaction_error err,
+					      const char *details,
 					      void *cb_data)
 {
 	struct ref_rejection_data *data = cb_data;
@@ -1678,17 +1793,53 @@ static void ref_transaction_rejection_handler(const char *refname,
 			"branches"), data->remote_name);
 		data->conflict_msg_shown = true;
 	} else {
-		const char *reason = ref_transaction_error_msg(err);
-
-		error(_("fetching ref %s failed: %s"), refname, reason);
+		if (details)
+			error("%s", details);
+		else
+			error(_("fetching ref %s failed: %s"),
+			      refname, ref_transaction_error_msg(err));
 	}
 
+	strmap_put(data->rejected_refs, refname, NULL);
 	*data->retcode = 1;
 }
 
+/*
+ * Commit the reference transaction. If it isn't an atomic transaction, handle
+ * rejected updates as part of using batched updates.
+ */
+static int commit_ref_transaction(struct ref_transaction **transaction,
+				  bool is_atomic, const char *remote_name,
+				  struct strmap *rejected_refs,
+				  struct strbuf *err)
+{
+	int retcode = ref_transaction_commit(*transaction, err);
+	if (retcode)
+		goto out;
+
+	if (!is_atomic) {
+		struct ref_rejection_data data = {
+			.conflict_msg_shown = 0,
+			.remote_name = remote_name,
+			.retcode = &retcode,
+			.rejected_refs = rejected_refs,
+		};
+
+		ref_transaction_for_each_rejected_update(*transaction,
+							 ref_transaction_rejection_handler,
+							 &data);
+	}
+
+out:
+	ref_transaction_free(*transaction);
+	*transaction = NULL;
+	return retcode;
+}
+
 static int do_fetch(struct transport *transport,
 		    struct refspec *rs,
-		    const struct fetch_config *config)
+		    const struct fetch_config *config,
+		    struct list_objects_filter_options *filter_options)
 {
 	struct ref_transaction *transaction = NULL;
 	struct ref *ref_map = NULL;
@@ -1701,6 +1852,9 @@ static int do_fetch(struct transport *transport,
 	struct fetch_head fetch_head = { 0 };
 	struct strbuf err = STRBUF_INIT;
 	int do_set_head = 0;
+	struct ref_update_display_info_array display_array = { 0 };
+	struct strmap rejected_refs = STRMAP_INIT;
+	int summary_width = 0;
 
 	if (tags == TAGS_DEFAULT) {
 		if (transport->remote->fetch_tags == 2)
@@ -1825,7 +1979,7 @@ static int do_fetch(struct transport *transport,
 	}
 
 	if (fetch_and_consume_refs(&display_state, transport, transaction, ref_map,
-				   &fetch_head, config)) {
+				   &fetch_head, config, &display_array)) {
 		retcode = 1;
 		goto cleanup;
 	}
@@ -1848,7 +2002,7 @@ static int do_fetch(struct transport *transport,
 			 * the transaction and don't commit anything.
 			 */
 			if (backfill_tags(&display_state, transport, transaction, tags_ref_map,
-					  &fetch_head, config))
+					  &fetch_head, config, &display_array, filter_options))
 				retcode = 1;
 		}
 
@@ -1858,33 +2012,18 @@ static int do_fetch(struct transport *transport,
 	if (retcode)
 		goto cleanup;
 
-	retcode = ref_transaction_commit(transaction, &err);
-	if (retcode) {
-		/*
-		 * Explicitly handle transaction cleanup to avoid
-		 * aborting an already closed transaction.
-		 */
-		ref_transaction_free(transaction);
-		transaction = NULL;
+	if (verbosity >= 0)
+		summary_width = transport_summary_width(ref_map);
+
+	retcode = commit_ref_transaction(&transaction, atomic_fetch,
+					 transport->remote->name,
+					 &rejected_refs, &err);
+	/*
+	 * With '--atomic', bail out if the transaction fails. Without '--atomic',
+	 * continue to fetch head and perform other post-fetch operations.
+	 */
+	if (retcode && atomic_fetch)
 		goto cleanup;
-	}
-
-	if (!atomic_fetch) {
-		struct ref_rejection_data data = {
-			.retcode = &retcode,
-			.conflict_msg_shown = 0,
-			.remote_name = transport->remote->name,
-		};
-
-		ref_transaction_for_each_rejected_update(transaction,
-							 ref_transaction_rejection_handler,
-							 &data);
-		if (retcode) {
-			ref_transaction_free(transaction);
-			transaction = NULL;
-			goto cleanup;
-		}
-	}
 
 	commit_fetch_head(&fetch_head);
 
@@ -1950,6 +2089,24 @@ static int do_fetch(struct transport *transport,
 	}
 
 cleanup:
+	/*
+	 * When using batched updates, we want to commit the non-rejected
+	 * updates and also handle the rejections.
+	 */
+	if (retcode && !atomic_fetch && transaction)
+		commit_ref_transaction(&transaction, false,
+				       transport->remote->name,
+				       &rejected_refs, &err);
+
+	for (size_t i = 0; i < display_array.nr; i++) {
+		struct ref_update_display_info *info = &display_array.info[i];
+
+		if (!info->failed && strmap_contains(&rejected_refs, info->ref))
+			ref_update_display_info_set_failed(info);
+		ref_update_display_info_display(info, &display_state, summary_width);
+		ref_update_display_info_free(info);
+	}
+
 	if (retcode) {
 		if (err.len) {
 			error("%s", err.buf);
@@ -1963,6 +2120,9 @@ static int do_fetch(struct transport *transport,
 
 	if (transaction)
 		ref_transaction_free(transaction);
+
+	free(display_array.info);
+	strmap_clear(&rejected_refs, 0);
 	display_state_release(&display_state);
 	close_fetch_head(&fetch_head);
 	strbuf_release(&err);
@@ -2184,20 +2344,21 @@ static int fetch_multiple(struct string_list *list, int max_children,
  * Fetching from the promisor remote should use the given filter-spec
  * or inherit the default filter-spec from the config.
  */
-static inline void fetch_one_setup_partial(struct remote *remote)
+static inline void fetch_one_setup_partial(struct remote *remote,
+					   struct list_objects_filter_options *filter_options)
 {
 	/*
 	 * Explicit --no-filter argument overrides everything, regardless
 	 * of any prior partial clones and fetches.
 	 */
-	if (filter_options.no_filter)
+	if (filter_options->no_filter)
 		return;
 
 	/*
 	 * If no prior partial clone/fetch and the current fetch DID NOT
 	 * request a partial-fetch, do a normal fetch.
 	 */
-	if (!repo_has_promisor_remote(the_repository) && !filter_options.choice)
+	if (!repo_has_promisor_remote(the_repository) && !filter_options->choice)
 		return;
 
 	/*
@@ -2206,8 +2367,8 @@ static inline void fetch_one_setup_partial(struct remote *remote)
 	 * filter-spec as the default for subsequent fetches to this
 	 * remote if there is currently no default filter-spec.
 	 */
-	if (filter_options.choice) {
-		partial_clone_register(remote->name, &filter_options);
+	if (filter_options->choice) {
+		partial_clone_register(remote->name, filter_options);
 		return;
 	}
 
@@ -2216,14 +2377,15 @@ static inline void fetch_one_setup_partial(struct remote *remote)
 	 * explicitly given filter-spec or inherit the filter-spec from
 	 * the config.
 	 */
-	if (!filter_options.choice)
-		partial_clone_get_default_filter_spec(&filter_options, remote->name);
+	if (!filter_options->choice)
+		partial_clone_get_default_filter_spec(filter_options, remote->name);
 	return;
 }
 
 static int fetch_one(struct remote *remote, int argc, const char **argv,
 		     int prune_tags_ok, int use_stdin_refspecs,
-		     const struct fetch_config *config)
+		     const struct fetch_config *config,
+		     struct list_objects_filter_options *filter_options)
 {
 	struct refspec rs = REFSPEC_INIT_FETCH;
 	int i;
@@ -2235,7 +2397,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
 		die(_("no remote repository specified; please specify either a URL or a\n"
 		      "remote name from which new revisions should be fetched"));
 
-	gtransport = prepare_transport(remote, 1);
+	gtransport = prepare_transport(remote, 1, filter_options);
 
 	if (prune < 0) {
 		/* no command line request */
@@ -2290,7 +2452,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
 	sigchain_push_common(unlock_pack_on_signal);
 	atexit(unlock_pack_atexit);
 	sigchain_push(SIGPIPE, SIG_IGN);
-	exit_code = do_fetch(gtransport, &rs, config);
+	exit_code = do_fetch(gtransport, &rs, config, filter_options);
 	sigchain_pop(SIGPIPE);
 	refspec_clear(&rs);
 	transport_disconnect(gtransport);
@@ -2315,6 +2477,7 @@ int cmd_fetch(int argc,
 	const char *submodule_prefix = "";
 	const char *bundle_uri;
 	struct string_list list = STRING_LIST_INIT_DUP;
+	struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
 	struct remote *remote = NULL;
 	int all = -1, multiple = 0;
 	int result = 0;
@@ -2420,6 +2583,8 @@ int cmd_fetch(int argc,
 		OPT_END()
 	};
 
+	filter_options.allow_auto_filter = 1;
+
 	packet_trace_identity("fetch");
 
 	/* Record the command line for the reflog */
@@ -2580,7 +2745,7 @@ int cmd_fetch(int argc,
 		trace2_region_enter("fetch", "negotiate-only", the_repository);
 		if (!remote)
 			die(_("must supply remote when using --negotiate-only"));
-		gtransport = prepare_transport(remote, 1);
+		gtransport = prepare_transport(remote, 1, &filter_options);
 		if (gtransport->smart_options) {
 			gtransport->smart_options->acked_commits = &acked_commits;
 		} else {
@@ -2602,12 +2767,12 @@ int cmd_fetch(int argc,
 	} else if (remote) {
 		if (filter_options.choice || repo_has_promisor_remote(the_repository)) {
 			trace2_region_enter("fetch", "setup-partial", the_repository);
-			fetch_one_setup_partial(remote);
+			fetch_one_setup_partial(remote, &filter_options);
 			trace2_region_leave("fetch", "setup-partial", the_repository);
 		}
 		trace2_region_enter("fetch", "fetch-one", the_repository);
 		result = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs,
-				   &config);
+				   &config, &filter_options);
 		trace2_region_leave("fetch", "fetch-one", the_repository);
 	} else {
 		int max_children = max_jobs;
@@ -2713,5 +2878,6 @@ int cmd_fetch(int argc,
 
  cleanup:
 	string_list_clear(&list, 0);
+	list_objects_filter_release(&filter_options);
 	return result;
 }
diff --git a/builtin/for-each-repo.c b/builtin/for-each-repo.c
index 325a792..927d3d9 100644
--- a/builtin/for-each-repo.c
+++ b/builtin/for-each-repo.c
@@ -2,6 +2,7 @@
 
 #include "builtin.h"
 #include "config.h"
+#include "environment.h"
 #include "gettext.h"
 #include "parse-options.h"
 #include "path.h"
@@ -13,17 +14,16 @@ static const char * const for_each_repo_usage[] = {
 	NULL
 };
 
-static int run_command_on_repo(const char *path, int argc, const char ** argv)
+static int run_command_on_repo(const char *path, const char **argv)
 {
-	int i;
 	struct child_process child = CHILD_PROCESS_INIT;
 	char *abspath = interpolate_path(path, 0);
 
+	sanitize_repo_env(&child.env);
+
 	child.git_cmd = 1;
 	strvec_pushl(&child.args, "-C", abspath, NULL);
-
-	for (i = 0; i < argc; i++)
-		strvec_push(&child.args, argv[i]);
+	strvec_pushv(&child.args, argv);
 
 	free(abspath);
 
@@ -63,7 +63,7 @@ int cmd_for_each_repo(int argc,
 		return 0;
 
 	for (size_t i = 0; i < values->nr; i++) {
-		int ret = run_command_on_repo(values->items[i].string, argc, argv);
+		int ret = run_command_on_repo(values->items[i].string, argv);
 		if (ret) {
 			if (!keep_going)
 					return ret;
diff --git a/builtin/fsck.c b/builtin/fsck.c
index b1a650c..9bab32e 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -13,11 +13,11 @@
 #include "fsck.h"
 #include "parse-options.h"
 #include "progress.h"
-#include "streaming.h"
 #include "packfile.h"
 #include "object-file.h"
 #include "object-name.h"
 #include "odb.h"
+#include "odb/streaming.h"
 #include "path.h"
 #include "read-cache-ll.h"
 #include "replace-object.h"
@@ -51,6 +51,7 @@ static int show_progress = -1;
 static int show_dangling = 1;
 static int name_objects;
 static int check_references = 1;
+static timestamp_t now;
 #define ERROR_OBJECT 01
 #define ERROR_REACHABLE 02
 #define ERROR_PACK 04
@@ -161,7 +162,8 @@ static int mark_object(struct object *obj, enum object_type type,
 		return 0;
 
 	if (!(obj->flags & HAS_OBJ)) {
-		if (parent && !odb_has_object(the_repository->objects, &obj->oid, 1)) {
+		if (parent && !odb_has_object(the_repository->objects, &obj->oid,
+					      HAS_OBJECT_RECHECK_PACKED)) {
 			printf_ln(_("broken link from %7s %s\n"
 				    "              to %7s %s"),
 				  printable_type(&parent->oid, parent->type),
@@ -218,15 +220,17 @@ static int mark_used(struct object *obj, enum object_type type UNUSED,
 	return 0;
 }
 
-static void mark_unreachable_referents(const struct object_id *oid)
+static int mark_unreachable_referents(const struct object_id *oid,
+				      struct object_info *oi UNUSED,
+				      void *data UNUSED)
 {
 	struct fsck_options options = FSCK_OPTIONS_DEFAULT;
 	struct object *obj = lookup_object(the_repository, oid);
 
 	if (!obj || !(obj->flags & HAS_OBJ))
-		return; /* not part of our original set */
+		return 0; /* not part of our original set */
 	if (obj->flags & REACHABLE)
-		return; /* reachable objects already traversed */
+		return 0; /* reachable objects already traversed */
 
 	/*
 	 * Avoid passing OBJ_NONE to fsck_walk, which will parse the object
@@ -243,22 +247,7 @@ static void mark_unreachable_referents(const struct object_id *oid)
 	fsck_walk(obj, NULL, &options);
 	if (obj->type == OBJ_TREE)
 		free_tree_buffer((struct tree *)obj);
-}
 
-static int mark_loose_unreachable_referents(const struct object_id *oid,
-					    const char *path UNUSED,
-					    void *data UNUSED)
-{
-	mark_unreachable_referents(oid);
-	return 0;
-}
-
-static int mark_packed_unreachable_referents(const struct object_id *oid,
-					     struct packed_git *pack UNUSED,
-					     uint32_t pos UNUSED,
-					     void *data UNUSED)
-{
-	mark_unreachable_referents(oid);
 	return 0;
 }
 
@@ -340,7 +329,8 @@ static void check_unreachable_object(struct object *obj)
 			}
 			f = xfopen(filename, "w");
 			if (obj->type == OBJ_BLOB) {
-				if (stream_blob_to_fd(fileno(f), &obj->oid, NULL, 1))
+				if (odb_stream_blob_to_fd(the_repository->objects, fileno(f),
+							  &obj->oid, NULL, 1))
 					die_errno(_("could not write '%s'"), filename);
 			} else
 				fprintf(f, "%s\n", describe_object(&obj->oid));
@@ -393,12 +383,8 @@ static void check_connectivity(void)
 		 * and ignore any that weren't present in our earlier
 		 * traversal.
 		 */
-		for_each_loose_object(the_repository->objects,
-				      mark_loose_unreachable_referents, NULL, 0);
-		for_each_packed_object(the_repository,
-				       mark_packed_unreachable_referents,
-				       NULL,
-				       0);
+		odb_for_each_object(the_repository->objects, NULL,
+				    mark_unreachable_referents, NULL, 0);
 	}
 
 	/* Look up all the requirements, warn about missing objects.. */
@@ -509,6 +495,9 @@ static int fsck_handle_reflog_ent(const char *refname,
 				  timestamp_t timestamp, int tz UNUSED,
 				  const char *message UNUSED, void *cb_data UNUSED)
 {
+	if (now && timestamp > now)
+		return 0;
+
 	if (verbose)
 		fprintf_ln(stderr, _("Checking reflog %s->%s"),
 			   oid_to_hex(ooid), oid_to_hex(noid));
@@ -530,14 +519,27 @@ static int fsck_handle_reflog(const char *logname, void *cb_data)
 	return 0;
 }
 
-static int fsck_handle_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-			   int flag UNUSED, void *cb_data UNUSED)
+struct ref_snapshot {
+	char *refname;
+	struct object_id oid;
+	/* TODO: Maybe supplement with latest reflog entry info too? */
+};
+
+struct snapshot {
+	size_t nr;
+	size_t alloc;
+	struct ref_snapshot *ref;
+	/* TODO: Consider also snapshotting the index of each worktree. */
+};
+
+static int snapshot_ref(const struct reference *ref, void *cb_data)
 {
+	struct snapshot *snap = cb_data;
 	struct object *obj;
 
-	obj = parse_object(the_repository, oid);
+	obj = parse_object(the_repository, ref->oid);
 	if (!obj) {
-		if (is_promisor_object(the_repository, oid)) {
+		if (is_promisor_object(the_repository, ref->oid)) {
 			/*
 			 * Increment default_refs anyway, because this is a
 			 * valid ref.
@@ -546,54 +548,134 @@ static int fsck_handle_ref(const char *refname, const char *referent UNUSED, con
 			 return 0;
 		}
 		error(_("%s: invalid sha1 pointer %s"),
-		      refname, oid_to_hex(oid));
+		      ref->name, oid_to_hex(ref->oid));
 		errors_found |= ERROR_REACHABLE;
 		/* We'll continue with the rest despite the error.. */
 		return 0;
 	}
-	if (obj->type != OBJ_COMMIT && is_branch(refname)) {
-		error(_("%s: not a commit"), refname);
+	if (obj->type != OBJ_COMMIT && is_branch(ref->name)) {
+		error(_("%s: not a commit"), ref->name);
 		errors_found |= ERROR_REFS;
 	}
 	default_refs++;
+
+	ALLOC_GROW(snap->ref, snap->nr + 1, snap->alloc);
+	snap->ref[snap->nr].refname = xstrdup(ref->name);
+	oidcpy(&snap->ref[snap->nr].oid, ref->oid);
+	snap->nr++;
+
+	return 0;
+}
+
+static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED)
+{
+	struct object *obj;
+
+	obj = parse_object(the_repository, ref->oid);
 	obj->flags |= USED;
 	fsck_put_object_name(&fsck_walk_options,
-			     oid, "%s", refname);
+			     ref->oid, "%s", ref->name);
 	mark_object_reachable(obj);
 
 	return 0;
 }
 
-static int fsck_head_link(const char *head_ref_name,
-			  const char **head_points_at,
-			  struct object_id *head_oid);
-
-static void get_default_heads(void)
+static void snapshot_refs(struct snapshot *snap, int argc, const char **argv)
 {
+	struct refs_for_each_ref_options opts = {
+		.flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+	};
 	struct worktree **worktrees, **p;
 	const char *head_points_at;
 	struct object_id head_oid;
 
-	refs_for_each_rawref(get_main_ref_store(the_repository),
-			     fsck_handle_ref, NULL);
+	for (int i = 0; i < argc; i++) {
+		const char *arg = argv[i];
+		struct object_id oid;
+		if (!repo_get_oid(the_repository, arg, &oid)) {
+			struct reference ref = {
+				.name = arg,
+				.oid = &oid,
+			};
+
+			snapshot_ref(&ref, snap);
+			continue;
+		}
+		error(_("invalid parameter: expected sha1, got '%s'"), arg);
+		errors_found |= ERROR_OBJECT;
+	}
+
+	if (argc) {
+		include_reflogs = 0;
+		return;
+	}
+
+	refs_for_each_ref_ext(get_main_ref_store(the_repository),
+			      snapshot_ref, snap, &opts);
 
 	worktrees = get_worktrees();
 	for (p = worktrees; *p; p++) {
 		struct worktree *wt = *p;
-		struct strbuf ref = STRBUF_INIT;
+		struct strbuf refname = STRBUF_INIT;
 
-		strbuf_worktree_ref(wt, &ref, "HEAD");
-		fsck_head_link(ref.buf, &head_points_at, &head_oid);
-		if (head_points_at && !is_null_oid(&head_oid))
-			fsck_handle_ref(ref.buf, NULL, &head_oid, 0, NULL);
-		strbuf_release(&ref);
+		strbuf_worktree_ref(wt, &refname, "HEAD");
 
-		if (include_reflogs)
-			refs_for_each_reflog(get_worktree_ref_store(wt),
-					     fsck_handle_reflog, wt);
+		head_points_at = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+							 refname.buf, 0, &head_oid, NULL);
+
+		if (head_points_at && !is_null_oid(&head_oid)) {
+			struct reference ref = {
+				.name = refname.buf,
+				.oid = &head_oid,
+			};
+
+			snapshot_ref(&ref, snap);
+		}
+		strbuf_release(&refname);
+
+		/*
+		 * TODO: Could use refs_for_each_reflog(...) to find
+		 * latest entry instead of using a global 'now' for that
+		 * purpose.
+		 */
 	}
 	free_worktrees(worktrees);
 
+	/* Ignore reflogs newer than now */
+	now = time(NULL);
+}
+
+
+static void free_snapshot_refs(struct snapshot *snap)
+{
+	for (size_t i = 0; i < snap->nr; i++)
+		free(snap->ref[i].refname);
+	free(snap->ref);
+}
+
+static void process_refs(struct snapshot *snap)
+{
+	struct worktree **worktrees, **p;
+
+	for (size_t i = 0; i < snap->nr; i++) {
+		struct reference ref = {
+			.name = snap->ref[i].refname,
+			.oid = &snap->ref[i].oid,
+		};
+		fsck_handle_ref(&ref, NULL);
+	}
+
+	if (include_reflogs) {
+		worktrees = get_worktrees();
+		for (p = worktrees; *p; p++) {
+			struct worktree *wt = *p;
+
+			refs_for_each_reflog(get_worktree_ref_store(wt),
+					     fsck_handle_reflog, wt);
+		}
+		free_worktrees(worktrees);
+	}
+
 	/*
 	 * Not having any default heads isn't really fatal, but
 	 * it does mean that "--unreachable" no longer makes any
@@ -707,43 +789,6 @@ static void fsck_source(struct odb_source *source)
 	stop_progress(&progress);
 }
 
-static int fsck_head_link(const char *head_ref_name,
-			  const char **head_points_at,
-			  struct object_id *head_oid)
-{
-	int null_is_error = 0;
-
-	if (verbose)
-		fprintf_ln(stderr, _("Checking %s link"), head_ref_name);
-
-	*head_points_at = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
-						  head_ref_name, 0, head_oid,
-						  NULL);
-	if (!*head_points_at) {
-		errors_found |= ERROR_REFS;
-		return error(_("invalid %s"), head_ref_name);
-	}
-	if (!strcmp(*head_points_at, head_ref_name))
-		/* detached HEAD */
-		null_is_error = 1;
-	else if (!starts_with(*head_points_at, "refs/heads/")) {
-		errors_found |= ERROR_REFS;
-		return error(_("%s points to something strange (%s)"),
-			     head_ref_name, *head_points_at);
-	}
-	if (is_null_oid(head_oid)) {
-		if (null_is_error) {
-			errors_found |= ERROR_REFS;
-			return error(_("%s: detached HEAD points at nothing"),
-				     head_ref_name);
-		}
-		fprintf_ln(stderr,
-			   _("notice: %s points to an unborn branch (%s)"),
-			   head_ref_name, *head_points_at + 11);
-	}
-	return 0;
-}
-
 static int fsck_cache_tree(struct cache_tree *it, const char *index_path)
 {
 	int i;
@@ -842,26 +887,12 @@ static void fsck_index(struct index_state *istate, const char *index_path,
 	fsck_resolve_undo(istate, index_path);
 }
 
-static void mark_object_for_connectivity(const struct object_id *oid)
+static int mark_object_for_connectivity(const struct object_id *oid,
+					struct object_info *oi UNUSED,
+					void *cb_data UNUSED)
 {
 	struct object *obj = lookup_unknown_object(the_repository, oid);
 	obj->flags |= HAS_OBJ;
-}
-
-static int mark_loose_for_connectivity(const struct object_id *oid,
-				       const char *path UNUSED,
-				       void *data UNUSED)
-{
-	mark_object_for_connectivity(oid);
-	return 0;
-}
-
-static int mark_packed_for_connectivity(const struct object_id *oid,
-					struct packed_git *pack UNUSED,
-					uint32_t pos UNUSED,
-					void *data UNUSED)
-{
-	mark_object_for_connectivity(oid);
 	return 0;
 }
 
@@ -957,8 +988,12 @@ int cmd_fsck(int argc,
 	     const char *prefix,
 	     struct repository *repo UNUSED)
 {
-	int i;
 	struct odb_source *source;
+	struct snapshot snap = {
+		.nr = 0,
+		.alloc = 0,
+		.ref = NULL
+	};
 
 	/* fsck knows how to handle missing promisor objects */
 	fetch_if_missing = 0;
@@ -994,11 +1029,20 @@ int cmd_fsck(int argc,
 	if (check_references)
 		fsck_refs(the_repository);
 
+	/*
+	 * Take a snapshot of the refs before walking objects to avoid looking
+	 * at a set of refs that may be changed by the user while we are walking
+	 * objects. We can still walk over new objects that are added during the
+	 * execution of fsck but won't miss any objects that were reachable.
+	 */
+	snapshot_refs(&snap, argc, argv);
+
+	/* Ensure we get a "fresh" view of the odb */
+	odb_reprepare(the_repository->objects);
+
 	if (connectivity_only) {
-		for_each_loose_object(the_repository->objects,
-				      mark_loose_for_connectivity, NULL, 0);
-		for_each_packed_object(the_repository,
-				       mark_packed_for_connectivity, NULL, 0);
+		odb_for_each_object(the_repository->objects, NULL,
+				    mark_object_for_connectivity, NULL, 0);
 	} else {
 		odb_prepare_alternates(the_repository->objects);
 		for (source = the_repository->objects->sources; source; source = source->next)
@@ -1035,42 +1079,18 @@ int cmd_fsck(int argc,
 			errors_found |= ERROR_OBJECT;
 	}
 
-	for (i = 0; i < argc; i++) {
-		const char *arg = argv[i];
-		struct object_id oid;
-		if (!repo_get_oid(the_repository, arg, &oid)) {
-			struct object *obj = lookup_object(the_repository,
-							   &oid);
+	/* Process the snapshotted refs and the reflogs. */
+	process_refs(&snap);
 
-			if (!obj || !(obj->flags & HAS_OBJ)) {
-				if (is_promisor_object(the_repository, &oid))
-					continue;
-				error(_("%s: object missing"), oid_to_hex(&oid));
-				errors_found |= ERROR_OBJECT;
-				continue;
-			}
-
-			obj->flags |= USED;
-			fsck_put_object_name(&fsck_walk_options, &oid,
-					     "%s", arg);
-			mark_object_reachable(obj);
-			continue;
-		}
-		error(_("invalid parameter: expected sha1, got '%s'"), arg);
-		errors_found |= ERROR_OBJECT;
-	}
-
-	/*
-	 * If we've not been given any explicit head information, do the
-	 * default ones from .git/refs. We also consider the index file
-	 * in this case (ie this implies --cache).
-	 */
-	if (!argc) {
-		get_default_heads();
+	/* If not given any explicit objects, process index files too. */
+	if (!argc)
 		keep_cache_objects = 1;
-	}
-
 	if (keep_cache_objects) {
+		/*
+		 * TODO: Consider first walking these indexes in snapshot_refs,
+		 * to snapshot where the index entries used to point, and then
+		 * check those snapshotted locations here.
+		 */
 		struct worktree **worktrees, **p;
 
 		verify_index_checksum = 1;
@@ -1088,7 +1108,7 @@ int cmd_fsck(int argc,
 			 * and may get overwritten by other calls
 			 * while we're examining the index.
 			 */
-			path = xstrdup(worktree_git_path(the_repository, wt, "index"));
+			path = xstrdup(worktree_git_path(wt, "index"));
 			wt_gitdir = get_worktree_git_dir(wt);
 
 			read_index_from(&istate, path, wt_gitdir);
@@ -1143,5 +1163,6 @@ int cmd_fsck(int argc,
 		}
 	}
 
+	free_snapshot_refs(&snap);
 	return errors_found;
 }
diff --git a/builtin/gc.c b/builtin/gc.c
index d212cbb..fb329c2 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -36,6 +36,7 @@
 #include "reflog.h"
 #include "repack.h"
 #include "rerere.h"
+#include "revision.h"
 #include "blob.h"
 #include "tree.h"
 #include "promisor-remote.h"
@@ -286,12 +287,26 @@ static void maintenance_run_opts_release(struct maintenance_run_opts *opts)
 
 static int pack_refs_condition(UNUSED struct gc_config *cfg)
 {
-	/*
-	 * The auto-repacking logic for refs is handled by the ref backends and
-	 * exposed via `git pack-refs --auto`. We thus always return truish
-	 * here and let the backend decide for us.
-	 */
-	return 1;
+	struct string_list included_refs = STRING_LIST_INIT_NODUP;
+	struct ref_exclusions excludes = REF_EXCLUSIONS_INIT;
+	struct refs_optimize_opts optimize_opts = {
+		.exclusions = &excludes,
+		.includes = &included_refs,
+		.flags = REFS_OPTIMIZE_PRUNE | REFS_OPTIMIZE_AUTO,
+	};
+	bool required;
+
+	/* Check for all refs, similar to 'git refs optimize --all'. */
+	string_list_append(optimize_opts.includes, "*");
+
+	if (refs_optimize_required(get_main_ref_store(the_repository),
+				   &optimize_opts, &required))
+		return 0;
+
+	clear_ref_exclusions(&excludes);
+	string_list_clear(&included_refs, 0);
+
+	return required;
 }
 
 static int maintenance_task_pack_refs(struct maintenance_run_opts *opts,
@@ -1048,7 +1063,7 @@ int cmd_gc(int argc,
 	report_garbage = report_pack_garbage;
 	odb_reprepare(the_repository->objects);
 	if (pack_garbage.nr > 0) {
-		close_object_store(the_repository->objects);
+		odb_close(the_repository->objects);
 		clean_pack_garbage();
 	}
 
@@ -1095,34 +1110,30 @@ static int maintenance_opt_schedule(const struct option *opt, const char *arg,
 	return 0;
 }
 
-/* Remember to update object flag allocation in object.h */
-#define SEEN		(1u<<0)
-
 struct cg_auto_data {
 	int num_not_in_graph;
 	int limit;
 };
 
-static int dfs_on_ref(const char *refname UNUSED,
-		      const char *referent UNUSED,
-		      const struct object_id *oid,
-		      int flags UNUSED,
-		      void *cb_data)
+static int dfs_on_ref(const struct reference *ref, void *cb_data)
 {
 	struct cg_auto_data *data = (struct cg_auto_data *)cb_data;
 	int result = 0;
+	const struct object_id *maybe_peeled = ref->oid;
 	struct object_id peeled;
 	struct commit_list *stack = NULL;
 	struct commit *commit;
 
-	if (!peel_iterated_oid(the_repository, oid, &peeled))
-		oid = &peeled;
-	if (odb_read_object_info(the_repository->objects, oid, NULL) != OBJ_COMMIT)
+	if (!reference_get_peeled_oid(the_repository, ref, &peeled))
+		maybe_peeled = &peeled;
+	if (odb_read_object_info(the_repository->objects, maybe_peeled, NULL) != OBJ_COMMIT)
 		return 0;
 
-	commit = lookup_commit(the_repository, oid);
-	if (!commit)
+	commit = lookup_commit(the_repository, maybe_peeled);
+	if (!commit || commit->object.flags & SEEN)
 		return 0;
+	commit->object.flags |= SEEN;
+
 	if (repo_parse_commit(the_repository, commit) ||
 	    commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH)
 		return 0;
@@ -1132,7 +1143,7 @@ static int dfs_on_ref(const char *refname UNUSED,
 	if (data->num_not_in_graph >= data->limit)
 		return 1;
 
-	commit_list_append(commit, &stack);
+	commit_list_insert(commit, &stack);
 
 	while (!result && stack) {
 		struct commit_list *parent;
@@ -1153,11 +1164,11 @@ static int dfs_on_ref(const char *refname UNUSED,
 				break;
 			}
 
-			commit_list_append(parent->item, &stack);
+			commit_list_insert(parent->item, &stack);
 		}
 	}
 
-	free_commit_list(stack);
+	commit_list_free(stack);
 	return result;
 }
 
@@ -1969,7 +1980,7 @@ static void initialize_task_config(struct maintenance_run_opts *opts,
 		strategy = none_strategy;
 		type = MAINTENANCE_TYPE_SCHEDULED;
 	} else {
-		strategy = gc_strategy;
+		strategy = geometric_strategy;
 		type = MAINTENANCE_TYPE_MANUAL;
 	}
 
@@ -3447,7 +3458,67 @@ static int maintenance_stop(int argc, const char **argv, const char *prefix,
 	return update_background_schedule(NULL, 0);
 }
 
-static const char * const builtin_maintenance_usage[] = {
+static const char *const builtin_maintenance_is_needed_usage[] = {
+	"git maintenance is-needed [--task=<task>] [--schedule]",
+	NULL
+};
+
+static int maintenance_is_needed(int argc, const char **argv, const char *prefix,
+				 struct repository *repo UNUSED)
+{
+	struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
+	struct string_list selected_tasks = STRING_LIST_INIT_DUP;
+	struct gc_config cfg = GC_CONFIG_INIT;
+	struct option options[] = {
+		OPT_BOOL(0, "auto", &opts.auto_flag,
+			 N_("run tasks based on the state of the repository")),
+		OPT_CALLBACK_F(0, "task", &selected_tasks, N_("task"),
+			       N_("check a specific task"),
+			       PARSE_OPT_NONEG, task_option_parse),
+		OPT_END()
+	};
+	bool is_needed = false;
+
+	argc = parse_options(argc, argv, prefix, options,
+			     builtin_maintenance_is_needed_usage,
+			     PARSE_OPT_STOP_AT_NON_OPTION);
+	if (argc)
+		usage_with_options(builtin_maintenance_is_needed_usage, options);
+
+	gc_config(&cfg);
+	initialize_task_config(&opts, &selected_tasks);
+
+	if (opts.auto_flag) {
+		for (size_t i = 0; i < opts.tasks_nr; i++) {
+			if (tasks[opts.tasks[i]].auto_condition &&
+			    tasks[opts.tasks[i]].auto_condition(&cfg)) {
+				is_needed = true;
+				break;
+			}
+		}
+	} else {
+		/*
+		 * When not using --auto we always require maintenance right now.
+		 *
+		 * TODO: this certainly is too eager, as some maintenance tasks may
+		 * decide to not do anything because the data structures are already
+		 * fully optimized. We may eventually want to extend the auto
+		 * condition to also cover non-auto runs so that we can detect such
+		 * cases.
+		 */
+		is_needed = true;
+	}
+
+	string_list_clear(&selected_tasks, 0);
+	maintenance_run_opts_release(&opts);
+	gc_config_release(&cfg);
+
+	if (is_needed)
+		return 0;
+	return 1;
+}
+
+static const char *const builtin_maintenance_usage[] = {
 	N_("git maintenance <subcommand> [<options>]"),
 	NULL,
 };
@@ -3464,6 +3535,7 @@ int cmd_maintenance(int argc,
 		OPT_SUBCOMMAND("stop", &fn, maintenance_stop),
 		OPT_SUBCOMMAND("register", &fn, maintenance_register),
 		OPT_SUBCOMMAND("unregister", &fn, maintenance_unregister),
+		OPT_SUBCOMMAND("is-needed", &fn, maintenance_is_needed),
 		OPT_END(),
 	};
 
diff --git a/builtin/grep.c b/builtin/grep.c
index 53cccf2..e33285e 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -482,7 +482,7 @@ static int grep_submodule(struct grep_opt *opt,
 	 *	"forget" the sparse-index feature switch. As a result, the index
 	 *	of these submodules are expanded unexpectedly.
 	 *
-	 * 2. "core_apply_sparse_checkout"
+	 * 2. "config_values_private_.apply_sparse_checkout"
 	 *	When running `grep` in the superproject, this setting is
 	 *	populated using the superproject's configs. However, once
 	 *	initialized, this config is globally accessible and is read by
@@ -1213,8 +1213,16 @@ int cmd_grep(int argc,
 		 */
 		if (recurse_submodules)
 			repo_read_gitmodules(the_repository, 1);
-		if (startup_info->have_repository)
-			packfile_store_prepare(the_repository->objects->packfiles);
+
+		if (startup_info->have_repository) {
+			struct odb_source *source;
+
+			odb_prepare_alternates(the_repository->objects);
+			for (source = the_repository->objects->sources; source; source = source->next) {
+				struct odb_source_files *files = odb_source_files_downcast(source);
+				packfile_store_prepare(files->packed);
+			}
+		}
 
 		start_threads(&opt);
 	} else {
diff --git a/builtin/help.c b/builtin/help.c
index c09cbc8..86a3d03 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -54,6 +54,7 @@ static enum help_action {
 	HELP_ACTION_DEVELOPER_INTERFACES,
 	HELP_ACTION_CONFIG_FOR_COMPLETION,
 	HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION,
+	HELP_ACTION_ALIASES_FOR_COMPLETION,
 } cmd_mode;
 
 static char *html_path;
@@ -90,6 +91,8 @@ static struct option builtin_help_options[] = {
 		    HELP_ACTION_CONFIG_FOR_COMPLETION, PARSE_OPT_HIDDEN),
 	OPT_CMDMODE_F(0, "config-sections-for-completion", &cmd_mode, "",
 		    HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, PARSE_OPT_HIDDEN),
+	OPT_CMDMODE_F(0, "aliases-for-completion", &cmd_mode, "",
+		    HELP_ACTION_ALIASES_FOR_COMPLETION, PARSE_OPT_HIDDEN),
 
 	OPT_END(),
 };
@@ -691,6 +694,16 @@ int cmd_help(int argc,
 			       help_format);
 		list_config_help(SHOW_CONFIG_SECTIONS);
 		return 0;
+	case HELP_ACTION_ALIASES_FOR_COMPLETION: {
+		struct string_list alias_list = STRING_LIST_INIT_DUP;
+		opt_mode_usage(argc, "--aliases-for-completion", help_format);
+		list_aliases(&alias_list);
+		for (size_t i = 0; i < alias_list.nr; i++)
+			printf("%s%c%s%c", alias_list.items[i].string, '\n',
+			       (char *)alias_list.items[i].util, '\0');
+		string_list_clear(&alias_list, 1);
+		return 0;
+	}
 	case HELP_ACTION_CONFIG:
 		opt_mode_usage(argc, "--config", help_format);
 		setup_pager(the_repository);
diff --git a/builtin/history.c b/builtin/history.c
new file mode 100644
index 0000000..88822a1
--- /dev/null
+++ b/builtin/history.c
@@ -0,0 +1,491 @@
+#define USE_THE_REPOSITORY_VARIABLE
+
+#include "builtin.h"
+#include "commit.h"
+#include "commit-reach.h"
+#include "config.h"
+#include "editor.h"
+#include "environment.h"
+#include "gettext.h"
+#include "hex.h"
+#include "parse-options.h"
+#include "refs.h"
+#include "replay.h"
+#include "revision.h"
+#include "sequencer.h"
+#include "strvec.h"
+#include "tree.h"
+#include "wt-status.h"
+
+#define GIT_HISTORY_REWORD_USAGE \
+	N_("git history reword <commit> [--dry-run] [--update-refs=(branches|head)]")
+
+static void change_data_free(void *util, const char *str UNUSED)
+{
+	struct wt_status_change_data *d = util;
+	free(d->rename_source);
+	free(d);
+}
+
+static int fill_commit_message(struct repository *repo,
+			       const struct object_id *old_tree,
+			       const struct object_id *new_tree,
+			       const char *default_message,
+			       const char *action,
+			       struct strbuf *out)
+{
+	const char *path = git_path_commit_editmsg();
+	const char *hint =
+		_("Please enter the commit message for the %s changes."
+		  " Lines starting\nwith '%s' will be ignored, and an"
+		  " empty message aborts the commit.\n");
+	struct wt_status s;
+
+	strbuf_addstr(out, default_message);
+	strbuf_addch(out, '\n');
+	strbuf_commented_addf(out, comment_line_str, hint, action, comment_line_str);
+	write_file_buf(path, out->buf, out->len);
+
+	wt_status_prepare(repo, &s);
+	FREE_AND_NULL(s.branch);
+	s.ahead_behind_flags = AHEAD_BEHIND_QUICK;
+	s.commit_template = 1;
+	s.colopts = 0;
+	s.display_comment_prefix = 1;
+	s.hints = 0;
+	s.use_color = 0;
+	s.whence = FROM_COMMIT;
+	s.committable = 1;
+
+	s.fp = fopen(git_path_commit_editmsg(), "a");
+	if (!s.fp)
+		return error_errno(_("could not open '%s'"), git_path_commit_editmsg());
+
+	wt_status_collect_changes_trees(&s, old_tree, new_tree);
+	wt_status_print(&s);
+	wt_status_collect_free_buffers(&s);
+	string_list_clear_func(&s.change, change_data_free);
+
+	strbuf_reset(out);
+	if (launch_editor(path, out, NULL)) {
+		fprintf(stderr, _("Aborting commit as launching the editor failed.\n"));
+		return -1;
+	}
+	strbuf_stripspace(out, comment_line_str);
+
+	cleanup_message(out, COMMIT_MSG_CLEANUP_ALL, 0);
+
+	if (!out->len) {
+		fprintf(stderr, _("Aborting commit due to empty commit message.\n"));
+		return -1;
+	}
+
+	return 0;
+}
+
+static int commit_tree_with_edited_message(struct repository *repo,
+					   const char *action,
+					   struct commit *original,
+					   struct commit **out)
+{
+	const char *exclude_gpgsig[] = {
+		/* We reencode the message, so the encoding needs to be stripped. */
+		"encoding",
+		/* We need to strip signatures as those will become invalid. */
+		"gpgsig",
+		"gpgsig-sha256",
+		NULL,
+	};
+	const char *original_message, *original_body, *ptr;
+	struct commit_extra_header *original_extra_headers = NULL;
+	struct strbuf commit_message = STRBUF_INIT;
+	struct object_id rewritten_commit_oid;
+	struct object_id original_tree_oid;
+	struct object_id parent_tree_oid;
+	char *original_author = NULL;
+	struct commit *parent;
+	size_t len;
+	int ret;
+
+	original_tree_oid = repo_get_commit_tree(repo, original)->object.oid;
+
+	parent = original->parents ? original->parents->item : NULL;
+	if (parent) {
+		if (repo_parse_commit(repo, parent)) {
+			ret = error(_("unable to parse parent commit %s"),
+				    oid_to_hex(&parent->object.oid));
+			goto out;
+		}
+
+		parent_tree_oid = repo_get_commit_tree(repo, parent)->object.oid;
+	} else {
+		oidcpy(&parent_tree_oid, repo->hash_algo->empty_tree);
+	}
+
+	/* We retain authorship of the original commit. */
+	original_message = repo_logmsg_reencode(repo, original, NULL, NULL);
+	ptr = find_commit_header(original_message, "author", &len);
+	if (ptr)
+		original_author = xmemdupz(ptr, len);
+	find_commit_subject(original_message, &original_body);
+
+	ret = fill_commit_message(repo, &parent_tree_oid, &original_tree_oid,
+				  original_body, action, &commit_message);
+	if (ret < 0)
+		goto out;
+
+	original_extra_headers = read_commit_extra_headers(original, exclude_gpgsig);
+
+	ret = commit_tree_extended(commit_message.buf, commit_message.len, &original_tree_oid,
+				   original->parents, &rewritten_commit_oid, original_author,
+				   NULL, NULL, original_extra_headers);
+	if (ret < 0)
+		goto out;
+
+	*out = lookup_commit_or_die(&rewritten_commit_oid, "rewritten commit");
+
+out:
+	free_commit_extra_headers(original_extra_headers);
+	strbuf_release(&commit_message);
+	free(original_author);
+	return ret;
+}
+
+enum ref_action {
+	REF_ACTION_DEFAULT,
+	REF_ACTION_BRANCHES,
+	REF_ACTION_HEAD,
+};
+
+static int parse_ref_action(const struct option *opt, const char *value, int unset)
+{
+	enum ref_action *action = opt->value;
+
+	BUG_ON_OPT_NEG_NOARG(unset, value);
+	if (!strcmp(value, "branches")) {
+		*action = REF_ACTION_BRANCHES;
+	} else if (!strcmp(value, "head")) {
+		*action = REF_ACTION_HEAD;
+	} else {
+		return error(_("%s expects one of 'branches' or 'head'"),
+			     opt->long_name);
+	}
+
+	return 0;
+}
+
+static int revwalk_contains_merges(struct repository *repo,
+				   const struct strvec *revwalk_args)
+{
+	struct strvec args = STRVEC_INIT;
+	struct rev_info revs;
+	int ret;
+
+	strvec_pushv(&args, revwalk_args->v);
+	strvec_push(&args, "--min-parents=2");
+
+	repo_init_revisions(repo, &revs, NULL);
+
+	setup_revisions_from_strvec(&args, &revs, NULL);
+	if (args.nr != 1)
+		BUG("revisions were set up with invalid argument");
+
+	if (prepare_revision_walk(&revs) < 0) {
+		ret = error(_("error preparing revisions"));
+		goto out;
+	}
+
+	if (get_revision(&revs)) {
+		ret = error(_("replaying merge commits is not supported yet!"));
+		goto out;
+	}
+
+	reset_revision_walk();
+	ret = 0;
+
+out:
+	release_revisions(&revs);
+	strvec_clear(&args);
+	return ret;
+}
+
+static int setup_revwalk(struct repository *repo,
+			 enum ref_action action,
+			 struct commit *original,
+			 struct rev_info *revs)
+{
+	struct strvec args = STRVEC_INIT;
+	int ret;
+
+	repo_init_revisions(repo, revs, NULL);
+	strvec_push(&args, "ignored");
+	strvec_push(&args, "--reverse");
+	strvec_push(&args, "--topo-order");
+	strvec_push(&args, "--full-history");
+
+	/* We only want to see commits that are descendants of the old commit. */
+	strvec_pushf(&args, "--ancestry-path=%s",
+		     oid_to_hex(&original->object.oid));
+
+	/*
+	 * Ancestry path may also show ancestors of the old commit, but we
+	 * don't want to see those, either.
+	 */
+	strvec_pushf(&args, "^%s", oid_to_hex(&original->object.oid));
+
+	/*
+	 * When we're asked to update HEAD we need to verify that the commit
+	 * that we want to rewrite is actually an ancestor of it and, if so,
+	 * update it. Otherwise we'll update (or print) all descendant
+	 * branches.
+	 */
+	if (action == REF_ACTION_HEAD) {
+		struct commit_list *from_list = NULL;
+		struct commit *head;
+
+		head = lookup_commit_reference_by_name("HEAD");
+		if (!head) {
+			ret = error(_("cannot look up HEAD"));
+			goto out;
+		}
+
+		commit_list_insert(original, &from_list);
+		ret = repo_is_descendant_of(repo, head, from_list);
+		free_commit_list(from_list);
+
+		if (ret < 0) {
+			ret = error(_("cannot determine descendance"));
+			goto out;
+		} else if (!ret) {
+			ret = error(_("rewritten commit must be an ancestor "
+				      "of HEAD when using --update-refs=head"));
+			goto out;
+		}
+
+		strvec_push(&args, "HEAD");
+	} else {
+		strvec_push(&args, "--branches");
+		strvec_push(&args, "HEAD");
+	}
+
+	ret = revwalk_contains_merges(repo, &args);
+	if (ret < 0)
+		goto out;
+
+	setup_revisions_from_strvec(&args, revs, NULL);
+	if (args.nr != 1)
+		BUG("revisions were set up with invalid argument");
+
+	ret = 0;
+
+out:
+	strvec_clear(&args);
+	return ret;
+}
+
+static int handle_ref_update(struct ref_transaction *transaction,
+			     const char *refname,
+			     const struct object_id *new_oid,
+			     const struct object_id *old_oid,
+			     const char *reflog_msg,
+			     struct strbuf *err)
+{
+	if (!transaction) {
+		printf("update %s %s %s\n",
+		       refname, oid_to_hex(new_oid), oid_to_hex(old_oid));
+		return 0;
+	}
+
+	return ref_transaction_update(transaction, refname, new_oid, old_oid,
+				      NULL, NULL, 0, reflog_msg, err);
+}
+
+static int handle_reference_updates(struct rev_info *revs,
+				    enum ref_action action,
+				    struct commit *original,
+				    struct commit *rewritten,
+				    const char *reflog_msg,
+				    int dry_run)
+{
+	const struct name_decoration *decoration;
+	struct replay_revisions_options opts = { 0 };
+	struct replay_result result = { 0 };
+	struct ref_transaction *transaction = NULL;
+	struct strbuf err = STRBUF_INIT;
+	char hex[GIT_MAX_HEXSZ + 1];
+	bool detached_head;
+	int head_flags = 0;
+	int ret;
+
+	refs_read_ref_full(get_main_ref_store(revs->repo), "HEAD",
+			   RESOLVE_REF_NO_RECURSE, NULL, &head_flags);
+	detached_head = !(head_flags & REF_ISSYMREF);
+
+	opts.onto = oid_to_hex_r(hex, &rewritten->object.oid);
+
+	ret = replay_revisions(revs, &opts, &result);
+	if (ret)
+		goto out;
+
+	if (action != REF_ACTION_BRANCHES && action != REF_ACTION_HEAD)
+		BUG("unsupported ref action %d", action);
+
+	if (!dry_run) {
+		transaction = ref_store_transaction_begin(get_main_ref_store(revs->repo), 0, &err);
+		if (!transaction) {
+			ret = error(_("failed to begin ref transaction: %s"), err.buf);
+			goto out;
+		}
+	}
+
+	for (size_t i = 0; i < result.updates_nr; i++) {
+		ret = handle_ref_update(transaction,
+					result.updates[i].refname,
+					&result.updates[i].new_oid,
+					&result.updates[i].old_oid,
+					reflog_msg, &err);
+		if (ret) {
+			ret = error(_("failed to update ref '%s': %s"),
+				    result.updates[i].refname, err.buf);
+			goto out;
+		}
+	}
+
+	/*
+	 * `replay_revisions()` only updates references that are
+	 * ancestors of `rewritten`, so we need to manually
+	 * handle updating references that point to `original`.
+	 */
+	for (decoration = get_name_decoration(&original->object);
+	     decoration;
+	     decoration = decoration->next)
+	{
+		if (decoration->type != DECORATION_REF_LOCAL &&
+		    decoration->type != DECORATION_REF_HEAD)
+			continue;
+
+		if (action == REF_ACTION_HEAD &&
+		    decoration->type != DECORATION_REF_HEAD)
+			continue;
+
+		/*
+		 * We only need to update HEAD separately in case it's
+		 * detached. If it's not we'd already update the branch
+		 * it is pointing to.
+		 */
+		if (action == REF_ACTION_BRANCHES &&
+		    decoration->type == DECORATION_REF_HEAD &&
+		    !detached_head)
+			continue;
+
+		ret = handle_ref_update(transaction,
+					decoration->name,
+					&rewritten->object.oid,
+					&original->object.oid,
+					reflog_msg, &err);
+		if (ret) {
+			ret = error(_("failed to update ref '%s': %s"),
+				    decoration->name, err.buf);
+			goto out;
+		}
+	}
+
+	if (transaction && ref_transaction_commit(transaction, &err)) {
+		ret = error(_("failed to commit ref transaction: %s"), err.buf);
+		goto out;
+	}
+
+	ret = 0;
+
+out:
+	ref_transaction_free(transaction);
+	replay_result_release(&result);
+	strbuf_release(&err);
+	return ret;
+}
+
+static int cmd_history_reword(int argc,
+			      const char **argv,
+			      const char *prefix,
+			      struct repository *repo)
+{
+	const char * const usage[] = {
+		GIT_HISTORY_REWORD_USAGE,
+		NULL,
+	};
+	enum ref_action action = REF_ACTION_DEFAULT;
+	int dry_run = 0;
+	struct option options[] = {
+		OPT_CALLBACK_F(0, "update-refs", &action, N_("<action>"),
+			       N_("control which refs should be updated (branches|head)"),
+			       PARSE_OPT_NONEG, parse_ref_action),
+		OPT_BOOL('n', "dry-run", &dry_run,
+			 N_("perform a dry-run without updating any refs")),
+		OPT_END(),
+	};
+	struct strbuf reflog_msg = STRBUF_INIT;
+	struct commit *original, *rewritten;
+	struct rev_info revs = { 0 };
+	int ret;
+
+	argc = parse_options(argc, argv, prefix, options, usage, 0);
+	if (argc != 1) {
+		ret = error(_("command expects a single revision"));
+		goto out;
+	}
+	repo_config(repo, git_default_config, NULL);
+
+	if (action == REF_ACTION_DEFAULT)
+		action = REF_ACTION_BRANCHES;
+
+	original = lookup_commit_reference_by_name(argv[0]);
+	if (!original) {
+		ret = error(_("commit cannot be found: %s"), argv[0]);
+		goto out;
+	}
+
+	ret = setup_revwalk(repo, action, original, &revs);
+	if (ret)
+		goto out;
+
+	ret = commit_tree_with_edited_message(repo, "reworded", original, &rewritten);
+	if (ret < 0) {
+		ret = error(_("failed writing reworded commit"));
+		goto out;
+	}
+
+	strbuf_addf(&reflog_msg, "reword: updating %s", argv[0]);
+
+	ret = handle_reference_updates(&revs, action, original, rewritten,
+				       reflog_msg.buf, dry_run);
+	if (ret < 0) {
+		ret = error(_("failed replaying descendants"));
+		goto out;
+	}
+
+	ret = 0;
+
+out:
+	strbuf_release(&reflog_msg);
+	release_revisions(&revs);
+	return ret;
+}
+
+int cmd_history(int argc,
+		const char **argv,
+		const char *prefix,
+		struct repository *repo)
+{
+	const char * const usage[] = {
+		GIT_HISTORY_REWORD_USAGE,
+		NULL,
+	};
+	parse_opt_subcommand_fn *fn = NULL;
+	struct option options[] = {
+		OPT_SUBCOMMAND("reword", &fn, cmd_history_reword),
+		OPT_END(),
+	};
+
+	argc = parse_options(argc, argv, prefix, options, usage, 0);
+	return fn(argc, argv, prefix, repo);
+}
diff --git a/builtin/hook.c b/builtin/hook.c
index 7afec38..83020df 100644
--- a/builtin/hook.c
+++ b/builtin/hook.c
@@ -6,12 +6,16 @@
 #include "hook.h"
 #include "parse-options.h"
 #include "strvec.h"
+#include "abspath.h"
 
 #define BUILTIN_HOOK_RUN_USAGE \
 	N_("git hook run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]")
+#define BUILTIN_HOOK_LIST_USAGE \
+	N_("git hook list [-z] <hook-name>")
 
 static const char * const builtin_hook_usage[] = {
 	BUILTIN_HOOK_RUN_USAGE,
+	BUILTIN_HOOK_LIST_USAGE,
 	NULL
 };
 
@@ -20,6 +24,67 @@ static const char * const builtin_hook_run_usage[] = {
 	NULL
 };
 
+static int list(int argc, const char **argv, const char *prefix,
+		 struct repository *repo)
+{
+	static const char *const builtin_hook_list_usage[] = {
+		BUILTIN_HOOK_LIST_USAGE,
+		NULL
+	};
+	struct string_list *head;
+	struct string_list_item *item;
+	const char *hookname = NULL;
+	int line_terminator = '\n';
+	int ret = 0;
+
+	struct option list_options[] = {
+		OPT_SET_INT('z', NULL, &line_terminator,
+			    N_("use NUL as line terminator"), '\0'),
+		OPT_END(),
+	};
+
+	argc = parse_options(argc, argv, prefix, list_options,
+			     builtin_hook_list_usage, 0);
+
+	/*
+	 * The only unnamed argument provided should be the hook-name; if we add
+	 * arguments later they probably should be caught by parse_options.
+	 */
+	if (argc != 1)
+		usage_msg_opt(_("You must specify a hook event name to list."),
+			      builtin_hook_list_usage, list_options);
+
+	hookname = argv[0];
+
+	head = list_hooks(repo, hookname, NULL);
+
+	if (!head->nr) {
+		warning(_("No hooks found for event '%s'"), hookname);
+		ret = 1; /* no hooks found */
+		goto cleanup;
+	}
+
+	for_each_string_list_item(item, head) {
+		struct hook *h = item->util;
+
+		switch (h->kind) {
+		case HOOK_TRADITIONAL:
+			printf("%s%c", _("hook from hookdir"), line_terminator);
+			break;
+		case HOOK_CONFIGURED:
+			printf("%s%c", h->u.configured.friendly_name, line_terminator);
+			break;
+		default:
+			BUG("unknown hook kind");
+		}
+	}
+
+cleanup:
+	hook_list_clear(head, NULL);
+	free(head);
+	return ret;
+}
+
 static int run(int argc, const char **argv, const char *prefix,
 	       struct repository *repo UNUSED)
 {
@@ -77,6 +142,7 @@ int cmd_hook(int argc,
 	parse_opt_subcommand_fn *fn = NULL;
 	struct option builtin_hook_options[] = {
 		OPT_SUBCOMMAND("run", &fn, run),
+		OPT_SUBCOMMAND("list", &fn, list),
 		OPT_END(),
 	};
 
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 2b78ba7..d1e4727 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -16,12 +16,12 @@
 #include "progress.h"
 #include "fsck.h"
 #include "strbuf.h"
-#include "streaming.h"
 #include "thread-utils.h"
 #include "packfile.h"
 #include "pack-revindex.h"
 #include "object-file.h"
 #include "odb.h"
+#include "odb/streaming.h"
 #include "oid-array.h"
 #include "oidset.h"
 #include "path.h"
@@ -762,7 +762,7 @@ static void find_ref_delta_children(const struct object_id *oid,
 
 struct compare_data {
 	struct object_entry *entry;
-	struct git_istream *st;
+	struct odb_read_stream *st;
 	unsigned char *buf;
 	unsigned long buf_size;
 };
@@ -779,7 +779,7 @@ static int compare_objects(const unsigned char *buf, unsigned long size,
 	}
 
 	while (size) {
-		ssize_t len = read_istream(data->st, data->buf, size);
+		ssize_t len = odb_read_stream_read(data->st, data->buf, size);
 		if (len == 0)
 			die(_("SHA1 COLLISION FOUND WITH %s !"),
 			    oid_to_hex(&data->entry->idx.oid));
@@ -798,8 +798,6 @@ static int compare_objects(const unsigned char *buf, unsigned long size,
 static int check_collison(struct object_entry *entry)
 {
 	struct compare_data data;
-	enum object_type type;
-	unsigned long size;
 
 	if (entry->size <= repo_settings_get_big_file_threshold(the_repository) ||
 	    entry->type != OBJ_BLOB)
@@ -807,15 +805,14 @@ static int check_collison(struct object_entry *entry)
 
 	memset(&data, 0, sizeof(data));
 	data.entry = entry;
-	data.st = open_istream(the_repository, &entry->idx.oid, &type, &size,
-			       NULL);
+	data.st = odb_read_stream_open(the_repository->objects, &entry->idx.oid, NULL);
 	if (!data.st)
 		return -1;
-	if (size != entry->size || type != entry->type)
+	if (data.st->size != entry->size || data.st->type != entry->type)
 		die(_("SHA1 COLLISION FOUND WITH %s !"),
 		    oid_to_hex(&entry->idx.oid));
 	unpack_data(entry, compare_objects, &data);
-	close_istream(data.st);
+	odb_read_stream_close(data.st);
 	free(data.buf);
 	return 0;
 }
@@ -1640,9 +1637,11 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
 	rename_tmp_packfile(&final_index_name, curr_index_name, &index_name,
 			    hash, "idx", 1);
 
-	if (do_fsck_object)
-		packfile_store_load_pack(the_repository->objects->packfiles,
-					 final_index_name, 0);
+	if (do_fsck_object && startup_info->have_repository) {
+		struct odb_source_files *files =
+			odb_source_files_downcast(the_repository->objects->sources);
+		packfile_store_load_pack(files->packed, final_index_name, 0);
+	}
 
 	if (!from_stdin) {
 		printf("%s\n", hash_to_hex(hash));
@@ -2110,8 +2109,23 @@ int cmd_index_pack(int argc,
 	else
 		close(input_fd);
 
-	if (do_fsck_object && fsck_finish(&fsck_options))
-		die(_("fsck error in pack objects"));
+	if (do_fsck_object) {
+		/*
+		 * We cannot perform queued consistency checks when running
+		 * outside of a repository because those require us to read
+		 * from the object database, which is uninitialized.
+		 *
+		 * TODO: we may eventually set up an in-memory object database,
+		 * which would allow us to perform these queued checks.
+		 */
+		if (!startup_info->have_repository &&
+		    fsck_has_queued_checks(&fsck_options))
+			die(_("cannot perform queued object checks outside "
+			      "of a repository"));
+
+		if (fsck_finish(&fsck_options))
+			die(_("fsck error in pack objects"));
+	}
 
 	free(opts.anomaly);
 	free(objects);
diff --git a/builtin/last-modified.c b/builtin/last-modified.c
index b0ecbdc..8900cee 100644
--- a/builtin/last-modified.c
+++ b/builtin/last-modified.c
@@ -53,8 +53,9 @@ define_commit_slab(active_paths_for_commit, struct bitmap *);
 struct last_modified {
 	struct hashmap paths;
 	struct rev_info rev;
-	bool recursive;
 	bool show_trees;
+	bool nul_termination;
+	int max_depth;
 
 	const char **all_paths;
 	size_t all_paths_nr;
@@ -123,7 +124,7 @@ static void add_path_from_diff(struct diff_queue_struct *q,
 
 static int populate_paths_from_revs(struct last_modified *lm)
 {
-	int num_interesting = 0;
+	int num_interesting = 0, ret = 0;
 	struct diff_options diffopt;
 
 	/*
@@ -145,16 +146,25 @@ static int populate_paths_from_revs(struct last_modified *lm)
 		if (obj->item->flags & UNINTERESTING)
 			continue;
 
-		if (num_interesting++)
-			return error(_("last-modified can only operate on one tree at a time"));
+		if (num_interesting++) {
+			ret = error(_("last-modified can only operate on one commit at a time"));
+			goto out;
+		}
+
+		if (!repo_peel_to_type(lm->rev.repo, obj->path, 0, obj->item, OBJ_COMMIT)) {
+			ret = error(_("revision argument '%s' is a %s, not a commit-ish"), obj->name, type_name(obj->item->type));
+			goto out;
+		}
 
 		diff_tree_oid(lm->rev.repo->hash_algo->empty_tree,
 			      &obj->item->oid, "", &diffopt);
 		diff_flush(&diffopt);
 	}
+
+out:
 	clear_pathspec(&diffopt.pathspec);
 
-	return 0;
+	return ret;
 }
 
 static void last_modified_emit(struct last_modified *lm,
@@ -165,10 +175,10 @@ static void last_modified_emit(struct last_modified *lm,
 		putchar('^');
 	printf("%s\t", oid_to_hex(&commit->object.oid));
 
-	if (lm->rev.diffopt.line_termination)
-		write_name_quoted(path, stdout, '\n');
-	else
+	if (lm->nul_termination)
 		printf("%s%c", path, '\0');
+	else
+		write_name_quoted(path, stdout, '\n');
 }
 
 static void mark_path(const char *path, const struct object_id *oid,
@@ -327,7 +337,7 @@ static void process_parent(struct last_modified *lm,
 	if (!(parent->object.flags & PARENT1))
 		active_paths_free(lm, parent);
 
-	memset(lm->scratch->words, 0x0, lm->scratch->word_alloc);
+	MEMZERO_ARRAY(lm->scratch->words, lm->scratch->word_alloc);
 	diff_queue_clear(&diff_queued_diff);
 }
 
@@ -479,8 +489,10 @@ static int last_modified_init(struct last_modified *lm, struct repository *r,
 	lm->rev.no_commit_id = 1;
 	lm->rev.diff = 1;
 	lm->rev.diffopt.flags.no_recursive_diff_tree_combined = 1;
-	lm->rev.diffopt.flags.recursive = lm->recursive;
+	lm->rev.diffopt.flags.recursive = 1;
 	lm->rev.diffopt.flags.tree_in_recursive = lm->show_trees;
+	lm->rev.diffopt.max_depth = lm->max_depth;
+	lm->rev.diffopt.max_depth_valid = lm->max_depth >= 0;
 
 	argc = setup_revisions(argc, argv, &lm->rev, NULL);
 	if (argc > 1) {
@@ -491,7 +503,7 @@ static int last_modified_init(struct last_modified *lm, struct repository *r,
 	lm->rev.bloom_filter_settings = get_bloom_filter_settings(lm->rev.repo);
 
 	if (populate_paths_from_revs(lm) < 0)
-		return error(_("unable to setup last-modified"));
+		return -1;
 
 	CALLOC_ARRAY(lm->all_paths, hashmap_get_size(&lm->paths));
 	lm->all_paths_nr = 0;
@@ -510,22 +522,27 @@ int cmd_last_modified(int argc, const char **argv, const char *prefix,
 	struct last_modified lm = { 0 };
 
 	const char * const last_modified_usage[] = {
-		N_("git last-modified [--recursive] [--show-trees] "
-		   "[<revision-range>] [[--] <path>...]"),
+		N_("git last-modified [--recursive] [--show-trees] [--max-depth=<depth>] [-z]\n"
+		   "                  [<revision-range>] [[--] <pathspec>...]"),
 		NULL
 	};
 
 	struct option last_modified_options[] = {
-		OPT_BOOL('r', "recursive", &lm.recursive,
-			 N_("recurse into subtrees")),
+		OPT_SET_INT('r', "recursive", &lm.max_depth,
+			    N_("recurse into subtrees"), -1),
 		OPT_BOOL('t', "show-trees", &lm.show_trees,
 			 N_("show tree entries when recursing into subtrees")),
+		OPT_INTEGER_F(0, "max-depth", &lm.max_depth,
+			      N_("maximum tree depth to recurse"), PARSE_OPT_NONEG),
+		OPT_BOOL('z', NULL, &lm.nul_termination,
+			 N_("lines are separated with NUL character")),
 		OPT_END()
 	};
 
 	argc = parse_options(argc, argv, prefix, last_modified_options,
 			     last_modified_usage,
-			     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
+			     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT |
+			     PARSE_OPT_KEEP_DASHDASH);
 
 	repo_config(repo, git_default_config, NULL);
 
diff --git a/builtin/log.c b/builtin/log.c
index c8319b8..89e8b8f 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -16,6 +16,7 @@
 #include "refs.h"
 #include "object-name.h"
 #include "odb.h"
+#include "odb/streaming.h"
 #include "pager.h"
 #include "color.h"
 #include "commit.h"
@@ -23,7 +24,6 @@
 #include "diff-merges.h"
 #include "revision.h"
 #include "log-tree.h"
-#include "builtin.h"
 #include "oid-array.h"
 #include "tag.h"
 #include "reflog-walk.h"
@@ -35,11 +35,11 @@
 #include "parse-options.h"
 #include "line-log.h"
 #include "branch.h"
-#include "streaming.h"
 #include "version.h"
 #include "mailmap.h"
 #include "progress.h"
 #include "commit-slab.h"
+#include "advice.h"
 
 #include "commit-reach.h"
 #include "range-diff.h"
@@ -337,7 +337,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
 	if (mailmap) {
 		rev->mailmap = xmalloc(sizeof(struct string_list));
 		string_list_init_nodup(rev->mailmap);
-		read_mailmap(rev->mailmap);
+		read_mailmap(the_repository, rev->mailmap);
 	}
 
 	if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) {
@@ -424,7 +424,7 @@ static int cmd_log_walk_no_free(struct rev_info *rev)
 			 */
 			free_commit_buffer(the_repository->parsed_objects,
 					   commit);
-			free_commit_list(commit->parents);
+			commit_list_free(commit->parents);
 			commit->parents = NULL;
 		}
 		if (saved_nrl < rev->diffopt.needed_rename_limit)
@@ -584,7 +584,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c
 	fflush(rev->diffopt.file);
 	if (!rev->diffopt.flags.textconv_set_via_cmdline ||
 	    !rev->diffopt.flags.allow_textconv)
-		return stream_blob_to_fd(1, oid, NULL, 0);
+		return odb_stream_blob_to_fd(the_repository->objects, 1, oid, NULL, 0);
 
 	if (get_oid_with_context(the_repository, obj_name,
 				 GET_OID_RECORD_PATH,
@@ -594,7 +594,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c
 	    !textconv_object(the_repository, obj_context.path,
 			     obj_context.mode, &oidc, 1, &buf, &size)) {
 		object_context_release(&obj_context);
-		return stream_blob_to_fd(1, oid, NULL, 0);
+		return odb_stream_blob_to_fd(the_repository->objects, 1, oid, NULL, 0);
 	}
 
 	if (!buf)
@@ -1096,7 +1096,18 @@ static int git_format_config(const char *var, const char *value,
 		return 0;
 	}
 	if (!strcmp(var, "format.noprefix")) {
-		format_no_prefix = 1;
+		format_no_prefix = git_parse_maybe_bool(value);
+		if (format_no_prefix < 0) {
+			int status = die_message(
+				_("bad boolean config value '%s' for '%s'"),
+				value, var);
+			advise(_("'%s' used to accept any value and "
+				 "treat that as 'true'.\n"
+				 "Now it only accepts boolean values, "
+				 "like what '%s' does.\n"),
+			       var, "diff.noprefix");
+			exit(status);
+		}
 		return 0;
 	}
 
@@ -1332,7 +1343,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
 			      int quiet,
 			      const struct format_config *cfg)
 {
-	const char *committer;
+	const char *from;
 	struct shortlog log;
 	struct strbuf sb = STRBUF_INIT;
 	int i;
@@ -1345,7 +1356,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
 	if (!cmit_fmt_is_mail(rev->commit_format))
 		die(_("cover letter needs email format"));
 
-	committer = git_committer_info(0);
+	from = cfg->from ? cfg->from : git_committer_info(0);
 
 	if (use_separate_file &&
 	    open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet))
@@ -1368,7 +1379,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
 	pp.date_mode.type = DATE_RFC2822;
 	pp.rev = rev;
 	pp.encode_email_headers = rev->encode_email_headers;
-	pp_user_info(&pp, NULL, &sb, committer, encoding);
+	pp_user_info(&pp, NULL, &sb, from, encoding);
 	prepare_cover_text(&pp, description_file, branch_name, &sb,
 			   encoding, need_8bit_cte, cfg);
 	fprintf(rev->diffopt.file, "%s\n", sb.buf);
@@ -1697,12 +1708,12 @@ static struct commit *get_base_commit(const struct format_config *cfg,
 				if (die_on_failure) {
 					die(_("could not find exact merge base"));
 				} else {
-					free_commit_list(base_list);
+					commit_list_free(base_list);
 					return NULL;
 				}
 			}
 			base = base_list->item;
-			free_commit_list(base_list);
+			commit_list_free(base_list);
 		} else {
 			if (die_on_failure)
 				die(_("failed to get upstream, if you want to record base commit automatically,\n"
@@ -1732,14 +1743,14 @@ static struct commit *get_base_commit(const struct format_config *cfg,
 				if (die_on_failure) {
 					die(_("failed to find exact merge base"));
 				} else {
-					free_commit_list(merge_base);
+					commit_list_free(merge_base);
 					free(rev);
 					return NULL;
 				}
 			}
 
 			rev[i] = merge_base->item;
-			free_commit_list(merge_base);
+			commit_list_free(merge_base);
 		}
 
 		if (rev_nr % 2)
@@ -1896,11 +1907,11 @@ int cmd_format_patch(int argc,
 {
 	struct format_config cfg;
 	struct commit *commit;
-	struct commit **list = NULL;
+	struct commit_stack list = COMMIT_STACK_INIT;
 	struct rev_info rev;
 	char *to_free = NULL;
 	struct setup_revision_opt s_r_opt;
-	size_t nr = 0, total, i;
+	size_t total, i;
 	int use_stdout = 0;
 	int start_number = -1;
 	int just_numbers = 0;
@@ -2283,14 +2294,12 @@ int cmd_format_patch(int argc,
 		if (ignore_if_in_upstream && has_commit_patch_id(commit, &ids))
 			continue;
 
-		nr++;
-		REALLOC_ARRAY(list, nr);
-		list[nr - 1] = commit;
+		commit_stack_push(&list, commit);
 	}
-	if (nr == 0)
+	if (!list.nr)
 		/* nothing to do */
 		goto done;
-	total = nr;
+	total = list.nr;
 	if (cover_letter == -1) {
 		if (cfg.config_cover_letter == COVER_AUTO)
 			cover_letter = (total > 1);
@@ -2308,7 +2317,7 @@ int cmd_format_patch(int argc,
 		if (!cover_letter && total != 1)
 			die(_("--interdiff requires --cover-letter or single patch"));
 		rev.idiff_oid1 = &idiff_prev.oid[idiff_prev.nr - 1];
-		rev.idiff_oid2 = get_commit_tree_oid(list[0]);
+		rev.idiff_oid2 = get_commit_tree_oid(list.items[0]);
 		rev.idiff_title = diff_title(&idiff_title, reroll_count,
 					     _("Interdiff:"),
 					     _("Interdiff against v%d:"));
@@ -2324,7 +2333,7 @@ int cmd_format_patch(int argc,
 			die(_("--range-diff requires --cover-letter or single patch"));
 
 		infer_range_diff_ranges(&rdiff1, &rdiff2, rdiff_prev,
-					origin, list[0]);
+					origin, list.items[0]);
 		rev.rdiff1 = rdiff1.buf;
 		rev.rdiff2 = rdiff2.buf;
 		rev.creation_factor = creation_factor;
@@ -2360,11 +2369,11 @@ int cmd_format_patch(int argc,
 	}
 
 	memset(&bases, 0, sizeof(bases));
-	base = get_base_commit(&cfg, list, nr);
+	base = get_base_commit(&cfg, list.items, list.nr);
 	if (base) {
 		reset_revision_walk();
 		clear_object_flags(the_repository, UNINTERESTING);
-		prepare_bases(&bases, base, list, nr);
+		prepare_bases(&bases, base, list.items, list.nr);
 	}
 
 	if (in_reply_to || cfg.thread || cover_letter) {
@@ -2381,7 +2390,8 @@ int cmd_format_patch(int argc,
 		if (cfg.thread)
 			gen_message_id(&rev, "cover");
 		make_cover_letter(&rev, !!output_directory,
-				  origin, nr, list, description_file, branch_name, quiet, &cfg);
+				  origin, list.nr, list.items,
+				  description_file, branch_name, quiet, &cfg);
 		print_bases(&bases, rev.diffopt.file);
 		print_signature(signature, rev.diffopt.file);
 		total++;
@@ -2395,12 +2405,12 @@ int cmd_format_patch(int argc,
 	if (show_progress)
 		progress = start_delayed_progress(the_repository,
 						  _("Generating patches"), total);
-	for (i = 0; i < nr; i++) {
-		size_t idx = nr - i - 1;
+	while (list.nr) {
+		size_t idx = list.nr - 1;
 		int shown;
 
 		display_progress(progress, total - idx);
-		commit = list[idx];
+		commit = commit_stack_pop(&list);
 		rev.nr = total - idx + (start_number - 1);
 
 		/* Make the second and subsequent mails replies to the first */
@@ -2469,7 +2479,7 @@ int cmd_format_patch(int argc,
 		}
 	}
 	stop_progress(&progress);
-	free(list);
+	commit_stack_clear(&list);
 	if (ignore_if_in_upstream)
 		free_patch_ids(&ids);
 
@@ -2611,7 +2621,7 @@ int cmd_cherry(int argc,
 		print_commit(sign, commit, verbose, abbrev, revs.diffopt.file);
 	}
 
-	free_commit_list(list);
+	commit_list_free(list);
 	free_patch_ids(&ids);
 	return 0;
 }
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
index df09000..fe77829 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -156,7 +156,7 @@ int cmd_ls_remote(int argc,
 			continue;
 		if (!tail_match(&pattern, ref->name))
 			continue;
-		item = ref_array_push(&ref_array, ref->name, &ref->old_oid);
+		item = ref_array_push(&ref_array, ref->name, &ref->old_oid, NULL);
 		item->symref = xstrdup_or_null(ref->symref);
 	}
 
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index ec6940f..113e4a9 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -421,7 +421,7 @@ int cmd_ls_tree(int argc,
 	for (i = 0; i < options.pathspec.nr; i++)
 		options.pathspec.items[i].nowildcard_len = options.pathspec.items[i].len;
 	options.pathspec.has_wildcard = 0;
-	tree = parse_tree_indirect(&oid);
+	tree = repo_parse_tree_indirect(the_repository, &oid);
 	if (!tree)
 		die("not a tree object");
 	/*
diff --git a/builtin/merge-base.c b/builtin/merge-base.c
index 3f82781..c7ee97f 100644
--- a/builtin/merge-base.c
+++ b/builtin/merge-base.c
@@ -15,7 +15,7 @@ static int show_merge_base(struct commit **rev, size_t rev_nr, int show_all)
 
 	if (repo_get_merge_bases_many_dirty(the_repository, rev[0],
 					    rev_nr - 1, rev + 1, &result) < 0) {
-		free_commit_list(result);
+		commit_list_free(result);
 		return -1;
 	}
 
@@ -28,7 +28,7 @@ static int show_merge_base(struct commit **rev, size_t rev_nr, int show_all)
 			break;
 	}
 
-	free_commit_list(result);
+	commit_list_free(result);
 	return 0;
 }
 
@@ -71,7 +71,7 @@ static int handle_independent(int count, const char **args)
 	for (rev = revs; rev; rev = rev->next)
 		printf("%s\n", oid_to_hex(&rev->item->object.oid));
 
-	free_commit_list(revs);
+	commit_list_free(revs);
 	return 0;
 }
 
@@ -85,11 +85,11 @@ static int handle_octopus(int count, const char **args, int show_all)
 		commit_list_insert(get_commit_reference(args[i]), &revs);
 
 	if (get_octopus_merge_bases(revs, &result) < 0) {
-		free_commit_list(revs);
-		free_commit_list(result);
+		commit_list_free(revs);
+		commit_list_free(result);
 		return 128;
 	}
-	free_commit_list(revs);
+	commit_list_free(revs);
 	reduce_heads_replace(&result);
 
 	if (!result)
@@ -101,7 +101,7 @@ static int handle_octopus(int count, const char **args, int show_all)
 			break;
 	}
 
-	free_commit_list(result);
+	commit_list_free(result);
 	return 0;
 }
 
diff --git a/builtin/merge-file.c b/builtin/merge-file.c
index 46775d0..084fdfe 100644
--- a/builtin/merge-file.c
+++ b/builtin/merge-file.c
@@ -60,7 +60,7 @@ static int diff_algorithm_cb(const struct option *opt,
 int cmd_merge_file(int argc,
 		   const char **argv,
 		   const char *prefix,
-		   struct repository *repo UNUSED)
+		   struct repository *repo)
 {
 	const char *names[3] = { 0 };
 	mmfile_t mmfs[3] = { 0 };
@@ -95,12 +95,10 @@ int cmd_merge_file(int argc,
 	xmp.style = 0;
 	xmp.favor = 0;
 
-	if (startup_info->have_repository) {
-		/* Read the configuration file */
-		repo_config(the_repository, git_xmerge_config, NULL);
-		if (0 <= git_xmerge_style)
-			xmp.style = git_xmerge_style;
-	}
+	/* Read the configuration file */
+	repo_config(repo, git_xmerge_config, NULL);
+	if (0 <= git_xmerge_style)
+		xmp.style = git_xmerge_style;
 
 	argc = parse_options(argc, argv, prefix, options, merge_file_usage, 0);
 	if (argc != 3)
@@ -128,7 +126,7 @@ int cmd_merge_file(int argc,
 				ret = error(_("object '%s' does not exist"),
 					      argv[i]);
 			else if (!oideq(&oid, the_hash_algo->empty_blob))
-				read_mmblob(mmf, &oid);
+				read_mmblob(mmf, the_repository->objects, &oid);
 			else
 				read_mmfile(mmf, "/dev/null");
 		} else if (read_mmfile(mmf, fname)) {
diff --git a/builtin/merge-ours.c b/builtin/merge-ours.c
index 97b8a79..405b298 100644
--- a/builtin/merge-ours.c
+++ b/builtin/merge-ours.c
@@ -8,31 +8,34 @@
  * Pretend we resolved the heads, but declare our tree trumps everybody else.
  */
 
-#define USE_THE_REPOSITORY_VARIABLE
-
 #include "git-compat-util.h"
 #include "builtin.h"
+#include "config.h"
+#include "environment.h"
 #include "diff.h"
 
-
 static const char builtin_merge_ours_usage[] =
 	"git merge-ours <base>... -- HEAD <remote>...";
 
 int cmd_merge_ours(int argc,
 		   const char **argv,
 		   const char *prefix UNUSED,
-		   struct repository *repo UNUSED)
+		   struct repository *repo)
 {
 	show_usage_if_asked(argc, argv, builtin_merge_ours_usage);
 
+	repo_config(repo, git_default_config, NULL);
+	prepare_repo_settings(repo);
+	repo->settings.command_requires_full_index = 0;
+
 	/*
 	 * The contents of the current index becomes the tree we
 	 * commit.  The index must match HEAD, or this merge cannot go
 	 * through.
 	 */
-	if (repo_read_index(the_repository) < 0)
+	if (repo_read_index(repo) < 0)
 		die_errno("read_cache failed");
-	if (index_differs_from(the_repository, "HEAD", NULL, 0))
+	if (index_differs_from(repo, "HEAD", NULL, 0))
 		return 2;
 	return 0;
 }
diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c
index 1c063d9..312b595 100644
--- a/builtin/merge-tree.c
+++ b/builtin/merge-tree.c
@@ -447,17 +447,20 @@ static int real_merge(struct merge_tree_options *o,
 
 		if (repo_get_oid_treeish(the_repository, merge_base, &base_oid))
 			die(_("could not parse as tree '%s'"), merge_base);
-		base_tree = parse_tree_indirect(&base_oid);
+		base_tree = repo_parse_tree_indirect(the_repository,
+						     &base_oid);
 		if (!base_tree)
 			die(_("unable to read tree (%s)"), oid_to_hex(&base_oid));
 		if (repo_get_oid_treeish(the_repository, branch1, &head_oid))
 			die(_("could not parse as tree '%s'"), branch1);
-		parent1_tree = parse_tree_indirect(&head_oid);
+		parent1_tree = repo_parse_tree_indirect(the_repository,
+							&head_oid);
 		if (!parent1_tree)
 			die(_("unable to read tree (%s)"), oid_to_hex(&head_oid));
 		if (repo_get_oid_treeish(the_repository, branch2, &merge_oid))
 			die(_("could not parse as tree '%s'"), branch2);
-		parent2_tree = parse_tree_indirect(&merge_oid);
+		parent2_tree = repo_parse_tree_indirect(the_repository,
+							&merge_oid);
 		if (!parent2_tree)
 			die(_("unable to read tree (%s)"), oid_to_hex(&merge_oid));
 
@@ -483,9 +486,9 @@ static int real_merge(struct merge_tree_options *o,
 			exit(128);
 		if (!merge_bases && !o->allow_unrelated_histories)
 			die(_("refusing to merge unrelated histories"));
-		merge_bases = reverse_commit_list(merge_bases);
+		merge_bases = commit_list_reverse(merge_bases);
 		merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
-		free_commit_list(merge_bases);
+		commit_list_free(merge_bases);
 	}
 
 	if (result.clean < 0)
diff --git a/builtin/merge.c b/builtin/merge.c
index c421a11..4e456a3 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -756,19 +756,19 @@ static int read_tree_trivial(struct object_id *common, struct object_id *head,
 	opts.trivial_merges_only = 1;
 	opts.merge = 1;
 	opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
-	trees[nr_trees] = parse_tree_indirect(common);
+	trees[nr_trees] = repo_parse_tree_indirect(the_repository, common);
 	if (!trees[nr_trees++])
 		return -1;
-	trees[nr_trees] = parse_tree_indirect(head);
+	trees[nr_trees] = repo_parse_tree_indirect(the_repository, head);
 	if (!trees[nr_trees++])
 		return -1;
-	trees[nr_trees] = parse_tree_indirect(one);
+	trees[nr_trees] = repo_parse_tree_indirect(the_repository, one);
 	if (!trees[nr_trees++])
 		return -1;
 	opts.fn = threeway_merge;
 	cache_tree_free(&the_repository->index->cache_tree);
 	for (i = 0; i < nr_trees; i++) {
-		parse_tree(trees[i]);
+		repo_parse_tree(the_repository, trees[i]);
 		init_tree_desc(t+i, &trees[i]->object.oid,
 			       trees[i]->buffer, trees[i]->size);
 	}
@@ -831,7 +831,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
 				       LOCK_DIE_ON_ERROR);
 		clean = merge_ort_recursive(&o, head, remoteheads->item,
 					    reversed, &result);
-		free_commit_list(reversed);
+		commit_list_free(reversed);
 		strbuf_release(&o.obuf);
 
 		if (clean < 0) {
@@ -1006,7 +1006,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
 	finish(head, remoteheads, &result_commit, "In-index merge");
 
 	remove_merge_branch_state(the_repository);
-	free_commit_list(parents);
+	commit_list_free(parents);
 	return 0;
 }
 
@@ -1022,7 +1022,7 @@ static int finish_automerge(struct commit *head,
 	struct object_id result_commit;
 
 	write_tree_trivial(result_tree);
-	free_commit_list(common);
+	commit_list_free(common);
 	parents = remoteheads;
 	if (!head_subsumed || fast_forward == FF_NO)
 		commit_list_insert(head, &parents);
@@ -1035,7 +1035,7 @@ static int finish_automerge(struct commit *head,
 
 	strbuf_release(&buf);
 	remove_merge_branch_state(the_repository);
-	free_commit_list(parents);
+	commit_list_free(parents);
 	return 0;
 }
 
@@ -1197,7 +1197,7 @@ static struct commit_list *reduce_parents(struct commit *head_commit,
 
 	/* Find what parents to record by checking independent ones. */
 	parents = reduce_heads(remoteheads);
-	free_commit_list(remoteheads);
+	commit_list_free(remoteheads);
 
 	remoteheads = NULL;
 	remotes = &remoteheads;
@@ -1748,7 +1748,7 @@ int cmd_merge(int argc,
 				exit(128);
 
 			common_item = common_one->item;
-			free_commit_list(common_one);
+			commit_list_free(common_one);
 			if (!oideq(&common_item->object.oid, &j->item->object.oid)) {
 				up_to_date = 0;
 				break;
@@ -1880,8 +1880,8 @@ int cmd_merge(int argc,
 
 done:
 	if (!automerge_was_ok) {
-		free_commit_list(common);
-		free_commit_list(remoteheads);
+		commit_list_free(common);
+		commit_list_free(remoteheads);
 	}
 	strbuf_release(&buf);
 	free(branch_to_free);
diff --git a/builtin/mv.c b/builtin/mv.c
index d439250..2215d34 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -238,6 +238,7 @@ int cmd_mv(int argc,
 	struct hashmap moved_dirs = HASHMAP_INIT(pathmap_cmp, NULL);
 	struct strbuf pathbuf = STRBUF_INIT;
 	int ret;
+	struct repo_config_values *cfg = repo_config_values(the_repository);
 
 	repo_config(the_repository, git_default_config, NULL);
 
@@ -572,7 +573,7 @@ int cmd_mv(int argc,
 		rename_index_entry_at(the_repository->index, pos, dst);
 
 		if (ignore_sparse &&
-		    core_apply_sparse_checkout &&
+		    cfg->apply_sparse_checkout &&
 		    core_sparse_checkout_cone) {
 			/*
 			 * NEEDSWORK: we are *not* paying attention to
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index 74512e5..6188cf9 100644
--- a/builtin/name-rev.c
+++ b/builtin/name-rev.c
@@ -180,8 +180,7 @@ static void name_rev(struct commit *start_commit,
 {
 	struct prio_queue queue;
 	struct commit *commit;
-	struct commit **parents_to_queue = NULL;
-	size_t parents_to_queue_nr, parents_to_queue_alloc = 0;
+	struct commit_stack parents_to_queue = COMMIT_STACK_INIT;
 	struct rev_name *start_name;
 
 	repo_parse_commit(the_repository, start_commit);
@@ -206,7 +205,7 @@ static void name_rev(struct commit *start_commit,
 		struct commit_list *parents;
 		int parent_number = 1;
 
-		parents_to_queue_nr = 0;
+		parents_to_queue.nr = 0;
 
 		for (parents = commit->parents;
 				parents;
@@ -238,22 +237,18 @@ static void name_rev(struct commit *start_commit,
 								string_pool);
 				else
 					parent_name->tip_name = name->tip_name;
-				ALLOC_GROW(parents_to_queue,
-					   parents_to_queue_nr + 1,
-					   parents_to_queue_alloc);
-				parents_to_queue[parents_to_queue_nr] = parent;
-				parents_to_queue_nr++;
+				commit_stack_push(&parents_to_queue, parent);
 			}
 		}
 
 		/* The first parent must come out first from the prio_queue */
-		while (parents_to_queue_nr)
+		while (parents_to_queue.nr)
 			prio_queue_put(&queue,
-				       parents_to_queue[--parents_to_queue_nr]);
+				       commit_stack_pop(&parents_to_queue));
 	}
 
 	clear_prio_queue(&queue);
-	free(parents_to_queue);
+	commit_stack_clear(&parents_to_queue);
 }
 
 static int subpath_matches(const char *path, const char *filter)
@@ -339,10 +334,9 @@ static int cmp_by_tag_and_age(const void *a_, const void *b_)
 	return a->taggerdate != b->taggerdate;
 }
 
-static int name_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,
-		    int flags UNUSED, void *cb_data)
+static int name_ref(const struct reference *ref, void *cb_data)
 {
-	struct object *o = parse_object(the_repository, oid);
+	struct object *o = parse_object(the_repository, ref->oid);
 	struct name_ref_data *data = cb_data;
 	int can_abbreviate_output = data->tags_only && data->name_only;
 	int deref = 0;
@@ -350,14 +344,14 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct
 	struct commit *commit = NULL;
 	timestamp_t taggerdate = TIME_MAX;
 
-	if (data->tags_only && !starts_with(path, "refs/tags/"))
+	if (data->tags_only && !starts_with(ref->name, "refs/tags/"))
 		return 0;
 
 	if (data->exclude_filters.nr) {
 		struct string_list_item *item;
 
 		for_each_string_list_item(item, &data->exclude_filters) {
-			if (subpath_matches(path, item->string) >= 0)
+			if (subpath_matches(ref->name, item->string) >= 0)
 				return 0;
 		}
 	}
@@ -378,7 +372,7 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct
 			 * shouldn't stop when seeing 'refs/tags/v1.4' matches
 			 * 'refs/tags/v*'.  We should show it as 'v1.4'.
 			 */
-			switch (subpath_matches(path, item->string)) {
+			switch (subpath_matches(ref->name, item->string)) {
 			case -1: /* did not match */
 				break;
 			case 0: /* matched fully */
@@ -406,13 +400,13 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct
 	}
 	if (o && o->type == OBJ_COMMIT) {
 		commit = (struct commit *)o;
-		from_tag = starts_with(path, "refs/tags/");
+		from_tag = starts_with(ref->name, "refs/tags/");
 		if (taggerdate == TIME_MAX)
 			taggerdate = commit->date;
 	}
 
-	add_to_tip_table(oid, path, can_abbreviate_output, commit, taggerdate,
-			 from_tag, deref);
+	add_to_tip_table(ref->oid, ref->name, can_abbreviate_output,
+			 commit, taggerdate, from_tag, deref);
 	return 0;
 }
 
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index b5454e5..cd013c0 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -22,7 +22,6 @@
 #include "pack-objects.h"
 #include "progress.h"
 #include "refs.h"
-#include "streaming.h"
 #include "thread-utils.h"
 #include "pack-bitmap.h"
 #include "delta-islands.h"
@@ -33,6 +32,7 @@
 #include "packfile.h"
 #include "object-file.h"
 #include "odb.h"
+#include "odb/streaming.h"
 #include "replace-object.h"
 #include "dir.h"
 #include "midx.h"
@@ -44,7 +44,6 @@
 #include "blob.h"
 #include "tree.h"
 #include "path-walk.h"
-#include "trace2.h"
 
 /*
  * Objects we are going to pack are collected in the `to_pack` structure.
@@ -404,7 +403,7 @@ static unsigned long do_compress(void **pptr, unsigned long size)
 	return stream.total_out;
 }
 
-static unsigned long write_large_blob_data(struct git_istream *st, struct hashfile *f,
+static unsigned long write_large_blob_data(struct odb_read_stream *st, struct hashfile *f,
 					   const struct object_id *oid)
 {
 	git_zstream stream;
@@ -417,7 +416,7 @@ static unsigned long write_large_blob_data(struct git_istream *st, struct hashfi
 	for (;;) {
 		ssize_t readlen;
 		int zret = Z_OK;
-		readlen = read_istream(st, ibuf, sizeof(ibuf));
+		readlen = odb_read_stream_read(st, ibuf, sizeof(ibuf));
 		if (readlen == -1)
 			die(_("unable to read %s"), oid_to_hex(oid));
 
@@ -513,17 +512,19 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent
 	unsigned hdrlen;
 	enum object_type type;
 	void *buf;
-	struct git_istream *st = NULL;
+	struct odb_read_stream *st = NULL;
 	const unsigned hashsz = the_hash_algo->rawsz;
 
 	if (!usable_delta) {
 		if (oe_type(entry) == OBJ_BLOB &&
 		    oe_size_greater_than(&to_pack, entry,
 					 repo_settings_get_big_file_threshold(the_repository)) &&
-		    (st = open_istream(the_repository, &entry->idx.oid, &type,
-				       &size, NULL)) != NULL)
+		    (st = odb_read_stream_open(the_repository->objects, &entry->idx.oid,
+					       NULL)) != NULL) {
 			buf = NULL;
-		else {
+			type = st->type;
+			size = st->size;
+		} else {
 			buf = odb_read_object(the_repository->objects,
 					      &entry->idx.oid, &type,
 					      &size);
@@ -577,7 +578,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent
 			dheader[--pos] = 128 | (--ofs & 127);
 		if (limit && hdrlen + sizeof(dheader) - pos + datalen + hashsz >= limit) {
 			if (st)
-				close_istream(st);
+				odb_read_stream_close(st);
 			free(buf);
 			return 0;
 		}
@@ -591,7 +592,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent
 		 */
 		if (limit && hdrlen + hashsz + datalen + hashsz >= limit) {
 			if (st)
-				close_istream(st);
+				odb_read_stream_close(st);
 			free(buf);
 			return 0;
 		}
@@ -601,7 +602,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent
 	} else {
 		if (limit && hdrlen + datalen + hashsz >= limit) {
 			if (st)
-				close_istream(st);
+				odb_read_stream_close(st);
 			free(buf);
 			return 0;
 		}
@@ -609,7 +610,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent
 	}
 	if (st) {
 		datalen = write_large_blob_data(st, f, &entry->idx.oid);
-		close_istream(st);
+		odb_read_stream_close(st);
 	} else {
 		hashwrite(f, buf, datalen);
 		free(buf);
@@ -831,15 +832,14 @@ static enum write_one_status write_one(struct hashfile *f,
 	return WRITE_ONE_WRITTEN;
 }
 
-static int mark_tagged(const char *path UNUSED, const char *referent UNUSED, const struct object_id *oid,
-		       int flag UNUSED, void *cb_data UNUSED)
+static int mark_tagged(const struct reference *ref, void *cb_data UNUSED)
 {
 	struct object_id peeled;
-	struct object_entry *entry = packlist_find(&to_pack, oid);
+	struct object_entry *entry = packlist_find(&to_pack, ref->oid);
 
 	if (entry)
 		entry->tagged = 1;
-	if (!peel_iterated_oid(the_repository, oid, &peeled)) {
+	if (!reference_get_peeled_oid(the_repository, ref, &peeled)) {
 		entry = packlist_find(&to_pack, &peeled);
 		if (entry)
 			entry->tagged = 1;
@@ -1528,49 +1528,54 @@ static int want_cruft_object_mtime(struct repository *r,
 				   const struct object_id *oid,
 				   unsigned flags, uint32_t mtime)
 {
-	struct packed_git **cache;
+	struct odb_source *source;
 
-	for (cache = kept_pack_cache(r, flags); *cache; cache++) {
-		struct packed_git *p = *cache;
-		off_t ofs;
-		uint32_t candidate_mtime;
+	for (source = r->objects->sources; source; source = source->next) {
+		struct odb_source_files *files = odb_source_files_downcast(source);
+		struct packed_git **cache = packfile_store_get_kept_pack_cache(files->packed, flags);
 
-		ofs = find_pack_entry_one(oid, p);
-		if (!ofs)
-			continue;
+		for (; *cache; cache++) {
+			struct packed_git *p = *cache;
+			off_t ofs;
+			uint32_t candidate_mtime;
 
-		/*
-		 * We have a copy of the object 'oid' in a non-cruft
-		 * pack. We can avoid packing an additional copy
-		 * regardless of what the existing copy's mtime is since
-		 * it is outside of a cruft pack.
-		 */
-		if (!p->is_cruft)
-			return 0;
-
-		/*
-		 * If we have a copy of the object 'oid' in a cruft
-		 * pack, then either read the cruft pack's mtime for
-		 * that object, or, if that can't be loaded, assume the
-		 * pack's mtime itself.
-		 */
-		if (!load_pack_mtimes(p)) {
-			uint32_t pos;
-			if (offset_to_pack_pos(p, ofs, &pos) < 0)
+			ofs = find_pack_entry_one(oid, p);
+			if (!ofs)
 				continue;
-			candidate_mtime = nth_packed_mtime(p, pos);
-		} else {
-			candidate_mtime = p->mtime;
-		}
 
-		/*
-		 * We have a surviving copy of the object in a cruft
-		 * pack whose mtime is greater than or equal to the one
-		 * we are considering. We can thus avoid packing an
-		 * additional copy of that object.
-		 */
-		if (mtime <= candidate_mtime)
-			return 0;
+			/*
+			 * We have a copy of the object 'oid' in a non-cruft
+			 * pack. We can avoid packing an additional copy
+			 * regardless of what the existing copy's mtime is since
+			 * it is outside of a cruft pack.
+			 */
+			if (!p->is_cruft)
+				return 0;
+
+			/*
+			 * If we have a copy of the object 'oid' in a cruft
+			 * pack, then either read the cruft pack's mtime for
+			 * that object, or, if that can't be loaded, assume the
+			 * pack's mtime itself.
+			 */
+			if (!load_pack_mtimes(p)) {
+				uint32_t pos;
+				if (offset_to_pack_pos(p, ofs, &pos) < 0)
+					continue;
+				candidate_mtime = nth_packed_mtime(p, pos);
+			} else {
+				candidate_mtime = p->mtime;
+			}
+
+			/*
+			 * We have a surviving copy of the object in a cruft
+			 * pack whose mtime is greater than or equal to the one
+			 * we are considering. We can thus avoid packing an
+			 * additional copy of that object.
+			 */
+			if (mtime <= candidate_mtime)
+				return 0;
+		}
 	}
 
 	return -1;
@@ -1623,9 +1628,9 @@ static int want_found_object(const struct object_id *oid, int exclude,
 		 */
 		unsigned flags = 0;
 		if (ignore_packed_keep_on_disk)
-			flags |= ON_DISK_KEEP_PACKS;
+			flags |= KEPT_PACK_ON_DISK;
 		if (ignore_packed_keep_in_core)
-			flags |= IN_CORE_KEEP_PACKS;
+			flags |= KEPT_PACK_IN_CORE;
 
 		/*
 		 * If the object is in a pack that we want to ignore, *and* we
@@ -1706,8 +1711,8 @@ static int want_object_in_pack_mtime(const struct object_id *oid,
 				     uint32_t found_mtime)
 {
 	int want;
+	struct packfile_list_entry *e;
 	struct odb_source *source;
-	struct list_head *pos;
 
 	if (!exclude && local) {
 		/*
@@ -1716,7 +1721,7 @@ static int want_object_in_pack_mtime(const struct object_id *oid,
 		 */
 		struct odb_source *source = the_repository->objects->sources->next;
 		for (; source; source = source->next)
-			if (has_loose_object(source, oid))
+			if (odb_source_loose_has_object(source, oid))
 				return 0;
 	}
 
@@ -1748,14 +1753,17 @@ static int want_object_in_pack_mtime(const struct object_id *oid,
 		}
 	}
 
-	list_for_each(pos, packfile_store_get_packs_mru(the_repository->objects->packfiles)) {
-		struct packed_git *p = list_entry(pos, struct packed_git, mru);
-		want = want_object_in_pack_one(p, oid, exclude, found_pack, found_offset, found_mtime);
-		if (!exclude && want > 0)
-			list_move(&p->mru,
-				  packfile_store_get_packs_mru(the_repository->objects->packfiles));
-		if (want != -1)
-			return want;
+	for (source = the_repository->objects->sources; source; source = source->next) {
+		struct odb_source_files *files = odb_source_files_downcast(source);
+
+		for (e = files->packed->packs.head; e; e = e->next) {
+			struct packed_git *p = e->pack;
+			want = want_object_in_pack_one(p, oid, exclude, found_pack, found_offset, found_mtime);
+			if (!exclude && want > 0)
+				packfile_list_prepend(&files->packed->packs, p);
+			if (want != -1)
+				return want;
+		}
 	}
 
 	if (uri_protocols.nr) {
@@ -2411,7 +2419,7 @@ static void drop_reused_delta(struct object_entry *entry)
 
 	oi.sizep = &size;
 	oi.typep = &type;
-	if (packed_object_info(the_repository, IN_PACK(entry), entry->in_pack_offset, &oi) < 0) {
+	if (packed_object_info(IN_PACK(entry), entry->in_pack_offset, &oi) < 0) {
 		/*
 		 * We failed to get the info from this pack for some reason;
 		 * fall back to odb_read_object_info, which may find another copy.
@@ -3293,7 +3301,7 @@ static void add_tag_chain(const struct object_id *oid)
 
 	tag = lookup_tag(the_repository, oid);
 	while (1) {
-		if (!tag || parse_tag(tag) || !tag->tagged)
+		if (!tag || parse_tag(the_repository, tag) || !tag->tagged)
 			die(_("unable to pack objects reachable from tag %s"),
 			    oid_to_hex(oid));
 
@@ -3306,13 +3314,13 @@ static void add_tag_chain(const struct object_id *oid)
 	}
 }
 
-static int add_ref_tag(const char *tag UNUSED, const char *referent UNUSED, const struct object_id *oid,
-		       int flag UNUSED, void *cb_data UNUSED)
+static int add_ref_tag(const struct reference *ref, void *cb_data UNUSED)
 {
 	struct object_id peeled;
 
-	if (!peel_iterated_oid(the_repository, oid, &peeled) && obj_is_packed(&peeled))
-		add_tag_chain(oid);
+	if (!reference_get_peeled_oid(the_repository, ref, &peeled) &&
+	    obj_is_packed(&peeled))
+		add_tag_chain(ref->oid);
 	return 0;
 }
 
@@ -3748,7 +3756,7 @@ static int add_object_entry_from_pack(const struct object_id *oid,
 		struct object_info oi = OBJECT_INFO_INIT;
 
 		oi.typep = &type;
-		if (packed_object_info(the_repository, p, ofs, &oi) < 0) {
+		if (packed_object_info(p, ofs, &oi) < 0) {
 			die(_("could not get type of object %s in pack %s"),
 			    oid_to_hex(oid), p->pack_name);
 		} else if (type == OBJ_COMMIT) {
@@ -3849,16 +3857,17 @@ static void read_packs_list_from_stdin(struct rev_info *revs)
 		strbuf_reset(&buf);
 	}
 
-	string_list_sort(&include_packs);
-	string_list_remove_duplicates(&include_packs, 0);
-	string_list_sort(&exclude_packs);
-	string_list_remove_duplicates(&exclude_packs, 0);
+	string_list_sort_u(&include_packs, 0);
+	string_list_sort_u(&exclude_packs, 0);
 
 	repo_for_each_pack(the_repository, p) {
 		const char *pack_name = pack_basename(p);
 
-		if ((item = string_list_lookup(&include_packs, pack_name)))
+		if ((item = string_list_lookup(&include_packs, pack_name))) {
+			if (exclude_promisor_objects && p->pack_promisor)
+				die(_("packfile %s is a promisor but --exclude-promisor-objects was given"), p->pack_name);
 			item->util = p;
+		}
 		if ((item = string_list_lookup(&exclude_packs, pack_name)))
 			item->util = p;
 	}
@@ -3906,7 +3915,7 @@ static void read_packs_list_from_stdin(struct rev_info *revs)
 		for_each_object_in_pack(p,
 					add_object_entry_from_pack,
 					revs,
-					FOR_EACH_OBJECT_PACK_ORDER);
+					ODB_FOR_EACH_OBJECT_PACK_ORDER);
 	}
 
 	strbuf_release(&buf);
@@ -3918,8 +3927,16 @@ static void add_unreachable_loose_objects(struct rev_info *revs);
 
 static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)
 {
+	int prev_fetch_if_missing = fetch_if_missing;
 	struct rev_info revs;
 
+	/*
+	 * The revision walk may hit objects that are promised, only. As the
+	 * walk is best-effort though we don't want to perform backfill fetches
+	 * for them.
+	 */
+	fetch_if_missing = 0;
+
 	repo_init_revisions(the_repository, &revs, NULL);
 	/*
 	 * Use a revision walk to fill in the namehash of objects in the include
@@ -3931,11 +3948,12 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)
 	 * an optimization during delta selection.
 	 */
 	revs.no_kept_objects = 1;
-	revs.keep_pack_cache_flags |= IN_CORE_KEEP_PACKS;
+	revs.keep_pack_cache_flags |= KEPT_PACK_IN_CORE;
 	revs.blob_objects = 1;
 	revs.tree_objects = 1;
 	revs.tag_objects = 1;
 	revs.ignore_missing_links = 1;
+	revs.exclude_promisor_objects = exclude_promisor_objects;
 
 	/* avoids adding objects in excluded packs */
 	ignore_packed_keep_in_core = 1;
@@ -3954,6 +3972,8 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)
 			   stdin_packs_found_nr);
 	trace2_data_intmax("pack-objects", the_repository, "stdin_packs_hints",
 			   stdin_packs_hints_nr);
+
+	fetch_if_missing = prev_fetch_if_missing;
 }
 
 static void add_cruft_object_entry(const struct object_id *oid, enum object_type type,
@@ -3978,7 +3998,7 @@ static void add_cruft_object_entry(const struct object_id *oid, enum object_type
 			int found = 0;
 
 			for (; !found && source; source = source->next)
-				if (has_loose_object(source, oid))
+				if (odb_source_loose_has_object(source, oid))
 					found = 1;
 
 			/*
@@ -4030,7 +4050,7 @@ static void show_cruft_commit(struct commit *commit, void *data)
 
 static int cruft_include_check_obj(struct object *obj, void *data UNUSED)
 {
-	return !has_object_kept_pack(to_pack.repo, &obj->oid, IN_CORE_KEEP_PACKS);
+	return !has_object_kept_pack(to_pack.repo, &obj->oid, KEPT_PACK_IN_CORE);
 }
 
 static int cruft_include_check(struct commit *commit, void *data)
@@ -4308,25 +4328,12 @@ static void show_edge(struct commit *commit)
 }
 
 static int add_object_in_unpacked_pack(const struct object_id *oid,
-				       struct packed_git *pack,
-				       uint32_t pos,
+				       struct object_info *oi,
 				       void *data UNUSED)
 {
 	if (cruft) {
-		off_t offset;
-		time_t mtime;
-
-		if (pack->is_cruft) {
-			if (load_pack_mtimes(pack) < 0)
-				die(_("could not load cruft pack .mtimes"));
-			mtime = nth_packed_mtime(pack, pos);
-		} else {
-			mtime = pack->mtime;
-		}
-		offset = nth_packed_object_offset(pack, pos);
-
-		add_cruft_object_entry(oid, OBJ_NONE, pack, offset,
-				       NULL, mtime);
+		add_cruft_object_entry(oid, OBJ_NONE, oi->u.packed.pack,
+				       oi->u.packed.offset, NULL, *oi->mtimep);
 	} else {
 		add_object_entry(oid, OBJ_NONE, "", 0);
 	}
@@ -4335,14 +4342,27 @@ static int add_object_in_unpacked_pack(const struct object_id *oid,
 
 static void add_objects_in_unpacked_packs(void)
 {
-	if (for_each_packed_object(to_pack.repo,
-				   add_object_in_unpacked_pack,
-				   NULL,
-				   FOR_EACH_OBJECT_PACK_ORDER |
-				   FOR_EACH_OBJECT_LOCAL_ONLY |
-				   FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |
-				   FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))
-		die(_("cannot open pack index"));
+	struct odb_source *source;
+	time_t mtime;
+	struct object_info oi = {
+		.mtimep = &mtime,
+	};
+
+	odb_prepare_alternates(to_pack.repo->objects);
+	for (source = to_pack.repo->objects->sources; source; source = source->next) {
+		struct odb_source_files *files = odb_source_files_downcast(source);
+
+		if (!source->local)
+			continue;
+
+		if (packfile_store_for_each_object(files->packed, &oi,
+						   add_object_in_unpacked_pack, NULL,
+						   ODB_FOR_EACH_OBJECT_PACK_ORDER |
+						   ODB_FOR_EACH_OBJECT_LOCAL_ONLY |
+						   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |
+						   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))
+			die(_("cannot open pack index"));
+	}
 }
 
 static int add_loose_object(const struct object_id *oid, const char *path,
@@ -4389,27 +4409,27 @@ static void add_unreachable_loose_objects(struct rev_info *revs)
 
 static int has_sha1_pack_kept_or_nonlocal(const struct object_id *oid)
 {
-	struct packfile_store *packs = the_repository->objects->packfiles;
-	static struct packed_git *last_found = (void *)1;
+	static struct packed_git *last_found = NULL;
 	struct packed_git *p;
 
-	p = (last_found != (void *)1) ? last_found :
-					packfile_store_get_packs(packs);
+	if (last_found && find_pack_entry_one(oid, last_found))
+		return 1;
 
-	while (p) {
-		if ((!p->pack_local || p->pack_keep ||
-				p->pack_keep_in_core) &&
-			find_pack_entry_one(oid, p)) {
+	repo_for_each_pack(the_repository, p) {
+		/*
+		 * We have already checked `last_found`, so there is no need to
+		 * re-check here.
+		 */
+		if (p == last_found)
+			continue;
+
+		if ((!p->pack_local || p->pack_keep || p->pack_keep_in_core) &&
+		    find_pack_entry_one(oid, p)) {
 			last_found = p;
 			return 1;
 		}
-		if (p == last_found)
-			p = packfile_store_get_packs(packs);
-		else
-			p = p->next;
-		if (p == last_found)
-			p = p->next;
 	}
+
 	return 0;
 }
 
@@ -4528,41 +4548,22 @@ static void record_recent_commit(struct commit *commit, void *data UNUSED)
 	oid_array_append(&recent_objects, &commit->object.oid);
 }
 
-static int mark_bitmap_preferred_tip(const char *refname,
-				     const char *referent UNUSED,
-				     const struct object_id *oid,
-				     int flags UNUSED,
-				     void *data UNUSED)
+static int mark_bitmap_preferred_tip(const struct reference *ref, void *data UNUSED)
 {
+	const struct object_id *maybe_peeled = ref->oid;
 	struct object_id peeled;
 	struct object *object;
 
-	if (!peel_iterated_oid(the_repository, oid, &peeled))
-		oid = &peeled;
+	if (!reference_get_peeled_oid(the_repository, ref, &peeled))
+		maybe_peeled = &peeled;
 
-	object = parse_object_or_die(the_repository, oid, refname);
+	object = parse_object_or_die(the_repository, maybe_peeled, ref->name);
 	if (object->type == OBJ_COMMIT)
 		object->flags |= NEEDS_BITMAP;
 
 	return 0;
 }
 
-static void mark_bitmap_preferred_tips(void)
-{
-	struct string_list_item *item;
-	const struct string_list *preferred_tips;
-
-	preferred_tips = bitmap_preferred_tips(the_repository);
-	if (!preferred_tips)
-		return;
-
-	for_each_string_list_item(item, preferred_tips) {
-		refs_for_each_ref_in(get_main_ref_store(the_repository),
-				     item->string, mark_bitmap_preferred_tip,
-				     NULL);
-	}
-}
-
 static inline int is_oid_uninteresting(struct repository *repo,
 				       struct object_id *oid)
 {
@@ -4703,7 +4704,8 @@ static void get_object_list(struct rev_info *revs, struct strvec *argv)
 		load_delta_islands(the_repository, progress);
 
 	if (write_bitmap_index)
-		mark_bitmap_preferred_tips();
+		for_each_preferred_bitmap_tip(the_repository, mark_bitmap_preferred_tip,
+					      NULL);
 
 	if (!fn_show_object)
 		fn_show_object = show_object;
@@ -4925,8 +4927,6 @@ int cmd_pack_objects(int argc,
 		OPT_CALLBACK_F(0, "stdin-packs", &stdin_packs, N_("mode"),
 			     N_("read packs from stdin"),
 			     PARSE_OPT_OPTARG, parse_stdin_packs_mode),
-		OPT_BOOL(0, "stdin-packs", &stdin_packs,
-			 N_("read packs from stdin")),
 		OPT_BOOL(0, "stdout", &pack_to_stdout,
 			 N_("output pack to stdout")),
 		OPT_BOOL(0, "include-tag", &include_tag,
@@ -5095,9 +5095,13 @@ int cmd_pack_objects(int argc,
 				  exclude_promisor_objects_best_effort,
 				  "--exclude-promisor-objects-best-effort");
 	if (exclude_promisor_objects) {
-		use_internal_rev_list = 1;
 		fetch_if_missing = 0;
-		strvec_push(&rp, "--exclude-promisor-objects");
+
+		/* --stdin-packs handles promisor objects separately. */
+		if (!stdin_packs) {
+			use_internal_rev_list = 1;
+			strvec_push(&rp, "--exclude-promisor-objects");
+		}
 	} else if (exclude_promisor_objects_best_effort) {
 		use_internal_rev_list = 1;
 		fetch_if_missing = 0;
diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c
index e4ecf77..86749bb 100644
--- a/builtin/pack-redundant.c
+++ b/builtin/pack-redundant.c
@@ -546,8 +546,10 @@ static struct pack_list * add_pack(struct packed_git *p)
 	l.pack = p;
 	llist_init(&l.remaining_objects);
 
-	if (open_pack_index(p))
+	if (open_pack_index(p)) {
+		llist_free(l.remaining_objects);
 		return NULL;
+	}
 
 	base = p->index_data;
 	base += 256 * 4 + ((p->index_version < 2) ? 4 : 8);
diff --git a/builtin/patch-id.c b/builtin/patch-id.c
index d26e9d0..2781598 100644
--- a/builtin/patch-id.c
+++ b/builtin/patch-id.c
@@ -228,9 +228,9 @@ int cmd_patch_id(int argc,
 	int opts = 0;
 	struct option builtin_patch_id_options[] = {
 		OPT_CMDMODE(0, "unstable", &opts,
-		    N_("use the unstable patch-id algorithm"), 1),
+		    N_("use the unstable patch ID algorithm"), 1),
 		OPT_CMDMODE(0, "stable", &opts,
-		    N_("use the stable patch-id algorithm"), 2),
+		    N_("use the stable patch ID algorithm"), 2),
 		OPT_CMDMODE(0, "verbatim", &opts,
 			N_("don't strip whitespace from the patch"), 3),
 		OPT_END()
diff --git a/builtin/pull.c b/builtin/pull.c
index 5ebd529..6ad420c 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -119,148 +119,6 @@ static int opt_show_forced_updates = -1;
 static const char *set_upstream;
 static struct strvec opt_fetch = STRVEC_INIT;
 
-static struct option pull_options[] = {
-	/* Shared options */
-	OPT__VERBOSITY(&opt_verbosity),
-	OPT_PASSTHRU(0, "progress", &opt_progress, NULL,
-		N_("force progress reporting"),
-		PARSE_OPT_NOARG),
-	OPT_CALLBACK_F(0, "recurse-submodules",
-		   &recurse_submodules_cli, N_("on-demand"),
-		   N_("control for recursive fetching of submodules"),
-		   PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),
-
-	/* Options passed to git-merge or git-rebase */
-	OPT_GROUP(N_("Options related to merging")),
-	OPT_CALLBACK_F('r', "rebase", &opt_rebase,
-		"(false|true|merges|interactive)",
-		N_("incorporate changes by rebasing rather than merging"),
-		PARSE_OPT_OPTARG, parse_opt_rebase),
-	OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,
-		N_("do not show a diffstat at the end of the merge"),
-		PARSE_OPT_NOARG | PARSE_OPT_NONEG),
-	OPT_PASSTHRU(0, "stat", &opt_diffstat, NULL,
-		N_("show a diffstat at the end of the merge"),
-		PARSE_OPT_NOARG),
-	OPT_PASSTHRU(0, "summary", &opt_diffstat, NULL,
-		N_("(synonym to --stat)"),
-		PARSE_OPT_NOARG | PARSE_OPT_HIDDEN),
-	OPT_PASSTHRU(0, "compact-summary", &opt_diffstat, NULL,
-		N_("show a compact-summary at the end of the merge"),
-		PARSE_OPT_NOARG),
-	OPT_PASSTHRU(0, "log", &opt_log, N_("n"),
-		N_("add (at most <n>) entries from shortlog to merge commit message"),
-		PARSE_OPT_OPTARG),
-	OPT_PASSTHRU(0, "signoff", &opt_signoff, NULL,
-		N_("add a Signed-off-by trailer"),
-		PARSE_OPT_OPTARG),
-	OPT_PASSTHRU(0, "squash", &opt_squash, NULL,
-		N_("create a single commit instead of doing a merge"),
-		PARSE_OPT_NOARG),
-	OPT_PASSTHRU(0, "commit", &opt_commit, NULL,
-		N_("perform a commit if the merge succeeds (default)"),
-		PARSE_OPT_NOARG),
-	OPT_PASSTHRU(0, "edit", &opt_edit, NULL,
-		N_("edit message before committing"),
-		PARSE_OPT_NOARG),
-	OPT_CLEANUP(&cleanup_arg),
-	OPT_PASSTHRU(0, "ff", &opt_ff, NULL,
-		N_("allow fast-forward"),
-		PARSE_OPT_NOARG),
-	OPT_PASSTHRU(0, "ff-only", &opt_ff, NULL,
-		N_("abort if fast-forward is not possible"),
-		PARSE_OPT_NOARG | PARSE_OPT_NONEG),
-	OPT_PASSTHRU(0, "verify", &opt_verify, NULL,
-		N_("control use of pre-merge-commit and commit-msg hooks"),
-		PARSE_OPT_NOARG),
-	OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL,
-		N_("verify that the named commit has a valid GPG signature"),
-		PARSE_OPT_NOARG),
-	OPT_BOOL(0, "autostash", &opt_autostash,
-		N_("automatically stash/stash pop before and after")),
-	OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"),
-		N_("merge strategy to use"),
-		0),
-	OPT_PASSTHRU_ARGV('X', "strategy-option", &opt_strategy_opts,
-		N_("option=value"),
-		N_("option for selected merge strategy"),
-		0),
-	OPT_PASSTHRU('S', "gpg-sign", &opt_gpg_sign, N_("key-id"),
-		N_("GPG sign commit"),
-		PARSE_OPT_OPTARG),
-	OPT_SET_INT(0, "allow-unrelated-histories",
-		    &opt_allow_unrelated_histories,
-		    N_("allow merging unrelated histories"), 1),
-
-	/* Options passed to git-fetch */
-	OPT_GROUP(N_("Options related to fetching")),
-	OPT_PASSTHRU(0, "all", &opt_all, NULL,
-		N_("fetch from all remotes"),
-		PARSE_OPT_NOARG),
-	OPT_PASSTHRU('a', "append", &opt_append, NULL,
-		N_("append to .git/FETCH_HEAD instead of overwriting"),
-		PARSE_OPT_NOARG),
-	OPT_PASSTHRU(0, "upload-pack", &opt_upload_pack, N_("path"),
-		N_("path to upload pack on remote end"),
-		0),
-	OPT__FORCE(&opt_force, N_("force overwrite of local branch"), 0),
-	OPT_PASSTHRU('t', "tags", &opt_tags, NULL,
-		N_("fetch all tags and associated objects"),
-		PARSE_OPT_NOARG),
-	OPT_PASSTHRU('p', "prune", &opt_prune, NULL,
-		N_("prune remote-tracking branches no longer on remote"),
-		PARSE_OPT_NOARG),
-	OPT_PASSTHRU('j', "jobs", &max_children, N_("n"),
-		N_("number of submodules pulled in parallel"),
-		PARSE_OPT_OPTARG),
-	OPT_BOOL(0, "dry-run", &opt_dry_run,
-		N_("dry run")),
-	OPT_PASSTHRU('k', "keep", &opt_keep, NULL,
-		N_("keep downloaded pack"),
-		PARSE_OPT_NOARG),
-	OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"),
-		N_("deepen history of shallow clone"),
-		0),
-	OPT_PASSTHRU_ARGV(0, "shallow-since", &opt_fetch, N_("time"),
-		N_("deepen history of shallow repository based on time"),
-		0),
-	OPT_PASSTHRU_ARGV(0, "shallow-exclude", &opt_fetch, N_("ref"),
-		N_("deepen history of shallow clone, excluding ref"),
-		0),
-	OPT_PASSTHRU_ARGV(0, "deepen", &opt_fetch, N_("n"),
-		N_("deepen history of shallow clone"),
-		0),
-	OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL,
-		N_("convert to a complete repository"),
-		PARSE_OPT_NONEG | PARSE_OPT_NOARG),
-	OPT_PASSTHRU(0, "update-shallow", &opt_update_shallow, NULL,
-		N_("accept refs that update .git/shallow"),
-		PARSE_OPT_NOARG),
-	OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"),
-		N_("specify fetch refmap"),
-		PARSE_OPT_NONEG),
-	OPT_PASSTHRU_ARGV('o', "server-option", &opt_fetch,
-		N_("server-specific"),
-		N_("option to transmit"),
-		0),
-	OPT_PASSTHRU('4',  "ipv4", &opt_ipv4, NULL,
-		N_("use IPv4 addresses only"),
-		PARSE_OPT_NOARG),
-	OPT_PASSTHRU('6',  "ipv6", &opt_ipv6, NULL,
-		N_("use IPv6 addresses only"),
-		PARSE_OPT_NOARG),
-	OPT_PASSTHRU_ARGV(0, "negotiation-tip", &opt_fetch, N_("revision"),
-		N_("report that we have only objects reachable from this object"),
-		0),
-	OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates,
-		 N_("check for forced-updates on all updated branches")),
-	OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL,
-		N_("set upstream for git pull/fetch"),
-		PARSE_OPT_NOARG),
-
-	OPT_END()
-};
-
 /**
  * Pushes "-q" or "-v" switches into arr to match the opt_verbosity level.
  */
@@ -846,14 +704,14 @@ static int get_octopus_merge_base(struct object_id *merge_base,
 
 	if (get_octopus_merge_bases(revs, &result) < 0)
 		exit(128);
-	free_commit_list(revs);
+	commit_list_free(revs);
 	reduce_heads_replace(&result);
 
 	if (!result)
 		return 1;
 
 	oidcpy(merge_base, &result->item->object.oid);
-	free_commit_list(result);
+	commit_list_free(result);
 	return 0;
 }
 
@@ -945,7 +803,7 @@ static int get_can_ff(struct object_id *orig_head,
 	commit_list_insert(head, &list);
 	merge_head = lookup_commit_reference(the_repository, orig_merge_head);
 	ret = repo_is_descendant_of(the_repository, merge_head, list);
-	free_commit_list(list);
+	commit_list_free(list);
 	if (ret < 0)
 		exit(128);
 	return ret;
@@ -970,7 +828,7 @@ static int already_up_to_date(struct object_id *orig_head,
 		theirs = lookup_commit_reference(the_repository, &merge_heads->oid[i]);
 		commit_list_insert(theirs, &list);
 		ok = repo_is_descendant_of(the_repository, ours, list);
-		free_commit_list(list);
+		commit_list_free(list);
 		if (ok < 0)
 			exit(128);
 		if (!ok)
@@ -1008,6 +866,147 @@ int cmd_pull(int argc,
 	int can_ff;
 	int divergent;
 	int ret;
+	static struct option pull_options[] = {
+		/* Shared options */
+		OPT__VERBOSITY(&opt_verbosity),
+		OPT_PASSTHRU(0, "progress", &opt_progress, NULL,
+			N_("force progress reporting"),
+			PARSE_OPT_NOARG),
+		OPT_CALLBACK_F(0, "recurse-submodules",
+			   &recurse_submodules_cli, N_("on-demand"),
+			   N_("control for recursive fetching of submodules"),
+			   PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),
+
+		/* Options passed to git-merge or git-rebase */
+		OPT_GROUP(N_("Options related to merging")),
+		OPT_CALLBACK_F('r', "rebase", &opt_rebase,
+			"(false|true|merges|interactive)",
+			N_("incorporate changes by rebasing rather than merging"),
+			PARSE_OPT_OPTARG, parse_opt_rebase),
+		OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,
+			N_("do not show a diffstat at the end of the merge"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG),
+		OPT_PASSTHRU(0, "stat", &opt_diffstat, NULL,
+			N_("show a diffstat at the end of the merge"),
+			PARSE_OPT_NOARG),
+		OPT_PASSTHRU(0, "summary", &opt_diffstat, NULL,
+			N_("(synonym to --stat)"),
+			PARSE_OPT_NOARG | PARSE_OPT_HIDDEN),
+		OPT_PASSTHRU(0, "compact-summary", &opt_diffstat, NULL,
+			N_("show a compact-summary at the end of the merge"),
+			PARSE_OPT_NOARG),
+		OPT_PASSTHRU(0, "log", &opt_log, N_("n"),
+			N_("add (at most <n>) entries from shortlog to merge commit message"),
+			PARSE_OPT_OPTARG),
+		OPT_PASSTHRU(0, "signoff", &opt_signoff, NULL,
+			N_("add a Signed-off-by trailer"),
+			PARSE_OPT_OPTARG),
+		OPT_PASSTHRU(0, "squash", &opt_squash, NULL,
+			N_("create a single commit instead of doing a merge"),
+			PARSE_OPT_NOARG),
+		OPT_PASSTHRU(0, "commit", &opt_commit, NULL,
+			N_("perform a commit if the merge succeeds (default)"),
+			PARSE_OPT_NOARG),
+		OPT_PASSTHRU(0, "edit", &opt_edit, NULL,
+			N_("edit message before committing"),
+			PARSE_OPT_NOARG),
+		OPT_CLEANUP(&cleanup_arg),
+		OPT_PASSTHRU(0, "ff", &opt_ff, NULL,
+			N_("allow fast-forward"),
+			PARSE_OPT_NOARG),
+		OPT_PASSTHRU(0, "ff-only", &opt_ff, NULL,
+			N_("abort if fast-forward is not possible"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG),
+		OPT_PASSTHRU(0, "verify", &opt_verify, NULL,
+			N_("control use of pre-merge-commit and commit-msg hooks"),
+			PARSE_OPT_NOARG),
+		OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL,
+			N_("verify that the named commit has a valid GPG signature"),
+			PARSE_OPT_NOARG),
+		OPT_BOOL(0, "autostash", &opt_autostash,
+			N_("automatically stash/stash pop before and after")),
+		OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"),
+			N_("merge strategy to use"),
+			0),
+		OPT_PASSTHRU_ARGV('X', "strategy-option", &opt_strategy_opts,
+			N_("option=value"),
+			N_("option for selected merge strategy"),
+			0),
+		OPT_PASSTHRU('S', "gpg-sign", &opt_gpg_sign, N_("key-id"),
+			N_("GPG sign commit"),
+			PARSE_OPT_OPTARG),
+		OPT_SET_INT(0, "allow-unrelated-histories",
+			    &opt_allow_unrelated_histories,
+			    N_("allow merging unrelated histories"), 1),
+
+		/* Options passed to git-fetch */
+		OPT_GROUP(N_("Options related to fetching")),
+		OPT_PASSTHRU(0, "all", &opt_all, NULL,
+			N_("fetch from all remotes"),
+			PARSE_OPT_NOARG),
+		OPT_PASSTHRU('a', "append", &opt_append, NULL,
+			N_("append to .git/FETCH_HEAD instead of overwriting"),
+			PARSE_OPT_NOARG),
+		OPT_PASSTHRU(0, "upload-pack", &opt_upload_pack, N_("path"),
+			N_("path to upload pack on remote end"),
+			0),
+		OPT__FORCE(&opt_force, N_("force overwrite of local branch"), 0),
+		OPT_PASSTHRU('t', "tags", &opt_tags, NULL,
+			N_("fetch all tags and associated objects"),
+			PARSE_OPT_NOARG),
+		OPT_PASSTHRU('p', "prune", &opt_prune, NULL,
+			N_("prune remote-tracking branches no longer on remote"),
+			PARSE_OPT_NOARG),
+		OPT_PASSTHRU('j', "jobs", &max_children, N_("n"),
+			N_("number of submodules pulled in parallel"),
+			PARSE_OPT_OPTARG),
+		OPT_BOOL(0, "dry-run", &opt_dry_run,
+			N_("dry run")),
+		OPT_PASSTHRU('k', "keep", &opt_keep, NULL,
+			N_("keep downloaded pack"),
+			PARSE_OPT_NOARG),
+		OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"),
+			N_("deepen history of shallow clone"),
+			0),
+		OPT_PASSTHRU_ARGV(0, "shallow-since", &opt_fetch, N_("time"),
+			N_("deepen history of shallow repository based on time"),
+			0),
+		OPT_PASSTHRU_ARGV(0, "shallow-exclude", &opt_fetch, N_("ref"),
+			N_("deepen history of shallow clone, excluding ref"),
+			0),
+		OPT_PASSTHRU_ARGV(0, "deepen", &opt_fetch, N_("n"),
+			N_("deepen history of shallow clone"),
+			0),
+		OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL,
+			N_("convert to a complete repository"),
+			PARSE_OPT_NONEG | PARSE_OPT_NOARG),
+		OPT_PASSTHRU(0, "update-shallow", &opt_update_shallow, NULL,
+			N_("accept refs that update .git/shallow"),
+			PARSE_OPT_NOARG),
+		OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"),
+			N_("specify fetch refmap"),
+			PARSE_OPT_NONEG),
+		OPT_PASSTHRU_ARGV('o', "server-option", &opt_fetch,
+			N_("server-specific"),
+			N_("option to transmit"),
+			0),
+		OPT_PASSTHRU('4',  "ipv4", &opt_ipv4, NULL,
+			N_("use IPv4 addresses only"),
+			PARSE_OPT_NOARG),
+		OPT_PASSTHRU('6',  "ipv6", &opt_ipv6, NULL,
+			N_("use IPv6 addresses only"),
+			PARSE_OPT_NOARG),
+		OPT_PASSTHRU_ARGV(0, "negotiation-tip", &opt_fetch, N_("revision"),
+			N_("report that we have only objects reachable from this object"),
+			0),
+		OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates,
+			 N_("check for forced-updates on all updated branches")),
+		OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL,
+			N_("set upstream for git pull/fetch"),
+			PARSE_OPT_NOARG),
+
+		OPT_END()
+	};
 
 	if (!getenv("GIT_REFLOG_ACTION"))
 		set_reflog_message(argc, argv);
diff --git a/builtin/push.c b/builtin/push.c
index 5b6cebb..7100ffb 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -151,6 +151,7 @@ static NORETURN void die_push_simple(struct branch *branch,
 	const char *advice_pushdefault_maybe = "";
 	const char *advice_automergesimple_maybe = "";
 	const char *short_upstream = branch->merge[0]->src;
+	struct repo_config_values *cfg = repo_config_values(the_repository);
 
 	skip_prefix(short_upstream, "refs/heads/", &short_upstream);
 
@@ -162,7 +163,7 @@ static NORETURN void die_push_simple(struct branch *branch,
 		advice_pushdefault_maybe = _("\n"
 				 "To choose either option permanently, "
 				 "see push.default in 'git help config'.\n");
-	if (git_branch_track != BRANCH_TRACK_SIMPLE)
+	if (cfg->branch_track != BRANCH_TRACK_SIMPLE)
 		advice_automergesimple_maybe = _("\n"
 				 "To avoid automatically configuring "
 				 "an upstream branch when its name\n"
diff --git a/builtin/read-tree.c b/builtin/read-tree.c
index 34f7a59..460b21e 100644
--- a/builtin/read-tree.c
+++ b/builtin/read-tree.c
@@ -32,7 +32,7 @@ static int list_tree(struct object_id *oid)
 
 	if (nr_trees >= MAX_UNPACK_TREES)
 		die("I cannot read more than %d trees", MAX_UNPACK_TREES);
-	tree = parse_tree_indirect(oid);
+	tree = repo_parse_tree_indirect(the_repository, oid);
 	if (!tree)
 		return -1;
 	trees[nr_trees++] = tree;
@@ -268,7 +268,7 @@ int cmd_read_tree(int argc,
 	cache_tree_free(&the_repository->index->cache_tree);
 	for (i = 0; i < nr_trees; i++) {
 		struct tree *tree = trees[i];
-		if (parse_tree(tree) < 0)
+		if (repo_parse_tree(the_repository, tree) < 0)
 			return 128;
 		init_tree_desc(t+i, &tree->object.oid, tree->buffer, tree->size);
 	}
diff --git a/builtin/rebase.c b/builtin/rebase.c
index c468828..c487e10 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -912,7 +912,7 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,
 	res = 1;
 
 done:
-	free_commit_list(merge_bases);
+	commit_list_free(merge_bases);
 	return res && is_linear_history(onto, head);
 }
 
@@ -929,7 +929,7 @@ static void fill_branch_base(struct rebase_options *options,
 	else
 		oidcpy(branch_base, &merge_bases->item->object.oid);
 
-	free_commit_list(merge_bases);
+	commit_list_free(merge_bases);
 }
 
 static int parse_opt_am(const struct option *opt, const char *arg, int unset)
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index c9288a9..d6225df 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -34,7 +34,6 @@
 #include "object-file.h"
 #include "object-name.h"
 #include "odb.h"
-#include "path.h"
 #include "protocol.h"
 #include "commit-reach.h"
 #include "server-info.h"
@@ -42,6 +41,7 @@
 #include "trace2.h"
 #include "worktree.h"
 #include "shallow.h"
+#include "setup.h"
 #include "parse-options.h"
 
 static const char * const receive_pack_usage[] = {
@@ -177,8 +177,9 @@ static int receive_pack_config(const char *var, const char *value,
 
 		if (git_config_pathname(&path, var, value))
 			return -1;
-		strbuf_addf(&fsck_msg_types, "%cskiplist=%s",
-			fsck_msg_types.len ? ',' : '=', path);
+		if (path)
+			strbuf_addf(&fsck_msg_types, "%cskiplist=%s",
+				    fsck_msg_types.len ? ',' : '=', path);
 		free(path);
 		return 0;
 	}
@@ -305,13 +306,12 @@ static void show_ref(const char *path, const struct object_id *oid)
 	}
 }
 
-static int show_ref_cb(const char *path_full, const char *referent UNUSED, const struct object_id *oid,
-		       int flag UNUSED, void *data)
+static int show_ref_cb(const struct reference *ref, void *data)
 {
 	struct oidset *seen = data;
-	const char *path = strip_namespace(path_full);
+	const char *path = strip_namespace(ref->name);
 
-	if (ref_is_hidden(path, path_full, &hidden_refs))
+	if (ref_is_hidden(path, ref->name, &hidden_refs))
 		return 0;
 
 	/*
@@ -320,13 +320,13 @@ static int show_ref_cb(const char *path_full, const char *referent UNUSED, const
 	 * transfer but will otherwise ignore them.
 	 */
 	if (!path) {
-		if (oidset_insert(seen, oid))
+		if (oidset_insert(seen, ref->oid))
 			return 0;
 		path = ".have";
 	} else {
-		oidset_insert(seen, oid);
+		oidset_insert(seen, ref->oid);
 	}
-	show_ref(path, oid);
+	show_ref(path, ref->oid);
 	return 0;
 }
 
@@ -343,9 +343,9 @@ static void show_one_alternate_ref(const struct object_id *oid,
 
 static void write_head_info(void)
 {
+	struct refs_for_each_ref_options opts = { 0 };
 	static struct oidset seen = OIDSET_INIT;
 	struct strvec excludes_vector = STRVEC_INIT;
-	const char **exclude_patterns;
 
 	/*
 	 * We need access to the reference names both with and without their
@@ -353,12 +353,12 @@ static void write_head_info(void)
 	 * thus have to adapt exclude patterns to carry the namespace prefix
 	 * ourselves.
 	 */
-	exclude_patterns = get_namespaced_exclude_patterns(
+	opts.exclude_patterns = get_namespaced_exclude_patterns(
 		hidden_refs_to_excludes(&hidden_refs),
 		get_git_namespace(), &excludes_vector);
 
-	refs_for_each_fullref_in(get_main_ref_store(the_repository), "",
-				 exclude_patterns, show_ref_cb, &seen);
+	refs_for_each_ref_ext(get_main_ref_store(the_repository),
+			      show_ref_cb, &seen, &opts);
 	odb_for_each_alternate_ref(the_repository->objects,
 				   show_one_alternate_ref, &seen);
 
@@ -393,7 +393,7 @@ struct command {
 static void proc_receive_ref_append(const char *prefix)
 {
 	struct proc_receive_ref *ref_pattern;
-	char *p;
+	const char *p;
 	int len;
 
 	CALLOC_ARRAY(ref_pattern, 1);
@@ -561,6 +561,48 @@ static int copy_to_sideband(int in, int out UNUSED, void *arg UNUSED)
 	return 0;
 }
 
+/*
+ * Start an async thread which redirects hook stderr over the sideband.
+ * The original stderr fd is saved to `saved_stderr` and STDERR_FILENO is
+ * redirected to the async's input pipe.
+ */
+static void prepare_sideband_async(struct async *sideband_async, int *saved_stderr, int *started)
+{
+	*started = 0;
+
+	if (!use_sideband)
+		return;
+
+	memset(sideband_async, 0, sizeof(*sideband_async));
+	sideband_async->proc = copy_to_sideband;
+	sideband_async->in = -1;
+
+	if (!start_async(sideband_async)) {
+		*started = 1;
+		*saved_stderr = dup(STDERR_FILENO);
+		if (*saved_stderr >= 0)
+			dup2(sideband_async->in, STDERR_FILENO);
+		close(sideband_async->in);
+	}
+}
+
+/*
+ * Restore the original stderr and wait for the async sideband thread to finish.
+ */
+static void finish_sideband_async(struct async *sideband_async, int saved_stderr, int started)
+{
+	if (!use_sideband)
+		return;
+
+	if (saved_stderr >= 0) {
+		dup2(saved_stderr, STDERR_FILENO);
+		close(saved_stderr);
+	}
+
+	if (started)
+		finish_async(sideband_async);
+}
+
 static void hmac_hash(unsigned char *out,
 		      const char *key_in, size_t key_len,
 		      const char *text, size_t text_len)
@@ -749,7 +791,7 @@ static int check_cert_push_options(const struct string_list *push_options)
 	return retval;
 }
 
-static void prepare_push_cert_sha1(struct child_process *proc)
+static void prepare_push_cert_sha1(struct run_hooks_opt *opt)
 {
 	static int already_done;
 
@@ -775,23 +817,23 @@ static void prepare_push_cert_sha1(struct child_process *proc)
 		nonce_status = check_nonce(sigcheck.payload);
 	}
 	if (!is_null_oid(&push_cert_oid)) {
-		strvec_pushf(&proc->env, "GIT_PUSH_CERT=%s",
+		strvec_pushf(&opt->env, "GIT_PUSH_CERT=%s",
 			     oid_to_hex(&push_cert_oid));
-		strvec_pushf(&proc->env, "GIT_PUSH_CERT_SIGNER=%s",
+		strvec_pushf(&opt->env, "GIT_PUSH_CERT_SIGNER=%s",
 			     sigcheck.signer ? sigcheck.signer : "");
-		strvec_pushf(&proc->env, "GIT_PUSH_CERT_KEY=%s",
+		strvec_pushf(&opt->env, "GIT_PUSH_CERT_KEY=%s",
 			     sigcheck.key ? sigcheck.key : "");
-		strvec_pushf(&proc->env, "GIT_PUSH_CERT_STATUS=%c",
+		strvec_pushf(&opt->env, "GIT_PUSH_CERT_STATUS=%c",
 			     sigcheck.result);
 		if (push_cert_nonce) {
-			strvec_pushf(&proc->env,
+			strvec_pushf(&opt->env,
 				     "GIT_PUSH_CERT_NONCE=%s",
 				     push_cert_nonce);
-			strvec_pushf(&proc->env,
+			strvec_pushf(&opt->env,
 				     "GIT_PUSH_CERT_NONCE_STATUS=%s",
 				     nonce_status);
 			if (nonce_status == NONCE_SLOP)
-				strvec_pushf(&proc->env,
+				strvec_pushf(&opt->env,
 					     "GIT_PUSH_CERT_NONCE_SLOP=%ld",
 					     nonce_stamp_slop);
 		}
@@ -803,94 +845,25 @@ struct receive_hook_feed_state {
 	struct ref_push_report *report;
 	int skip_broken;
 	struct strbuf buf;
-	const struct string_list *push_options;
 };
 
-typedef int (*feed_fn)(void *, const char **, size_t *);
-static int run_and_feed_hook(const char *hook_name, feed_fn feed,
-			     struct receive_hook_feed_state *feed_state)
+static int feed_receive_hook_cb(int hook_stdin_fd, void *pp_cb UNUSED, void *pp_task_cb)
 {
-	struct child_process proc = CHILD_PROCESS_INIT;
-	struct async muxer;
-	int code;
-	const char *hook_path = find_hook(the_repository, hook_name);
-
-	if (!hook_path)
-		return 0;
-
-	strvec_push(&proc.args, hook_path);
-	proc.in = -1;
-	proc.stdout_to_stderr = 1;
-	proc.trace2_hook_name = hook_name;
-
-	if (feed_state->push_options) {
-		size_t i;
-		for (i = 0; i < feed_state->push_options->nr; i++)
-			strvec_pushf(&proc.env,
-				     "GIT_PUSH_OPTION_%"PRIuMAX"=%s",
-				     (uintmax_t)i,
-				     feed_state->push_options->items[i].string);
-		strvec_pushf(&proc.env, "GIT_PUSH_OPTION_COUNT=%"PRIuMAX"",
-			     (uintmax_t)feed_state->push_options->nr);
-	} else
-		strvec_pushf(&proc.env, "GIT_PUSH_OPTION_COUNT");
-
-	if (tmp_objdir)
-		strvec_pushv(&proc.env, tmp_objdir_env(tmp_objdir));
-
-	if (use_sideband) {
-		memset(&muxer, 0, sizeof(muxer));
-		muxer.proc = copy_to_sideband;
-		muxer.in = -1;
-		code = start_async(&muxer);
-		if (code)
-			return code;
-		proc.err = muxer.in;
-	}
-
-	prepare_push_cert_sha1(&proc);
-
-	code = start_command(&proc);
-	if (code) {
-		if (use_sideband)
-			finish_async(&muxer);
-		return code;
-	}
-
-	sigchain_push(SIGPIPE, SIG_IGN);
-
-	while (1) {
-		const char *buf;
-		size_t n;
-		if (feed(feed_state, &buf, &n))
-			break;
-		if (write_in_full(proc.in, buf, n) < 0)
-			break;
-	}
-	close(proc.in);
-	if (use_sideband)
-		finish_async(&muxer);
-
-	sigchain_pop(SIGPIPE);
-
-	return finish_command(&proc);
-}
-
-static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
-{
-	struct receive_hook_feed_state *state = state_;
+	struct receive_hook_feed_state *state = pp_task_cb;
 	struct command *cmd = state->cmd;
 
+	strbuf_reset(&state->buf);
+
 	while (cmd &&
 	       state->skip_broken && (cmd->error_string || cmd->did_not_exist))
 		cmd = cmd->next;
+
 	if (!cmd)
-		return -1; /* EOF */
-	if (!bufp)
-		return 0; /* OK, can feed something. */
-	strbuf_reset(&state->buf);
+		return 1;  /* no more commands left */
+
 	if (!state->report)
 		state->report = cmd->report;
+
 	if (state->report) {
 		struct object_id *old_oid;
 		struct object_id *new_oid;
@@ -899,23 +872,53 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
 		old_oid = state->report->old_oid ? state->report->old_oid : &cmd->old_oid;
 		new_oid = state->report->new_oid ? state->report->new_oid : &cmd->new_oid;
 		ref_name = state->report->ref_name ? state->report->ref_name : cmd->ref_name;
+
 		strbuf_addf(&state->buf, "%s %s %s\n",
 			    oid_to_hex(old_oid), oid_to_hex(new_oid),
 			    ref_name);
+
 		state->report = state->report->next;
 		if (!state->report)
-			state->cmd = cmd->next;
+			cmd = cmd->next;
 	} else {
 		strbuf_addf(&state->buf, "%s %s %s\n",
 			    oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),
 			    cmd->ref_name);
-		state->cmd = cmd->next;
+		cmd = cmd->next;
 	}
-	if (bufp) {
-		*bufp = state->buf.buf;
-		*sizep = state->buf.len;
+
+	state->cmd = cmd;
+
+	if (state->buf.len > 0) {
+		int ret = write_in_full(hook_stdin_fd, state->buf.buf, state->buf.len);
+		if (ret < 0) {
+			if (errno == EPIPE)
+				return 1; /* child closed pipe */
+			return ret;
+		}
 	}
-	return 0;
+
+	return state->cmd ? 0 : 1;  /* 0 = more to come, 1 = EOF */
+}
+
+static void *receive_hook_feed_state_alloc(void *feed_pipe_ctx)
+{
+	struct receive_hook_feed_state *init_state = feed_pipe_ctx;
+	struct receive_hook_feed_state *data = xcalloc(1, sizeof(*data));
+	data->report = init_state->report;
+	data->cmd = init_state->cmd;
+	data->skip_broken = init_state->skip_broken;
+	strbuf_init(&data->buf, 0);
+	return data;
+}
+
+static void receive_hook_feed_state_free(void *data)
+{
+	struct receive_hook_feed_state *d = data;
+	if (!d)
+		return;
+	strbuf_release(&d->buf);
+	free(d);
 }
 
 static int run_receive_hook(struct command *commands,
@@ -923,47 +926,80 @@ static int run_receive_hook(struct command *commands,
 			    int skip_broken,
 			    const struct string_list *push_options)
 {
-	struct receive_hook_feed_state state;
-	int status;
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+	struct command *iter = commands;
+	struct receive_hook_feed_state feed_init_state = { 0 };
+	struct async sideband_async;
+	int sideband_async_started = 0;
+	int saved_stderr = -1;
+	int ret;
 
-	strbuf_init(&state.buf, 0);
-	state.cmd = commands;
-	state.skip_broken = skip_broken;
-	state.report = NULL;
-	if (feed_receive_hook(&state, NULL, NULL))
+	if (!hook_exists(the_repository, hook_name))
 		return 0;
-	state.cmd = commands;
-	state.push_options = push_options;
-	status = run_and_feed_hook(hook_name, feed_receive_hook, &state);
-	strbuf_release(&state.buf);
-	return status;
+
+	/* if there are no valid commands, don't invoke the hook at all. */
+	while (iter && skip_broken && (iter->error_string || iter->did_not_exist))
+		iter = iter->next;
+	if (!iter)
+		return 0;
+
+	if (push_options) {
+		for (int i = 0; i < push_options->nr; i++)
+			strvec_pushf(&opt.env, "GIT_PUSH_OPTION_%d=%s", i,
+				     push_options->items[i].string);
+		strvec_pushf(&opt.env, "GIT_PUSH_OPTION_COUNT=%"PRIuMAX"",
+					     (uintmax_t)push_options->nr);
+	} else {
+		strvec_push(&opt.env, "GIT_PUSH_OPTION_COUNT");
+	}
+
+	if (tmp_objdir)
+		strvec_pushv(&opt.env, tmp_objdir_env(tmp_objdir));
+
+	prepare_push_cert_sha1(&opt);
+
+	prepare_sideband_async(&sideband_async, &saved_stderr, &sideband_async_started);
+
+	/* set up stdin callback */
+	feed_init_state.cmd = commands;
+	feed_init_state.skip_broken = skip_broken;
+	opt.feed_pipe_ctx = &feed_init_state;
+	opt.feed_pipe = feed_receive_hook_cb;
+	opt.feed_pipe_cb_data_alloc = receive_hook_feed_state_alloc;
+	opt.feed_pipe_cb_data_free = receive_hook_feed_state_free;
+
+	ret = run_hooks_opt(the_repository, hook_name, &opt);
+
+	finish_sideband_async(&sideband_async, saved_stderr, sideband_async_started);
+
+	return ret;
 }
 
 static int run_update_hook(struct command *cmd)
 {
-	struct child_process proc = CHILD_PROCESS_INIT;
+	static const char hook_name[] = "update";
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+	struct async sideband_async;
+	int sideband_async_started = 0;
+	int saved_stderr = -1;
 	int code;
-	const char *hook_path = find_hook(the_repository, "update");
 
-	if (!hook_path)
+	if (!hook_exists(the_repository, hook_name))
 		return 0;
 
-	strvec_push(&proc.args, hook_path);
-	strvec_push(&proc.args, cmd->ref_name);
-	strvec_push(&proc.args, oid_to_hex(&cmd->old_oid));
-	strvec_push(&proc.args, oid_to_hex(&cmd->new_oid));
+	strvec_pushl(&opt.args,
+		     cmd->ref_name,
+		     oid_to_hex(&cmd->old_oid),
+		     oid_to_hex(&cmd->new_oid),
+		     NULL);
 
-	proc.no_stdin = 1;
-	proc.stdout_to_stderr = 1;
-	proc.err = use_sideband ? -1 : 0;
-	proc.trace2_hook_name = "update";
+	prepare_sideband_async(&sideband_async, &saved_stderr, &sideband_async_started);
 
-	code = start_command(&proc);
-	if (code)
-		return code;
-	if (use_sideband)
-		copy_to_sideband(proc.err, -1, NULL);
-	return finish_command(&proc);
+	code = run_hooks_opt(the_repository, hook_name, &opt);
+
+	finish_sideband_async(&sideband_async, saved_stderr, sideband_async_started);
+
+	return code;
 }
 
 static struct command *find_command_by_refname(struct command *list,
@@ -1639,34 +1675,29 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 
 static void run_update_post_hook(struct command *commands)
 {
+	static const char hook_name[] = "post-update";
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+	struct async sideband_async;
 	struct command *cmd;
-	struct child_process proc = CHILD_PROCESS_INIT;
-	const char *hook;
+	int sideband_async_started = 0;
+	int saved_stderr = -1;
 
-	hook = find_hook(the_repository, "post-update");
-	if (!hook)
+	if (!hook_exists(the_repository, hook_name))
 		return;
 
 	for (cmd = commands; cmd; cmd = cmd->next) {
 		if (cmd->error_string || cmd->did_not_exist)
 			continue;
-		if (!proc.args.nr)
-			strvec_push(&proc.args, hook);
-		strvec_push(&proc.args, cmd->ref_name);
+		strvec_push(&opt.args, cmd->ref_name);
 	}
-	if (!proc.args.nr)
+	if (!opt.args.nr)
 		return;
 
-	proc.no_stdin = 1;
-	proc.stdout_to_stderr = 1;
-	proc.err = use_sideband ? -1 : 0;
-	proc.trace2_hook_name = "post-update";
+	prepare_sideband_async(&sideband_async, &saved_stderr, &sideband_async_started);
 
-	if (!start_command(&proc)) {
-		if (use_sideband)
-			copy_to_sideband(proc.err, -1, NULL);
-		finish_command(&proc);
-	}
+	run_hooks_opt(the_repository, hook_name, &opt);
+
+	finish_sideband_async(&sideband_async, saved_stderr, sideband_async_started);
 }
 
 static void check_aliased_update_internal(struct command *cmd,
@@ -1853,10 +1884,14 @@ static void ref_transaction_rejection_handler(const char *refname,
 					      const char *old_target UNUSED,
 					      const char *new_target UNUSED,
 					      enum ref_transaction_error err,
+					      const char *details,
 					      void *cb_data)
 {
 	struct strmap *failed_refs = cb_data;
 
+	if (details)
+		rp_error("%s", details);
+
 	strmap_put(failed_refs, refname, (char *)ref_transaction_error_msg(err));
 }
 
@@ -1923,6 +1958,7 @@ static void execute_commands_non_atomic(struct command *commands,
 		}
 
 		ref_transaction_for_each_rejected_update(transaction,
+
 							 ref_transaction_rejection_handler,
 							 &failed_refs);
 
@@ -1934,7 +1970,7 @@ static void execute_commands_non_atomic(struct command *commands,
 			if (reported_error)
 				cmd->error_string = reported_error;
 			else if (strmap_contains(&failed_refs, cmd->ref_name))
-				cmd->error_string = strmap_get(&failed_refs, cmd->ref_name);
+				cmd->error_string = cmd->error_string_owned = xstrdup(strmap_get(&failed_refs, cmd->ref_name));
 		}
 
 	cleanup:
diff --git a/builtin/remote.c b/builtin/remote.c
index 8a7ed42..0fddaa1 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -332,7 +332,7 @@ static int config_read_branches(const char *key, const char *value,
 		info->remote_name = xstrdup(value);
 		break;
 	case MERGE: {
-		char *space = strchr(value, ' ');
+		const char *space = strchr(value, ' ');
 		value = abbrev_branch(value);
 		while (space) {
 			char *merge;
@@ -570,17 +570,14 @@ struct branches_for_remote {
 	struct known_remotes *keep;
 };
 
-static int add_branch_for_removal(const char *refname,
-				  const char *referent UNUSED,
-				  const struct object_id *oid UNUSED,
-				  int flags UNUSED, void *cb_data)
+static int add_branch_for_removal(const struct reference *ref, void *cb_data)
 {
 	struct branches_for_remote *branches = cb_data;
 	struct refspec_item refspec;
 	struct known_remote *kr;
 
 	memset(&refspec, 0, sizeof(refspec));
-	refspec.dst = (char *)refname;
+	refspec.dst = (char *)ref->name;
 	if (remote_find_tracking(branches->remote, &refspec))
 		return 0;
 	free(refspec.src);
@@ -588,7 +585,7 @@ static int add_branch_for_removal(const char *refname,
 	/* don't delete a branch if another remote also uses it */
 	for (kr = branches->keep->list; kr; kr = kr->next) {
 		memset(&refspec, 0, sizeof(refspec));
-		refspec.dst = (char *)refname;
+		refspec.dst = (char *)ref->name;
 		if (!remote_find_tracking(kr->remote, &refspec)) {
 			free(refspec.src);
 			return 0;
@@ -596,16 +593,16 @@ static int add_branch_for_removal(const char *refname,
 	}
 
 	/* don't delete non-remote-tracking refs */
-	if (!starts_with(refname, "refs/remotes/")) {
+	if (!starts_with(ref->name, "refs/remotes/")) {
 		/* advise user how to delete local branches */
-		if (starts_with(refname, "refs/heads/"))
+		if (starts_with(ref->name, "refs/heads/"))
 			string_list_append(branches->skipped,
-					   abbrev_branch(refname));
+					   abbrev_branch(ref->name));
 		/* silently skip over other non-remote refs */
 		return 0;
 	}
 
-	string_list_append(branches->branches, refname);
+	string_list_append(branches->branches, ref->name);
 
 	return 0;
 }
@@ -713,18 +710,18 @@ static int rename_one_reflog(const char *old_refname,
 	return error;
 }
 
-static int rename_one_ref(const char *old_refname, const char *referent,
-			  const struct object_id *oid,
-			  int flags, void *cb_data)
+static int rename_one_ref(const struct reference *ref, void *cb_data)
 {
 	struct strbuf new_referent = STRBUF_INIT;
 	struct strbuf new_refname = STRBUF_INIT;
 	struct rename_info *rename = cb_data;
+	const struct object_id *oid = ref->oid;
+	const char *referent = ref->target;
 	int error;
 
-	compute_renamed_ref(rename, old_refname, &new_refname);
+	compute_renamed_ref(rename, ref->name, &new_refname);
 
-	if (flags & REF_ISSYMREF) {
+	if (ref->flags & REF_ISSYMREF) {
 		/*
 		 * Stupidly enough `referent` is not pointing to the immediate
 		 * target of a symref, but it's the recursively resolved value.
@@ -732,25 +729,25 @@ static int rename_one_ref(const char *old_refname, const char *referent,
 		 * unborn symrefs don't have any value for the `referent` at all.
 		 */
 		referent = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
-						   old_refname, RESOLVE_REF_NO_RECURSE,
+						   ref->name, RESOLVE_REF_NO_RECURSE,
 						   NULL, NULL);
 		compute_renamed_ref(rename, referent, &new_referent);
 		oid = NULL;
 	}
 
-	error = ref_transaction_delete(rename->transaction, old_refname,
+	error = ref_transaction_delete(rename->transaction, ref->name,
 				       oid, referent, REF_NO_DEREF, NULL, rename->err);
 	if (error < 0)
 		goto out;
 
 	error = ref_transaction_update(rename->transaction, new_refname.buf, oid, null_oid(the_hash_algo),
-				       (flags & REF_ISSYMREF) ? new_referent.buf : NULL, NULL,
+				       (ref->flags & REF_ISSYMREF) ? new_referent.buf : NULL, NULL,
 				       REF_SKIP_CREATE_REFLOG | REF_NO_DEREF | REF_SKIP_OID_VERIFICATION,
 				       NULL, rename->err);
 	if (error < 0)
 		goto out;
 
-	error = rename_one_reflog(old_refname, oid, rename);
+	error = rename_one_reflog(ref->name, oid, rename);
 	if (error < 0)
 		goto out;
 
@@ -915,6 +912,9 @@ static int mv(int argc, const char **argv, const char *prefix,
 						old_remote_context.buf);
 
 	if (refspecs_need_update) {
+		struct refs_for_each_ref_options opts = {
+			.flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+		};
 		rename.transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
 							       0, &err);
 		if (!rename.transaction)
@@ -926,9 +926,10 @@ static int mv(int argc, const char **argv, const char *prefix,
 
 		strbuf_reset(&buf);
 		strbuf_addf(&buf, "refs/remotes/%s/", rename.old_name);
+		opts.prefix = buf.buf;
 
-		result = refs_for_each_rawref_in(get_main_ref_store(the_repository), buf.buf,
-				rename_one_ref, &rename);
+		result = refs_for_each_ref_ext(get_main_ref_store(the_repository),
+					       rename_one_ref, &rename, &opts);
 		if (result < 0)
 			die(_("queueing remote ref renames failed: %s"), rename.err->buf);
 
@@ -1125,19 +1126,16 @@ static void free_remote_ref_states(struct ref_states *states)
 	string_list_clear_func(&states->push, clear_push_info);
 }
 
-static int append_ref_to_tracked_list(const char *refname,
-				      const char *referent UNUSED,
-				      const struct object_id *oid UNUSED,
-				      int flags, void *cb_data)
+static int append_ref_to_tracked_list(const struct reference *ref, void *cb_data)
 {
 	struct ref_states *states = cb_data;
 	struct refspec_item refspec;
 
-	if (flags & REF_ISSYMREF)
+	if (ref->flags & REF_ISSYMREF)
 		return 0;
 
 	memset(&refspec, 0, sizeof(refspec));
-	refspec.dst = (char *)refname;
+	refspec.dst = (char *)ref->name;
 	if (!remote_find_tracking(states->remote, &refspec)) {
 		string_list_append(&states->tracked, abbrev_branch(refspec.src));
 		free(refspec.src);
diff --git a/builtin/repack.c b/builtin/repack.c
index cfdb4c0..f6bb04b 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -332,6 +332,9 @@ int cmd_repack(int argc,
 		    !(pack_everything & PACK_CRUFT))
 			strvec_push(&cmd.args, "--pack-loose-unreachable");
 	} else if (geometry.split_factor) {
+		pack_geometry_repack_promisors(repo, &po_args, &geometry,
+					       &names, packtmp);
+
 		if (midx_must_contain_cruft)
 			strvec_push(&cmd.args, "--stdin-packs");
 		else
@@ -488,7 +491,7 @@ int cmd_repack(int argc,
 
 	string_list_sort(&names);
 
-	close_object_store(repo->objects);
+	odb_close(repo->objects);
 
 	/*
 	 * Ok we have prepared all new packfiles.
diff --git a/builtin/replace.c b/builtin/replace.c
index 900b560..4c62c5a 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -47,30 +47,27 @@ struct show_data {
 	enum replace_format format;
 };
 
-static int show_reference(const char *refname,
-			  const char *referent UNUSED,
-			  const struct object_id *oid,
-			  int flag UNUSED, void *cb_data)
+static int show_reference(const struct reference *ref, void *cb_data)
 {
 	struct show_data *data = cb_data;
 
-	if (!wildmatch(data->pattern, refname, 0)) {
+	if (!wildmatch(data->pattern, ref->name, 0)) {
 		if (data->format == REPLACE_FORMAT_SHORT)
-			printf("%s\n", refname);
+			printf("%s\n", ref->name);
 		else if (data->format == REPLACE_FORMAT_MEDIUM)
-			printf("%s -> %s\n", refname, oid_to_hex(oid));
+			printf("%s -> %s\n", ref->name, oid_to_hex(ref->oid));
 		else { /* data->format == REPLACE_FORMAT_LONG */
 			struct object_id object;
 			enum object_type obj_type, repl_type;
 
-			if (repo_get_oid(data->repo, refname, &object))
-				return error(_("failed to resolve '%s' as a valid ref"), refname);
+			if (repo_get_oid(data->repo, ref->name, &object))
+				return error(_("failed to resolve '%s' as a valid ref"), ref->name);
 
 			obj_type = odb_read_object_info(data->repo->objects, &object, NULL);
-			repl_type = odb_read_object_info(data->repo->objects, oid, NULL);
+			repl_type = odb_read_object_info(data->repo->objects, ref->oid, NULL);
 
-			printf("%s (%s) -> %s (%s)\n", refname, type_name(obj_type),
-			       oid_to_hex(oid), type_name(repl_type));
+			printf("%s (%s) -> %s (%s)\n", ref->name, type_name(obj_type),
+			       oid_to_hex(ref->oid), type_name(repl_type));
 		}
 	}
 
diff --git a/builtin/replay.c b/builtin/replay.c
index 6172c8a..2cdde83 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -2,286 +2,68 @@
  * "git replay" builtin command
  */
 
-#define USE_THE_REPOSITORY_VARIABLE
-#define DISABLE_SIGN_COMPARE_WARNINGS
-
 #include "git-compat-util.h"
 
 #include "builtin.h"
-#include "environment.h"
+#include "config.h"
 #include "hex.h"
-#include "lockfile.h"
-#include "merge-ort.h"
 #include "object-name.h"
 #include "parse-options.h"
 #include "refs.h"
+#include "replay.h"
 #include "revision.h"
-#include "strmap.h"
-#include <oidset.h>
-#include <tree.h>
 
-static const char *short_commit_name(struct repository *repo,
-				     struct commit *commit)
-{
-	return repo_find_unique_abbrev(repo, &commit->object.oid,
-				       DEFAULT_ABBREV);
-}
-
-static struct commit *peel_committish(struct repository *repo, const char *name)
-{
-	struct object *obj;
-	struct object_id oid;
-
-	if (repo_get_oid(repo, name, &oid))
-		return NULL;
-	obj = parse_object(repo, &oid);
-	return (struct commit *)repo_peel_to_type(repo, name, 0, obj,
-						  OBJ_COMMIT);
-}
-
-static char *get_author(const char *message)
-{
-	size_t len;
-	const char *a;
-
-	a = find_commit_header(message, "author", &len);
-	if (a)
-		return xmemdupz(a, len);
-
-	return NULL;
-}
-
-static struct commit *create_commit(struct repository *repo,
-				    struct tree *tree,
-				    struct commit *based_on,
-				    struct commit *parent)
-{
-	struct object_id ret;
-	struct object *obj = NULL;
-	struct commit_list *parents = NULL;
-	char *author;
-	char *sign_commit = NULL; /* FIXME: cli users might want to sign again */
-	struct commit_extra_header *extra = NULL;
-	struct strbuf msg = STRBUF_INIT;
-	const char *out_enc = get_commit_output_encoding();
-	const char *message = repo_logmsg_reencode(repo, based_on,
-						   NULL, out_enc);
-	const char *orig_message = NULL;
-	const char *exclude_gpgsig[] = { "gpgsig", NULL };
-
-	commit_list_insert(parent, &parents);
-	extra = read_commit_extra_headers(based_on, exclude_gpgsig);
-	find_commit_subject(message, &orig_message);
-	strbuf_addstr(&msg, orig_message);
-	author = get_author(message);
-	reset_ident_date();
-	if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,
-				 &ret, author, NULL, sign_commit, extra)) {
-		error(_("failed to write commit object"));
-		goto out;
-	}
-
-	obj = parse_object(repo, &ret);
-
-out:
-	repo_unuse_commit_buffer(the_repository, based_on, message);
-	free_commit_extra_headers(extra);
-	free_commit_list(parents);
-	strbuf_release(&msg);
-	free(author);
-	return (struct commit *)obj;
-}
-
-struct ref_info {
-	struct commit *onto;
-	struct strset positive_refs;
-	struct strset negative_refs;
-	int positive_refexprs;
-	int negative_refexprs;
+enum ref_action_mode {
+	REF_ACTION_UPDATE,
+	REF_ACTION_PRINT,
 };
 
-static void get_ref_information(struct repository *repo,
-				struct rev_cmdline_info *cmd_info,
-				struct ref_info *ref_info)
+static enum ref_action_mode parse_ref_action_mode(const char *ref_action, const char *source)
 {
-	int i;
-
-	ref_info->onto = NULL;
-	strset_init(&ref_info->positive_refs);
-	strset_init(&ref_info->negative_refs);
-	ref_info->positive_refexprs = 0;
-	ref_info->negative_refexprs = 0;
-
-	/*
-	 * When the user specifies e.g.
-	 *   git replay origin/main..mybranch
-	 *   git replay ^origin/next mybranch1 mybranch2
-	 * we want to be able to determine where to replay the commits.  In
-	 * these examples, the branches are probably based on an old version
-	 * of either origin/main or origin/next, so we want to replay on the
-	 * newest version of that branch.  In contrast we would want to error
-	 * out if they ran
-	 *   git replay ^origin/master ^origin/next mybranch
-	 *   git replay mybranch~2..mybranch
-	 * the first of those because there's no unique base to choose, and
-	 * the second because they'd likely just be replaying commits on top
-	 * of the same commit and not making any difference.
-	 */
-	for (i = 0; i < cmd_info->nr; i++) {
-		struct rev_cmdline_entry *e = cmd_info->rev + i;
-		struct object_id oid;
-		const char *refexpr = e->name;
-		char *fullname = NULL;
-		int can_uniquely_dwim = 1;
-
-		if (*refexpr == '^')
-			refexpr++;
-		if (repo_dwim_ref(repo, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1)
-			can_uniquely_dwim = 0;
-
-		if (e->flags & BOTTOM) {
-			if (can_uniquely_dwim)
-				strset_add(&ref_info->negative_refs, fullname);
-			if (!ref_info->negative_refexprs)
-				ref_info->onto = lookup_commit_reference_gently(repo,
-										&e->item->oid, 1);
-			ref_info->negative_refexprs++;
-		} else {
-			if (can_uniquely_dwim)
-				strset_add(&ref_info->positive_refs, fullname);
-			ref_info->positive_refexprs++;
-		}
-
-		free(fullname);
-	}
+	if (!ref_action || !strcmp(ref_action, "update"))
+		return REF_ACTION_UPDATE;
+	if (!strcmp(ref_action, "print"))
+		return REF_ACTION_PRINT;
+	die(_("invalid %s value: '%s'"), source, ref_action);
 }
 
-static void determine_replay_mode(struct repository *repo,
-				  struct rev_cmdline_info *cmd_info,
-				  const char *onto_name,
-				  char **advance_name,
-				  struct commit **onto,
-				  struct strset **update_refs)
+static enum ref_action_mode get_ref_action_mode(struct repository *repo, const char *ref_action)
 {
-	struct ref_info rinfo;
+	const char *config_value = NULL;
 
-	get_ref_information(repo, cmd_info, &rinfo);
-	if (!rinfo.positive_refexprs)
-		die(_("need some commits to replay"));
+	/* Command line option takes precedence */
+	if (ref_action)
+		return parse_ref_action_mode(ref_action, "--ref-action");
 
-	die_for_incompatible_opt2(!!onto_name, "--onto",
-				  !!*advance_name, "--advance");
-	if (onto_name) {
-		*onto = peel_committish(repo, onto_name);
-		if (rinfo.positive_refexprs <
-		    strset_get_size(&rinfo.positive_refs))
-			die(_("all positive revisions given must be references"));
-	} else if (*advance_name) {
-		struct object_id oid;
-		char *fullname = NULL;
+	/* Check config value */
+	if (!repo_config_get_string_tmp(repo, "replay.refAction", &config_value))
+		return parse_ref_action_mode(config_value, "replay.refAction");
 
-		*onto = peel_committish(repo, *advance_name);
-		if (repo_dwim_ref(repo, *advance_name, strlen(*advance_name),
-			     &oid, &fullname, 0) == 1) {
-			free(*advance_name);
-			*advance_name = fullname;
-		} else {
-			die(_("argument to --advance must be a reference"));
-		}
-		if (rinfo.positive_refexprs > 1)
-			die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
-	} else {
-		int positive_refs_complete = (
-			rinfo.positive_refexprs ==
-			strset_get_size(&rinfo.positive_refs));
-		int negative_refs_complete = (
-			rinfo.negative_refexprs ==
-			strset_get_size(&rinfo.negative_refs));
-		/*
-		 * We need either positive_refs_complete or
-		 * negative_refs_complete, but not both.
-		 */
-		if (rinfo.negative_refexprs > 0 &&
-		    positive_refs_complete == negative_refs_complete)
-			die(_("cannot implicitly determine whether this is an --advance or --onto operation"));
-		if (negative_refs_complete) {
-			struct hashmap_iter iter;
-			struct strmap_entry *entry;
-			const char *last_key = NULL;
-
-			if (rinfo.negative_refexprs == 0)
-				die(_("all positive revisions given must be references"));
-			else if (rinfo.negative_refexprs > 1)
-				die(_("cannot implicitly determine whether this is an --advance or --onto operation"));
-			else if (rinfo.positive_refexprs > 1)
-				die(_("cannot advance target with multiple source branches because ordering would be ill-defined"));
-
-			/* Only one entry, but we have to loop to get it */
-			strset_for_each_entry(&rinfo.negative_refs,
-					      &iter, entry) {
-				last_key = entry->key;
-			}
-
-			free(*advance_name);
-			*advance_name = xstrdup_or_null(last_key);
-		} else { /* positive_refs_complete */
-			if (rinfo.negative_refexprs > 1)
-				die(_("cannot implicitly determine correct base for --onto"));
-			if (rinfo.negative_refexprs == 1)
-				*onto = rinfo.onto;
-		}
-	}
-	if (!*advance_name) {
-		*update_refs = xcalloc(1, sizeof(**update_refs));
-		**update_refs = rinfo.positive_refs;
-		memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
-	}
-	strset_clear(&rinfo.negative_refs);
-	strset_clear(&rinfo.positive_refs);
+	/* Default to update mode */
+	return REF_ACTION_UPDATE;
 }
 
-static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,
-				    struct commit *commit,
-				    struct commit *fallback)
+static int handle_ref_update(enum ref_action_mode mode,
+			     struct ref_transaction *transaction,
+			     const char *refname,
+			     const struct object_id *new_oid,
+			     const struct object_id *old_oid,
+			     const char *reflog_msg,
+			     struct strbuf *err)
 {
-	khint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);
-	if (pos == kh_end(replayed_commits))
-		return fallback;
-	return kh_value(replayed_commits, pos);
-}
-
-static struct commit *pick_regular_commit(struct repository *repo,
-					  struct commit *pickme,
-					  kh_oid_map_t *replayed_commits,
-					  struct commit *onto,
-					  struct merge_options *merge_opt,
-					  struct merge_result *result)
-{
-	struct commit *base, *replayed_base;
-	struct tree *pickme_tree, *base_tree;
-
-	base = pickme->parents->item;
-	replayed_base = mapped_commit(replayed_commits, base, onto);
-
-	result->tree = repo_get_commit_tree(repo, replayed_base);
-	pickme_tree = repo_get_commit_tree(repo, pickme);
-	base_tree = repo_get_commit_tree(repo, base);
-
-	merge_opt->branch1 = short_commit_name(repo, replayed_base);
-	merge_opt->branch2 = short_commit_name(repo, pickme);
-	merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
-
-	merge_incore_nonrecursive(merge_opt,
-				  base_tree,
-				  result->tree,
-				  pickme_tree,
-				  result);
-
-	free((char*)merge_opt->ancestor);
-	merge_opt->ancestor = NULL;
-	if (!result->clean)
-		return NULL;
-	return create_commit(repo, result->tree, pickme, replayed_base);
+	switch (mode) {
+	case REF_ACTION_PRINT:
+		printf("update %s %s %s\n",
+		       refname,
+		       oid_to_hex(new_oid),
+		       oid_to_hex(old_oid));
+		return 0;
+	case REF_ACTION_UPDATE:
+		return ref_transaction_update(transaction, refname, new_oid, old_oid,
+					      NULL, NULL, 0, reflog_msg, err);
+	default:
+		BUG("unknown ref_action_mode %d", mode);
+	}
 }
 
 int cmd_replay(int argc,
@@ -289,51 +71,52 @@ int cmd_replay(int argc,
 	       const char *prefix,
 	       struct repository *repo)
 {
-	const char *advance_name_opt = NULL;
-	char *advance_name = NULL;
-	struct commit *onto = NULL;
-	const char *onto_name = NULL;
-	int contained = 0;
-
+	struct replay_revisions_options opts = { 0 };
+	struct replay_result result = { 0 };
+	const char *ref_action = NULL;
+	enum ref_action_mode ref_mode;
 	struct rev_info revs;
-	struct commit *last_commit = NULL;
-	struct commit *commit;
-	struct merge_options merge_opt;
-	struct merge_result result;
-	struct strset *update_refs = NULL;
-	kh_oid_map_t *replayed_commits;
+	struct ref_transaction *transaction = NULL;
+	struct strbuf transaction_err = STRBUF_INIT;
+	struct strbuf reflog_msg = STRBUF_INIT;
 	int ret = 0;
 
-	const char * const replay_usage[] = {
+	const char *const replay_usage[] = {
 		N_("(EXPERIMENTAL!) git replay "
 		   "([--contained] --onto <newbase> | --advance <branch>) "
-		   "<revision-range>..."),
+		   "[--ref-action[=<mode>]] <revision-range>"),
 		NULL
 	};
 	struct option replay_options[] = {
-		OPT_STRING(0, "advance", &advance_name_opt,
+		OPT_STRING(0, "advance", &opts.advance,
 			   N_("branch"),
 			   N_("make replay advance given branch")),
-		OPT_STRING(0, "onto", &onto_name,
+		OPT_STRING(0, "onto", &opts.onto,
 			   N_("revision"),
 			   N_("replay onto given commit")),
-		OPT_BOOL(0, "contained", &contained,
-			 N_("advance all branches contained in revision-range")),
+		OPT_BOOL(0, "contained", &opts.contained,
+			 N_("update all branches that point at commits in <revision-range>")),
+		OPT_STRING(0, "ref-action", &ref_action,
+			   N_("mode"),
+			   N_("control ref update behavior (update|print)")),
 		OPT_END()
 	};
 
 	argc = parse_options(argc, argv, prefix, replay_options, replay_usage,
 			     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
 
-	if (!onto_name && !advance_name_opt) {
+	if (!opts.onto && !opts.advance) {
 		error(_("option --onto or --advance is mandatory"));
 		usage_with_options(replay_usage, replay_options);
 	}
 
-	if (advance_name_opt && contained)
-		die(_("options '%s' and '%s' cannot be used together"),
-		    "--advance", "--contained");
-	advance_name = xstrdup_or_null(advance_name_opt);
+	die_for_incompatible_opt2(!!opts.advance, "--advance",
+				  opts.contained, "--contained");
+	die_for_incompatible_opt2(!!opts.advance, "--advance",
+				  !!opts.onto, "--onto");
+
+	/* Parse ref action mode from command line or config */
+	ref_mode = get_ref_action_mode(repo, ref_action);
 
 	repo_init_revisions(repo, &revs, prefix);
 
@@ -386,85 +169,63 @@ int cmd_replay(int argc,
 		revs.simplify_history = 0;
 	}
 
-	determine_replay_mode(repo, &revs.cmdline, onto_name, &advance_name,
-			      &onto, &update_refs);
-
-	if (!onto) /* FIXME: Should handle replaying down to root commit */
-		die("Replaying down to root commit is not supported yet!");
-
-	if (prepare_revision_walk(&revs) < 0) {
-		ret = error(_("error preparing revisions"));
+	ret = replay_revisions(&revs, &opts, &result);
+	if (ret)
 		goto cleanup;
+
+	/* Build reflog message */
+	if (opts.advance) {
+		strbuf_addf(&reflog_msg, "replay --advance %s", opts.advance);
+	} else {
+		struct object_id oid;
+		if (repo_get_oid_committish(repo, opts.onto, &oid))
+			BUG("--onto commit should have been resolved beforehand already");
+		strbuf_addf(&reflog_msg, "replay --onto %s", oid_to_hex(&oid));
 	}
 
-	init_basic_merge_options(&merge_opt, repo);
-	memset(&result, 0, sizeof(result));
-	merge_opt.show_rename_progress = 0;
-	last_commit = onto;
-	replayed_commits = kh_init_oid_map();
-	while ((commit = get_revision(&revs))) {
-		const struct name_decoration *decoration;
-		khint_t pos;
-		int hr;
-
-		if (!commit->parents)
-			die(_("replaying down to root commit is not supported yet!"));
-		if (commit->parents->next)
-			die(_("replaying merge commits is not supported yet!"));
-
-		last_commit = pick_regular_commit(repo, commit, replayed_commits,
-						  onto, &merge_opt, &result);
-		if (!last_commit)
-			break;
-
-		/* Record commit -> last_commit mapping */
-		pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);
-		if (hr == 0)
-			BUG("Duplicate rewritten commit: %s\n",
-			    oid_to_hex(&commit->object.oid));
-		kh_value(replayed_commits, pos) = last_commit;
-
-		/* Update any necessary branches */
-		if (advance_name)
-			continue;
-		decoration = get_name_decoration(&commit->object);
-		if (!decoration)
-			continue;
-		while (decoration) {
-			if (decoration->type == DECORATION_REF_LOCAL &&
-			    (contained || strset_contains(update_refs,
-							  decoration->name))) {
-				printf("update %s %s %s\n",
-				       decoration->name,
-				       oid_to_hex(&last_commit->object.oid),
-				       oid_to_hex(&commit->object.oid));
-			}
-			decoration = decoration->next;
+	/* Initialize ref transaction if using update mode */
+	if (ref_mode == REF_ACTION_UPDATE) {
+		transaction = ref_store_transaction_begin(get_main_ref_store(repo),
+							  0, &transaction_err);
+		if (!transaction) {
+			ret = error(_("failed to begin ref transaction: %s"),
+				    transaction_err.buf);
+			goto cleanup;
 		}
 	}
 
-	/* In --advance mode, advance the target ref */
-	if (result.clean == 1 && advance_name) {
-		printf("update %s %s %s\n",
-		       advance_name,
-		       oid_to_hex(&last_commit->object.oid),
-		       oid_to_hex(&onto->object.oid));
+	for (size_t i = 0; i < result.updates_nr; i++) {
+		ret = handle_ref_update(ref_mode, transaction, result.updates[i].refname,
+					&result.updates[i].new_oid, &result.updates[i].old_oid,
+					reflog_msg.buf, &transaction_err);
+		if (ret) {
+			ret = error(_("failed to update ref '%s': %s"),
+				    result.updates[i].refname, transaction_err.buf);
+			goto cleanup;
+		}
 	}
 
-	merge_finalize(&merge_opt, &result);
-	kh_destroy_oid_map(replayed_commits);
-	if (update_refs) {
-		strset_clear(update_refs);
-		free(update_refs);
+	/* Commit the ref transaction if we have one */
+	if (transaction) {
+		if (ref_transaction_commit(transaction, &transaction_err)) {
+			ret = error(_("failed to commit ref transaction: %s"),
+				    transaction_err.buf);
+			goto cleanup;
+		}
 	}
-	ret = result.clean;
+
+	ret = 0;
 
 cleanup:
+	if (transaction)
+		ref_transaction_free(transaction);
+	replay_result_release(&result);
+	strbuf_release(&transaction_err);
+	strbuf_release(&reflog_msg);
 	release_revisions(&revs);
-	free(advance_name);
 
 	/* Return */
 	if (ret < 0)
 		exit(128);
-	return ret ? 0 : 1;
+	return ret;
 }
diff --git a/builtin/repo.c b/builtin/repo.c
index 9d4749f..55f9b90 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -1,7 +1,11 @@
 #define USE_THE_REPOSITORY_VARIABLE
 
 #include "builtin.h"
+#include "commit.h"
 #include "environment.h"
+#include "hash.h"
+#include "hex.h"
+#include "odb.h"
 #include "parse-options.h"
 #include "path-walk.h"
 #include "progress.h"
@@ -12,11 +16,14 @@
 #include "strbuf.h"
 #include "string-list.h"
 #include "shallow.h"
+#include "tree.h"
+#include "tree-walk.h"
 #include "utf8.h"
 
 static const char *const repo_usage[] = {
-	"git repo info [--format=(keyvalue|nul)] [-z] [<key>...]",
-	"git repo structure [--format=(table|keyvalue|nul)]",
+	"git repo info [--format=(lines|nul) | -z] [--all | <key>...]",
+	"git repo info --keys [--format=(lines|nul) | -z]",
+	"git repo structure [--format=(table|lines|nul) | -z]",
 	NULL
 };
 
@@ -24,11 +31,11 @@ typedef int get_value_fn(struct repository *repo, struct strbuf *buf);
 
 enum output_format {
 	FORMAT_TABLE,
-	FORMAT_KEYVALUE,
+	FORMAT_NEWLINE_TERMINATED,
 	FORMAT_NUL_TERMINATED,
 };
 
-struct field {
+struct repo_info_field {
 	const char *key;
 	get_value_fn *get_value;
 };
@@ -59,30 +66,49 @@ static int get_references_format(struct repository *repo, struct strbuf *buf)
 	return 0;
 }
 
-/* repo_info_fields keys must be in lexicographical order */
-static const struct field repo_info_fields[] = {
+/* repo_info_field keys must be in lexicographical order */
+static const struct repo_info_field repo_info_field[] = {
 	{ "layout.bare", get_layout_bare },
 	{ "layout.shallow", get_layout_shallow },
 	{ "object.format", get_object_format },
 	{ "references.format", get_references_format },
 };
 
-static int repo_info_fields_cmp(const void *va, const void *vb)
+static int repo_info_field_cmp(const void *va, const void *vb)
 {
-	const struct field *a = va;
-	const struct field *b = vb;
+	const struct repo_info_field *a = va;
+	const struct repo_info_field *b = vb;
 
 	return strcmp(a->key, b->key);
 }
 
-static get_value_fn *get_value_fn_for_key(const char *key)
+static const struct repo_info_field *get_repo_info_field(const char *key)
 {
-	const struct field search_key = { key, NULL };
-	const struct field *found = bsearch(&search_key, repo_info_fields,
-					    ARRAY_SIZE(repo_info_fields),
-					    sizeof(*found),
-					    repo_info_fields_cmp);
-	return found ? found->get_value : NULL;
+	const struct repo_info_field search_key = { key, NULL };
+	const struct repo_info_field *found = bsearch(&search_key,
+						      repo_info_field,
+						      ARRAY_SIZE(repo_info_field),
+						      sizeof(*found),
+						      repo_info_field_cmp);
+
+	return found;
+}
+
+static void print_field(enum output_format format, const char *key,
+			const char *value)
+{
+	switch (format) {
+	case FORMAT_NEWLINE_TERMINATED:
+		printf("%s=", key);
+		quote_c_style(value, NULL, stdout, 0);
+		putchar('\n');
+		break;
+	case FORMAT_NUL_TERMINATED:
+		printf("%s\n%s%c", key, value, '\0');
+		break;
+	default:
+		BUG("not a valid output format: %d", format);
+	}
 }
 
 static int print_fields(int argc, const char **argv,
@@ -91,42 +117,65 @@ static int print_fields(int argc, const char **argv,
 {
 	int ret = 0;
 	struct strbuf valbuf = STRBUF_INIT;
-	struct strbuf quotbuf = STRBUF_INIT;
 
 	for (int i = 0; i < argc; i++) {
-		get_value_fn *get_value;
 		const char *key = argv[i];
+		const struct repo_info_field *field = get_repo_info_field(key);
 
-		get_value = get_value_fn_for_key(key);
-
-		if (!get_value) {
+		if (!field) {
 			ret = error(_("key '%s' not found"), key);
 			continue;
 		}
 
 		strbuf_reset(&valbuf);
-		strbuf_reset(&quotbuf);
-
-		get_value(repo, &valbuf);
-
-		switch (format) {
-		case FORMAT_KEYVALUE:
-			quote_c_style(valbuf.buf, &quotbuf, NULL, 0);
-			printf("%s=%s\n", key, quotbuf.buf);
-			break;
-		case FORMAT_NUL_TERMINATED:
-			printf("%s\n%s%c", key, valbuf.buf, '\0');
-			break;
-		default:
-			BUG("not a valid output format: %d", format);
-		}
+		field->get_value(repo, &valbuf);
+		print_field(format, key, valbuf.buf);
 	}
 
 	strbuf_release(&valbuf);
-	strbuf_release(&quotbuf);
 	return ret;
 }
 
+static int print_all_fields(struct repository *repo,
+			    enum output_format format)
+{
+	struct strbuf valbuf = STRBUF_INIT;
+
+	for (size_t i = 0; i < ARRAY_SIZE(repo_info_field); i++) {
+		const struct repo_info_field *field = &repo_info_field[i];
+
+		strbuf_reset(&valbuf);
+		field->get_value(repo, &valbuf);
+		print_field(format, field->key, valbuf.buf);
+	}
+
+	strbuf_release(&valbuf);
+	return 0;
+}
+
+static int print_keys(enum output_format format)
+{
+	char sep;
+
+	switch (format) {
+	case FORMAT_NEWLINE_TERMINATED:
+		sep = '\n';
+		break;
+	case FORMAT_NUL_TERMINATED:
+		sep = '\0';
+		break;
+	default:
+		die(_("--keys can only be used with --format=lines or --format=nul"));
+	}
+
+	for (size_t i = 0; i < ARRAY_SIZE(repo_info_field); i++) {
+		const struct repo_info_field *field = &repo_info_field[i];
+		printf("%s%c", field->key, sep);
+	}
+
+	return 0;
+}
+
 static int parse_format_cb(const struct option *opt,
 			   const char *arg, int unset UNUSED)
 {
@@ -136,8 +185,8 @@ static int parse_format_cb(const struct option *opt,
 		*format = FORMAT_NUL_TERMINATED;
 	else if (!strcmp(arg, "nul"))
 		*format = FORMAT_NUL_TERMINATED;
-	else if (!strcmp(arg, "keyvalue"))
-		*format = FORMAT_KEYVALUE;
+	else if (!strcmp(arg, "lines"))
+		*format = FORMAT_NEWLINE_TERMINATED;
 	else if (!strcmp(arg, "table"))
 		*format = FORMAT_TABLE;
 	else
@@ -149,7 +198,9 @@ static int parse_format_cb(const struct option *opt,
 static int cmd_repo_info(int argc, const char **argv, const char *prefix,
 			 struct repository *repo)
 {
-	enum output_format format = FORMAT_KEYVALUE;
+	enum output_format format = FORMAT_NEWLINE_TERMINATED;
+	int all_keys = 0;
+	int show_keys = 0;
 	struct option options[] = {
 		OPT_CALLBACK_F(0, "format", &format, N_("format"),
 			       N_("output format"),
@@ -158,16 +209,46 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix,
 			       N_("synonym for --format=nul"),
 			       PARSE_OPT_NONEG | PARSE_OPT_NOARG,
 			       parse_format_cb),
+		OPT_BOOL(0, "all", &all_keys, N_("print all keys/values")),
+		OPT_BOOL(0, "keys", &show_keys, N_("show keys")),
 		OPT_END()
 	};
 
 	argc = parse_options(argc, argv, prefix, options, repo_usage, 0);
-	if (format != FORMAT_KEYVALUE && format != FORMAT_NUL_TERMINATED)
+
+	if (show_keys && (all_keys || argc))
+		die(_("--keys cannot be used with a <key> or --all"));
+
+	if (show_keys)
+		return print_keys(format);
+
+	if (format != FORMAT_NEWLINE_TERMINATED && format != FORMAT_NUL_TERMINATED)
 		die(_("unsupported output format"));
 
-	return print_fields(argc, argv, repo, format);
+	if (all_keys && argc)
+		die(_("--all and <key> cannot be used together"));
+
+	if (all_keys)
+		return print_all_fields(repo, format);
+	else
+		return print_fields(argc, argv, repo, format);
 }
 
+struct object_data {
+	struct object_id oid;
+	size_t value;
+};
+
+struct largest_objects {
+	struct object_data tag_size;
+	struct object_data commit_size;
+	struct object_data tree_size;
+	struct object_data blob_size;
+
+	struct object_data parent_count;
+	struct object_data tree_entries;
+};
+
 struct ref_stats {
 	size_t branches;
 	size_t remotes;
@@ -175,13 +256,20 @@ struct ref_stats {
 	size_t others;
 };
 
-struct object_stats {
+struct object_values {
 	size_t tags;
 	size_t commits;
 	size_t trees;
 	size_t blobs;
 };
 
+struct object_stats {
+	struct object_values type_counts;
+	struct object_values inflated_sizes;
+	struct object_values disk_sizes;
+	struct largest_objects largest;
+};
+
 struct repo_structure {
 	struct ref_stats refs;
 	struct object_stats objects;
@@ -189,9 +277,11 @@ struct repo_structure {
 
 struct stats_table {
 	struct string_list rows;
+	struct string_list annotations;
 
 	int name_col_width;
 	int value_col_width;
+	int unit_col_width;
 };
 
 /*
@@ -199,6 +289,9 @@ struct stats_table {
  */
 struct stats_table_entry {
 	char *value;
+	const char *unit;
+	size_t index;
+	struct object_id *oid;
 };
 
 static void stats_table_vaddf(struct stats_table *table,
@@ -219,11 +312,26 @@ static void stats_table_vaddf(struct stats_table *table,
 
 	if (name_width > table->name_col_width)
 		table->name_col_width = name_width;
-	if (entry) {
+	if (!entry)
+		return;
+	if (entry->oid) {
+		entry->index = table->annotations.nr + 1;
+		strbuf_addf(&buf, "[%" PRIuMAX "] %s", (uintmax_t)entry->index,
+			    oid_to_hex(entry->oid));
+		string_list_append_nodup(&table->annotations, strbuf_detach(&buf, NULL));
+	}
+	if (entry->value) {
 		int value_width = utf8_strwidth(entry->value);
 		if (value_width > table->value_col_width)
 			table->value_col_width = value_width;
 	}
+	if (entry->unit) {
+		int unit_width = utf8_strwidth(entry->unit);
+		if (unit_width > table->unit_col_width)
+			table->unit_col_width = unit_width;
+	}
+
+	strbuf_release(&buf);
 }
 
 static void stats_table_addf(struct stats_table *table, const char *format, ...)
@@ -242,7 +350,63 @@ static void stats_table_count_addf(struct stats_table *table, size_t value,
 	va_list ap;
 
 	CALLOC_ARRAY(entry, 1);
-	entry->value = xstrfmt("%" PRIuMAX, (uintmax_t)value);
+	humanise_count(value, &entry->value, &entry->unit);
+
+	va_start(ap, format);
+	stats_table_vaddf(table, entry, format, ap);
+	va_end(ap);
+}
+
+static void stats_table_object_count_addf(struct stats_table *table,
+					  struct object_id *oid, size_t value,
+					  const char *format, ...)
+{
+	struct stats_table_entry *entry;
+	va_list ap;
+
+	CALLOC_ARRAY(entry, 1);
+	humanise_count(value, &entry->value, &entry->unit);
+
+	/*
+	 * A NULL OID should not have a table annotation.
+	 */
+	if (!is_null_oid(oid))
+		entry->oid = oid;
+
+	va_start(ap, format);
+	stats_table_vaddf(table, entry, format, ap);
+	va_end(ap);
+}
+
+static void stats_table_size_addf(struct stats_table *table, size_t value,
+				  const char *format, ...)
+{
+	struct stats_table_entry *entry;
+	va_list ap;
+
+	CALLOC_ARRAY(entry, 1);
+	humanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);
+
+	va_start(ap, format);
+	stats_table_vaddf(table, entry, format, ap);
+	va_end(ap);
+}
+
+static void stats_table_object_size_addf(struct stats_table *table,
+					 struct object_id *oid, size_t value,
+					 const char *format, ...)
+{
+	struct stats_table_entry *entry;
+	va_list ap;
+
+	CALLOC_ARRAY(entry, 1);
+	humanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);
+
+	/*
+	 * A NULL OID should not have a table annotation.
+	 */
+	if (!is_null_oid(oid))
+		entry->oid = oid;
 
 	va_start(ap, format);
 	stats_table_vaddf(table, entry, format, ap);
@@ -254,9 +418,9 @@ static inline size_t get_total_reference_count(struct ref_stats *stats)
 	return stats->branches + stats->remotes + stats->tags + stats->others;
 }
 
-static inline size_t get_total_object_count(struct object_stats *stats)
+static inline size_t get_total_object_values(struct object_values *values)
 {
-	return stats->tags + stats->commits + stats->trees + stats->blobs;
+	return values->tags + values->commits + values->trees + values->blobs;
 }
 
 static void stats_table_setup_structure(struct stats_table *table,
@@ -264,7 +428,9 @@ static void stats_table_setup_structure(struct stats_table *table,
 {
 	struct object_stats *objects = &stats->objects;
 	struct ref_stats *refs = &stats->refs;
-	size_t object_total;
+	size_t inflated_object_total;
+	size_t object_count_total;
+	size_t disk_object_total;
 	size_t ref_total;
 
 	ref_total = get_total_reference_count(refs);
@@ -275,51 +441,147 @@ static void stats_table_setup_structure(struct stats_table *table,
 	stats_table_count_addf(table, refs->remotes, "    * %s", _("Remotes"));
 	stats_table_count_addf(table, refs->others, "    * %s", _("Others"));
 
-	object_total = get_total_object_count(objects);
+	object_count_total = get_total_object_values(&objects->type_counts);
 	stats_table_addf(table, "");
 	stats_table_addf(table, "* %s", _("Reachable objects"));
-	stats_table_count_addf(table, object_total, "  * %s", _("Count"));
-	stats_table_count_addf(table, objects->commits, "    * %s", _("Commits"));
-	stats_table_count_addf(table, objects->trees, "    * %s", _("Trees"));
-	stats_table_count_addf(table, objects->blobs, "    * %s", _("Blobs"));
-	stats_table_count_addf(table, objects->tags, "    * %s", _("Tags"));
+	stats_table_count_addf(table, object_count_total, "  * %s", _("Count"));
+	stats_table_count_addf(table, objects->type_counts.commits,
+			       "    * %s", _("Commits"));
+	stats_table_count_addf(table, objects->type_counts.trees,
+			       "    * %s", _("Trees"));
+	stats_table_count_addf(table, objects->type_counts.blobs,
+			       "    * %s", _("Blobs"));
+	stats_table_count_addf(table, objects->type_counts.tags,
+			       "    * %s", _("Tags"));
+
+	inflated_object_total = get_total_object_values(&objects->inflated_sizes);
+	stats_table_size_addf(table, inflated_object_total,
+			      "  * %s", _("Inflated size"));
+	stats_table_size_addf(table, objects->inflated_sizes.commits,
+			      "    * %s", _("Commits"));
+	stats_table_size_addf(table, objects->inflated_sizes.trees,
+			      "    * %s", _("Trees"));
+	stats_table_size_addf(table, objects->inflated_sizes.blobs,
+			      "    * %s", _("Blobs"));
+	stats_table_size_addf(table, objects->inflated_sizes.tags,
+			      "    * %s", _("Tags"));
+
+	disk_object_total = get_total_object_values(&objects->disk_sizes);
+	stats_table_size_addf(table, disk_object_total,
+			      "  * %s", _("Disk size"));
+	stats_table_size_addf(table, objects->disk_sizes.commits,
+			      "    * %s", _("Commits"));
+	stats_table_size_addf(table, objects->disk_sizes.trees,
+			      "    * %s", _("Trees"));
+	stats_table_size_addf(table, objects->disk_sizes.blobs,
+			      "    * %s", _("Blobs"));
+	stats_table_size_addf(table, objects->disk_sizes.tags,
+			      "    * %s", _("Tags"));
+
+	stats_table_addf(table, "");
+	stats_table_addf(table, "* %s", _("Largest objects"));
+	stats_table_addf(table, "  * %s", _("Commits"));
+	stats_table_object_size_addf(table,
+				     &objects->largest.commit_size.oid,
+				     objects->largest.commit_size.value,
+				     "    * %s", _("Maximum size"));
+	stats_table_object_count_addf(table,
+				      &objects->largest.parent_count.oid,
+				      objects->largest.parent_count.value,
+				      "    * %s", _("Maximum parents"));
+	stats_table_addf(table, "  * %s", _("Trees"));
+	stats_table_object_size_addf(table,
+				     &objects->largest.tree_size.oid,
+				     objects->largest.tree_size.value,
+				     "    * %s", _("Maximum size"));
+	stats_table_object_count_addf(table,
+				      &objects->largest.tree_entries.oid,
+				      objects->largest.tree_entries.value,
+				      "    * %s", _("Maximum entries"));
+	stats_table_addf(table, "  * %s", _("Blobs"));
+	stats_table_object_size_addf(table,
+				     &objects->largest.blob_size.oid,
+				     objects->largest.blob_size.value,
+				     "    * %s", _("Maximum size"));
+	stats_table_addf(table, "  * %s", _("Tags"));
+	stats_table_object_size_addf(table,
+				     &objects->largest.tag_size.oid,
+				     objects->largest.tag_size.value,
+				     "    * %s", _("Maximum size"));
 }
 
+#define INDEX_WIDTH 4
+
 static void stats_table_print_structure(const struct stats_table *table)
 {
 	const char *name_col_title = _("Repository structure");
 	const char *value_col_title = _("Value");
-	int name_col_width = utf8_strwidth(name_col_title);
-	int value_col_width = utf8_strwidth(value_col_title);
+	int title_name_width = utf8_strwidth(name_col_title);
+	int title_value_width = utf8_strwidth(value_col_title);
+	int name_col_width = table->name_col_width;
+	int value_col_width = table->value_col_width;
+	int unit_col_width = table->unit_col_width;
 	struct string_list_item *item;
+	struct strbuf buf = STRBUF_INIT;
 
-	if (table->name_col_width > name_col_width)
-		name_col_width = table->name_col_width;
-	if (table->value_col_width > value_col_width)
-		value_col_width = table->value_col_width;
+	if (title_name_width > name_col_width)
+		name_col_width = title_name_width;
+	if (title_value_width > value_col_width + unit_col_width + 1)
+		value_col_width = title_value_width - unit_col_width;
 
-	printf("| %-*s | %-*s |\n", name_col_width, name_col_title,
-	       value_col_width, value_col_title);
+	strbuf_addstr(&buf, "| ");
+	strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width + INDEX_WIDTH,
+			  name_col_title);
+	strbuf_addstr(&buf, " | ");
+	strbuf_utf8_align(&buf, ALIGN_LEFT,
+			  value_col_width + unit_col_width + 1, value_col_title);
+	strbuf_addstr(&buf, " |");
+	printf("%s\n", buf.buf);
+
 	printf("| ");
-	for (int i = 0; i < name_col_width; i++)
+	for (int i = 0; i < name_col_width + INDEX_WIDTH; i++)
 		putchar('-');
 	printf(" | ");
-	for (int i = 0; i < value_col_width; i++)
+	for (int i = 0; i < value_col_width + unit_col_width + 1; i++)
 		putchar('-');
 	printf(" |\n");
 
 	for_each_string_list_item(item, &table->rows) {
 		struct stats_table_entry *entry = item->util;
 		const char *value = "";
+		const char *unit = "";
 
 		if (entry) {
-			struct stats_table_entry *entry = item->util;
 			value = entry->value;
+			if (entry->unit)
+				unit = entry->unit;
 		}
 
-		printf("| %-*s | %*s |\n", name_col_width, item->string,
-		       value_col_width, value);
+		strbuf_reset(&buf);
+		strbuf_addstr(&buf, "| ");
+		strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width, item->string);
+
+		if (entry && entry->oid)
+			strbuf_addf(&buf, " [%" PRIuMAX "]",
+				    (uintmax_t)entry->index);
+		else
+			strbuf_addchars(&buf, ' ', INDEX_WIDTH);
+
+		strbuf_addstr(&buf, " | ");
+		strbuf_utf8_align(&buf, ALIGN_RIGHT, value_col_width, value);
+		strbuf_addch(&buf, ' ');
+		strbuf_utf8_align(&buf, ALIGN_LEFT, unit_col_width, unit);
+		strbuf_addstr(&buf, " |");
+		printf("%s\n", buf.buf);
 	}
+
+	if (table->annotations.nr) {
+		printf("\n");
+		for_each_string_list_item(item, &table->annotations)
+			printf("%s\n", item->string);
+	}
+
+	strbuf_release(&buf);
 }
 
 static void stats_table_clear(struct stats_table *table)
@@ -334,28 +596,76 @@ static void stats_table_clear(struct stats_table *table)
 	}
 
 	string_list_clear(&table->rows, 1);
+	string_list_clear(&table->annotations, 1);
+}
+
+static inline void print_keyvalue(const char *key, char key_delim, size_t value,
+				  char value_delim)
+{
+	printf("%s%c%" PRIuMAX "%c", key, key_delim, (uintmax_t)value,
+	       value_delim);
+}
+
+static void print_object_data(const char *key, char key_delim,
+			      struct object_data *data, char value_delim)
+{
+	print_keyvalue(key, key_delim, data->value, value_delim);
+	printf("%s_oid%c%s%c", key, key_delim, oid_to_hex(&data->oid),
+	       value_delim);
 }
 
 static void structure_keyvalue_print(struct repo_structure *stats,
 				     char key_delim, char value_delim)
 {
-	printf("references.branches.count%c%" PRIuMAX "%c", key_delim,
-	       (uintmax_t)stats->refs.branches, value_delim);
-	printf("references.tags.count%c%" PRIuMAX "%c", key_delim,
-	       (uintmax_t)stats->refs.tags, value_delim);
-	printf("references.remotes.count%c%" PRIuMAX "%c", key_delim,
-	       (uintmax_t)stats->refs.remotes, value_delim);
-	printf("references.others.count%c%" PRIuMAX "%c", key_delim,
-	       (uintmax_t)stats->refs.others, value_delim);
+	print_keyvalue("references.branches.count", key_delim,
+		       stats->refs.branches, value_delim);
+	print_keyvalue("references.tags.count", key_delim,
+		       stats->refs.tags, value_delim);
+	print_keyvalue("references.remotes.count", key_delim,
+		       stats->refs.remotes, value_delim);
+	print_keyvalue("references.others.count", key_delim,
+		       stats->refs.others, value_delim);
 
-	printf("objects.commits.count%c%" PRIuMAX "%c", key_delim,
-	       (uintmax_t)stats->objects.commits, value_delim);
-	printf("objects.trees.count%c%" PRIuMAX "%c", key_delim,
-	       (uintmax_t)stats->objects.trees, value_delim);
-	printf("objects.blobs.count%c%" PRIuMAX "%c", key_delim,
-	       (uintmax_t)stats->objects.blobs, value_delim);
-	printf("objects.tags.count%c%" PRIuMAX "%c", key_delim,
-	       (uintmax_t)stats->objects.tags, value_delim);
+	print_keyvalue("objects.commits.count", key_delim,
+		       stats->objects.type_counts.commits, value_delim);
+	print_keyvalue("objects.trees.count", key_delim,
+		       stats->objects.type_counts.trees, value_delim);
+	print_keyvalue("objects.blobs.count", key_delim,
+		       stats->objects.type_counts.blobs, value_delim);
+	print_keyvalue("objects.tags.count", key_delim,
+		       stats->objects.type_counts.tags, value_delim);
+
+	print_keyvalue("objects.commits.inflated_size", key_delim,
+		       stats->objects.inflated_sizes.commits, value_delim);
+	print_keyvalue("objects.trees.inflated_size", key_delim,
+		       stats->objects.inflated_sizes.trees, value_delim);
+	print_keyvalue("objects.blobs.inflated_size", key_delim,
+		       stats->objects.inflated_sizes.blobs, value_delim);
+	print_keyvalue("objects.tags.inflated_size", key_delim,
+		       stats->objects.inflated_sizes.tags, value_delim);
+
+	print_keyvalue("objects.commits.disk_size", key_delim,
+		       stats->objects.disk_sizes.commits, value_delim);
+	print_keyvalue("objects.trees.disk_size", key_delim,
+		       stats->objects.disk_sizes.trees, value_delim);
+	print_keyvalue("objects.blobs.disk_size", key_delim,
+		       stats->objects.disk_sizes.blobs, value_delim);
+	print_keyvalue("objects.tags.disk_size", key_delim,
+		       stats->objects.disk_sizes.tags, value_delim);
+
+	print_object_data("objects.commits.max_size", key_delim,
+			  &stats->objects.largest.commit_size, value_delim);
+	print_object_data("objects.trees.max_size", key_delim,
+			  &stats->objects.largest.tree_size, value_delim);
+	print_object_data("objects.blobs.max_size", key_delim,
+			  &stats->objects.largest.blob_size, value_delim);
+	print_object_data("objects.tags.max_size", key_delim,
+			  &stats->objects.largest.tag_size, value_delim);
+
+	print_object_data("objects.commits.max_parents", key_delim,
+			  &stats->objects.largest.parent_count, value_delim);
+	print_object_data("objects.trees.max_entries", key_delim,
+			  &stats->objects.largest.tree_entries, value_delim);
 
 	fflush(stdout);
 }
@@ -366,16 +676,13 @@ struct count_references_data {
 	struct progress *progress;
 };
 
-static int count_references(const char *refname,
-			    const char *referent UNUSED,
-			    const struct object_id *oid,
-			    int flags UNUSED, void *cb_data)
+static int count_references(const struct reference *ref, void *cb_data)
 {
 	struct count_references_data *data = cb_data;
 	struct ref_stats *stats = data->stats;
 	size_t ref_count;
 
-	switch (ref_kind_from_refname(refname)) {
+	switch (ref_kind_from_refname(ref->name)) {
 	case FILTER_REFS_BRANCHES:
 		stats->branches++;
 		break;
@@ -396,7 +703,7 @@ static int count_references(const char *refname,
 	 * While iterating through references for counting, also add OIDs in
 	 * preparation for the path walk.
 	 */
-	add_pending_oid(data->revs, NULL, oid, 0);
+	add_pending_oid(data->revs, NULL, ref->oid, 0);
 
 	ref_count = get_total_reference_count(stats);
 	display_progress(data->progress, ref_count);
@@ -423,10 +730,34 @@ static void structure_count_references(struct ref_stats *stats,
 }
 
 struct count_objects_data {
+	struct object_database *odb;
 	struct object_stats *stats;
 	struct progress *progress;
 };
 
+static void check_largest(struct object_data *data, struct object_id *oid,
+			  size_t value)
+{
+	if (value > data->value || is_null_oid(&data->oid)) {
+		oidcpy(&data->oid, oid);
+		data->value = value;
+	}
+}
+
+static size_t count_tree_entries(struct object *obj)
+{
+	struct tree *t = object_as_type(obj, OBJ_TREE, 0);
+	struct name_entry entry;
+	struct tree_desc desc;
+	size_t count = 0;
+
+	init_tree_desc(&desc, &t->object.oid, t->buffer, t->size);
+	while (tree_entry(&desc, &entry))
+		count++;
+
+	return count;
+}
+
 static int count_objects(const char *path UNUSED, struct oid_array *oids,
 			 enum object_type type, void *cb_data)
 {
@@ -434,24 +765,70 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids,
 	struct object_stats *stats = data->stats;
 	size_t object_count;
 
-	switch (type) {
-	case OBJ_TAG:
-		stats->tags += oids->nr;
-		break;
-	case OBJ_COMMIT:
-		stats->commits += oids->nr;
-		break;
-	case OBJ_TREE:
-		stats->trees += oids->nr;
-		break;
-	case OBJ_BLOB:
-		stats->blobs += oids->nr;
-		break;
-	default:
-		BUG("invalid object type");
+	for (size_t i = 0; i < oids->nr; i++) {
+		struct object_info oi = OBJECT_INFO_INIT;
+		unsigned long inflated;
+		struct commit *commit;
+		struct object *obj;
+		void *content;
+		off_t disk;
+		int eaten;
+
+		oi.sizep = &inflated;
+		oi.disk_sizep = &disk;
+		oi.contentp = &content;
+
+		if (odb_read_object_info_extended(data->odb, &oids->oid[i], &oi,
+						  OBJECT_INFO_SKIP_FETCH_OBJECT |
+						  OBJECT_INFO_QUICK) < 0)
+			continue;
+
+		obj = parse_object_buffer(the_repository, &oids->oid[i], type,
+					  inflated, content, &eaten);
+
+		switch (type) {
+		case OBJ_TAG:
+			stats->type_counts.tags++;
+			stats->inflated_sizes.tags += inflated;
+			stats->disk_sizes.tags += disk;
+			check_largest(&stats->largest.tag_size, &oids->oid[i],
+				      inflated);
+			break;
+		case OBJ_COMMIT:
+			commit = object_as_type(obj, OBJ_COMMIT, 0);
+			stats->type_counts.commits++;
+			stats->inflated_sizes.commits += inflated;
+			stats->disk_sizes.commits += disk;
+			check_largest(&stats->largest.commit_size, &oids->oid[i],
+				      inflated);
+			check_largest(&stats->largest.parent_count, &oids->oid[i],
+				      commit_list_count(commit->parents));
+			break;
+		case OBJ_TREE:
+			stats->type_counts.trees++;
+			stats->inflated_sizes.trees += inflated;
+			stats->disk_sizes.trees += disk;
+			check_largest(&stats->largest.tree_size, &oids->oid[i],
+				      inflated);
+			check_largest(&stats->largest.tree_entries, &oids->oid[i],
+				      count_tree_entries(obj));
+			break;
+		case OBJ_BLOB:
+			stats->type_counts.blobs++;
+			stats->inflated_sizes.blobs += inflated;
+			stats->disk_sizes.blobs += disk;
+			check_largest(&stats->largest.blob_size, &oids->oid[i],
+				      inflated);
+			break;
+		default:
+			BUG("invalid object type");
+		}
+
+		if (!eaten)
+			free(content);
 	}
 
-	object_count = get_total_object_count(stats);
+	object_count = get_total_object_values(&stats->type_counts);
 	display_progress(data->progress, object_count);
 
 	return 0;
@@ -463,6 +840,7 @@ static void structure_count_objects(struct object_stats *stats,
 {
 	struct path_walk_info info = PATH_WALK_INFO_INIT;
 	struct count_objects_data data = {
+		.odb = repo->objects,
 		.stats = stats,
 	};
 
@@ -483,6 +861,7 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
 {
 	struct stats_table table = {
 		.rows = STRING_LIST_INIT_DUP,
+		.annotations = STRING_LIST_INIT_DUP,
 	};
 	enum output_format format = FORMAT_TABLE;
 	struct repo_structure stats = { 0 };
@@ -492,6 +871,10 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
 		OPT_CALLBACK_F(0, "format", &format, N_("format"),
 			       N_("output format"),
 			       PARSE_OPT_NONEG, parse_format_cb),
+		OPT_CALLBACK_F('z', NULL, &format, NULL,
+			       N_("synonym for --format=nul"),
+			       PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+			       parse_format_cb),
 		OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
 		OPT_END()
 	};
@@ -513,7 +896,7 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
 		stats_table_setup_structure(&table, &stats);
 		stats_table_print_structure(&table);
 		break;
-	case FORMAT_KEYVALUE:
+	case FORMAT_NEWLINE_TERMINATED:
 		structure_keyvalue_print(&stats, '=', '\n');
 		break;
 	case FORMAT_NUL_TERMINATED:
diff --git a/builtin/reset.c b/builtin/reset.c
index ed35802..88f95f9 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -118,7 +118,7 @@ static int reset_index(const char *ref, const struct object_id *oid, int reset_t
 		goto out;
 
 	if (reset_type == MIXED || reset_type == HARD) {
-		tree = parse_tree_indirect(oid);
+		tree = repo_parse_tree_indirect(the_repository, oid);
 		if (!tree) {
 			error(_("unable to read tree (%s)"), oid_to_hex(oid));
 			goto out;
@@ -371,6 +371,8 @@ int cmd_reset(int argc,
 			       PARSE_OPT_OPTARG,
 			       option_parse_recurse_submodules_worktree_updater),
 		OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
+		OPT_BOOL(0, "auto-advance", &add_p_opt.auto_advance,
+			 N_("auto advance to the next file when selecting hunks interactively")),
 		OPT_DIFF_UNIFIED(&add_p_opt.context),
 		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT_BOOL('N', "intent-to-add", &intent_to_add,
@@ -417,7 +419,7 @@ int cmd_reset(int argc,
 		struct tree *tree;
 		if (repo_get_oid_treeish(the_repository, rev, &oid))
 			die(_("Failed to resolve '%s' as a valid tree."), rev);
-		tree = parse_tree_indirect(&oid);
+		tree = repo_parse_tree_indirect(the_repository, &oid);
 		if (!tree)
 			die(_("Could not parse object '%s'."), rev);
 		oidcpy(&oid, &tree->object.oid);
@@ -443,6 +445,8 @@ int cmd_reset(int argc,
 			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
 		if (add_p_opt.interhunkcontext != -1)
 			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+		if (!add_p_opt.auto_advance)
+			die(_("the option '%s' requires '%s'"), "--no-auto-advance", "--patch");
 	}
 
 	/* git reset tree [--] paths... can be used to
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 99f876b..854d82e 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -88,9 +88,19 @@ static int arg_print_omitted; /* print objects omitted by filter */
 
 struct missing_objects_map_entry {
 	struct oidmap_entry entry;
-	const char *path;
+	char *path;
 	unsigned type;
 };
+
+static void missing_objects_map_entry_free(void *e)
+{
+	struct missing_objects_map_entry *entry =
+		container_of(e, struct missing_objects_map_entry, entry);
+
+	free(entry->path);
+	free(entry);
+}
+
 static struct oidmap missing_objects;
 enum missing_action {
 	MA_ERROR = 0,    /* fail if any missing objects are encountered */
@@ -216,7 +226,7 @@ static inline void finish_object__ma(struct object *obj, const char *name)
 
 static void finish_commit(struct commit *commit)
 {
-	free_commit_list(commit->parents);
+	commit_list_free(commit->parents);
 	commit->parents = NULL;
 	free_commit_buffer(the_repository->parsed_objects,
 			   commit);
@@ -935,10 +945,9 @@ int cmd_rev_list(int argc,
 		while ((entry = oidmap_iter_next(&iter))) {
 			print_missing_object(entry, arg_missing_action ==
 							    MA_PRINT_INFO);
-			free((void *)entry->path);
 		}
 
-		oidmap_clear(&missing_objects, true);
+		oidmap_clear_with_free(&missing_objects, missing_objects_map_entry_free);
 	}
 
 	stop_progress(&progress);
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 7b3711c..01a6280 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -217,19 +217,17 @@ static int show_default(void)
 	return 0;
 }
 
-static int show_reference(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-			  int flag UNUSED, void *cb_data UNUSED)
+static int show_reference(const struct reference *ref, void *cb_data UNUSED)
 {
-	if (ref_excluded(&ref_excludes, refname))
+	if (ref_excluded(&ref_excludes, ref->name))
 		return 0;
-	show_rev(NORMAL, oid, refname);
+	show_rev(NORMAL, ref->oid, ref->name);
 	return 0;
 }
 
-static int anti_reference(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-			  int flag UNUSED, void *cb_data UNUSED)
+static int anti_reference(const struct reference *ref, void *cb_data UNUSED)
 {
-	show_rev(REVERSED, oid, refname);
+	show_rev(REVERSED, ref->oid, ref->name);
 	return 0;
 }
 
@@ -615,13 +613,22 @@ static int opt_with_value(const char *arg, const char *opt, const char **value)
 
 static void handle_ref_opt(const char *pattern, const char *prefix)
 {
-	if (pattern)
-		refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
-					  show_reference, pattern, prefix,
-					  NULL);
-	else
-		refs_for_each_ref_in(get_main_ref_store(the_repository),
-				     prefix, show_reference, NULL);
+	if (pattern) {
+		struct refs_for_each_ref_options opts = {
+			.pattern = pattern,
+			.prefix = prefix,
+			.trim_prefix = prefix ? strlen(prefix) : 0,
+		};
+		refs_for_each_ref_ext(get_main_ref_store(the_repository),
+				      show_reference, NULL, &opts);
+	} else {
+		struct refs_for_each_ref_options opts = {
+			.prefix = prefix,
+			.trim_prefix = strlen(prefix),
+		};
+		refs_for_each_ref_ext(get_main_ref_store(the_repository),
+				      show_reference, NULL, &opts);
+	}
 	clear_ref_exclusions(&ref_excludes);
 }
 
@@ -933,14 +940,13 @@ int cmd_rev_parse(int argc,
 				continue;
 			}
 			if (!strcmp(arg, "--bisect")) {
-				refs_for_each_fullref_in(get_main_ref_store(the_repository),
-							 "refs/bisect/bad",
-							 NULL, show_reference,
-							 NULL);
-				refs_for_each_fullref_in(get_main_ref_store(the_repository),
-							 "refs/bisect/good",
-							 NULL, anti_reference,
-							 NULL);
+				struct refs_for_each_ref_options opts = { 0 };
+				opts.prefix = "refs/bisect/bad";
+				refs_for_each_ref_ext(get_main_ref_store(the_repository),
+						      show_reference, NULL, &opts);
+				opts.prefix = "refs/bisect/good";
+				refs_for_each_ref_ext(get_main_ref_store(the_repository),
+						      anti_reference, NULL, &opts);
 				continue;
 			}
 			if (opt_with_value(arg, "--branches", &arg)) {
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index b91acf4..6b2a0b9 100644
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
@@ -76,7 +76,7 @@ static void insert_one_record(struct shortlog *log,
 		if (!eol)
 			eol = oneline + strlen(oneline);
 		if (starts_with(oneline, "[PATCH")) {
-			char *eob = strchr(oneline, ']');
+			const char *eob = strchr(oneline, ']');
 			if (eob && (!eol || eob < eol))
 				oneline = eob + 1;
 		}
@@ -357,7 +357,7 @@ void shortlog_init(struct shortlog *log)
 {
 	memset(log, 0, sizeof(*log));
 
-	read_mailmap(&log->mailmap);
+	read_mailmap(the_repository, &log->mailmap);
 
 	log->list.strdup_strings = 1;
 	log->wrap = DEFAULT_WRAPLEN;
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index 441babf..f02831b 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -18,6 +18,7 @@
 #include "commit-slab.h"
 #include "date.h"
 #include "wildmatch.h"
+#include "prio-queue.h"
 
 static const char*const show_branch_usage[] = {
     N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n"
@@ -59,11 +60,10 @@ static const char *get_color_reset_code(void)
 	return "";
 }
 
-static struct commit *interesting(struct commit_list *list)
+static struct commit *interesting(struct prio_queue *queue)
 {
-	while (list) {
-		struct commit *commit = list->item;
-		list = list->next;
+	for (size_t i = 0; i < queue->nr; i++) {
+		struct commit *commit = queue->array[i].data;
 		if (commit->object.flags & UNINTERESTING)
 			continue;
 		return commit;
@@ -222,17 +222,18 @@ static int mark_seen(struct commit *commit, struct commit_list **seen_p)
 	return 0;
 }
 
-static void join_revs(struct commit_list **list_p,
+static void join_revs(struct prio_queue *queue,
 		      struct commit_list **seen_p,
 		      int num_rev, int extra)
 {
 	int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
 	int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
 
-	while (*list_p) {
+	while (queue->nr) {
 		struct commit_list *parents;
-		int still_interesting = !!interesting(*list_p);
-		struct commit *commit = pop_commit(list_p);
+		int still_interesting = !!interesting(queue);
+		struct commit *commit = prio_queue_peek(queue);
+		bool get_pending = true;
 		int flags = commit->object.flags & all_mask;
 
 		if (!still_interesting && extra <= 0)
@@ -253,8 +254,14 @@ static void join_revs(struct commit_list **list_p,
 			if (mark_seen(p, seen_p) && !still_interesting)
 				extra--;
 			p->object.flags |= flags;
-			commit_list_insert_by_date(p, list_p);
+			if (get_pending)
+				prio_queue_replace(queue, p);
+			else
+				prio_queue_put(queue, p);
+			get_pending = false;
 		}
+		if (get_pending)
+			prio_queue_get(queue);
 	}
 
 	/*
@@ -413,34 +420,32 @@ static int append_ref(const char *refname, const struct object_id *oid,
 	return 0;
 }
 
-static int append_head_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-			   int flag UNUSED, void *cb_data UNUSED)
+static int append_head_ref(const struct reference *ref, void *cb_data UNUSED)
 {
 	struct object_id tmp;
 	int ofs = 11;
-	if (!starts_with(refname, "refs/heads/"))
+	if (!starts_with(ref->name, "refs/heads/"))
 		return 0;
 	/* If both heads/foo and tags/foo exists, get_sha1 would
 	 * get confused.
 	 */
-	if (repo_get_oid(the_repository, refname + ofs, &tmp) || !oideq(&tmp, oid))
+	if (repo_get_oid(the_repository, ref->name + ofs, &tmp) || !oideq(&tmp, ref->oid))
 		ofs = 5;
-	return append_ref(refname + ofs, oid, 0);
+	return append_ref(ref->name + ofs, ref->oid, 0);
 }
 
-static int append_remote_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-			     int flag UNUSED, void *cb_data UNUSED)
+static int append_remote_ref(const struct reference *ref, void *cb_data UNUSED)
 {
 	struct object_id tmp;
 	int ofs = 13;
-	if (!starts_with(refname, "refs/remotes/"))
+	if (!starts_with(ref->name, "refs/remotes/"))
 		return 0;
 	/* If both heads/foo and tags/foo exists, get_sha1 would
 	 * get confused.
 	 */
-	if (repo_get_oid(the_repository, refname + ofs, &tmp) || !oideq(&tmp, oid))
+	if (repo_get_oid(the_repository, ref->name + ofs, &tmp) || !oideq(&tmp, ref->oid))
 		ofs = 5;
-	return append_ref(refname + ofs, oid, 0);
+	return append_ref(ref->name + ofs, ref->oid, 0);
 }
 
 static int append_tag_ref(const char *refname, const struct object_id *oid,
@@ -454,27 +459,26 @@ static int append_tag_ref(const char *refname, const struct object_id *oid,
 static const char *match_ref_pattern = NULL;
 static int match_ref_slash = 0;
 
-static int append_matching_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-			       int flag, void *cb_data)
+static int append_matching_ref(const struct reference *ref, void *cb_data)
 {
 	/* we want to allow pattern hold/<asterisk> to show all
 	 * branches under refs/heads/hold/, and v0.99.9? to show
 	 * refs/tags/v0.99.9a and friends.
 	 */
 	const char *tail;
-	int slash = count_slashes(refname);
-	for (tail = refname; *tail && match_ref_slash < slash; )
+	int slash = count_slashes(ref->name);
+	for (tail = ref->name; *tail && match_ref_slash < slash; )
 		if (*tail++ == '/')
 			slash--;
 	if (!*tail)
 		return 0;
 	if (wildmatch(match_ref_pattern, tail, 0))
 		return 0;
-	if (starts_with(refname, "refs/heads/"))
-		return append_head_ref(refname, NULL, oid, flag, cb_data);
-	if (starts_with(refname, "refs/tags/"))
-		return append_tag_ref(refname, oid, flag, cb_data);
-	return append_ref(refname, oid, 0);
+	if (starts_with(ref->name, "refs/heads/"))
+		return append_head_ref(ref, cb_data);
+	if (starts_with(ref->name, "refs/tags/"))
+		return append_tag_ref(ref->name, ref->oid, ref->flags, cb_data);
+	return append_ref(ref->name, ref->oid, 0);
 }
 
 static void snarf_refs(int head, int remotes)
@@ -642,7 +646,8 @@ int cmd_show_branch(int ac,
 {
 	struct commit *rev[MAX_REVS], *commit;
 	char *reflog_msg[MAX_REVS] = {0};
-	struct commit_list *list = NULL, *seen = NULL;
+	struct commit_list *seen = NULL;
+	struct prio_queue queue = { compare_commits_by_commit_date };
 	unsigned int rev_mask[MAX_REVS];
 	int num_rev, i, extra = 0;
 	int all_heads = 0, all_remotes = 0;
@@ -886,14 +891,14 @@ int cmd_show_branch(int ac,
 		 */
 		commit->object.flags |= flag;
 		if (commit->object.flags == flag)
-			commit_list_insert_by_date(commit, &list);
+			prio_queue_put(&queue, commit);
 		rev[num_rev] = commit;
 	}
 	for (i = 0; i < num_rev; i++)
 		rev_mask[i] = rev[i]->object.flags;
 
 	if (0 <= extra)
-		join_revs(&list, &seen, num_rev, extra);
+		join_revs(&queue, &seen, num_rev, extra);
 
 	commit_list_sort_by_date(&seen);
 
@@ -1003,8 +1008,8 @@ int cmd_show_branch(int ac,
 out:
 	for (size_t i = 0; i < ARRAY_SIZE(reflog_msg); i++)
 		free(reflog_msg[i]);
-	free_commit_list(seen);
-	free_commit_list(list);
+	commit_list_free(seen);
+	clear_prio_queue(&queue);
 	free(args_copy);
 	free(head);
 	return ret;
diff --git a/builtin/show-index.c b/builtin/show-index.c
index 2c3e294..24f0230 100644
--- a/builtin/show-index.c
+++ b/builtin/show-index.c
@@ -43,32 +43,35 @@ int cmd_show_index(int argc,
 	/*
 	 * Fallback to SHA1 if we are running outside of a repository.
 	 *
-	 * TODO: Figure out and implement a way to detect the hash algorithm in use by the
-	 *       the index file passed in and use that instead.
+	 * TODO: If a future implementation of index file version encodes the hash
+	 *       algorithm in its header, enable show-index to infer it from the
+	 *       header rather than relying on repository context or a default fallback.
 	 */
-	if (!the_hash_algo)
+	if (!the_hash_algo) {
+		warning(_("assuming SHA-1; use --object-format to override"));
 		repo_set_hash_algo(the_repository, GIT_HASH_DEFAULT);
+	}
 
 	hashsz = the_hash_algo->rawsz;
 
 	if (fread(top_index, 2 * 4, 1, stdin) != 1)
-		die("unable to read header");
+		die(_("unable to read header"));
 	if (top_index[0] == htonl(PACK_IDX_SIGNATURE)) {
 		version = ntohl(top_index[1]);
 		if (version < 2 || version > 2)
-			die("unknown index version");
+			die(_("unknown index version"));
 		if (fread(top_index, 256 * 4, 1, stdin) != 1)
-			die("unable to read index");
+			die(_("unable to read index"));
 	} else {
 		version = 1;
 		if (fread(&top_index[2], 254 * 4, 1, stdin) != 1)
-			die("unable to read index");
+			die(_("unable to read index"));
 	}
 	nr = 0;
 	for (i = 0; i < 256; i++) {
 		unsigned n = ntohl(top_index[i]);
 		if (n < nr)
-			die("corrupt index file");
+			die(_("corrupt index file"));
 		nr = n;
 	}
 	if (version == 1) {
@@ -76,7 +79,7 @@ int cmd_show_index(int argc,
 			unsigned int offset, entry[(GIT_MAX_RAWSZ + 4) / sizeof(unsigned int)];
 
 			if (fread(entry, 4 + hashsz, 1, stdin) != 1)
-				die("unable to read entry %u/%u", i, nr);
+				die(_("unable to read entry %u/%u"), i, nr);
 			offset = ntohl(entry[0]);
 			printf("%u %s\n", offset, hash_to_hex((void *)(entry+1)));
 		}
@@ -90,15 +93,15 @@ int cmd_show_index(int argc,
 		ALLOC_ARRAY(entries, nr);
 		for (i = 0; i < nr; i++) {
 			if (fread(entries[i].oid.hash, hashsz, 1, stdin) != 1)
-				die("unable to read sha1 %u/%u", i, nr);
+				die(_("unable to read sha1 %u/%u"), i, nr);
 			entries[i].oid.algo = hash_algo_by_ptr(the_hash_algo);
 		}
 		for (i = 0; i < nr; i++)
 			if (fread(&entries[i].crc, 4, 1, stdin) != 1)
-				die("unable to read crc %u/%u", i, nr);
+				die(_("unable to read crc %u/%u"), i, nr);
 		for (i = 0; i < nr; i++)
 			if (fread(&entries[i].off, 4, 1, stdin) != 1)
-				die("unable to read 32b offset %u/%u", i, nr);
+				die(_("unable to read 32b offset %u/%u"), i, nr);
 		for (i = 0; i < nr; i++) {
 			uint64_t offset;
 			uint32_t off = ntohl(entries[i].off);
@@ -107,9 +110,9 @@ int cmd_show_index(int argc,
 			} else {
 				uint32_t off64[2];
 				if ((off & 0x7fffffff) != off64_nr)
-					die("inconsistent 64b offset index");
+					die(_("inconsistent 64b offset index"));
 				if (fread(off64, 8, 1, stdin) != 1)
-					die("unable to read 64b offset %u", off64_nr);
+					die(_("unable to read 64b offset %u"), off64_nr);
 				offset = (((uint64_t)ntohl(off64[0])) << 32) |
 						     ntohl(off64[1]);
 				off64_nr++;
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 0b6f9ed..5d31ace 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -31,31 +31,31 @@ struct show_one_options {
 };
 
 static void show_one(const struct show_one_options *opts,
-		     const char *refname, const struct object_id *oid)
+		     const struct reference *ref)
 {
 	const char *hex;
 	struct object_id peeled;
 
-	if (!odb_has_object(the_repository->objects, oid,
+	if (!odb_has_object(the_repository->objects, ref->oid,
 			    HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR))
-		die("git show-ref: bad ref %s (%s)", refname,
-		    oid_to_hex(oid));
+		die("git show-ref: bad ref %s (%s)", ref->name,
+		    oid_to_hex(ref->oid));
 
 	if (opts->quiet)
 		return;
 
-	hex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);
+	hex = repo_find_unique_abbrev(the_repository, ref->oid, opts->abbrev);
 	if (opts->hash_only)
 		printf("%s\n", hex);
 	else
-		printf("%s %s\n", hex, refname);
+		printf("%s %s\n", hex, ref->name);
 
 	if (!opts->deref_tags)
 		return;
 
-	if (!peel_iterated_oid(the_repository, oid, &peeled)) {
+	if (!reference_get_peeled_oid(the_repository, ref, &peeled)) {
 		hex = repo_find_unique_abbrev(the_repository, &peeled, opts->abbrev);
-		printf("%s %s^{}\n", hex, refname);
+		printf("%s %s^{}\n", hex, ref->name);
 	}
 }
 
@@ -66,26 +66,25 @@ struct show_ref_data {
 	int show_head;
 };
 
-static int show_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-		    int flag UNUSED, void *cbdata)
+static int show_ref(const struct reference *ref, void *cbdata)
 {
 	struct show_ref_data *data = cbdata;
 
-	if (data->show_head && !strcmp(refname, "HEAD"))
+	if (data->show_head && !strcmp(ref->name, "HEAD"))
 		goto match;
 
 	if (data->patterns) {
-		int reflen = strlen(refname);
+		int reflen = strlen(ref->name);
 		const char **p = data->patterns, *m;
 		while ((m = *p++) != NULL) {
 			int len = strlen(m);
 			if (len > reflen)
 				continue;
-			if (memcmp(m, refname + reflen - len, len))
+			if (memcmp(m, ref->name + reflen - len, len))
 				continue;
 			if (len == reflen)
 				goto match;
-			if (refname[reflen - len - 1] == '/')
+			if (ref->name[reflen - len - 1] == '/')
 				goto match;
 		}
 		return 0;
@@ -94,18 +93,15 @@ static int show_ref(const char *refname, const char *referent UNUSED, const stru
 match:
 	data->found_match++;
 
-	show_one(data->show_one_opts, refname, oid);
+	show_one(data->show_one_opts, ref);
 
 	return 0;
 }
 
-static int add_existing(const char *refname,
-			const char *referent UNUSED,
-			const struct object_id *oid UNUSED,
-			int flag UNUSED, void *cbdata)
+static int add_existing(const struct reference *ref, void *cbdata)
 {
 	struct string_list *list = (struct string_list *)cbdata;
-	string_list_insert(list, refname);
+	string_list_insert(list, ref->name);
 	return 0;
 }
 
@@ -179,12 +175,18 @@ static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,
 
 		if ((starts_with(*refs, "refs/") || refname_is_safe(*refs)) &&
 		    !refs_read_ref(get_main_ref_store(the_repository), *refs, &oid)) {
-			show_one(show_one_opts, *refs, &oid);
-		}
-		else if (!show_one_opts->quiet)
+			struct reference ref = {
+				.name = *refs,
+				.oid = &oid,
+			};
+
+			show_one(show_one_opts, &ref);
+		} else if (!show_one_opts->quiet) {
 			die("'%s' - not a valid ref", *refs);
-		else
+		} else {
 			return 1;
+		}
+
 		refs++;
 	}
 
@@ -213,14 +215,19 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,
 		refs_head_ref(get_main_ref_store(the_repository), show_ref,
 			      &show_ref_data);
 	if (opts->branches_only || opts->tags_only) {
-		if (opts->branches_only)
-			refs_for_each_fullref_in(get_main_ref_store(the_repository),
-						 "refs/heads/", NULL,
-						 show_ref, &show_ref_data);
-		if (opts->tags_only)
-			refs_for_each_fullref_in(get_main_ref_store(the_repository),
-						 "refs/tags/", NULL, show_ref,
-						 &show_ref_data);
+		struct refs_for_each_ref_options for_each_ref_opts = { 0 };
+
+		if (opts->branches_only) {
+			for_each_ref_opts.prefix = "refs/heads/";
+			refs_for_each_ref_ext(get_main_ref_store(the_repository),
+					      show_ref, &show_ref_data, &for_each_ref_opts);
+		}
+
+		if (opts->tags_only) {
+			for_each_ref_opts.prefix = "refs/tags/";
+			refs_for_each_ref_ext(get_main_ref_store(the_repository),
+					      show_ref, &show_ref_data, &for_each_ref_opts);
+		}
 	} else {
 		refs_for_each_ref(get_main_ref_store(the_repository),
 				  show_ref, &show_ref_data);
diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
index 15d51e6..f4aa405 100644
--- a/builtin/sparse-checkout.c
+++ b/builtin/sparse-checkout.c
@@ -61,9 +61,10 @@ static int sparse_checkout_list(int argc, const char **argv, const char *prefix,
 	struct pattern_list pl;
 	char *sparse_filename;
 	int res;
+	struct repo_config_values *cfg = repo_config_values(the_repository);
 
 	setup_work_tree();
-	if (!core_apply_sparse_checkout)
+	if (!cfg->apply_sparse_checkout)
 		die(_("this worktree is not sparse"));
 
 	argc = parse_options(argc, argv, prefix,
@@ -91,10 +92,10 @@ static int sparse_checkout_list(int argc, const char **argv, const char *prefix,
 
 		hashmap_for_each_entry(&pl.recursive_hashmap, &iter, pe, ent) {
 			/* pe->pattern starts with "/", skip it */
-			string_list_insert(&sl, pe->pattern + 1);
+			string_list_append(&sl, pe->pattern + 1);
 		}
 
-		string_list_sort(&sl);
+		string_list_sort_u(&sl, 0);
 
 		for (i = 0; i < sl.nr; i++) {
 			quote_c_style(sl.items[i].string, NULL, stdout, 0);
@@ -289,11 +290,10 @@ static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
 		if (!hashmap_contains_parent(&pl->recursive_hashmap,
 					     pe->pattern,
 					     &parent_pattern))
-			string_list_insert(&sl, pe->pattern);
+			string_list_append(&sl, pe->pattern);
 	}
 
-	string_list_sort(&sl);
-	string_list_remove_duplicates(&sl, 0);
+	string_list_sort_u(&sl, 0);
 
 	fprintf(fp, "/*\n!/*/\n");
 
@@ -311,13 +311,12 @@ static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
 		if (!hashmap_contains_parent(&pl->recursive_hashmap,
 					     pe->pattern,
 					     &parent_pattern))
-			string_list_insert(&sl, pe->pattern);
+			string_list_append(&sl, pe->pattern);
 	}
 
 	strbuf_release(&parent_pattern);
 
-	string_list_sort(&sl);
-	string_list_remove_duplicates(&sl, 0);
+	string_list_sort_u(&sl, 0);
 
 	for (i = 0; i < sl.nr; i++) {
 		char *pattern = escaped_pattern(sl.items[i].string);
@@ -399,12 +398,14 @@ static int set_config(struct repository *repo,
 }
 
 static enum sparse_checkout_mode update_cone_mode(int *cone_mode) {
+	struct repo_config_values *cfg = repo_config_values(the_repository);
+
 	/* If not specified, use previous definition of cone mode */
-	if (*cone_mode == -1 && core_apply_sparse_checkout)
+	if (*cone_mode == -1 && cfg->apply_sparse_checkout)
 		*cone_mode = core_sparse_checkout_cone;
 
 	/* Set cone/non-cone mode appropriately */
-	core_apply_sparse_checkout = 1;
+	cfg->apply_sparse_checkout = 1;
 	if (*cone_mode == 1 || *cone_mode == -1) {
 		core_sparse_checkout_cone = 1;
 		return MODE_CONE_PATTERNS;
@@ -416,9 +417,10 @@ static enum sparse_checkout_mode update_cone_mode(int *cone_mode) {
 static int update_modes(struct repository *repo, int *cone_mode, int *sparse_index)
 {
 	int mode, record_mode;
+	struct repo_config_values *cfg = repo_config_values(the_repository);
 
 	/* Determine if we need to record the mode; ensure sparse checkout on */
-	record_mode = (*cone_mode != -1) || !core_apply_sparse_checkout;
+	record_mode = (*cone_mode != -1) || !cfg->apply_sparse_checkout;
 
 	mode = update_cone_mode(cone_mode);
 	if (record_mode && set_config(repo, mode))
@@ -684,6 +686,7 @@ static int modify_pattern_list(struct repository *repo,
 	int result;
 	int changed_config = 0;
 	struct pattern_list *pl = xcalloc(1, sizeof(*pl));
+	struct repo_config_values *cfg = repo_config_values(the_repository);
 
 	switch (m) {
 	case ADD:
@@ -699,9 +702,9 @@ static int modify_pattern_list(struct repository *repo,
 		break;
 	}
 
-	if (!core_apply_sparse_checkout) {
+	if (!cfg->apply_sparse_checkout) {
 		set_config(repo, MODE_ALL_PATTERNS);
-		core_apply_sparse_checkout = 1;
+		cfg->apply_sparse_checkout = 1;
 		changed_config = 1;
 	}
 
@@ -796,9 +799,10 @@ static int sparse_checkout_add(int argc, const char **argv, const char *prefix,
 	};
 	struct strvec patterns = STRVEC_INIT;
 	int ret;
+	struct repo_config_values *cfg = repo_config_values(the_repository);
 
 	setup_work_tree();
-	if (!core_apply_sparse_checkout)
+	if (!cfg->apply_sparse_checkout)
 		die(_("no sparse-checkout to add to"));
 
 	repo_read_index(repo);
@@ -905,9 +909,10 @@ static int sparse_checkout_reapply(int argc, const char **argv,
 			 N_("toggle the use of a sparse index")),
 		OPT_END(),
 	};
+	struct repo_config_values *cfg = repo_config_values(the_repository);
 
 	setup_work_tree();
-	if (!core_apply_sparse_checkout)
+	if (!cfg->apply_sparse_checkout)
 		die(_("must be in a sparse-checkout to reapply sparsity patterns"));
 
 	reapply_opts.cone_mode = -1;
@@ -960,6 +965,7 @@ static int sparse_checkout_clean(int argc, const char **argv,
 	size_t worktree_len;
 	int force = 0, dry_run = 0, verbose = 0;
 	int require_force = 1;
+	struct repo_config_values *cfg = repo_config_values(the_repository);
 
 	struct option builtin_sparse_checkout_clean_options[] = {
 		OPT__DRY_RUN(&dry_run, N_("dry run")),
@@ -969,7 +975,7 @@ static int sparse_checkout_clean(int argc, const char **argv,
 	};
 
 	setup_work_tree();
-	if (!core_apply_sparse_checkout)
+	if (!cfg->apply_sparse_checkout)
 		die(_("must be in a sparse-checkout to clean directories"));
 	if (!core_sparse_checkout_cone)
 		die(_("must be in a cone-mode sparse-checkout to clean directories"));
@@ -1033,9 +1039,10 @@ static int sparse_checkout_disable(int argc, const char **argv,
 		OPT_END(),
 	};
 	struct pattern_list pl;
+	struct repo_config_values *cfg = repo_config_values(the_repository);
 
 	/*
-	 * We do not exit early if !core_apply_sparse_checkout; due to the
+	 * We do not exit early if !repo->config_values.apply_sparse_checkout; due to the
 	 * ability for users to manually muck things up between
 	 *   direct editing of .git/info/sparse-checkout
 	 *   running read-tree -m u HEAD or update-index --skip-worktree
@@ -1061,7 +1068,7 @@ static int sparse_checkout_disable(int argc, const char **argv,
 	hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0);
 	hashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0);
 	pl.use_cone_patterns = 0;
-	core_apply_sparse_checkout = 1;
+	cfg->apply_sparse_checkout = 1;
 
 	add_pattern("/*", empty_base, 0, &pl, 0);
 
diff --git a/builtin/stash.c b/builtin/stash.c
index 948eba0..e79d612 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -347,8 +347,8 @@ static int reset_tree(struct object_id *i_tree, int update, int reset)
 
 	memset(&opts, 0, sizeof(opts));
 
-	tree = parse_tree_indirect(i_tree);
-	if (parse_tree(tree))
+	tree = repo_parse_tree_indirect(the_repository, i_tree);
+	if (repo_parse_tree(the_repository, tree))
 		return -1;
 
 	init_tree_desc(t, &tree->object.oid, tree->buffer, tree->size);
@@ -940,8 +940,8 @@ static void diff_include_untracked(const struct stash_info *info, struct diff_op
 	struct unpack_trees_options unpack_tree_opt = { 0 };
 
 	for (size_t i = 0; i < ARRAY_SIZE(oid); i++) {
-		tree[i] = parse_tree_indirect(oid[i]);
-		if (parse_tree(tree[i]) < 0)
+		tree[i] = repo_parse_tree_indirect(the_repository, oid[i]);
+		if (repo_parse_tree(the_repository, tree[i]) < 0)
 			die(_("failed to parse tree"));
 		init_tree_desc(&tree_desc[i], &tree[i]->object.oid,
 			       tree[i]->buffer, tree[i]->size);
@@ -1495,7 +1495,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
 		goto done;
 	}
 
-	free_commit_list(parents);
+	commit_list_free(parents);
 	parents = NULL;
 
 	if (include_untracked) {
@@ -1564,7 +1564,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
 	strbuf_release(&commit_tree_label);
 	strbuf_release(&msg);
 	strbuf_release(&untracked_files);
-	free_commit_list(parents);
+	commit_list_free(parents);
 	free(branch_name_buf);
 	return ret;
 }
@@ -1849,6 +1849,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 			 N_("stash staged changes only")),
 		OPT_BOOL('p', "patch", &patch_mode,
 			 N_("stash in patch mode")),
+		OPT_BOOL(0, "auto-advance", &add_p_opt.auto_advance,
+			 N_("auto advance to the next file when selecting hunks interactively")),
 		OPT_DIFF_UNIFIED(&add_p_opt.context),
 		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT__QUIET(&quiet, N_("quiet mode")),
@@ -1911,6 +1913,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
 		if (add_p_opt.interhunkcontext != -1)
 			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+		if (!add_p_opt.auto_advance)
+			die(_("the option '%s' requires '%s'"), "--no-auto-advance", "--patch");
 	}
 
 	if (add_p_opt.context < -1)
@@ -1952,6 +1956,8 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 			 N_("stash staged changes only")),
 		OPT_BOOL('p', "patch", &patch_mode,
 			 N_("stash in patch mode")),
+		OPT_BOOL(0, "auto-advance", &add_p_opt.auto_advance,
+			 N_("auto advance to the next file when selecting hunks interactively")),
 		OPT_DIFF_UNIFIED(&add_p_opt.context),
 		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT__QUIET(&quiet, N_("quiet mode")),
@@ -1983,6 +1989,8 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
 		if (add_p_opt.interhunkcontext != -1)
 			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+		if (!add_p_opt.auto_advance)
+			die(_("the option '%s' requires '%s'"), "--no-auto-advance", "--patch");
 	}
 
 	ret = do_push_stash(&ps, stash_msg, quiet, keep_index,
@@ -2184,7 +2192,7 @@ static int do_import_stash(struct repository *r, const char *rev)
 out:
 	if (this && buffer)
 		repo_unuse_commit_buffer(r, this, buffer);
-	free_commit_list(items);
+	commit_list_free(items);
 	free(msg);
 
 	return res;
@@ -2308,7 +2316,7 @@ static int do_export_stash(struct repository *r,
 	 * but where their first parents form a chain to our original empty
 	 * base commit.
 	 */
-	items = reverse_commit_list(items);
+	items = commit_list_reverse(items);
 	for (cur = items; cur; cur = cur->next) {
 		struct commit_list *parents = NULL;
 		struct commit_list **next = &parents;
@@ -2318,7 +2326,7 @@ static int do_export_stash(struct repository *r,
 		next = commit_list_append(prev, next);
 		next = commit_list_append(stash, next);
 		res = write_commit_with_parents(r, &out, &stash->object.oid, parents);
-		free_commit_list(parents);
+		commit_list_free(parents);
 		if (res)
 			goto out;
 		prev = lookup_commit_reference(r, &out);
@@ -2330,7 +2338,7 @@ static int do_export_stash(struct repository *r,
 		puts(oid_to_hex(&prev->object.oid));
 out:
 	strbuf_release(&revision);
-	free_commit_list(items);
+	commit_list_free(items);
 
 	return res;
 }
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index fcd73ab..143f7cb 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -34,6 +34,7 @@
 #include "list-objects-filter-options.h"
 #include "wildmatch.h"
 #include "strbuf.h"
+#include "url.h"
 
 #define OPT_QUIET (1 << 0)
 #define OPT_CACHED (1 << 1)
@@ -435,6 +436,102 @@ struct init_cb {
 };
 #define INIT_CB_INIT { 0 }
 
+static int validate_and_set_submodule_gitdir(struct strbuf *gitdir_path,
+					     const char *submodule_name)
+{
+	const char *value;
+	char *key;
+
+	if (validate_submodule_git_dir(gitdir_path->buf, submodule_name))
+		return -1;
+
+	key = xstrfmt("submodule.%s.gitdir", submodule_name);
+
+	/* Nothing to do if the config already exists. */
+	if (!repo_config_get_string_tmp(the_repository, key, &value)) {
+		free(key);
+		return 0;
+	}
+
+	if (repo_config_set_gently(the_repository, key, gitdir_path->buf)) {
+		free(key);
+		return -1;
+	}
+
+	free(key);
+	return 0;
+}
+
+static void create_default_gitdir_config(const char *submodule_name)
+{
+	struct strbuf gitdir_path = STRBUF_INIT;
+	struct git_hash_ctx ctx;
+	char hex_name_hash[GIT_MAX_HEXSZ + 1], header[128];
+	unsigned char raw_name_hash[GIT_MAX_RAWSZ];
+	int header_len;
+
+	/* Case 1: try the plain module name */
+	repo_git_path_append(the_repository, &gitdir_path, "modules/%s", submodule_name);
+	if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) {
+		strbuf_release(&gitdir_path);
+		return;
+	}
+
+	/* Case 2.1: Try URI-safe (RFC3986) encoding first, this fixes nested gitdirs */
+	strbuf_reset(&gitdir_path);
+	repo_git_path_append(the_repository, &gitdir_path, "modules/");
+	strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_rfc3986_unreserved);
+	if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) {
+		strbuf_release(&gitdir_path);
+		return;
+	}
+
+	/* Case 2.2: Try extended uppercase URI (RFC3986) encoding, to fix case-folding */
+	strbuf_reset(&gitdir_path);
+	repo_git_path_append(the_repository, &gitdir_path, "modules/");
+	strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_casefolding_rfc3986_unreserved);
+	if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name))
+		return;
+
+	/* Case 2.3: Try some derived gitdir names, see if one sticks */
+	for (char c = '0'; c <= '9'; c++) {
+		strbuf_reset(&gitdir_path);
+		repo_git_path_append(the_repository, &gitdir_path, "modules/");
+		strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_rfc3986_unreserved);
+		strbuf_addch(&gitdir_path, c);
+		if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name))
+			return;
+
+		strbuf_reset(&gitdir_path);
+		repo_git_path_append(the_repository, &gitdir_path, "modules/");
+		strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_casefolding_rfc3986_unreserved);
+		strbuf_addch(&gitdir_path, c);
+		if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name))
+			return;
+	}
+
+	/* Case 2.4: If all the above failed, try a hash of the name as a last resort */
+	header_len = snprintf(header, sizeof(header), "blob %zu", strlen(submodule_name));
+	the_hash_algo->init_fn(&ctx);
+	the_hash_algo->update_fn(&ctx, header, header_len);
+	the_hash_algo->update_fn(&ctx, "\0", 1);
+	the_hash_algo->update_fn(&ctx, submodule_name, strlen(submodule_name));
+	the_hash_algo->final_fn(raw_name_hash, &ctx);
+	hash_to_hex_algop_r(hex_name_hash, raw_name_hash, the_hash_algo);
+	strbuf_reset(&gitdir_path);
+	repo_git_path_append(the_repository, &gitdir_path, "modules/%s", hex_name_hash);
+	if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) {
+		strbuf_release(&gitdir_path);
+		return;
+	}
+
+	/* Case 3: nothing worked, error out */
+	die(_("failed to set a valid default config for 'submodule.%s.gitdir'. "
+	      "Please ensure it is set, for example by running something like: "
+	      "'git config submodule.%s.gitdir .git/modules/%s'"),
+	    submodule_name, submodule_name, submodule_name);
+}
+
 static void init_submodule(const char *path, const char *prefix,
 			   const char *super_prefix,
 			   unsigned int flags)
@@ -511,6 +608,10 @@ static void init_submodule(const char *path, const char *prefix,
 		if (repo_config_set_gently(the_repository, sb.buf, upd))
 			die(_("Failed to register update mode for submodule path '%s'"), displaypath);
 	}
+
+	if (the_repository->repository_format_submodule_path_cfg)
+		create_default_gitdir_config(sub->name);
+
 	strbuf_release(&sb);
 	free(displaypath);
 	free(url);
@@ -593,16 +694,12 @@ static void print_status(unsigned int flags, char state, const char *path,
 	printf("\n");
 }
 
-static int handle_submodule_head_ref(const char *refname UNUSED,
-				     const char *referent UNUSED,
-				     const struct object_id *oid,
-				     int flags UNUSED,
-				     void *cb_data)
+static int handle_submodule_head_ref(const struct reference *ref, void *cb_data)
 {
 	struct object_id *output = cb_data;
 
-	if (oid)
-		oidcpy(output, oid);
+	if (ref->oid)
+		oidcpy(output, ref->oid);
 
 	return 0;
 }
@@ -1208,6 +1305,82 @@ static int module_summary(int argc, const char **argv, const char *prefix,
 	return ret;
 }
 
+static int module_gitdir(int argc, const char **argv, const char *prefix UNUSED,
+			 struct repository *repo)
+{
+	struct strbuf gitdir = STRBUF_INIT;
+
+	if (argc != 2)
+		usage(_("git submodule--helper gitdir <name>"));
+
+	submodule_name_to_gitdir(&gitdir, repo, argv[1]);
+
+	printf("%s\n", gitdir.buf);
+
+	strbuf_release(&gitdir);
+	return 0;
+}
+
+static int module_migrate(int argc UNUSED, const char **argv UNUSED,
+			  const char *prefix UNUSED, struct repository *repo)
+{
+	struct strbuf module_dir = STRBUF_INIT;
+	DIR *dir;
+	struct dirent *de;
+	int repo_version = 0;
+
+	repo_git_path_append(repo, &module_dir, "modules/");
+
+	dir = opendir(module_dir.buf);
+	if (!dir)
+		die(_("could not open '%s'"), module_dir.buf);
+
+	while ((de = readdir(dir))) {
+		struct strbuf gitdir_path = STRBUF_INIT;
+		char *key;
+		const char *value;
+
+		if (is_dot_or_dotdot(de->d_name))
+			continue;
+
+		strbuf_addf(&gitdir_path, "%s/%s", module_dir.buf, de->d_name);
+		if (!is_git_directory(gitdir_path.buf)) {
+			strbuf_release(&gitdir_path);
+			continue;
+		}
+		strbuf_release(&gitdir_path);
+
+		key = xstrfmt("submodule.%s.gitdir", de->d_name);
+		if (!repo_config_get_string_tmp(repo, key, &value)) {
+			/* Already has a gitdir config, nothing to do. */
+			free(key);
+			continue;
+		}
+		free(key);
+
+		create_default_gitdir_config(de->d_name);
+	}
+
+	closedir(dir);
+	strbuf_release(&module_dir);
+
+	repo_config_get_int(the_repository, "core.repositoryformatversion", &repo_version);
+	if (repo_version == 0 &&
+	    repo_config_set_gently(repo, "core.repositoryformatversion", "1"))
+		die(_("could not set core.repositoryformatversion to 1.\n"
+		      "Please set it for migration to work, for example:\n"
+		      "git config core.repositoryformatversion 1"));
+
+	if (repo_config_set_gently(repo, "extensions.submodulePathConfig", "true"))
+		die(_("could not enable submodulePathConfig extension. It is required\n"
+		      "for migration to work. Please enable it in the root repo:\n"
+		      "git config extensions.submodulePathConfig true"));
+
+	repo->repository_format_submodule_path_cfg = 1;
+
+	return 0;
+}
+
 struct sync_cb {
 	const char *prefix;
 	const char *super_prefix;
@@ -1703,10 +1876,6 @@ static int clone_submodule(const struct module_clone_data *clone_data,
 		clone_data_path = to_free = xstrfmt("%s/%s", repo_get_work_tree(the_repository),
 						    clone_data->path);
 
-	if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0)
-		die(_("refusing to create/use '%s' in another submodule's "
-		      "git dir"), sm_gitdir);
-
 	if (!file_exists(sm_gitdir)) {
 		if (clone_data->require_init && !stat(clone_data_path, &st) &&
 		    !is_empty_dir(clone_data_path))
@@ -1793,8 +1962,9 @@ static int clone_submodule(const struct module_clone_data *clone_data,
 		char *head = xstrfmt("%s/HEAD", sm_gitdir);
 		unlink(head);
 		free(head);
-		die(_("refusing to create/use '%s' in another submodule's "
-		      "git dir"), sm_gitdir);
+		die(_("refusing to create/use '%s' in another submodule's git dir. "
+		      "Enabling extensions.submodulePathConfig should fix this."),
+		    sm_gitdir);
 	}
 
 	connect_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0);
@@ -1907,6 +2077,13 @@ static int determine_submodule_update_strategy(struct repository *r,
 	const char *val;
 	int ret;
 
+	/*
+	 * NEEDSWORK: audit and ensure that update_submodule() has right
+	 * to assume that submodule_from_path() above will always succeed.
+	 */
+	if (!sub)
+		BUG("update_submodule assumes a submodule exists at path (%s)",
+		    path);
 	key = xstrfmt("submodule.%s.update", sub->name);
 
 	if (update) {
@@ -3123,9 +3300,10 @@ static int module_create_branch(int argc, const char **argv, const char *prefix,
 		N_("git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start-oid> <start-name>"),
 		NULL
 	};
+	struct repo_config_values *cfg = repo_config_values(the_repository);
 
 	repo_config(the_repository, git_default_config, NULL);
-	track = git_branch_track;
+	track = cfg->branch_track;
 	argc = parse_options(argc, argv, prefix, options, usage, 0);
 
 	if (argc != 3)
@@ -3187,13 +3365,13 @@ static void append_fetch_remotes(struct strbuf *msg, const char *git_dir_path)
 
 static int add_submodule(const struct add_data *add_data)
 {
-	char *submod_gitdir_path;
 	struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT;
 	struct string_list reference = STRING_LIST_INIT_NODUP;
 	int ret = -1;
 
 	/* perhaps the path already exists and is already a git repo, else clone it */
 	if (is_directory(add_data->sm_path)) {
+		char *submod_gitdir_path;
 		struct strbuf sm_path = STRBUF_INIT;
 		strbuf_addstr(&sm_path, add_data->sm_path);
 		submod_gitdir_path = xstrfmt("%s/.git", add_data->sm_path);
@@ -3207,10 +3385,11 @@ static int add_submodule(const struct add_data *add_data)
 		free(submod_gitdir_path);
 	} else {
 		struct child_process cp = CHILD_PROCESS_INIT;
+		struct strbuf submod_gitdir = STRBUF_INIT;
 
-		submod_gitdir_path = xstrfmt(".git/modules/%s", add_data->sm_name);
+		submodule_name_to_gitdir(&submod_gitdir, the_repository, add_data->sm_name);
 
-		if (is_directory(submod_gitdir_path)) {
+		if (is_directory(submod_gitdir.buf)) {
 			if (!add_data->force) {
 				struct strbuf msg = STRBUF_INIT;
 				char *die_msg;
@@ -3219,8 +3398,8 @@ static int add_submodule(const struct add_data *add_data)
 						    "locally with remote(s):\n"),
 					    add_data->sm_name);
 
-				append_fetch_remotes(&msg, submod_gitdir_path);
-				free(submod_gitdir_path);
+				append_fetch_remotes(&msg, submod_gitdir.buf);
+				strbuf_release(&submod_gitdir);
 
 				strbuf_addf(&msg, _("If you want to reuse this local git "
 						    "directory instead of cloning again from\n"
@@ -3238,7 +3417,7 @@ static int add_submodule(const struct add_data *add_data)
 					 "submodule '%s'\n"), add_data->sm_name);
 			}
 		}
-		free(submod_gitdir_path);
+		strbuf_release(&submod_gitdir);
 
 		clone_data.prefix = add_data->prefix;
 		clone_data.path = add_data->sm_path;
@@ -3531,14 +3710,15 @@ static int module_add(int argc, const char **argv, const char *prefix,
 		}
 	}
 
-	if(!add_data.sm_name)
+	if (!add_data.sm_name)
 		add_data.sm_name = add_data.sm_path;
 
 	existing = submodule_from_name(the_repository,
 					null_oid(the_hash_algo),
 					add_data.sm_name);
 
-	if (existing && strcmp(existing->path, add_data.sm_path)) {
+	if (existing && existing->path &&
+	    strcmp(existing->path, add_data.sm_path)) {
 		if (!force) {
 			die(_("submodule name '%s' already used for path '%s'"),
 			    add_data.sm_name, existing->path);
@@ -3565,6 +3745,9 @@ static int module_add(int argc, const char **argv, const char *prefix,
 	add_data.progress = !!progress;
 	add_data.dissociate = !!dissociate;
 
+	if (the_repository->repository_format_submodule_path_cfg)
+		create_default_gitdir_config(add_data.sm_name);
+
 	if (add_submodule(&add_data))
 		goto cleanup;
 	configure_added_submodule(&add_data);
@@ -3590,6 +3773,8 @@ int cmd_submodule__helper(int argc,
 		NULL
 	};
 	struct option options[] = {
+		OPT_SUBCOMMAND("migrate-gitdir-configs", &fn, module_migrate),
+		OPT_SUBCOMMAND("gitdir", &fn, module_gitdir),
 		OPT_SUBCOMMAND("clone", &fn, module_clone),
 		OPT_SUBCOMMAND("add", &fn, module_add),
 		OPT_SUBCOMMAND("update", &fn, module_update),
diff --git a/builtin/tag.c b/builtin/tag.c
index f0665af..aeb04c4 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -149,11 +149,11 @@ static int verify_tag(const char *name, const char *ref UNUSED,
 	if (format->format)
 		flags = GPG_VERIFY_OMIT_STATUS;
 
-	if (gpg_verify_tag(oid, name, flags))
+	if (gpg_verify_tag(the_repository, oid, name, flags))
 		return -1;
 
 	if (format->format)
-		pretty_print_ref(name, oid, format);
+		pretty_print_ref(name, oid, NULL, format);
 
 	return 0;
 }
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index ef79e43..6fc64e9 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -363,7 +363,7 @@ struct input_zstream_data {
 	int status;
 };
 
-static const void *feed_input_zstream(struct input_stream *in_stream,
+static const void *feed_input_zstream(struct odb_write_stream *in_stream,
 				      unsigned long *readlen)
 {
 	struct input_zstream_data *data = in_stream->data;
@@ -393,7 +393,7 @@ static void stream_blob(unsigned long size, unsigned nr)
 {
 	git_zstream zstream = { 0 };
 	struct input_zstream_data data = { 0 };
-	struct input_stream in_stream = {
+	struct odb_write_stream in_stream = {
 		.read = feed_input_zstream,
 		.data = &data,
 	};
@@ -402,8 +402,7 @@ static void stream_blob(unsigned long size, unsigned nr)
 	data.zstream = &zstream;
 	git_inflate_init(&zstream);
 
-	if (stream_loose_object(the_repository->objects->sources,
-				&in_stream, size, &info->oid))
+	if (odb_write_object_stream(the_repository->objects, &in_stream, size, &info->oid))
 		die(_("failed to write object in stream"));
 
 	if (data.status != Z_STREAM_END)
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 195437e..2d68c40 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -573,15 +573,18 @@ static void print_rejected_refs(const char *refname,
 				const char *old_target,
 				const char *new_target,
 				enum ref_transaction_error err,
+				const char *details,
 				void *cb_data UNUSED)
 {
 	struct strbuf sb = STRBUF_INIT;
-	const char *reason = ref_transaction_error_msg(err);
+
+	if (details && *details)
+		error("%s", details);
 
 	strbuf_addf(&sb, "rejected %s %s %s %s\n", refname,
 		    new_oid ? oid_to_hex(new_oid) : new_target,
 		    old_oid ? oid_to_hex(old_oid) : old_target,
-		    reason);
+		    ref_transaction_error_msg(err));
 
 	fwrite(sb.buf, sb.len, 1, stdout);
 	strbuf_release(&sb);
diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c
index 97d7c95..25312bb 100644
--- a/builtin/upload-archive.c
+++ b/builtin/upload-archive.c
@@ -4,8 +4,8 @@
 #define USE_THE_REPOSITORY_VARIABLE
 #include "builtin.h"
 #include "archive.h"
-#include "path.h"
 #include "pkt-line.h"
+#include "setup.h"
 #include "sideband.h"
 #include "run-command.h"
 #include "strvec.h"
diff --git a/builtin/upload-pack.c b/builtin/upload-pack.c
index c2bbc03..30498fa 100644
--- a/builtin/upload-pack.c
+++ b/builtin/upload-pack.c
@@ -5,11 +5,11 @@
 #include "gettext.h"
 #include "pkt-line.h"
 #include "parse-options.h"
-#include "path.h"
 #include "protocol.h"
 #include "replace-object.h"
 #include "upload-pack.h"
 #include "serve.h"
+#include "setup.h"
 #include "commit.h"
 #include "environment.h"
 
diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c
index cd6bc11..4a261b2 100644
--- a/builtin/verify-tag.c
+++ b/builtin/verify-tag.c
@@ -61,13 +61,13 @@ int cmd_verify_tag(int argc,
 			continue;
 		}
 
-		if (gpg_verify_tag(&oid, name, flags)) {
+		if (gpg_verify_tag(repo, &oid, name, flags)) {
 			had_error = 1;
 			continue;
 		}
 
 		if (format.format)
-			pretty_print_ref(name, &oid, &format);
+			pretty_print_ref(name, &oid, NULL, &format);
 	}
 	return had_error;
 }
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 812774a..bc2d0d6 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -252,7 +252,7 @@ static int prune(int ac, const char **av, const char *prefix,
 		OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
 		OPT__VERBOSE(&verbose, N_("report pruned working trees")),
 		OPT_EXPIRY_DATE(0, "expire", &expire,
-				N_("expire working trees older than <time>")),
+				N_("prune missing working trees older than <time>")),
 		OPT_END()
 	};
 
@@ -425,6 +425,39 @@ static int make_worktree_orphan(const char * ref, const struct add_opts *opts,
 	return run_command(&cp);
 }
 
+/*
+ * References for worktrees are generally stored in '$GIT_DIR/worktrees/<wt_id>'.
+ * But when using alternate reference directories, we want to store the worktree
+ * references in '$ALTERNATE_REFERENCE_DIR/worktrees/<wt_id>'.
+ *
+ * Create the necessary folder structure to facilitate the same. But to ensure
+ * that the former path is still considered a Git directory, add stubs.
+ */
+static void setup_alternate_ref_dir(struct worktree *wt, const char *wt_git_path)
+{
+	struct strbuf sb = STRBUF_INIT;
+	char *path;
+
+	path = wt->repo->ref_storage_payload;
+	if (!path)
+		return;
+
+	if (!is_absolute_path(path))
+		strbuf_addf(&sb, "%s/", wt->repo->commondir);
+
+	strbuf_addf(&sb, "%s/worktrees", path);
+	safe_create_dir(wt->repo, sb.buf, 1);
+	strbuf_addf(&sb, "/%s", wt->id);
+	safe_create_dir(wt->repo, sb.buf, 1);
+	strbuf_reset(&sb);
+
+	strbuf_addf(&sb, "this worktree stores references in %s/worktrees/%s",
+		    path, wt->id);
+	refs_create_refdir_stubs(wt->repo, wt_git_path, sb.buf);
+
+	strbuf_release(&sb);
+}
+
 static int add_worktree(const char *path, const char *refname,
 			const struct add_opts *opts)
 {
@@ -440,6 +473,7 @@ static int add_worktree(const char *path, const char *refname,
 	struct strbuf sb_name = STRBUF_INIT;
 	struct worktree **worktrees, *wt = NULL;
 	struct ref_store *wt_refs;
+	struct repo_config_values *cfg = repo_config_values(the_repository);
 
 	worktrees = get_worktrees();
 	check_candidate_path(path, opts->force, worktrees, "add");
@@ -518,6 +552,7 @@ static int add_worktree(const char *path, const char *refname,
 		ret = error(_("could not find created worktree '%s'"), name);
 		goto done;
 	}
+	setup_alternate_ref_dir(wt, sb_repo.buf);
 	wt_refs = get_worktree_ref_store(wt);
 
 	ret = ref_store_create_on_disk(wt_refs, REF_STORE_CREATE_ON_DISK_IS_WORKTREE, &sb);
@@ -536,7 +571,7 @@ static int add_worktree(const char *path, const char *refname,
 	 * If the current worktree has sparse-checkout enabled, then copy
 	 * the sparse-checkout patterns from the current worktree.
 	 */
-	if (core_apply_sparse_checkout)
+	if (cfg->apply_sparse_checkout)
 		copy_sparse_checkout(sb_repo.buf);
 
 	/*
@@ -635,11 +670,7 @@ static void print_preparing_worktree_line(int detach,
  *
  * Returns 0 on failure and non-zero on success.
  */
-static int first_valid_ref(const char *refname UNUSED,
-			   const char *referent UNUSED,
-			   const struct object_id *oid UNUSED,
-			   int flags UNUSED,
-			   void *cb_data UNUSED)
+static int first_valid_ref(const struct reference *ref UNUSED, void *cb_data UNUSED)
 {
 	return 1;
 }
@@ -979,14 +1010,18 @@ static void show_worktree_porcelain(struct worktree *wt, int line_terminator)
 	fputc(line_terminator, stdout);
 }
 
-static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
+struct worktree_display {
+	char *path;
+	int width;
+};
+
+static void show_worktree(struct worktree *wt, struct worktree_display *display,
+			  int path_maxwidth, int abbrev_len)
 {
 	struct strbuf sb = STRBUF_INIT;
-	int cur_path_len = strlen(wt->path);
-	int path_adj = cur_path_len - utf8_strwidth(wt->path);
 	const char *reason;
 
-	strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path);
+	strbuf_addf(&sb, "%s%*s", display->path, 1 + path_maxwidth - display->width, "");
 	if (wt->is_bare)
 		strbuf_addstr(&sb, "(bare)");
 	else {
@@ -1020,20 +1055,27 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
 	strbuf_release(&sb);
 }
 
-static void measure_widths(struct worktree **wt, int *abbrev, int *maxlen)
+static void measure_widths(struct worktree **wt, int *abbrev,
+			   struct worktree_display **d, int *maxwidth)
 {
-	int i;
+	int i, display_alloc = 0;
+	struct worktree_display *display = NULL;
+	struct strbuf buf = STRBUF_INIT;
 
 	for (i = 0; wt[i]; i++) {
 		int sha1_len;
-		int path_len = strlen(wt[i]->path);
+		ALLOC_GROW(display, i + 1, display_alloc);
+		quote_path(wt[i]->path, NULL, &buf, 0);
+		display[i].width = utf8_strwidth(buf.buf);
+		display[i].path = strbuf_detach(&buf, NULL);
 
-		if (path_len > *maxlen)
-			*maxlen = path_len;
+		if (display[i].width > *maxwidth)
+			*maxwidth = display[i].width;
 		sha1_len = strlen(repo_find_unique_abbrev(the_repository, &wt[i]->head_oid, *abbrev));
 		if (sha1_len > *abbrev)
 			*abbrev = sha1_len;
 	}
+	*d = display;
 }
 
 static int pathcmp(const void *a_, const void *b_)
@@ -1063,7 +1105,7 @@ static int list(int ac, const char **av, const char *prefix,
 		OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
 		OPT__VERBOSE(&verbose, N_("show extended annotations and reasons, if available")),
 		OPT_EXPIRY_DATE(0, "expire", &expire,
-				N_("add 'prunable' annotation to worktrees older than <time>")),
+				N_("add 'prunable' annotation to missing worktrees older than <time>")),
 		OPT_SET_INT('z', NULL, &line_terminator,
 			    N_("terminate records with a NUL character"), '\0'),
 		OPT_END()
@@ -1079,21 +1121,27 @@ static int list(int ac, const char **av, const char *prefix,
 		die(_("the option '%s' requires '%s'"), "-z", "--porcelain");
 	else {
 		struct worktree **worktrees = get_worktrees();
-		int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i;
+		int path_maxwidth = 0, abbrev = DEFAULT_ABBREV, i;
+		struct worktree_display *display = NULL;
 
 		/* sort worktrees by path but keep main worktree at top */
 		pathsort(worktrees + 1);
 
 		if (!porcelain)
-			measure_widths(worktrees, &abbrev, &path_maxlen);
+			measure_widths(worktrees, &abbrev,
+				       &display, &path_maxwidth);
 
 		for (i = 0; worktrees[i]; i++) {
 			if (porcelain)
 				show_worktree_porcelain(worktrees[i],
 							line_terminator);
 			else
-				show_worktree(worktrees[i], path_maxlen, abbrev);
+				show_worktree(worktrees[i],
+					      &display[i], path_maxwidth, abbrev);
 		}
+		for (i = 0; display && worktrees[i]; i++)
+			free(display[i].path);
+		free(display);
 		free_worktrees(worktrees);
 	}
 	return 0;
@@ -1178,14 +1226,14 @@ static void validate_no_submodules(const struct worktree *wt)
 
 	wt_gitdir = get_worktree_git_dir(wt);
 
-	if (is_directory(worktree_git_path(the_repository, wt, "modules"))) {
+	if (is_directory(worktree_git_path(wt, "modules"))) {
 		/*
 		 * There could be false positives, e.g. the "modules"
 		 * directory exists but is empty. But it's a rare case and
 		 * this simpler check is probably good enough for now.
 		 */
 		found_submodules = 1;
-	} else if (read_index_from(&istate, worktree_git_path(the_repository, wt, "index"),
+	} else if (read_index_from(&istate, worktree_git_path(wt, "index"),
 				   wt_gitdir) > 0) {
 		for (i = 0; i < istate.cache_nr; i++) {
 			struct cache_entry *ce = istate.cache[i];
diff --git a/bundle-uri.c b/bundle-uri.c
index 57cccfc..3b2e347 100644
--- a/bundle-uri.c
+++ b/bundle-uri.c
@@ -89,7 +89,10 @@ static int summarize_bundle(struct remote_bundle_info *info, void *data)
 {
 	FILE *fp = data;
 	fprintf(fp, "[bundle \"%s\"]\n", info->id);
-	fprintf(fp, "\turi = %s\n", info->uri);
+	if (info->uri)
+		fprintf(fp, "\turi = %s\n", info->uri);
+	else
+		fprintf(fp, "\t# uri = (missing)\n");
 
 	if (info->creationToken)
 		fprintf(fp, "\tcreationToken = %"PRIu64"\n", info->creationToken);
@@ -267,6 +270,19 @@ int bundle_uri_parse_config_format(const char *uri,
 		result = 1;
 	}
 
+	if (!result) {
+		struct hashmap_iter iter;
+		struct remote_bundle_info *bundle;
+
+		hashmap_for_each_entry(&list->bundles, &iter, bundle, ent) {
+			if (!bundle->uri) {
+				error(_("bundle list at '%s': bundle '%s' has no uri"),
+				      uri, bundle->id ? bundle->id : "<unknown>");
+				result = 1;
+			}
+		}
+	}
+
 	return result;
 }
 
@@ -751,6 +767,12 @@ static int fetch_bundle_uri_internal(struct repository *r,
 		return -1;
 	}
 
+	if (!bundle->uri) {
+		error(_("bundle '%s' has no uri"),
+		      bundle->id ? bundle->id : "<unknown>");
+		return -1;
+	}
+
 	if (!bundle->file &&
 	    !(bundle->file = find_temp_filename())) {
 		result = -1;
diff --git a/cache-tree.c b/cache-tree.c
index 2aba470..16c3a36 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -548,12 +548,41 @@ void cache_tree_write(struct strbuf *sb, struct cache_tree *root)
 	trace2_region_leave("cache_tree", "write", the_repository);
 }
 
+static int parse_int(const char **ptr, unsigned long *len_p, int *out)
+{
+	const char *s = *ptr;
+	unsigned long len = *len_p;
+	int ret = 0;
+	int sign = 1;
+
+	while (len && *s == '-') {
+		sign *= -1;
+		s++;
+		len--;
+	}
+
+	while (len) {
+		if (!isdigit(*s))
+			break;
+		ret *= 10;
+		ret += *s - '0';
+		s++;
+		len--;
+	}
+
+	if (s == *ptr)
+		return -1;
+
+	*ptr = s;
+	*len_p = len;
+	*out = sign * ret;
+	return 0;
+}
+
 static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)
 {
 	const char *buf = *buffer;
 	unsigned long size = *size_p;
-	const char *cp;
-	char *ep;
 	struct cache_tree *it;
 	int i, subtree_nr;
 	const unsigned rawsz = the_hash_algo->rawsz;
@@ -569,19 +598,14 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)
 	buf++; size--;
 	it = cache_tree();
 
-	cp = buf;
-	it->entry_count = strtol(cp, &ep, 10);
-	if (cp == ep)
+	if (parse_int(&buf, &size, &it->entry_count) < 0)
 		goto free_return;
-	cp = ep;
-	subtree_nr = strtol(cp, &ep, 10);
-	if (cp == ep)
+	if (!size || *buf != ' ')
 		goto free_return;
-	while (size && *buf && *buf != '\n') {
-		size--;
-		buf++;
-	}
-	if (!size)
+	buf++; size--;
+	if (parse_int(&buf, &size, &subtree_nr) < 0)
+		goto free_return;
+	if (!size || *buf != '\n')
 		goto free_return;
 	buf++; size--;
 	if (0 <= it->entry_count) {
@@ -789,7 +813,7 @@ static void prime_cache_tree_rec(struct repository *r,
 			struct cache_tree_sub *sub;
 			struct tree *subtree = lookup_tree(r, &entry.oid);
 
-			if (parse_tree(subtree) < 0)
+			if (repo_parse_tree(the_repository, subtree) < 0)
 				exit(128);
 			sub = cache_tree_sub(it, entry.path);
 			sub->cache_tree = cache_tree();
diff --git a/chdir-notify.c b/chdir-notify.c
index 0d7bc04..f8bfe3c 100644
--- a/chdir-notify.c
+++ b/chdir-notify.c
@@ -25,6 +25,24 @@ void chdir_notify_register(const char *name,
 	list_add_tail(&e->list, &chdir_notify_entries);
 }
 
+void chdir_notify_unregister(const char *name, chdir_notify_callback cb,
+			     void *data)
+{
+	struct list_head *pos, *p;
+
+	list_for_each_safe(pos, p, &chdir_notify_entries) {
+		struct chdir_notify_entry *e =
+			list_entry(pos, struct chdir_notify_entry, list);
+
+		if (e->cb != cb || e->data != data || !e->name != !name ||
+		    (e->name && strcmp(e->name, name)))
+			continue;
+
+		list_del(pos);
+		free(e);
+	}
+}
+
 static void reparent_cb(const char *name,
 			const char *old_cwd,
 			const char *new_cwd,
diff --git a/chdir-notify.h b/chdir-notify.h
index 366e4c1..81eb69d 100644
--- a/chdir-notify.h
+++ b/chdir-notify.h
@@ -41,6 +41,8 @@ typedef void (*chdir_notify_callback)(const char *name,
 				      const char *new_cwd,
 				      void *data);
 void chdir_notify_register(const char *name, chdir_notify_callback cb, void *data);
+void chdir_notify_unregister(const char *name, chdir_notify_callback cb,
+			     void *data);
 void chdir_notify_reparent(const char *name, char **path);
 
 /*
diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh
index 6ee8216..c55441d 100755
--- a/ci/install-dependencies.sh
+++ b/ci/install-dependencies.sh
@@ -76,6 +76,17 @@
 		sudo update-alternatives --set sudo /usr/bin/sudo.ws
 	fi
 
+	# on uutils v0.2.2 from rust-coreutils,
+	#     dirname "foo/."
+	# outputs "." instead of "foo" like it should.
+	# Use GNU coreutils to provide dirname instead.
+	#
+	# See <https://github.com/uutils/coreutils/issues/10508>.
+	if test -x /usr/bin/gnudirname
+	then
+		ln -sfT /usr/bin/gnudirname /usr/bin/dirname
+	fi
+
 	case "$distro" in
 	ubuntu-*)
 		mkdir --parents "$CUSTOM_PATH"
diff --git a/ci/lib.sh b/ci/lib.sh
index f561884..42a2b6a 100755
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -231,6 +231,10 @@
 	distro=$(echo "$CI_JOB_IMAGE" | tr : -)
 elif test true = "$GITLAB_CI"
 then
+	# This environment is multiple kB in size and may cause us to exceed
+	# xargs(1) limits on Windows.
+	unset GITLAB_FEATURES
+
 	CI_TYPE=gitlab-ci
 	CI_BRANCH="$CI_COMMIT_REF_NAME"
 	CI_COMMIT="$CI_COMMIT_SHA"
@@ -356,6 +360,9 @@
 	;;
 linux-leaks|linux-reftable-leaks)
 	export SANITIZE=leak
+	export NO_CVS_TESTS=LetsSaveSomeTime
+	export NO_SVN_TESTS=LetsSaveSomeTime
+	export NO_P4_TESTS=LetsSaveSomeTime
 	;;
 linux-asan-ubsan)
 	export SANITIZE=address,undefined
diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
index 8bda62b..28cfe73 100755
--- a/ci/run-build-and-tests.sh
+++ b/ci/run-build-and-tests.sh
@@ -5,6 +5,8 @@
 
 . ${0%/*}/lib.sh
 
+export TEST_CONTRIB_TOO=yes
+
 case "$jobname" in
 fedora-breaking-changes-musl|linux-breaking-changes)
 	export WITH_BREAKING_CHANGES=YesPlease
@@ -36,6 +38,7 @@
 linux-reftable|linux-reftable-leaks|osx-reftable)
 	export GIT_TEST_DEFAULT_REF_FORMAT=reftable
 	;;
+
 esac
 
 case "$jobname" in
diff --git a/ci/run-test-slice-meson.sh b/ci/run-test-slice-meson.sh
new file mode 100755
index 0000000..a6df927
--- /dev/null
+++ b/ci/run-test-slice-meson.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# We must load the build options so we know where to find
+# things like TEST_OUTPUT_DIRECTORY. This has to come before
+# loading lib.sh, though, because it may clobber some CI lib
+# variables like our custom GIT_TEST_OPTS.
+. "$1"/GIT-BUILD-OPTIONS
+. ${0%/*}/lib.sh
+
+group "Run tests" \
+	meson test -C "$1" --no-rebuild --print-errorlogs \
+		--test-args="$GIT_TEST_OPTS" --slice "$(($2))/$3" ||
+handle_failed_tests
diff --git a/ci/run-test-slice.sh b/ci/run-test-slice.sh
index 0444c79..ff948e3 100755
--- a/ci/run-test-slice.sh
+++ b/ci/run-test-slice.sh
@@ -5,9 +5,9 @@
 
 . ${0%/*}/lib.sh
 
-group "Run tests" make --quiet -C t T="$(cd t &&
-	./helper/test-tool path-utils slice-tests "$1" "$2" t[0-9]*.sh |
-	tr '\n' ' ')" ||
+TESTS=$(cd t && ./helper/test-tool path-utils slice-tests "$1" "$2" t[0-9]*.sh)
+
+group "Run tests" make --quiet -C t T="$(echo "$TESTS" | tr '\n' ' ')" ||
 handle_failed_tests
 
 # We only have one unit test at the moment, so run it in the first slice
diff --git a/color.c b/color.c
index 07ac8c9..00b53f9 100644
--- a/color.c
+++ b/color.c
@@ -223,11 +223,6 @@ static int parse_attr(const char *name, size_t len)
 	return -1;
 }
 
-int color_parse(const char *value, char *dst)
-{
-	return color_parse_mem(value, strlen(value), dst);
-}
-
 /*
  * Write the ANSI color codes for "c" to "out"; the string should
  * already have the ANSI escape code in it. "out" should have enough
@@ -264,7 +259,8 @@ static int color_empty(const struct color *c)
 	return c->type <= COLOR_NORMAL;
 }
 
-int color_parse_mem(const char *value, int value_len, char *dst)
+static int color_parse_mem_1(const char *value, int value_len,
+			     char *dst, int quiet)
 {
 	const char *ptr = value;
 	int len = value_len;
@@ -365,10 +361,25 @@ int color_parse_mem(const char *value, int value_len, char *dst)
 	OUT(0);
 	return 0;
 bad:
-	return error(_("invalid color value: %.*s"), value_len, value);
+	return quiet ? -1 : error(_("invalid color value: %.*s"), value_len, value);
 #undef OUT
 }
 
+int color_parse_mem(const char *value, int value_len, char *dst)
+{
+	return color_parse_mem_1(value, value_len, dst, 0);
+}
+
+int color_parse(const char *value, char *dst)
+{
+	return color_parse_mem(value, strlen(value), dst);
+}
+
+int color_parse_quietly(const char *value, char *dst)
+{
+	return color_parse_mem_1(value, strlen(value), dst, 1);
+}
+
 enum git_colorbool git_config_colorbool(const char *var, const char *value)
 {
 	if (value) {
diff --git a/color.h b/color.h
index 43e6c9a..0d72540 100644
--- a/color.h
+++ b/color.h
@@ -118,6 +118,7 @@ bool want_color_fd(int fd, enum git_colorbool var);
  * terminal.
  */
 int color_parse(const char *value, char *dst);
+int color_parse_quietly(const char *value, char *dst);
 int color_parse_mem(const char *value, int len, char *dst);
 
 /*
diff --git a/command-list.txt b/command-list.txt
index accd3d0..f9005cf 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -115,6 +115,7 @@
 git-gui                                 mainporcelain
 git-hash-object                         plumbingmanipulators
 git-help                                ancillaryinterrogators          complete
+git-history                             mainporcelain           history
 git-hook                                purehelpers
 git-http-backend                        synchingrepositories
 git-http-fetch                          synchelpers
diff --git a/commit-graph.c b/commit-graph.c
index 474454d..f8e2414 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -965,7 +965,7 @@ static int fill_commit_in_graph(struct commit *item,
 	do {
 		if (g->chunk_extra_edges_size / sizeof(uint32_t) <= parent_data_pos) {
 			error(_("commit-graph extra-edges pointer out of bounds"));
-			free_commit_list(item->parents);
+			commit_list_free(item->parents);
 			item->parents = NULL;
 			item->object.parsed = 0;
 			return 0;
@@ -1127,18 +1127,12 @@ struct tree *get_commit_tree_in_graph(struct repository *r, const struct commit
 	return get_commit_tree_in_graph_one(r->objects->commit_graph, c);
 }
 
-struct packed_commit_list {
-	struct commit **list;
-	size_t nr;
-	size_t alloc;
-};
-
 struct write_commit_graph_context {
 	struct repository *r;
 	struct odb_source *odb_source;
 	char *graph_name;
 	struct oid_array oids;
-	struct packed_commit_list commits;
+	struct commit_stack commits;
 	int num_extra_edges;
 	int num_generation_data_overflows;
 	unsigned long approx_nr_objects;
@@ -1180,7 +1174,7 @@ static int write_graph_chunk_fanout(struct hashfile *f,
 {
 	struct write_commit_graph_context *ctx = data;
 	int i, count = 0;
-	struct commit **list = ctx->commits.list;
+	struct commit **list = ctx->commits.items;
 
 	/*
 	 * Write the first-level table (the list is sorted,
@@ -1206,7 +1200,7 @@ static int write_graph_chunk_oids(struct hashfile *f,
 				  void *data)
 {
 	struct write_commit_graph_context *ctx = data;
-	struct commit **list = ctx->commits.list;
+	struct commit **list = ctx->commits.items;
 	int count;
 	for (count = 0; count < ctx->commits.nr; count++, list++) {
 		display_progress(ctx->progress, ++ctx->progress_cnt);
@@ -1226,8 +1220,8 @@ static int write_graph_chunk_data(struct hashfile *f,
 				  void *data)
 {
 	struct write_commit_graph_context *ctx = data;
-	struct commit **list = ctx->commits.list;
-	struct commit **last = ctx->commits.list + ctx->commits.nr;
+	struct commit **list = ctx->commits.items;
+	struct commit **last = ctx->commits.items + ctx->commits.nr;
 	uint32_t num_extra_edges = 0;
 
 	while (list < last) {
@@ -1249,7 +1243,7 @@ static int write_graph_chunk_data(struct hashfile *f,
 			edge_value = GRAPH_PARENT_NONE;
 		else {
 			edge_value = oid_pos(&parent->item->object.oid,
-					     ctx->commits.list,
+					     ctx->commits.items,
 					     ctx->commits.nr,
 					     commit_to_oid);
 
@@ -1280,7 +1274,7 @@ static int write_graph_chunk_data(struct hashfile *f,
 			edge_value = GRAPH_EXTRA_EDGES_NEEDED | num_extra_edges;
 		else {
 			edge_value = oid_pos(&parent->item->object.oid,
-					     ctx->commits.list,
+					     ctx->commits.items,
 					     ctx->commits.nr,
 					     commit_to_oid);
 
@@ -1332,7 +1326,7 @@ static int write_graph_chunk_generation_data(struct hashfile *f,
 	int i, num_generation_data_overflows = 0;
 
 	for (i = 0; i < ctx->commits.nr; i++) {
-		struct commit *c = ctx->commits.list[i];
+		struct commit *c = ctx->commits.items[i];
 		timestamp_t offset;
 		repo_parse_commit(ctx->r, c);
 		offset = commit_graph_data_at(c)->generation - c->date;
@@ -1355,7 +1349,7 @@ static int write_graph_chunk_generation_data_overflow(struct hashfile *f,
 	struct write_commit_graph_context *ctx = data;
 	int i;
 	for (i = 0; i < ctx->commits.nr; i++) {
-		struct commit *c = ctx->commits.list[i];
+		struct commit *c = ctx->commits.items[i];
 		timestamp_t offset = commit_graph_data_at(c)->generation - c->date;
 		display_progress(ctx->progress, ++ctx->progress_cnt);
 
@@ -1372,8 +1366,8 @@ static int write_graph_chunk_extra_edges(struct hashfile *f,
 					 void *data)
 {
 	struct write_commit_graph_context *ctx = data;
-	struct commit **list = ctx->commits.list;
-	struct commit **last = ctx->commits.list + ctx->commits.nr;
+	struct commit **list = ctx->commits.items;
+	struct commit **last = ctx->commits.items + ctx->commits.nr;
 	struct commit_list *parent;
 
 	while (list < last) {
@@ -1393,7 +1387,7 @@ static int write_graph_chunk_extra_edges(struct hashfile *f,
 		/* Since num_parents > 2, this initializer is safe. */
 		for (parent = (*list)->parents->next; parent; parent = parent->next) {
 			int edge_value = oid_pos(&parent->item->object.oid,
-						 ctx->commits.list,
+						 ctx->commits.items,
 						 ctx->commits.nr,
 						 commit_to_oid);
 
@@ -1427,8 +1421,8 @@ static int write_graph_chunk_bloom_indexes(struct hashfile *f,
 					   void *data)
 {
 	struct write_commit_graph_context *ctx = data;
-	struct commit **list = ctx->commits.list;
-	struct commit **last = ctx->commits.list + ctx->commits.nr;
+	struct commit **list = ctx->commits.items;
+	struct commit **last = ctx->commits.items + ctx->commits.nr;
 	uint32_t cur_pos = 0;
 
 	while (list < last) {
@@ -1463,8 +1457,8 @@ static int write_graph_chunk_bloom_data(struct hashfile *f,
 					void *data)
 {
 	struct write_commit_graph_context *ctx = data;
-	struct commit **list = ctx->commits.list;
-	struct commit **last = ctx->commits.list + ctx->commits.nr;
+	struct commit **list = ctx->commits.items;
+	struct commit **last = ctx->commits.items + ctx->commits.nr;
 
 	trace2_bloom_filter_settings(ctx);
 
@@ -1485,24 +1479,16 @@ static int write_graph_chunk_bloom_data(struct hashfile *f,
 	return 0;
 }
 
-static int add_packed_commits(const struct object_id *oid,
-			      struct packed_git *pack,
-			      uint32_t pos,
-			      void *data)
+static int add_packed_commits_oi(const struct object_id *oid,
+				 struct object_info *oi,
+				 void *data)
 {
 	struct write_commit_graph_context *ctx = (struct write_commit_graph_context*)data;
-	enum object_type type;
-	off_t offset = nth_packed_object_offset(pack, pos);
-	struct object_info oi = OBJECT_INFO_INIT;
 
 	if (ctx->progress)
 		display_progress(ctx->progress, ++ctx->progress_done);
 
-	oi.typep = &type;
-	if (packed_object_info(ctx->r, pack, offset, &oi) < 0)
-		die(_("unable to get type of object %s"), oid_to_hex(oid));
-
-	if (type != OBJ_COMMIT)
+	if (*oi->typep != OBJ_COMMIT)
 		return 0;
 
 	oid_array_append(&ctx->oids, oid);
@@ -1511,6 +1497,22 @@ static int add_packed_commits(const struct object_id *oid,
 	return 0;
 }
 
+static int add_packed_commits(const struct object_id *oid,
+			      struct packed_git *pack,
+			      uint32_t pos,
+			      void *data)
+{
+	enum object_type type;
+	off_t offset = nth_packed_object_offset(pack, pos);
+	struct object_info oi = OBJECT_INFO_INIT;
+
+	oi.typep = &type;
+	if (packed_object_info(pack, offset, &oi) < 0)
+		die(_("unable to get type of object %s"), oid_to_hex(oid));
+
+	return add_packed_commits_oi(oid, &oi, data);
+}
+
 static void add_missing_parents(struct write_commit_graph_context *ctx, struct commit *commit)
 {
 	struct commit_list *parent;
@@ -1585,7 +1587,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)
 
 struct compute_generation_info {
 	struct repository *r;
-	struct packed_commit_list *commits;
+	struct commit_stack *commits;
 	struct progress *progress;
 	int progress_cnt;
 
@@ -1622,7 +1624,7 @@ static void compute_reachable_generation_numbers(
 	struct commit_list *list = NULL;
 
 	for (i = 0; i < info->commits->nr; i++) {
-		struct commit *c = info->commits->list[i];
+		struct commit *c = info->commits->items[i];
 		timestamp_t gen;
 		repo_parse_commit(info->r, c);
 		gen = info->get_generation(c, info->data);
@@ -1729,7 +1731,7 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)
 
 	if (!ctx->trust_generation_numbers) {
 		for (i = 0; i < ctx->commits.nr; i++) {
-			struct commit *c = ctx->commits.list[i];
+			struct commit *c = ctx->commits.items[i];
 			repo_parse_commit(ctx->r, c);
 			commit_graph_data_at(c)->generation = GENERATION_NUMBER_ZERO;
 		}
@@ -1738,7 +1740,7 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)
 	compute_reachable_generation_numbers(&info, 2);
 
 	for (i = 0; i < ctx->commits.nr; i++) {
-		struct commit *c = ctx->commits.list[i];
+		struct commit *c = ctx->commits.items[i];
 		timestamp_t offset = commit_graph_data_at(c)->generation - c->date;
 		if (offset > GENERATION_NUMBER_V2_OFFSET_MAX)
 			ctx->num_generation_data_overflows++;
@@ -1760,8 +1762,8 @@ void ensure_generations_valid(struct repository *r,
 			      struct commit **commits, size_t nr)
 {
 	int generation_version = get_configured_generation_version(r);
-	struct packed_commit_list list = {
-		.list = commits,
+	struct commit_stack list = {
+		.items = commits,
 		.alloc = nr,
 		.nr = nr,
 	};
@@ -1804,7 +1806,7 @@ static void compute_bloom_filters(struct write_commit_graph_context *ctx)
 			_("Computing commit changed paths Bloom filters"),
 			ctx->commits.nr);
 
-	DUP_ARRAY(sorted_commits, ctx->commits.list, ctx->commits.nr);
+	DUP_ARRAY(sorted_commits, ctx->commits.items, ctx->commits.nr);
 
 	if (ctx->order_by_pack)
 		QSORT(sorted_commits, ctx->commits.nr, commit_pos_cmp);
@@ -1851,18 +1853,16 @@ struct refs_cb_data {
 	struct progress *progress;
 };
 
-static int add_ref_to_set(const char *refname UNUSED,
-			  const char *referent UNUSED,
-			  const struct object_id *oid,
-			  int flags UNUSED, void *cb_data)
+static int add_ref_to_set(const struct reference *ref, void *cb_data)
 {
+	const struct object_id *maybe_peeled = ref->oid;
 	struct object_id peeled;
 	struct refs_cb_data *data = (struct refs_cb_data *)cb_data;
 
-	if (!peel_iterated_oid(data->repo, oid, &peeled))
-		oid = &peeled;
-	if (odb_read_object_info(data->repo->objects, oid, NULL) == OBJ_COMMIT)
-		oidset_insert(data->commits, oid);
+	if (!reference_get_peeled_oid(data->repo, ref, &peeled))
+		maybe_peeled = &peeled;
+	if (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) == OBJ_COMMIT)
+		oidset_insert(data->commits, maybe_peeled);
 
 	display_progress(data->progress, oidset_size(data->commits));
 
@@ -1935,7 +1935,7 @@ static int fill_oids_from_packs(struct write_commit_graph_context *ctx,
 			goto cleanup;
 		}
 		for_each_object_in_pack(p, add_packed_commits, ctx,
-					FOR_EACH_OBJECT_PACK_ORDER);
+					ODB_FOR_EACH_OBJECT_PACK_ORDER);
 		close_pack(p);
 		free(p);
 	}
@@ -1967,13 +1967,25 @@ static int fill_oids_from_commits(struct write_commit_graph_context *ctx,
 
 static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx)
 {
+	struct odb_source *source;
+	enum object_type type;
+	struct object_info oi = {
+		.typep = &type,
+	};
+
 	if (ctx->report_progress)
 		ctx->progress = start_delayed_progress(
 			ctx->r,
 			_("Finding commits for commit graph among packed objects"),
 			ctx->approx_nr_objects);
-	for_each_packed_object(ctx->r, add_packed_commits, ctx,
-			       FOR_EACH_OBJECT_PACK_ORDER);
+
+	odb_prepare_alternates(ctx->r->objects);
+	for (source = ctx->r->objects->sources; source; source = source->next) {
+		struct odb_source_files *files = odb_source_files_downcast(source);
+		packfile_store_for_each_object(files->packed, &oi, add_packed_commits_oi,
+					       ctx, ODB_FOR_EACH_OBJECT_PACK_ORDER);
+	}
+
 	if (ctx->progress_done < ctx->approx_nr_objects)
 		display_progress(ctx->progress, ctx->approx_nr_objects);
 	stop_progress(&ctx->progress);
@@ -1994,26 +2006,26 @@ static void copy_oids_to_commits(struct write_commit_graph_context *ctx)
 	oid_array_sort(&ctx->oids);
 	for (i = 0; i < ctx->oids.nr; i = oid_array_next_unique(&ctx->oids, i)) {
 		unsigned int num_parents;
+		struct commit *commit;
 
 		display_progress(ctx->progress, i + 1);
 
-		ALLOC_GROW(ctx->commits.list, ctx->commits.nr + 1, ctx->commits.alloc);
-		ctx->commits.list[ctx->commits.nr] = lookup_commit(ctx->r, &ctx->oids.oid[i]);
+		commit = lookup_commit(ctx->r, &ctx->oids.oid[i]);
 
 		if (ctx->split && flags != COMMIT_GRAPH_SPLIT_REPLACE &&
-		    commit_graph_position(ctx->commits.list[ctx->commits.nr]) != COMMIT_NOT_FROM_GRAPH)
+		    commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH)
 			continue;
 
 		if (ctx->split && flags == COMMIT_GRAPH_SPLIT_REPLACE)
-			repo_parse_commit(ctx->r, ctx->commits.list[ctx->commits.nr]);
+			repo_parse_commit(ctx->r, commit);
 		else
-			repo_parse_commit_no_graph(ctx->r, ctx->commits.list[ctx->commits.nr]);
+			repo_parse_commit_no_graph(ctx->r, commit);
 
-		num_parents = commit_list_count(ctx->commits.list[ctx->commits.nr]->parents);
+		num_parents = commit_list_count(commit->parents);
 		if (num_parents > 2)
 			ctx->num_extra_edges += num_parents - 1;
 
-		ctx->commits.nr++;
+		commit_stack_push(&ctx->commits, commit);
 	}
 	stop_progress(&ctx->progress);
 }
@@ -2332,7 +2344,7 @@ static void merge_commit_graph(struct write_commit_graph_context *ctx,
 		    oid_to_hex(&g->oid),
 		    (uintmax_t)st_add(ctx->commits.nr, g->num_commits));
 
-	ALLOC_GROW(ctx->commits.list, ctx->commits.nr + g->num_commits, ctx->commits.alloc);
+	commit_stack_grow(&ctx->commits, g->num_commits);
 
 	for (i = 0; i < g->num_commits; i++) {
 		struct object_id oid;
@@ -2345,10 +2357,8 @@ static void merge_commit_graph(struct write_commit_graph_context *ctx,
 		/* only add commits if they still exist in the repo */
 		result = lookup_commit_reference_gently(ctx->r, &oid, 1);
 
-		if (result) {
-			ctx->commits.list[ctx->commits.nr] = result;
-			ctx->commits.nr++;
-		}
+		if (result)
+			commit_stack_push(&ctx->commits, result);
 	}
 }
 
@@ -2369,14 +2379,14 @@ static void sort_and_scan_merged_commits(struct write_commit_graph_context *ctx)
 					_("Scanning merged commits"),
 					ctx->commits.nr);
 
-	QSORT(ctx->commits.list, ctx->commits.nr, commit_compare);
+	QSORT(ctx->commits.items, ctx->commits.nr, commit_compare);
 
 	ctx->num_extra_edges = 0;
 	for (i = 0; i < ctx->commits.nr; i++) {
 		display_progress(ctx->progress, i + 1);
 
-		if (i && oideq(&ctx->commits.list[i - 1]->object.oid,
-			  &ctx->commits.list[i]->object.oid)) {
+		if (i && oideq(&ctx->commits.items[i - 1]->object.oid,
+			  &ctx->commits.items[i]->object.oid)) {
 			/*
 			 * Silently ignore duplicates. These were likely
 			 * created due to a commit appearing in multiple
@@ -2387,10 +2397,10 @@ static void sort_and_scan_merged_commits(struct write_commit_graph_context *ctx)
 		} else {
 			unsigned int num_parents;
 
-			ctx->commits.list[dedup_i] = ctx->commits.list[i];
+			ctx->commits.items[dedup_i] = ctx->commits.items[i];
 			dedup_i++;
 
-			num_parents = commit_list_count(ctx->commits.list[i]->parents);
+			num_parents = commit_list_count(ctx->commits.items[i]->parents);
 			if (num_parents > 2)
 				ctx->num_extra_edges += num_parents - 1;
 		}
@@ -2668,7 +2678,7 @@ int write_commit_graph(struct odb_source *source,
 cleanup:
 	free(ctx.graph_name);
 	free(ctx.base_graph_name);
-	free(ctx.commits.list);
+	commit_stack_clear(&ctx.commits);
 	oid_array_clear(&ctx.oids);
 	clear_topo_level_slab(&topo_levels);
 
diff --git a/commit-reach.c b/commit-reach.c
index cc18c86..9604bbd 100644
--- a/commit-reach.c
+++ b/commit-reach.c
@@ -109,7 +109,7 @@ static int paint_down_to_common(struct repository *r,
 				continue;
 			if (repo_parse_commit(r, p)) {
 				clear_prio_queue(&queue);
-				free_commit_list(*result);
+				commit_list_free(*result);
 				*result = NULL;
 				/*
 				 * At this stage, we know that the commit is
@@ -166,7 +166,7 @@ static int merge_bases_many(struct repository *r,
 	}
 
 	if (paint_down_to_common(r, one, n, twos, 0, 0, &list)) {
-		free_commit_list(list);
+		commit_list_free(list);
 		return -1;
 	}
 
@@ -195,8 +195,8 @@ int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result)
 			struct commit_list *bases = NULL;
 			if (repo_get_merge_bases(the_repository, i->item,
 						 j->item, &bases) < 0) {
-				free_commit_list(bases);
-				free_commit_list(*result);
+				commit_list_free(bases);
+				commit_list_free(*result);
 				*result = NULL;
 				return -1;
 			}
@@ -207,7 +207,7 @@ int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result)
 			for (k = bases; k; k = k->next)
 				end = k;
 		}
-		free_commit_list(*result);
+		commit_list_free(*result);
 		*result = new_commits;
 	}
 	return 0;
@@ -249,7 +249,7 @@ static int remove_redundant_no_gen(struct repository *r,
 					 work, min_generation, 0, &common)) {
 			clear_commit_marks(array[i], all_flags);
 			clear_commit_marks_many(filled, work, all_flags);
-			free_commit_list(common);
+			commit_list_free(common);
 			free(work);
 			free(redundant);
 			free(filled_index);
@@ -262,7 +262,7 @@ static int remove_redundant_no_gen(struct repository *r,
 				redundant[filled_index[j]] = 1;
 		clear_commit_marks(array[i], all_flags);
 		clear_commit_marks_many(filled, work, all_flags);
-		free_commit_list(common);
+		commit_list_free(common);
 	}
 
 	/* Now collect the result */
@@ -283,8 +283,8 @@ static int remove_redundant_with_gen(struct repository *r,
 {
 	size_t i, count_non_stale = 0, count_still_independent = cnt;
 	timestamp_t min_generation = GENERATION_NUMBER_INFINITY;
-	struct commit **walk_start, **sorted;
-	size_t walk_start_nr = 0, walk_start_alloc = cnt;
+	struct commit **sorted;
+	struct commit_stack walk_start = COMMIT_STACK_INIT;
 	size_t min_gen_pos = 0;
 
 	/*
@@ -298,7 +298,7 @@ static int remove_redundant_with_gen(struct repository *r,
 	QSORT(sorted, cnt, compare_commits_by_gen);
 	min_generation = commit_graph_generation(sorted[0]);
 
-	ALLOC_ARRAY(walk_start, walk_start_alloc);
+	commit_stack_grow(&walk_start, cnt);
 
 	/* Mark all parents of the input as STALE */
 	for (i = 0; i < cnt; i++) {
@@ -312,18 +312,17 @@ static int remove_redundant_with_gen(struct repository *r,
 			repo_parse_commit(r, parents->item);
 			if (!(parents->item->object.flags & STALE)) {
 				parents->item->object.flags |= STALE;
-				ALLOC_GROW(walk_start, walk_start_nr + 1, walk_start_alloc);
-				walk_start[walk_start_nr++] = parents->item;
+				commit_stack_push(&walk_start, parents->item);
 			}
 			parents = parents->next;
 		}
 	}
 
-	QSORT(walk_start, walk_start_nr, compare_commits_by_gen);
+	QSORT(walk_start.items, walk_start.nr, compare_commits_by_gen);
 
 	/* remove STALE bit for now to allow walking through parents */
-	for (i = 0; i < walk_start_nr; i++)
-		walk_start[i]->object.flags &= ~STALE;
+	for (i = 0; i < walk_start.nr; i++)
+		walk_start.items[i]->object.flags &= ~STALE;
 
 	/*
 	 * Start walking from the highest generation. Hopefully, it will
@@ -331,12 +330,12 @@ static int remove_redundant_with_gen(struct repository *r,
 	 * terminate early. Otherwise, we will do the same amount of work
 	 * as before.
 	 */
-	for (i = walk_start_nr; i && count_still_independent > 1; i--) {
+	for (i = walk_start.nr; i && count_still_independent > 1; i--) {
 		/* push the STALE bits up to min generation */
 		struct commit_list *stack = NULL;
 
-		commit_list_insert(walk_start[i - 1], &stack);
-		walk_start[i - 1]->object.flags |= STALE;
+		commit_list_insert(walk_start.items[i - 1], &stack);
+		walk_start.items[i - 1]->object.flags |= STALE;
 
 		while (stack) {
 			struct commit_list *parents;
@@ -375,7 +374,7 @@ static int remove_redundant_with_gen(struct repository *r,
 			if (!parents)
 				pop_commit(&stack);
 		}
-		free_commit_list(stack);
+		commit_list_free(stack);
 	}
 	free(sorted);
 
@@ -390,8 +389,8 @@ static int remove_redundant_with_gen(struct repository *r,
 	}
 
 	/* clear marks */
-	clear_commit_marks_many(walk_start_nr, walk_start, STALE);
-	free(walk_start);
+	clear_commit_marks_many(walk_start.nr, walk_start.items, STALE);
+	commit_stack_clear(&walk_start);
 
 	*dedup_cnt = count_non_stale;
 	return 0;
@@ -452,7 +451,7 @@ static int get_merge_bases_many_0(struct repository *r,
 	CALLOC_ARRAY(rslt, cnt);
 	for (list = *result, i = 0; list; list = list->next)
 		rslt[i++] = list->item;
-	free_commit_list(*result);
+	commit_list_free(*result);
 	*result = NULL;
 
 	clear_commit_marks(one, all_flags);
@@ -511,7 +510,7 @@ int repo_is_descendant_of(struct repository *r,
 		int result;
 		commit_list_insert(commit, &from_list);
 		result = can_all_from_reach(from_list, with_commit, 0);
-		free_commit_list(from_list);
+		commit_list_free(from_list);
 		return result;
 	} else {
 		while (with_commit) {
@@ -562,7 +561,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,
 		ret = 1;
 	clear_commit_marks(commit, all_flags);
 	clear_commit_marks_many(nr_reference, reference, all_flags);
-	free_commit_list(bases);
+	commit_list_free(bases);
 	return ret;
 }
 
@@ -579,7 +578,7 @@ int repo_in_merge_bases(struct repository *r,
 
 	next = commit_list_append(commit, next);
 	res = repo_is_descendant_of(r, reference, list);
-	free_commit_list(list);
+	commit_list_free(list);
 
 	return res;
 }
@@ -627,7 +626,7 @@ struct commit_list *reduce_heads(struct commit_list *heads)
 void reduce_heads_replace(struct commit_list **heads)
 {
 	struct commit_list *result = reduce_heads(*heads);
-	free_commit_list(*heads);
+	commit_list_free(*heads);
 	*heads = result;
 }
 
@@ -662,7 +661,7 @@ int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid)
 				    new_commit, old_commit_list);
 	if (ret < 0)
 		exit(128);
-	free_commit_list(old_commit_list);
+	commit_list_free(old_commit_list);
 	return ret;
 }
 
@@ -1237,7 +1236,7 @@ void tips_reachable_from_bases(struct repository *r,
 done:
 	free(commits);
 	repo_clear_commit_marks(r, SEEN);
-	free_commit_list(stack);
+	commit_list_free(stack);
 }
 
 /*
diff --git a/commit.c b/commit.c
index 16d91b2..0ffdd66 100644
--- a/commit.c
+++ b/commit.c
@@ -31,7 +31,6 @@
 #include "parse.h"
 #include "object-file.h"
 #include "object-file-convert.h"
-#include "prio-queue.h"
 
 static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
 
@@ -43,13 +42,35 @@ const char *commit_type = "commit";
 struct commit *lookup_commit_reference_gently(struct repository *r,
 		const struct object_id *oid, int quiet)
 {
-	struct object *obj = deref_tag(r,
-				       parse_object(r, oid),
-				       NULL, 0);
+	const struct object_id *maybe_peeled;
+	struct object_id peeled_oid;
+	struct commit *commit;
+	enum object_type type;
 
-	if (!obj)
+	switch (peel_object_ext(r, oid, &peeled_oid, 0, &type)) {
+	case PEEL_NON_TAG:
+		maybe_peeled = oid;
+		break;
+	case PEEL_PEELED:
+		maybe_peeled = &peeled_oid;
+		break;
+	default:
 		return NULL;
-	return object_as_type(obj, OBJ_COMMIT, quiet);
+	}
+
+	if (type != OBJ_COMMIT) {
+		if (!quiet)
+			error(_("object %s is a %s, not a %s"),
+			      oid_to_hex(oid), type_name(type),
+			      type_name(OBJ_COMMIT));
+		return NULL;
+	}
+
+	commit = lookup_commit(r, maybe_peeled);
+	if (!commit || repo_parse_commit_gently(r, commit, quiet) < 0)
+		return NULL;
+
+	return commit;
 }
 
 struct commit *lookup_commit_reference(struct repository *r, const struct object_id *oid)
@@ -191,7 +212,7 @@ void unparse_commit(struct repository *r, const struct object_id *oid)
 
 	if (!c->object.parsed)
 		return;
-	free_commit_list(c->parents);
+	commit_list_free(c->parents);
 	c->parents = NULL;
 	c->object.parsed = 0;
 }
@@ -436,7 +457,7 @@ void release_commit_memory(struct parsed_object_pool *pool, struct commit *c)
 	set_commit_tree(c, NULL);
 	free_commit_buffer(pool, c);
 	c->index = 0;
-	free_commit_list(c->parents);
+	commit_list_free(c->parents);
 
 	c->object.parsed = 0;
 }
@@ -480,7 +501,7 @@ int parse_commit_buffer(struct repository *r, struct commit *item, const void *b
 	 * same error, but that's good, since it lets our caller know
 	 * the result cannot be trusted.
 	 */
-	free_commit_list(item->parents);
+	commit_list_free(item->parents);
 	item->parents = NULL;
 
 	tail += size;
@@ -680,7 +701,7 @@ unsigned commit_list_count(const struct commit_list *l)
 	return c;
 }
 
-struct commit_list *copy_commit_list(const struct commit_list *list)
+struct commit_list *commit_list_copy(const struct commit_list *list)
 {
 	struct commit_list *head = NULL;
 	struct commit_list **pp = &head;
@@ -691,7 +712,7 @@ struct commit_list *copy_commit_list(const struct commit_list *list)
 	return head;
 }
 
-struct commit_list *reverse_commit_list(struct commit_list *list)
+struct commit_list *commit_list_reverse(struct commit_list *list)
 {
 	struct commit_list *next = NULL, *current, *backup;
 	for (current = list; current; current = backup) {
@@ -702,7 +723,7 @@ struct commit_list *reverse_commit_list(struct commit_list *list)
 	return next;
 }
 
-void free_commit_list(struct commit_list *list)
+void commit_list_free(struct commit_list *list)
 {
 	while (list)
 		pop_commit(&list);
@@ -977,7 +998,7 @@ void sort_in_topological_order(struct commit_list **list, enum rev_sort_order so
 		prio_queue_reverse(&queue);
 
 	/* We no longer need the commit list */
-	free_commit_list(orig);
+	commit_list_free(orig);
 
 	pptr = list;
 	*list = NULL;
@@ -1015,9 +1036,7 @@ void sort_in_topological_order(struct commit_list **list, enum rev_sort_order so
 }
 
 struct rev_collect {
-	struct commit **commit;
-	int nr;
-	int alloc;
+	struct commit_stack stack;
 	unsigned int initial : 1;
 };
 
@@ -1034,8 +1053,7 @@ static void add_one_commit(struct object_id *oid, struct rev_collect *revs)
 	    repo_parse_commit(the_repository, commit))
 		return;
 
-	ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc);
-	revs->commit[revs->nr++] = commit;
+	commit_stack_push(&revs->stack, commit);
 	commit->object.flags |= TMP_MARK;
 }
 
@@ -1060,7 +1078,7 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)
 	struct object_id oid;
 	struct rev_collect revs;
 	struct commit_list *bases = NULL;
-	int i;
+	size_t i;
 	struct commit *ret = NULL;
 	char *full_refname;
 
@@ -1074,19 +1092,19 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)
 		die("Ambiguous refname: '%s'", refname);
 	}
 
-	memset(&revs, 0, sizeof(revs));
+	commit_stack_init(&revs.stack);
 	revs.initial = 1;
 	refs_for_each_reflog_ent(get_main_ref_store(the_repository),
 				 full_refname, collect_one_reflog_ent, &revs);
 
-	if (!revs.nr)
+	if (!revs.stack.nr)
 		add_one_commit(&oid, &revs);
 
-	for (i = 0; i < revs.nr; i++)
-		revs.commit[i]->object.flags &= ~TMP_MARK;
+	for (i = 0; i < revs.stack.nr; i++)
+		revs.stack.items[i]->object.flags &= ~TMP_MARK;
 
-	if (repo_get_merge_bases_many(the_repository, commit, revs.nr,
-				      revs.commit, &bases) < 0)
+	if (repo_get_merge_bases_many(the_repository, commit, revs.stack.nr,
+				      revs.stack.items, &bases) < 0)
 		exit(128);
 
 	/*
@@ -1097,17 +1115,17 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)
 		goto cleanup_return;
 
 	/* And the found one must be one of the reflog entries */
-	for (i = 0; i < revs.nr; i++)
-		if (&bases->item->object == &revs.commit[i]->object)
+	for (i = 0; i < revs.stack.nr; i++)
+		if (&bases->item->object == &revs.stack.items[i]->object)
 			break; /* found */
-	if (revs.nr <= i)
+	if (revs.stack.nr <= i)
 		goto cleanup_return;
 
 	ret = bases->item;
 
 cleanup_return:
-	free(revs.commit);
-	free_commit_list(bases);
+	commit_stack_clear(&revs.stack);
+	commit_list_free(bases);
 	free(full_refname);
 	return ret;
 }
@@ -1315,7 +1333,8 @@ static void handle_signed_tag(const struct commit *parent, struct commit_extra_h
 	free(buf);
 }
 
-int check_commit_signature(const struct commit *commit, struct signature_check *sigc)
+int verify_commit_buffer(const char *buffer, size_t size,
+			 struct signature_check *sigc)
 {
 	struct strbuf payload = STRBUF_INIT;
 	struct strbuf signature = STRBUF_INIT;
@@ -1323,7 +1342,8 @@ int check_commit_signature(const struct commit *commit, struct signature_check *
 
 	sigc->result = 'N';
 
-	if (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)
+	if (parse_buffer_signed_by_header(buffer, size, &payload,
+					  &signature, the_hash_algo) <= 0)
 		goto out;
 
 	sigc->payload_type = SIGNATURE_PAYLOAD_COMMIT;
@@ -1337,6 +1357,17 @@ int check_commit_signature(const struct commit *commit, struct signature_check *
 	return ret;
 }
 
+int check_commit_signature(const struct commit *commit, struct signature_check *sigc)
+{
+	unsigned long size;
+	const char *buffer = repo_get_commit_buffer(the_repository, commit, &size);
+	int ret = verify_commit_buffer(buffer, size, sigc);
+
+	repo_unuse_commit_buffer(the_repository, commit, buffer);
+
+	return ret;
+}
+
 void verify_merge_signature(struct commit *commit, int verbosity,
 			    int check_trust)
 {
@@ -1968,3 +1999,31 @@ int run_commit_hook(int editor_is_used, const char *index_file,
 	opt.invoked_hook = invoked_hook;
 	return run_hooks_opt(the_repository, name, &opt);
 }
+
+void commit_stack_init(struct commit_stack *stack)
+{
+	stack->items = NULL;
+	stack->nr = stack->alloc = 0;
+}
+
+void commit_stack_grow(struct commit_stack *stack, size_t extra)
+{
+	ALLOC_GROW(stack->items, st_add(stack->nr, extra), stack->alloc);
+}
+
+void commit_stack_push(struct commit_stack *stack, struct commit *commit)
+{
+	commit_stack_grow(stack, 1);
+	stack->items[stack->nr++] = commit;
+}
+
+struct commit *commit_stack_pop(struct commit_stack *stack)
+{
+	return stack->nr ? stack->items[--stack->nr] : NULL;
+}
+
+void commit_stack_clear(struct commit_stack *stack)
+{
+	free(stack->items);
+	commit_stack_init(stack);
+}
diff --git a/commit.h b/commit.h
index 1d6e0c7..f2f39e1 100644
--- a/commit.h
+++ b/commit.h
@@ -103,16 +103,26 @@ static inline int repo_parse_commit(struct repository *r, struct commit *item)
 	return repo_parse_commit_gently(r, item, 0);
 }
 
+void unparse_commit(struct repository *r, const struct object_id *oid);
+
 static inline int repo_parse_commit_no_graph(struct repository *r,
 					     struct commit *commit)
 {
+	/*
+	 * When the commit has been parsed but its tree wasn't populated then
+	 * this is an indicator that it has been parsed via the commit-graph.
+	 * We cannot read the tree via the commit-graph, as we're explicitly
+	 * told not to use it. We thus have to first un-parse the object so
+	 * that we can re-parse it without the graph.
+	 */
+	if (commit->object.parsed && !commit->maybe_tree)
+		unparse_commit(r, &commit->object.oid);
+
 	return repo_parse_commit_internal(r, commit, 0, 0);
 }
 
 void parse_commit_or_die(struct commit *item);
 
-void unparse_commit(struct repository *r, const struct object_id *oid);
-
 struct buffer_slab;
 struct buffer_slab *allocate_commit_buffer_slab(void);
 void free_commit_buffer_slab(struct buffer_slab *bs);
@@ -186,12 +196,31 @@ struct commit_list *commit_list_insert_by_date(struct commit *item,
 void commit_list_sort_by_date(struct commit_list **list);
 
 /* Shallow copy of the input list */
-struct commit_list *copy_commit_list(const struct commit_list *list);
+struct commit_list *commit_list_copy(const struct commit_list *list);
 
 /* Modify list in-place to reverse it, returning new head; list will be tail */
-struct commit_list *reverse_commit_list(struct commit_list *list);
+struct commit_list *commit_list_reverse(struct commit_list *list);
 
-void free_commit_list(struct commit_list *list);
+void commit_list_free(struct commit_list *list);
+
+/*
+ * Deprecated compatibility functions for `struct commit_list`, to be removed
+ * once Git 2.53 is released.
+ */
+static inline struct commit_list *copy_commit_list(struct commit_list *l)
+{
+	return commit_list_copy(l);
+}
+
+static inline struct commit_list *reverse_commit_list(struct commit_list *l)
+{
+	return commit_list_reverse(l);
+}
+
+static inline void free_commit_list(struct commit_list *l)
+{
+	commit_list_free(l);
+}
 
 struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
 
@@ -333,6 +362,13 @@ int remove_signature(struct strbuf *buf);
  */
 int check_commit_signature(const struct commit *commit, struct signature_check *sigc);
 
+/*
+ * Same as check_commit_signature() but accepts a commit buffer and
+ * its size, instead of a `struct commit *`.
+ */
+int verify_commit_buffer(const char *buffer, size_t size,
+			 struct signature_check *sigc);
+
 /* record author-date for each commit object */
 struct author_date_slab;
 void record_author_date(struct author_date_slab *author_date,
@@ -374,4 +410,16 @@ int parse_buffer_signed_by_header(const char *buffer,
 				  const struct git_hash_algo *algop);
 int add_header_signature(struct strbuf *buf, struct strbuf *sig, const struct git_hash_algo *algo);
 
+struct commit_stack {
+	struct commit **items;
+	size_t nr, alloc;
+};
+#define COMMIT_STACK_INIT { 0 }
+
+void commit_stack_init(struct commit_stack *);
+void commit_stack_grow(struct commit_stack *, size_t);
+void commit_stack_push(struct commit_stack *, struct commit *);
+struct commit *commit_stack_pop(struct commit_stack *);
+void commit_stack_clear(struct commit_stack *);
+
 #endif /* COMMIT_H */
diff --git a/compat/darwin/procinfo.c b/compat/darwin/procinfo.c
new file mode 100644
index 0000000..c8954f0
--- /dev/null
+++ b/compat/darwin/procinfo.c
@@ -0,0 +1,97 @@
+#include "git-compat-util.h"
+#include "strbuf.h"
+#include "strvec.h"
+#include "trace2.h"
+#include <sys/sysctl.h>
+
+/*
+ * An arbitrarily chosen value to limit the depth of the ancestor chain.
+ */
+#define NR_PIDS_LIMIT 10
+
+/*
+ * Get the process name and parent PID for a given PID using sysctl().
+ * Returns 0 on success, -1 on failure.
+ */
+static int get_proc_info(pid_t pid, struct strbuf *name, pid_t *ppid)
+{
+	int mib[4];
+	struct kinfo_proc proc;
+	size_t size = sizeof(proc);
+
+	mib[0] = CTL_KERN;
+	mib[1] = KERN_PROC;
+	mib[2] = KERN_PROC_PID;
+	mib[3] = pid;
+
+	if (sysctl(mib, 4, &proc, &size, NULL, 0) < 0)
+		return -1;
+
+	if (size == 0)
+		return -1;
+
+	strbuf_addstr(name, proc.kp_proc.p_comm);
+	*ppid = proc.kp_eproc.e_ppid;
+
+	return 0;
+}
+
+/*
+ * Recursively push process names onto the ancestry array.
+ * We guard against cycles by limiting the depth to NR_PIDS_LIMIT.
+ */
+static void push_ancestry_name(struct strvec *names, pid_t pid, int depth)
+{
+	struct strbuf name = STRBUF_INIT;
+	pid_t ppid;
+
+	if (depth >= NR_PIDS_LIMIT)
+		return;
+
+	if (pid <= 0)
+		return;
+
+	if (get_proc_info(pid, &name, &ppid) < 0)
+		goto cleanup;
+
+	strvec_push(names, name.buf);
+
+	/*
+	 * Recurse to the parent process. Stop if ppid not valid
+	 * or if we've reached ourselves (cycle).
+	 */
+	if (ppid && ppid != pid)
+		push_ancestry_name(names, ppid, depth + 1);
+
+cleanup:
+	strbuf_release(&name);
+}
+
+void trace2_collect_process_info(enum trace2_process_info_reason reason)
+{
+	struct strvec names = STRVEC_INIT;
+
+	if (!trace2_is_enabled())
+		return;
+
+	switch (reason) {
+	case TRACE2_PROCESS_INFO_STARTUP:
+		push_ancestry_name(&names, getppid(), 0);
+		if (names.nr)
+			trace2_cmd_ancestry(names.v);
+
+		strvec_clear(&names);
+		break;
+
+	case TRACE2_PROCESS_INFO_EXIT:
+		/*
+		 * The Windows version of this calls its
+		 * get_peak_memory_info() here. We may want to insert
+		 * similar process-end statistics here in the future.
+		 */
+		break;
+
+	default:
+		BUG("trace2_collect_process_info: unknown reason '%d'", reason);
+	}
+}
diff --git a/compat/mingw-posix.h b/compat/mingw-posix.h
index 631a208..2d989fd 100644
--- a/compat/mingw-posix.h
+++ b/compat/mingw-posix.h
@@ -121,10 +121,6 @@ struct utsname {
  * trivial stubs
  */
 
-static inline int readlink(const char *path UNUSED, char *buf UNUSED, size_t bufsiz UNUSED)
-{ errno = ENOSYS; return -1; }
-static inline int symlink(const char *oldpath UNUSED, const char *newpath UNUSED)
-{ errno = ENOSYS; return -1; }
 static inline int fchmod(int fildes UNUSED, mode_t mode UNUSED)
 { errno = ENOSYS; return -1; }
 #ifndef __MINGW64_VERSION_MAJOR
@@ -197,6 +193,8 @@ int setitimer(int type, struct itimerval *in, struct itimerval *out);
 int sigaction(int sig, struct sigaction *in, struct sigaction *out);
 int link(const char *oldpath, const char *newpath);
 int uname(struct utsname *buf);
+int symlink(const char *target, const char *link);
+int readlink(const char *path, char *buf, size_t bufsiz);
 
 /*
  * replacements of existing functions
@@ -241,9 +239,6 @@ int mingw_chdir(const char *dirname);
 int mingw_chmod(const char *filename, int mode);
 #define chmod mingw_chmod
 
-char *mingw_mktemp(char *template);
-#define mktemp mingw_mktemp
-
 char *mingw_getcwd(char *pointer, int len);
 #define getcwd mingw_getcwd
 
diff --git a/compat/mingw.c b/compat/mingw.c
index 736a07a..c667a2d 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -21,14 +21,13 @@
 #define SECURITY_WIN32
 #include <sspi.h>
 #include <wchar.h>
+#include <winioctl.h>
 #include <winternl.h>
 
 #define STATUS_DELETE_PENDING ((NTSTATUS) 0xC0000056)
 
 #define HCAST(type, handle) ((type)(intptr_t)handle)
 
-static const int delay[] = { 0, 1, 10, 20, 40 };
-
 void open_in_gdb(void)
 {
 	static struct child_process cp = CHILD_PROCESS_INIT;
@@ -103,6 +102,7 @@ int err_win_to_posix(DWORD winerr)
 	case ERROR_INVALID_PARAMETER: error = EINVAL; break;
 	case ERROR_INVALID_PASSWORD: error = EPERM; break;
 	case ERROR_INVALID_PRIMARY_GROUP: error = EINVAL; break;
+	case ERROR_INVALID_REPARSE_DATA: error = EINVAL; break;
 	case ERROR_INVALID_SIGNAL_NUMBER: error = EINVAL; break;
 	case ERROR_INVALID_TARGET_HANDLE: error = EIO; break;
 	case ERROR_INVALID_WORKSTATION: error = EACCES; break;
@@ -117,6 +117,7 @@ int err_win_to_posix(DWORD winerr)
 	case ERROR_NEGATIVE_SEEK: error = ESPIPE; break;
 	case ERROR_NOACCESS: error = EFAULT; break;
 	case ERROR_NONE_MAPPED: error = EINVAL; break;
+	case ERROR_NOT_A_REPARSE_POINT: error = EINVAL; break;
 	case ERROR_NOT_ENOUGH_MEMORY: error = ENOMEM; break;
 	case ERROR_NOT_READY: error = EAGAIN; break;
 	case ERROR_NOT_SAME_DEVICE: error = EXDEV; break;
@@ -137,6 +138,9 @@ int err_win_to_posix(DWORD winerr)
 	case ERROR_PIPE_NOT_CONNECTED: error = EPIPE; break;
 	case ERROR_PRIVILEGE_NOT_HELD: error = EACCES; break;
 	case ERROR_READ_FAULT: error = EIO; break;
+	case ERROR_REPARSE_ATTRIBUTE_CONFLICT: error = EINVAL; break;
+	case ERROR_REPARSE_TAG_INVALID: error = EINVAL; break;
+	case ERROR_REPARSE_TAG_MISMATCH: error = EINVAL; break;
 	case ERROR_SEEK: error = EIO; break;
 	case ERROR_SEEK_ON_DEVICE: error = ESPIPE; break;
 	case ERROR_SHARING_BUFFER_EXCEEDED: error = ENFILE; break;
@@ -204,15 +208,12 @@ static int read_yes_no_answer(void)
 	return -1;
 }
 
-static int ask_yes_no_if_possible(const char *format, ...)
+static int ask_yes_no_if_possible(const char *format, va_list args)
 {
 	char question[4096];
 	const char *retry_hook;
-	va_list args;
 
-	va_start(args, format);
 	vsnprintf(question, sizeof(question), format, args);
-	va_end(args);
 
 	retry_hook = mingw_getenv("GIT_ASK_YESNO");
 	if (retry_hook) {
@@ -237,6 +238,31 @@ static int ask_yes_no_if_possible(const char *format, ...)
 	}
 }
 
+static int retry_ask_yes_no(int *tries, const char *format, ...)
+{
+	static const int delay[] = { 0, 1, 10, 20, 40 };
+	va_list args;
+	int result, saved_errno = errno;
+
+	if ((*tries) < ARRAY_SIZE(delay)) {
+		/*
+		 * We assume that some other process had the file open at the wrong
+		 * moment and retry. In order to give the other process a higher
+		 * chance to complete its operation, we give up our time slice now.
+		 * If we have to retry again, we do sleep a bit.
+		 */
+		Sleep(delay[*tries]);
+		(*tries)++;
+		return 1;
+	}
+
+	va_start(args, format);
+	result = ask_yes_no_if_possible(format, args);
+	va_end(args);
+	errno = saved_errno;
+	return result;
+}
+
 /* Windows only */
 enum hide_dotfiles_type {
 	HIDE_DOTFILES_FALSE = 0,
@@ -270,6 +296,134 @@ int mingw_core_config(const char *var, const char *value,
 	return 0;
 }
 
+static inline int is_wdir_sep(wchar_t wchar)
+{
+	return wchar == L'/' || wchar == L'\\';
+}
+
+static const wchar_t *make_relative_to(const wchar_t *path,
+				       const wchar_t *relative_to, wchar_t *out,
+				       size_t size)
+{
+	size_t i = wcslen(relative_to), len;
+
+	/* Is `path` already absolute? */
+	if (is_wdir_sep(path[0]) ||
+	    (iswalpha(path[0]) && path[1] == L':' && is_wdir_sep(path[2])))
+		return path;
+
+	while (i > 0 && !is_wdir_sep(relative_to[i - 1]))
+		i--;
+
+	/* Is `relative_to` in the current directory? */
+	if (!i)
+		return path;
+
+	len = wcslen(path);
+	if (i + len + 1 > size) {
+		error("Could not make '%ls' relative to '%ls' (too large)",
+		      path, relative_to);
+		return NULL;
+	}
+
+	memcpy(out, relative_to, i * sizeof(wchar_t));
+	wcscpy(out + i, path);
+	return out;
+}
+
+static DWORD symlink_file_flags = 0, symlink_directory_flags = 1;
+
+enum phantom_symlink_result {
+	PHANTOM_SYMLINK_RETRY,
+	PHANTOM_SYMLINK_DONE,
+	PHANTOM_SYMLINK_DIRECTORY
+};
+
+/*
+ * Changes a file symlink to a directory symlink if the target exists and is a
+ * directory.
+ */
+static enum phantom_symlink_result
+process_phantom_symlink(const wchar_t *wtarget, const wchar_t *wlink)
+{
+	HANDLE hnd;
+	BY_HANDLE_FILE_INFORMATION fdata;
+	wchar_t relative[MAX_PATH];
+	const wchar_t *rel;
+
+	/* check that wlink is still a file symlink */
+	if ((GetFileAttributesW(wlink)
+			& (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
+			!= FILE_ATTRIBUTE_REPARSE_POINT)
+		return PHANTOM_SYMLINK_DONE;
+
+	/* make it relative, if necessary */
+	rel = make_relative_to(wtarget, wlink, relative, ARRAY_SIZE(relative));
+	if (!rel)
+		return PHANTOM_SYMLINK_DONE;
+
+	/* let Windows resolve the link by opening it */
+	hnd = CreateFileW(rel, 0,
+			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+			OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+	if (hnd == INVALID_HANDLE_VALUE) {
+		errno = err_win_to_posix(GetLastError());
+		return PHANTOM_SYMLINK_RETRY;
+	}
+
+	if (!GetFileInformationByHandle(hnd, &fdata)) {
+		errno = err_win_to_posix(GetLastError());
+		CloseHandle(hnd);
+		return PHANTOM_SYMLINK_RETRY;
+	}
+	CloseHandle(hnd);
+
+	/* if target exists and is a file, we're done */
+	if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+		return PHANTOM_SYMLINK_DONE;
+
+	/* otherwise recreate the symlink with directory flag */
+	if (DeleteFileW(wlink) &&
+	    CreateSymbolicLinkW(wlink, wtarget, symlink_directory_flags))
+		return PHANTOM_SYMLINK_DIRECTORY;
+
+	errno = err_win_to_posix(GetLastError());
+	return PHANTOM_SYMLINK_RETRY;
+}
+
+/* keep track of newly created symlinks to non-existing targets */
+struct phantom_symlink_info {
+	struct phantom_symlink_info *next;
+	wchar_t *wlink;
+	wchar_t *wtarget;
+};
+
+static struct phantom_symlink_info *phantom_symlinks = NULL;
+static CRITICAL_SECTION phantom_symlinks_cs;
+
+static void process_phantom_symlinks(void)
+{
+	struct phantom_symlink_info *current, **psi;
+	EnterCriticalSection(&phantom_symlinks_cs);
+	/* process phantom symlinks list */
+	psi = &phantom_symlinks;
+	while ((current = *psi)) {
+		enum phantom_symlink_result result = process_phantom_symlink(
+				current->wtarget, current->wlink);
+		if (result == PHANTOM_SYMLINK_RETRY) {
+			psi = &current->next;
+		} else {
+			/* symlink was processed, remove from list */
+			*psi = current->next;
+			free(current);
+			/* if symlink was a directory, start over */
+			if (result == PHANTOM_SYMLINK_DIRECTORY)
+				psi = &phantom_symlinks;
+		}
+	}
+	LeaveCriticalSection(&phantom_symlinks_cs);
+}
+
 /* Normalizes NT paths as returned by some low-level APIs. */
 static wchar_t *normalize_ntpath(wchar_t *wbuf)
 {
@@ -297,7 +451,7 @@ static wchar_t *normalize_ntpath(wchar_t *wbuf)
 
 int mingw_unlink(const char *pathname, int handle_in_use_error)
 {
-	int ret, tries = 0;
+	int tries = 0;
 	wchar_t wpathname[MAX_PATH];
 	if (xutftowcs_path(wpathname, pathname) < 0)
 		return -1;
@@ -305,29 +459,26 @@ int mingw_unlink(const char *pathname, int handle_in_use_error)
 	if (DeleteFileW(wpathname))
 		return 0;
 
-	/* read-only files cannot be removed */
-	_wchmod(wpathname, 0666);
-	while ((ret = _wunlink(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
+	do {
+		/* read-only files cannot be removed */
+		_wchmod(wpathname, 0666);
+		if (!_wunlink(wpathname))
+			return 0;
 		if (!is_file_in_use_error(GetLastError()))
 			break;
-		if (!handle_in_use_error)
-			return ret;
-
 		/*
-		 * We assume that some other process had the source or
-		 * destination file open at the wrong moment and retry.
-		 * In order to give the other process a higher chance to
-		 * complete its operation, we give up our time slice now.
-		 * If we have to retry again, we do sleep a bit.
+		 * _wunlink() / DeleteFileW() for directory symlinks fails with
+		 * ERROR_ACCESS_DENIED (EACCES), so try _wrmdir() as well. This is the
+		 * same error we get if a file is in use (already checked above).
 		 */
-		Sleep(delay[tries]);
-		tries++;
-	}
-	while (ret == -1 && is_file_in_use_error(GetLastError()) &&
-	       ask_yes_no_if_possible("Unlink of file '%s' failed. "
-			"Should I try again?", pathname))
-	       ret = _wunlink(wpathname);
-	return ret;
+		if (!_wrmdir(wpathname))
+			return 0;
+
+		if (!handle_in_use_error)
+			return -1;
+	} while (retry_ask_yes_no(&tries, "Unlink of file '%s' failed. "
+			"Should I try again?", pathname));
+	return -1;
 }
 
 static int is_dir_empty(const wchar_t *wpath)
@@ -354,7 +505,7 @@ static int is_dir_empty(const wchar_t *wpath)
 
 int mingw_rmdir(const char *pathname)
 {
-	int ret, tries = 0;
+	int tries = 0;
 	wchar_t wpathname[MAX_PATH];
 	struct stat st;
 
@@ -380,7 +531,11 @@ int mingw_rmdir(const char *pathname)
 	if (xutftowcs_path(wpathname, pathname) < 0)
 		return -1;
 
-	while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
+	do {
+		if (!_wrmdir(wpathname)) {
+			invalidate_lstat_cache();
+			return 0;
+		}
 		if (!is_file_in_use_error(GetLastError()))
 			errno = err_win_to_posix(GetLastError());
 		if (errno != EACCES)
@@ -389,23 +544,9 @@ int mingw_rmdir(const char *pathname)
 			errno = ENOTEMPTY;
 			break;
 		}
-		/*
-		 * We assume that some other process had the source or
-		 * destination file open at the wrong moment and retry.
-		 * In order to give the other process a higher chance to
-		 * complete its operation, we give up our time slice now.
-		 * If we have to retry again, we do sleep a bit.
-		 */
-		Sleep(delay[tries]);
-		tries++;
-	}
-	while (ret == -1 && errno == EACCES && is_file_in_use_error(GetLastError()) &&
-	       ask_yes_no_if_possible("Deletion of directory '%s' failed. "
-			"Should I try again?", pathname))
-	       ret = _wrmdir(wpathname);
-	if (!ret)
-		invalidate_lstat_cache();
-	return ret;
+	} while (retry_ask_yes_no(&tries, "Deletion of directory '%s' failed. "
+			"Should I try again?", pathname));
+	return -1;
 }
 
 static inline int needs_hiding(const char *path)
@@ -466,6 +607,8 @@ int mingw_mkdir(const char *path, int mode UNUSED)
 	if (xutftowcs_path(wpath, path) < 0)
 		return -1;
 	ret = _wmkdir(wpath);
+	if (!ret)
+		process_phantom_symlinks();
 	if (!ret && needs_hiding(path))
 		return set_hidden_flag(wpath, 1);
 	return ret;
@@ -491,8 +634,10 @@ static int mingw_open_append(wchar_t const *wfilename, int oflags, ...)
 	DWORD create = (oflags & O_CREAT) ? OPEN_ALWAYS : OPEN_EXISTING;
 
 	/* only these flags are supported */
-	if ((oflags & ~O_CREAT) != (O_WRONLY | O_APPEND))
-		return errno = ENOSYS, -1;
+	if ((oflags & ~O_CREAT) != (O_WRONLY | O_APPEND)) {
+		errno = ENOSYS;
+		return -1;
+	}
 
 	/*
 	 * FILE_SHARE_WRITE is required to permit child processes
@@ -627,6 +772,7 @@ int mingw_open (const char *filename, int oflags, ...)
 	int fd, create = (oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL);
 	wchar_t wfilename[MAX_PATH];
 	open_fn_t open_fn;
+	WIN32_FILE_ATTRIBUTE_DATA fdata;
 
 	DECLARE_PROC_ADDR(ntdll.dll, NTSTATUS, NTAPI, RtlGetLastNtStatus, void);
 
@@ -651,6 +797,19 @@ int mingw_open (const char *filename, int oflags, ...)
 	else if (xutftowcs_path(wfilename, filename) < 0)
 		return -1;
 
+	/*
+	 * When `symlink` exists and is a symbolic link pointing to a
+	 * non-existing file, `_wopen(symlink, O_CREAT | O_EXCL)` would
+	 * create that file. Not what we want: Linux would say `EEXIST`
+	 * in that instance, which is therefore what Git expects.
+	 */
+	if (create &&
+	    GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata) &&
+	    (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
+		errno = EEXIST;
+		return -1;
+	}
+
 	fd = open_fn(wfilename, oflags, mode);
 
 	/*
@@ -837,9 +996,27 @@ int mingw_access(const char *filename, int mode)
 int mingw_chdir(const char *dirname)
 {
 	wchar_t wdirname[MAX_PATH];
+
 	if (xutftowcs_path(wdirname, dirname) < 0)
 		return -1;
-	return _wchdir(wdirname);
+
+	if (has_symlinks) {
+		HANDLE hnd = CreateFileW(wdirname, 0,
+				FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+				OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+		if (hnd == INVALID_HANDLE_VALUE) {
+			errno = err_win_to_posix(GetLastError());
+			return -1;
+		}
+		if (!GetFinalPathNameByHandleW(hnd, wdirname, ARRAY_SIZE(wdirname), 0)) {
+			errno = err_win_to_posix(GetLastError());
+			CloseHandle(hnd);
+			return -1;
+		}
+		CloseHandle(hnd);
+	}
+
+	return _wchdir(normalize_ntpath(wdirname));
 }
 
 int mingw_chmod(const char *filename, int mode)
@@ -901,53 +1078,139 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
 	return 1;
 }
 
-/* We keep the do_lstat code in a separate function to avoid recursion.
- * When a path ends with a slash, the stat will fail with ENOENT. In
- * this case, we strip the trailing slashes and stat again.
- *
- * If follow is true then act like stat() and report on the link
- * target. Otherwise report on the link itself.
+#ifndef _WINNT_H
+/*
+ * The REPARSE_DATA_BUFFER structure is defined in the Windows DDK (in
+ * ntifs.h) and in MSYS1's winnt.h (which defines _WINNT_H). So define
+ * it ourselves if we are on MSYS2 (whose winnt.h defines _WINNT_).
  */
-static int do_lstat(int follow, const char *file_name, struct stat *buf)
+typedef struct _REPARSE_DATA_BUFFER {
+	DWORD  ReparseTag;
+	WORD   ReparseDataLength;
+	WORD   Reserved;
+#ifndef _MSC_VER
+	_ANONYMOUS_UNION
+#endif
+	union {
+		struct {
+			WORD   SubstituteNameOffset;
+			WORD   SubstituteNameLength;
+			WORD   PrintNameOffset;
+			WORD   PrintNameLength;
+			ULONG  Flags;
+			WCHAR PathBuffer[1];
+		} SymbolicLinkReparseBuffer;
+		struct {
+			WORD   SubstituteNameOffset;
+			WORD   SubstituteNameLength;
+			WORD   PrintNameOffset;
+			WORD   PrintNameLength;
+			WCHAR PathBuffer[1];
+		} MountPointReparseBuffer;
+		struct {
+			BYTE   DataBuffer[1];
+		} GenericReparseBuffer;
+	} DUMMYUNIONNAME;
+} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
+#endif
+
+static int read_reparse_point(const WCHAR *wpath, BOOL fail_on_unknown_tag,
+			      char *tmpbuf, int *plen, DWORD *ptag)
+{
+	HANDLE handle;
+	WCHAR *wbuf;
+	REPARSE_DATA_BUFFER *b = alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
+	DWORD dummy;
+
+	/* read reparse point data */
+	handle = CreateFileW(wpath, 0,
+			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+			OPEN_EXISTING,
+			FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
+	if (handle == INVALID_HANDLE_VALUE) {
+		errno = err_win_to_posix(GetLastError());
+		return -1;
+	}
+	if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, b,
+			MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dummy, NULL)) {
+		errno = err_win_to_posix(GetLastError());
+		CloseHandle(handle);
+		return -1;
+	}
+	CloseHandle(handle);
+
+	/* get target path for symlinks or mount points (aka 'junctions') */
+	switch ((*ptag = b->ReparseTag)) {
+	case IO_REPARSE_TAG_SYMLINK:
+		wbuf = (WCHAR*) (((char*) b->SymbolicLinkReparseBuffer.PathBuffer)
+				+ b->SymbolicLinkReparseBuffer.SubstituteNameOffset);
+		*(WCHAR*) (((char*) wbuf)
+				+ b->SymbolicLinkReparseBuffer.SubstituteNameLength) = 0;
+		break;
+	case IO_REPARSE_TAG_MOUNT_POINT:
+		wbuf = (WCHAR*) (((char*) b->MountPointReparseBuffer.PathBuffer)
+				+ b->MountPointReparseBuffer.SubstituteNameOffset);
+		*(WCHAR*) (((char*) wbuf)
+				+ b->MountPointReparseBuffer.SubstituteNameLength) = 0;
+		break;
+	default:
+		if (fail_on_unknown_tag) {
+			errno = EINVAL;
+			return -1;
+		} else {
+			*plen = MAX_PATH;
+			return 0;
+		}
+	}
+
+	if ((*plen =
+	     xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_PATH)) <  0)
+		return -1;
+	return 0;
+}
+
+int mingw_lstat(const char *file_name, struct stat *buf)
 {
 	WIN32_FILE_ATTRIBUTE_DATA fdata;
+	DWORD reparse_tag = 0;
+	int link_len = 0;
 	wchar_t wfilename[MAX_PATH];
-	if (xutftowcs_path(wfilename, file_name) < 0)
+	int wlen = xutftowcs_path(wfilename, file_name);
+	if (wlen < 0)
 		return -1;
 
+	/* strip trailing '/', or GetFileAttributes will fail */
+	while (wlen && is_dir_sep(wfilename[wlen - 1]))
+		wfilename[--wlen] = 0;
+	if (!wlen) {
+		errno = ENOENT;
+		return -1;
+	}
+
 	if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
+		/* for reparse points, get the link tag and length */
+		if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+			char tmpbuf[MAX_PATH];
+
+			if (read_reparse_point(wfilename, FALSE, tmpbuf,
+					       &link_len, &reparse_tag) < 0)
+				return -1;
+		}
 		buf->st_ino = 0;
 		buf->st_gid = 0;
 		buf->st_uid = 0;
 		buf->st_nlink = 1;
-		buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
-		buf->st_size = fdata.nFileSizeLow |
-			(((off_t)fdata.nFileSizeHigh)<<32);
+		buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
+				reparse_tag);
+		buf->st_size = S_ISLNK(buf->st_mode) ? link_len :
+			fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
 		buf->st_dev = buf->st_rdev = 0; /* not used by Git */
 		filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
 		filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
 		filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
-		if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
-			WIN32_FIND_DATAW findbuf;
-			HANDLE handle = FindFirstFileW(wfilename, &findbuf);
-			if (handle != INVALID_HANDLE_VALUE) {
-				if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
-						(findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) {
-					if (follow) {
-						char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
-						buf->st_size = readlink(file_name, buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
-					} else {
-						buf->st_mode = S_IFLNK;
-					}
-					buf->st_mode |= S_IREAD;
-					if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
-						buf->st_mode |= S_IWRITE;
-				}
-				FindClose(handle);
-			}
-		}
 		return 0;
 	}
+
 	switch (GetLastError()) {
 	case ERROR_ACCESS_DENIED:
 	case ERROR_SHARING_VIOLATION:
@@ -974,39 +1237,6 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
 	return -1;
 }
 
-/* We provide our own lstat/fstat functions, since the provided
- * lstat/fstat functions are so slow. These stat functions are
- * tailored for Git's usage (read: fast), and are not meant to be
- * complete. Note that Git stat()s are redirected to mingw_lstat()
- * too, since Windows doesn't really handle symlinks that well.
- */
-static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
-{
-	size_t namelen;
-	char alt_name[PATH_MAX];
-
-	if (!do_lstat(follow, file_name, buf))
-		return 0;
-
-	/* if file_name ended in a '/', Windows returned ENOENT;
-	 * try again without trailing slashes
-	 */
-	if (errno != ENOENT)
-		return -1;
-
-	namelen = strlen(file_name);
-	if (namelen && file_name[namelen-1] != '/')
-		return -1;
-	while (namelen && file_name[namelen-1] == '/')
-		--namelen;
-	if (!namelen || namelen >= PATH_MAX)
-		return -1;
-
-	memcpy(alt_name, file_name, namelen);
-	alt_name[namelen] = 0;
-	return do_lstat(follow, alt_name, buf);
-}
-
 static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
 {
 	BY_HANDLE_FILE_INFORMATION fdata;
@@ -1020,7 +1250,7 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
 	buf->st_gid = 0;
 	buf->st_uid = 0;
 	buf->st_nlink = 1;
-	buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
+	buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes, 0);
 	buf->st_size = fdata.nFileSizeLow |
 		(((off_t)fdata.nFileSizeHigh)<<32);
 	buf->st_dev = buf->st_rdev = 0; /* not used by Git */
@@ -1030,13 +1260,37 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
 	return 0;
 }
 
-int mingw_lstat(const char *file_name, struct stat *buf)
-{
-	return do_stat_internal(0, file_name, buf);
-}
 int mingw_stat(const char *file_name, struct stat *buf)
 {
-	return do_stat_internal(1, file_name, buf);
+	wchar_t wfile_name[MAX_PATH];
+	HANDLE hnd;
+	int result;
+
+	/* open the file and let Windows resolve the links */
+	if (xutftowcs_path(wfile_name, file_name) < 0)
+		return -1;
+	hnd = CreateFileW(wfile_name, 0,
+			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+			OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+	if (hnd == INVALID_HANDLE_VALUE) {
+		DWORD err = GetLastError();
+
+		if (err == ERROR_ACCESS_DENIED &&
+		    !mingw_lstat(file_name, buf) &&
+		    !S_ISLNK(buf->st_mode))
+			/*
+			 * POSIX semantics state to still try to fill
+			 * information, even if permission is denied to create
+			 * a file handle.
+			 */
+			return 0;
+
+		errno = err_win_to_posix(err);
+		return -1;
+	}
+	result = get_file_info_by_handle(hnd, buf);
+	CloseHandle(hnd);
+	return result;
 }
 
 int mingw_fstat(int fd, struct stat *buf)
@@ -1162,18 +1416,6 @@ unsigned int sleep (unsigned int seconds)
 	return 0;
 }
 
-char *mingw_mktemp(char *template)
-{
-	wchar_t wtemplate[MAX_PATH];
-	if (xutftowcs_path(wtemplate, template) < 0)
-		return NULL;
-	if (!_wmktemp(wtemplate))
-		return NULL;
-	if (xwcstoutf(template, wtemplate, strlen(template) + 1) < 0)
-		return NULL;
-	return template;
-}
-
 int mkstemp(char *template)
 {
 	return git_mkstemp_mode(template, 0600);
@@ -1235,18 +1477,16 @@ char *mingw_getcwd(char *pointer, int len)
 {
 	wchar_t cwd[MAX_PATH], wpointer[MAX_PATH];
 	DWORD ret = GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
+	HANDLE hnd;
 
 	if (!ret || ret >= ARRAY_SIZE(cwd)) {
 		errno = ret ? ENAMETOOLONG : err_win_to_posix(GetLastError());
 		return NULL;
 	}
-	ret = GetLongPathNameW(cwd, wpointer, ARRAY_SIZE(wpointer));
-	if (!ret && GetLastError() == ERROR_ACCESS_DENIED) {
-		HANDLE hnd = CreateFileW(cwd, 0,
-			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
-			OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
-		if (hnd == INVALID_HANDLE_VALUE)
-			return NULL;
+	hnd = CreateFileW(cwd, 0,
+			  FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+			  OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+	if (hnd != INVALID_HANDLE_VALUE) {
 		ret = GetFinalPathNameByHandleW(hnd, wpointer, ARRAY_SIZE(wpointer), 0);
 		CloseHandle(hnd);
 		if (!ret || ret >= ARRAY_SIZE(wpointer))
@@ -1255,13 +1495,11 @@ char *mingw_getcwd(char *pointer, int len)
 			return NULL;
 		return pointer;
 	}
-	if (!ret || ret >= ARRAY_SIZE(wpointer))
-		return NULL;
-	if (GetFileAttributesW(wpointer) == INVALID_FILE_ATTRIBUTES) {
+	if (GetFileAttributesW(cwd) == INVALID_FILE_ATTRIBUTES) {
 		errno = ENOENT;
 		return NULL;
 	}
-	if (xwcstoutf(pointer, wpointer, len) < 0)
+	if (xwcstoutf(pointer, cwd, len) < 0)
 		return NULL;
 	convert_slashes(pointer);
 	return pointer;
@@ -1982,6 +2220,16 @@ int mingw_kill(pid_t pid, int sig)
 			CloseHandle(h);
 			return 0;
 		}
+		/*
+		 * OpenProcess returns ERROR_INVALID_PARAMETER for
+		 * non-existent PIDs. Map this to ESRCH for POSIX
+		 * compatibility with kill(pid, 0).
+		 */
+		if (GetLastError() == ERROR_INVALID_PARAMETER)
+			errno = ESRCH;
+		else
+			errno = err_win_to_posix(GetLastError());
+		return -1;
 	}
 
 	errno = EINVAL;
@@ -2197,7 +2445,7 @@ int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz)
 int mingw_rename(const char *pold, const char *pnew)
 {
 	static int supports_file_rename_info_ex = 1;
-	DWORD attrs, gle;
+	DWORD attrs = INVALID_FILE_ATTRIBUTES, gle;
 	int tries = 0;
 	wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
 	int wpnew_len;
@@ -2208,15 +2456,6 @@ int mingw_rename(const char *pold, const char *pnew)
 	if (wpnew_len < 0)
 		return -1;
 
-	/*
-	 * Try native rename() first to get errno right.
-	 * It is based on MoveFile(), which cannot overwrite existing files.
-	 */
-	if (!_wrename(wpold, wpnew))
-		return 0;
-	if (errno != EEXIST)
-		return -1;
-
 repeat:
 	if (supports_file_rename_info_ex) {
 		/*
@@ -2292,13 +2531,22 @@ int mingw_rename(const char *pold, const char *pnew)
 		 * to retry.
 		 */
 	} else {
-		if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
+		if (MoveFileExW(wpold, wpnew,
+				MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
 			return 0;
 		gle = GetLastError();
 	}
 
-	/* TODO: translate more errors */
-	if (gle == ERROR_ACCESS_DENIED &&
+	/* revert file attributes on failure */
+	if (attrs != INVALID_FILE_ATTRIBUTES)
+		SetFileAttributesW(wpnew, attrs);
+
+	if (!is_file_in_use_error(gle)) {
+		errno = err_win_to_posix(gle);
+		return -1;
+	}
+
+	if (attrs == INVALID_FILE_ATTRIBUTES &&
 	    (attrs = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) {
 		if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
 			DWORD attrsold = GetFileAttributesW(wpold);
@@ -2310,28 +2558,10 @@ int mingw_rename(const char *pold, const char *pnew)
 			return -1;
 		}
 		if ((attrs & FILE_ATTRIBUTE_READONLY) &&
-		    SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
-			if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
-				return 0;
-			gle = GetLastError();
-			/* revert file attributes on failure */
-			SetFileAttributesW(wpnew, attrs);
-		}
+		    SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY))
+			goto repeat;
 	}
-	if (tries < ARRAY_SIZE(delay) && gle == ERROR_ACCESS_DENIED) {
-		/*
-		 * We assume that some other process had the source or
-		 * destination file open at the wrong moment and retry.
-		 * In order to give the other process a higher chance to
-		 * complete its operation, we give up our time slice now.
-		 * If we have to retry again, we do sleep a bit.
-		 */
-		Sleep(delay[tries]);
-		tries++;
-		goto repeat;
-	}
-	if (gle == ERROR_ACCESS_DENIED &&
-	       ask_yes_no_if_possible("Rename from '%s' to '%s' failed. "
+	if (retry_ask_yes_no(&tries, "Rename from '%s' to '%s' failed. "
 		       "Should I try again?", pold, pnew))
 		goto repeat;
 
@@ -2450,12 +2680,14 @@ static int start_timer_thread(void)
 	timer_event = CreateEvent(NULL, FALSE, FALSE, NULL);
 	if (timer_event) {
 		timer_thread = (HANDLE) _beginthreadex(NULL, 0, ticktack, NULL, 0, NULL);
-		if (!timer_thread )
-			return errno = ENOMEM,
-				error("cannot start timer thread");
-	} else
-		return errno = ENOMEM,
-			error("cannot allocate resources for timer");
+		if (!timer_thread ) {
+			errno = ENOMEM;
+			return error("cannot start timer thread");
+		}
+	} else {
+		errno = ENOMEM;
+		return error("cannot allocate resources for timer");
+	}
 	return 0;
 }
 
@@ -2488,13 +2720,15 @@ int setitimer(int type UNUSED, struct itimerval *in, struct itimerval *out)
 	static const struct timeval zero;
 	static int atexit_done;
 
-	if (out)
-		return errno = EINVAL,
-			error("setitimer param 3 != NULL not implemented");
+	if (out) {
+		errno = EINVAL;
+		return error("setitimer param 3 != NULL not implemented");
+	}
 	if (!is_timeval_eq(&in->it_interval, &zero) &&
-	    !is_timeval_eq(&in->it_interval, &in->it_value))
-		return errno = EINVAL,
-			error("setitimer: it_interval must be zero or eq it_value");
+	    !is_timeval_eq(&in->it_interval, &in->it_value)) {
+		errno = EINVAL;
+		return error("setitimer: it_interval must be zero or eq it_value");
+	}
 
 	if (timer_thread)
 		stop_timer_thread();
@@ -2516,12 +2750,14 @@ int sigaction(int sig, struct sigaction *in, struct sigaction *out)
 {
 	if (sig == SIGCHLD)
 		return -1;
-	else if (sig != SIGALRM)
-		return errno = EINVAL,
-			error("sigaction only implemented for SIGALRM");
-	if (out)
-		return errno = EINVAL,
-			error("sigaction: param 3 != NULL not implemented");
+	else if (sig != SIGALRM) {
+		errno = EINVAL;
+		return error("sigaction only implemented for SIGALRM");
+	}
+	if (out) {
+		errno = EINVAL;
+		return error("sigaction: param 3 != NULL not implemented");
+	}
 
 	timer_fn = in->sa_handler;
 	return 0;
@@ -2614,6 +2850,94 @@ int link(const char *oldpath, const char *newpath)
 	return 0;
 }
 
+int symlink(const char *target, const char *link)
+{
+	wchar_t wtarget[MAX_PATH], wlink[MAX_PATH];
+	int len;
+
+	/* fail if symlinks are disabled or API is not supported (WinXP) */
+	if (!has_symlinks) {
+		errno = ENOSYS;
+		return -1;
+	}
+
+	if ((len = xutftowcs_path(wtarget, target)) < 0
+			|| xutftowcs_path(wlink, link) < 0)
+		return -1;
+
+	/* convert target dir separators to backslashes */
+	while (len--)
+		if (wtarget[len] == '/')
+			wtarget[len] = '\\';
+
+	/* create file symlink */
+	if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags)) {
+		errno = err_win_to_posix(GetLastError());
+		return -1;
+	}
+
+	/* convert to directory symlink if target exists */
+	switch (process_phantom_symlink(wtarget, wlink)) {
+	case PHANTOM_SYMLINK_RETRY:	{
+		/* if target doesn't exist, add to phantom symlinks list */
+		wchar_t wfullpath[MAX_PATH];
+		struct phantom_symlink_info *psi;
+
+		/* convert to absolute path to be independent of cwd */
+		len = GetFullPathNameW(wlink, MAX_PATH, wfullpath, NULL);
+		if (!len || len >= MAX_PATH) {
+			errno = err_win_to_posix(GetLastError());
+			return -1;
+		}
+
+		/* over-allocate and fill phantom_symlink_info structure */
+		psi = xmalloc(sizeof(struct phantom_symlink_info)
+			+ sizeof(wchar_t) * (len + wcslen(wtarget) + 2));
+		psi->wlink = (wchar_t *)(psi + 1);
+		wcscpy(psi->wlink, wfullpath);
+		psi->wtarget = psi->wlink + len + 1;
+		wcscpy(psi->wtarget, wtarget);
+
+		EnterCriticalSection(&phantom_symlinks_cs);
+		psi->next = phantom_symlinks;
+		phantom_symlinks = psi;
+		LeaveCriticalSection(&phantom_symlinks_cs);
+		break;
+	}
+	case PHANTOM_SYMLINK_DIRECTORY:
+		/* if we created a dir symlink, process other phantom symlinks */
+		process_phantom_symlinks();
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
+int readlink(const char *path, char *buf, size_t bufsiz)
+{
+	WCHAR wpath[MAX_PATH];
+	char tmpbuf[MAX_PATH];
+	int len;
+	DWORD tag;
+
+	if (xutftowcs_path(wpath, path) < 0)
+		return -1;
+
+	if (read_reparse_point(wpath, TRUE, tmpbuf, &len, &tag) < 0)
+		return -1;
+
+	/*
+	 * Adapt to strange readlink() API: Copy up to bufsiz *bytes*, potentially
+	 * cutting off a UTF-8 sequence. Insufficient bufsize is *not* a failure
+	 * condition. There is no conversion function that produces invalid UTF-8,
+	 * so convert to a (hopefully large enough) temporary buffer, then memcpy
+	 * the requested number of bytes (including '\0' for robustness).
+	 */
+	memcpy(buf, tmpbuf, min(bufsiz, len + 1));
+	return min(bufsiz, len);
+}
+
 pid_t waitpid(pid_t pid, int *status, int options)
 {
 	HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
@@ -2802,6 +3126,15 @@ static void setup_windows_environment(void)
 		if (!tmp && (tmp = getenv("USERPROFILE")))
 			setenv("HOME", tmp, 1);
 	}
+
+	/*
+	 * Change 'core.symlinks' default to false, unless native symlinks are
+	 * enabled in MSys2 (via 'MSYS=winsymlinks:nativestrict'). Thus we can
+	 * run the test suite (which doesn't obey config files) with or without
+	 * symlink support.
+	 */
+	if (!(tmp = getenv("MSYS")) || !strstr(tmp, "winsymlinks:nativestrict"))
+		has_symlinks = 0;
 }
 
 static void get_current_user_sid(PSID *sid, HANDLE *linked_token)
@@ -3215,6 +3548,24 @@ static void maybe_redirect_std_handles(void)
 				  GENERIC_WRITE, FILE_FLAG_NO_BUFFERING);
 }
 
+static void adjust_symlink_flags(void)
+{
+	/*
+	 * Starting with Windows 10 Build 14972, symbolic links can be created
+	 * using CreateSymbolicLink() without elevation by passing the flag
+	 * SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE (0x02) as last
+	 * parameter, provided the Developer Mode has been enabled. Some
+	 * earlier Windows versions complain about this flag with an
+	 * ERROR_INVALID_PARAMETER, hence we have to test the build number
+	 * specifically.
+	 */
+	if (GetVersion() >= 14972 << 16) {
+		symlink_file_flags |= 2;
+		symlink_directory_flags |= 2;
+	}
+
+}
+
 #ifdef _MSC_VER
 #ifdef _DEBUG
 #include <crtdbg.h>
@@ -3250,6 +3601,7 @@ int wmain(int argc, const wchar_t **wargv)
 #endif
 
 	maybe_redirect_std_handles();
+	adjust_symlink_flags();
 
 	/* determine size of argv and environ conversion buffer */
 	maxlen = wcslen(wargv[0]);
@@ -3279,6 +3631,7 @@ int wmain(int argc, const wchar_t **wargv)
 
 	/* initialize critical section for waitpid pinfo_t list */
 	InitializeCriticalSection(&pinfo_cs);
+	InitializeCriticalSection(&phantom_symlinks_cs);
 
 	/* set up default file mode and file modes for stdin/out/err */
 	_fmode = _O_BINARY;
diff --git a/compat/mkdtemp.c b/compat/mkdtemp.c
deleted file mode 100644
index 1136119..0000000
--- a/compat/mkdtemp.c
+++ /dev/null
@@ -1,8 +0,0 @@
-#include "../git-compat-util.h"
-
-char *gitmkdtemp(char *template)
-{
-	if (!*mktemp(template) || mkdir(template, 0700))
-		return NULL;
-	return template;
-}
diff --git a/compat/mmap.c b/compat/mmap.c
index 2fe1c77..1a11871 100644
--- a/compat/mmap.c
+++ b/compat/mmap.c
@@ -38,7 +38,7 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t of
 	return start;
 }
 
-int git_munmap(void *start, size_t length)
+int git_munmap(void *start, size_t length UNUSED)
 {
 	free(start);
 	return 0;
diff --git a/compat/posix.h b/compat/posix.h
index 067a00f..245386f 100644
--- a/compat/posix.h
+++ b/compat/posix.h
@@ -329,8 +329,7 @@ int gitsetenv(const char *, const char *, int);
 #endif
 
 #ifdef NO_MKDTEMP
-#define mkdtemp gitmkdtemp
-char *gitmkdtemp(char *);
+#define mkdtemp git_mkdtemp
 #endif
 
 #ifdef NO_UNSETENV
diff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c
index a8fc812..4a3e7df 100644
--- a/compat/simple-ipc/ipc-win32.c
+++ b/compat/simple-ipc/ipc-win32.c
@@ -686,7 +686,7 @@ static LPSECURITY_ATTRIBUTES get_sa(struct my_sa_data *d)
 		goto fail;
 	}
 
-	memset(ea, 0, NR_EA * sizeof(EXPLICIT_ACCESS));
+	MEMZERO_ARRAY(ea, NR_EA);
 
 	ea[0].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE;
 	ea[0].grfAccessMode = SET_ACCESS;
diff --git a/compat/win32.h b/compat/win32.h
index a97e880..671bcc8 100644
--- a/compat/win32.h
+++ b/compat/win32.h
@@ -6,10 +6,12 @@
 #include <windows.h>
 #endif
 
-static inline int file_attr_to_st_mode (DWORD attr)
+static inline int file_attr_to_st_mode (DWORD attr, DWORD tag)
 {
 	int fMode = S_IREAD;
-	if (attr & FILE_ATTRIBUTE_DIRECTORY)
+	if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) && tag == IO_REPARSE_TAG_SYMLINK)
+		fMode |= S_IFLNK;
+	else if (attr & FILE_ATTRIBUTE_DIRECTORY)
 		fMode |= S_IFDIR;
 	else
 		fMode |= S_IFREG;
diff --git a/compat/win32/dirent.c b/compat/win32/dirent.c
index 52420ec..24ee9b8 100644
--- a/compat/win32/dirent.c
+++ b/compat/win32/dirent.c
@@ -12,7 +12,10 @@ static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
 	xwcstoutf(ent->d_name, fdata->cFileName, sizeof(ent->d_name));
 
 	/* Set file type, based on WIN32_FIND_DATA */
-	if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+	if ((fdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
+			&& fdata->dwReserved0 == IO_REPARSE_TAG_SYMLINK)
+		ent->d_type = DT_LNK;
+	else if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
 		ent->d_type = DT_DIR;
 	else
 		ent->d_type = DT_REG;
diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c
index 58980a5..7e93146 100644
--- a/compat/win32/pthread.c
+++ b/compat/win32/pthread.c
@@ -59,3 +59,10 @@ pthread_t pthread_self(void)
 	t.tid = GetCurrentThreadId();
 	return t;
 }
+
+int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
+{
+	if (SleepConditionVariableCS(cond, mutex, INFINITE) == 0)
+		return err_win_to_posix(GetLastError());
+	return 0;
+}
diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h
index e2b5c4f..ccacc5a 100644
--- a/compat/win32/pthread.h
+++ b/compat/win32/pthread.h
@@ -34,9 +34,8 @@ typedef int pthread_mutexattr_t;
 
 #define pthread_cond_t CONDITION_VARIABLE
 
-#define pthread_cond_init(a,b) InitializeConditionVariable((a))
+#define pthread_cond_init(a,b) return_0((InitializeConditionVariable((a)), 0))
 #define pthread_cond_destroy(a) do {} while (0)
-#define pthread_cond_wait(a,b) return_0(SleepConditionVariableCS((a), (b), INFINITE))
 #define pthread_cond_signal WakeConditionVariable
 #define pthread_cond_broadcast WakeAllConditionVariable
 
@@ -64,6 +63,8 @@ int win32_pthread_join(pthread_t *thread, void **value_ptr);
 #define pthread_equal(t1, t2) ((t1).tid == (t2).tid)
 pthread_t pthread_self(void);
 
+int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
+
 static inline void NORETURN pthread_exit(void *ret)
 {
 	_endthreadex((unsigned)(uintptr_t)ret);
diff --git a/compat/win32/trace2_win32_process_info.c b/compat/win32/trace2_win32_process_info.c
index f147da7..6a6a396 100644
--- a/compat/win32/trace2_win32_process_info.c
+++ b/compat/win32/trace2_win32_process_info.c
@@ -3,6 +3,7 @@
 #include "../../git-compat-util.h"
 #include "../../json-writer.h"
 #include "../../repository.h"
+#include "../../strvec.h"
 #include "../../trace2.h"
 #include "lazyload.h"
 #include <psapi.h>
@@ -32,12 +33,7 @@ static int find_pid(DWORD pid, HANDLE hSnapshot, PROCESSENTRY32 *pe32)
 }
 
 /*
- * Accumulate JSON array of our parent processes:
- *     [
- *         exe-name-parent,
- *         exe-name-grand-parent,
- *         ...
- *     ]
+ * Accumulate array of our parent process names.
  *
  * Note: we only report the filename of the process executable; the
  *       only way to get its full pathname is to use OpenProcess()
@@ -73,7 +69,7 @@ static int find_pid(DWORD pid, HANDLE hSnapshot, PROCESSENTRY32 *pe32)
  * simple and avoid the alloc/realloc overhead.  It is OK if we
  * truncate the search and return a partial answer.
  */
-static void get_processes(struct json_writer *jw, HANDLE hSnapshot)
+static void get_processes(struct strvec *names, HANDLE hSnapshot)
 {
 	PROCESSENTRY32 pe32;
 	DWORD pid;
@@ -82,19 +78,19 @@ static void get_processes(struct json_writer *jw, HANDLE hSnapshot)
 
 	pid = GetCurrentProcessId();
 	while (find_pid(pid, hSnapshot, &pe32)) {
-		/* Only report parents. Omit self from the JSON output. */
+		/* Only report parents. Omit self from the output. */
 		if (nr_pids)
-			jw_array_string(jw, pe32.szExeFile);
+			strvec_push(names, pe32.szExeFile);
 
 		/* Check for cycle in snapshot. (Yes, it happened.) */
 		for (k = 0; k < nr_pids; k++)
 			if (pid == pid_list[k]) {
-				jw_array_string(jw, "(cycle)");
+				strvec_push(names, "(cycle)");
 				return;
 			}
 
 		if (nr_pids == NR_PIDS_LIMIT) {
-			jw_array_string(jw, "(truncated)");
+			strvec_push(names, "(truncated)");
 			return;
 		}
 
@@ -105,24 +101,14 @@ static void get_processes(struct json_writer *jw, HANDLE hSnapshot)
 }
 
 /*
- * Emit JSON data for the current and parent processes.  Individual
- * trace2 targets can decide how to actually print it.
+ * Collect the list of parent process names.
  */
-static void get_ancestry(void)
+static void get_ancestry(struct strvec *names)
 {
 	HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
 
 	if (hSnapshot != INVALID_HANDLE_VALUE) {
-		struct json_writer jw = JSON_WRITER_INIT;
-
-		jw_array_begin(&jw, 0);
-		get_processes(&jw, hSnapshot);
-		jw_end(&jw);
-
-		trace2_data_json("process", the_repository, "windows/ancestry",
-				 &jw);
-
-		jw_release(&jw);
+		get_processes(names, hSnapshot);
 		CloseHandle(hSnapshot);
 	}
 }
@@ -176,13 +162,35 @@ static void get_peak_memory_info(void)
 
 void trace2_collect_process_info(enum trace2_process_info_reason reason)
 {
+	struct strvec names = STRVEC_INIT;
+
 	if (!trace2_is_enabled())
 		return;
 
 	switch (reason) {
 	case TRACE2_PROCESS_INFO_STARTUP:
 		get_is_being_debugged();
-		get_ancestry();
+		get_ancestry(&names);
+		if (names.nr) {
+			/*
+			  Emit the ancestry data as a data_json event to
+			  maintain compatibility for consumers of the older
+			  "windows/ancestry" event.
+			 */
+			struct json_writer jw = JSON_WRITER_INIT;
+			jw_array_begin(&jw, 0);
+			for (size_t i = 0; i < names.nr; i++)
+				jw_array_string(&jw, names.v[i]);
+			jw_end(&jw);
+			trace2_data_json("process", the_repository,
+					 "windows/ancestry", &jw);
+			jw_release(&jw);
+
+			/* Emit the ancestry data with the new event. */
+			trace2_cmd_ancestry(names.v);
+		}
+
+		strvec_clear(&names);
 		return;
 
 	case TRACE2_PROCESS_INFO_EXIT:
diff --git a/config.c b/config.c
index f1def0d..156f2a2 100644
--- a/config.c
+++ b/config.c
@@ -160,7 +160,7 @@ static int handle_path_include(const struct key_value_info *kvi,
 	 * based on the including config file.
 	 */
 	if (!is_absolute_path(path)) {
-		char *slash;
+		const char *slash;
 
 		if (!kvi || kvi->origin_type != CONFIG_ORIGIN_FILE) {
 			ret = error(_("relative config includes must come from files"));
@@ -1291,6 +1291,7 @@ int git_config_pathname(char **dest, const char *var, const char *value)
 
 	if (is_optional && is_missing_file(path)) {
 		free(path);
+		*dest = NULL;
 		return 0;
 	}
 
@@ -1953,7 +1954,7 @@ int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *d
 		return 1;
 }
 
-int git_configset_get_pathname(struct config_set *set, const char *key, char **dest)
+static int git_configset_get_pathname(struct config_set *set, const char *key, char **dest)
 {
 	const char *value;
 	if (!git_configset_get_value(set, key, &value, NULL))
@@ -2434,14 +2435,14 @@ int repo_config_get_expiry_in_days(struct repository *r, const char *key,
 				   timestamp_t *expiry, timestamp_t now)
 {
 	const char *expiry_string;
-	intmax_t days;
+	int days;
 	timestamp_t when;
 
 	if (repo_config_get_string_tmp(r, key, &expiry_string))
 		return 1; /* no such thing */
 
-	if (git_parse_signed(expiry_string, &days, maximum_signed_value_of_type(int))) {
-		const int scale = 86400;
+	if (git_parse_int(expiry_string, &days)) {
+		const intmax_t scale = 86400;
 		*expiry = now - days * scale;
 		return 0;
 	}
diff --git a/config.h b/config.h
index 19c87fc..ba426a9 100644
--- a/config.h
+++ b/config.h
@@ -564,7 +564,6 @@ int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned lon
 int git_configset_get_bool(struct config_set *cs, const char *key, int *dest);
 int git_configset_get_bool_or_int(struct config_set *cs, const char *key, int *is_bool, int *dest);
 int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest);
-int git_configset_get_pathname(struct config_set *cs, const char *key, char **dest);
 
 /**
  * Run only the discover part of the repo_config_get_*() functions
diff --git a/config.mak.uname b/config.mak.uname
index 1691c6a..5feb582 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -124,6 +124,7 @@
 	# - MacOS 10.0.* and MacOS 10.1.0 = Darwin 1.*
 	# - MacOS 10.x.* = Darwin (x+4).* for (1 <= x)
 	# i.e. "begins with [15678] and a dot" means "10.4.* or older".
+	DARWIN_MAJOR_VERSION = $(shell expr "$(uname_R)" : '\([0-9]*\)\.')
         ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2)
 		OLD_ICONV = UnfortunatelyYes
 		NO_APPLE_COMMON_CRYPTO = YesPlease
@@ -148,29 +149,17 @@
 	HAVE_NS_GET_EXECUTABLE_PATH = YesPlease
 	CSPRNG_METHOD = arc4random
 	USE_ENHANCED_BASIC_REGULAR_EXPRESSIONS = YesPlease
+	HAVE_PLATFORM_PROCINFO = YesPlease
+	COMPAT_OBJS += compat/darwin/procinfo.o
 
-	# Workaround for `gettext` being keg-only and not even being linked via
-	# `brew link --force gettext`, should be obsolete as of
-	# https://github.com/Homebrew/homebrew-core/pull/53489
-        ifeq ($(shell test -d /usr/local/opt/gettext/ && echo y),y)
-		BASIC_CFLAGS += -I/usr/local/include -I/usr/local/opt/gettext/include
-		BASIC_LDFLAGS += -L/usr/local/lib -L/usr/local/opt/gettext/lib
-                ifeq ($(shell test -x /usr/local/opt/gettext/bin/msgfmt && echo y),y)
-			MSGFMT = /usr/local/opt/gettext/bin/msgfmt
-                endif
-	# On newer ARM-based machines the default installation path has changed to
-	# /opt/homebrew. Include it in our search paths so that the user does not
-	# have to configure this manually.
-	#
-	# Note that we do not employ the same workaround as above where we manually
-	# add gettext. The issue was fixed more than three years ago by now, and at
-	# that point there haven't been any ARM-based Macs yet.
-        else ifeq ($(shell test -d /opt/homebrew/ && echo y),y)
-		BASIC_CFLAGS += -I/opt/homebrew/include
-		BASIC_LDFLAGS += -L/opt/homebrew/lib
-                ifeq ($(shell test -x /opt/homebrew/bin/msgfmt && echo y),y)
-			MSGFMT = /opt/homebrew/bin/msgfmt
-                endif
+        ifeq ($(uname_M),arm64)
+		HOMEBREW_PREFIX = /opt/homebrew
+        else
+		HOMEBREW_PREFIX = /usr/local
+        endif
+        ifeq ($(shell test "$(DARWIN_MAJOR_VERSION)" -ge 24 && echo 1),1)
+		USE_HOMEBREW_LIBICONV = UnfortunatelyYes
+		NEEDS_GOOD_LIBICONV = UnfortunatelyYes
         endif
 
 	# The builtin FSMonitor on MacOS builds upon Simple-IPC.  Both require
diff --git a/connect.c b/connect.c
index 8352b71..a02583a 100644
--- a/connect.c
+++ b/connect.c
@@ -240,6 +240,8 @@ static void process_capabilities(struct packet_reader *reader, size_t *linelen)
 	size_t nul_location = strlen(line);
 	if (nul_location == *linelen)
 		return;
+
+	free(server_capabilities_v1);
 	server_capabilities_v1 = xstrdup(line + nul_location + 1);
 	*linelen = nul_location;
 
@@ -503,7 +505,8 @@ static void send_capabilities(int fd_out, struct packet_reader *reader)
 		reader->hash_algo = &hash_algos[GIT_HASH_SHA1_LEGACY];
 	}
 	if (server_feature_v2("promisor-remote", &promisor_remote_info)) {
-		char *reply = promisor_remote_reply(promisor_remote_info);
+		char *reply;
+		promisor_remote_reply(promisor_remote_info, &reply);
 		if (reply) {
 			packet_write_fmt(fd_out, "promisor-remote=%s", reply);
 			free(reply);
diff --git a/connected.c b/connected.c
index 7940310..6718503 100644
--- a/connected.c
+++ b/connected.c
@@ -45,20 +45,6 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
 		return err;
 	}
 
-	if (transport && transport->smart_options &&
-	    transport->smart_options->self_contained_and_connected &&
-	    transport->pack_lockfiles.nr == 1 &&
-	    strip_suffix(transport->pack_lockfiles.items[0].string,
-			 ".keep", &base_len)) {
-		struct strbuf idx_file = STRBUF_INIT;
-		strbuf_add(&idx_file, transport->pack_lockfiles.items[0].string,
-			   base_len);
-		strbuf_addstr(&idx_file, ".idx");
-		new_pack = add_packed_git(the_repository, idx_file.buf,
-					  idx_file.len, 1);
-		strbuf_release(&idx_file);
-	}
-
 	if (repo_has_promisor_remote(the_repository)) {
 		/*
 		 * For partial clones, we don't want to have to do a regular
@@ -90,7 +76,6 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
 promisor_pack_found:
 			;
 		} while ((oid = fn(cb_data)) != NULL);
-		free(new_pack);
 		return 0;
 	}
 
@@ -127,15 +112,27 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
 	else
 		rev_list.no_stderr = opt->quiet;
 
-	if (start_command(&rev_list)) {
-		free(new_pack);
+	if (start_command(&rev_list))
 		return error(_("Could not run 'git rev-list'"));
-	}
 
 	sigchain_push(SIGPIPE, SIG_IGN);
 
 	rev_list_in = xfdopen(rev_list.in, "w");
 
+	if (transport && transport->smart_options &&
+	    transport->smart_options->self_contained_and_connected &&
+	    transport->pack_lockfiles.nr == 1 &&
+	    strip_suffix(transport->pack_lockfiles.items[0].string,
+			 ".keep", &base_len)) {
+		struct strbuf idx_file = STRBUF_INIT;
+		strbuf_add(&idx_file, transport->pack_lockfiles.items[0].string,
+			   base_len);
+		strbuf_addstr(&idx_file, ".idx");
+		new_pack = add_packed_git(the_repository, idx_file.buf,
+					  idx_file.len, 1);
+		strbuf_release(&idx_file);
+	}
+
 	do {
 		/*
 		 * If index-pack already checked that:
@@ -162,6 +159,9 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
 		err = error_errno(_("failed to close rev-list's stdin"));
 
 	sigchain_pop(SIGPIPE);
-	free(new_pack);
+	if (new_pack) {
+		close_pack(new_pack);
+		free(new_pack);
+	}
 	return finish_command(&rev_list) || err;
 }
diff --git a/contrib/Makefile b/contrib/Makefile
new file mode 100644
index 0000000..787cd07
--- /dev/null
+++ b/contrib/Makefile
@@ -0,0 +1,10 @@
+all::
+
+test::
+	$(MAKE) -C diff-highlight $@
+	$(MAKE) -C subtree $@
+
+clean::
+	$(MAKE) -C contacts $@
+	$(MAKE) -C diff-highlight $@
+	$(MAKE) -C subtree $@
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index edb0fc0..c6cfb87 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -274,6 +274,8 @@
 elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
 	add_compile_definitions(PROCFS_EXECUTABLE_PATH="/proc/self/exe" HAVE_DEV_TTY )
 	list(APPEND compat_SOURCES unix-socket.c unix-stream-server.c compat/linux/procinfo.c)
+elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+	list(APPEND compat_SOURCES compat/darwin/procinfo.c)
 endif()
 
 if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
@@ -411,10 +413,6 @@
 	list(APPEND compat_SOURCES compat/setenv.c)
 endif()
 
-if(NOT HAVE_MKDTEMP)
-	list(APPEND compat_SOURCES compat/mkdtemp.c)
-endif()
-
 if(NOT HAVE_PREAD)
 	list(APPEND compat_SOURCES compat/pread.c)
 endif()
@@ -679,18 +677,6 @@
 
 add_library(libgit ${libgit_SOURCES} ${compat_SOURCES})
 
-#libxdiff
-parse_makefile_for_sources(libxdiff_SOURCES ${CMAKE_SOURCE_DIR}/Makefile "XDIFF_OBJS")
-
-list(TRANSFORM libxdiff_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/")
-add_library(xdiff STATIC ${libxdiff_SOURCES})
-
-#reftable
-parse_makefile_for_sources(reftable_SOURCES ${CMAKE_SOURCE_DIR}/Makefile "REFTABLE_OBJS")
-
-list(TRANSFORM reftable_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/")
-add_library(reftable STATIC ${reftable_SOURCES})
-
 if(WIN32)
 	add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/git.rc
 			COMMAND "${SH_EXE}" "${CMAKE_SOURCE_DIR}/GIT-VERSION-GEN"
@@ -720,7 +706,7 @@
 #link all required libraries to common-main
 add_library(common-main OBJECT ${CMAKE_SOURCE_DIR}/common-main.c)
 
-target_link_libraries(common-main libgit xdiff reftable ${ZLIB_LIBRARIES})
+target_link_libraries(common-main libgit ${ZLIB_LIBRARIES})
 if(Intl_FOUND)
 	target_link_libraries(common-main ${Intl_LIBRARIES})
 endif()
diff --git a/contrib/coccinelle/array.cocci b/contrib/coccinelle/array.cocci
index 27a3b47..e71baea 100644
--- a/contrib/coccinelle/array.cocci
+++ b/contrib/coccinelle/array.cocci
@@ -101,3 +101,47 @@
 -ALLOC_ARRAY(dst, n);
 -COPY_ARRAY(dst, src, n);
 +DUP_ARRAY(dst, src, n);
+
+@@
+type T;
+T *ptr;
+expression n;
+@@
+- memset(ptr, \( 0 \| '\0' \), \( (n) \| n \) * \( sizeof(T)
+-                                               \| sizeof(ptr[...])
+-                                               \| sizeof(*ptr)
+-                                               \) )
++ MEMZERO_ARRAY(ptr, n)
+
+@@
+type T;
+T *ptr;
+expression n;
+@@
+- memset(ptr, \( 0 \| '\0' \), \( sizeof(T)
+-                              \| sizeof(ptr[...])
+-                              \| sizeof(*ptr)
+-                              \) * \( (n) \| n \) )
++ MEMZERO_ARRAY(ptr, n)
+
+@@
+type T;
+T[] ptr;
+expression n;
+@@
+- memset(ptr, \( 0 \| '\0' \), \( (n) \| n \) * \( sizeof(T)
+-                                               \| sizeof(ptr[...])
+-                                               \| sizeof(*ptr)
+-                                               \) )
++ MEMZERO_ARRAY(ptr, n)
+
+@@
+type T;
+T[] ptr;
+expression n;
+@@
+- memset(ptr, \( 0 \| '\0' \), \( sizeof(T)
+-                              \| sizeof(ptr[...])
+-                              \| sizeof(*ptr)
+-                              \) * \( (n) \| n \) )
++ MEMZERO_ARRAY(ptr, n)
diff --git a/contrib/coccinelle/commit.cocci b/contrib/coccinelle/commit.cocci
index c528460..4272516 100644
--- a/contrib/coccinelle/commit.cocci
+++ b/contrib/coccinelle/commit.cocci
@@ -26,7 +26,7 @@
 // repo_get_commit_tree() on the LHS.
 @@
 identifier f != { repo_get_commit_tree, get_commit_tree_in_graph_one,
-		  load_tree_for_commit, set_commit_tree };
+		  load_tree_for_commit, set_commit_tree, repo_parse_commit_no_graph };
 expression c;
 @@
   f(...) {<...
diff --git a/contrib/coccinelle/free.cocci b/contrib/coccinelle/free.cocci
index 6fb9eb6..03799e1 100644
--- a/contrib/coccinelle/free.cocci
+++ b/contrib/coccinelle/free.cocci
@@ -5,7 +5,7 @@
 (
   free(E);
 |
-  free_commit_list(E);
+  commit_list_free(E);
 )
 
 @@
@@ -15,7 +15,7 @@
 (
   free(E);
 |
-  free_commit_list(E);
+  commit_list_free(E);
 )
 
 @@
@@ -30,7 +30,7 @@
 @@
 - if (E)
 - {
-  free_commit_list(E);
+  commit_list_free(E);
   E = NULL;
 - }
 
@@ -41,5 +41,5 @@
 - if (E) {
 + if (E)
   S
-  free_commit_list(E);
+  commit_list_free(E);
 - }
diff --git a/contrib/coccinelle/meson.build b/contrib/coccinelle/meson.build
index dc3f73c..ae7f5b5 100644
--- a/contrib/coccinelle/meson.build
+++ b/contrib/coccinelle/meson.build
@@ -50,6 +50,11 @@
   coccinelle_headers += meson.project_source_root() / header
 endforeach
 
+coccinelle_includes = []
+foreach path : ['compat', 'ewah', 'refs', 'sha256', 'trace2', 'win32', 'xdiff']
+  coccinelle_includes += ['-I', meson.project_source_root() / path]
+endforeach
+
 patches = [ ]
 foreach source : coccinelle_sources
   patches += custom_target(
@@ -58,6 +63,7 @@
       '--all-includes',
       '--sp-file', concatenated_rules,
       '--patch', meson.project_source_root(),
+      coccinelle_includes,
       '@INPUT@',
     ],
     input: meson.project_source_root() / source,
diff --git a/contrib/coccinelle/the_repository.cocci b/contrib/coccinelle/the_repository.cocci
index ea7fe1c..f1129f7 100644
--- a/contrib/coccinelle/the_repository.cocci
+++ b/contrib/coccinelle/the_repository.cocci
@@ -2,121 +2,16 @@
 @@
 @@
 (
-// cache.h
-- get_oid
-+ repo_get_oid
+// TODO: remove the rules below and the macros from tree.h after the
+// next Git release.
+- parse_tree
++ repo_parse_tree
 |
-- get_oid_commit
-+ repo_get_oid_commit
+- parse_tree_gently
++ repo_parse_tree_gently
 |
-- get_oid_committish
-+ repo_get_oid_committish
-|
-- get_oid_tree
-+ repo_get_oid_tree
-|
-- get_oid_treeish
-+ repo_get_oid_treeish
-|
-- get_oid_blob
-+ repo_get_oid_blob
-|
-- get_oid_mb
-+ repo_get_oid_mb
-|
-- find_unique_abbrev
-+ repo_find_unique_abbrev
-|
-- find_unique_abbrev_r
-+ repo_find_unique_abbrev_r
-|
-- for_each_abbrev
-+ repo_for_each_abbrev
-|
-- interpret_branch_name
-+ repo_interpret_branch_name
-|
-- peel_to_type
-+ repo_peel_to_type
-// commit-reach.h
-|
-- get_merge_bases
-+ repo_get_merge_bases
-|
-- get_merge_bases_many
-+ repo_get_merge_bases_many
-|
-- get_merge_bases_many_dirty
-+ repo_get_merge_bases_many_dirty
-|
-- in_merge_bases
-+ repo_in_merge_bases
-|
-- in_merge_bases_many
-+ repo_in_merge_bases_many
-// commit.h
-|
-- parse_commit_internal
-+ repo_parse_commit_internal
-|
-- parse_commit
-+ repo_parse_commit
-|
-- get_commit_buffer
-+ repo_get_commit_buffer
-|
-- unuse_commit_buffer
-+ repo_unuse_commit_buffer
-|
-- logmsg_reencode
-+ repo_logmsg_reencode
-|
-- get_commit_tree
-+ repo_get_commit_tree
-// diff.h
-|
-- diff_setup
-+ repo_diff_setup
-// odb.h
-|
-- read_object_file
-+ repo_read_object_file
-|
-- has_object_file
-+ repo_has_object_file
-|
-- has_object_file_with_flags
-+ repo_has_object_file_with_flags
-// pretty.h
-|
-- format_commit_message
-+ repo_format_commit_message
-// packfile.h
-|
-- approximate_object_count
-+ repo_approximate_object_count
-// promisor-remote.h
-|
-- promisor_remote_reinit
-+ repo_promisor_remote_reinit
-|
-- promisor_remote_find
-+ repo_promisor_remote_find
-|
-- has_promisor_remote
-+ repo_has_promisor_remote
-// refs.h
-|
-- dwim_ref
-+ repo_dwim_ref
-// rerere.h
-|
-- rerere
-+ repo_rerere
-// revision.h
-|
-- init_revisions
-+ repo_init_revisions
+- parse_tree_indirect
++ repo_parse_tree_indirect
 )
   (
 + the_repository,
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 73abea3..a8e7c6d 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -13,7 +13,8 @@
 #    *) git email aliases for git-send-email
 #    *) tree paths within 'ref:path/to/file' expressions
 #    *) file paths within current working directory and index
-#    *) common --long-options
+#    *) common --long-options but not single-letter options
+#    *) arguments to long and single-letter options
 #
 # To use these routines:
 #
@@ -3464,7 +3465,7 @@
 
 _git_stash ()
 {
-	local subcommands='push list show apply clear drop pop create branch'
+	local subcommands='push list show apply clear drop pop create branch import export'
 	local subcommand="$(__git_find_on_cmdline "$subcommands save")"
 
 	if [ -z "$subcommand" ]; then
@@ -3490,6 +3491,9 @@
 	show,--*)
 		__gitcomp_builtin stash_show "$__git_diff_common_options"
 		;;
+	export,--*)
+		__gitcomp_builtin stash_export "--print --to-ref"
+		;;
 	*,--*)
 		__gitcomp_builtin "stash_$subcommand"
 		;;
@@ -3501,7 +3505,10 @@
 					| sed -n -e 's/:.*//p')"
 		fi
 		;;
-	show,*|apply,*|drop,*|pop,*)
+	import,*)
+		__git_complete_refs
+		;;
+	show,*|apply,*|drop,*|pop,*|export,*)
 		__gitcomp_nl "$(__git stash list \
 				| sed -n -e 's/:.*//p')"
 		;;
diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh
index f5877bd..c32186a 100644
--- a/contrib/completion/git-completion.zsh
+++ b/contrib/completion/git-completion.zsh
@@ -202,7 +202,7 @@
 __git_zsh_cmd_alias ()
 {
 	local -a list
-	list=(${${(0)"$(git config -z --get-regexp '^alias\.*')"}#alias.})
+	list=(${(0)"$(git help --aliases-for-completion)"})
 	list=(${(f)"$(printf "%s:alias for '%s'\n" ${(f@)list})"})
 	_describe -t alias-commands 'aliases' list && _ret=0
 }
diff --git a/contrib/credential/osxkeychain/Makefile b/contrib/credential/osxkeychain/Makefile
index 9680717..219b0d7 100644
--- a/contrib/credential/osxkeychain/Makefile
+++ b/contrib/credential/osxkeychain/Makefile
@@ -1,29 +1,13 @@
 # The default target of this Makefile is...
 all:: git-credential-osxkeychain
 
--include ../../../config.mak.autogen
--include ../../../config.mak
+git-credential-osxkeychain:
+	$(MAKE) -C ../../.. contrib/credential/osxkeychain/git-credential-osxkeychain
 
-prefix ?= /usr/local
-gitexecdir ?= $(prefix)/libexec/git-core
-
-CC ?= gcc
-CFLAGS ?= -g -O2 -Wall
-INSTALL ?= install
-RM ?= rm -f
-
-%.o: %.c
-	$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ -c $<
-
-git-credential-osxkeychain: git-credential-osxkeychain.o
-	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) \
-		-framework Security -framework CoreFoundation
-
-install: git-credential-osxkeychain
-	$(INSTALL) -d -m 755 $(DESTDIR)$(gitexecdir)
-	$(INSTALL) -m 755 $< $(DESTDIR)$(gitexecdir)
+install:
+	$(MAKE) -C ../../.. install-git-credential-osxkeychain
 
 clean:
-	$(RM) git-credential-osxkeychain git-credential-osxkeychain.o
+	$(MAKE) -C ../../.. clean-git-credential-osxkeychain
 
-.PHONY: all install clean
+.PHONY: all git-credential-osxkeychain install clean
diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
index 611c979..b180267 100644
--- a/contrib/credential/osxkeychain/git-credential-osxkeychain.c
+++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
@@ -2,6 +2,9 @@
 #include <string.h>
 #include <stdlib.h>
 #include <Security/Security.h>
+#include "git-compat-util.h"
+#include "strbuf.h"
+#include "wrapper.h"
 
 #define ENCODING kCFStringEncodingUTF8
 static CFStringRef protocol; /* Stores constant strings - not memory managed */
@@ -12,7 +15,7 @@ static CFStringRef username;
 static CFDataRef password;
 static CFDataRef password_expiry_utc;
 static CFDataRef oauth_refresh_token;
-static int state_seen;
+static char *state_seen;
 
 static void clear_credential(void)
 {
@@ -48,27 +51,6 @@ static void clear_credential(void)
 
 #define STRING_WITH_LENGTH(s) s, sizeof(s) - 1
 
-__attribute__((format (printf, 1, 2), __noreturn__))
-static void die(const char *err, ...)
-{
-	char msg[4096];
-	va_list params;
-	va_start(params, err);
-	vsnprintf(msg, sizeof(msg), err, params);
-	fprintf(stderr, "%s\n", msg);
-	va_end(params);
-	clear_credential();
-	exit(1);
-}
-
-static void *xmalloc(size_t len)
-{
-	void *ret = malloc(len);
-	if (!ret)
-		die("Out of memory");
-	return ret;
-}
-
 static CFDictionaryRef create_dictionary(CFAllocatorRef allocator, ...)
 {
 	va_list args;
@@ -112,6 +94,66 @@ static void write_item(const char *what, const char *buf, size_t len)
 	putchar('\n');
 }
 
+static void write_item_strbuf(struct strbuf *sb, const char *what, const char *buf, int n)
+{
+	char s[32];
+
+	xsnprintf(s, sizeof(s), "__%s=", what);
+	strbuf_add(sb, s, strlen(s));
+	strbuf_add(sb, buf, n);
+}
+
+static void write_item_strbuf_cfstring(struct strbuf *sb, const char *what, CFStringRef ref)
+{
+	char *buf;
+	int len;
+
+	if (!ref)
+		return;
+	len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(ref), ENCODING) + 1;
+	buf = xmalloc(len);
+	if (CFStringGetCString(ref, buf, len, ENCODING))
+		write_item_strbuf(sb, what, buf, strlen(buf));
+	free(buf);
+}
+
+static void write_item_strbuf_cfnumber(struct strbuf *sb, const char *what, CFNumberRef ref)
+{
+	short n;
+	char buf[32];
+
+	if (!ref)
+		return;
+	if (!CFNumberGetValue(ref, kCFNumberShortType, &n))
+		return;
+	xsnprintf(buf, sizeof(buf), "%d", n);
+	write_item_strbuf(sb, what, buf, strlen(buf));
+}
+
+static void write_item_strbuf_cfdata(struct strbuf *sb, const char *what, CFDataRef ref)
+{
+	char *buf;
+	int len;
+
+	if (!ref)
+		return;
+	buf = (char *)CFDataGetBytePtr(ref);
+	if (!buf || strlen(buf) == 0)
+		return;
+	len = CFDataGetLength(ref);
+	write_item_strbuf(sb, what, buf, len);
+}
+
+static void encode_state_seen(struct strbuf *sb)
+{
+	strbuf_add(sb, "osxkeychain:seen=", strlen("osxkeychain:seen="));
+	write_item_strbuf_cfstring(sb, "host", host);
+	write_item_strbuf_cfnumber(sb, "port", port);
+	write_item_strbuf_cfstring(sb, "path", path);
+	write_item_strbuf_cfstring(sb, "username", username);
+	write_item_strbuf_cfdata(sb, "password", password);
+}
+
 static void find_username_in_item(CFDictionaryRef item)
 {
 	CFStringRef account_ref;
@@ -124,6 +166,7 @@ static void find_username_in_item(CFDictionaryRef item)
 		write_item("username", "", 0);
 		return;
 	}
+	username = CFStringCreateCopy(kCFAllocatorDefault, account_ref);
 
 	username_buf = (char *)CFStringGetCStringPtr(account_ref, ENCODING);
 	if (username_buf)
@@ -163,6 +206,7 @@ static OSStatus find_internet_password(void)
 	}
 
 	data = CFDictionaryGetValue(item, kSecValueData);
+	password = CFDataCreateCopy(kCFAllocatorDefault, data);
 
 	write_item("password",
 		   (const char *)CFDataGetBytePtr(data),
@@ -173,7 +217,14 @@ static OSStatus find_internet_password(void)
 	CFRelease(item);
 
 	write_item("capability[]", "state", strlen("state"));
-	write_item("state[]", "osxkeychain:seen=1", strlen("osxkeychain:seen=1"));
+	{
+		struct strbuf sb;
+
+		strbuf_init(&sb, 1024);
+		encode_state_seen(&sb);
+		write_item("state[]", sb.buf, strlen(sb.buf));
+		strbuf_release(&sb);
+	}
 
 out:
 	CFRelease(attrs);
@@ -288,13 +339,22 @@ static OSStatus add_internet_password(void)
 	CFDictionaryRef attrs;
 	OSStatus result;
 
-	if (state_seen)
-		return errSecSuccess;
-
 	/* Only store complete credentials */
 	if (!protocol || !host || !username || !password)
 		return -1;
 
+	if (state_seen) {
+		struct strbuf sb;
+
+		strbuf_init(&sb, 1024);
+		encode_state_seen(&sb);
+		if (!strcmp(state_seen, sb.buf)) {
+			strbuf_release(&sb);
+			return errSecSuccess;
+		}
+		strbuf_release(&sb);
+	}
+
 	data = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, password);
 	if (password_expiry_utc) {
 		CFDataAppendBytes(data,
@@ -403,8 +463,9 @@ static void read_credential(void)
 							   (UInt8 *)v,
 							   strlen(v));
 		else if (!strcmp(buf, "state[]")) {
-			if (!strcmp(v, "osxkeychain:seen=1"))
-				state_seen = 1;
+			int len = strlen("osxkeychain:seen=");
+			if (!strncmp(v, "osxkeychain:seen=", len))
+				state_seen = xstrdup(v);
 		}
 		/*
 		 * Ignore other lines; we don't know what they mean, but
@@ -443,5 +504,8 @@ int main(int argc, const char **argv)
 
 	clear_credential();
 
+	if (state_seen)
+		free(state_seen);
+
 	return 0;
 }
diff --git a/contrib/credential/osxkeychain/meson.build b/contrib/credential/osxkeychain/meson.build
index 3c7677f..ec91d0c 100644
--- a/contrib/credential/osxkeychain/meson.build
+++ b/contrib/credential/osxkeychain/meson.build
@@ -1,6 +1,7 @@
 executable('git-credential-osxkeychain',
   sources: 'git-credential-osxkeychain.c',
   dependencies: [
+    libgit,
     dependency('CoreFoundation'),
     dependency('Security'),
   ],
diff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c
index 5683846..73c2b9b 100644
--- a/contrib/credential/wincred/git-credential-wincred.c
+++ b/contrib/credential/wincred/git-credential-wincred.c
@@ -165,7 +165,7 @@ static void get_credential(void)
 			write_item("username", creds[i]->UserName,
 				creds[i]->UserName ? wcslen(creds[i]->UserName) : 0);
 			if (creds[i]->CredentialBlobSize > 0) {
-				secret = xmalloc(creds[i]->CredentialBlobSize);
+				secret = xmalloc(creds[i]->CredentialBlobSize + sizeof(WCHAR));
 				wcsncpy_s(secret, creds[i]->CredentialBlobSize, (LPCWSTR)creds[i]->CredentialBlob, creds[i]->CredentialBlobSize / sizeof(WCHAR));
 				line = wcstok_s(secret, L"\r\n", &remaining_lines);
 				write_item("password", line, line ? wcslen(line) : 0);
diff --git a/contrib/diff-highlight/t/t9400-diff-highlight.sh b/contrib/diff-highlight/t/t9400-diff-highlight.sh
index f6f5195..dee2967 100755
--- a/contrib/diff-highlight/t/t9400-diff-highlight.sh
+++ b/contrib/diff-highlight/t/t9400-diff-highlight.sh
@@ -10,6 +10,8 @@
 CW="$(printf "\033[7m")"	# white
 CR="$(printf "\033[27m")"	# reset
 
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 . "$TEST_DIRECTORY"/test-lib.sh
 
 if ! test_have_prereq PERL
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index 17106d1..791fd82 100755
--- a/contrib/subtree/git-subtree.sh
+++ b/contrib/subtree/git-subtree.sh
@@ -257,6 +257,9 @@
 		test -e "$arg_prefix" &&
 			die "fatal: prefix '$arg_prefix' already exists."
 		;;
+	split)
+		# checked later against the commit, not the working tree
+		;;
 	*)
 		test -e "$arg_prefix" ||
 			die "fatal: '$arg_prefix' does not exist; use 'git subtree add'"
@@ -785,42 +788,6 @@
 		die "fatal: '$1' does not look like a ref"
 }
 
-# Usage: should_ignore_subtree_split_commit REV
-#
-# Check if REV is a commit from another subtree and should be
-# ignored from processing for splits
-should_ignore_subtree_split_commit () {
-	assert test $# = 1
-
-	git show \
-		--no-patch \
-		--no-show-signature \
-		--format='%(trailers:key=git-subtree-dir,key=git-subtree-mainline)' \
-		"$1" |
-	(
-	have_mainline=
-	subtree_dir=
-
-	while read -r trailer val
-	do
-		case "$trailer" in
-		git-subtree-dir:)
-			subtree_dir="${val%/}" ;;
-		git-subtree-mainline:)
-			have_mainline=y ;;
-		esac
-	done
-
-	if test -n "${subtree_dir}" &&
-		test -z "${have_mainline}" &&
-		test "${subtree_dir}" != "$arg_prefix"
-	then
-		return 0
-	fi
-	return 1
-	)
-}
-
 # Usage: process_split_commit REV PARENTS
 process_split_commit () {
 	assert test $# = 2
@@ -966,6 +933,12 @@
 	else
 		die "fatal: you must provide exactly one revision, and optionally a repository.  Got: '$*'"
 	fi
+
+	# Now validate prefix against the commit, not the working tree
+	if ! git cat-file -e "$rev:$dir" 2>/dev/null
+	then
+		die "fatal: '$dir' does not exist; use 'git subtree add'"
+	fi
 	repository=""
 	if test "$#" = 2
 	then
@@ -1006,19 +979,7 @@
 	eval "$grl" |
 	while read rev parents
 	do
-		if should_ignore_subtree_split_commit "$rev"
-		then
-			continue
-		fi
-		parsedparents=''
-		for parent in $parents
-		do
-			if ! should_ignore_subtree_split_commit "$parent"
-			then
-				parsedparents="$parsedparents$parent "
-			fi
-		done
-		process_split_commit "$rev" "$parsedparents"
+		process_split_commit "$rev" "$parents"
 	done || exit $?
 
 	latest_new=$(cache_get latest_new) || exit $?
diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh
index 316dc52..18d2b56 100755
--- a/contrib/subtree/t/t7900-subtree.sh
+++ b/contrib/subtree/t/t7900-subtree.sh
@@ -368,6 +368,28 @@
 	)
 '
 
+test_expect_success 'split works when prefix exists in commit but not in working tree' '
+	subtree_test_create_repo "$test_count" &&
+	(
+		cd "$test_count" &&
+
+		# create subtree
+		mkdir pkg &&
+		echo ok >pkg/file &&
+		git add pkg &&
+		git commit -m "add pkg" &&
+		good=$(git rev-parse HEAD) &&
+
+		# remove it from working tree in later commit
+		git rm -r pkg &&
+		git commit -m "remove pkg" &&
+
+		# must still be able to split using the old commit
+		git subtree split --prefix=pkg "$good" >out &&
+		test -s out
+	)
+'
+
 test_expect_success 'split rejects flags for add' '
 	subtree_test_create_repo "$test_count" &&
 	subtree_test_create_repo "$test_count/sub proj" &&
@@ -411,8 +433,9 @@
 		git fetch ./"sub proj" HEAD &&
 		git subtree merge --prefix="sub dir" FETCH_HEAD &&
 		split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
-		git subtree split --prefix="sub dir" --annotate="*" --rejoin &&
-		test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$split_hash'\''"
+		git subtree split --prefix="sub dir" --annotate="*" -b spl --rejoin &&
+		test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$split_hash'\''" &&
+		test "$(git rev-list --count spl)" -eq 5
 	)
 '
 
@@ -427,8 +450,7 @@
 # 	- Perform 'split' on subtree B
 # 	- Create new commits with changes to subtree A and B
 # 	- Perform split on subtree A
-# 	- Check that the commits in subtree B are not processed
-#			as part of the subtree A split
+# 	- Check for expected history
 test_expect_success 'split with multiple subtrees' '
 	subtree_test_create_repo "$test_count" &&
 	subtree_test_create_repo "$test_count/subA" &&
@@ -442,18 +464,25 @@
 	git -C "$test_count" subtree add --prefix=subADir FETCH_HEAD &&
 	git -C "$test_count" fetch ./subB HEAD &&
 	git -C "$test_count" subtree add --prefix=subBDir FETCH_HEAD &&
+	test "$(git -C "$test_count" rev-list --count main)" -eq 7 &&
 	test_create_commit "$test_count" subADir/main-subA1 &&
 	test_create_commit "$test_count" subBDir/main-subB1 &&
 	git -C "$test_count" subtree split --prefix=subADir \
-		--squash --rejoin -m "Sub A Split 1" &&
+		--squash --rejoin -m "Sub A Split 1" -b a1 &&
+	test "$(git -C "$test_count" rev-list --count main..a1)" -eq 1 &&
 	git -C "$test_count" subtree split --prefix=subBDir \
-		--squash --rejoin -m "Sub B Split 1" &&
+		--squash --rejoin -m "Sub B Split 1" -b b1 &&
+	test "$(git -C "$test_count" rev-list --count main..b1)" -eq 1 &&
 	test_create_commit "$test_count" subADir/main-subA2 &&
 	test_create_commit "$test_count" subBDir/main-subB2 &&
 	git -C "$test_count" subtree split --prefix=subADir \
-		--squash --rejoin -m "Sub A Split 2" &&
-	test "$(git -C "$test_count" subtree split --prefix=subBDir \
-		--squash --rejoin -d -m "Sub B Split 1" 2>&1 | grep -w "\[1\]")" = ""
+		--squash --rejoin -m "Sub A Split 2" -b a2 &&
+	test "$(git -C "$test_count" rev-list --count main..a2)" -eq 2 &&
+	test "$(git -C "$test_count" rev-list --count a1..a2)" -eq 1 &&
+	git -C "$test_count" subtree split --prefix=subBDir \
+		--squash --rejoin -d -m "Sub B Split 1" -b b2 &&
+	test "$(git -C "$test_count" rev-list --count main..b2)" -eq 2 &&
+	test "$(git -C "$test_count" rev-list --count b1..b2)" -eq 1
 '
 
 # When subtree split-ing a directory that has other subtree
@@ -477,6 +506,7 @@
 			test_path_is_file subA/file1.t &&
 			test_path_is_file subA/subB/file2.t &&
 			git subtree split --prefix=subA --branch=bsplit &&
+			test "$(git rev-list --count bsplit)" -eq 2 &&
 			git checkout bsplit &&
 			test_path_is_file file1.t &&
 			test_path_is_file subB/file2.t &&
@@ -489,6 +519,7 @@
 				--prefix=subA/subB mksubtree &&
 			test_path_is_file subA/subB/file3.t &&
 			git subtree split --prefix=subA --branch=bsplit &&
+			test "$(git rev-list --count bsplit)" -eq 3 &&
 			git checkout bsplit &&
 			test_path_is_file file1.t &&
 			test_path_is_file subB/file2.t &&
@@ -497,6 +528,67 @@
 	'
 done
 
+# Usually,
+#
+#    git subtree merge -P subA --squash f00...
+#
+# makes two commits, in this order:
+#
+# 1. Squashed 'subA/' content from commit f00...
+# 2. Merge commit (1) as 'subA'
+#
+# Commit 1 updates the subtree but does *not* rewrite paths.
+# Commit 2 rewrites all trees to start with `subA/`
+#
+# Commit 1 either has no parents or depends only on other
+# "Squashed 'subA/' content" commits.
+#
+# For merge without --squash, subtree produces just one commit:
+# a merge commit with git-subtree trailers.
+#
+# In either case, if the user rebases these commits, they will
+# still have the git-subtree-* trailers… but will NOT have
+# the layout described above.
+#
+# Test that subsequent `git subtree split` are not confused by this.
+test_expect_success 'split with rebased subtree commit' '
+	subtree_test_create_repo "$test_count" &&
+	(
+		cd "$test_count" &&
+		test_commit file0 &&
+		test_create_subtree_add \
+			. mksubtree subA file1 --squash &&
+		test_path_is_file subA/file1.t &&
+		mkdir subB &&
+		test_commit subB/bfile &&
+		git commit --amend -F - <<'EOF' &&
+Squashed '\''subB/'\'' content from commit '\''badf00da911bbe895347b4b236f5461d55dc9877'\''
+
+Simulate a cherry-picked or rebased subtree commit.
+
+git-subtree-dir: subB
+git-subtree-split: badf00da911bbe895347b4b236f5461d55dc9877
+EOF
+		test_commit subA/file2 &&
+		test_commit subB/bfile2 &&
+		git commit --amend -F - <<'EOF' &&
+Split '\''subB/'\'' into commit '\''badf00da911bbe895347b4b236f5461d55dc9877'\''
+
+Simulate a cherry-picked or rebased subtree commit.
+
+git-subtree-dir: subB
+git-subtree-mainline: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+git-subtree-split: badf00da911bbe895347b4b236f5461d55dc9877
+EOF
+		git subtree split --prefix=subA --branch=bsplit &&
+		git checkout bsplit &&
+		test_path_is_file file1.t &&
+		test_path_is_file file2.t &&
+		test "$(last_commit_subject)" = "subA/file2" &&
+		test "$(git rev-list --count bsplit)" -eq 2
+	)
+'
+
 test_expect_success 'split sub dir/ with --rejoin from scratch' '
 	subtree_test_create_repo "$test_count" &&
 	test_create_commit "$test_count" main1 &&
@@ -1575,6 +1667,116 @@
 	)
 '
 
+# --ignore-joins must ignore mainline content outside of the
+# subtree. This test verifies that the logic in
+# `find_existing_splits()` correctly handles a `git subtree add`
+# In this test, the split history must not contain a commit titled
+#
+#     Add 'sub/' from commit ...
+#
+# see: dd21d43b58 (subtree: make --ignore-joins pay
+#      attention to adds, 2018-09-28)
+test_expect_success 'split --ignore-joins respects subtree add' '
+	subtree_test_create_repo "$test_count" &&
+	(
+		cd "$test_count" &&
+		test_commit main_must_not_be_in_subtree &&
+		test_create_subtree_add . mksubtree sub sub1 &&
+		test_commit sub/sub2 &&
+		test_commit main_must_not_be_in_subtree2 &&
+		git subtree split --prefix sub -b first_split --rejoin &&
+		test_commit sub/sub3 &&
+		no_ignore_joins="$(git subtree split --prefix sub -b no_ignore_joins)" &&
+		ignore_joins="$(git subtree split --prefix sub --ignore-joins -b ignore_joins)" &&
+		git checkout ignore_joins &&
+		test_path_is_file sub1.t &&
+		test_path_is_file sub2.t &&
+		test_path_is_file sub3.t &&
+		! test_path_is_file main_must_not_be_in_subtree.t &&
+		! test_path_is_file main_must_not_be_in_subtree2.t &&
+		test -z "$(git log -1 --grep "Add '''sub/''' from commit" ignore_joins)" &&
+		test "$no_ignore_joins" = "$ignore_joins" &&
+		test "$(git rev-list --count ignore_joins)" -eq 3;
+	)
+'
+
+# split excludes commits reachable from any previous --rejoin.
+# These ignored commits can still be the basis for new work
+# after the --rejoin. These commits must be processed, even
+# if they are excluded. Otherwise, the split history will be
+# incorrect.
+#
+# here, the merge
+#
+#     git merge --no-ff new_work_based_on_prejoin
+#
+# doesn't contain any subtree changes and so should not end
+# up in the split history. this subtree should be flat,
+# with no merges.
+#
+# see: 315a84f9aa (subtree: use commits before rejoins for
+#      splits, 2018-09-28)
+test_expect_success 'split links out-of-tree pre --rejoin commits with post --rejoin commits' '
+	subtree_test_create_repo "$test_count" &&
+	(
+		cd "$test_count" &&
+		test_commit main_must_not_be_in_subtree &&
+		mkdir sub &&
+		test_commit sub/sub1 &&
+		test_commit sub/sub2 &&
+		git subtree split --prefix sub --rejoin &&
+		test "$(git rev-list --count HEAD)" -eq 6 &&
+		git checkout sub/sub1 &&
+		git checkout -b new_work_based_on_prejoin &&
+		test_commit main_must_not_be_in_subtree2 &&
+		git checkout main &&
+		git merge --no-ff new_work_based_on_prejoin &&
+		test_commit sub/sub3 &&
+		git subtree split -d --prefix sub -b second_split &&
+		git checkout second_split &&
+		test_path_is_file sub1.t &&
+		test_path_is_file sub2.t &&
+		test_path_is_file sub3.t &&
+		! test_path_is_file main_must_not_be_in_subtree.t &&
+		! test_path_is_file main_must_not_be_in_subtree2.t &&
+		test "$(git rev-list --count --merges second_split)" -eq 0 &&
+		test "$(git rev-list --count second_split)" -eq 3;
+	)
+'
+
+# split must keep merge commits with unrelated histories, even
+# if both parents are treesame. When deciding whether or not
+# to eliminate a parent, copy_or_skip compares the merge-base
+# of each parent.
+#
+# in the split_of_merges branch:
+#
+#   * expect 4 commits
+#   * HEAD~ must be a merge
+#
+# see: 68f8ff8151 (subtree: improve decision on merges kept
+#      in split, 2018-09-28)
+test_expect_success 'split preserves merges with unrelated history' '
+	subtree_test_create_repo "$test_count" &&
+	(
+		cd "$test_count" &&
+		test_commit main_must_not_be_in_subtree &&
+		mkdir sub &&
+		test_commit sub/sub1 &&
+		git checkout --orphan new_history &&
+		git checkout sub/sub1 -- . &&
+		git add . &&
+		git commit -m "treesame history but not a merge-base" &&
+		git checkout main &&
+		git merge --allow-unrelated-histories --no-ff new_history &&
+		test "$(git rev-parse "HEAD^1^{tree}")" = "$(git rev-parse "HEAD^2^{tree}")" &&
+		test_commit sub/sub2 &&
+		git subtree split -d --prefix sub -b split_of_merges &&
+		test "$(git rev-list --count split_of_merges)" -eq 4 &&
+		test -n "$(git rev-list --merges HEAD~)";
+	)
+'
+
 #
 # This test covers 2 cases in subtree split copy_or_skip code
 # 1) Merges where one parent is a superset of the changes of the other
@@ -1597,7 +1799,6 @@
 
 test_expect_success 'subtree descendant check' '
 	subtree_test_create_repo "$test_count" &&
-	defaultBranch=$(sed "s,ref: refs/heads/,," "$test_count/.git/HEAD") &&
 	test_create_commit "$test_count" folder_subtree/a &&
 	(
 		cd "$test_count" &&
@@ -1614,7 +1815,7 @@
 	(
 		cd "$test_count" &&
 		git cherry-pick $cherry &&
-		git checkout $defaultBranch &&
+		git checkout main &&
 		git merge -m "merge should be kept on subtree" branch &&
 		git branch no_subtree_work_branch
 	) &&
@@ -1626,10 +1827,10 @@
 	test_create_commit "$test_count" not_a_subtree_change &&
 	(
 		cd "$test_count" &&
-		git checkout $defaultBranch &&
+		git checkout main &&
 		git merge -m "merge should be skipped on subtree" no_subtree_work_branch &&
 
-		git subtree split --prefix folder_subtree/ --branch subtree_tip $defaultBranch &&
+		git subtree split --prefix folder_subtree/ --branch subtree_tip main &&
 		git subtree split --prefix folder_subtree/ --branch subtree_branch branch &&
 		test $(git rev-list --count subtree_tip..subtree_branch) = 0
 	)
diff --git a/convert.c b/convert.c
index c7d6a85..a34ec6e 100644
--- a/convert.c
+++ b/convert.c
@@ -1122,7 +1122,8 @@ static int count_ident(const char *cp, unsigned long size)
 static int ident_to_git(const char *src, size_t len,
 			struct strbuf *buf, int ident)
 {
-	char *dst, *dollar;
+	char *dst;
+	const char *dollar;
 
 	if (!ident || (src && !count_ident(src, len)))
 		return 0;
diff --git a/csum-file.c b/csum-file.c
index 6e21e3c..3d3047c 100644
--- a/csum-file.c
+++ b/csum-file.c
@@ -110,7 +110,7 @@ void discard_hashfile(struct hashfile *f)
 	free_hashfile(f);
 }
 
-void hashwrite(struct hashfile *f, const void *buf, unsigned int count)
+void hashwrite(struct hashfile *f, const void *buf, uint32_t count)
 {
 	while (count) {
 		unsigned left = f->buffer_len - f->offset;
diff --git a/csum-file.h b/csum-file.h
index 07ae110..ecce9d2 100644
--- a/csum-file.h
+++ b/csum-file.h
@@ -63,7 +63,7 @@ void free_hashfile(struct hashfile *f);
  */
 int finalize_hashfile(struct hashfile *, unsigned char *, enum fsync_component, unsigned int);
 void discard_hashfile(struct hashfile *);
-void hashwrite(struct hashfile *, const void *, unsigned int);
+void hashwrite(struct hashfile *, const void *, uint32_t);
 void hashflush(struct hashfile *f);
 void crc32_begin(struct hashfile *);
 uint32_t crc32_end(struct hashfile *);
diff --git a/delta-islands.c b/delta-islands.c
index 36c9479..f4d2468 100644
--- a/delta-islands.c
+++ b/delta-islands.c
@@ -283,7 +283,7 @@ void resolve_tree_islands(struct repository *r,
 		root_marks = kh_value(island_marks, pos);
 
 		tree = lookup_tree(r, &ent->idx.oid);
-		if (!tree || parse_tree(tree) < 0)
+		if (!tree || repo_parse_tree(r, tree) < 0)
 			die(_("bad tree object %s"), oid_to_hex(&ent->idx.oid));
 
 		init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size);
@@ -390,8 +390,7 @@ static void add_ref_to_island(kh_str_t *remote_islands, const char *island_name,
 	rl->hash += sha_core;
 }
 
-static int find_island_for_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-			       int flags UNUSED, void *cb)
+static int find_island_for_ref(const struct reference *ref, void *cb)
 {
 	struct island_load_data *ild = cb;
 
@@ -406,7 +405,7 @@ static int find_island_for_ref(const char *refname, const char *referent UNUSED,
 
 	/* walk backwards to get last-one-wins ordering */
 	for (i = ild->nr - 1; i >= 0; i--) {
-		if (!regexec(&ild->rx[i], refname,
+		if (!regexec(&ild->rx[i], ref->name,
 			     ARRAY_SIZE(matches), matches, 0))
 			break;
 	}
@@ -428,10 +427,10 @@ static int find_island_for_ref(const char *refname, const char *referent UNUSED,
 		if (island_name.len)
 			strbuf_addch(&island_name, '-');
 
-		strbuf_add(&island_name, refname + match->rm_so, match->rm_eo - match->rm_so);
+		strbuf_add(&island_name, ref->name + match->rm_so, match->rm_eo - match->rm_so);
 	}
 
-	add_ref_to_island(ild->remote_islands, island_name.buf, oid);
+	add_ref_to_island(ild->remote_islands, island_name.buf, ref->oid);
 	strbuf_release(&island_name);
 	return 0;
 }
diff --git a/diff-delta.c b/diff-delta.c
index 71d3736..43c339f 100644
--- a/diff-delta.c
+++ b/diff-delta.c
@@ -171,7 +171,7 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
 	mem = hash + hsize;
 	entry = mem;
 
-	memset(hash, 0, hsize * sizeof(*hash));
+	MEMZERO_ARRAY(hash, hsize);
 
 	/* allocate an array to count hash entries */
 	hash_count = calloc(hsize, sizeof(*hash_count));
diff --git a/diff-lib.c b/diff-lib.c
index b8f8f3b..ae91027 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -226,8 +226,12 @@ void run_diff_files(struct rev_info *revs, unsigned int option)
 				continue;
 		}
 
-		if (ce_uptodate(ce) || ce_skip_worktree(ce))
+		if (ce_uptodate(ce) || ce_skip_worktree(ce)) {
+			if (revs->diffopt.flags.find_copies_harder)
+				diff_same(&revs->diffopt, ce->ce_mode,
+					  &ce->oid, ce->name);
 			continue;
+		}
 
 		/*
 		 * When CE_VALID is set (via "update-index --assume-unchanged"
@@ -272,8 +276,10 @@ void run_diff_files(struct rev_info *revs, unsigned int option)
 		if (!changed && !dirty_submodule) {
 			ce_mark_uptodate(ce);
 			mark_fsmonitor_valid(istate, ce);
-			if (!revs->diffopt.flags.find_copies_harder)
-				continue;
+			if (revs->diffopt.flags.find_copies_harder)
+				diff_same(&revs->diffopt, newmode,
+					  &ce->oid, ce->name);
+			continue;
 		}
 		oldmode = ce->ce_mode;
 		old_oid = &ce->oid;
@@ -418,13 +424,12 @@ static int show_modified(struct rev_info *revs,
 	}
 
 	oldmode = old_entry->ce_mode;
-	if (mode == oldmode && oideq(oid, &old_entry->oid) && !dirty_submodule &&
-	    !revs->diffopt.flags.find_copies_harder)
-		return 0;
-
-	diff_change(&revs->diffopt, oldmode, mode,
-		    &old_entry->oid, oid, 1, !is_null_oid(oid),
-		    old_entry->name, 0, dirty_submodule);
+	if (mode != oldmode || !oideq(oid, &old_entry->oid) || dirty_submodule)
+		diff_change(&revs->diffopt, oldmode, mode,
+			    &old_entry->oid, oid, 1, !is_null_oid(oid),
+			    old_entry->name, 0, dirty_submodule);
+	else if (revs->diffopt.flags.find_copies_harder)
+		diff_same(&revs->diffopt, mode, oid, old_entry->name);
 	return 0;
 }
 
@@ -547,7 +552,7 @@ static int diff_cache(struct rev_info *revs,
 	struct tree_desc t;
 	struct unpack_trees_options opts;
 
-	tree = parse_tree_indirect(tree_oid);
+	tree = repo_parse_tree_indirect(the_repository, tree_oid);
 	if (!tree)
 		return error("bad tree object %s",
 			     tree_name ? tree_name : oid_to_hex(tree_oid));
@@ -610,7 +615,7 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb)
 
 	oidcpy(mb, &merge_bases->item->object.oid);
 
-	free_commit_list(merge_bases);
+	commit_list_free(merge_bases);
 }
 
 void run_diff_index(struct rev_info *revs, unsigned int option)
diff --git a/diff.c b/diff.c
index a196152..e87847f 100644
--- a/diff.c
+++ b/diff.c
@@ -601,6 +601,7 @@ struct emit_callback {
 	int blank_at_eof_in_postimage;
 	int lno_in_preimage;
 	int lno_in_postimage;
+	int last_line_kind;
 	const char **label_path;
 	struct diff_words_data *diff_words;
 	struct diff_options *opt;
@@ -796,21 +797,23 @@ enum diff_symbol {
 	DIFF_SYMBOL_CONTEXT_INCOMPLETE,
 	DIFF_SYMBOL_PLUS,
 	DIFF_SYMBOL_MINUS,
-	DIFF_SYMBOL_NO_LF_EOF,
 	DIFF_SYMBOL_CONTEXT_FRAGINFO,
 	DIFF_SYMBOL_CONTEXT_MARKER,
 	DIFF_SYMBOL_SEPARATOR
 };
+
 /*
  * Flags for content lines:
- * 0..12 are whitespace rules
- * 13-15 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT
- * 16 is marking if the line is blank at EOF
+ * 0..15 are whitespace rules (see ws.h)
+ * 16..18 are WSEH_NEW | WSEH_CONTEXT | WSEH_OLD
+ * 19 is marking if the line is blank at EOF
+ * 20..22 are used for color-moved.
  */
-#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF	(1<<16)
-#define DIFF_SYMBOL_MOVED_LINE			(1<<17)
-#define DIFF_SYMBOL_MOVED_LINE_ALT		(1<<18)
-#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING	(1<<19)
+#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF	(1<<19)
+#define DIFF_SYMBOL_MOVED_LINE			(1<<20)
+#define DIFF_SYMBOL_MOVED_LINE_ALT		(1<<21)
+#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING	(1<<22)
+
 #define DIFF_SYMBOL_CONTENT_WS_MASK (WSEH_NEW | WSEH_OLD | WSEH_CONTEXT | WS_RULE_MASK)
 
 /*
@@ -1318,20 +1321,25 @@ static void emit_line_ws_markup(struct diff_options *o,
 	const char *ws = NULL;
 	int sign = o->output_indicators[sign_index];
 
+	if (diff_suppress_blank_empty &&
+	    sign_index == OUTPUT_INDICATOR_CONTEXT &&
+	    len == 1 && line[0] == '\n')
+		sign = 0;
+
 	if (o->ws_error_highlight & ws_rule) {
 		ws = diff_get_color_opt(o, DIFF_WHITESPACE);
 		if (!*ws)
 			ws = NULL;
 	}
 
-	if (!ws && !set_sign)
+	if (!ws && !set_sign) {
 		emit_line_0(o, set, NULL, 0, reset, sign, line, len);
-	else if (!ws) {
+	} else if (!ws) {
 		emit_line_0(o, set_sign, set, !!set_sign, reset, sign, line, len);
-	} else if (blank_at_eof)
+	} else if (blank_at_eof) {
 		/* Blank line at EOF - paint '+' as well */
 		emit_line_0(o, ws, NULL, 0, reset, sign, line, len);
-	else {
+	} else {
 		/* Emit just the prefix, then the rest. */
 		emit_line_0(o, set_sign ? set_sign : set, NULL, !!set_sign, reset,
 			    sign, "", 0);
@@ -1343,7 +1351,6 @@ static void emit_line_ws_markup(struct diff_options *o,
 static void emit_diff_symbol_from_struct(struct diff_options *o,
 					 struct emitted_diff_symbol *eds)
 {
-	static const char *nneof = " No newline at end of file\n";
 	const char *context, *reset, *set, *set_sign, *meta, *fraginfo;
 
 	enum diff_symbol s = eds->s;
@@ -1355,13 +1362,6 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 		return;
 
 	switch (s) {
-	case DIFF_SYMBOL_NO_LF_EOF:
-		context = diff_get_color_opt(o, DIFF_CONTEXT);
-		reset = diff_get_color_opt(o, DIFF_RESET);
-		putc('\n', o->file);
-		emit_line_0(o, context, NULL, 0, reset, '\\',
-			    nneof, strlen(nneof));
-		break;
 	case DIFF_SYMBOL_SUBMODULE_HEADER:
 	case DIFF_SYMBOL_SUBMODULE_ERROR:
 	case DIFF_SYMBOL_SUBMODULE_PIPETHROUGH:
@@ -1373,6 +1373,14 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 		emit_line(o, "", "", line, len);
 		break;
 	case DIFF_SYMBOL_CONTEXT_INCOMPLETE:
+		if ((flags & WS_INCOMPLETE_LINE) &&
+		    (flags & o->ws_error_highlight))
+			set = diff_get_color_opt(o, DIFF_WHITESPACE);
+		else
+			set = diff_get_color_opt(o, DIFF_CONTEXT);
+		reset = diff_get_color_opt(o, DIFF_RESET);
+		emit_line(o, set, reset, line, len);
+		break;
 	case DIFF_SYMBOL_CONTEXT_MARKER:
 		context = diff_get_color_opt(o, DIFF_CONTEXT);
 		reset = diff_get_color_opt(o, DIFF_RESET);
@@ -1498,15 +1506,9 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 	case DIFF_SYMBOL_WORDS:
 		context = diff_get_color_opt(o, DIFF_CONTEXT);
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		/*
-		 * Skip the prefix character, if any.  With
-		 * diff_suppress_blank_empty, there may be
-		 * none.
-		 */
-		if (line[0] != '\n') {
-			line++;
-			len--;
-		}
+
+		/* Skip the prefix character */
+		line++; len--;
 		emit_line(o, context, reset, line, len);
 		break;
 	case DIFF_SYMBOL_FILEPAIR_PLUS:
@@ -1668,6 +1670,19 @@ static void emit_context_line(struct emit_callback *ecbdata,
 	emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_CONTEXT, line, len, flags);
 }
 
+static void emit_incomplete_line_marker(struct emit_callback *ecbdata,
+					const char *line, int len)
+{
+	int last_line_kind = ecbdata->last_line_kind;
+	unsigned flags = (last_line_kind == '+'
+			  ? WSEH_NEW
+			  : last_line_kind == '-'
+			  ? WSEH_OLD
+			  : WSEH_CONTEXT) | ecbdata->ws_rule;
+	emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_CONTEXT_INCOMPLETE,
+			 line, len, flags);
+}
+
 static void emit_hunk_header(struct emit_callback *ecbdata,
 			     const char *line, int len)
 {
@@ -1769,28 +1784,44 @@ static void add_line_count(struct strbuf *out, int count)
 	}
 }
 
-static void emit_rewrite_lines(struct emit_callback *ecb,
+static void emit_rewrite_lines(struct emit_callback *ecbdata,
 			       int prefix, const char *data, int size)
 {
 	const char *endp = NULL;
 
 	while (0 < size) {
-		int len;
+		int len, plen;
+		char *pdata = NULL;
 
 		endp = memchr(data, '\n', size);
-		len = endp ? (endp - data + 1) : size;
-		if (prefix != '+') {
-			ecb->lno_in_preimage++;
-			emit_del_line(ecb, data, len);
+
+		if (endp) {
+			len = endp - data + 1;
+			plen = len;
 		} else {
-			ecb->lno_in_postimage++;
-			emit_add_line(ecb, data, len);
+			len = size;
+			plen = len + 1;
+			pdata = xmalloc(plen + 2);
+			memcpy(pdata, data, len);
+			pdata[len] = '\n';
+			pdata[len + 1] = '\0';
 		}
+		if (prefix != '+') {
+			ecbdata->lno_in_preimage++;
+			emit_del_line(ecbdata, pdata ? pdata : data, plen);
+		} else {
+			ecbdata->lno_in_postimage++;
+			emit_add_line(ecbdata, pdata ? pdata : data, plen);
+		}
+		free(pdata);
 		size -= len;
 		data += len;
 	}
-	if (!endp)
-		emit_diff_symbol(ecb->opt, DIFF_SYMBOL_NO_LF_EOF, NULL, 0, 0);
+	if (!endp) {
+		static const char nneof[] = "\\ No newline at end of file\n";
+		ecbdata->last_line_kind = prefix;
+		emit_incomplete_line_marker(ecbdata, nneof, sizeof(nneof) - 1);
+	}
 }
 
 static void emit_rewrite_diff(const char *name_a,
@@ -1806,6 +1837,7 @@ static void emit_rewrite_diff(const char *name_a,
 	const char *a_prefix, *b_prefix;
 	char *data_one, *data_two;
 	size_t size_one, size_two;
+	unsigned ws_rule;
 	struct emit_callback ecbdata;
 	struct strbuf out = STRBUF_INIT;
 
@@ -1828,9 +1860,15 @@ static void emit_rewrite_diff(const char *name_a,
 	size_one = fill_textconv(o->repo, textconv_one, one, &data_one);
 	size_two = fill_textconv(o->repo, textconv_two, two, &data_two);
 
+	ws_rule = whitespace_rule(o->repo->index, name_b);
+
+	/* symlink being an incomplete line is not a news */
+	if (DIFF_FILE_VALID(two) && S_ISLNK(two->mode))
+		ws_rule &= ~WS_INCOMPLETE_LINE;
+
 	memset(&ecbdata, 0, sizeof(ecbdata));
 	ecbdata.color_diff = o->use_color;
-	ecbdata.ws_rule = whitespace_rule(o->repo->index, name_b);
+	ecbdata.ws_rule = ws_rule;
 	ecbdata.opt = o;
 	if (ecbdata.ws_rule & WS_BLANK_AT_EOF) {
 		mmfile_t mf1, mf2;
@@ -1930,7 +1968,7 @@ static int fn_out_diff_words_write_helper(struct diff_options *o,
 	struct strbuf sb = STRBUF_INIT;
 
 	while (count) {
-		char *p = memchr(buf, '\n', count);
+		const char *p = memchr(buf, '\n', count);
 		if (print)
 			strbuf_addstr(&sb, diff_line_prefix(o));
 
@@ -2375,12 +2413,6 @@ static int fn_out_consume(void *priv, char *line, unsigned long len)
 		ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
 	}
 
-	if (diff_suppress_blank_empty
-	    && len == 2 && line[0] == ' ' && line[1] == '\n') {
-		line[0] = '\n';
-		len = 1;
-	}
-
 	if (line[0] == '@') {
 		if (ecbdata->diff_words)
 			diff_words_flush(ecbdata);
@@ -2431,13 +2463,24 @@ static int fn_out_consume(void *priv, char *line, unsigned long len)
 		ecbdata->lno_in_preimage++;
 		emit_context_line(ecbdata, line + 1, len - 1);
 		break;
-	default:
+	case '\\':
 		/* incomplete line at the end */
+		switch (ecbdata->last_line_kind) {
+		case '+':
+		case '-':
+		case ' ':
+			break;
+		default:
+			BUG("fn_out_consume: '\\No newline' after unknown line (%c)",
+			    ecbdata->last_line_kind);
+		}
 		ecbdata->lno_in_preimage++;
-		emit_diff_symbol(o, DIFF_SYMBOL_CONTEXT_INCOMPLETE,
-				 line, len, 0);
+		emit_incomplete_line_marker(ecbdata, line, len);
 		break;
+	default:
+		BUG("fn_out_consume: unknown line '%s'", line);
 	}
+	ecbdata->last_line_kind = line[0];
 	return 0;
 }
 
@@ -2713,7 +2756,9 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
 	count = i; /* where we can stop scanning in data->files[] */
 
 	/*
-	 * We have width = stat_width or term_columns() columns total.
+	 * We have width = stat_width or term_columns() columns total minus the
+	 * length of line_prefix skipping ANSI escape codes to get the display
+	 * width (e.g., skip ANSI-colored strings in "log --graph --stat").
 	 * We want a maximum of min(max_len, stat_name_width) for the name part.
 	 * We want a maximum of min(max_change, stat_graph_width) for the +- part.
 	 * We also need 1 for " " and 4 + decimal_width(max_change)
@@ -2740,14 +2785,8 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
 	 * separators and this message, this message will "overflow"
 	 * making the line longer than the maximum width.
 	 */
-
-	/*
-	 * NEEDSWORK: line_prefix is often used for "log --graph" output
-	 * and contains ANSI-colored string.  utf8_strnwidth() should be
-	 * used to correctly count the display width instead of strlen().
-	 */
 	if (options->stat_width == -1)
-		width = term_columns() - strlen(line_prefix);
+		width = term_columns() - utf8_strnwidth(line_prefix, strlen(line_prefix), 1);
 	else
 		width = options->stat_width ? options->stat_width : 80;
 	number_width = decimal_width(max_change) > number_width ?
@@ -2823,17 +2862,12 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
 			char *slash;
 			prefix = "...";
 			len -= 3;
-			/*
-			 * NEEDSWORK: (name_len - len) counts the display
-			 * width, which would be shorter than the byte
-			 * length of the corresponding substring.
-			 * Advancing "name" by that number of bytes does
-			 * *NOT* skip over that many columns, so it is
-			 * very likely that chomping the pathname at the
-			 * slash we will find starting from "name" will
-			 * leave the resulting string still too long.
-			 */
-			name += name_len - len;
+			if (len < 0)
+				len = 0;
+
+			while (name_len > len)
+				name_len -= utf8_width((const char**)&name, NULL);
+
 			slash = strchr(name, '/');
 			if (slash)
 				name = slash;
@@ -3013,7 +3047,7 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir,
 		struct dirstat_file *f = dir->files;
 		int namelen = strlen(f->name);
 		unsigned long changes;
-		char *slash;
+		const char *slash;
 
 		if (namelen < baselen)
 			break;
@@ -3231,6 +3265,7 @@ struct checkdiff_t {
 	struct diff_options *o;
 	unsigned ws_rule;
 	unsigned status;
+	int last_line_kind;
 };
 
 static int is_conflict_marker(const char *line, int marker_size, unsigned long len)
@@ -3269,6 +3304,7 @@ static void checkdiff_consume_hunk(void *priv,
 static int checkdiff_consume(void *priv, char *line, unsigned long len)
 {
 	struct checkdiff_t *data = priv;
+	int last_line_kind;
 	int marker_size = data->conflict_marker_size;
 	const char *ws = diff_get_color(data->o->use_color, DIFF_WHITESPACE);
 	const char *reset = diff_get_color(data->o->use_color, DIFF_RESET);
@@ -3279,6 +3315,8 @@ static int checkdiff_consume(void *priv, char *line, unsigned long len)
 	assert(data->o);
 	line_prefix = diff_line_prefix(data->o);
 
+	last_line_kind = data->last_line_kind;
+	data->last_line_kind = line[0];
 	if (line[0] == '+') {
 		unsigned bad;
 		data->lineno++;
@@ -3301,6 +3339,17 @@ static int checkdiff_consume(void *priv, char *line, unsigned long len)
 			      data->o->file, set, reset, ws);
 	} else if (line[0] == ' ') {
 		data->lineno++;
+	} else if (line[0] == '\\') {
+		/* no newline at the end of the line */
+		if ((data->ws_rule & WS_INCOMPLETE_LINE) &&
+		    (last_line_kind == '+')) {
+			unsigned bad = WS_INCOMPLETE_LINE;
+			data->status |= bad;
+			err = whitespace_error_string(bad);
+			fprintf(data->o->file, "%s%s:%d: %s.\n",
+				line_prefix, data->filename, data->lineno, err);
+			free(err);
+		}
 	}
 	return 0;
 }
@@ -3530,7 +3579,6 @@ static int set_diff_algorithm(struct diff_options *opts,
 		return -1;
 
 	/* clear out previous settings */
-	DIFF_XDL_CLR(opts, NEED_MINIMAL);
 	opts->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK;
 	opts->xdl_opts |= value;
 
@@ -3714,6 +3762,7 @@ static void builtin_diff(const char *name_a,
 		xpparam_t xpp;
 		xdemitconf_t xecfg;
 		struct emit_callback ecbdata;
+		unsigned ws_rule;
 		const struct userdiff_funcname *pe;
 
 		if (must_show_header) {
@@ -3725,6 +3774,12 @@ static void builtin_diff(const char *name_a,
 		mf1.size = fill_textconv(o->repo, textconv_one, one, &mf1.ptr);
 		mf2.size = fill_textconv(o->repo, textconv_two, two, &mf2.ptr);
 
+		ws_rule = whitespace_rule(o->repo->index, name_b);
+
+		/* symlink being an incomplete line is not a news */
+		if (DIFF_FILE_VALID(two) && S_ISLNK(two->mode))
+			ws_rule &= ~WS_INCOMPLETE_LINE;
+
 		pe = diff_funcname_pattern(o, one);
 		if (!pe)
 			pe = diff_funcname_pattern(o, two);
@@ -3736,7 +3791,7 @@ static void builtin_diff(const char *name_a,
 			lbl[0] = NULL;
 		ecbdata.label_path = lbl;
 		ecbdata.color_diff = o->use_color;
-		ecbdata.ws_rule = whitespace_rule(o->repo->index, name_b);
+		ecbdata.ws_rule = ws_rule;
 		if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
 			check_blank_at_eof(&mf1, &mf2, &ecbdata);
 		ecbdata.opt = o;
@@ -3943,6 +3998,10 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
 	data.ws_rule = whitespace_rule(o->repo->index, attr_path);
 	data.conflict_marker_size = ll_merge_marker_size(o->repo->index, attr_path);
 
+	/* symlink being an incomplete line is not a news */
+	if (DIFF_FILE_VALID(two) && S_ISLNK(two->mode))
+		data.ws_rule &= ~WS_INCOMPLETE_LINE;
+
 	if (fill_mmfile(o->repo, &mf1, one) < 0 ||
 	    fill_mmfile(o->repo, &mf2, two) < 0)
 		die("unable to read files to diff");
@@ -4987,6 +5046,8 @@ void diff_setup_done(struct diff_options *options)
 	if (options->flags.quick) {
 		options->output_format = DIFF_FORMAT_NO_OUTPUT;
 		options->flags.exit_with_status = 1;
+		options->detect_rename = 0;
+		options->flags.find_copies_harder = 0;
 	}
 
 	/*
@@ -5163,6 +5224,8 @@ static int diff_opt_find_object(const struct option *option,
 	struct object_id oid;
 
 	BUG_ON_OPT_NEG(unset);
+	if (!startup_info->have_repository)
+		return error(_("--find-object requires a git repository"));
 	if (repo_get_oid(the_repository, arg, &oid))
 		return error(_("unable to resolve '%s'"), arg);
 
@@ -7046,6 +7109,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
 			if (!diffopt->flags.no_index)
 				diffopt->skip_stat_unmatch++;
 			diff_free_filepair(p);
+			q->queue[i] = NULL;
 		}
 	}
 	free(q->queue);
@@ -7089,6 +7153,10 @@ void diff_queued_diff_prefetch(void *repository)
 
 	for (i = 0; i < q->nr; i++) {
 		struct diff_filepair *p = q->queue[i];
+
+		if (!p)
+			continue;
+
 		diff_add_if_missing(repo, &to_fetch, p->one);
 		diff_add_if_missing(repo, &to_fetch, p->two);
 	}
@@ -7124,7 +7192,7 @@ void diffcore_std(struct diff_options *options)
 	 * If no prefetching occurs, diffcore_rename() will prefetch if it
 	 * decides that it needs inexact rename detection.
 	 */
-	if (options->repo == the_repository && repo_has_promisor_remote(the_repository) &&
+	if (repo_has_promisor_remote(options->repo) &&
 	    (options->output_format & output_formats_to_prefetch ||
 	     options->pickaxe_opts & DIFF_PICKAXE_KINDS_MASK))
 		diff_queued_diff_prefetch(options->repo);
@@ -7347,6 +7415,26 @@ void diff_change(struct diff_options *options,
 			  concatpath, old_dirty_submodule, new_dirty_submodule);
 }
 
+void diff_same(struct diff_options *options,
+	       unsigned mode,
+	       const struct object_id *oid,
+	       const char *concatpath)
+{
+	struct diff_filespec *one;
+
+	if (S_ISGITLINK(mode) && is_submodule_ignored(concatpath, options))
+		return;
+
+	if (options->prefix &&
+	    strncmp(concatpath, options->prefix, options->prefix_length))
+		return;
+
+	one = alloc_filespec(concatpath);
+	fill_filespec(one, oid, 1, mode);
+	one->count++;
+	diff_queue(&diff_queued_diff, one, one);
+}
+
 struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path)
 {
 	struct diff_filepair *pair;
diff --git a/diff.h b/diff.h
index 31eedd5..7eb84aa 100644
--- a/diff.h
+++ b/diff.h
@@ -331,9 +331,9 @@ struct diff_options {
 
 	int ita_invisible_in_index;
 /* white-space error highlighting */
-#define WSEH_NEW (1<<12)
-#define WSEH_CONTEXT (1<<13)
-#define WSEH_OLD (1<<14)
+#define WSEH_NEW        (1<<16)
+#define WSEH_CONTEXT    (1<<17)
+#define WSEH_OLD        (1<<18)
 	unsigned ws_error_highlight;
 	const char *prefix;
 	int prefix_length;
@@ -572,6 +572,11 @@ void diff_change(struct diff_options *,
 		 const char *fullpath,
 		 unsigned dirty_submodule1, unsigned dirty_submodule2);
 
+void diff_same(struct diff_options *,
+	       unsigned mode,
+	       const struct object_id *oid,
+	       const char *fullpath);
+
 struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);
 
 void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,
diff --git a/diffcore-break.c b/diffcore-break.c
index c4c2173..17b5ad1 100644
--- a/diffcore-break.c
+++ b/diffcore-break.c
@@ -69,7 +69,7 @@ static int should_break(struct repository *r,
 	    oideq(&src->oid, &dst->oid))
 		return 0; /* they are the same */
 
-	if (r == the_repository && repo_has_promisor_remote(the_repository)) {
+	if (repo_has_promisor_remote(r)) {
 		options.missing_object_cb = diff_queued_diff_prefetch;
 		options.missing_object_data = r;
 	}
@@ -222,6 +222,7 @@ void diffcore_break(struct repository *r, int break_score)
 				free(p); /* not diff_free_filepair(), we are
 					  * reusing one and two here.
 					  */
+				q->queue[i] = NULL;
 				continue;
 			}
 		}
diff --git a/diffcore-delta.c b/diffcore-delta.c
index ba6cbee..2b7db39 100644
--- a/diffcore-delta.c
+++ b/diffcore-delta.c
@@ -56,7 +56,7 @@ static struct spanhash_top *spanhash_rehash(struct spanhash_top *orig)
 			     st_mult(sizeof(struct spanhash), sz)));
 	new_spanhash->alloc_log2 = orig->alloc_log2 + 1;
 	new_spanhash->free = INITIAL_FREE(new_spanhash->alloc_log2);
-	memset(new_spanhash->data, 0, sizeof(struct spanhash) * sz);
+	MEMZERO_ARRAY(new_spanhash->data, sz);
 	for (i = 0; i < osz; i++) {
 		struct spanhash *o = &(orig->data[i]);
 		int bucket;
@@ -135,7 +135,7 @@ static struct spanhash_top *hash_chars(struct repository *r,
 			      st_mult(sizeof(struct spanhash), (size_t)1 << i)));
 	hash->alloc_log2 = i;
 	hash->free = INITIAL_FREE(i);
-	memset(hash->data, 0, sizeof(struct spanhash) * ((size_t)1 << i));
+	MEMZERO_ARRAY(hash->data, (size_t)1 << i);
 
 	n = 0;
 	accum1 = accum2 = 0;
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 7723bc3..c797d8e 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -379,7 +379,7 @@ struct dir_rename_info {
 
 static char *get_dirname(const char *filename)
 {
-	char *slash = strrchr(filename, '/');
+	const char *slash = strrchr(filename, '/');
 	return slash ? xstrndup(filename, slash - filename) : xstrdup("");
 }
 
@@ -987,7 +987,7 @@ static int find_basename_matches(struct diff_options *options,
 			strintmap_set(&dests, base, i);
 	}
 
-	if (options->repo == the_repository && repo_has_promisor_remote(the_repository)) {
+	if (repo_has_promisor_remote(options->repo)) {
 		dpf_options.missing_object_cb = basename_prefetch;
 		dpf_options.missing_object_data = &prefetch_options;
 	}
@@ -1574,7 +1574,7 @@ void diffcore_rename_extended(struct diff_options *options,
 
 	/* Finish setting up dpf_options */
 	prefetch_options.skip_unmodified = skip_unmodified;
-	if (options->repo == the_repository && repo_has_promisor_remote(the_repository)) {
+	if (repo_has_promisor_remote(options->repo)) {
 		dpf_options.missing_object_cb = inexact_prefetch;
 		dpf_options.missing_object_data = &prefetch_options;
 	}
diff --git a/dir.c b/dir.c
index b00821f..026d851 100644
--- a/dir.c
+++ b/dir.c
@@ -1551,7 +1551,9 @@ enum pattern_match_result path_matches_pattern_list(
 
 int init_sparse_checkout_patterns(struct index_state *istate)
 {
-	if (!core_apply_sparse_checkout)
+	struct repo_config_values *cfg = repo_config_values(the_repository);
+
+	if (!cfg->apply_sparse_checkout)
 		return 1;
 	if (istate->sparse_checkout_patterns)
 		return 0;
diff --git a/entry.c b/entry.c
index cae02eb..7817aee 100644
--- a/entry.c
+++ b/entry.c
@@ -2,13 +2,13 @@
 
 #include "git-compat-util.h"
 #include "odb.h"
+#include "odb/streaming.h"
 #include "dir.h"
 #include "environment.h"
 #include "gettext.h"
 #include "hex.h"
 #include "name-hash.h"
 #include "sparse-index.h"
-#include "streaming.h"
 #include "submodule.h"
 #include "symlinks.h"
 #include "progress.h"
@@ -139,7 +139,7 @@ static int streaming_write_entry(const struct cache_entry *ce, char *path,
 	if (fd < 0)
 		return -1;
 
-	result |= stream_blob_to_fd(fd, &ce->oid, filter, 1);
+	result |= odb_stream_blob_to_fd(the_repository->objects, fd, &ce->oid, filter, 1);
 	*fstat_done = fstat_checkout_output(fd, state, statbuf);
 	result |= close(fd);
 
diff --git a/environment.c b/environment.c
index a770b59..fc3ed8b 100644
--- a/environment.c
+++ b/environment.c
@@ -21,6 +21,7 @@
 #include "gettext.h"
 #include "git-zlib.h"
 #include "ident.h"
+#include "lockfile.h"
 #include "mailmap.h"
 #include "object-name.h"
 #include "repository.h"
@@ -53,7 +54,6 @@ char *git_commit_encoding;
 char *git_log_output_encoding;
 char *apply_default_whitespace;
 char *apply_default_ignorewhitespace;
-char *git_attributes_file;
 int zlib_compression_level = Z_BEST_SPEED;
 int pack_compression_level = Z_DEFAULT_COMPRESSION;
 int fsync_object_files = -1;
@@ -67,7 +67,6 @@ enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;
 enum eol core_eol = EOL_UNSET;
 int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;
 char *check_roundtrip_encoding;
-enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
 enum rebase_setup_type autorebase = AUTOREBASE_NEVER;
 enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;
 #ifndef OBJECT_CREATION_MODE
@@ -75,35 +74,10 @@ enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;
 #endif
 enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;
 int grafts_keep_true_parents;
-int core_apply_sparse_checkout;
 int core_sparse_checkout_cone;
 int sparse_expect_files_outside_of_patterns;
 int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
 unsigned long pack_size_limit_cfg;
-int max_allowed_tree_depth =
-#ifdef _MSC_VER
-	/*
-	 * When traversing into too-deep trees, Visual C-compiled Git seems to
-	 * run into some internal stack overflow detection in the
-	 * `RtlpAllocateHeap()` function that is called from within
-	 * `git_inflate_init()`'s call tree. The following value seems to be
-	 * low enough to avoid that by letting Git exit with an error before
-	 * the stack overflow can occur.
-	 */
-	512;
-#elif defined(GIT_WINDOWS_NATIVE) && defined(__clang__) && defined(__aarch64__)
-	/*
-	 * Similar to Visual C, it seems that on Windows/ARM64 the clang-based
-	 * builds have a smaller stack space available. When running out of
-	 * that stack space, a `STATUS_STACK_OVERFLOW` is produced. When the
-	 * Git command was run from an MSYS2 Bash, this unfortunately results
-	 * in an exit code 127. Let's prevent that by lowering the maximal
-	 * tree depth; This value seems to be low enough.
-	 */
-	1280;
-#else
-	2048;
-#endif
 
 #ifndef PROTECT_HFS_DEFAULT
 #define PROTECT_HFS_DEFAULT 0
@@ -324,9 +298,11 @@ static enum fsync_component parse_fsync_components(const char *var, const char *
 	return (current & ~negative) | positive;
 }
 
-static int git_default_core_config(const char *var, const char *value,
-				   const struct config_context *ctx, void *cb)
+int git_default_core_config(const char *var, const char *value,
+			    const struct config_context *ctx, void *cb)
 {
+	struct repo_config_values *cfg = repo_config_values(the_repository);
+
 	/* This needs a better name */
 	if (!strcmp(var, "core.filemode")) {
 		trust_executable_bit = git_config_bool(var, value);
@@ -364,8 +340,8 @@ static int git_default_core_config(const char *var, const char *value,
 	}
 
 	if (!strcmp(var, "core.attributesfile")) {
-		FREE_AND_NULL(git_attributes_file);
-		return git_config_pathname(&git_attributes_file, var, value);
+		FREE_AND_NULL(cfg->attributes_file);
+		return git_config_pathname(&cfg->attributes_file, var, value);
 	}
 
 	if (!strcmp(var, "core.bare")) {
@@ -532,6 +508,11 @@ static int git_default_core_config(const char *var, const char *value,
 		return 0;
 	}
 
+	if (!strcmp(var, "core.lockfilepid")) {
+		lockfile_pid_enabled = git_config_bool(var, value);
+		return 0;
+	}
+
 	if (!strcmp(var, "core.createobject")) {
 		if (!value)
 			return config_error_nonbool(var);
@@ -545,7 +526,7 @@ static int git_default_core_config(const char *var, const char *value,
 	}
 
 	if (!strcmp(var, "core.sparsecheckout")) {
-		core_apply_sparse_checkout = git_config_bool(var, value);
+		cfg->apply_sparse_checkout = git_config_bool(var, value);
 		return 0;
 	}
 
@@ -569,11 +550,6 @@ static int git_default_core_config(const char *var, const char *value,
 		return 0;
 	}
 
-	if (!strcmp(var, "core.maxtreedepth")) {
-		max_allowed_tree_depth = git_config_int(var, value, ctx->kvi);
-		return 0;
-	}
-
 	/* Add other config variables here and to Documentation/config.adoc. */
 	return platform_core_config(var, value, ctx, cb);
 }
@@ -607,18 +583,20 @@ static int git_default_i18n_config(const char *var, const char *value)
 
 static int git_default_branch_config(const char *var, const char *value)
 {
+	struct repo_config_values *cfg = repo_config_values(the_repository);
+
 	if (!strcmp(var, "branch.autosetupmerge")) {
 		if (value && !strcmp(value, "always")) {
-			git_branch_track = BRANCH_TRACK_ALWAYS;
+			cfg->branch_track = BRANCH_TRACK_ALWAYS;
 			return 0;
 		} else if (value && !strcmp(value, "inherit")) {
-			git_branch_track = BRANCH_TRACK_INHERIT;
+			cfg->branch_track = BRANCH_TRACK_INHERIT;
 			return 0;
 		} else if (value && !strcmp(value, "simple")) {
-			git_branch_track = BRANCH_TRACK_SIMPLE;
+			cfg->branch_track = BRANCH_TRACK_SIMPLE;
 			return 0;
 		}
-		git_branch_track = git_config_bool(var, value);
+		cfg->branch_track = git_config_bool(var, value);
 		return 0;
 	}
 	if (!strcmp(var, "branch.autosetuprebase")) {
@@ -670,22 +648,6 @@ static int git_default_push_config(const char *var, const char *value)
 	return 0;
 }
 
-static int git_default_mailmap_config(const char *var, const char *value)
-{
-	if (!strcmp(var, "mailmap.file")) {
-		FREE_AND_NULL(git_mailmap_file);
-		return git_config_pathname(&git_mailmap_file, var, value);
-	}
-
-	if (!strcmp(var, "mailmap.blob")) {
-		FREE_AND_NULL(git_mailmap_blob);
-		return git_config_string(&git_mailmap_blob, var, value);
-	}
-
-	/* Add other config variables here and to Documentation/config.adoc. */
-	return 0;
-}
-
 static int git_default_attr_config(const char *var, const char *value)
 {
 	if (!strcmp(var, "attr.tree")) {
@@ -720,9 +682,6 @@ int git_default_config(const char *var, const char *value,
 	if (starts_with(var, "push."))
 		return git_default_push_config(var, value);
 
-	if (starts_with(var, "mailmap."))
-		return git_default_mailmap_config(var, value);
-
 	if (starts_with(var, "attr."))
 		return git_default_attr_config(var, value);
 
@@ -756,3 +715,10 @@ int git_default_config(const char *var, const char *value,
 	/* Add other config variables here and to Documentation/config.adoc. */
 	return 0;
 }
+
+void repo_config_values_init(struct repo_config_values *cfg)
+{
+	cfg->attributes_file = NULL;
+	cfg->apply_sparse_checkout = 0;
+	cfg->branch_track = BRANCH_TRACK_REMOTE;
+}
diff --git a/environment.h b/environment.h
index 51898c9..123a71c 100644
--- a/environment.h
+++ b/environment.h
@@ -2,6 +2,7 @@
 #define ENVIRONMENT_H
 
 #include "repo-settings.h"
+#include "branch.h"
 
 /* Double-check local_repo_env below if you add to this list. */
 #define GIT_DIR_ENVIRONMENT "GIT_DIR"
@@ -42,6 +43,7 @@
 #define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS"
 #define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT "GIT_TEXTDOMAINDIR"
 #define GIT_ATTR_SOURCE_ENVIRONMENT "GIT_ATTR_SOURCE"
+#define GIT_REFERENCE_BACKEND_ENVIRONMENT "GIT_REFERENCE_BACKEND"
 
 /*
  * Environment variable used to propagate the --no-advice global option to the
@@ -84,6 +86,18 @@ extern const char * const local_repo_env[];
 
 struct strvec;
 
+struct repository;
+struct repo_config_values {
+	/* section "core" config values */
+	char *attributes_file;
+	int apply_sparse_checkout;
+
+	/* section "branch" config values */
+	enum branch_track branch_track;
+};
+
+struct repo_config_values *repo_config_values(struct repository *repo);
+
 /*
  * Wrapper of getenv() that returns a strdup value. This value is kept
  * in argv to be freed later.
@@ -106,6 +120,10 @@ const char *strip_namespace(const char *namespaced_ref);
 
 int git_default_config(const char *, const char *,
 		       const struct config_context *, void *);
+int git_default_core_config(const char *var, const char *value,
+			    const struct config_context *ctx, void *cb);
+
+void repo_config_values_init(struct repo_config_values *cfg);
 
 /*
  * TODO: All the below state either explicitly or implicitly relies on
@@ -152,17 +170,14 @@ extern int assume_unchanged;
 extern int warn_on_object_refname_ambiguity;
 extern char *apply_default_whitespace;
 extern char *apply_default_ignorewhitespace;
-extern char *git_attributes_file;
 extern int zlib_compression_level;
 extern int pack_compression_level;
 extern unsigned long pack_size_limit_cfg;
-extern int max_allowed_tree_depth;
 
 extern int precomposed_unicode;
 extern int protect_hfs;
 extern int protect_ntfs;
 
-extern int core_apply_sparse_checkout;
 extern int core_sparse_checkout_cone;
 extern int sparse_expect_files_outside_of_patterns;
 
diff --git a/ewah/bitmap.c b/ewah/bitmap.c
index 55928da..c378e0a 100644
--- a/ewah/bitmap.c
+++ b/ewah/bitmap.c
@@ -46,8 +46,7 @@ static void bitmap_grow(struct bitmap *self, size_t word_alloc)
 {
 	size_t old_size = self->word_alloc;
 	ALLOC_GROW(self->words, word_alloc, self->word_alloc);
-	memset(self->words + old_size, 0x0,
-	       (self->word_alloc - old_size) * sizeof(eword_t));
+	MEMZERO_ARRAY(self->words + old_size, self->word_alloc - old_size);
 }
 
 void bitmap_set(struct bitmap *self, size_t pos)
@@ -192,8 +191,8 @@ void bitmap_or_ewah(struct bitmap *self, struct ewah_bitmap *other)
 	if (self->word_alloc < other_final) {
 		self->word_alloc = other_final;
 		REALLOC_ARRAY(self->words, self->word_alloc);
-		memset(self->words + original_size, 0x0,
-			(self->word_alloc - original_size) * sizeof(eword_t));
+		MEMZERO_ARRAY(self->words + original_size,
+			      self->word_alloc - original_size);
 	}
 
 	ewah_iterator_init(&it, other);
diff --git a/fetch-pack.c b/fetch-pack.c
index fe7a84b..6ecd468 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -35,6 +35,7 @@
 #include "sigchain.h"
 #include "mergesort.h"
 #include "prio-queue.h"
+#include "promisor-remote.h"
 
 static int transfer_unpack_limit = -1;
 static int fetch_unpack_limit = -1;
@@ -188,13 +189,9 @@ static int rev_list_insert_ref(struct fetch_negotiator *negotiator,
 	return 0;
 }
 
-static int rev_list_insert_ref_oid(const char *refname UNUSED,
-				   const char *referent UNUSED,
-				   const struct object_id *oid,
-				   int flag UNUSED,
-				   void *cb_data)
+static int rev_list_insert_ref_oid(const struct reference *ref, void *cb_data)
 {
-	return rev_list_insert_ref(cb_data, oid);
+	return rev_list_insert_ref(cb_data, ref->oid);
 }
 
 enum ack_type {
@@ -296,11 +293,14 @@ static int next_flush(int stateless_rpc, int count)
 static void mark_tips(struct fetch_negotiator *negotiator,
 		      const struct oid_array *negotiation_tips)
 {
+	struct refs_for_each_ref_options opts = {
+		.flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+	};
 	int i;
 
 	if (!negotiation_tips) {
-		refs_for_each_rawref(get_main_ref_store(the_repository),
-				     rev_list_insert_ref_oid, negotiator);
+		refs_for_each_ref_ext(get_main_ref_store(the_repository),
+				      rev_list_insert_ref_oid, negotiator, &opts);
 		return;
 	}
 
@@ -616,13 +616,9 @@ static int mark_complete(const struct object_id *oid)
 	return 0;
 }
 
-static int mark_complete_oid(const char *refname UNUSED,
-			     const char *referent UNUSED,
-			     const struct object_id *oid,
-			     int flag UNUSED,
-			     void *cb_data UNUSED)
+static int mark_complete_oid(const struct reference *ref, void *cb_data UNUSED)
 {
-	return mark_complete(oid);
+	return mark_complete(ref->oid);
 }
 
 static void mark_recent_complete_commits(struct fetch_pack_args *args,
@@ -800,8 +796,12 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,
 	 */
 	trace2_region_enter("fetch-pack", "mark_complete_local_refs", NULL);
 	if (!args->deepen) {
-		refs_for_each_rawref(get_main_ref_store(the_repository),
-				     mark_complete_oid, NULL);
+		struct refs_for_each_ref_options opts = {
+			.flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+		};
+
+		refs_for_each_ref_ext(get_main_ref_store(the_repository),
+				      mark_complete_oid, NULL, &opts);
 		for_each_cached_alternate(NULL, mark_alternate_complete);
 		if (cutoff)
 			mark_recent_complete_commits(args, cutoff);
@@ -1669,6 +1669,29 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
 	struct string_list packfile_uris = STRING_LIST_INIT_DUP;
 	int i;
 	struct strvec index_pack_args = STRVEC_INIT;
+	const char *promisor_remote_config;
+
+	if (server_feature_v2("promisor-remote", &promisor_remote_config))
+		promisor_remote_reply(promisor_remote_config, NULL);
+
+	if (args->filter_options.choice == LOFC_AUTO) {
+		struct strbuf errbuf = STRBUF_INIT;
+		char *constructed_filter = promisor_remote_construct_filter(r);
+
+		list_objects_filter_release(&args->filter_options);
+		/* Disallow 'auto' as a result of the resolution of this 'auto' filter below */
+		args->filter_options.allow_auto_filter = 0;
+
+		if (constructed_filter &&
+		    gently_parse_list_objects_filter(&args->filter_options,
+						     constructed_filter,
+						     &errbuf))
+			die(_("couldn't resolve 'auto' filter '%s': %s"),
+			    constructed_filter, errbuf.buf);
+
+		free(constructed_filter);
+		strbuf_release(&errbuf);
+	}
 
 	negotiator = &negotiator_alloc;
 	if (args->refetch)
@@ -1873,8 +1896,9 @@ int fetch_pack_fsck_config(const char *var, const char *value,
 
 		if (git_config_pathname(&path, var, value))
 			return -1;
-		strbuf_addf(msg_types, "%cskiplist=%s",
-			msg_types->len ? ',' : '=', path);
+		if (path)
+			strbuf_addf(msg_types, "%cskiplist=%s",
+				    msg_types->len ? ',' : '=', path);
 		free(path);
 		return 0;
 	}
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c
index c9085ed..45d8b20 100644
--- a/fmt-merge-msg.c
+++ b/fmt-merge-msg.c
@@ -246,7 +246,8 @@ static void add_branch_desc(struct strbuf *out, const char *name)
 static void record_person_from_buf(int which, struct string_list *people,
 				   const char *buffer)
 {
-	char *name_buf, *name, *name_end;
+	char *name_buf;
+	const char *name, *name_end;
 	struct string_list_item *elem;
 	const char *field;
 
@@ -421,7 +422,7 @@ static void shortlog(const char *name,
 
 	clear_commit_marks((struct commit *)branch, flags);
 	clear_commit_marks(head, flags);
-	free_commit_list(rev->commits);
+	commit_list_free(rev->commits);
 	rev->commits = NULL;
 	rev->pending.nr = 0;
 
diff --git a/fsck.c b/fsck.c
index 341e100..0f02cf8 100644
--- a/fsck.c
+++ b/fsck.c
@@ -360,7 +360,7 @@ static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *op
 	int res = 0;
 	const char *name;
 
-	if (parse_tree(tree))
+	if (repo_parse_tree(the_repository, tree))
 		return -1;
 
 	name = fsck_get_object_name(options, &tree->object.oid);
@@ -474,7 +474,7 @@ static int fsck_walk_tag(struct tag *tag, void *data, struct fsck_options *optio
 {
 	const char *name = fsck_get_object_name(options, &tag->object.oid);
 
-	if (parse_tag(tag))
+	if (parse_tag(the_repository, tag))
 		return -1;
 	if (name)
 		fsck_put_object_name(options, &tag->tagged->oid, "%s", name);
@@ -860,31 +860,60 @@ static int verify_headers(const void *data, unsigned long size,
 		FSCK_MSG_UNTERMINATED_HEADER, "unterminated header");
 }
 
-static int fsck_ident(const char **ident,
+static timestamp_t parse_timestamp_from_buf(const char **start, const char *end)
+{
+	const char *p = *start;
+	char buf[24]; /* big enough for 2^64 */
+	size_t i = 0;
+
+	while (p < end && isdigit(*p)) {
+		if (i >= ARRAY_SIZE(buf) - 1)
+			return TIME_MAX;
+		buf[i++] = *p++;
+	}
+	buf[i] = '\0';
+	*start = p;
+	return parse_timestamp(buf, NULL, 10);
+}
+
+static int fsck_ident(const char **ident, const char *ident_end,
 		      const struct object_id *oid, enum object_type type,
 		      struct fsck_options *options)
 {
 	const char *p = *ident;
-	char *end;
+	const char *nl;
 
-	*ident = strchrnul(*ident, '\n');
-	if (**ident == '\n')
-		(*ident)++;
+	nl = memchr(p, '\n', ident_end - p);
+	if (!nl)
+		BUG("verify_headers() should have made sure we have a newline");
+	*ident = nl + 1;
 
 	if (*p == '<')
 		return report(options, oid, type, FSCK_MSG_MISSING_NAME_BEFORE_EMAIL, "invalid author/committer line - missing space before email");
-	p += strcspn(p, "<>\n");
-	if (*p == '>')
-		return report(options, oid, type, FSCK_MSG_BAD_NAME, "invalid author/committer line - bad name");
-	if (*p != '<')
-		return report(options, oid, type, FSCK_MSG_MISSING_EMAIL, "invalid author/committer line - missing email");
+	for (;;) {
+		if (p >= ident_end || *p == '\n')
+			return report(options, oid, type, FSCK_MSG_MISSING_EMAIL, "invalid author/committer line - missing email");
+		if (*p == '>')
+			return report(options, oid, type, FSCK_MSG_BAD_NAME, "invalid author/committer line - bad name");
+		if (*p == '<')
+			break; /* end of name, beginning of email */
+
+		/* otherwise, skip past arbitrary name char */
+		p++;
+	}
 	if (p[-1] != ' ')
 		return report(options, oid, type, FSCK_MSG_MISSING_SPACE_BEFORE_EMAIL, "invalid author/committer line - missing space before email");
-	p++;
-	p += strcspn(p, "<>\n");
-	if (*p != '>')
-		return report(options, oid, type, FSCK_MSG_BAD_EMAIL, "invalid author/committer line - bad email");
-	p++;
+	p++; /* skip past '<' we found */
+	for (;;) {
+		if (p >= ident_end || *p == '<' || *p == '\n')
+			return report(options, oid, type, FSCK_MSG_BAD_EMAIL, "invalid author/committer line - bad email");
+		if (*p == '>')
+			break; /* end of email */
+
+		/* otherwise, skip past arbitrary email char */
+		p++;
+	}
+	p++; /* skip past '>' we found */
 	if (*p != ' ')
 		return report(options, oid, type, FSCK_MSG_MISSING_SPACE_BEFORE_DATE, "invalid author/committer line - missing space before date");
 	p++;
@@ -904,11 +933,11 @@ static int fsck_ident(const char **ident,
 			      "invalid author/committer line - bad date");
 	if (*p == '0' && p[1] != ' ')
 		return report(options, oid, type, FSCK_MSG_ZERO_PADDED_DATE, "invalid author/committer line - zero-padded date");
-	if (date_overflows(parse_timestamp(p, &end, 10)))
+	if (date_overflows(parse_timestamp_from_buf(&p, ident_end)))
 		return report(options, oid, type, FSCK_MSG_BAD_DATE_OVERFLOW, "invalid author/committer line - date causes integer overflow");
-	if ((end == p || *end != ' '))
+	if (*p != ' ')
 		return report(options, oid, type, FSCK_MSG_BAD_DATE, "invalid author/committer line - bad date");
-	p = end + 1;
+	p++;
 	if ((*p != '+' && *p != '-') ||
 	    !isdigit(p[1]) ||
 	    !isdigit(p[2]) ||
@@ -958,7 +987,7 @@ static int fsck_commit(const struct object_id *oid,
 	author_count = 0;
 	while (buffer < buffer_end && skip_prefix(buffer, "author ", &buffer)) {
 		author_count++;
-		err = fsck_ident(&buffer, oid, OBJ_COMMIT, options);
+		err = fsck_ident(&buffer, buffer_end, oid, OBJ_COMMIT, options);
 		if (err)
 			return err;
 	}
@@ -970,7 +999,7 @@ static int fsck_commit(const struct object_id *oid,
 		return err;
 	if (buffer >= buffer_end || !skip_prefix(buffer, "committer ", &buffer))
 		return report(options, oid, OBJ_COMMIT, FSCK_MSG_MISSING_COMMITTER, "invalid format - expected 'committer' line");
-	err = fsck_ident(&buffer, oid, OBJ_COMMIT, options);
+	err = fsck_ident(&buffer, buffer_end, oid, OBJ_COMMIT, options);
 	if (err)
 		return err;
 	if (memchr(buffer_begin, '\0', size)) {
@@ -997,7 +1026,7 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer,
 			int *tagged_type)
 {
 	int ret = 0;
-	char *eol;
+	const char *eol;
 	struct strbuf sb = STRBUF_INIT;
 	const char *buffer_end = buffer + size;
 	const char *p;
@@ -1065,7 +1094,7 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer,
 			goto done;
 	}
 	else
-		ret = fsck_ident(&buffer, oid, OBJ_TAG, options);
+		ret = fsck_ident(&buffer, buffer_end, oid, OBJ_TAG, options);
 
 	if (buffer < buffer_end && (skip_prefix(buffer, "gpgsig ", &buffer) || skip_prefix(buffer, "gpgsig-sha256 ", &buffer))) {
 		eol = memchr(buffer, '\n', buffer_end - buffer);
@@ -1281,11 +1310,6 @@ int fsck_refs_error_function(struct fsck_options *options UNUSED,
 
 	strbuf_addstr(&sb, report->path);
 
-	if (report->oid)
-		strbuf_addf(&sb, " -> (%s)", oid_to_hex(report->oid));
-	else if (report->referent)
-		strbuf_addf(&sb, " -> (%s)", report->referent);
-
 	if (msg_type == FSCK_WARN)
 		warning("%s: %s", sb.buf, message);
 	else
@@ -1350,6 +1374,12 @@ int fsck_finish(struct fsck_options *options)
 	return ret;
 }
 
+bool fsck_has_queued_checks(struct fsck_options *options)
+{
+	return !oidset_equal(&options->gitmodules_found, &options->gitmodules_done) ||
+	       !oidset_equal(&options->gitattributes_found, &options->gitattributes_done);
+}
+
 void fsck_options_clear(struct fsck_options *options)
 {
 	free(options->msg_type);
@@ -1369,14 +1399,16 @@ int git_fsck_config(const char *var, const char *value,
 
 	if (strcmp(var, "fsck.skiplist") == 0) {
 		char *path;
-		struct strbuf sb = STRBUF_INIT;
 
 		if (git_config_pathname(&path, var, value))
 			return -1;
-		strbuf_addf(&sb, "skiplist=%s", path);
-		free(path);
-		fsck_set_msg_types(options, sb.buf);
-		strbuf_release(&sb);
+		if (path) {
+			struct strbuf sb = STRBUF_INIT;
+			strbuf_addf(&sb, "skiplist=%s", path);
+			free(path);
+			fsck_set_msg_types(options, sb.buf);
+			strbuf_release(&sb);
+		}
 		return 0;
 	}
 
diff --git a/fsck.h b/fsck.h
index cb6ef32..65ecbb7 100644
--- a/fsck.h
+++ b/fsck.h
@@ -30,6 +30,7 @@ enum fsck_msg_type {
 	FUNC(BAD_DATE_OVERFLOW, ERROR) \
 	FUNC(BAD_EMAIL, ERROR) \
 	FUNC(BAD_GPGSIG, ERROR) \
+	FUNC(BAD_HEAD_TARGET, ERROR) \
 	FUNC(BAD_NAME, ERROR) \
 	FUNC(BAD_OBJECT_SHA1, ERROR) \
 	FUNC(BAD_PACKED_REF_ENTRY, ERROR) \
@@ -39,6 +40,7 @@ enum fsck_msg_type {
 	FUNC(BAD_REF_CONTENT, ERROR) \
 	FUNC(BAD_REF_FILETYPE, ERROR) \
 	FUNC(BAD_REF_NAME, ERROR) \
+	FUNC(BAD_REF_OID, ERROR) \
 	FUNC(BAD_TIMEZONE, ERROR) \
 	FUNC(BAD_TREE, ERROR) \
 	FUNC(BAD_TREE_SHA1, ERROR) \
@@ -162,8 +164,6 @@ struct fsck_object_report {
 
 struct fsck_ref_report {
 	const char *path;
-	const struct object_id *oid;
-	const char *referent;
 };
 
 struct fsck_options {
@@ -249,6 +249,13 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer,
 int fsck_finish(struct fsck_options *options);
 
 /*
+ * Check whether there are any checks that have been queued up and that still
+ * need to be run. Returns `false` iff `fsck_finish()` wouldn't perform any
+ * actions, `true` otherwise.
+ */
+bool fsck_has_queued_checks(struct fsck_options *options);
+
+/*
  * Clear the fsck_options struct, freeing any allocated memory.
  */
 void fsck_options_clear(struct fsck_options *options);
diff --git a/generate-configlist.sh b/generate-configlist.sh
index 75c39ad..e28054f 100755
--- a/generate-configlist.sh
+++ b/generate-configlist.sh
@@ -2,10 +2,11 @@
 
 SOURCE_DIR="$1"
 OUTPUT="$2"
+DEPFILE="$3"
 
 if test -z "$SOURCE_DIR" || ! test -d "$SOURCE_DIR" || test -z "$OUTPUT"
 then
-	echo >&2 "USAGE: $0 <SOURCE_DIR> <OUTPUT>"
+	echo >&2 "USAGE: $0 <SOURCE_DIR> <OUTPUT> [<DEPFILE>]"
 	exit 1
 fi
 
@@ -36,3 +37,16 @@
 	echo
 	print_config_list
 } >"$OUTPUT"
+
+if test -n "$DEPFILE"
+then
+	QUOTED_OUTPUT="$(printf '%s\n' "$OUTPUT" | sed 's,[&/\],\\&,g')"
+	{
+		printf '%s\n' "$SOURCE_DIR"/Documentation/*config.adoc \
+			"$SOURCE_DIR"/Documentation/config/*.adoc |
+			sed -e 's/[# ]/\\&/g' -e "s/^/$QUOTED_OUTPUT: /"
+		printf '%s:\n' "$SOURCE_DIR"/Documentation/*config.adoc \
+			"$SOURCE_DIR"/Documentation/config/*.adoc |
+			sed -e 's/[# ]/\\&/g'
+	} >"$DEPFILE"
+fi
diff --git a/git-compat-util.h b/git-compat-util.h
index 398e0fa..bebcf9f 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -38,37 +38,8 @@ struct strbuf;
 DISABLE_WARNING(-Wsign-compare)
 #endif
 
-#ifndef FLEX_ARRAY
-/*
- * See if our compiler is known to support flexible array members.
- */
-
-/*
- * Check vendor specific quirks first, before checking the
- * __STDC_VERSION__, as vendor compilers can lie and we need to be
- * able to work them around.  Note that by not defining FLEX_ARRAY
- * here, we can fall back to use the "safer but a bit wasteful" one
- * later.
- */
-#if defined(__SUNPRO_C) && (__SUNPRO_C <= 0x580)
-#elif defined(__GNUC__)
-# if (__GNUC__ >= 3)
-#  define FLEX_ARRAY /* empty */
-# else
-#  define FLEX_ARRAY 0 /* older GNU extension */
-# endif
-#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
-# define FLEX_ARRAY /* empty */
-#endif
-
-/*
- * Otherwise, default to safer but a bit wasteful traditional style
- */
-#ifndef FLEX_ARRAY
-# define FLEX_ARRAY 1
-#endif
-#endif
-
+#undef FLEX_ARRAY
+#define FLEX_ARRAY /* empty - weather balloon to require C99 FAM */
 
 /*
  * BUILD_ASSERT_OR_ZERO - assert a build-time dependency, as an expression.
@@ -607,6 +578,30 @@ static inline bool strip_suffix(const char *str, const char *suffix,
 #define DEFAULT_PACKED_GIT_LIMIT \
 	((1024L * 1024L) * (size_t)(sizeof(void*) >= 8 ? (32 * 1024L * 1024L) : 256))
 
+#ifdef _MSC_VER
+  /*
+   * When traversing into too-deep trees, Visual C-compiled Git seems to
+   * run into some internal stack overflow detection in the
+   * `RtlpAllocateHeap()` function that is called from within
+   * `git_inflate_init()`'s call tree. The following value seems to be
+   * low enough to avoid that by letting Git exit with an error before
+   * the stack overflow can occur.
+   */
+#define DEFAULT_MAX_ALLOWED_TREE_DEPTH 512
+#elif defined(GIT_WINDOWS_NATIVE) && defined(__clang__) && defined(__aarch64__)
+  /*
+   * Similar to Visual C, it seems that on Windows/ARM64 the clang-based
+   * builds have a smaller stack space available. When running out of
+   * that stack space, a `STATUS_STACK_OVERFLOW` is produced. When the
+   * Git command was run from an MSYS2 Bash, this unfortunately results
+   * in an exit code 127. Let's prevent that by lowering the maximal
+   * tree depth; This value seems to be low enough.
+   */
+#define DEFAULT_MAX_ALLOWED_TREE_DEPTH 1280
+#else
+#define DEFAULT_MAX_ALLOWED_TREE_DEPTH 2048
+#endif
+
 int git_open_cloexec(const char *name, int flags);
 #define git_open(name) git_open_cloexec(name, O_RDONLY)
 
@@ -726,6 +721,7 @@ static inline uint64_t u64_add(uint64_t a, uint64_t b)
 #define ALLOC_ARRAY(x, alloc) (x) = xmalloc(st_mult(sizeof(*(x)), (alloc)))
 #define CALLOC_ARRAY(x, alloc) (x) = xcalloc((alloc), sizeof(*(x)))
 #define REALLOC_ARRAY(x, alloc) (x) = xrealloc((x), st_mult(sizeof(*(x)), (alloc)))
+#define MEMZERO_ARRAY(x, alloc) memset((x), 0x0, st_mult(sizeof(*(x)), (alloc)))
 
 #define COPY_ARRAY(dst, src, n) copy_array((dst), (src), (n), sizeof(*(dst)) + \
 	BARF_UNLESS_COPYABLE((dst), (src)))
diff --git a/git-gui/.gitattributes b/git-gui/.gitattributes
index 889d582..f03c0fe 100644
--- a/git-gui/.gitattributes
+++ b/git-gui/.gitattributes
@@ -1,7 +1,7 @@
 *           whitespace=indent-with-non-tab,trailing-space,space-before-tab,tabwidth=4
 *           encoding=US-ASCII
 git-gui.sh  encoding=UTF-8
-/po/*.po    encoding=UTF-8
+*.po        encoding=UTF-8
 /GIT-VERSION-GEN eol=lf
 Makefile    whitespace=!indent,trail,space
 meson.build whitespace=space
diff --git a/git-gui/po/bg.po b/git-gui/po/bg.po
index 21f5bd5..ff5adb2 100644
--- a/git-gui/po/bg.po
+++ b/git-gui/po/bg.po
@@ -1,7 +1,7 @@
 # Bulgarian translation of git-gui po-file.
-# Copyright (C) 2012, 2013, 2014, 2015, 2016, 2024, 2025 Alexander Shopov <ash@kambanaria.org>.
+# Copyright (C) 2012, 2013, 2014, 2015, 2016, 2024, 2025, 2026 Alexander Shopov <ash@kambanaria.org>.
 # This file is distributed under the same license as the git package.
-# Alexander Shopov <ash@kambanaria.org>, 2012, 2013, 2014, 2015, 2016, 2024, 2025.
+# Alexander Shopov <ash@kambanaria.org>, 2012, 2013, 2014, 2015, 2016, 2024, 2025, 2026.
 #
 #
 msgid ""
@@ -9,7 +9,7 @@
 "Project-Id-Version: git-gui master\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2025-07-22 17:37+0200\n"
-"PO-Revision-Date: 2025-07-28 11:56+0200\n"
+"PO-Revision-Date: 2026-01-19 10:12+0100\n"
 "Last-Translator: Alexander Shopov <ash@kambanaria.org>\n"
 "Language-Team: Bulgarian <dict@fsa-bg.org>\n"
 "Language: bg\n"
@@ -230,7 +230,7 @@
 msgstr "Изтриване…"
 
 msgid "Reset..."
-msgstr "Отмяна на промените…"
+msgstr "Отмяна на промѐните…"
 
 msgid "Done"
 msgstr "Готово"
@@ -322,7 +322,7 @@
 msgstr "Промени в индекса (за подаване)"
 
 msgid "Stage Changed"
-msgstr "Индексът е променен"
+msgstr "Добавяне към индекса"
 
 msgid "Push"
 msgstr "Изтласкване"
@@ -394,7 +394,7 @@
 msgstr "Връщане към родителската версия"
 
 msgid "Visualize These Changes In The Submodule"
-msgstr "Визуализиране на промените в подмодула"
+msgstr "Визуализиране на промѐните в подмодула"
 
 msgid "Visualize Current Branch History In The Submodule"
 msgstr "Визуализация на историята на текущия клон в историята за подмодула"
@@ -535,7 +535,7 @@
 msgstr "Опции"
 
 msgid "Fetch Tracking Branch"
-msgstr "Изтегляне на промените от следения клон"
+msgstr "Изтегляне на промѐните от следения клон"
 
 msgid "Detach From Local Branch"
 msgstr "Изтриване от локалния клон"
@@ -602,14 +602,14 @@
 msgstr "Локални клони"
 
 msgid "Delete Only If Merged Into"
-msgstr "Изтриване, само ако промените са слети и другаде"
+msgstr "Изтриване, само ако промѐните са слети и другаде"
 
 msgid "Always (Do not perform merge checks)"
 msgstr "Винаги (без проверка за сливане)"
 
 #, tcl-format
 msgid "The following branches are not completely merged into %s:"
-msgstr "Не всички промени в клоните са слети в „%s“:"
+msgstr "Не всички промѐни в клоните са слети в „%s“:"
 
 msgid ""
 "Recovering deleted branches is difficult.\n"
@@ -1035,10 +1035,37 @@
 "\n"
 "You must stage at least 1 file before you can commit.\n"
 msgstr ""
-"Няма промени за подаване.\n"
+"Няма промѐни за подаване.\n"
 "\n"
 "Трябва да добавите поне един файл към индекса, за да подадете.\n"
 
+msgid "Calling pre-commit hook..."
+msgstr "Изпълняване на куката преди подаване…"
+
+msgid "Commit declined by pre-commit hook."
+msgstr "Подаването е отхвърлено от куката преди подаване."
+
+msgid ""
+"You are about to commit on a detached head. This is a potentially dangerous "
+"thing to do because if you switch to another branch you will lose your "
+"changes and it can be difficult to retrieve them later from the reflog. You "
+"should probably cancel this commit and create a new branch to continue.\n"
+" \n"
+" Do you really want to proceed with your Commit?"
+msgstr ""
+"Ще подадете към несвързан, отделѐн указател „HEAD“. Това е опасно, защото "
+"при преминаването към клон ще загубите промѐните си, като единственият начин "
+"да ги върнете ще е чрез журнала на указателите (reflog). Най-вероятно трябва "
+"да не правите това подаване, а да създадете нов клон, преди да продължите.\n"
+" \n"
+"Сигурни ли сте, че искате да извършите текущото подаване?"
+
+msgid "Calling commit-msg hook..."
+msgstr "Изпълняване на куката за съобщението при подаване…"
+
+msgid "Commit declined by commit-msg hook."
+msgstr "Подаването е отхвърлено от куката за съобщението при подаване."
+
 msgid ""
 "Please supply a commit message.\n"
 "\n"
@@ -1056,35 +1083,8 @@
 "● Втори ред: празен.\n"
 "● Останалите редове: опишете защо се налага тази промяна.\n"
 
-msgid "Calling pre-commit hook..."
-msgstr "Изпълняване на куката преди подаване…"
-
-msgid "Commit declined by pre-commit hook."
-msgstr "Подаването е отхвърлено от куката преди подаване."
-
-msgid ""
-"You are about to commit on a detached head. This is a potentially dangerous "
-"thing to do because if you switch to another branch you will lose your "
-"changes and it can be difficult to retrieve them later from the reflog. You "
-"should probably cancel this commit and create a new branch to continue.\n"
-" \n"
-" Do you really want to proceed with your Commit?"
-msgstr ""
-"Ще подадете към несвързан, отделѐн указател „HEAD“. Това е опасно, защото "
-"при преминаването към клон ще загубите промените си, като единственият начин "
-"да ги върнете ще е чрез журнала на указателите (reflog). Най-вероятно трябва "
-"да не правите това подаване, а да създадете нов клон, преди да продължите.\n"
-" \n"
-"Сигурни ли сте, че искате да извършите текущото подаване?"
-
-msgid "Calling commit-msg hook..."
-msgstr "Изпълняване на куката за съобщението при подаване…"
-
-msgid "Commit declined by commit-msg hook."
-msgstr "Подаването е отхвърлено от куката за съобщението при подаване."
-
 msgid "Committing changes..."
-msgstr "Подаване на промените…"
+msgstr "Подаване на промѐните…"
 
 msgid "write-tree failed:"
 msgstr "неуспешно запазване на дървото (write-tree):"
@@ -1103,7 +1103,7 @@
 "\n"
 "A rescan will be automatically started now.\n"
 msgstr ""
-"Няма промени за подаване.\n"
+"Няма промѐни за подаване.\n"
 "\n"
 "В това подаване не са променяни никакви файлове, а и не е подаване със "
 "сливане.\n"
@@ -1111,7 +1111,7 @@
 "Автоматично ще започне нова проверка.\n"
 
 msgid "No changes to commit."
-msgstr "Няма промени за подаване."
+msgstr "Няма промѐни за подаване."
 
 msgid "commit-tree failed:"
 msgstr "неуспешно подаване на дървото (commit-tree):"
@@ -1191,7 +1191,7 @@
 "* No differences detected; stage the file to de-list it from Unstaged "
 "Changes.\n"
 msgstr ""
-"● Няма разлики. Добавете файла към индекса, за да се извади от промените "
+"● Няма разлики. Добавете файла към индекса, за да се извади от промѐните "
 "извън индекса.\n"
 
 msgid "* Click to find other files that may have the same state.\n"
@@ -1350,15 +1350,15 @@
 
 #, tcl-format
 msgid "Revert changes in file %s?"
-msgstr "Да се махнат ли промените във файла „%s“?"
+msgstr "Да се махнат ли промѐните във файла „%s“?"
 
 #, tcl-format
 msgid "Revert changes in these %i files?"
-msgstr "Да се махнат ли промените в тези %i файла?"
+msgstr "Да се махнат ли промѐните в тези %i файла?"
 
 msgid "Any unstaged changes will be permanently lost by the revert."
 msgstr ""
-"Всички промени, които не са били добавени в индекса, ще се загубят "
+"Всички промѐни, които не са били добавени в индекса, ще се загубят "
 "безвъзвратно."
 
 msgid "Do Nothing"
@@ -1393,11 +1393,11 @@
 msgstr "%d от избраните %d файла не бяха изтрити."
 
 msgid "Reverting selected files"
-msgstr "Махане на промените в избраните файлове"
+msgstr "Махане на промѐните в избраните файлове"
 
 #, tcl-format
 msgid "Reverting %s"
-msgstr "Махане на промените в „%s“"
+msgstr "Махане на промѐните в „%s“"
 
 msgid "Goto Line:"
 msgstr "Към ред:"
@@ -1507,7 +1507,7 @@
 msgstr ""
 "Да се преустанови ли сливането?\n"
 "\n"
-"В такъв случай ●ВСИЧКИ● неподадени промени ще се загубят безвъзвратно.\n"
+"В такъв случай ●ВСИЧКИ● неподадени промѐни ще се загубят безвъзвратно.\n"
 "\n"
 "Наистина ли да се преустанови сливането?"
 
@@ -1518,17 +1518,17 @@
 "\n"
 "Continue with resetting the current changes?"
 msgstr ""
-"Да се занулят ли промените?\n"
+"Да се занулят ли промѐните?\n"
 "\n"
-"В такъв случай ●ВСИЧКИ● неподадени промени ще се загубят безвъзвратно.\n"
+"В такъв случай ●ВСИЧКИ● неподадени промѐни ще се загубят безвъзвратно.\n"
 "\n"
-"Наистина ли да се занулят промените?"
+"Наистина ли да се занулят промѐните?"
 
 msgid "Aborting"
 msgstr "Преустановяване"
 
 msgid "files reset"
-msgstr "файла със занулени промени"
+msgstr "файла със занулени промѐни"
 
 msgid "Abort failed."
 msgstr "Неуспешно преустановяване."
@@ -1766,7 +1766,7 @@
 msgstr "Незабавно доставяне"
 
 msgid "Initialize Remote Repository and Push"
-msgstr "Инициализиране на отдалеченото хранилище и изтласкване на промените"
+msgstr "Инициализиране на отдалеченото хранилище и изтласкване на промѐните"
 
 msgid "Do Nothing Else Now"
 msgstr "Да не се прави нищо"
@@ -1896,6 +1896,10 @@
 msgid "%s (%s): Create Desktop Icon"
 msgstr "%s (%s): Добавяне на икона на работния плот"
 
+#, tcl-format
+msgid "Replace existing shortcut: %s?"
+msgstr "Да се замени ли съществуващата клавишна комбинация: %s?"
+
 msgid "Cannot write shortcut:"
 msgstr "Клавишната комбинация не може да се запази:"
 
@@ -2081,7 +2085,7 @@
 
 #, tcl-format
 msgid "Fetching new changes from %s"
-msgstr "Доставяне на промените от „%s“"
+msgstr "Доставяне на промѐните от „%s“"
 
 #, tcl-format
 msgid "remote prune %s"
@@ -2095,7 +2099,7 @@
 msgstr "доставяне от всички отдалечени"
 
 msgid "Fetching new changes from all remotes"
-msgstr "Доставяне на промените от всички отдалечени хранилища"
+msgstr "Доставяне на промѐните от всички отдалечени хранилища"
 
 msgid "remote prune all remotes"
 msgstr "окастряне на следящите изтрити"
@@ -2107,7 +2111,7 @@
 
 #, tcl-format
 msgid "Pushing changes to %s"
-msgstr "Изтласкване на промените към „%s“"
+msgstr "Изтласкване на промѐните към „%s“"
 
 #, tcl-format
 msgid "Mirroring to %s"
@@ -2131,7 +2135,7 @@
 
 msgid "Force overwrite existing branch (may discard changes)"
 msgstr ""
-"Изрично презаписване на съществуващ клон (някои промени може да се загубят)"
+"Изрично презаписване на съществуващ клон (някои промѐни може да се загубят)"
 
 msgid "Use thin pack (for slow network connections)"
 msgstr "Максимална компресия (за бавни мрежови връзки)"
diff --git a/git-gui/po/glossary/bg.po b/git-gui/po/glossary/bg.po
index 8b71fad..96563ef 100644
--- a/git-gui/po/glossary/bg.po
+++ b/git-gui/po/glossary/bg.po
@@ -1,14 +1,14 @@
 # Bulgarian translation of git-gui-glossary po-file.
-# Copyright (C) 2012, 2013, 2014 Alexander Shopov <ash@kambanaria.org>.
+# Copyright (C) 2012, 2013, 2014, 2026 Alexander Shopov <ash@kambanaria.org>.
 # This file is distributed under the same license as the git package.
-# Alexander Shopov <ash@kambanaria.org>, 2012, 2013, 2014.
+# Alexander Shopov <ash@kambanaria.org>, 2012, 2013, 2014, 2026.
 #
 #
 msgid ""
 msgstr ""
 "Project-Id-Version: git-gui-glossary master\n"
-"POT-Creation-Date: 2014-01-13 21:39+0200\n"
-"PO-Revision-Date: 2014-01-13 21:39+0200\n"
+"POT-Creation-Date: 2020-01-26 22:26+0100\n"
+"PO-Revision-Date: 2026-01-24 11:18+0100\n"
 "Last-Translator: Alexander Shopov <ash@kambanaria.org>\n"
 "Language-Team: Bulgarian <dict@fsa-bg.org>\n"
 "Language: bg\n"
@@ -20,23 +20,55 @@
 #. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)"
 msgid ""
 "English Term (Dear translator: This file will never be visible to the user!)"
-msgstr "Термин"
+msgstr "Български превод на термина"
+
+#. "prematurely stop and abandon an operation"
+msgid "abort"
+msgstr "преустановявам"
 
 #. ""
 msgid "amend"
 msgstr "поправям"
 
+#. "a commit that succeeds the current one in git's graph of commits (not necessarily directly)"
+msgid "ancestor"
+msgstr "предшественик"
+
 #. ""
 msgid "annotate"
 msgstr "анотирам"
 
+#. "The person who initially created (authored) a commit"
+msgid "author"
+msgstr "автор"
+
+#. "a repository with only .git directory, without working directory"
+msgid "bare repository"
+msgstr "голо хранилище"
+
+#. "a parent version of the current file"
+msgid "base"
+msgstr "базова версия"
+
+#. ""
+msgid "bisect"
+msgstr "двоично търсене"
+
+#. "get the authors responsible for each line in a file"
+msgid "blame"
+msgstr "анотирам/анотиране"
+
+#.      ""
+msgid "blob"
+msgstr "обект-BLOB"
+
 #. "A 'branch' is an active line of development."
 msgid "branch [noun]"
-msgstr "клон, разклонение [съществително]"
+msgstr "клон [съществително]"
 
 #. ""
 msgid "branch [verb]"
-msgstr "разклонявам [глагол]"
+msgstr "създавам клон [глагол]"
 
 #. ""
 msgid "checkout [noun]"
@@ -46,6 +78,18 @@
 msgid "checkout [verb]"
 msgstr "изтегляне [глагол]"
 
+#. "to select and apply a single commit to the current HEAD without merging"
+msgid "cherry-pick"
+msgstr "отбирам"
+
+#. "a commit that directly succeeds the current one in git's graph of commits"
+msgid "child commit"
+msgstr "подаване-наследник"
+
+#. "clean the state of the git repository, often after manually stopped operation"
+msgid "cleanup"
+msgstr "зачиствам"
+
 #. ""
 msgid "clone [verb]"
 msgstr "клонирам [глагол]"
@@ -56,180 +100,80 @@
 
 #. "The action of storing a new snapshot of the project's state in the git history."
 msgid "commit [verb]"
-msgstr "подавам [съществително]"
+msgstr "подавам [глагол]"
+
+#. "a message that gets attached with any commit"
+msgid "commit message"
+msgstr "съобщение при подаване"
+
+#.   "The person who committed a commit (to the current branch), which might be different than the author."
+msgid "committer"
+msgstr "подаващ"
+
+#. "a commit that precedes the current one in git's graph of commits (not necessarily directly)"
+msgid "descendant"
+msgstr "наследник"
+
+#.       "checkout of a revision rather than some head"
+msgid "detached HEAD"
+msgstr "несвързан указател „HEAD“"
+
+#. "checkout of a revision rather than some head"
+msgid "detached checkout"
+msgstr "несвързано изтегляне"
 
 #. ""
 msgid "diff [noun]"
-msgstr "разлика/промени [съществително]"
+msgstr "разлика/промѐни [съществително]"
 
 #. ""
 msgid "diff [verb]"
-msgstr "изчислявам разлика/промени [глагол]"
+msgstr "изчислявам разлика/промѐни [глагол]"
 
-#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have."
-msgid "fast forward merge"
-msgstr "превъртащо/директно/тривиално сливане"
+#.   ""
+msgid "directory"
+msgstr "директория"
+
+#. "A fast-forward merge is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have."
+msgid "fast-forward"
+msgstr "превъртане"
 
 #. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too."
 msgid "fetch"
 msgstr "доставяне"
 
+#. "any merge strategy that works on a file by file basis"
+msgid "file level merging"
+msgstr "пофайлово сливане"
+
+#.      ""
+msgid "file"
+msgstr "файл"
+
+#. "the last revision in a branch"
+msgid "head"
+msgstr "връх (на клон, разработка)"
+
+#. "script that gets executed automatically on some event"
+msgid "hook"
+msgstr "кука"
+
 #. "One context of consecutive lines in a whole patch, which consists of many such hunks"
 msgid "hunk"
 msgstr "парче"
 
 #. "A collection of files. The index is a stored version of your working tree."
 msgid "index (in git-gui: staging area)"
-msgstr "скеле/индекс/изба"
-
-#. "A successful merge results in the creation of a new commit representing the result of the merge."
-msgid "merge [noun]"
-msgstr "сливане/обединяване [съществително]"
-
-#. "To bring the contents of another branch into the current branch."
-msgid "merge [verb]"
-msgstr "сливам/обединявам [глагол]"
-
-#. ""
-msgid "message"
-msgstr "съобщение"
-
-#. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'."
-msgid "prune"
-msgstr "окастрям"
-
-#. "Pulling a branch means to fetch it and merge it."
-msgid "pull"
-msgstr "издърпвам"
-
-#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)"
-msgid "push"
-msgstr "изтласквам"
-
-#. ""
-msgid "redo"
-msgstr "повтарям/правя наново"
-
-#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
-msgid "remote"
-msgstr "отдалечено хранилище"
-
-#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
-msgid "repository"
-msgstr "хранилище"
-
-#. ""
-msgid "reset"
-msgstr "занулявам/отменям"
-
-#. ""
-msgid "revert"
-msgstr "връщам/отменям"
-
-#. "A particular state of files and directories which was stored in the object database."
-msgid "revision"
-msgstr "версия"
-
-#. ""
-msgid "sign off"
-msgstr "подписвам"
-
-#. ""
-msgid "staging area"
-msgstr "скеле/индекс/изба"
-
-#. ""
-msgid "status"
-msgstr "състояние"
-
-#. "A ref pointing to a tag or commit object"
-msgid "tag [noun]"
-msgstr "етикет [съществително]"
-
-#. ""
-msgid "tag [verb]"
-msgstr "задавам етикет [глагол]"
-
-#. "A regular git branch that is used to follow changes from another repository."
-msgid "tracking branch"
-msgstr "следящ клон"
-
-#. ""
-msgid "undo"
-msgstr "отменям"
-
-#. ""
-msgid "update"
-msgstr "обновявам"
-
-#. ""
-msgid "verify"
-msgstr "проверявам"
-
-#. "The tree of actual checked out files."
-msgid "working copy, working tree"
-msgstr "работно копие/работно дърво"
-
-#. "a commit that succeeds the current one in git's graph of commits (not necessarily directly)"
-msgid "ancestor"
-msgstr "предшественик"
-
-#. "prematurely stop and abandon an operation"
-msgid "abort"
-msgstr "преустановявам"
-
-#. "a repository with only .git directory, without working directory"
-msgid "bare repository"
-msgstr "голо хранилище"
-
-#. "a parent version of the current file"
-msgid "base"
-msgstr "родителска версия"
-
-#. "get the authors responsible for each line in a file"
-msgid "blame"
-msgstr "анотирам/анотиране"
-
-#. "to select and apply a single commit without merging"
-msgid "cherry-pick"
-msgstr "отбирам"
-
-#. "a commit that directly succeeds the current one in git's graph of commits"
-msgid "child"
-msgstr "дете"
-
-#. "clean the state of the git repository, often after manually stopped operation"
-msgid "cleanup"
-msgstr "зачиствам"
-
-#. "a message that gets attached with any commit"
-msgid "commit message"
-msgstr "съобщение при подаване"
-
-#. "a commit that precedes the current one in git's graph of commits (not necessarily directly)"
-msgid "descendant"
-msgstr "наследник"
-
-#. "checkout of a revision rather than a some head"
-msgid "detached checkout"
-msgstr "несвързано изтегляне"
-
-#. "any merge strategy that works on a file by file basis"
-msgid "file level merging"
-msgstr "пофайлово сливане"
-
-#. "the last revision in a branch"
-msgid "head"
-msgstr "глава/връх (на клон, разработка)"
-
-#. "script that gets executed automatically on some event"
-msgid "hook"
-msgstr "кука/автоматичен скрипт"
+msgstr "индекс"
 
 #. "the first checkout during a clone operation"
 msgid "initial checkout"
 msgstr "първоначално изтегляне"
 
+#. "The very first commit in a repository"
+msgid "initial commit"
+msgstr "първоначално подаване"
+
 #. "a branch that resides in the local git repository"
 msgid "local branch"
 msgstr "локален клон"
@@ -242,26 +186,112 @@
 msgid "master branch"
 msgstr "основен клон"
 
+#. "A successful merge results in the creation of a new commit representing the result of the merge."
+msgid "merge [noun]"
+msgstr "сливане [съществително]"
+
+#. "To bring the contents of another branch into the current branch."
+msgid "merge [verb]"
+msgstr "сливам [глагол]"
+
+#. ""
+msgid "message"
+msgstr "съобщение"
+
 #. "a remote called by convention 'origin' that the current git repository has been cloned from"
 msgid "origin"
 msgstr "хранилище-източник"
 
+#.       ""
+msgid "orphan commit"
+msgstr "неродено подаване"
+
+#.  ""
+msgid "orphan reference"
+msgstr "неродѐн указател"
+
 #. "a file containing many git objects packed together"
 msgid "pack [noun]"
-msgstr "етикет"
+msgstr "пакет [глагол]"
+
+#.     "the process of creating a pack file"
+msgid "pack [verb]"
+msgstr "пакетирам [глагол]"
 
 #. "a Git object part of some pack"
 msgid "packed object"
 msgstr "пакетиран обект"
 
 #. "a commit that directly precedes the current one in git's graph of commits"
-msgid "parent"
-msgstr "родител"
+msgid "parent commit"
+msgstr "подаване-родител"
+
+msgid "patch"
+msgstr "кръпка"
+
+#. "The path to a file"
+msgid "path"
+msgstr "път"
+
+#. "Delete all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'."
+msgid "prune"
+msgstr "окастрям"
+
+#. "Pulling a branch means to fetch it and merge it."
+msgid "pull"
+msgstr "издърпвам"
+
+#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)"
+msgid "push"
+msgstr "изтласквам"
+
+#. "The process of rebasing one set of commits on top of another branch's head"
+msgid "rebase [noun]"
+msgstr "пребазиране [съществително]"
+
+#. "Re-apply one set of commits on top of another branch's head. Contrary to merge."
+msgid "rebase [verb]"
+msgstr "пребазирам [глагол]"
+
+#. ""
+msgid "redo"
+msgstr "повтарям/правя наново"
+
+#.   ""
+msgid "reference"
+msgstr "указател"
 
 #. "the log file containing all states of the HEAD reference (in other words past pristine states of the working copy)"
 msgid "reflog"
 msgstr "журнал на указателите"
 
+msgid "refmap"
+msgstr "карта с указатели"
+
+#. ""
+msgid "refspec"
+msgstr "указател на версия"
+
+#. "The adjective for anything which is outside of the current (local) repository"
+msgid "remote [adj]"
+msgstr "отдалечен [прилагателно]"
+
+#.       "A branch in any other ('remote') repository"
+msgid "remote branch"
+msgstr "отдалечен клон"
+
+#.   "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
+msgid "remote repository"
+msgstr "отдалечено/насрещно хранилище"
+
+#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
+msgid "repository"
+msgstr "хранилище"
+
+#. ""
+msgid "reset"
+msgstr "занулявам"
+
 #. "decide which changes from alternative versions of a file should persist in Git"
 msgid "resolve (a conflict)"
 msgstr "коригирам (конфликт)"
@@ -270,18 +300,102 @@
 msgid "revert changes"
 msgstr "връщане на оригинала"
 
+#. ""
+msgid "revert"
+msgstr "отменям"
+
 #. "expression that signifies a revision in git"
 msgid "revision expression"
 msgstr "израз за версия"
 
+#. "A particular state of files and directories which was stored in the object database."
+msgid "revision"
+msgstr "версия"
+
+#. ""
+msgid "sign off"
+msgstr "подписвам"
+
+#.     "see: staging area. In some areas of git this is called 'index'."
+msgid "stage [noun], index"
+msgstr "индекс [съществително]"
+
 #. "add some content of files and directories to the staging area in preparation for a commit"
-msgid "stage/unstage"
-msgstr "(добавяне) към скелето за подаване/изваждане от скелето за подаване"
+msgid "stage [verb]"
+msgstr "добавям в индекса [глагол]"
+
+#. "The place where changes from files are marked to be included for the next commit. In some areas of git this is called 'index'."
+msgid "staging area"
+msgstr "индекс"
+
+#. "The place (stack) where changes can be temporarily saved without committing"
+msgid "stash [noun]"
+msgstr "скатано [съществително]"
 
 #. "temporarily save changes in a stack without committing"
-msgid "stash"
-msgstr "скатавам промени"
+msgid "stash [verb]"
+msgstr "скатано [глагол]"
+
+#. ""
+msgid "status"
+msgstr "състояние"
+
+#. ""
+msgid "submodule"
+msgstr "подмодул"
+
+#. "A ref pointing to some commit object. In other words: A label on a specific commit."
+msgid "tag [noun]"
+msgstr "етикет [съществително]"
+
+#. "The process of creating a tag at a specific commit object"
+msgid "tag [verb]"
+msgstr "задавам етикет [глагол]"
+
+#. "The person who created a tag"
+msgid "tagger"
+msgstr "етикиращ"
 
 #. "file whose content is tracked/not tracked by git"
 msgid "tracked/untracked"
 msgstr "следен/неследен"
+
+#. "A regular git branch that is used to follow changes from another repository."
+msgid "tracking branch"
+msgstr "следящ клон"
+
+#. ""
+msgid "trailer"
+msgstr "епилог"
+
+#. "1. tree object, 2. directory tree"
+msgid "tree"
+msgstr "обект-дърво/дърво"
+
+#. ""
+msgid "undo"
+msgstr "отменям"
+
+#. "Remove content of files from the staging area again so that it will not be part of the next commit"
+msgid "unstage"
+msgstr "изваждено от индекса"
+
+#. "Retrieving the temporarily saved changes back again from the stash"
+msgid "unstash [verb]"
+msgstr "изваждам от индекса [глагол]"
+
+#. ""
+msgid "update"
+msgstr "обновявам"
+
+#. ""
+msgid "upstream branch"
+msgstr "следен клон"
+
+#. ""
+msgid "verify"
+msgstr "проверявам"
+
+#. "The tree of actual checked out files."
+msgid "working directory, working copy, working tree"
+msgstr "работна директория, работно копие, работно дърво"
diff --git a/git-send-email.perl b/git-send-email.perl
index cd4b316..bb8ddd1 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -23,6 +23,7 @@
 use Git::LoadCPAN::Error qw(:try);
 use Git;
 use Git::I18N;
+use Encode qw(find_encoding);
 
 Getopt::Long::Configure qw/ pass_through /;
 
@@ -66,6 +67,8 @@
     --smtp-ssl-cert-path    <str>  * Path to ca-certificates (either directory or file).
                                      Pass an empty string to disable certificate
                                      verification.
+    --smtp-ssl-client-cert  <str>  * Path to the client certificate file
+    --smtp-ssl-client-key   <str>  * Path to the private key file for the client certificate
     --smtp-domain           <str>  * The domain name sent to HELO/EHLO handshake
     --smtp-auth             <str>  * Space-separated list of allowed AUTH mechanisms, or
                                      "none" to disable authentication.
@@ -279,6 +282,7 @@
 my ($to_cmd, $cc_cmd, $header_cmd);
 my ($smtp_server, $smtp_server_port, @smtp_server_options);
 my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path);
+my ($smtp_ssl_client_cert, $smtp_ssl_client_key);
 my ($batch_size, $relogin_delay);
 my ($identity, $aliasfiletype, @alias_files, $smtp_domain, $smtp_auth);
 my ($imap_sent_folder);
@@ -350,6 +354,8 @@
 my %config_path_settings = (
     "aliasesfile" => \@alias_files,
     "smtpsslcertpath" => \$smtp_ssl_cert_path,
+    "smtpsslclientcert" => \$smtp_ssl_client_cert,
+    "smtpsslclientkey" => \$smtp_ssl_client_key,
     "mailmap.file" => \$mailmap_file,
     "mailmap.blob" => \$mailmap_blob,
 );
@@ -531,6 +537,8 @@
 		    "smtp-ssl" => sub { $smtp_encryption = 'ssl' },
 		    "smtp-encryption=s" => \$smtp_encryption,
 		    "smtp-ssl-cert-path=s" => \$smtp_ssl_cert_path,
+		    "smtp-ssl-client-cert=s" => \$smtp_ssl_client_cert,
+		    "smtp-ssl-client-key=s" => \$smtp_ssl_client_key,
 		    "smtp-debug:i" => \$debug_net_smtp,
 		    "smtp-domain:s" => \$smtp_domain,
 		    "smtp-auth=s" => \$smtp_auth,
@@ -1044,9 +1052,27 @@
 	foreach my $f (sort keys %broken_encoding) {
 		print "    $f\n";
 	}
-	$auto_8bit_encoding = ask(__("Which 8bit encoding should I declare [UTF-8]? "),
-				  valid_re => qr/.{4}/, confirm_only => 1,
-				  default => "UTF-8");
+	while (1) {
+		my $encoding = ask(
+			__("Declare which 8bit encoding to use [default: UTF-8]? "),
+			valid_re => qr/^\S+$/,
+			default  => "UTF-8");
+		next unless defined $encoding;
+		if (find_encoding($encoding)) {
+			$auto_8bit_encoding = $encoding;
+			last;
+		}
+		my $yesno = ask(
+			sprintf(
+			__("'%s' does not appear to be a valid charset name. Use it anyway [y/N]? "),
+			$encoding),
+			valid_re => qr/^(?:y|n)/i,
+			default => "n");
+		if (defined $yesno && $yesno =~ /^y/i) {
+			$auto_8bit_encoding = $encoding;
+			last;
+		}
+	}
 }
 
 if (!$force) {
@@ -1474,6 +1500,8 @@
 						user     => $cred->{'username'},
 						pass     => $cred->{'password'},
 						authname => $cred->{'username'},
+						host     => $smtp_server,
+						(defined $smtp_server_port ? (port => $smtp_server_port) : ()),
 					}
 				);
 				$result = $smtp->auth($sasl);
@@ -1520,6 +1548,8 @@
 }
 
 sub ssl_verify_params {
+	my %ret = ();
+
 	eval {
 		require IO::Socket::SSL;
 		IO::Socket::SSL->import(qw/SSL_VERIFY_PEER SSL_VERIFY_NONE/);
@@ -1531,20 +1561,36 @@
 
 	if (!defined $smtp_ssl_cert_path) {
 		# use the OpenSSL defaults
-		return (SSL_verify_mode => SSL_VERIFY_PEER());
+		$ret{SSL_verify_mode} = SSL_VERIFY_PEER();
+	}
+	else {
+		if ($smtp_ssl_cert_path eq "") {
+			$ret{SSL_verify_mode} = SSL_VERIFY_NONE();
+		} elsif (-d $smtp_ssl_cert_path) {
+			$ret{SSL_verify_mode} = SSL_VERIFY_PEER();
+			$ret{SSL_ca_path} = $smtp_ssl_cert_path;
+		} elsif (-f $smtp_ssl_cert_path) {
+			$ret{SSL_verify_mode} = SSL_VERIFY_PEER();
+			$ret{SSL_ca_file} = $smtp_ssl_cert_path;
+		} else {
+			die sprintf(__("CA path \"%s\" does not exist"), $smtp_ssl_cert_path);
+		}
 	}
 
-	if ($smtp_ssl_cert_path eq "") {
-		return (SSL_verify_mode => SSL_VERIFY_NONE());
-	} elsif (-d $smtp_ssl_cert_path) {
-		return (SSL_verify_mode => SSL_VERIFY_PEER(),
-			SSL_ca_path => $smtp_ssl_cert_path);
-	} elsif (-f $smtp_ssl_cert_path) {
-		return (SSL_verify_mode => SSL_VERIFY_PEER(),
-			SSL_ca_file => $smtp_ssl_cert_path);
-	} else {
-		die sprintf(__("CA path \"%s\" does not exist"), $smtp_ssl_cert_path);
+	if (defined $smtp_ssl_client_cert) {
+		$ret{SSL_cert_file} = $smtp_ssl_client_cert;
 	}
+	if (defined $smtp_ssl_client_key) {
+		if (!defined $smtp_ssl_client_cert) {
+			# Accept the client key only when a certificate is given.
+			# We die here because this case is a user error.
+			die sprintf(__("Only client key \"%s\" specified"),
+				    $smtp_ssl_client_key);
+		}
+		$ret{SSL_key_file} = $smtp_ssl_client_key;
+	}
+
+	return %ret;
 }
 
 sub file_name_is_absolute {
diff --git a/git.c b/git.c
index c5fad56..2b212e6 100644
--- a/git.c
+++ b/git.c
@@ -119,7 +119,7 @@ static int list_cmds(const char *spec)
 	}
 	for (size_t i = 0; i < list.nr; i++)
 		puts(list.items[i].string);
-	string_list_clear(&list, 0);
+	string_list_clear(&list, 1);
 	return 0;
 }
 
@@ -586,7 +586,8 @@ static struct cmd_struct commands[] = {
 	{ "grep", cmd_grep, RUN_SETUP_GENTLY },
 	{ "hash-object", cmd_hash_object },
 	{ "help", cmd_help },
-	{ "hook", cmd_hook, RUN_SETUP },
+	{ "history", cmd_history, RUN_SETUP },
+	{ "hook", cmd_hook, RUN_SETUP_GENTLY },
 	{ "index-pack", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },
 	{ "init", cmd_init_db },
 	{ "init-db", cmd_init_db },
diff --git a/gitk-git/gitk b/gitk-git/gitk
index c02db01..cbaaee9 100755
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -2131,12 +2131,14 @@
     return $w
 }
 
-proc make_transient {window origin} {
+proc make_transient {window origin {geometry ""}} {
     wm transient $window $origin
 
-    # Windows fails to place transient windows normally, so
-    # schedule a callback to center them on the parent.
-    if {[tk windowingsystem] eq {win32}} {
+    if {$geometry ne ""} {
+        after idle [list wm geometry $window $geometry]
+    } elseif {[tk windowingsystem] eq {win32}} {
+        # Windows fails to place transient windows normally, so
+        # schedule a callback to center them on the parent.
         after idle [list tk::PlaceWindow $window widget $origin]
     }
 }
@@ -2723,17 +2725,9 @@
     .pwbottom add .bright
     .ctop add .pwbottom
 
-    # restore window width & height if known
+    # restore window position if known
     if {[info exists geometry(main)]} {
-        if {[scan $geometry(main) "%dx%d" w h] >= 2} {
-            if {$w > [winfo screenwidth .]} {
-                set w [winfo screenwidth .]
-            }
-            if {$h > [winfo screenheight .]} {
-                set h [winfo screenheight .]
-            }
-            wm geometry . "${w}x$h"
-        }
+        wm geometry . "$geometry(main)"
     }
 
     if {[info exists geometry(state)] && $geometry(state) eq "zoomed"} {
@@ -3073,6 +3067,11 @@
         puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sashpos 1] 1\""
         puts $f "set geometry(botwidth) [winfo width .bleft]"
         puts $f "set geometry(botheight) [winfo height .bleft]"
+        unset -nocomplain geometry
+        global geometry
+        if {[info exists geometry(showrefs)]} {
+            puts $f "set geometry(showrefs) $geometry(showrefs)"
+        }
 
         array set view_save {}
         array set views {}
@@ -3788,6 +3787,34 @@
                "revision $diffid"]
 }
 
+proc check_for_renames_in_diff {filepath} { # renames
+    global difffilestart ctext
+
+    set filename [file tail $filepath]
+    set renames {}
+
+    foreach loc $difffilestart {
+        set loclineend [string map {.0 .end} $loc]
+        set fromlineloc "$loc + 2 lines"
+        set tolineloc "$loc + 3 lines"
+        set renfromline [$ctext get $fromlineloc [string map {.0 .end} $fromlineloc]]
+        set rentoline [$ctext get $tolineloc [string map {.0 .end} $tolineloc]]
+        if {[string equal -length 12 "rename from " $renfromline]
+            && [string equal -length 10 "rename to " $rentoline]} {
+            set renfrom [string range $renfromline 12 end]
+            set rento [string range $rentoline 10 end]
+            if {[string first $filename $renfrom] != -1
+                || [string first $filename $rento] != -1} {
+                lappend renames $renfrom
+                lappend renames $rento
+                break
+            }
+        }
+    }
+
+    return $renames
+}
+
 proc external_diff {} {
     global nullid nullid2
     global flist_menu_file
@@ -3818,8 +3845,16 @@
     if {$diffdir eq {}} return
 
     # gather files to diff
-    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
-    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
+    set renames [check_for_renames_in_diff $flist_menu_file]
+    set renamefrom [lindex $renames 0]
+    set renameto [lindex $renames 1]
+    if {$renamefrom ne {} && $renameto ne {}} {
+        set difffromfile [external_diff_get_one_file $diffidfrom $renamefrom $diffdir]
+        set difftofile [external_diff_get_one_file $diffidto $renameto $diffdir]
+    } else {
+        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
+        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
+    }
 
     if {$difffromfile ne {} && $difftofile ne {}} {
         set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]
@@ -6806,7 +6841,7 @@
             set xl [expr {$xl - $delta/2}]
             $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
                 -width 1 -outline black -fill $col -tags tag.$id
-            if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
+            if {[regexp {^(remotes/[^/]*/|remotes/)} $tag match remoteprefix]} {
                 set rwid [font measure mainfont $remoteprefix]
                 set xi [expr {$x + 1}]
                 set yti [expr {$yt + 1}]
@@ -8296,7 +8331,7 @@
         if {![regexp {^diff (--cc|--git) } $line m type]} {
             set line [convertfrom utf-8 $line]
             $ctext insert end "$line\n" hunksep
-            continue
+            return
         }
         # start of a new file
         set diffinhdr 1
@@ -8401,6 +8436,7 @@
             if {$i >= 0} {
                 setinlist difffilestart $i $curdiffstart
             }
+            set line "rename from $fname"
         } elseif {![string compare -length 10 $line "rename to "] ||
                   ![string compare -length 8 $line "copy to "]} {
             set fname [string range $line [expr 4 + [string first " to " $line] ] end]
@@ -8408,6 +8444,13 @@
                 set fname [lindex $fname 0]
             }
             makediffhdr $fname $ids
+            set line "[lindex $line 0] to $fname"
+        } elseif {![string compare -length 10 $line "copy from "]} {
+            set fname [string range $line 10 end]
+            if {[string index $fname 0] eq "\""} {
+                set fname [lindex $fname 0]
+            }
+            set line "copy from $fname"
         } elseif {[string compare -length 3 $line "---"] == 0} {
             # do nothing
             return
@@ -10160,6 +10203,7 @@
 proc showrefs {} {
     global showrefstop bgcolor fgcolor selectbgcolor
     global bglist fglist reflistfilter reflist maincursor
+    global geometry
 
     set top .showrefs
     set showrefstop $top
@@ -10170,7 +10214,11 @@
     }
     ttk_toplevel $top
     wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
-    make_transient $top .
+    if {[info exists geometry(showrefs)]} {
+        make_transient $top . $geometry(showrefs)
+    } else {
+        make_transient $top .
+    }
     text $top.list -background $bgcolor -foreground $fgcolor \
         -selectbackground $selectbgcolor -font mainfont \
         -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
@@ -10206,6 +10254,9 @@
     bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
     set reflist {}
     refill_reflist
+    # avoid <Configure> being bound to child windows
+    bindtags $top [linsert [bindtags $top] 1 bind$top]
+    bind bind$top <Configure> {set geometry(showrefs) [wm geometry %W]}
 }
 
 proc sel_reflist {w x y} {
diff --git a/gitk-git/meson.build b/gitk-git/meson.build
index ca3c0ce..aecc068 100644
--- a/gitk-git/meson.build
+++ b/gitk-git/meson.build
@@ -25,6 +25,6 @@
   install_dir: get_option('bindir'),
 )
 
-if find_program('msgfmt').found()
+if find_program('msgfmt', required: false).found()
   subdir('po')
 endif
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index b5490df..fde8045 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -4214,6 +4214,7 @@
 <head>
 <meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/>
 <meta name="robots" content="index, nofollow"/>
+<meta name="viewport" content="width=device-width, initial-scale=1"/>
 <title>$title</title>
 EOF
 	# the stylesheet, favicon etc urls won't work correctly with path_info
diff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css
index 48d2e51..e2e6dd9 100644
--- a/gitweb/static/gitweb.css
+++ b/gitweb/static/gitweb.css
@@ -42,7 +42,7 @@
 }
 
 div.page_header {
-	height: 25px;
+	min-height: 25px;
 	padding: 8px;
 	font-size: 150%;
 	font-weight: bold;
@@ -73,11 +73,17 @@
 }
 
 div.page_footer {
-	height: 22px;
+	min-height: 22px;
 	padding: 4px 8px;
 	background-color: #d9d8d1;
 }
 
+div.page_footer::after {
+	content: "";
+	display: table;
+	clear: both;
+}
+
 div.page_footer_text {
 	line-height: 22px;
 	float: left;
@@ -123,6 +129,7 @@
 
 div.log_body {
 	padding: 8px 8px 8px 150px;
+	overflow-wrap: anywhere;
 }
 
 span.age {
@@ -684,3 +691,66 @@
 .kwb    { color:#830000; }
 .kwc    { color:#000000; font-weight:bold; }
 .kwd    { color:#010181; }
+
+@media (max-width: 768px) {
+	div.page_body {
+		overflow-x: auto;
+		-webkit-overflow-scrolling: touch;
+	}
+
+	div.page_body div.pre {
+		min-width: max-content;
+	}
+
+	div.projsearch {
+		padding: 0 8px;
+		box-sizing: border-box;
+	}
+
+	div.projsearch input[type="text"] {
+		max-width: 100%;
+		box-sizing: border-box;
+	}
+
+	div.title_text {
+		overflow-x: auto;
+		-webkit-overflow-scrolling: touch;
+		padding-left: 4px;
+		padding-right: 4px;
+		box-sizing: border-box;
+	}
+
+	div.title_text table.object_header {
+		width: max-content;
+	}
+
+	div.log_body {
+		padding: 8px;
+		clear: left;
+	}
+
+	div.patchset div.patch {
+		width: max-content;
+		min-width: 100%;
+	}
+
+	div.diff.header {
+		padding: 4px 8px 2px 8px;
+		white-space: nowrap;
+		overflow-wrap: normal;
+	}
+
+	div.diff.extended_header {
+		padding: 2px 8px;
+		white-space: nowrap;
+		overflow-wrap: normal;
+	}
+
+	div.diff.ctx,
+	div.diff.add,
+	div.diff.rem,
+	div.diff.chunk_header {
+		padding: 0 8px;
+		white-space: pre;
+	}
+}
diff --git a/gpg-interface.c b/gpg-interface.c
index f680ed3..7e6a152 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -382,7 +382,8 @@ static int verify_gpg_signed_buffer(struct signature_check *sigc,
 
 	delete_tempfile(&temp);
 
-	ret |= !strstr(gpg_stdout.buf, "\n[GNUPG:] GOODSIG ");
+	ret |= !strstr(gpg_stdout.buf, "\n[GNUPG:] GOODSIG ") &&
+	       !strstr(gpg_stdout.buf, "\n[GNUPG:] EXPKEYSIG ");
 	sigc->output = strbuf_detach(&gpg_stderr, NULL);
 	sigc->gpg_status = strbuf_detach(&gpg_stdout, NULL);
 
@@ -398,7 +399,7 @@ static void parse_ssh_output(struct signature_check *sigc)
 {
 	const char *line, *principal, *search;
 	char *to_free;
-	char *key = NULL;
+	const char *key;
 
 	/*
 	 * ssh-keygen output should be:
@@ -680,7 +681,7 @@ int check_signature(struct signature_check *sigc,
 	if (status && !sigc->output)
 		return !!status;
 
-	status |= sigc->result != 'G';
+	status |= sigc->result != 'G' && sigc->result != 'Y';
 	status |= sigc->trust_level < configured_min_trust_level;
 
 	return !!status;
@@ -794,8 +795,16 @@ static int git_gpg_config(const char *var, const char *value,
 		fmtname = "ssh";
 
 	if (fmtname) {
+		char *program;
+		int status;
+
 		fmt = get_format_by_name(fmtname);
-		return git_config_pathname((char **) &fmt->program, var, value);
+		status = git_config_pathname(&program, var, value);
+		if (status)
+			return status;
+		if (program)
+			fmt->program = program;
+		return status;
 	}
 
 	return 0;
@@ -1146,6 +1155,8 @@ int parse_sign_mode(const char *arg, enum sign_mode *mode)
 		*mode = SIGN_WARN_STRIP;
 	else if (!strcmp(arg, "strip"))
 		*mode = SIGN_STRIP;
+	else if (!strcmp(arg, "strip-if-invalid"))
+		*mode = SIGN_STRIP_IF_INVALID;
 	else
 		return -1;
 	return 0;
diff --git a/gpg-interface.h b/gpg-interface.h
index ead1ed6..789d1ff 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -111,6 +111,7 @@ enum sign_mode {
 	SIGN_VERBATIM,
 	SIGN_WARN_STRIP,
 	SIGN_STRIP,
+	SIGN_STRIP_IF_INVALID,
 };
 
 /*
diff --git a/hash.c b/hash.c
index 4a04ecb..553f200 100644
--- a/hash.c
+++ b/hash.c
@@ -241,7 +241,49 @@ const char *empty_tree_oid_hex(const struct git_hash_algo *algop)
 	return oid_to_hex_r(buf, algop->empty_tree);
 }
 
-int hash_algo_by_name(const char *name)
+const struct git_hash_algo *hash_algo_ptr_by_number(uint32_t algo)
+{
+	if (algo >= GIT_HASH_NALGOS)
+		return NULL;
+	return &hash_algos[algo];
+}
+
+struct git_hash_ctx *git_hash_alloc(void)
+{
+	return xmalloc(sizeof(struct git_hash_ctx));
+}
+
+void git_hash_free(struct git_hash_ctx *ctx)
+{
+	free(ctx);
+}
+
+void git_hash_init(struct git_hash_ctx *ctx, const struct git_hash_algo *algop)
+{
+	algop->init_fn(ctx);
+}
+
+void git_hash_clone(struct git_hash_ctx *dst, const struct git_hash_ctx *src)
+{
+	src->algop->clone_fn(dst, src);
+}
+
+void git_hash_update(struct git_hash_ctx *ctx, const void *in, size_t len)
+{
+	ctx->algop->update_fn(ctx, in, len);
+}
+
+void git_hash_final(unsigned char *hash, struct git_hash_ctx *ctx)
+{
+	ctx->algop->final_fn(hash, ctx);
+}
+
+void git_hash_final_oid(struct object_id *oid, struct git_hash_ctx *ctx)
+{
+	ctx->algop->final_oid_fn(oid, ctx);
+}
+
+uint32_t hash_algo_by_name(const char *name)
 {
 	if (!name)
 		return GIT_HASH_UNKNOWN;
@@ -251,7 +293,7 @@ int hash_algo_by_name(const char *name)
 	return GIT_HASH_UNKNOWN;
 }
 
-int hash_algo_by_id(uint32_t format_id)
+uint32_t hash_algo_by_id(uint32_t format_id)
 {
 	for (size_t i = 1; i < GIT_HASH_NALGOS; i++)
 		if (format_id == hash_algos[i].format_id)
@@ -259,7 +301,7 @@ int hash_algo_by_id(uint32_t format_id)
 	return GIT_HASH_UNKNOWN;
 }
 
-int hash_algo_by_length(size_t len)
+uint32_t hash_algo_by_length(size_t len)
 {
 	for (size_t i = 1; i < GIT_HASH_NALGOS; i++)
 		if (len == hash_algos[i].rawsz)
diff --git a/hash.h b/hash.h
index fae966b..d51efce 100644
--- a/hash.h
+++ b/hash.h
@@ -211,7 +211,7 @@ static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *s
 
 struct object_id {
 	unsigned char hash[GIT_MAX_RAWSZ];
-	int algo;	/* XXX requires 4-byte alignment */
+	uint32_t algo;	/* XXX requires 4-byte alignment */
 };
 
 #define GET_OID_QUIETLY                  01
@@ -320,37 +320,25 @@ struct git_hash_algo {
 };
 extern const struct git_hash_algo hash_algos[GIT_HASH_NALGOS];
 
-static inline void git_hash_clone(struct git_hash_ctx *dst, const struct git_hash_ctx *src)
-{
-	src->algop->clone_fn(dst, src);
-}
-
-static inline void git_hash_update(struct git_hash_ctx *ctx, const void *in, size_t len)
-{
-	ctx->algop->update_fn(ctx, in, len);
-}
-
-static inline void git_hash_final(unsigned char *hash, struct git_hash_ctx *ctx)
-{
-	ctx->algop->final_fn(hash, ctx);
-}
-
-static inline void git_hash_final_oid(struct object_id *oid, struct git_hash_ctx *ctx)
-{
-	ctx->algop->final_oid_fn(oid, ctx);
-}
-
+void git_hash_init(struct git_hash_ctx *ctx, const struct git_hash_algo *algop);
+void git_hash_clone(struct git_hash_ctx *dst, const struct git_hash_ctx *src);
+void git_hash_update(struct git_hash_ctx *ctx, const void *in, size_t len);
+void git_hash_final(unsigned char *hash, struct git_hash_ctx *ctx);
+void git_hash_final_oid(struct object_id *oid, struct git_hash_ctx *ctx);
+const struct git_hash_algo *hash_algo_ptr_by_number(uint32_t algo);
+struct git_hash_ctx *git_hash_alloc(void);
+void git_hash_free(struct git_hash_ctx *ctx);
 /*
  * Return a GIT_HASH_* constant based on the name.  Returns GIT_HASH_UNKNOWN if
  * the name doesn't match a known algorithm.
  */
-int hash_algo_by_name(const char *name);
+uint32_t hash_algo_by_name(const char *name);
 /* Identical, except based on the format ID. */
-int hash_algo_by_id(uint32_t format_id);
+uint32_t hash_algo_by_id(uint32_t format_id);
 /* Identical, except based on the length. */
-int hash_algo_by_length(size_t len);
+uint32_t hash_algo_by_length(size_t len);
 /* Identical, except for a pointer to struct git_hash_algo. */
-static inline int hash_algo_by_ptr(const struct git_hash_algo *p)
+static inline uint32_t hash_algo_by_ptr(const struct git_hash_algo *p)
 {
 	size_t i;
 	for (i = 0; i < GIT_HASH_NALGOS; i++) {
diff --git a/hashmap.c b/hashmap.c
index a711377..3b5d6f1 100644
--- a/hashmap.c
+++ b/hashmap.c
@@ -194,7 +194,7 @@ void hashmap_partial_clear_(struct hashmap *map, ssize_t entry_offset)
 		return;
 	if (entry_offset >= 0)  /* called by hashmap_clear_entries */
 		free_individual_entries(map, entry_offset);
-	memset(map->table, 0, map->tablesize * sizeof(struct hashmap_entry *));
+	MEMZERO_ARRAY(map->table, map->tablesize);
 	map->shrink_at = 0;
 	map->private_size = 0;
 }
diff --git a/help.c b/help.c
index 5854dd4..3e59d07 100644
--- a/help.c
+++ b/help.c
@@ -20,6 +20,8 @@
 #include "prompt.h"
 #include "fsmonitor-ipc.h"
 #include "repository.h"
+#include "alias.h"
+#include "utf8.h"
 
 #ifndef NO_CURL
 #include "git-curl-compat.h" /* For LIBCURL_VERSION only */
@@ -107,7 +109,7 @@ static void print_command_list(const struct cmdname_help *cmds,
 
 	for (i = 0; cmds[i].name; i++) {
 		if (cmds[i].category & mask) {
-			size_t len = strlen(cmds[i].name);
+			size_t len = utf8_strwidth(cmds[i].name);
 			printf("   %s   ", cmds[i].name);
 			if (longest > len)
 				mput_char(' ', longest - len);
@@ -420,8 +422,7 @@ void list_cmds_by_config(struct string_list *list)
 	if (repo_config_get_string_tmp(the_repository, "completion.commands", &cmd_list))
 		return;
 
-	string_list_sort(list);
-	string_list_remove_duplicates(list, 0);
+	string_list_sort_u(list, 1);
 
 	while (*cmd_list) {
 		struct strbuf sb = STRBUF_INIT;
@@ -469,20 +470,6 @@ void list_developer_interfaces_help(void)
 	putchar('\n');
 }
 
-static int get_alias(const char *var, const char *value,
-		     const struct config_context *ctx UNUSED, void *data)
-{
-	struct string_list *list = data;
-
-	if (skip_prefix(var, "alias.", &var)) {
-		if (!value)
-			return config_error_nonbool(var);
-		string_list_append(list, var)->util = xstrdup(value);
-	}
-
-	return 0;
-}
-
 static void list_all_cmds_help_external_commands(void)
 {
 	struct string_list others = STRING_LIST_INIT_DUP;
@@ -502,11 +489,11 @@ static void list_all_cmds_help_aliases(int longest)
 	struct cmdname_help *aliases;
 	int i;
 
-	repo_config(the_repository, get_alias, &alias_list);
+	list_aliases(&alias_list);
 	string_list_sort(&alias_list);
 
 	for (i = 0; i < alias_list.nr; i++) {
-		size_t len = strlen(alias_list.items[i].string);
+		size_t len = utf8_strwidth(alias_list.items[i].string);
 		if (longest < len)
 			longest = len;
 	}
@@ -587,7 +574,8 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 				  void *cb)
 {
 	struct help_unknown_cmd_config *cfg = cb;
-	const char *p;
+	const char *subsection, *key;
+	size_t subsection_len;
 
 	if (!strcmp(var, "help.autocorrect")) {
 		int v = parse_autocorrect(value);
@@ -602,8 +590,18 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 	}
 
 	/* Also use aliases for command lookup */
-	if (skip_prefix(var, "alias.", &p))
-		add_cmdname(&cfg->aliases, p, strlen(p));
+	if (!parse_config_key(var, "alias", &subsection, &subsection_len,
+			      &key)) {
+		if (subsection) {
+			/* [alias "name"] command = value */
+			if (!strcmp(key, "command"))
+				add_cmdname(&cfg->aliases, subsection,
+					    subsection_len);
+		} else {
+			/* alias.name = value */
+			add_cmdname(&cfg->aliases, key, strlen(key));
+		}
+	}
 
 	return 0;
 }
@@ -799,6 +797,9 @@ void get_version_info(struct strbuf *buf, int show_build_options)
 
 		if (fsmonitor_ipc__is_supported())
 			strbuf_addstr(buf, "feature: fsmonitor--daemon\n");
+#if !defined NO_GETTEXT
+		strbuf_addstr(buf, "gettext: enabled\n");
+#endif
 #if defined LIBCURL_VERSION
 		strbuf_addf(buf, "libcurl: %s\n", LIBCURL_VERSION);
 #endif
@@ -851,18 +852,16 @@ struct similar_ref_cb {
 	struct string_list *similar_refs;
 };
 
-static int append_similar_ref(const char *refname, const char *referent UNUSED,
-			      const struct object_id *oid UNUSED,
-			      int flags UNUSED, void *cb_data)
+static int append_similar_ref(const struct reference *ref, void *cb_data)
 {
 	struct similar_ref_cb *cb = (struct similar_ref_cb *)(cb_data);
-	char *branch = strrchr(refname, '/') + 1;
+	const char *branch = strrchr(ref->name, '/') + 1;
 
 	/* A remote branch of the same name is deemed similar */
-	if (starts_with(refname, "refs/remotes/") &&
+	if (starts_with(ref->name, "refs/remotes/") &&
 	    !strcmp(branch, cb->base_ref))
 		string_list_append_nodup(cb->similar_refs,
-					 refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), refname, 1));
+					 refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), ref->name, 1));
 	return 0;
 }
 
diff --git a/hook.c b/hook.c
index b3de104..2c8252b 100644
--- a/hook.c
+++ b/hook.c
@@ -4,9 +4,11 @@
 #include "gettext.h"
 #include "hook.h"
 #include "path.h"
+#include "parse.h"
 #include "run-command.h"
 #include "config.h"
 #include "strbuf.h"
+#include "strmap.h"
 #include "environment.h"
 #include "setup.h"
 
@@ -16,6 +18,9 @@ const char *find_hook(struct repository *r, const char *name)
 
 	int found_hook;
 
+	if (!r || !r->gitdir)
+		return NULL;
+
 	repo_git_path_replace(r, &path, "hooks/%s", name);
 	found_hook = access(path.buf, X_OK) >= 0;
 #ifdef STRIP_EXTENSION
@@ -47,42 +52,381 @@ const char *find_hook(struct repository *r, const char *name)
 	return path.buf;
 }
 
+static void hook_clear(struct hook *h, cb_data_free_fn cb_data_free)
+{
+	if (!h)
+		return;
+
+	if (h->kind == HOOK_TRADITIONAL)
+		free((void *)h->u.traditional.path);
+	else if (h->kind == HOOK_CONFIGURED) {
+		free((void *)h->u.configured.friendly_name);
+		free((void *)h->u.configured.command);
+	}
+
+	if (cb_data_free)
+		cb_data_free(h->feed_pipe_cb_data);
+
+	free(h);
+}
+
+void hook_list_clear(struct string_list *hooks, cb_data_free_fn cb_data_free)
+{
+	struct string_list_item *item;
+
+	for_each_string_list_item(item, hooks)
+		hook_clear(item->util, cb_data_free);
+
+	string_list_clear(hooks, 0);
+}
+
+/* Helper to detect and add default "traditional" hooks from the hookdir. */
+static void list_hooks_add_default(struct repository *r, const char *hookname,
+				   struct string_list *hook_list,
+				   struct run_hooks_opt *options)
+{
+	const char *hook_path = find_hook(r, hookname);
+	struct hook *h;
+
+	if (!hook_path)
+		return;
+
+	h = xcalloc(1, sizeof(struct hook));
+
+	/*
+	 * If the hook is to run in a specific dir, a relative path can
+	 * become invalid in that dir, so convert to an absolute path.
+	 */
+	if (options && options->dir)
+		hook_path = absolute_path(hook_path);
+
+	/* Setup per-hook internal state cb data */
+	if (options && options->feed_pipe_cb_data_alloc)
+		h->feed_pipe_cb_data = options->feed_pipe_cb_data_alloc(options->feed_pipe_ctx);
+
+	h->kind = HOOK_TRADITIONAL;
+	h->u.traditional.path = xstrdup(hook_path);
+
+	string_list_append(hook_list, hook_path)->util = h;
+}
+
+static void unsorted_string_list_remove(struct string_list *list,
+					const char *str)
+{
+	struct string_list_item *item = unsorted_string_list_lookup(list, str);
+	if (item)
+		unsorted_string_list_delete_item(list, item - list->items, 0);
+}
+
+/*
+ * Callback struct to collect all hook.* keys in a single config pass.
+ * commands: friendly-name to command map.
+ * event_hooks: event-name to list of friendly-names map.
+ * disabled_hooks: set of friendly-names with hook.name.enabled = false.
+ */
+struct hook_all_config_cb {
+	struct strmap commands;
+	struct strmap event_hooks;
+	struct string_list disabled_hooks;
+};
+
+/* repo_config() callback that collects all hook.* configuration in one pass. */
+static int hook_config_lookup_all(const char *key, const char *value,
+				  const struct config_context *ctx UNUSED,
+				  void *cb_data)
+{
+	struct hook_all_config_cb *data = cb_data;
+	const char *name, *subkey;
+	char *hook_name;
+	size_t name_len = 0;
+
+	if (parse_config_key(key, "hook", &name, &name_len, &subkey))
+		return 0;
+
+	if (!value)
+		return config_error_nonbool(key);
+
+	/* Extract name, ensuring it is null-terminated. */
+	hook_name = xmemdupz(name, name_len);
+
+	if (!strcmp(subkey, "event")) {
+		if (!*value) {
+			/* Empty values reset previous events for this hook. */
+			struct hashmap_iter iter;
+			struct strmap_entry *e;
+
+			strmap_for_each_entry(&data->event_hooks, &iter, e)
+				unsorted_string_list_remove(e->value, hook_name);
+		} else {
+			struct string_list *hooks =
+				strmap_get(&data->event_hooks, value);
+
+			if (!hooks) {
+				hooks = xcalloc(1, sizeof(*hooks));
+				string_list_init_dup(hooks);
+				strmap_put(&data->event_hooks, value, hooks);
+			}
+
+			/* Re-insert if necessary to preserve last-seen order. */
+			unsorted_string_list_remove(hooks, hook_name);
+			string_list_append(hooks, hook_name);
+		}
+	} else if (!strcmp(subkey, "command")) {
+		/* Store command overwriting the old value */
+		char *old = strmap_put(&data->commands, hook_name,
+				       xstrdup(value));
+		free(old);
+	} else if (!strcmp(subkey, "enabled")) {
+		switch (git_parse_maybe_bool(value)) {
+		case 0: /* disabled */
+			if (!unsorted_string_list_lookup(&data->disabled_hooks,
+							 hook_name))
+				string_list_append(&data->disabled_hooks,
+						   hook_name);
+			break;
+		case 1: /* enabled: undo a prior disabled entry */
+			unsorted_string_list_remove(&data->disabled_hooks,
+						    hook_name);
+			break;
+		default:
+			break; /* ignore unrecognised values */
+		}
+	}
+
+	free(hook_name);
+	return 0;
+}
+
+/*
+ * The hook config cache maps each hook event name to a string_list where
+ * every item's string is the hook's friendly-name and its util pointer is
+ * the corresponding command string. Both strings are owned by the map.
+ *
+ * Disabled hooks and hooks missing a command are already filtered out at
+ * parse time, so callers can iterate the list directly.
+ */
+void hook_cache_clear(struct strmap *cache)
+{
+	struct hashmap_iter iter;
+	struct strmap_entry *e;
+
+	strmap_for_each_entry(cache, &iter, e) {
+		struct string_list *hooks = e->value;
+		string_list_clear(hooks, 1); /* free util (command) pointers */
+		free(hooks);
+	}
+	strmap_clear(cache, 0);
+}
+
+/* Populate `cache` with the complete hook configuration */
+static void build_hook_config_map(struct repository *r, struct strmap *cache)
+{
+	struct hook_all_config_cb cb_data;
+	struct hashmap_iter iter;
+	struct strmap_entry *e;
+
+	strmap_init(&cb_data.commands);
+	strmap_init(&cb_data.event_hooks);
+	string_list_init_dup(&cb_data.disabled_hooks);
+
+	/* Parse all configs in one run. */
+	repo_config(r, hook_config_lookup_all, &cb_data);
+
+	/* Construct the cache from parsed configs. */
+	strmap_for_each_entry(&cb_data.event_hooks, &iter, e) {
+		struct string_list *hook_names = e->value;
+		struct string_list *hooks = xcalloc(1, sizeof(*hooks));
+
+		string_list_init_dup(hooks);
+
+		for (size_t i = 0; i < hook_names->nr; i++) {
+			const char *hname = hook_names->items[i].string;
+			char *command;
+
+			/* filter out disabled hooks */
+			if (unsorted_string_list_lookup(&cb_data.disabled_hooks,
+							hname))
+				continue;
+
+			command = strmap_get(&cb_data.commands, hname);
+			if (!command)
+				die(_("'hook.%s.command' must be configured or "
+				      "'hook.%s.event' must be removed;"
+				      " aborting."), hname, hname);
+
+			/* util stores the command; owned by the cache. */
+			string_list_append(hooks, hname)->util =
+				xstrdup(command);
+		}
+
+		strmap_put(cache, e->key, hooks);
+	}
+
+	strmap_clear(&cb_data.commands, 1);
+	string_list_clear(&cb_data.disabled_hooks, 0);
+	strmap_for_each_entry(&cb_data.event_hooks, &iter, e) {
+		string_list_clear(e->value, 0);
+		free(e->value);
+	}
+	strmap_clear(&cb_data.event_hooks, 0);
+}
+
+/*
+ * Return the hook config map for `r`, populating it first if needed.
+ *
+ * Out-of-repo calls (r->gitdir == NULL) allocate and return a temporary
+ * cache map; the caller is responsible for freeing it with
+ * hook_cache_clear() + free().
+ */
+static struct strmap *get_hook_config_cache(struct repository *r)
+{
+	struct strmap *cache = NULL;
+
+	if (r && r->gitdir) {
+		/*
+		 * For in-repo calls, the map is stored in r->hook_config_cache,
+		 * so repeated invocations don't parse the configs, so allocate
+		 * it just once on the first call.
+		 */
+		if (!r->hook_config_cache) {
+			r->hook_config_cache = xcalloc(1, sizeof(*cache));
+			strmap_init(r->hook_config_cache);
+			build_hook_config_map(r, r->hook_config_cache);
+		}
+		cache = r->hook_config_cache;
+	} else {
+		/*
+		 * Out-of-repo calls (no gitdir) allocate and return a temporary
+		 * map cache which gets free'd immediately by the caller.
+		 */
+		cache = xcalloc(1, sizeof(*cache));
+		strmap_init(cache);
+		build_hook_config_map(r, cache);
+	}
+
+	return cache;
+}
+
+static void list_hooks_add_configured(struct repository *r,
+				      const char *hookname,
+				      struct string_list *list,
+				      struct run_hooks_opt *options)
+{
+	struct strmap *cache = get_hook_config_cache(r);
+	struct string_list *configured_hooks = strmap_get(cache, hookname);
+
+	/* Iterate through configured hooks and initialize internal states */
+	for (size_t i = 0; configured_hooks && i < configured_hooks->nr; i++) {
+		const char *friendly_name = configured_hooks->items[i].string;
+		const char *command = configured_hooks->items[i].util;
+		struct hook *hook = xcalloc(1, sizeof(struct hook));
+
+		if (options && options->feed_pipe_cb_data_alloc)
+			hook->feed_pipe_cb_data =
+				options->feed_pipe_cb_data_alloc(
+					options->feed_pipe_ctx);
+
+		hook->kind = HOOK_CONFIGURED;
+		hook->u.configured.friendly_name = xstrdup(friendly_name);
+		hook->u.configured.command = xstrdup(command);
+
+		string_list_append(list, friendly_name)->util = hook;
+	}
+
+	/*
+	 * Cleanup temporary cache for out-of-repo calls since they can't be
+	 * stored persistently. Next out-of-repo calls will have to re-parse.
+	 */
+	if (!r || !r->gitdir) {
+		hook_cache_clear(cache);
+		free(cache);
+	}
+}
+
+struct string_list *list_hooks(struct repository *r, const char *hookname,
+			       struct run_hooks_opt *options)
+{
+	struct string_list *hook_head;
+
+	if (!hookname)
+		BUG("null hookname was provided to hook_list()!");
+
+	hook_head = xmalloc(sizeof(struct string_list));
+	string_list_init_dup(hook_head);
+
+	/* Add hooks from the config, e.g. hook.myhook.event = pre-commit */
+	list_hooks_add_configured(r, hookname, hook_head, options);
+
+	/* Add the default "traditional" hooks from hookdir. */
+	list_hooks_add_default(r, hookname, hook_head, options);
+
+	return hook_head;
+}
+
 int hook_exists(struct repository *r, const char *name)
 {
-	return !!find_hook(r, name);
+	struct string_list *hooks = list_hooks(r, name, NULL);
+	int exists = hooks->nr > 0;
+	hook_list_clear(hooks, NULL);
+	free(hooks);
+	return exists;
 }
 
 static int pick_next_hook(struct child_process *cp,
 			  struct strbuf *out UNUSED,
 			  void *pp_cb,
-			  void **pp_task_cb UNUSED)
+			  void **pp_task_cb)
 {
 	struct hook_cb_data *hook_cb = pp_cb;
-	const char *hook_path = hook_cb->hook_path;
+	struct string_list *hook_list = hook_cb->hook_command_list;
+	struct hook *h;
 
-	if (!hook_path)
+	if (hook_cb->hook_to_run_index >= hook_list->nr)
 		return 0;
 
+	h = hook_list->items[hook_cb->hook_to_run_index++].util;
+
 	cp->no_stdin = 1;
 	strvec_pushv(&cp->env, hook_cb->options->env.v);
+
+	if (hook_cb->options->path_to_stdin && hook_cb->options->feed_pipe)
+		BUG("options path_to_stdin and feed_pipe are mutually exclusive");
+
 	/* reopen the file for stdin; run_command closes it. */
 	if (hook_cb->options->path_to_stdin) {
 		cp->no_stdin = 0;
 		cp->in = xopen(hook_cb->options->path_to_stdin, O_RDONLY);
 	}
-	cp->stdout_to_stderr = 1;
+
+	if (hook_cb->options->feed_pipe) {
+		cp->no_stdin = 0;
+		/* start_command() will allocate a pipe / stdin fd for us */
+		cp->in = -1;
+	}
+
+	cp->stdout_to_stderr = hook_cb->options->stdout_to_stderr;
 	cp->trace2_hook_name = hook_cb->hook_name;
 	cp->dir = hook_cb->options->dir;
 
-	strvec_push(&cp->args, hook_path);
+	/* Add hook exec paths or commands */
+	if (h->kind == HOOK_TRADITIONAL) {
+		strvec_push(&cp->args, h->u.traditional.path);
+	} else if (h->kind == HOOK_CONFIGURED) {
+		/* to enable oneliners, let config-specified hooks run in shell. */
+		cp->use_shell = true;
+		strvec_push(&cp->args, h->u.configured.command);
+	}
+
+	if (!cp->args.nr)
+		BUG("hook must have at least one command or exec path");
+
 	strvec_pushv(&cp->args, hook_cb->options->args.v);
 
 	/*
-	 * This pick_next_hook() will be called again, we're only
-	 * running one hook, so indicate that no more work will be
-	 * done.
+	 * Provide per-hook internal state via task_cb for easy access, so
+	 * hook callbacks don't have to go through hook_cb->options.
 	 */
-	hook_cb->hook_path = NULL;
+	*pp_task_cb = h->feed_pipe_cb_data;
 
 	return 1;
 }
@@ -123,23 +467,22 @@ static void run_hooks_opt_clear(struct run_hooks_opt *options)
 int run_hooks_opt(struct repository *r, const char *hook_name,
 		  struct run_hooks_opt *options)
 {
-	struct strbuf abs_path = STRBUF_INIT;
 	struct hook_cb_data cb_data = {
 		.rc = 0,
 		.hook_name = hook_name,
 		.options = options,
 	};
-	const char *const hook_path = find_hook(r, hook_name);
 	int ret = 0;
 	const struct run_process_parallel_opts opts = {
 		.tr2_category = "hook",
 		.tr2_label = hook_name,
 
-		.processes = 1,
-		.ungroup = 1,
+		.processes = options->jobs,
+		.ungroup = options->jobs == 1,
 
 		.get_next_task = pick_next_hook,
 		.start_failure = notify_start_failure,
+		.feed_pipe = options->feed_pipe,
 		.task_finished = notify_hook_finished,
 
 		.data = &cb_data,
@@ -148,27 +491,35 @@ int run_hooks_opt(struct repository *r, const char *hook_name,
 	if (!options)
 		BUG("a struct run_hooks_opt must be provided to run_hooks");
 
+	if (options->path_to_stdin && options->feed_pipe)
+		BUG("options path_to_stdin and feed_pipe are mutually exclusive");
+
+	if (!options->jobs)
+		BUG("run_hooks_opt must be called with options.jobs >= 1");
+
+	/*
+	 * Ensure cb_data copy and free functions are either provided together,
+	 * or neither one is provided.
+	 */
+	if ((options->feed_pipe_cb_data_alloc && !options->feed_pipe_cb_data_free) ||
+	    (!options->feed_pipe_cb_data_alloc && options->feed_pipe_cb_data_free))
+		BUG("feed_pipe_cb_data_alloc and feed_pipe_cb_data_free must be set together");
+
 	if (options->invoked_hook)
 		*options->invoked_hook = 0;
 
-	if (!hook_path && !options->error_if_missing)
+	cb_data.hook_command_list = list_hooks(r, hook_name, options);
+	if (!cb_data.hook_command_list->nr) {
+		if (options->error_if_missing)
+			ret = error("cannot find a hook named %s", hook_name);
 		goto cleanup;
-
-	if (!hook_path) {
-		ret = error("cannot find a hook named %s", hook_name);
-		goto cleanup;
-	}
-
-	cb_data.hook_path = hook_path;
-	if (options->dir) {
-		strbuf_add_absolute_path(&abs_path, hook_path);
-		cb_data.hook_path = abs_path.buf;
 	}
 
 	run_processes_parallel(&opts);
 	ret = cb_data.rc;
 cleanup:
-	strbuf_release(&abs_path);
+	hook_list_clear(cb_data.hook_command_list, options->feed_pipe_cb_data_free);
+	free(cb_data.hook_command_list);
 	run_hooks_opt_clear(options);
 	return ret;
 }
diff --git a/hook.h b/hook.h
index 11863fa..e949f5d 100644
--- a/hook.h
+++ b/hook.h
@@ -1,9 +1,51 @@
 #ifndef HOOK_H
 #define HOOK_H
 #include "strvec.h"
+#include "run-command.h"
+#include "string-list.h"
+#include "strmap.h"
 
 struct repository;
 
+/**
+ * Represents a hook command to be run.
+ * Hooks can be:
+ * 1. "traditional" (found in the hooks directory)
+ * 2. "configured" (defined in Git's configuration via hook.<name>.event).
+ * The 'kind' field determines which part of the union 'u' is valid.
+ */
+struct hook {
+	enum {
+		HOOK_TRADITIONAL,
+		HOOK_CONFIGURED,
+	} kind;
+	union {
+		struct {
+			const char *path;
+		} traditional;
+		struct {
+			const char *friendly_name;
+			const char *command;
+		} configured;
+	} u;
+
+	/**
+	 * Opaque data pointer used to keep internal state across callback calls.
+	 *
+	 * It can be accessed directly via the third hook callback arg:
+	 * struct ... *state = pp_task_cb;
+	 *
+	 * The caller is responsible for managing the memory for this data by
+	 * providing alloc/free callbacks to `run_hooks_opt`.
+	 *
+	 * Only useful when using `run_hooks_opt.feed_pipe`, otherwise ignore it.
+	 */
+	void *feed_pipe_cb_data;
+};
+
+typedef void (*cb_data_free_fn)(void *data);
+typedef void *(*cb_data_alloc_fn)(void *init_ctx);
+
 struct run_hooks_opt
 {
 	/* Environment vars to be set for each hook */
@@ -16,6 +58,14 @@ struct run_hooks_opt
 	unsigned int error_if_missing:1;
 
 	/**
+	 *  Number of processes to parallelize across.
+	 *
+	 * If > 1, output will be buffered and de-interleaved (ungroup=0).
+	 * If == 1, output will be real-time (ungroup=1).
+	 */
+	unsigned int jobs;
+
+	/**
 	 * An optional initial working directory for the hook,
 	 * translates to "struct child_process"'s "dir" member.
 	 */
@@ -34,25 +84,120 @@ struct run_hooks_opt
 	int *invoked_hook;
 
 	/**
+	 * Send the hook's stdout to stderr.
+	 *
+	 * This is the default behavior for all hooks except pre-push,
+	 * which has separate stdout and stderr streams for backwards
+	 * compatibility reasons.
+	 */
+	unsigned int stdout_to_stderr:1;
+
+	/**
 	 * Path to file which should be piped to stdin for each hook.
 	 */
 	const char *path_to_stdin;
+
+	/**
+	 * Callback used to incrementally feed a child hook stdin pipe.
+	 *
+	 * Useful especially if a hook consumes large quantities of data
+	 * (e.g. a list of all refs in a client push), so feeding it via
+	 * in-memory strings or slurping to/from files is inefficient.
+	 * While the callback allows piecemeal writing, it can also be
+	 * used for smaller inputs, where it gets called only once.
+	 *
+	 * Add hook callback initalization context to `feed_pipe_ctx`.
+	 * Add hook callback internal state to `feed_pipe_cb_data`.
+	 *
+	 */
+	feed_pipe_fn feed_pipe;
+
+	/**
+	 * Opaque data pointer used to pass context to `feed_pipe_fn`.
+	 *
+	 * It can be accessed via the second callback arg 'pp_cb':
+	 * ((struct hook_cb_data *) pp_cb)->hook_cb->options->feed_pipe_ctx;
+	 *
+	 * The caller is responsible for managing the memory for this data.
+	 * Only useful when using `run_hooks_opt.feed_pipe`, otherwise ignore it.
+	 */
+	void *feed_pipe_ctx;
+
+	/**
+	 * Some hooks need to create a fresh `feed_pipe_cb_data` internal state,
+	 * so they can keep track of progress without affecting one another.
+	 *
+	 * If provided, this function will be called to alloc & initialize the
+	 * `feed_pipe_cb_data` for each hook.
+	 *
+	 * The `feed_pipe_ctx` pointer can be used to pass initialization data.
+	 */
+	cb_data_alloc_fn feed_pipe_cb_data_alloc;
+
+	/**
+	 * Called to free the memory initialized by `feed_pipe_cb_data_alloc`.
+	 *
+	 * Must always be provided when `feed_pipe_cb_data_alloc` is provided.
+	 */
+	cb_data_free_fn feed_pipe_cb_data_free;
 };
 
 #define RUN_HOOKS_OPT_INIT { \
 	.env = STRVEC_INIT, \
 	.args = STRVEC_INIT, \
+	.stdout_to_stderr = 1, \
+	.jobs = 1, \
 }
 
 struct hook_cb_data {
 	/* rc reflects the cumulative failure state */
 	int rc;
 	const char *hook_name;
-	const char *hook_path;
+
+	/**
+	 * A list of hook commands/paths to run for the 'hook_name' event.
+	 *
+	 * The 'string' member of each item holds the path (for traditional hooks)
+	 * or the unique friendly-name for hooks specified in configs.
+	 * The 'util' member of each item points to the corresponding struct hook.
+	 */
+	struct string_list *hook_command_list;
+
+	/* Iterator/cursor for the above list, pointing to the next hook to run. */
+	size_t hook_to_run_index;
+
 	struct run_hooks_opt *options;
 };
 
-/*
+/**
+ * Provides a list of hook commands to run for the 'hookname' event.
+ *
+ * This function consolidates hooks from two sources:
+ * 1. The config-based hooks (not yet implemented).
+ * 2. The "traditional" hook found in the repository hooks directory
+ *    (e.g., .git/hooks/pre-commit).
+ *
+ * The list is ordered by execution priority.
+ *
+ * The caller is responsible for freeing the memory of the returned list
+ * using string_list_clear() and free().
+ */
+struct string_list *list_hooks(struct repository *r, const char *hookname,
+			       struct run_hooks_opt *options);
+
+/**
+ * Frees the memory allocated for the hook list, including the `struct hook`
+ * items and their internal state.
+ */
+void hook_list_clear(struct string_list *hooks, cb_data_free_fn cb_data_free);
+
+/**
+ * Frees the hook configuration cache stored in `struct repository`.
+ * Called by repo_clear().
+ */
+void hook_cache_clear(struct strmap *cache);
+
+/**
  * Returns the path to the hook file, or NULL if the hook is missing
  * or disabled. Note that this points to static storage that will be
  * overwritten by further calls to find_hook and run_hook_*.
diff --git a/http-backend.c b/http-backend.c
index 52f0483..1a171c5 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -16,6 +16,7 @@
 #include "run-command.h"
 #include "string-list.h"
 #include "url.h"
+#include "setup.h"
 #include "strvec.h"
 #include "packfile.h"
 #include "odb.h"
@@ -143,8 +144,10 @@ static NORETURN void not_found(struct strbuf *hdr, const char *err, ...)
 	end_headers(hdr);
 
 	va_start(params, err);
-	if (err && *err)
+	if (err && *err) {
 		vfprintf(stderr, err, params);
+		putc('\n', stderr);
+	}
 	va_end(params);
 	exit(0);
 }
@@ -159,8 +162,10 @@ static NORETURN void forbidden(struct strbuf *hdr, const char *err, ...)
 	end_headers(hdr);
 
 	va_start(params, err);
-	if (err && *err)
+	if (err && *err) {
 		vfprintf(stderr, err, params);
+		putc('\n', stderr);
+	}
 	va_end(params);
 	exit(0);
 }
@@ -513,18 +518,17 @@ static void run_service(const char **argv, int buffer_input)
 		exit(1);
 }
 
-static int show_text_ref(const char *name, const char *referent UNUSED, const struct object_id *oid,
-			 int flag UNUSED, void *cb_data)
+static int show_text_ref(const struct reference *ref, void *cb_data)
 {
-	const char *name_nons = strip_namespace(name);
+	const char *name_nons = strip_namespace(ref->name);
 	struct strbuf *buf = cb_data;
-	struct object *o = parse_object(the_repository, oid);
+	struct object *o = parse_object(the_repository, ref->oid);
 	if (!o)
 		return 0;
 
-	strbuf_addf(buf, "%s\t%s\n", oid_to_hex(oid), name_nons);
+	strbuf_addf(buf, "%s\t%s\n", oid_to_hex(ref->oid), name_nons);
 	if (o->type == OBJ_TAG) {
-		o = deref_tag(the_repository, o, name, 0);
+		o = deref_tag(the_repository, o, ref->name, 0);
 		if (!o)
 			return 0;
 		strbuf_addf(buf, "%s\t%s^{}\n", oid_to_hex(&o->oid),
@@ -561,29 +565,32 @@ static void get_info_refs(struct strbuf *hdr, char *arg UNUSED)
 		run_service(argv, 0);
 
 	} else {
+		struct refs_for_each_ref_options opts = {
+			.namespace = get_git_namespace(),
+		};
+
 		select_getanyfile(hdr);
-		refs_for_each_namespaced_ref(get_main_ref_store(the_repository),
-					     NULL, show_text_ref, &buf);
+		refs_for_each_ref_ext(get_main_ref_store(the_repository),
+				      show_text_ref, &buf, &opts);
 		send_strbuf(hdr, "text/plain", &buf);
 	}
 	strbuf_release(&buf);
 }
 
-static int show_head_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-			 int flag, void *cb_data)
+static int show_head_ref(const struct reference *ref, void *cb_data)
 {
 	struct strbuf *buf = cb_data;
 
-	if (flag & REF_ISSYMREF) {
+	if (ref->flags & REF_ISSYMREF) {
 		const char *target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
-							     refname,
+							     ref->name,
 							     RESOLVE_REF_READING,
 							     NULL, NULL);
 
 		if (target)
 			strbuf_addf(buf, "ref: %s\n", strip_namespace(target));
 	} else {
-		strbuf_addf(buf, "%s\n", oid_to_hex(oid));
+		strbuf_addf(buf, "%s\n", oid_to_hex(ref->oid));
 	}
 
 	return 0;
diff --git a/http-push.c b/http-push.c
index a1c01e3..9ae6062 100644
--- a/http-push.c
+++ b/http-push.c
@@ -104,7 +104,7 @@ struct repo {
 	int has_info_refs;
 	int can_update_info_refs;
 	int has_info_packs;
-	struct packed_git *packs;
+	struct packfile_list packs;
 	struct remote_lock *locks;
 };
 
@@ -311,7 +311,7 @@ static void start_fetch_packed(struct transfer_request *request)
 	struct transfer_request *check_request = request_queue_head;
 	struct http_pack_request *preq;
 
-	target = find_oid_pack(&request->obj->oid, repo->packs);
+	target = packfile_list_find_oid(repo->packs.head, &request->obj->oid);
 	if (!target) {
 		fprintf(stderr, "Unable to fetch %s, will not be able to update server info refs\n", oid_to_hex(&request->obj->oid));
 		repo->can_update_info_refs = 0;
@@ -683,7 +683,7 @@ static int add_send_request(struct object *obj, struct remote_lock *lock)
 		get_remote_object_list(obj->oid.hash[0]);
 	if (obj->flags & (REMOTE | PUSHING))
 		return 0;
-	target = find_oid_pack(&obj->oid, repo->packs);
+	target = packfile_list_find_oid(repo->packs.head, &obj->oid);
 	if (target) {
 		obj->flags |= REMOTE;
 		return 0;
@@ -1311,7 +1311,7 @@ static struct object_list **process_tree(struct tree *tree,
 
 	if (obj->flags & (UNINTERESTING | SEEN))
 		return p;
-	if (parse_tree(tree) < 0)
+	if (repo_parse_tree(the_repository, tree) < 0)
 		die("bad tree object %s", oid_to_hex(&obj->oid));
 
 	obj->flags |= SEEN;
@@ -1725,6 +1725,7 @@ int cmd_main(int argc, const char **argv)
 	int i;
 	int new_refs;
 	struct ref *ref, *local_refs = NULL;
+	const char *gitdir;
 
 	CALLOC_ARRAY(repo, 1);
 
@@ -1767,7 +1768,7 @@ int cmd_main(int argc, const char **argv)
 				usage(http_push_usage);
 		}
 		if (!repo->url) {
-			char *path = strstr(arg, "//");
+			const char *path = strstr(arg, "//");
 			str_end_url_with_slash(arg, &repo->url);
 			repo->path_len = strlen(repo->url);
 			if (path) {
@@ -1787,7 +1788,7 @@ int cmd_main(int argc, const char **argv)
 	if (delete_branch && rs.nr != 1)
 		die("You must specify only one branch name when deleting a remote branch");
 
-	setup_git_directory();
+	gitdir = setup_git_directory();
 
 	memset(remote_dir_exists, -1, 256);
 
@@ -1941,7 +1942,7 @@ int cmd_main(int argc, const char **argv)
 		if (!push_all && !is_null_oid(&ref->old_oid))
 			strvec_pushf(&commit_argv, "^%s",
 				     oid_to_hex(&ref->old_oid));
-		repo_init_revisions(the_repository, &revs, setup_git_directory());
+		repo_init_revisions(the_repository, &revs, gitdir);
 		setup_revisions_from_strvec(&commit_argv, &revs, NULL);
 		revs.edge_hint = 0; /* just in case */
 
diff --git a/http-walker.c b/http-walker.c
index 0f7ae46..e886e64 100644
--- a/http-walker.c
+++ b/http-walker.c
@@ -15,7 +15,7 @@
 struct alt_base {
 	char *base;
 	int got_indices;
-	struct packed_git *packs;
+	struct packfile_list packs;
 	struct alt_base *next;
 };
 
@@ -324,11 +324,8 @@ static void process_alternates_response(void *callback_data)
 				} else if (is_alternate_allowed(target.buf)) {
 					warning("adding alternate object store: %s",
 						target.buf);
-					newalt = xmalloc(sizeof(*newalt));
-					newalt->next = NULL;
+					CALLOC_ARRAY(newalt, 1);
 					newalt->base = strbuf_detach(&target, NULL);
-					newalt->got_indices = 0;
-					newalt->packs = NULL;
 
 					while (tail->next != NULL)
 						tail = tail->next;
@@ -435,7 +432,7 @@ static int http_fetch_pack(struct walker *walker, struct alt_base *repo,
 
 	if (fetch_indices(walker, repo))
 		return -1;
-	target = find_oid_pack(oid, repo->packs);
+	target = packfile_list_find_oid(repo->packs.head, oid);
 	if (!target)
 		return -1;
 	close_pack_index(target);
@@ -584,17 +581,15 @@ static void cleanup(struct walker *walker)
 	if (data) {
 		alt = data->alt;
 		while (alt) {
-			struct packed_git *pack;
+			struct packfile_list_entry *e;
 
 			alt_next = alt->next;
 
-			pack = alt->packs;
-			while (pack) {
-				struct packed_git *pack_next = pack->next;
-				close_pack(pack);
-				free(pack);
-				pack = pack_next;
+			for (e = alt->packs.head; e; e = e->next) {
+				close_pack(e->pack);
+				free(e->pack);
 			}
+			packfile_list_clear(&alt->packs);
 
 			free(alt->base);
 			free(alt);
@@ -612,14 +607,11 @@ struct walker *get_http_walker(const char *url)
 	struct walker_data *data = xmalloc(sizeof(struct walker_data));
 	struct walker *walker = xmalloc(sizeof(struct walker));
 
-	data->alt = xmalloc(sizeof(*data->alt));
+	CALLOC_ARRAY(data->alt, 1);
 	data->alt->base = xstrdup(url);
 	for (s = data->alt->base + strlen(data->alt->base) - 1; *s == '/'; --s)
 		*s = 0;
 
-	data->alt->got_indices = 0;
-	data->alt->packs = NULL;
-	data->alt->next = NULL;
 	data->got_alternates = -1;
 
 	walker->corrupt_object_found = 0;
diff --git a/http.c b/http.c
index 1713082..8ea1b9d 100644
--- a/http.c
+++ b/http.c
@@ -2413,8 +2413,9 @@ static char *fetch_pack_index(unsigned char *hash, const char *base_url)
 	return tmp;
 }
 
-static int fetch_and_setup_pack_index(struct packed_git **packs_head,
-	unsigned char *sha1, const char *base_url)
+static int fetch_and_setup_pack_index(struct packfile_list *packs,
+				      unsigned char *sha1,
+				      const char *base_url)
 {
 	struct packed_git *new_pack, *p;
 	char *tmp_idx = NULL;
@@ -2448,12 +2449,11 @@ static int fetch_and_setup_pack_index(struct packed_git **packs_head,
 	if (ret)
 		return -1;
 
-	new_pack->next = *packs_head;
-	*packs_head = new_pack;
+	packfile_list_prepend(packs, new_pack);
 	return 0;
 }
 
-int http_get_info_packs(const char *base_url, struct packed_git **packs_head)
+int http_get_info_packs(const char *base_url, struct packfile_list *packs)
 {
 	struct http_get_options options = {0};
 	int ret = 0;
@@ -2477,7 +2477,7 @@ int http_get_info_packs(const char *base_url, struct packed_git **packs_head)
 		    !parse_oid_hex(data, &oid, &data) &&
 		    skip_prefix(data, ".pack", &data) &&
 		    (*data == '\n' || *data == '\0')) {
-			fetch_and_setup_pack_index(packs_head, oid.hash, base_url);
+			fetch_and_setup_pack_index(packs, oid.hash, base_url);
 		} else {
 			data = strchrnul(data, '\n');
 		}
@@ -2541,15 +2541,11 @@ int finish_http_pack_request(struct http_pack_request *preq)
 }
 
 void http_install_packfile(struct packed_git *p,
-			   struct packed_git **list_to_remove_from)
+			   struct packfile_list *list_to_remove_from)
 {
-	struct packed_git **lst = list_to_remove_from;
-
-	while (*lst != p)
-		lst = &((*lst)->next);
-	*lst = (*lst)->next;
-
-	packfile_store_add_pack(the_repository->objects->packfiles, p);
+	struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->sources);
+	packfile_list_remove(list_to_remove_from, p);
+	packfile_store_add_pack(files->packed, p);
 }
 
 struct http_pack_request *new_http_pack_request(
diff --git a/http.h b/http.h
index 553e162..f9d4593 100644
--- a/http.h
+++ b/http.h
@@ -2,6 +2,7 @@
 #define HTTP_H
 
 struct packed_git;
+struct packfile_list;
 
 #include "git-zlib.h"
 
@@ -190,7 +191,7 @@ struct curl_slist *http_append_auth_header(const struct credential *c,
 
 /* Helpers for fetching packs */
 int http_get_info_packs(const char *base_url,
-			struct packed_git **packs_head);
+			struct packfile_list *packs);
 
 /* Helper for getting Accept-Language header */
 const char *http_get_accept_language_header(void);
@@ -226,7 +227,7 @@ void release_http_pack_request(struct http_pack_request *preq);
  * from http_get_info_packs() and have chosen a specific pack to fetch.
  */
 void http_install_packfile(struct packed_git *p,
-			   struct packed_git **list_to_remove_from);
+			   struct packfile_list *list_to_remove_from);
 
 /* Helpers for fetching object */
 struct http_object_request {
diff --git a/line-log.c b/line-log.c
index 8bd4221..eeaf684 100644
--- a/line-log.c
+++ b/line-log.c
@@ -1239,7 +1239,7 @@ static int process_ranges_merge_commit(struct rev_info *rev, struct commit *comm
 			 * don't follow any other path in history
 			 */
 			add_line_range(rev, parent, cand[i]);
-			free_commit_list(commit->parents);
+			commit_list_free(commit->parents);
 			commit_list_append(parent, &commit->parents);
 
 			ret = 0;
diff --git a/linear-assignment.c b/linear-assignment.c
index 5416cbc..97b4f75 100644
--- a/linear-assignment.c
+++ b/linear-assignment.c
@@ -20,8 +20,8 @@ void compute_assignment(int column_count, int row_count, int *cost,
 	int i, j, phase;
 
 	if (column_count < 2) {
-		memset(column2row, 0, sizeof(int) * column_count);
-		memset(row2column, 0, sizeof(int) * row_count);
+		MEMZERO_ARRAY(column2row, column_count);
+		MEMZERO_ARRAY(row2column, row_count);
 		return;
 	}
 
diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c
index 7420bf8..7f3e7b8 100644
--- a/list-objects-filter-options.c
+++ b/list-objects-filter-options.c
@@ -20,6 +20,8 @@ const char *list_object_filter_config_name(enum list_objects_filter_choice c)
 	case LOFC_DISABLED:
 		/* we have no name for "no filter at all" */
 		break;
+	case LOFC_AUTO:
+		return "auto";
 	case LOFC_BLOB_NONE:
 		return "blob:none";
 	case LOFC_BLOB_LIMIT:
@@ -52,7 +54,16 @@ int gently_parse_list_objects_filter(
 	if (filter_options->choice)
 		BUG("filter_options already populated");
 
-	if (!strcmp(arg, "blob:none")) {
+	if (!strcmp(arg, "auto")) {
+		if (!filter_options->allow_auto_filter) {
+			strbuf_addstr(errbuf,
+				      _("'auto' filter not supported by this command"));
+			return 1;
+		}
+		filter_options->choice = LOFC_AUTO;
+		return 0;
+
+	} else if (!strcmp(arg, "blob:none")) {
 		filter_options->choice = LOFC_BLOB_NONE;
 		return 0;
 
@@ -146,10 +157,22 @@ static int parse_combine_subfilter(
 
 	decoded = url_percent_decode(subspec->buf);
 
-	result = has_reserved_character(subspec, errbuf) ||
-		gently_parse_list_objects_filter(
-			&filter_options->sub[new_index], decoded, errbuf);
+	result = has_reserved_character(subspec, errbuf);
+	if (result)
+		goto cleanup;
 
+	result = gently_parse_list_objects_filter(
+			&filter_options->sub[new_index], decoded, errbuf);
+	if (result)
+		goto cleanup;
+
+	result = (filter_options->sub[new_index].choice == LOFC_AUTO);
+	if (result) {
+		strbuf_addstr(errbuf, _("an 'auto' filter cannot be combined"));
+		goto cleanup;
+	}
+
+cleanup:
 	free(decoded);
 	return result;
 }
@@ -263,6 +286,9 @@ void parse_list_objects_filter(
 	} else {
 		struct list_objects_filter_options *sub;
 
+		if (filter_options->choice == LOFC_AUTO)
+			die(_("an 'auto' filter is incompatible with any other filter"));
+
 		/*
 		 * Make filter_options an LOFC_COMBINE spec so we can trivially
 		 * add subspecs to it.
@@ -277,6 +303,9 @@ void parse_list_objects_filter(
 		if (gently_parse_list_objects_filter(sub, arg, &errbuf))
 			die("%s", errbuf.buf);
 
+		if (sub->choice == LOFC_AUTO)
+			die(_("an 'auto' filter is incompatible with any other filter"));
+
 		strbuf_addch(&filter_options->filter_spec, '+');
 		filter_spec_append_urlencode(filter_options, arg);
 	}
@@ -317,15 +346,19 @@ void list_objects_filter_release(
 	struct list_objects_filter_options *filter_options)
 {
 	size_t sub;
+	unsigned int allow_auto_filter;
 
 	if (!filter_options)
 		return;
+
+	allow_auto_filter = filter_options->allow_auto_filter;
 	strbuf_release(&filter_options->filter_spec);
 	free(filter_options->sparse_oid_name);
 	for (sub = 0; sub < filter_options->sub_nr; sub++)
 		list_objects_filter_release(&filter_options->sub[sub]);
 	free(filter_options->sub);
 	list_objects_filter_init(filter_options);
+	filter_options->allow_auto_filter = allow_auto_filter;
 }
 
 void partial_clone_register(
diff --git a/list-objects-filter-options.h b/list-objects-filter-options.h
index 7b2108b..77d7bbc 100644
--- a/list-objects-filter-options.h
+++ b/list-objects-filter-options.h
@@ -18,6 +18,7 @@ enum list_objects_filter_choice {
 	LOFC_SPARSE_OID,
 	LOFC_OBJECT_TYPE,
 	LOFC_COMBINE,
+	LOFC_AUTO,
 	LOFC__COUNT /* must be last */
 };
 
@@ -51,6 +52,11 @@ struct list_objects_filter_options {
 	unsigned int no_filter : 1;
 
 	/*
+	 * Is LOFC_AUTO a valid option?
+	 */
+	unsigned int allow_auto_filter : 1;
+
+	/*
 	 * BEGIN choice-specific parsed values from within the filter-spec. Only
 	 * some values will be defined for any given choice.
 	 */
diff --git a/list-objects-filter.c b/list-objects-filter.c
index acd65eb..78316e7 100644
--- a/list-objects-filter.c
+++ b/list-objects-filter.c
@@ -745,6 +745,13 @@ static void filter_combine__init(
 	filter->finalize_omits_fn = filter_combine__finalize_omits;
 }
 
+static void filter_auto__init(
+	struct list_objects_filter_options *filter_options UNUSED,
+	struct filter *filter UNUSED)
+{
+	BUG("LOFC_AUTO should have been resolved before initializing the filter");
+}
+
 typedef void (*filter_init_fn)(
 	struct list_objects_filter_options *filter_options,
 	struct filter *filter);
@@ -760,6 +767,7 @@ static filter_init_fn s_filters[] = {
 	filter_sparse_oid__init,
 	filter_object_type__init,
 	filter_combine__init,
+	filter_auto__init,
 };
 
 struct filter *list_objects_filter__init(
diff --git a/list-objects.c b/list-objects.c
index 42c17d9..91b23e2 100644
--- a/list-objects.c
+++ b/list-objects.c
@@ -167,10 +167,10 @@ static void process_tree(struct traversal_context *ctx,
 	    !revs->include_check_obj(&tree->object, revs->include_check_data))
 		return;
 
-	if (ctx->depth > max_allowed_tree_depth)
+	if (ctx->depth > revs->repo->settings.max_allowed_tree_depth)
 		die("exceeded maximum allowed tree depth");
 
-	failed_parse = parse_tree_gently(tree, 1);
+	failed_parse = repo_parse_tree_gently(the_repository, tree, 1);
 	if (failed_parse) {
 		if (revs->ignore_missing_links)
 			return;
diff --git a/lockfile.c b/lockfile.c
index 1d5ed01..7add2f1 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -6,6 +6,9 @@
 #include "abspath.h"
 #include "gettext.h"
 #include "lockfile.h"
+#include "parse.h"
+#include "strbuf.h"
+#include "wrapper.h"
 
 /*
  * path = absolute or relative path name
@@ -19,14 +22,14 @@ static void trim_last_path_component(struct strbuf *path)
 	int i = path->len;
 
 	/* back up past trailing slashes, if any */
-	while (i && path->buf[i - 1] == '/')
+	while (i && is_dir_sep(path->buf[i - 1]))
 		i--;
 
 	/*
 	 * then go backwards until a slash, or the beginning of the
 	 * string
 	 */
-	while (i && path->buf[i - 1] != '/')
+	while (i && !is_dir_sep(path->buf[i - 1]))
 		i--;
 
 	strbuf_setlen(path, i);
@@ -71,19 +74,115 @@ static void resolve_symlink(struct strbuf *path)
 	strbuf_reset(&link);
 }
 
+/*
+ * Lock PID file functions - write PID to a foo~pid.lock file alongside
+ * the lock file for debugging stale locks. The PID file is registered
+ * as a tempfile so it gets cleaned up by signal/atexit handlers.
+ *
+ * Naming: For "foo.lock", the PID file is "foo~pid.lock". The tilde is
+ * forbidden in refnames and allowed in Windows filenames, guaranteeing
+ * no collision with the refs namespace.
+ */
+
+/* Global config variable, initialized from core.lockfilePid */
+int lockfile_pid_enabled;
+
+/*
+ * Path generation helpers.
+ * Given base path "foo", generate:
+ *   - lock path: "foo.lock"
+ *   - pid path:  "foo-pid.lock"
+ */
+static void get_lock_path(struct strbuf *out, const char *path)
+{
+	strbuf_addstr(out, path);
+	strbuf_addstr(out, LOCK_SUFFIX);
+}
+
+static void get_pid_path(struct strbuf *out, const char *path)
+{
+	strbuf_addstr(out, path);
+	strbuf_addstr(out, LOCK_PID_INFIX);
+	strbuf_addstr(out, LOCK_SUFFIX);
+}
+
+static struct tempfile *create_lock_pid_file(const char *pid_path, int mode)
+{
+	struct strbuf content = STRBUF_INIT;
+	struct tempfile *pid_tempfile = NULL;
+	int fd;
+
+	if (!lockfile_pid_enabled)
+		goto out;
+
+	fd = open(pid_path, O_WRONLY | O_CREAT | O_EXCL, mode);
+	if (fd < 0)
+		goto out;
+
+	strbuf_addf(&content, "pid %" PRIuMAX "\n", (uintmax_t)getpid());
+	if (write_in_full(fd, content.buf, content.len) < 0) {
+		warning_errno(_("could not write lock pid file '%s'"), pid_path);
+		close(fd);
+		unlink(pid_path);
+		goto out;
+	}
+
+	close(fd);
+	pid_tempfile = register_tempfile(pid_path);
+
+out:
+	strbuf_release(&content);
+	return pid_tempfile;
+}
+
+static int read_lock_pid(const char *pid_path, uintmax_t *pid_out)
+{
+	struct strbuf content = STRBUF_INIT;
+	const char *val;
+	int ret = -1;
+
+	if (strbuf_read_file(&content, pid_path, LOCK_PID_MAXLEN) <= 0)
+		goto out;
+
+	strbuf_rtrim(&content);
+
+	if (skip_prefix(content.buf, "pid ", &val)) {
+		char *endptr;
+		*pid_out = strtoumax(val, &endptr, 10);
+		if (*pid_out > 0 && !*endptr)
+			ret = 0;
+	}
+
+	if (ret)
+		warning(_("malformed lock pid file '%s'"), pid_path);
+
+out:
+	strbuf_release(&content);
+	return ret;
+}
+
 /* Make sure errno contains a meaningful value on error */
 static int lock_file(struct lock_file *lk, const char *path, int flags,
 		     int mode)
 {
-	struct strbuf filename = STRBUF_INIT;
+	struct strbuf base_path = STRBUF_INIT;
+	struct strbuf lock_path = STRBUF_INIT;
+	struct strbuf pid_path = STRBUF_INIT;
 
-	strbuf_addstr(&filename, path);
+	strbuf_addstr(&base_path, path);
 	if (!(flags & LOCK_NO_DEREF))
-		resolve_symlink(&filename);
+		resolve_symlink(&base_path);
 
-	strbuf_addstr(&filename, LOCK_SUFFIX);
-	lk->tempfile = create_tempfile_mode(filename.buf, mode);
-	strbuf_release(&filename);
+	get_lock_path(&lock_path, base_path.buf);
+	get_pid_path(&pid_path, base_path.buf);
+
+	lk->tempfile = create_tempfile_mode(lock_path.buf, mode);
+	if (lk->tempfile)
+		lk->pid_tempfile = create_lock_pid_file(pid_path.buf, mode);
+
+	strbuf_release(&base_path);
+	strbuf_release(&lock_path);
+	strbuf_release(&pid_path);
 	return lk->tempfile ? lk->tempfile->fd : -1;
 }
 
@@ -151,16 +250,49 @@ static int lock_file_timeout(struct lock_file *lk, const char *path,
 void unable_to_lock_message(const char *path, int err, struct strbuf *buf)
 {
 	if (err == EEXIST) {
-		strbuf_addf(buf, _("Unable to create '%s.lock': %s.\n\n"
-		    "Another git process seems to be running in this repository, e.g.\n"
-		    "an editor opened by 'git commit'. Please make sure all processes\n"
-		    "are terminated then try again. If it still fails, a git process\n"
-		    "may have crashed in this repository earlier:\n"
-		    "remove the file manually to continue."),
-			    absolute_path(path), strerror(err));
-	} else
+		const char *abs_path = absolute_path(path);
+		struct strbuf lock_path = STRBUF_INIT;
+		struct strbuf pid_path = STRBUF_INIT;
+		uintmax_t pid;
+		int pid_status = 0; /* 0 = unknown, 1 = running, -1 = stale */
+
+		get_lock_path(&lock_path, abs_path);
+		get_pid_path(&pid_path, abs_path);
+
+		strbuf_addf(buf, _("Unable to create '%s': %s.\n\n"),
+			    lock_path.buf, strerror(err));
+
+		/*
+		 * Try to read PID file unconditionally - it may exist if
+		 * core.lockfilePid was enabled.
+		 */
+		if (!read_lock_pid(pid_path.buf, &pid)) {
+			if (kill((pid_t)pid, 0) == 0 || errno == EPERM)
+				pid_status = 1;  /* running (or no permission to signal) */
+			else if (errno == ESRCH)
+				pid_status = -1; /* no such process - stale lock */
+		}
+
+		if (pid_status == 1)
+			strbuf_addf(buf, _("Lock may be held by process %" PRIuMAX "; "
+					   "if no git process is running, the lock file "
+					   "may be stale (PIDs can be reused)"),
+				    pid);
+		else if (pid_status == -1)
+			strbuf_addf(buf, _("Lock was held by process %" PRIuMAX ", "
+					   "which is no longer running; the lock file "
+					   "appears to be stale"),
+				    pid);
+		else
+			strbuf_addstr(buf, _("Another git process seems to be running in this repository, "
+					     "or the lock file may be stale"));
+
+		strbuf_release(&lock_path);
+		strbuf_release(&pid_path);
+	} else {
 		strbuf_addf(buf, _("Unable to create '%s.lock': %s"),
 			    absolute_path(path), strerror(err));
+	}
 }
 
 NORETURN void unable_to_lock_die(const char *path, int err)
@@ -207,6 +339,8 @@ int commit_lock_file(struct lock_file *lk)
 {
 	char *result_path = get_locked_file_path(lk);
 
+	delete_tempfile(&lk->pid_tempfile);
+
 	if (commit_lock_file_to(lk, result_path)) {
 		int save_errno = errno;
 		free(result_path);
@@ -216,3 +350,9 @@ int commit_lock_file(struct lock_file *lk)
 	free(result_path);
 	return 0;
 }
+
+int rollback_lock_file(struct lock_file *lk)
+{
+	delete_tempfile(&lk->pid_tempfile);
+	return delete_tempfile(&lk->tempfile);
+}
diff --git a/lockfile.h b/lockfile.h
index 1bb9926..e7233f2 100644
--- a/lockfile.h
+++ b/lockfile.h
@@ -119,6 +119,7 @@
 
 struct lock_file {
 	struct tempfile *tempfile;
+	struct tempfile *pid_tempfile;
 };
 
 #define LOCK_INIT { 0 }
@@ -127,6 +128,22 @@ struct lock_file {
 #define LOCK_SUFFIX ".lock"
 #define LOCK_SUFFIX_LEN 5
 
+/*
+ * PID file naming: for a lock file "foo.lock", the PID file is "foo~pid.lock".
+ * The tilde is forbidden in refnames and allowed in Windows filenames, avoiding
+ * namespace collisions (e.g., refs "foo" and "foo~pid" cannot both exist).
+ */
+#define LOCK_PID_INFIX "~pid"
+#define LOCK_PID_INFIX_LEN 4
+
+/* Maximum length for PID file content */
+#define LOCK_PID_MAXLEN 32
+
+/*
+ * Whether to create PID files alongside lock files.
+ * Configured via core.lockfilePid (boolean).
+ */
+extern int lockfile_pid_enabled;
 
 /*
  * Flags
@@ -169,12 +186,12 @@ struct lock_file {
  * handling, and mode are described above.
  */
 int hold_lock_file_for_update_timeout_mode(
-		struct lock_file *lk, const char *path,
-		int flags, long timeout_ms, int mode);
+	struct lock_file *lk, const char *path,
+	int flags, long timeout_ms, int mode);
 
 static inline int hold_lock_file_for_update_timeout(
-		struct lock_file *lk, const char *path,
-		int flags, long timeout_ms)
+	struct lock_file *lk, const char *path,
+	int flags, long timeout_ms)
 {
 	return hold_lock_file_for_update_timeout_mode(lk, path, flags,
 						      timeout_ms, 0666);
@@ -186,15 +203,14 @@ static inline int hold_lock_file_for_update_timeout(
  * argument and error handling are described above.
  */
 static inline int hold_lock_file_for_update(
-		struct lock_file *lk, const char *path,
-		int flags)
+	struct lock_file *lk, const char *path, int flags)
 {
 	return hold_lock_file_for_update_timeout(lk, path, flags, 0);
 }
 
 static inline int hold_lock_file_for_update_mode(
-		struct lock_file *lk, const char *path,
-		int flags, int mode)
+	struct lock_file *lk, const char *path,
+	int flags, int mode)
 {
 	return hold_lock_file_for_update_timeout_mode(lk, path, flags, 0, mode);
 }
@@ -319,13 +335,10 @@ static inline int commit_lock_file_to(struct lock_file *lk, const char *path)
 
 /*
  * Roll back `lk`: close the file descriptor and/or file pointer and
- * remove the lockfile. It is a NOOP to call `rollback_lock_file()`
- * for a `lock_file` object that has already been committed or rolled
- * back. No error will be returned in this case.
+ * remove the lockfile and any associated PID file. It is a NOOP to
+ * call `rollback_lock_file()` for a `lock_file` object that has already
+ * been committed or rolled back. No error will be returned in this case.
  */
-static inline int rollback_lock_file(struct lock_file *lk)
-{
-	return delete_tempfile(&lk->tempfile);
-}
+int rollback_lock_file(struct lock_file *lk);
 
 #endif /* LOCKFILE_H */
diff --git a/log-tree.c b/log-tree.c
index 7d917f2..7e04870 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -147,9 +147,7 @@ static int ref_filter_match(const char *refname,
 	return 1;
 }
 
-static int add_ref_decoration(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-			      int flags UNUSED,
-			      void *cb_data)
+static int add_ref_decoration(const struct reference *ref, void *cb_data)
 {
 	int i;
 	struct object *obj;
@@ -158,16 +156,16 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED,
 	struct decoration_filter *filter = (struct decoration_filter *)cb_data;
 	const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
 
-	if (filter && !ref_filter_match(refname, filter))
+	if (filter && !ref_filter_match(ref->name, filter))
 		return 0;
 
-	if (starts_with(refname, git_replace_ref_base)) {
+	if (starts_with(ref->name, git_replace_ref_base)) {
 		struct object_id original_oid;
 		if (!replace_refs_enabled(the_repository))
 			return 0;
-		if (get_oid_hex(refname + strlen(git_replace_ref_base),
+		if (get_oid_hex(ref->name + strlen(git_replace_ref_base),
 				&original_oid)) {
-			warning("invalid replace ref %s", refname);
+			warning("invalid replace ref %s", ref->name);
 			return 0;
 		}
 		obj = parse_object(the_repository, &original_oid);
@@ -176,10 +174,10 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED,
 		return 0;
 	}
 
-	objtype = odb_read_object_info(the_repository->objects, oid, NULL);
+	objtype = odb_read_object_info(the_repository->objects, ref->oid, NULL);
 	if (objtype < 0)
 		return 0;
-	obj = lookup_object_by_type(the_repository, oid, objtype);
+	obj = lookup_object_by_type(the_repository, ref->oid, objtype);
 
 	for (i = 0; i < ARRAY_SIZE(ref_namespace); i++) {
 		struct ref_namespace_info *info = &ref_namespace[i];
@@ -187,24 +185,24 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED,
 		if (!info->decoration)
 			continue;
 		if (info->exact) {
-			if (!strcmp(refname, info->ref)) {
+			if (!strcmp(ref->name, info->ref)) {
 				deco_type = info->decoration;
 				break;
 			}
-		} else if (starts_with(refname, info->ref)) {
+		} else if (starts_with(ref->name, info->ref)) {
 			deco_type = info->decoration;
 			break;
 		}
 	}
 
-	add_name_decoration(deco_type, refname, obj);
+	add_name_decoration(deco_type, ref->name, obj);
 	while (obj->type == OBJ_TAG) {
 		if (!obj->parsed)
 			parse_object(the_repository, &obj->oid);
 		obj = ((struct tag *)obj)->tagged;
 		if (!obj)
 			break;
-		add_name_decoration(DECORATION_REF_TAG, refname, obj);
+		add_name_decoration(DECORATION_REF_TAG, ref->name, obj);
 	}
 	return 0;
 }
@@ -1079,7 +1077,7 @@ static int do_remerge_diff(struct rev_info *opt,
 	log_tree_diff_flush(opt);
 
 	/* Cleanup */
-	free_commit_list(bases);
+	commit_list_free(bases);
 	cleanup_additional_headers(&opt->diffopt);
 	strbuf_release(&parent1_desc);
 	strbuf_release(&parent2_desc);
diff --git a/loose.c b/loose.c
index e8ea6e7..07333be 100644
--- a/loose.c
+++ b/loose.c
@@ -1,7 +1,9 @@
 #include "git-compat-util.h"
 #include "hash.h"
 #include "path.h"
+#include "object-file.h"
 #include "odb.h"
+#include "odb/source-files.h"
 #include "hex.h"
 #include "repository.h"
 #include "wrapper.h"
@@ -48,27 +50,29 @@ static int insert_loose_map(struct odb_source *source,
 			    const struct object_id *oid,
 			    const struct object_id *compat_oid)
 {
-	struct loose_object_map *map = source->loose_map;
+	struct odb_source_files *files = odb_source_files_downcast(source);
+	struct loose_object_map *map = files->loose->map;
 	int inserted = 0;
 
 	inserted |= insert_oid_pair(map->to_compat, oid, compat_oid);
 	inserted |= insert_oid_pair(map->to_storage, compat_oid, oid);
 	if (inserted)
-		oidtree_insert(source->loose_objects_cache, compat_oid);
+		oidtree_insert(files->loose->cache, compat_oid);
 
 	return inserted;
 }
 
 static int load_one_loose_object_map(struct repository *repo, struct odb_source *source)
 {
+	struct odb_source_files *files = odb_source_files_downcast(source);
 	struct strbuf buf = STRBUF_INIT, path = STRBUF_INIT;
 	FILE *fp;
 
-	if (!source->loose_map)
-		loose_object_map_init(&source->loose_map);
-	if (!source->loose_objects_cache) {
-		ALLOC_ARRAY(source->loose_objects_cache, 1);
-		oidtree_init(source->loose_objects_cache);
+	if (!files->loose->map)
+		loose_object_map_init(&files->loose->map);
+	if (!files->loose->cache) {
+		ALLOC_ARRAY(files->loose->cache, 1);
+		oidtree_init(files->loose->cache);
 	}
 
 	insert_loose_map(source, repo->hash_algo->empty_tree, repo->compat_hash_algo->empty_tree);
@@ -124,7 +128,8 @@ int repo_read_loose_object_map(struct repository *repo)
 
 int repo_write_loose_object_map(struct repository *repo)
 {
-	kh_oid_map_t *map = repo->objects->sources->loose_map->to_compat;
+	struct odb_source_files *files = odb_source_files_downcast(repo->objects->sources);
+	kh_oid_map_t *map = files->loose->map->to_compat;
 	struct lock_file lock;
 	int fd;
 	khiter_t iter;
@@ -230,7 +235,8 @@ int repo_loose_object_map_oid(struct repository *repo,
 	khiter_t pos;
 
 	for (source = repo->objects->sources; source; source = source->next) {
-		struct loose_object_map *loose_map = source->loose_map;
+		struct odb_source_files *files = odb_source_files_downcast(source);
+		struct loose_object_map *loose_map = files->loose->map;
 		if (!loose_map)
 			continue;
 		map = (to == repo->compat_hash_algo) ?
diff --git a/ls-refs.c b/ls-refs.c
index c47acde..9759826 100644
--- a/ls-refs.c
+++ b/ls-refs.c
@@ -75,42 +75,42 @@ struct ls_refs_data {
 	unsigned unborn : 1;
 };
 
-static int send_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-		    int flag, void *cb_data)
+static int send_ref(const struct reference *ref, void *cb_data)
 {
 	struct ls_refs_data *data = cb_data;
-	const char *refname_nons = strip_namespace(refname);
+	const char *refname_nons = strip_namespace(ref->name);
 
 	strbuf_reset(&data->buf);
 
-	if (ref_is_hidden(refname_nons, refname, &data->hidden_refs))
+	if (ref_is_hidden(refname_nons, ref->name, &data->hidden_refs))
 		return 0;
 
 	if (!ref_match(&data->prefixes, refname_nons))
 		return 0;
 
-	if (oid)
-		strbuf_addf(&data->buf, "%s %s", oid_to_hex(oid), refname_nons);
+	if (ref->oid)
+		strbuf_addf(&data->buf, "%s %s", oid_to_hex(ref->oid), refname_nons);
 	else
 		strbuf_addf(&data->buf, "unborn %s", refname_nons);
-	if (data->symrefs && flag & REF_ISSYMREF) {
+	if (data->symrefs && ref->flags & REF_ISSYMREF) {
+		int unused_flag;
 		struct object_id unused;
 		const char *symref_target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
-								    refname,
+								    ref->name,
 								    0,
 								    &unused,
-								    &flag);
+								    &unused_flag);
 
 		if (!symref_target)
-			die("'%s' is a symref but it is not?", refname);
+			die("'%s' is a symref but it is not?", ref->name);
 
 		strbuf_addf(&data->buf, " symref-target:%s",
 			    strip_namespace(symref_target));
 	}
 
-	if (data->peel && oid) {
+	if (data->peel && ref->oid) {
 		struct object_id peeled;
-		if (!peel_iterated_oid(the_repository, oid, &peeled))
+		if (!reference_get_peeled_oid(the_repository, ref, &peeled))
 			strbuf_addf(&data->buf, " peeled:%s", oid_to_hex(&peeled));
 	}
 
@@ -131,9 +131,17 @@ static void send_possibly_unborn_head(struct ls_refs_data *data)
 	if (!refs_resolve_ref_unsafe(get_main_ref_store(the_repository), namespaced.buf, 0, &oid, &flag))
 		return; /* bad ref */
 	oid_is_null = is_null_oid(&oid);
+
 	if (!oid_is_null ||
-	    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))
-		send_ref(namespaced.buf, NULL, oid_is_null ? NULL : &oid, flag, data);
+	    (data->unborn && data->symrefs && (flag & REF_ISSYMREF))) {
+		struct reference ref = {
+			.name = namespaced.buf,
+			.oid = oid_is_null ? NULL : &oid,
+			.flags = flag,
+		};
+
+		send_ref(&ref, data);
+	}
 	strbuf_release(&namespaced);
 }
 
@@ -152,6 +160,7 @@ static int ls_refs_config(const char *var, const char *value,
 
 int ls_refs(struct repository *r, struct packet_reader *request)
 {
+	struct refs_for_each_ref_options opts = { 0 };
 	struct ls_refs_data data;
 
 	memset(&data, 0, sizeof(data));
@@ -193,10 +202,12 @@ int ls_refs(struct repository *r, struct packet_reader *request)
 	send_possibly_unborn_head(&data);
 	if (!data.prefixes.nr)
 		strvec_push(&data.prefixes, "");
-	refs_for_each_fullref_in_prefixes(get_main_ref_store(r),
-					  get_git_namespace(), data.prefixes.v,
-					  hidden_refs_to_excludes(&data.hidden_refs),
-					  send_ref, &data);
+
+	opts.exclude_patterns = hidden_refs_to_excludes(&data.hidden_refs);
+	opts.namespace = get_git_namespace();
+
+	refs_for_each_ref_in_prefixes(get_main_ref_store(r), data.prefixes.v,
+				      &opts, send_ref, &data);
 	packet_fflush(stdout);
 	strvec_clear(&data.prefixes);
 	strbuf_release(&data.buf);
diff --git a/mailinfo.c b/mailinfo.c
index 99ac596..a2f06db 100644
--- a/mailinfo.c
+++ b/mailinfo.c
@@ -1141,7 +1141,7 @@ static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf
 {
 	const char *sp = data->buf;
 	while (1) {
-		char *ep = strchr(sp, '\n');
+		const char *ep = strchr(sp, '\n');
 		int len;
 		if (!ep)
 			len = strlen(sp);
diff --git a/mailmap.c b/mailmap.c
index 37fd158..3b26917 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -7,9 +7,7 @@
 #include "object-name.h"
 #include "odb.h"
 #include "setup.h"
-
-char *git_mailmap_file;
-char *git_mailmap_blob;
+#include "config.h"
 
 struct mailmap_info {
 	char *name;
@@ -183,7 +181,8 @@ static void read_mailmap_string(struct string_list *map, char *buf)
 	}
 }
 
-int read_mailmap_blob(struct string_list *map, const char *name)
+int read_mailmap_blob(struct repository *repo, struct string_list *map,
+		      const char *name)
 {
 	struct object_id oid;
 	char *buf;
@@ -192,10 +191,10 @@ int read_mailmap_blob(struct string_list *map, const char *name)
 
 	if (!name)
 		return 0;
-	if (repo_get_oid(the_repository, name, &oid) < 0)
+	if (repo_get_oid(repo, name, &oid) < 0)
 		return 0;
 
-	buf = odb_read_object(the_repository->objects, &oid, &type, &size);
+	buf = odb_read_object(repo->objects, &oid, &type, &size);
 	if (!buf)
 		return error("unable to read mailmap object at %s", name);
 	if (type != OBJ_BLOB) {
@@ -209,23 +208,32 @@ int read_mailmap_blob(struct string_list *map, const char *name)
 	return 0;
 }
 
-int read_mailmap(struct string_list *map)
+int read_mailmap(struct repository *repo, struct string_list *map)
 {
 	int err = 0;
+	char *mailmap_file = NULL, *mailmap_blob = NULL;
+
+	repo_config_get_pathname(repo, "mailmap.file", &mailmap_file);
+	repo_config_get_string(repo, "mailmap.blob", &mailmap_blob);
 
 	map->strdup_strings = 1;
 	map->cmp = namemap_cmp;
 
-	if (!git_mailmap_blob && is_bare_repository())
-		git_mailmap_blob = xstrdup("HEAD:.mailmap");
+	if (!mailmap_blob && is_bare_repository())
+		mailmap_blob = xstrdup("HEAD:.mailmap");
 
 	if (!startup_info->have_repository || !is_bare_repository())
 		err |= read_mailmap_file(map, ".mailmap",
 					 startup_info->have_repository ?
 					 MAILMAP_NOFOLLOW : 0);
 	if (startup_info->have_repository)
-		err |= read_mailmap_blob(map, git_mailmap_blob);
-	err |= read_mailmap_file(map, git_mailmap_file, 0);
+		err |= read_mailmap_blob(repo, map, mailmap_blob);
+
+	err |= read_mailmap_file(map, mailmap_file, 0);
+
+	free(mailmap_file);
+	free(mailmap_blob);
+
 	return err;
 }
 
diff --git a/mailmap.h b/mailmap.h
index 908365e..6866cb6 100644
--- a/mailmap.h
+++ b/mailmap.h
@@ -1,19 +1,18 @@
 #ifndef MAILMAP_H
 #define MAILMAP_H
 
+struct repository;
 struct string_list;
 
-extern char *git_mailmap_file;
-extern char *git_mailmap_blob;
-
 /* Flags for read_mailmap_file() */
 #define MAILMAP_NOFOLLOW (1<<0)
 
 int read_mailmap_file(struct string_list *map, const char *filename,
 		      unsigned flags);
-int read_mailmap_blob(struct string_list *map, const char *name);
+int read_mailmap_blob(struct repository *repo, struct string_list *map,
+		      const char *name);
 
-int read_mailmap(struct string_list *map);
+int read_mailmap(struct repository *repo, struct string_list *map);
 void clear_mailmap(struct string_list *map);
 
 int map_user(struct string_list *map,
diff --git a/mem-pool.c b/mem-pool.c
index 62441dc..8bc77cb 100644
--- a/mem-pool.c
+++ b/mem-pool.c
@@ -169,7 +169,7 @@ char *mem_pool_strdup(struct mem_pool *pool, const char *str)
 
 char *mem_pool_strndup(struct mem_pool *pool, const char *str, size_t len)
 {
-	char *p = memchr(str, '\0', len);
+	const char *p = memchr(str, '\0', len);
 	size_t actual_len = (p ? p - str : len);
 	char *ret = mem_pool_alloc(pool, actual_len+1);
 
diff --git a/merge-ort-wrappers.c b/merge-ort-wrappers.c
index c54d56b..2110844 100644
--- a/merge-ort-wrappers.c
+++ b/merge-ort-wrappers.c
@@ -120,7 +120,7 @@ int merge_ort_generic(struct merge_options *opt,
 	repo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR);
 	clean = merge_ort_recursive(opt, head_commit, next_commit, ca,
 				    result);
-	free_commit_list(ca);
+	commit_list_free(ca);
 	if (clean < 0) {
 		rollback_lock_file(&lock);
 		return clean;
diff --git a/merge-ort.c b/merge-ort.c
index 2985807..00923ce 100644
--- a/merge-ort.c
+++ b/merge-ort.c
@@ -54,6 +54,14 @@
 #include "xdiff-interface.h"
 
 /*
+ * We technically need USE_THE_REPOSITORY_VARIABLE above for DEFAULT_ABBREV,
+ * but do not want more uses of the_repository.  Prevent them.
+ *
+ * opt->repo is available; use it instead.
+ */
+#define the_repository DO_NOT_USE_THE_REPOSITORY
+
+/*
  * We have many arrays of size 3.  Whenever we have such an array, the
  * indices refer to one of the sides of the three-way merge.  This is so
  * pervasive that the constants 0, 1, and 2 are used in many places in the
@@ -1502,11 +1510,44 @@ static void resolve_trivial_directory_merge(struct conflict_info *ci, int side)
 	VERIFY_CI(ci);
 	assert((side == 1 && ci->match_mask == 5) ||
 	       (side == 2 && ci->match_mask == 3));
+
+	/*
+	 * Since ci->stages[0] matches ci->stages[3-side], resolve merge in
+	 * favor of ci->stages[side].
+	 */
 	oidcpy(&ci->merged.result.oid, &ci->stages[side].oid);
 	ci->merged.result.mode = ci->stages[side].mode;
 	ci->merged.is_null = is_null_oid(&ci->stages[side].oid);
+
+	/*
+	 * Because we resolved in favor of "side", we are no longer
+	 * considering the paths which matched (i.e. had the same hash) any
+	 * more.  Strip the matching paths from both dirmask & filemask.
+	 * Another consequence of merging in favor of side is that we can no
+	 * longer have a directory/file conflict either..but there's a slight
+	 * nuance we consider before clearing it.
+	 *
+	 * In most cases, resolving in favor of the other side means there's
+	 * no conflict at all, but if we had a directory/file conflict to
+	 * start, and the directory is resolved away, the remaining file could
+	 * still be part of a rename.  If the remaining file is part of a
+	 * rename, then it may also be part of a rename conflict (e.g.
+	 * rename/delete or rename/rename(1to2)), so we can't
+	 * mark it as a clean merge if we started with a directory/file
+	 * conflict and still have a file left.
+	 *
+	 * In contrast, if we started with a directory/file conflict and
+	 * still have a directory left, no file under that directory can be
+	 * part of a rename, otherwise we would have had to recurse into the
+	 * directory and would have never ended up within
+	 * resolve_trivial_directory_merge() for that directory.
+	 */
+	ci->dirmask &= (~ci->match_mask);
+	ci->filemask &= (~ci->match_mask);
+	assert(!ci->filemask || !ci->dirmask);
 	ci->match_mask = 0;
-	ci->merged.clean = 1; /* (ci->filemask == 0); */
+	ci->merged.clean = !ci->df_conflict || ci->dirmask;
+	ci->df_conflict = 0;
 }
 
 static int handle_deferred_entries(struct merge_options *opt,
@@ -1699,9 +1740,9 @@ static int collect_merge_info(struct merge_options *opt,
 	info.data = opt;
 	info.show_all_errors = 1;
 
-	if (parse_tree(merge_base) < 0 ||
-	    parse_tree(side1) < 0 ||
-	    parse_tree(side2) < 0)
+	if (repo_parse_tree(opt->repo, merge_base) < 0 ||
+	    repo_parse_tree(opt->repo, side1) < 0 ||
+	    repo_parse_tree(opt->repo, side2) < 0)
 		return -1;
 	init_tree_desc(t + 0, &merge_base->object.oid,
 		       merge_base->buffer, merge_base->size);
@@ -1824,7 +1865,7 @@ static int merge_submodule(struct merge_options *opt,
 		BUG("submodule deleted on one side; this should be handled outside of merge_submodule()");
 
 	if ((sub_not_initialized = repo_submodule_init(&subrepo,
-		opt->repo, path, null_oid(the_hash_algo)))) {
+		opt->repo, path, null_oid(opt->repo->hash_algo)))) {
 		path_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0,
 			 path, NULL, NULL, NULL,
 			 _("Failed to merge submodule %s (not checked out)"),
@@ -2103,9 +2144,9 @@ static int merge_3way(struct merge_options *opt,
 		name2 = mkpathdup("%s:%s", opt->branch2,  pathnames[2]);
 	}
 
-	read_mmblob(&orig, o);
-	read_mmblob(&src1, a);
-	read_mmblob(&src2, b);
+	read_mmblob(&orig, opt->repo->objects, o);
+	read_mmblob(&src1, opt->repo->objects, a);
+	read_mmblob(&src2, opt->repo->objects, b);
 
 	merge_status = ll_merge(result_buf, path, &orig, base,
 				&src1, name1, &src2, name2,
@@ -2207,7 +2248,7 @@ static int handle_content_merge(struct merge_options *opt,
 		two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode));
 
 		merge_status = merge_3way(opt, path,
-					  two_way ? null_oid(the_hash_algo) : &o->oid,
+					  two_way ? null_oid(opt->repo->hash_algo) : &o->oid,
 					  &a->oid, &b->oid,
 					  pathnames, extra_marker_size,
 					  &result_buf);
@@ -2221,7 +2262,7 @@ static int handle_content_merge(struct merge_options *opt,
 		}
 
 		if (!ret && record_object &&
-		    odb_write_object(the_repository->objects, result_buf.ptr, result_buf.size,
+		    odb_write_object(opt->repo->objects, result_buf.ptr, result_buf.size,
 				     OBJ_BLOB, &result->oid)) {
 			path_msg(opt, ERROR_OBJECT_WRITE_FAILED, 0,
 				 pathnames[0], pathnames[1], pathnames[2], NULL,
@@ -2239,7 +2280,7 @@ static int handle_content_merge(struct merge_options *opt,
 	} else if (S_ISGITLINK(a->mode)) {
 		int two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode));
 		clean = merge_submodule(opt, pathnames[0],
-					two_way ? null_oid(the_hash_algo) : &o->oid,
+					two_way ? null_oid(opt->repo->hash_algo) : &o->oid,
 					&a->oid, &b->oid, &result->oid);
 		if (clean < 0)
 			return -1;
@@ -2698,7 +2739,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
 
 	while (1) {
 		/* Find the parent directory of cur_path */
-		char *last_slash = strrchr(cur_path, '/');
+		const char *last_slash = strrchr(cur_path, '/');
 		if (last_slash) {
 			parent_name = mem_pool_strndup(&opt->priv->pool,
 						       cur_path,
@@ -2753,7 +2794,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
 		assert(!new_ci->match_mask);
 		new_ci->dirmask = 0;
 		new_ci->stages[1].mode = 0;
-		oidcpy(&new_ci->stages[1].oid, null_oid(the_hash_algo));
+		oidcpy(&new_ci->stages[1].oid, null_oid(opt->repo->hash_algo));
 
 		/*
 		 * Now that we have the file information in new_ci, make sure
@@ -2766,7 +2807,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
 				continue;
 			/* zero out any entries related to files */
 			ci->stages[i].mode = 0;
-			oidcpy(&ci->stages[i].oid, null_oid(the_hash_algo));
+			oidcpy(&ci->stages[i].oid, null_oid(opt->repo->hash_algo));
 		}
 
 		/* Now we want to focus on new_ci, so reassign ci to it. */
@@ -2913,6 +2954,32 @@ static int process_renames(struct merge_options *opt,
 			continue;
 
 		/*
+		 * Rename caching from a previous commit might give us an
+		 * irrelevant rename for the current commit.
+		 *
+		 * Imagine:
+		 *     foo/A -> bar/A
+		 * was a cached rename for the upstream side from the
+		 * previous commit (without the directories being renamed),
+		 * but the next commit being replayed
+		 *     * does NOT add or delete files
+		 *     * does NOT have directory renames
+		 *     * does NOT modify any files under bar/
+		 *     * does NOT modify foo/A
+		 *     * DOES modify other files under foo/ (otherwise the
+		 *       !oldinfo check above would have already exited for
+		 *       us)
+		 * In such a case, our trivial directory resolution will
+		 * have already merged bar/, and our attempt to process
+		 * the cached
+		 *     foo/A -> bar/A
+		 * would be counterproductive, and lack the necessary
+		 * information anyway.  Skip such renames.
+		 */
+		if (!newinfo)
+			continue;
+
+		/*
 		 * diff_filepairs have copies of pathnames, thus we have to
 		 * use standard 'strcmp()' (negated) instead of '=='.
 		 */
@@ -3155,7 +3222,7 @@ static int process_renames(struct merge_options *opt,
 			if (type_changed) {
 				/* rename vs. typechange */
 				/* Mark the original as resolved by removal */
-				memcpy(&oldinfo->stages[0].oid, null_oid(the_hash_algo),
+				memcpy(&oldinfo->stages[0].oid, null_oid(opt->repo->hash_algo),
 				       sizeof(oldinfo->stages[0].oid));
 				oldinfo->stages[0].mode = 0;
 				oldinfo->filemask &= 0x06;
@@ -3438,7 +3505,7 @@ static int collect_renames(struct merge_options *opt,
 			continue;
 		}
 		if (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_NONE &&
-		    p->status == 'R' && 1) {
+		    p->status == 'R') {
 			possibly_cache_new_pair(renames, p, side_index, NULL);
 			goto skip_directory_renames;
 		}
@@ -3654,7 +3721,7 @@ static int read_oid_strbuf(struct merge_options *opt,
 	void *buf;
 	enum object_type type;
 	unsigned long size;
-	buf = odb_read_object(the_repository->objects, oid, &type, &size);
+	buf = odb_read_object(opt->repo->objects, oid, &type, &size);
 	if (!buf) {
 		path_msg(opt, ERROR_OBJECT_READ_FAILED, 0,
 			 path, NULL, NULL, NULL,
@@ -3763,15 +3830,16 @@ static int tree_entry_order(const void *a_, const void *b_)
 				 b->string, strlen(b->string), bmi->result.mode);
 }
 
-static int write_tree(struct object_id *result_oid,
+static int write_tree(struct repository *repo,
+		      struct object_id *result_oid,
 		      struct string_list *versions,
-		      unsigned int offset,
-		      size_t hash_size)
+		      unsigned int offset)
 {
 	size_t maxlen = 0, extra;
 	unsigned int nr;
 	struct strbuf buf = STRBUF_INIT;
 	int i, ret = 0;
+	size_t hash_size = repo->hash_algo->rawsz;
 
 	assert(offset <= versions->nr);
 	nr = versions->nr - offset;
@@ -3797,7 +3865,7 @@ static int write_tree(struct object_id *result_oid,
 	}
 
 	/* Write this object file out, and record in result_oid */
-	if (odb_write_object(the_repository->objects, buf.buf,
+	if (odb_write_object(repo->objects, buf.buf,
 			     buf.len, OBJ_TREE, result_oid))
 		ret = -1;
 	strbuf_release(&buf);
@@ -3967,8 +4035,8 @@ static int write_completed_directory(struct merge_options *opt,
 		dir_info->is_null = 0;
 		dir_info->result.mode = S_IFDIR;
 		if (record_tree &&
-		    write_tree(&dir_info->result.oid, &info->versions, offset,
-			       opt->repo->hash_algo->rawsz) < 0)
+		    write_tree(opt->repo, &dir_info->result.oid, &info->versions,
+			       offset) < 0)
 			ret = -1;
 	}
 
@@ -4042,7 +4110,7 @@ static int process_entry(struct merge_options *opt,
 			if (ci->filemask & (1 << i))
 				continue;
 			ci->stages[i].mode = 0;
-			oidcpy(&ci->stages[i].oid, null_oid(the_hash_algo));
+			oidcpy(&ci->stages[i].oid, null_oid(opt->repo->hash_algo));
 		}
 	} else if (ci->df_conflict && ci->merged.result.mode != 0) {
 		/*
@@ -4089,7 +4157,7 @@ static int process_entry(struct merge_options *opt,
 				continue;
 			/* zero out any entries related to directories */
 			new_ci->stages[i].mode = 0;
-			oidcpy(&new_ci->stages[i].oid, null_oid(the_hash_algo));
+			oidcpy(&new_ci->stages[i].oid, null_oid(opt->repo->hash_algo));
 		}
 
 		/*
@@ -4211,11 +4279,11 @@ static int process_entry(struct merge_options *opt,
 			new_ci->merged.result.mode = ci->stages[2].mode;
 			oidcpy(&new_ci->merged.result.oid, &ci->stages[2].oid);
 			new_ci->stages[1].mode = 0;
-			oidcpy(&new_ci->stages[1].oid, null_oid(the_hash_algo));
+			oidcpy(&new_ci->stages[1].oid, null_oid(opt->repo->hash_algo));
 			new_ci->filemask = 5;
 			if ((S_IFMT & b_mode) != (S_IFMT & o_mode)) {
 				new_ci->stages[0].mode = 0;
-				oidcpy(&new_ci->stages[0].oid, null_oid(the_hash_algo));
+				oidcpy(&new_ci->stages[0].oid, null_oid(opt->repo->hash_algo));
 				new_ci->filemask = 4;
 			}
 
@@ -4223,11 +4291,11 @@ static int process_entry(struct merge_options *opt,
 			ci->merged.result.mode = ci->stages[1].mode;
 			oidcpy(&ci->merged.result.oid, &ci->stages[1].oid);
 			ci->stages[2].mode = 0;
-			oidcpy(&ci->stages[2].oid, null_oid(the_hash_algo));
+			oidcpy(&ci->stages[2].oid, null_oid(opt->repo->hash_algo));
 			ci->filemask = 3;
 			if ((S_IFMT & a_mode) != (S_IFMT & o_mode)) {
 				ci->stages[0].mode = 0;
-				oidcpy(&ci->stages[0].oid, null_oid(the_hash_algo));
+				oidcpy(&ci->stages[0].oid, null_oid(opt->repo->hash_algo));
 				ci->filemask = 2;
 			}
 
@@ -4355,7 +4423,7 @@ static int process_entry(struct merge_options *opt,
 		/* Deleted on both sides */
 		ci->merged.is_null = 1;
 		ci->merged.result.mode = 0;
-		oidcpy(&ci->merged.result.oid, null_oid(the_hash_algo));
+		oidcpy(&ci->merged.result.oid, null_oid(opt->repo->hash_algo));
 		assert(!ci->df_conflict);
 		ci->merged.clean = !ci->path_conflict;
 	}
@@ -4379,7 +4447,7 @@ static void prefetch_for_content_merges(struct merge_options *opt,
 	struct string_list_item *e;
 	struct oid_array to_fetch = OID_ARRAY_INIT;
 
-	if (opt->repo != the_repository || !repo_has_promisor_remote(the_repository))
+	if (!repo_has_promisor_remote(opt->repo))
 		return;
 
 	for (e = &plist->items[plist->nr-1]; e >= plist->items; --e) {
@@ -4514,8 +4582,7 @@ static int process_entries(struct merge_options *opt,
 		BUG("dir_metadata accounting completely off; shouldn't happen");
 	}
 	if (record_tree &&
-	    write_tree(result_oid, &dir_metadata.versions, 0,
-		       opt->repo->hash_algo->rawsz) < 0)
+	    write_tree(opt->repo, result_oid, &dir_metadata.versions, 0) < 0)
 		ret = -1;
 cleanup:
 	string_list_clear(&plist, 0);
@@ -4560,10 +4627,10 @@ static int checkout(struct merge_options *opt,
 	unpack_opts.verbose_update = (opt->verbosity > 2);
 	unpack_opts.fn = twoway_merge;
 	unpack_opts.preserve_ignored = 0; /* FIXME: !opts->overwrite_ignore */
-	if (parse_tree(prev) < 0)
+	if (repo_parse_tree(opt->repo, prev) < 0)
 		return -1;
 	init_tree_desc(&trees[0], &prev->object.oid, prev->buffer, prev->size);
-	if (parse_tree(next) < 0)
+	if (repo_parse_tree(opt->repo, next) < 0)
 		return -1;
 	init_tree_desc(&trees[1], &next->object.oid, next->buffer, next->size);
 
@@ -5118,7 +5185,8 @@ static void merge_check_renames_reusable(struct merge_options *opt,
 	 * optimization" comment near that case).
 	 *
 	 * This could be revisited in the future; see the commit message
-	 * where this comment was added for some possible pointers.
+	 * where this comment was added for some possible pointers, or the
+	 * later commit where this comment was added.
 	 */
 	if (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_NONE) {
 		renames->cached_pairs_valid_side = 0; /* neither side valid */
@@ -5220,7 +5288,8 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt,
 
 	if (result->clean >= 0) {
 		if (!opt->mergeability_only) {
-			result->tree = parse_tree_indirect(&working_tree_oid);
+			result->tree = repo_parse_tree_indirect(opt->repo,
+								&working_tree_oid);
 			if (!result->tree)
 				die(_("unable to read tree (%s)"),
 				    oid_to_hex(&working_tree_oid));
@@ -5241,20 +5310,20 @@ static void merge_ort_internal(struct merge_options *opt,
 			       struct commit *h2,
 			       struct merge_result *result)
 {
-	struct commit_list *merge_bases = copy_commit_list(_merge_bases);
+	struct commit_list *merge_bases = commit_list_copy(_merge_bases);
 	struct commit *next;
 	struct commit *merged_merge_bases;
 	const char *ancestor_name;
 	struct strbuf merge_base_abbrev = STRBUF_INIT;
 
 	if (!merge_bases) {
-		if (repo_get_merge_bases(the_repository, h1, h2,
+		if (repo_get_merge_bases(opt->repo, h1, h2,
 					 &merge_bases) < 0) {
 			result->clean = -1;
 			goto out;
 		}
 		/* See merge-ort.h:merge_incore_recursive() declaration NOTE */
-		merge_bases = reverse_commit_list(merge_bases);
+		merge_bases = commit_list_reverse(merge_bases);
 	}
 
 	merged_merge_bases = pop_commit(&merge_bases);
@@ -5322,7 +5391,7 @@ static void merge_ort_internal(struct merge_options *opt,
 	opt->ancestor = NULL;  /* avoid accidental re-use of opt->ancestor */
 
 out:
-	free_commit_list(merge_bases);
+	commit_list_free(merge_bases);
 }
 
 void merge_incore_nonrecursive(struct merge_options *opt,
@@ -5379,20 +5448,20 @@ static void merge_recursive_config(struct merge_options *opt, int ui)
 {
 	char *value = NULL;
 	int renormalize = 0;
-	repo_config_get_int(the_repository, "merge.verbosity", &opt->verbosity);
-	repo_config_get_int(the_repository, "diff.renamelimit", &opt->rename_limit);
-	repo_config_get_int(the_repository, "merge.renamelimit", &opt->rename_limit);
-	repo_config_get_bool(the_repository, "merge.renormalize", &renormalize);
+	repo_config_get_int(opt->repo, "merge.verbosity", &opt->verbosity);
+	repo_config_get_int(opt->repo, "diff.renamelimit", &opt->rename_limit);
+	repo_config_get_int(opt->repo, "merge.renamelimit", &opt->rename_limit);
+	repo_config_get_bool(opt->repo, "merge.renormalize", &renormalize);
 	opt->renormalize = renormalize;
-	if (!repo_config_get_string(the_repository, "diff.renames", &value)) {
+	if (!repo_config_get_string(opt->repo, "diff.renames", &value)) {
 		opt->detect_renames = git_config_rename("diff.renames", value);
 		free(value);
 	}
-	if (!repo_config_get_string(the_repository, "merge.renames", &value)) {
+	if (!repo_config_get_string(opt->repo, "merge.renames", &value)) {
 		opt->detect_renames = git_config_rename("merge.renames", value);
 		free(value);
 	}
-	if (!repo_config_get_string(the_repository, "merge.directoryrenames", &value)) {
+	if (!repo_config_get_string(opt->repo, "merge.directoryrenames", &value)) {
 		int boolval = git_parse_maybe_bool(value);
 		if (0 <= boolval) {
 			opt->detect_directory_renames = boolval ?
@@ -5405,7 +5474,7 @@ static void merge_recursive_config(struct merge_options *opt, int ui)
 		free(value);
 	}
 	if (ui) {
-		if (!repo_config_get_string(the_repository, "diff.algorithm", &value)) {
+		if (!repo_config_get_string(opt->repo, "diff.algorithm", &value)) {
 			long diff_algorithm = parse_algorithm_value(value);
 			if (diff_algorithm < 0)
 				die(_("unknown value for config '%s': %s"), "diff.algorithm", value);
@@ -5413,7 +5482,7 @@ static void merge_recursive_config(struct merge_options *opt, int ui)
 			free(value);
 		}
 	}
-	repo_config(the_repository, git_xmerge_config, NULL);
+	repo_config(opt->repo, git_xmerge_config, NULL);
 }
 
 static void init_merge_options(struct merge_options *opt,
@@ -5496,7 +5565,6 @@ int parse_merge_opt(struct merge_options *opt, const char *s)
 		if (value < 0)
 			return -1;
 		/* clear out previous settings */
-		DIFF_XDL_CLR(opt, NEED_MINIMAL);
 		opt->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK;
 		opt->xdl_opts |= value;
 	}
diff --git a/merge.c b/merge.c
index 5ecaf50..0f5e823 100644
--- a/merge.c
+++ b/merge.c
@@ -68,18 +68,18 @@ int checkout_fast_forward(struct repository *r,
 	memset(&trees, 0, sizeof(trees));
 	memset(&t, 0, sizeof(t));
 
-	trees[nr_trees] = parse_tree_indirect(head);
+	trees[nr_trees] = repo_parse_tree_indirect(the_repository, head);
 	if (!trees[nr_trees++]) {
 		rollback_lock_file(&lock_file);
 		return -1;
 	}
-	trees[nr_trees] = parse_tree_indirect(remote);
+	trees[nr_trees] = repo_parse_tree_indirect(the_repository, remote);
 	if (!trees[nr_trees++]) {
 		rollback_lock_file(&lock_file);
 		return -1;
 	}
 	for (i = 0; i < nr_trees; i++) {
-		if (parse_tree(trees[i]) < 0) {
+		if (repo_parse_tree(the_repository, trees[i]) < 0) {
 			rollback_lock_file(&lock_file);
 			return -1;
 		}
diff --git a/meson.build b/meson.build
index 1f95a06..9e9ba9b 100644
--- a/meson.build
+++ b/meson.build
@@ -239,7 +239,9 @@
 sed = find_program('sed', dirs: program_path, native: true)
 shell = find_program('sh', dirs: program_path, native: true)
 tar = find_program('tar', dirs: program_path, native: true)
+tclsh = find_program('tclsh', required: get_option('git_gui'), native: false)
 time = find_program('time', dirs: program_path, required: get_option('benchmarks'))
+wish = find_program('wish', required: get_option('git_gui').enabled() or get_option('gitk').enabled(), native: false)
 
 # Detect the target shell that is used by Git at runtime. Note that we prefer
 # "/bin/sh" over a PATH-based lookup, which provides a working shell on most
@@ -397,6 +399,9 @@
   'object-name.c',
   'object.c',
   'odb.c',
+  'odb/source.c',
+  'odb/source-files.c',
+  'odb/streaming.c',
   'oid-array.c',
   'oidmap.c',
   'oidset.c',
@@ -470,6 +475,7 @@
   'repack-midx.c',
   'repack-promisor.c',
   'replace-object.c',
+  'replay.c',
   'repo-settings.c',
   'repository.c',
   'rerere.c',
@@ -490,7 +496,6 @@
   'stable-qsort.c',
   'statinfo.c',
   'strbuf.c',
-  'streaming.c',
   'string-list.c',
   'strmap.c',
   'strvec.c',
@@ -609,6 +614,7 @@
   'builtin/grep.c',
   'builtin/hash-object.c',
   'builtin/help.c',
+  'builtin/history.c',
   'builtin/hook.c',
   'builtin/index-pack.c',
   'builtin/init-db.c',
@@ -718,11 +724,14 @@
 
 builtin_sources += custom_target(
   output: 'config-list.h',
+  depfile: 'config-list.h.d',
+  depend_files: [ 'generate-configlist.sh' ],
   command: [
     shell,
-    meson.current_source_dir() + '/generate-configlist.sh',
+    meson.current_source_dir() / 'generate-configlist.sh',
     meson.current_source_dir(),
     '@OUTPUT@',
+    '@DEPFILE@',
   ],
   env: script_environment,
 )
@@ -1033,42 +1042,52 @@
     have_old_iconv = true
   endif
 
-  iconv_omits_bom_source = '''#
-    #include <iconv.h>
+  if meson.can_run_host_binaries()
+    if compiler.run('''
+      #include <iconv.h>
 
-    int main(int argc, const char **argv)
-    {
-  '''
-  if have_old_iconv
-    iconv_omits_bom_source += '''
-      typedef const char *iconv_ibp;
-    '''
-  else
-    iconv_omits_bom_source += '''
-      typedef char *iconv_ibp;
-    '''
-  endif
-  iconv_omits_bom_source += '''
-      int v;
-      iconv_t conv;
-      char in[] = "a"; iconv_ibp pin = in;
-      char out[20] = ""; char *pout = out;
-      size_t isz = sizeof in;
-      size_t osz = sizeof out;
+      int main(int argc, const char **argv)
+      {
+        char in[] = "a", *inpos = in;
+        char out[20] = "", *outpos = out;
+        size_t insz = sizeof(in), outsz = sizeof(out);
+        iconv_t conv = iconv_open("UTF-16", "UTF-8");
+        iconv(conv, (void *) &inpos, &insz, &outpos, &outsz);
+        iconv_close(conv);
+        return (unsigned char)(out[0]) + (unsigned char)(out[1]) != 0xfe + 0xff;
+      }
+    ''',
+      dependencies: iconv,
+      name: 'iconv omits BOM',
+    ).returncode() != 0
+      libgit_c_args += '-DICONV_OMITS_BOM'
+    endif
 
-      conv = iconv_open("UTF-16", "UTF-8");
-      iconv(conv, &pin, &isz, &pout, &osz);
-      iconv_close(conv);
-      v = (unsigned char)(out[0]) + (unsigned char)(out[1]);
-      return v != 0xfe + 0xff;
-    }
-  '''
+    if compiler.run('''
+      #include <iconv.h>
+      #include <string.h>
 
-  if compiler.run(iconv_omits_bom_source,
-    dependencies: iconv,
-    name: 'iconv omits BOM',
-  ).returncode() != 0
-    libgit_c_args += '-DICONV_OMITS_BOM'
+      int main(int argc, const char *argv[])
+      {
+          char in[] = "\x1b\x24\x42\x24\x22\x24\x22\x1b\x28\x42", *inpos = in;
+          char out[7] = { 0 }, *outpos = out;
+          size_t insz = sizeof(in) - 1, outsz = 4;
+          iconv_t conv = iconv_open("UTF-8", "ISO-2022-JP");
+          if (!conv)
+              return 1;
+          if (iconv(conv, (void *) &inpos, &insz, &outpos, &outsz) != (size_t) -1)
+              return 2;
+          outsz = sizeof(out) - (outpos - out);
+          if (iconv(conv, (void *) &inpos, &insz, &outpos, &outsz) == (size_t) -1)
+              return 3;
+          return strcmp("\343\201\202\343\201\202", out) ? 4 : 0;
+      }
+    ''',
+      dependencies: iconv,
+      name: 'iconv handles restarts properly',
+    ).returncode() != 0
+      libgit_c_args += '-DICONV_RESTART_RESET'
+    endif
   endif
 else
   libgit_c_args += '-DNO_ICONV'
@@ -1292,6 +1311,8 @@
   libgit_sources += 'compat/linux/procinfo.c'
 elif host_machine.system() == 'windows'
   libgit_sources += 'compat/win32/trace2_win32_process_info.c'
+elif host_machine.system() == 'darwin'
+  libgit_sources += 'compat/darwin/procinfo.c'
 else
   libgit_sources += 'compat/stub/procinfo.c'
 endif
@@ -1401,7 +1422,7 @@
   'strlcpy' : ['strlcpy.c'],
   'strtoull' : [],
   'setenv' : ['setenv.c'],
-  'mkdtemp' : ['mkdtemp.c'],
+  'mkdtemp' : [],
   'initgroups' : [],
   'strtoumax' : ['strtoumax.c', 'strtoimax.c'],
   'pread' : ['pread.c'],
@@ -1411,12 +1432,18 @@
   libgit_c_args += '-DUSE_WIN32_MMAP'
 else
   checkfuncs += {
-    'mmap' : ['mmap.c'],
     # provided by compat/mingw.c.
     'unsetenv' : ['unsetenv.c'],
     # provided by compat/mingw.c.
     'getpagesize' : [],
   }
+
+  if get_option('b_sanitize').contains('address') or get_option('b_sanitize').contains('leak')
+    libgit_c_args += '-DNO_MMAP'
+    libgit_sources += 'compat/mmap.c'
+  else
+    checkfuncs += { 'mmap': ['mmap.c'] }
+  endif
 endif
 
 foreach func, impls : checkfuncs
@@ -1486,7 +1513,7 @@
   endif
 endif
 
-if not meson.is_cross_build() and compiler.run('''
+if meson.can_run_host_binaries() and compiler.run('''
   #include <stdio.h>
 
   int main(int argc, const char **argv)
@@ -2244,6 +2271,16 @@
   configuration: build_options_config,
 )
 
+gitk_option = get_option('gitk').disable_auto_if(not wish.found())
+if gitk_option.allowed()
+  subproject('gitk')
+endif
+
+git_gui_option = get_option('git_gui').disable_auto_if(not tclsh.found() or not wish.found())
+if git_gui_option.allowed()
+  subproject('git-gui')
+endif
+
 # Development environments can be used via `meson devenv -C <builddir>`. This
 # allows you to execute test scripts directly with the built Git version and
 # puts the built version of Git in your PATH.
@@ -2270,6 +2307,8 @@
   'curl': curl,
   'expat': expat,
   'gettext': intl,
+  'gitk': gitk_option.allowed(),
+  'git-gui': git_gui_option.allowed(),
   'gitweb': gitweb_option.allowed(),
   'iconv': iconv,
   'pcre2': pcre2,
diff --git a/meson_options.txt b/meson_options.txt
index e0be260..659cbb2 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -43,6 +43,10 @@
   description: 'Build helpers used to push to remotes with the HTTP transport.')
 option('gettext', type: 'feature', value: 'auto',
   description: 'Build translation files.')
+option('gitk', type: 'feature', value: 'auto',
+  description: 'Build the Gitk graphical repository browser. Requires Tcl/Tk.')
+option('git_gui', type: 'feature', value: 'auto',
+  description: 'Build the git-gui graphical user interface for Git. Requires Tcl/Tk.')
 option('gitweb', type: 'feature', value: 'auto',
   description: 'Build Git web interface. Requires Perl.')
 option('iconv', type: 'feature', value: 'auto',
diff --git a/midx-write.c b/midx-write.c
index c73010d..6485cb6 100644
--- a/midx-write.c
+++ b/midx-write.c
@@ -697,36 +697,33 @@ static void prepare_midx_packing_data(struct packing_data *pdata,
 	trace2_region_leave("midx", "prepare_midx_packing_data", ctx->repo);
 }
 
-static int add_ref_to_pending(const char *refname, const char *referent UNUSED,
-			      const struct object_id *oid,
-			      int flag, void *cb_data)
+static int add_ref_to_pending(const struct reference *ref, void *cb_data)
 {
 	struct rev_info *revs = (struct rev_info*)cb_data;
+	const struct object_id *maybe_peeled = ref->oid;
 	struct object_id peeled;
 	struct object *object;
 
-	if ((flag & REF_ISSYMREF) && (flag & REF_ISBROKEN)) {
-		warning("symbolic ref is dangling: %s", refname);
+	if ((ref->flags & REF_ISSYMREF) && (ref->flags & REF_ISBROKEN)) {
+		warning("symbolic ref is dangling: %s", ref->name);
 		return 0;
 	}
 
-	if (!peel_iterated_oid(revs->repo, oid, &peeled))
-		oid = &peeled;
+	if (!reference_get_peeled_oid(revs->repo, ref, &peeled))
+		maybe_peeled = &peeled;
 
-	object = parse_object_or_die(revs->repo, oid, refname);
+	object = parse_object_or_die(revs->repo, maybe_peeled, ref->name);
 	if (object->type != OBJ_COMMIT)
 		return 0;
 
 	add_pending_object(revs, object, "");
-	if (bitmap_is_preferred_refname(revs->repo, refname))
+	if (bitmap_is_preferred_refname(revs->repo, ref->name))
 		object->flags |= NEEDS_BITMAP;
 	return 0;
 }
 
 struct bitmap_commit_cb {
-	struct commit **commits;
-	size_t commits_nr, commits_alloc;
-
+	struct commit_stack *commits;
 	struct write_midx_context *ctx;
 };
 
@@ -746,8 +743,7 @@ static void bitmap_show_commit(struct commit *commit, void *_data)
 	if (pos < 0)
 		return;
 
-	ALLOC_GROW(data->commits, data->commits_nr + 1, data->commits_alloc);
-	data->commits[data->commits_nr++] = commit;
+	commit_stack_push(data->commits, commit);
 }
 
 static int read_refs_snapshot(const char *refs_snapshot,
@@ -785,17 +781,15 @@ static int read_refs_snapshot(const char *refs_snapshot,
 	return 0;
 }
 
-static struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr_p,
-						    const char *refs_snapshot,
-						    struct write_midx_context *ctx)
+static void find_commits_for_midx_bitmap(struct commit_stack *commits,
+					 const char *refs_snapshot,
+					 struct write_midx_context *ctx)
 {
 	struct rev_info revs;
-	struct bitmap_commit_cb cb = {0};
+	struct bitmap_commit_cb cb = { .commits = commits, .ctx = ctx };
 
 	trace2_region_enter("midx", "find_commits_for_midx_bitmap", ctx->repo);
 
-	cb.ctx = ctx;
-
 	repo_init_revisions(ctx->repo, &revs, NULL);
 	if (refs_snapshot) {
 		read_refs_snapshot(refs_snapshot, &revs);
@@ -824,14 +818,10 @@ static struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr
 		die(_("revision walk setup failed"));
 
 	traverse_commit_list(&revs, bitmap_show_commit, NULL, &cb);
-	if (indexed_commits_nr_p)
-		*indexed_commits_nr_p = cb.commits_nr;
 
 	release_revisions(&revs);
 
 	trace2_region_leave("midx", "find_commits_for_midx_bitmap", ctx->repo);
-
-	return cb.commits;
 }
 
 static int write_midx_bitmap(struct write_midx_context *ctx,
@@ -1015,6 +1005,79 @@ static void clear_midx_files(struct odb_source *source,
 	strbuf_release(&buf);
 }
 
+static bool midx_needs_update(struct multi_pack_index *midx, struct write_midx_context *ctx)
+{
+	struct strset packs = STRSET_INIT;
+	struct strbuf buf = STRBUF_INIT;
+	bool needed = true;
+
+	/*
+	 * Ensure that we have a valid checksum before consulting the
+	 * exisiting MIDX in order to determine if we can avoid an
+	 * update.
+	 *
+	 * This is necessary because the given MIDX is loaded directly
+	 * from the object store (because we still compare our proposed
+	 * update to any on-disk MIDX regardless of whether or not we
+	 * have assigned "ctx.m") and is thus not guaranteed to have a
+	 * valid checksum.
+	 */
+	if (!midx_checksum_valid(midx))
+		goto out;
+
+	/*
+	 * Ignore incremental updates for now. The assumption is that any
+	 * incremental update would be either empty (in which case we will bail
+	 * out later) or it would actually cover at least one new pack.
+	 */
+	if (ctx->incremental)
+		goto out;
+
+	/*
+	 * Otherwise, we need to verify that the packs covered by the existing
+	 * MIDX match the packs that we already have. The logic to do so is way
+	 * more complicated than it has any right to be. This is because:
+	 *
+	 *   - We cannot assume any ordering.
+	 *
+	 *   - The MIDX packs may not be loaded at all, and loading them would
+	 *     be wasteful. So we need to use the pack names tracked by the
+	 *     MIDX itself.
+	 *
+	 *   - The MIDX pack names are tracking the ".idx" files, whereas the
+	 *     packs themselves are tracking the ".pack" files. So we need to
+	 *     strip suffixes.
+	 */
+	if (ctx->nr != midx->num_packs + midx->num_packs_in_base)
+		goto out;
+
+	for (uint32_t i = 0; i < ctx->nr; i++) {
+		strbuf_reset(&buf);
+		strbuf_addstr(&buf, pack_basename(ctx->info[i].p));
+		strbuf_strip_suffix(&buf, ".pack");
+
+		if (!strset_add(&packs, buf.buf))
+			BUG("same pack added twice?");
+	}
+
+	for (uint32_t i = 0; i < ctx->nr; i++) {
+		strbuf_reset(&buf);
+		strbuf_addstr(&buf, midx->pack_names[i]);
+		strbuf_strip_suffix(&buf, ".idx");
+
+		if (!strset_contains(&packs, buf.buf))
+			goto out;
+		strset_remove(&packs, buf.buf);
+	}
+
+	needed = false;
+
+out:
+	strbuf_release(&buf);
+	strset_clear(&packs);
+	return needed;
+}
+
 static int write_midx_internal(struct odb_source *source,
 			       struct string_list *packs_to_include,
 			       struct string_list *packs_to_drop,
@@ -1032,6 +1095,7 @@ static int write_midx_internal(struct odb_source *source,
 	struct write_midx_context ctx = {
 		.preferred_pack_idx = NO_PREFERRED_PACK,
 	 };
+	struct multi_pack_index *midx_to_free = NULL;
 	int bitmapped_packs_concat_len = 0;
 	int pack_name_concat_len = 0;
 	int dropped_packs = 0;
@@ -1112,27 +1176,39 @@ static int write_midx_internal(struct odb_source *source,
 	for_each_file_in_pack_dir(source->path, add_pack_to_midx, &ctx);
 	stop_progress(&ctx.progress);
 
-	if ((ctx.m && ctx.nr == ctx.m->num_packs + ctx.m->num_packs_in_base) &&
-	    !ctx.incremental &&
-	    !(packs_to_include || packs_to_drop)) {
-		struct bitmap_index *bitmap_git;
-		int bitmap_exists;
-		int want_bitmap = flags & MIDX_WRITE_BITMAP;
+	if (!packs_to_drop) {
+		/*
+		 * If there is no MIDX then either it doesn't exist, or we're
+		 * doing a geometric repack. Try to load it from the source to
+		 * tell these two cases apart.
+		 */
+		struct multi_pack_index *midx = ctx.m;
+		if (!midx)
+			midx = midx_to_free = load_multi_pack_index(ctx.source);
 
-		bitmap_git = prepare_midx_bitmap_git(ctx.m);
-		bitmap_exists = bitmap_git && bitmap_is_midx(bitmap_git);
-		free_bitmap_index(bitmap_git);
+		if (midx && !midx_needs_update(midx, &ctx)) {
+			struct bitmap_index *bitmap_git;
+			int bitmap_exists;
+			int want_bitmap = flags & MIDX_WRITE_BITMAP;
 
-		if (bitmap_exists || !want_bitmap) {
-			/*
-			 * The correct MIDX already exists, and so does a
-			 * corresponding bitmap (or one wasn't requested).
-			 */
-			if (!want_bitmap)
-				clear_midx_files_ext(source, "bitmap", NULL);
-			result = 0;
-			goto cleanup;
+			bitmap_git = prepare_midx_bitmap_git(midx);
+			bitmap_exists = bitmap_git && bitmap_is_midx(bitmap_git);
+			free_bitmap_index(bitmap_git);
+
+			if (bitmap_exists || !want_bitmap) {
+				/*
+				 * The correct MIDX already exists, and so does a
+				 * corresponding bitmap (or one wasn't requested).
+				 */
+				if (!want_bitmap)
+					clear_midx_files_ext(source, "bitmap", NULL);
+				result = 0;
+				goto cleanup;
+			}
 		}
+
+		close_midx(midx_to_free);
+		midx_to_free = NULL;
 	}
 
 	if (ctx.incremental && !ctx.nr) {
@@ -1376,15 +1452,14 @@ static int write_midx_internal(struct odb_source *source,
 
 	if (flags & MIDX_WRITE_BITMAP) {
 		struct packing_data pdata;
-		struct commit **commits;
-		uint32_t commits_nr;
+		struct commit_stack commits = COMMIT_STACK_INIT;
 
 		if (!ctx.entries_nr)
 			BUG("cannot write a bitmap without any objects");
 
 		prepare_midx_packing_data(&pdata, &ctx);
 
-		commits = find_commits_for_midx_bitmap(&commits_nr, refs_snapshot, &ctx);
+		find_commits_for_midx_bitmap(&commits, refs_snapshot, &ctx);
 
 		/*
 		 * The previous steps translated the information from
@@ -1395,17 +1470,16 @@ static int write_midx_internal(struct odb_source *source,
 		FREE_AND_NULL(ctx.entries);
 		ctx.entries_nr = 0;
 
-		if (write_midx_bitmap(&ctx,
-				      midx_hash, &pdata, commits, commits_nr,
-				      flags) < 0) {
+		if (write_midx_bitmap(&ctx, midx_hash, &pdata,
+				      commits.items, commits.nr, flags) < 0) {
 			error(_("could not write multi-pack bitmap"));
 			clear_packing_data(&pdata);
-			free(commits);
+			commit_stack_clear(&commits);
 			goto cleanup;
 		}
 
 		clear_packing_data(&pdata);
-		free(commits);
+		commit_stack_clear(&commits);
 	}
 	/*
 	 * NOTE: Do not use ctx.entries beyond this point, since it might
@@ -1459,7 +1533,7 @@ static int write_midx_internal(struct odb_source *source,
 	}
 
 	if (ctx.m || ctx.base_midx)
-		close_object_store(ctx.repo->objects);
+		odb_close(ctx.repo->objects);
 
 	if (commit_lock_file(&lk) < 0)
 		die_errno(_("could not write multi-pack-index"));
@@ -1488,6 +1562,7 @@ static int write_midx_internal(struct odb_source *source,
 		free(keep_hashes);
 	}
 	strbuf_release(&midx_name);
+	close_midx(midx_to_free);
 
 	trace2_region_leave("midx", "write_midx_internal", r);
 
diff --git a/midx.c b/midx.c
index 1d6269f..ab8e261 100644
--- a/midx.c
+++ b/midx.c
@@ -95,8 +95,9 @@ static int midx_read_object_offsets(const unsigned char *chunk_start,
 
 struct multi_pack_index *get_multi_pack_index(struct odb_source *source)
 {
-	packfile_store_prepare(source->odb->packfiles);
-	return source->midx;
+	struct odb_source_files *files = odb_source_files_downcast(source);
+	packfile_store_prepare(files->packed);
+	return files->packed->midx;
 }
 
 static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *source,
@@ -447,7 +448,7 @@ static uint32_t midx_for_pack(struct multi_pack_index **_m,
 int prepare_midx_pack(struct multi_pack_index *m,
 		      uint32_t pack_int_id)
 {
-	struct repository *r = m->source->odb->repo;
+	struct odb_source_files *files = odb_source_files_downcast(m->source);
 	struct strbuf pack_name = STRBUF_INIT;
 	struct packed_git *p;
 
@@ -458,12 +459,10 @@ int prepare_midx_pack(struct multi_pack_index *m,
 	if (m->packs[pack_int_id])
 		return 0;
 
-	strbuf_addf(&pack_name, "%s/pack/%s", m->source->path,
+	strbuf_addf(&pack_name, "%s/pack/%s", files->base.path,
 		    m->pack_names[pack_int_id]);
-	p = packfile_store_load_pack(r->objects->packfiles,
-				     pack_name.buf, m->source->local);
-	if (p)
-		list_add_tail(&p->mru, &r->objects->packfiles->mru);
+	p = packfile_store_load_pack(files->packed,
+				     pack_name.buf, files->base.local);
 	strbuf_release(&pack_name);
 
 	if (!p) {
@@ -688,7 +687,7 @@ int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id)
 {
 	if (m->preferred_pack_idx == -1) {
 		uint32_t midx_pos;
-		if (load_midx_revindex(m) < 0) {
+		if (load_midx_revindex(m)) {
 			m->preferred_pack_idx = -2;
 			return -1;
 		}
@@ -706,18 +705,19 @@ int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id)
 
 int prepare_multi_pack_index_one(struct odb_source *source)
 {
+	struct odb_source_files *files = odb_source_files_downcast(source);
 	struct repository *r = source->odb->repo;
 
 	prepare_repo_settings(r);
 	if (!r->settings.core_multi_pack_index)
 		return 0;
 
-	if (source->midx)
+	if (files->packed->midx)
 		return 1;
 
-	source->midx = load_multi_pack_index(source);
+	files->packed->midx = load_multi_pack_index(source);
 
-	return !!source->midx;
+	return !!files->packed->midx;
 }
 
 int midx_checksum_valid(struct multi_pack_index *m)
@@ -806,9 +806,10 @@ void clear_midx_file(struct repository *r)
 		struct odb_source *source;
 
 		for (source = r->objects->sources; source; source = source->next) {
-			if (source->midx)
-				close_midx(source->midx);
-			source->midx = NULL;
+			struct odb_source_files *files = odb_source_files_downcast(source);
+			if (files->packed->midx)
+				close_midx(files->packed->midx);
+			files->packed->midx = NULL;
 		}
 	}
 
diff --git a/negotiator/default.c b/negotiator/default.c
index c479da9..116dedc 100644
--- a/negotiator/default.c
+++ b/negotiator/default.c
@@ -38,11 +38,10 @@ static void rev_list_push(struct negotiation_state *ns,
 	}
 }
 
-static int clear_marks(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-		       int flag UNUSED,
-		       void *cb_data UNUSED)
+static int clear_marks(const struct reference *ref, void *cb_data UNUSED)
 {
-	struct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0);
+	struct object *o = deref_tag(the_repository, parse_object(the_repository, ref->oid),
+				     ref->name, 0);
 
 	if (o && o->type == OBJ_COMMIT)
 		clear_commit_marks((struct commit *)o,
diff --git a/negotiator/skipping.c b/negotiator/skipping.c
index 616df6b..0a27213 100644
--- a/negotiator/skipping.c
+++ b/negotiator/skipping.c
@@ -75,11 +75,10 @@ static struct entry *rev_list_push(struct data *data, struct commit *commit, int
 	return entry;
 }
 
-static int clear_marks(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-		       int flag UNUSED,
-		       void *cb_data UNUSED)
+static int clear_marks(const struct reference *ref, void *cb_data UNUSED)
 {
-	struct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0);
+	struct object *o = deref_tag(the_repository, parse_object(the_repository, ref->oid),
+				     ref->name, 0);
 
 	if (o && o->type == OBJ_COMMIT)
 		clear_commit_marks((struct commit *)o,
diff --git a/notes-merge.c b/notes-merge.c
index 5869399..b9322ab 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -359,9 +359,9 @@ static int ll_merge_in_worktree(struct notes_merge_options *o,
 	mmfile_t base, local, remote;
 	enum ll_merge_result status;
 
-	read_mmblob(&base, &p->base);
-	read_mmblob(&local, &p->local);
-	read_mmblob(&remote, &p->remote);
+	read_mmblob(&base, the_repository->objects, &p->base);
+	read_mmblob(&local, the_repository->objects, &p->local);
+	read_mmblob(&remote, the_repository->objects, &p->remote);
 
 	status = ll_merge(&result_buf, oid_to_hex(&p->obj), &base, NULL,
 			  &local, o->local_ref, &remote, o->remote_ref,
@@ -668,11 +668,11 @@ int notes_merge(struct notes_merge_options *o,
 		commit_list_insert(local, &parents);
 		create_notes_commit(o->repo, local_tree, parents, o->commit_msg.buf,
 				    o->commit_msg.len, result_oid);
-		free_commit_list(parents);
+		commit_list_free(parents);
 	}
 
 found_result:
-	free_commit_list(bases);
+	commit_list_free(bases);
 	strbuf_release(&(o->commit_msg));
 	trace_printf("notes_merge(): result = %i, result_oid = %.7s\n",
 	       result, oid_to_hex(result_oid));
diff --git a/notes-utils.c b/notes-utils.c
index 6a50c6d..5c1c75d 100644
--- a/notes-utils.c
+++ b/notes-utils.c
@@ -40,7 +40,7 @@ void create_notes_commit(struct repository *r,
 			NULL))
 		die("Failed to commit notes tree to database");
 
-	free_commit_list(parents_to_free);
+	commit_list_free(parents_to_free);
 }
 
 void commit_notes(struct repository *r, struct notes_tree *t, const char *msg)
diff --git a/notes.c b/notes.c
index 9a2e918..51a7ef9 100644
--- a/notes.c
+++ b/notes.c
@@ -921,8 +921,7 @@ int combine_notes_cat_sort_uniq(struct object_id *cur_oid,
 	if (string_list_add_note_lines(&sort_uniq_list, new_oid))
 		goto out;
 	string_list_remove_empty_items(&sort_uniq_list, 0);
-	string_list_sort(&sort_uniq_list);
-	string_list_remove_duplicates(&sort_uniq_list, 0);
+	string_list_sort_u(&sort_uniq_list, 0);
 
 	/* create a new blob object from sort_uniq_list */
 	if (for_each_string_list(&sort_uniq_list,
@@ -938,13 +937,11 @@ int combine_notes_cat_sort_uniq(struct object_id *cur_oid,
 	return ret;
 }
 
-static int string_list_add_one_ref(const char *refname, const char *referent UNUSED,
-				   const struct object_id *oid UNUSED,
-				   int flag UNUSED, void *cb)
+static int string_list_add_one_ref(const struct reference *ref, void *cb)
 {
 	struct string_list *refs = cb;
-	if (!unsorted_string_list_has_string(refs, refname))
-		string_list_append(refs, refname);
+	if (!unsorted_string_list_has_string(refs, ref->name))
+		string_list_append(refs, ref->name);
 	return 0;
 }
 
@@ -955,8 +952,11 @@ void string_list_add_refs_by_glob(struct string_list *list, const char *glob)
 {
 	assert(list->strdup_strings);
 	if (has_glob_specials(glob)) {
-		refs_for_each_glob_ref(get_main_ref_store(the_repository),
-				       string_list_add_one_ref, glob, list);
+		struct refs_for_each_ref_options opts = {
+			.pattern = glob,
+		};
+		refs_for_each_ref_ext(get_main_ref_store(the_repository),
+				      string_list_add_one_ref, list, &opts);
 	} else {
 		struct object_id oid;
 		if (repo_get_oid(the_repository, glob, &oid))
diff --git a/object-file-convert.c b/object-file-convert.c
index 7ab875a..63ee186 100644
--- a/object-file-convert.c
+++ b/object-file-convert.c
@@ -6,14 +6,13 @@
 #include "hex.h"
 #include "repository.h"
 #include "hash.h"
-#include "hash.h"
 #include "object.h"
 #include "loose.h"
 #include "commit.h"
 #include "gpg-interface.h"
 #include "object-file-convert.h"
 
-int repo_oid_to_algop(struct repository *repo, const struct object_id *src,
+int repo_oid_to_algop(struct repository *repo, const struct object_id *srcoid,
 		      const struct git_hash_algo *to, struct object_id *dest)
 {
 	/*
@@ -21,9 +20,17 @@ int repo_oid_to_algop(struct repository *repo, const struct object_id *src,
 	 * default hash algorithm for that object.
 	 */
 	const struct git_hash_algo *from =
-		src->algo ? &hash_algos[src->algo] : repo->hash_algo;
+		srcoid->algo ? &hash_algos[srcoid->algo] : repo->hash_algo;
+	struct object_id temp;
+	const struct object_id *src = srcoid;
 
-	if (from == to) {
+	if (!srcoid->algo) {
+		oidcpy(&temp, srcoid);
+		temp.algo = hash_algo_by_ptr(repo->hash_algo);
+		src = &temp;
+	}
+
+	if (from == to || !to) {
 		if (src != dest)
 			oidcpy(dest, src);
 		return 0;
diff --git a/object-file.c b/object-file.c
index 4675c8e..c62e549 100644
--- a/object-file.c
+++ b/object-file.c
@@ -20,13 +20,13 @@
 #include "object-file-convert.h"
 #include "object-file.h"
 #include "odb.h"
+#include "odb/streaming.h"
 #include "oidtree.h"
 #include "pack.h"
 #include "packfile.h"
 #include "path.h"
 #include "read-cache-ll.h"
 #include "setup.h"
-#include "streaming.h"
 #include "tempfile.h"
 #include "tmp-objdir.h"
 
@@ -99,8 +99,8 @@ static int check_and_freshen_source(struct odb_source *source,
 	return check_and_freshen_file(path.buf, freshen);
 }
 
-int has_loose_object(struct odb_source *source,
-		     const struct object_id *oid)
+int odb_source_loose_has_object(struct odb_source *source,
+				const struct object_id *oid)
 {
 	return check_and_freshen_source(source, oid, 0);
 }
@@ -129,32 +129,27 @@ int check_object_signature(struct repository *r, const struct object_id *oid,
 	return !oideq(oid, &real_oid) ? -1 : 0;
 }
 
-int stream_object_signature(struct repository *r, const struct object_id *oid)
+int stream_object_signature(struct repository *r,
+			    struct odb_read_stream *st,
+			    const struct object_id *oid)
 {
 	struct object_id real_oid;
-	unsigned long size;
-	enum object_type obj_type;
-	struct git_istream *st;
 	struct git_hash_ctx c;
 	char hdr[MAX_HEADER_LEN];
 	int hdrlen;
 
-	st = open_istream(r, oid, &obj_type, &size, NULL);
-	if (!st)
-		return -1;
-
 	/* Generate the header */
-	hdrlen = format_object_header(hdr, sizeof(hdr), obj_type, size);
+	hdrlen = format_object_header(hdr, sizeof(hdr), st->type, st->size);
 
 	/* Sha1.. */
 	r->hash_algo->init_fn(&c);
 	git_hash_update(&c, hdr, hdrlen);
 	for (;;) {
 		char buf[1024 * 16];
-		ssize_t readlen = read_istream(st, buf, sizeof(buf));
+		ssize_t readlen = odb_read_stream_read(st, buf, sizeof(buf));
 
 		if (readlen < 0) {
-			close_istream(st);
+			odb_read_stream_close(st);
 			return -1;
 		}
 		if (!readlen)
@@ -162,71 +157,35 @@ int stream_object_signature(struct repository *r, const struct object_id *oid)
 		git_hash_update(&c, buf, readlen);
 	}
 	git_hash_final_oid(&real_oid, &c);
-	close_istream(st);
 	return !oideq(oid, &real_oid) ? -1 : 0;
 }
 
 /*
- * Find "oid" as a loose object in the local repository or in an alternate.
- * Returns 0 on success, negative on failure.
+ * Find "oid" as a loose object in given source, open the object and return its
+ * file descriptor. Returns the file descriptor on success, negative on failure.
  *
  * The "path" out-parameter will give the path of the object we found (if any).
  * Note that it may point to static storage and is only valid until another
  * call to stat_loose_object().
  */
-static int stat_loose_object(struct repository *r, const struct object_id *oid,
-			     struct stat *st, const char **path)
-{
-	struct odb_source *source;
-	static struct strbuf buf = STRBUF_INIT;
-
-	odb_prepare_alternates(r->objects);
-	for (source = r->objects->sources; source; source = source->next) {
-		*path = odb_loose_path(source, &buf, oid);
-		if (!lstat(*path, st))
-			return 0;
-	}
-
-	return -1;
-}
-
-/*
- * Like stat_loose_object(), but actually open the object and return the
- * descriptor. See the caveats on the "path" parameter above.
- */
-static int open_loose_object(struct repository *r,
+static int open_loose_object(struct odb_source_loose *loose,
 			     const struct object_id *oid, const char **path)
 {
-	int fd;
-	struct odb_source *source;
-	int most_interesting_errno = ENOENT;
 	static struct strbuf buf = STRBUF_INIT;
+	int fd;
 
-	odb_prepare_alternates(r->objects);
-	for (source = r->objects->sources; source; source = source->next) {
-		*path = odb_loose_path(source, &buf, oid);
-		fd = git_open(*path);
-		if (fd >= 0)
-			return fd;
+	*path = odb_loose_path(loose->source, &buf, oid);
+	fd = git_open(*path);
+	if (fd >= 0)
+		return fd;
 
-		if (most_interesting_errno == ENOENT)
-			most_interesting_errno = errno;
-	}
-	errno = most_interesting_errno;
 	return -1;
 }
 
-static int quick_has_loose(struct repository *r,
+static int quick_has_loose(struct odb_source_loose *loose,
 			   const struct object_id *oid)
 {
-	struct odb_source *source;
-
-	odb_prepare_alternates(r->objects);
-	for (source = r->objects->sources; source; source = source->next) {
-		if (oidtree_contains(odb_loose_cache(source, oid), oid))
-			return 1;
-	}
-	return 0;
+	return !!oidtree_contains(odb_source_loose_cache(loose->source, oid), oid);
 }
 
 /*
@@ -252,23 +211,42 @@ static void *map_fd(int fd, const char *path, unsigned long *size)
 	return map;
 }
 
-void *map_loose_object(struct repository *r,
-		       const struct object_id *oid,
-		       unsigned long *size)
+static void *odb_source_loose_map_object(struct odb_source *source,
+					 const struct object_id *oid,
+					 unsigned long *size)
 {
+	struct odb_source_files *files = odb_source_files_downcast(source);
 	const char *p;
-	int fd = open_loose_object(r, oid, &p);
+	int fd = open_loose_object(files->loose, oid, &p);
 
 	if (fd < 0)
 		return NULL;
 	return map_fd(fd, p, size);
 }
 
-enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,
-						    unsigned char *map,
-						    unsigned long mapsize,
-						    void *buffer,
-						    unsigned long bufsiz)
+enum unpack_loose_header_result {
+	ULHR_OK,
+	ULHR_BAD,
+	ULHR_TOO_LONG,
+};
+
+/**
+ * unpack_loose_header() initializes the data stream needed to unpack
+ * a loose object header.
+ *
+ * Returns:
+ *
+ * - ULHR_OK on success
+ * - ULHR_BAD on error
+ * - ULHR_TOO_LONG if the header was too long
+ *
+ * It will only parse up to MAX_HEADER_LEN bytes.
+ */
+static enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,
+							   unsigned char *map,
+							   unsigned long mapsize,
+							   void *buffer,
+							   unsigned long bufsiz)
 {
 	int status;
 
@@ -347,11 +325,18 @@ static void *unpack_loose_rest(git_zstream *stream,
 }
 
 /*
+ * parse_loose_header() parses the starting "<type> <len>\0" of an
+ * object. If it doesn't follow that format -1 is returned. To check
+ * the validity of the <type> populate the "typep" in the "struct
+ * object_info". It will be OBJ_BAD if the object type is unknown. The
+ * parsed <len> can be retrieved via "oi->sizep", and from there
+ * passed to unpack_loose_rest().
+ *
  * We used to just use "sscanf()", but that's actually way
  * too permissive for what we want to check. So do an anal
  * object header parse by hand.
  */
-int parse_loose_header(const char *hdr, struct object_info *oi)
+static int parse_loose_header(const char *hdr, struct object_info *oi)
 {
 	const char *type_buf = hdr;
 	size_t size;
@@ -407,22 +392,22 @@ int parse_loose_header(const char *hdr, struct object_info *oi)
 	return 0;
 }
 
-int loose_object_info(struct repository *r,
-		      const struct object_id *oid,
-		      struct object_info *oi, int flags)
+static int read_object_info_from_path(struct odb_source *source,
+				      const char *path,
+				      const struct object_id *oid,
+				      struct object_info *oi,
+				      enum object_info_flags flags)
 {
-	int status = 0;
+	struct odb_source_files *files = odb_source_files_downcast(source);
+	int ret;
 	int fd;
 	unsigned long mapsize;
-	const char *path;
-	void *map;
-	git_zstream stream;
+	void *map = NULL;
+	git_zstream stream, *stream_to_end = NULL;
 	char hdr[MAX_HEADER_LEN];
 	unsigned long size_scratch;
 	enum object_type type_scratch;
-
-	if (oi->delta_base_oid)
-		oidclr(oi->delta_base_oid, r->hash_algo);
+	struct stat st;
 
 	/*
 	 * If we don't care about type or size, then we don't
@@ -432,73 +417,143 @@ int loose_object_info(struct repository *r,
 	 * return value implicitly indicates whether the
 	 * object even exists.
 	 */
-	if (!oi->typep && !oi->sizep && !oi->contentp) {
+	if (!oi || (!oi->typep && !oi->sizep && !oi->contentp)) {
 		struct stat st;
-		if (!oi->disk_sizep && (flags & OBJECT_INFO_QUICK))
-			return quick_has_loose(r, oid) ? 0 : -1;
-		if (stat_loose_object(r, oid, &st, &path) < 0)
-			return -1;
-		if (oi->disk_sizep)
-			*oi->disk_sizep = st.st_size;
-		return 0;
+
+		if ((!oi || (!oi->disk_sizep && !oi->mtimep)) && (flags & OBJECT_INFO_QUICK)) {
+			ret = quick_has_loose(files->loose, oid) ? 0 : -1;
+			goto out;
+		}
+
+		if (lstat(path, &st) < 0) {
+			ret = -1;
+			goto out;
+		}
+
+		if (oi) {
+			if (oi->disk_sizep)
+				*oi->disk_sizep = st.st_size;
+			if (oi->mtimep)
+				*oi->mtimep = st.st_mtime;
+		}
+
+		ret = 0;
+		goto out;
 	}
 
-	fd = open_loose_object(r, oid, &path);
+	fd = git_open(path);
 	if (fd < 0) {
 		if (errno != ENOENT)
 			error_errno(_("unable to open loose object %s"), oid_to_hex(oid));
-		return -1;
+		ret = -1;
+		goto out;
 	}
-	map = map_fd(fd, path, &mapsize);
-	if (!map)
-		return -1;
 
-	if (!oi->sizep)
-		oi->sizep = &size_scratch;
-	if (!oi->typep)
-		oi->typep = &type_scratch;
+	if (fstat(fd, &st)) {
+		close(fd);
+		ret = -1;
+		goto out;
+	}
+
+	mapsize = xsize_t(st.st_size);
+	if (!mapsize) {
+		close(fd);
+		ret = error(_("object file %s is empty"), path);
+		goto out;
+	}
+
+	map = xmmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);
+	close(fd);
+	if (!map) {
+		ret = -1;
+		goto out;
+	}
 
 	if (oi->disk_sizep)
 		*oi->disk_sizep = mapsize;
+	if (oi->mtimep)
+		*oi->mtimep = st.st_mtime;
+
+	stream_to_end = &stream;
 
 	switch (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr))) {
 	case ULHR_OK:
-		if (parse_loose_header(hdr, oi) < 0)
-			status = error(_("unable to parse %s header"), oid_to_hex(oid));
-		else if (*oi->typep < 0)
+		if (!oi->sizep)
+			oi->sizep = &size_scratch;
+		if (!oi->typep)
+			oi->typep = &type_scratch;
+
+		if (parse_loose_header(hdr, oi) < 0) {
+			ret = error(_("unable to parse %s header"), oid_to_hex(oid));
+			goto corrupt;
+		}
+
+		if (*oi->typep < 0)
 			die(_("invalid object type"));
 
-		if (!oi->contentp)
-			break;
-		*oi->contentp = unpack_loose_rest(&stream, hdr, *oi->sizep, oid);
-		if (*oi->contentp)
-			goto cleanup;
+		if (oi->contentp) {
+			*oi->contentp = unpack_loose_rest(&stream, hdr, *oi->sizep, oid);
+			if (!*oi->contentp) {
+				ret = -1;
+				goto corrupt;
+			}
+		}
 
-		status = -1;
 		break;
 	case ULHR_BAD:
-		status = error(_("unable to unpack %s header"),
-			       oid_to_hex(oid));
-		break;
+		ret = error(_("unable to unpack %s header"),
+			    oid_to_hex(oid));
+		goto corrupt;
 	case ULHR_TOO_LONG:
-		status = error(_("header for %s too long, exceeds %d bytes"),
-			       oid_to_hex(oid), MAX_HEADER_LEN);
-		break;
+		ret = error(_("header for %s too long, exceeds %d bytes"),
+			    oid_to_hex(oid), MAX_HEADER_LEN);
+		goto corrupt;
 	}
 
-	if (status && (flags & OBJECT_INFO_DIE_IF_CORRUPT))
+	ret = 0;
+
+corrupt:
+	if (ret && (flags & OBJECT_INFO_DIE_IF_CORRUPT))
 		die(_("loose object %s (stored in %s) is corrupt"),
 		    oid_to_hex(oid), path);
 
-cleanup:
-	git_inflate_end(&stream);
-	munmap(map, mapsize);
-	if (oi->sizep == &size_scratch)
-		oi->sizep = NULL;
-	if (oi->typep == &type_scratch)
-		oi->typep = NULL;
-	oi->whence = OI_LOOSE;
-	return status;
+out:
+	if (stream_to_end)
+		git_inflate_end(stream_to_end);
+	if (map)
+		munmap(map, mapsize);
+	if (oi) {
+		if (oi->sizep == &size_scratch)
+			oi->sizep = NULL;
+		if (oi->typep == &type_scratch)
+			oi->typep = NULL;
+		if (oi->delta_base_oid)
+			oidclr(oi->delta_base_oid, source->odb->repo->hash_algo);
+		if (!ret)
+			oi->whence = OI_LOOSE;
+	}
+
+	return ret;
+}
+
+int odb_source_loose_read_object_info(struct odb_source *source,
+				      const struct object_id *oid,
+				      struct object_info *oi,
+				      enum object_info_flags flags)
+{
+	static struct strbuf buf = STRBUF_INIT;
+
+	/*
+	 * The second read shouldn't cause new loose objects to show up, unless
+	 * there was a race condition with a secondary process. We don't care
+	 * about this case though, so we simply skip reading loose objects a
+	 * second time.
+	 */
+	if (flags & OBJECT_INFO_SECOND_READ)
+		return -1;
+
+	odb_loose_path(source, &buf, oid);
+	return read_object_info_from_path(source, buf.buf, oid, oi, flags);
 }
 
 static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_ctx *c,
@@ -678,32 +733,38 @@ struct transaction_packfile {
 	uint32_t nr_written;
 };
 
-struct odb_transaction {
-	struct object_database *odb;
+struct odb_transaction_files {
+	struct odb_transaction base;
 
 	struct tmp_objdir *objdir;
 	struct transaction_packfile packfile;
 };
 
-static void prepare_loose_object_transaction(struct odb_transaction *transaction)
+static void prepare_loose_object_transaction(struct odb_transaction *base)
 {
+	struct odb_transaction_files *transaction =
+		container_of_or_null(base, struct odb_transaction_files, base);
+
 	/*
 	 * We lazily create the temporary object directory
 	 * the first time an object might be added, since
 	 * callers may not know whether any objects will be
-	 * added at the time they call object_file_transaction_begin.
+	 * added at the time they call odb_transaction_files_begin.
 	 */
 	if (!transaction || transaction->objdir)
 		return;
 
-	transaction->objdir = tmp_objdir_create(transaction->odb->repo, "bulk-fsync");
+	transaction->objdir = tmp_objdir_create(base->source->odb->repo, "bulk-fsync");
 	if (transaction->objdir)
 		tmp_objdir_replace_primary_odb(transaction->objdir, 0);
 }
 
-static void fsync_loose_object_transaction(struct odb_transaction *transaction,
+static void fsync_loose_object_transaction(struct odb_transaction *base,
 					   int fd, const char *filename)
 {
+	struct odb_transaction_files *transaction =
+		container_of_or_null(base, struct odb_transaction_files, base);
+
 	/*
 	 * If we have an active ODB transaction, we issue a call that
 	 * cleans the filesystem page cache but avoids a hardware flush
@@ -722,7 +783,7 @@ static void fsync_loose_object_transaction(struct odb_transaction *transaction,
 /*
  * Cleanup after batch-mode fsync_object_files.
  */
-static void flush_loose_object_transaction(struct odb_transaction *transaction)
+static void flush_loose_object_transaction(struct odb_transaction_files *transaction)
 {
 	struct strbuf temp_path = STRBUF_INIT;
 	struct tempfile *temp;
@@ -740,7 +801,7 @@ static void flush_loose_object_transaction(struct odb_transaction *transaction)
 	 * the final name is visible.
 	 */
 	strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX",
-		    repo_get_object_directory(transaction->odb->repo));
+		    repo_get_object_directory(transaction->base.source->odb->repo));
 	temp = xmks_tempfile(temp_path.buf);
 	fsync_or_die(get_tempfile_fd(temp), get_tempfile_path(temp));
 	delete_tempfile(&temp);
@@ -986,35 +1047,15 @@ static int write_loose_object(struct odb_source *source,
 					  FOF_SKIP_COLLISION_CHECK);
 }
 
-static int freshen_loose_object(struct object_database *odb,
-				const struct object_id *oid)
+int odb_source_loose_freshen_object(struct odb_source *source,
+				    const struct object_id *oid)
 {
-	odb_prepare_alternates(odb);
-	for (struct odb_source *source = odb->sources; source; source = source->next)
-		if (check_and_freshen_source(source, oid, 1))
-			return 1;
-	return 0;
+	return !!check_and_freshen_source(source, oid, 1);
 }
 
-static int freshen_packed_object(struct object_database *odb,
-				 const struct object_id *oid)
-{
-	struct pack_entry e;
-	if (!find_pack_entry(odb->repo, oid, &e))
-		return 0;
-	if (e.p->is_cruft)
-		return 0;
-	if (e.p->freshened)
-		return 1;
-	if (!freshen_file(e.p->pack_name))
-		return 0;
-	e.p->freshened = 1;
-	return 1;
-}
-
-int stream_loose_object(struct odb_source *source,
-			struct input_stream *in_stream, size_t len,
-			struct object_id *oid)
+int odb_source_loose_write_stream(struct odb_source *source,
+				  struct odb_write_stream *in_stream, size_t len,
+				  struct object_id *oid)
 {
 	const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;
 	struct object_id compat_oid;
@@ -1091,12 +1132,10 @@ int stream_loose_object(struct odb_source *source,
 		die(_("deflateEnd on stream object failed (%d)"), ret);
 	close_loose_object(source, fd, tmp_file.buf);
 
-	if (freshen_packed_object(source->odb, oid) ||
-	    freshen_loose_object(source->odb, oid)) {
+	if (odb_freshen_object(source->odb, oid)) {
 		unlink_or_warn(tmp_file.buf);
 		goto cleanup;
 	}
-
 	odb_loose_path(source, &filename, oid);
 
 	/* We finally know the object path, and create the missing dir. */
@@ -1124,10 +1163,10 @@ int stream_loose_object(struct odb_source *source,
 	return err;
 }
 
-int write_object_file(struct odb_source *source,
-		      const void *buf, unsigned long len,
-		      enum object_type type, struct object_id *oid,
-		      struct object_id *compat_oid_in, unsigned flags)
+int odb_source_loose_write_object(struct odb_source *source,
+				  const void *buf, unsigned long len,
+				  enum object_type type, struct object_id *oid,
+				  struct object_id *compat_oid_in, unsigned flags)
 {
 	const struct git_hash_algo *algo = source->odb->repo->hash_algo;
 	const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;
@@ -1155,8 +1194,7 @@ int write_object_file(struct odb_source *source,
 	 * it out into .git/objects/??/?{38} file.
 	 */
 	write_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen);
-	if (freshen_packed_object(source->odb, oid) ||
-	    freshen_loose_object(source->odb, oid))
+	if (odb_freshen_object(source->odb, oid))
 		return 0;
 	if (write_loose_object(source, oid, hdr, hdrlen, buf, len, 0, flags))
 		return -1;
@@ -1179,7 +1217,7 @@ int force_object_loose(struct odb_source *source,
 	int ret;
 
 	for (struct odb_source *s = source->odb->sources; s; s = s->next)
-		if (has_loose_object(s, oid))
+		if (odb_source_loose_has_object(s, oid))
 			return 0;
 
 	oi.typep = &type;
@@ -1331,11 +1369,11 @@ static int index_core(struct index_state *istate,
 	return ret;
 }
 
-static int already_written(struct odb_transaction *transaction,
+static int already_written(struct odb_transaction_files *transaction,
 			   struct object_id *oid)
 {
 	/* The object may already exist in the repository */
-	if (odb_has_object(transaction->odb, oid,
+	if (odb_has_object(transaction->base.source->odb, oid,
 			   HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR))
 		return 1;
 
@@ -1349,14 +1387,14 @@ static int already_written(struct odb_transaction *transaction,
 }
 
 /* Lazily create backing packfile for the state */
-static void prepare_packfile_transaction(struct odb_transaction *transaction,
+static void prepare_packfile_transaction(struct odb_transaction_files *transaction,
 					 unsigned flags)
 {
 	struct transaction_packfile *state = &transaction->packfile;
 	if (!(flags & INDEX_WRITE_OBJECT) || state->f)
 		return;
 
-	state->f = create_tmp_packfile(transaction->odb->repo,
+	state->f = create_tmp_packfile(transaction->base.source->odb->repo,
 				       &state->pack_tmp_name);
 	reset_pack_idx_option(&state->pack_idx_opts);
 
@@ -1457,10 +1495,10 @@ static int stream_blob_to_pack(struct transaction_packfile *state,
 	return 0;
 }
 
-static void flush_packfile_transaction(struct odb_transaction *transaction)
+static void flush_packfile_transaction(struct odb_transaction_files *transaction)
 {
 	struct transaction_packfile *state = &transaction->packfile;
-	struct repository *repo = transaction->odb->repo;
+	struct repository *repo = transaction->base.source->odb->repo;
 	unsigned char hash[GIT_MAX_RAWSZ];
 	struct strbuf packname = STRBUF_INIT;
 	char *idx_tmp_name = NULL;
@@ -1485,7 +1523,7 @@ static void flush_packfile_transaction(struct odb_transaction *transaction)
 	}
 
 	strbuf_addf(&packname, "%s/pack/pack-%s.",
-		    repo_get_object_directory(transaction->odb->repo),
+		    repo_get_object_directory(transaction->base.source->odb->repo),
 		    hash_to_hex_algop(hash, repo->hash_algo));
 
 	stage_tmp_packfiles(repo, &packname, state->pack_tmp_name,
@@ -1525,7 +1563,7 @@ static void flush_packfile_transaction(struct odb_transaction *transaction)
  * binary blobs, they generally do not want to get any conversion, and
  * callers should avoid this code path when filters are requested.
  */
-static int index_blob_packfile_transaction(struct odb_transaction *transaction,
+static int index_blob_packfile_transaction(struct odb_transaction_files *transaction,
 					   struct object_id *result_oid, int fd,
 					   size_t size, const char *path,
 					   unsigned flags)
@@ -1544,7 +1582,7 @@ static int index_blob_packfile_transaction(struct odb_transaction *transaction,
 
 	header_len = format_object_header((char *)obuf, sizeof(obuf),
 					  OBJ_BLOB, size);
-	transaction->odb->repo->hash_algo->init_fn(&ctx);
+	transaction->base.source->odb->repo->hash_algo->init_fn(&ctx);
 	git_hash_update(&ctx, obuf, header_len);
 
 	/* Note: idx is non-NULL when we are writing */
@@ -1620,11 +1658,15 @@ int index_fd(struct index_state *istate, struct object_id *oid,
 		ret = index_core(istate, oid, fd, xsize_t(st->st_size),
 				 type, path, flags);
 	} else {
+		struct object_database *odb = the_repository->objects;
+		struct odb_transaction_files *files_transaction;
 		struct odb_transaction *transaction;
 
-		transaction = odb_transaction_begin(the_repository->objects);
-		ret = index_blob_packfile_transaction(the_repository->objects->transaction,
-						      oid, fd,
+		transaction = odb_transaction_begin(odb);
+		files_transaction = container_of(odb->transaction,
+						 struct odb_transaction_files,
+						 base);
+		ret = index_blob_packfile_transaction(files_transaction, oid, fd,
 						      xsize_t(st->st_size),
 						      path, flags);
 		odb_transaction_commit(transaction);
@@ -1661,7 +1703,11 @@ int index_path(struct index_state *istate, struct object_id *oid,
 		strbuf_release(&sb);
 		break;
 	case S_IFDIR:
-		return repo_resolve_gitlink_ref(istate->repo, path, "HEAD", oid);
+		if (repo_resolve_gitlink_ref(istate->repo, path, "HEAD", oid))
+			return error(_("'%s' does not have a commit checked out"), path);
+		if (&hash_algos[oid->algo] != istate->repo->hash_algo)
+			return error(_("cannot add a submodule of a different hash algorithm"));
+		break;
 	default:
 		return error(_("%s: unsupported file type"), path);
 	}
@@ -1774,24 +1820,52 @@ int for_each_loose_file_in_source(struct odb_source *source,
 	return r;
 }
 
-int for_each_loose_object(struct object_database *odb,
-			  each_loose_object_fn cb, void *data,
-			  enum for_each_object_flags flags)
-{
+struct for_each_object_wrapper_data {
 	struct odb_source *source;
+	const struct object_info *request;
+	odb_for_each_object_cb cb;
+	void *cb_data;
+};
 
-	odb_prepare_alternates(odb);
-	for (source = odb->sources; source; source = source->next) {
-		int r = for_each_loose_file_in_source(source, cb, NULL,
-						      NULL, data);
-		if (r)
-			return r;
+static int for_each_object_wrapper_cb(const struct object_id *oid,
+				      const char *path,
+				      void *cb_data)
+{
+	struct for_each_object_wrapper_data *data = cb_data;
 
-		if (flags & FOR_EACH_OBJECT_LOCAL_ONLY)
-			break;
+	if (data->request) {
+		struct object_info oi = *data->request;
+
+		if (read_object_info_from_path(data->source, path, oid, &oi, 0) < 0)
+			return -1;
+
+		return data->cb(oid, &oi, data->cb_data);
+	} else {
+		return data->cb(oid, NULL, data->cb_data);
 	}
+}
 
-	return 0;
+int odb_source_loose_for_each_object(struct odb_source *source,
+				     const struct object_info *request,
+				     odb_for_each_object_cb cb,
+				     void *cb_data,
+				     unsigned flags)
+{
+	struct for_each_object_wrapper_data data = {
+		.source = source,
+		.request = request,
+		.cb = cb,
+		.cb_data = cb_data,
+	};
+
+	/* There are no loose promisor objects, so we can return immediately. */
+	if ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY))
+		return 0;
+	if ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !source->local)
+		return 0;
+
+	return for_each_loose_file_in_source(source, for_each_object_wrapper_cb,
+					     NULL, NULL, &data);
 }
 
 static int append_loose_object(const struct object_id *oid,
@@ -1802,44 +1876,51 @@ static int append_loose_object(const struct object_id *oid,
 	return 0;
 }
 
-struct oidtree *odb_loose_cache(struct odb_source *source,
-				const struct object_id *oid)
+struct oidtree *odb_source_loose_cache(struct odb_source *source,
+				       const struct object_id *oid)
 {
+	struct odb_source_files *files = odb_source_files_downcast(source);
 	int subdir_nr = oid->hash[0];
 	struct strbuf buf = STRBUF_INIT;
-	size_t word_bits = bitsizeof(source->loose_objects_subdir_seen[0]);
+	size_t word_bits = bitsizeof(files->loose->subdir_seen[0]);
 	size_t word_index = subdir_nr / word_bits;
 	size_t mask = (size_t)1u << (subdir_nr % word_bits);
 	uint32_t *bitmap;
 
 	if (subdir_nr < 0 ||
-	    (size_t) subdir_nr >= bitsizeof(source->loose_objects_subdir_seen))
+	    (size_t) subdir_nr >= bitsizeof(files->loose->subdir_seen))
 		BUG("subdir_nr out of range");
 
-	bitmap = &source->loose_objects_subdir_seen[word_index];
+	bitmap = &files->loose->subdir_seen[word_index];
 	if (*bitmap & mask)
-		return source->loose_objects_cache;
-	if (!source->loose_objects_cache) {
-		ALLOC_ARRAY(source->loose_objects_cache, 1);
-		oidtree_init(source->loose_objects_cache);
+		return files->loose->cache;
+	if (!files->loose->cache) {
+		ALLOC_ARRAY(files->loose->cache, 1);
+		oidtree_init(files->loose->cache);
 	}
 	strbuf_addstr(&buf, source->path);
 	for_each_file_in_obj_subdir(subdir_nr, &buf,
 				    source->odb->repo->hash_algo,
 				    append_loose_object,
 				    NULL, NULL,
-				    source->loose_objects_cache);
+				    files->loose->cache);
 	*bitmap |= mask;
 	strbuf_release(&buf);
-	return source->loose_objects_cache;
+	return files->loose->cache;
 }
 
-void odb_clear_loose_cache(struct odb_source *source)
+static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
 {
-	oidtree_clear(source->loose_objects_cache);
-	FREE_AND_NULL(source->loose_objects_cache);
-	memset(&source->loose_objects_subdir_seen, 0,
-	       sizeof(source->loose_objects_subdir_seen));
+	oidtree_clear(loose->cache);
+	FREE_AND_NULL(loose->cache);
+	memset(&loose->subdir_seen, 0,
+	       sizeof(loose->subdir_seen));
+}
+
+void odb_source_loose_reprepare(struct odb_source *source)
+{
+	struct odb_source_files *files = odb_source_files_downcast(source);
+	odb_source_loose_clear_cache(files->loose);
 }
 
 static int check_stream_oid(git_zstream *stream,
@@ -1967,31 +2048,170 @@ int read_loose_object(struct repository *repo,
 	return ret;
 }
 
-struct odb_transaction *object_file_transaction_begin(struct odb_source *source)
+static void odb_transaction_files_commit(struct odb_transaction *base)
 {
+	struct odb_transaction_files *transaction =
+		container_of(base, struct odb_transaction_files, base);
+
+	flush_loose_object_transaction(transaction);
+	flush_packfile_transaction(transaction);
+}
+
+struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)
+{
+	struct odb_transaction_files *transaction;
 	struct object_database *odb = source->odb;
 
 	if (odb->transaction)
 		return NULL;
 
-	CALLOC_ARRAY(odb->transaction, 1);
-	odb->transaction->odb = odb;
+	transaction = xcalloc(1, sizeof(*transaction));
+	transaction->base.source = source;
+	transaction->base.commit = odb_transaction_files_commit;
 
-	return odb->transaction;
+	return &transaction->base;
 }
 
-void object_file_transaction_commit(struct odb_transaction *transaction)
+struct odb_source_loose *odb_source_loose_new(struct odb_source *source)
 {
-	if (!transaction)
+	struct odb_source_loose *loose;
+	CALLOC_ARRAY(loose, 1);
+	loose->source = source;
+	return loose;
+}
+
+void odb_source_loose_free(struct odb_source_loose *loose)
+{
+	if (!loose)
 		return;
+	odb_source_loose_clear_cache(loose);
+	loose_object_map_clear(&loose->map);
+	free(loose);
+}
+
+struct odb_loose_read_stream {
+	struct odb_read_stream base;
+	git_zstream z;
+	enum {
+		ODB_LOOSE_READ_STREAM_INUSE,
+		ODB_LOOSE_READ_STREAM_DONE,
+		ODB_LOOSE_READ_STREAM_ERROR,
+	} z_state;
+	void *mapped;
+	unsigned long mapsize;
+	char hdr[32];
+	int hdr_avail;
+	int hdr_used;
+};
+
+static ssize_t read_istream_loose(struct odb_read_stream *_st, char *buf, size_t sz)
+{
+	struct odb_loose_read_stream *st =
+		container_of(_st, struct odb_loose_read_stream, base);
+	size_t total_read = 0;
+
+	switch (st->z_state) {
+	case ODB_LOOSE_READ_STREAM_DONE:
+		return 0;
+	case ODB_LOOSE_READ_STREAM_ERROR:
+		return -1;
+	default:
+		break;
+	}
+
+	if (st->hdr_used < st->hdr_avail) {
+		size_t to_copy = st->hdr_avail - st->hdr_used;
+		if (sz < to_copy)
+			to_copy = sz;
+		memcpy(buf, st->hdr + st->hdr_used, to_copy);
+		st->hdr_used += to_copy;
+		total_read += to_copy;
+	}
+
+	while (total_read < sz) {
+		int status;
+
+		st->z.next_out = (unsigned char *)buf + total_read;
+		st->z.avail_out = sz - total_read;
+		status = git_inflate(&st->z, Z_FINISH);
+
+		total_read = st->z.next_out - (unsigned char *)buf;
+
+		if (status == Z_STREAM_END) {
+			git_inflate_end(&st->z);
+			st->z_state = ODB_LOOSE_READ_STREAM_DONE;
+			break;
+		}
+		if (status != Z_OK && (status != Z_BUF_ERROR || total_read < sz)) {
+			git_inflate_end(&st->z);
+			st->z_state = ODB_LOOSE_READ_STREAM_ERROR;
+			return -1;
+		}
+	}
+	return total_read;
+}
+
+static int close_istream_loose(struct odb_read_stream *_st)
+{
+	struct odb_loose_read_stream *st =
+		container_of(_st, struct odb_loose_read_stream, base);
+
+	if (st->z_state == ODB_LOOSE_READ_STREAM_INUSE)
+		git_inflate_end(&st->z);
+	munmap(st->mapped, st->mapsize);
+	return 0;
+}
+
+int odb_source_loose_read_object_stream(struct odb_read_stream **out,
+					struct odb_source *source,
+					const struct object_id *oid)
+{
+	struct object_info oi = OBJECT_INFO_INIT;
+	struct odb_loose_read_stream *st;
+	unsigned long mapsize;
+	void *mapped;
+
+	mapped = odb_source_loose_map_object(source, oid, &mapsize);
+	if (!mapped)
+		return -1;
 
 	/*
-	 * Ensure the transaction ending matches the pending transaction.
+	 * Note: we must allocate this structure early even though we may still
+	 * fail. This is because we need to initialize the zlib stream, and it
+	 * is not possible to copy the stream around after the fact because it
+	 * has self-referencing pointers.
 	 */
-	ASSERT(transaction == transaction->odb->transaction);
+	CALLOC_ARRAY(st, 1);
 
-	flush_loose_object_transaction(transaction);
-	flush_packfile_transaction(transaction);
-	transaction->odb->transaction = NULL;
-	free(transaction);
+	switch (unpack_loose_header(&st->z, mapped, mapsize, st->hdr,
+				    sizeof(st->hdr))) {
+	case ULHR_OK:
+		break;
+	case ULHR_BAD:
+	case ULHR_TOO_LONG:
+		goto error;
+	}
+
+	oi.sizep = &st->base.size;
+	oi.typep = &st->base.type;
+
+	if (parse_loose_header(st->hdr, &oi) < 0 || st->base.type < 0)
+		goto error;
+
+	st->mapped = mapped;
+	st->mapsize = mapsize;
+	st->hdr_used = strlen(st->hdr) + 1;
+	st->hdr_avail = st->z.total_out;
+	st->z_state = ODB_LOOSE_READ_STREAM_INUSE;
+	st->base.close = close_istream_loose;
+	st->base.read = read_istream_loose;
+
+	*out = &st->base;
+
+	return 0;
+error:
+	git_inflate_end(&st->z);
+	munmap(mapped, mapsize);
+	free(st);
+	return -1;
 }
diff --git a/object-file.h b/object-file.h
index 3fd48dc..ff6da65 100644
--- a/object-file.h
+++ b/object-file.h
@@ -7,14 +7,6 @@
 
 struct index_state;
 
-/*
- * Set this to 0 to prevent odb_read_object_info_extended() from fetching missing
- * blobs. This has a difference only if extensions.partialClone is set.
- *
- * Its default value is 1.
- */
-extern int fetch_if_missing;
-
 enum {
 	INDEX_WRITE_OBJECT = (1 << 0),
 	INDEX_FORMAT_CHECK = (1 << 1),
@@ -24,17 +16,70 @@ enum {
 int index_fd(struct index_state *istate, struct object_id *oid, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
 int index_path(struct index_state *istate, struct object_id *oid, const char *path, struct stat *st, unsigned flags);
 
+struct object_info;
+struct odb_read_stream;
 struct odb_source;
 
+struct odb_source_loose {
+	struct odb_source *source;
+
+	/*
+	 * Used to store the results of readdir(3) calls when we are OK
+	 * sacrificing accuracy due to races for speed. That includes
+	 * object existence with OBJECT_INFO_QUICK, as well as
+	 * our search for unique abbreviated hashes. Don't use it for tasks
+	 * requiring greater accuracy!
+	 *
+	 * Be sure to call odb_load_loose_cache() before using.
+	 */
+	uint32_t subdir_seen[8]; /* 256 bits */
+	struct oidtree *cache;
+
+	/* Map between object IDs for loose objects. */
+	struct loose_object_map *map;
+};
+
+struct odb_source_loose *odb_source_loose_new(struct odb_source *source);
+void odb_source_loose_free(struct odb_source_loose *loose);
+
+/* Reprepare the loose source by emptying the loose object cache. */
+void odb_source_loose_reprepare(struct odb_source *source);
+
+int odb_source_loose_read_object_info(struct odb_source *source,
+				      const struct object_id *oid,
+				      struct object_info *oi,
+				      enum object_info_flags flags);
+
+int odb_source_loose_read_object_stream(struct odb_read_stream **out,
+					struct odb_source *source,
+					const struct object_id *oid);
+
+/*
+ * Return true iff an object database source has a loose object
+ * with the specified name.  This function does not respect replace
+ * references.
+ */
+int odb_source_loose_has_object(struct odb_source *source,
+				const struct object_id *oid);
+
+int odb_source_loose_freshen_object(struct odb_source *source,
+				    const struct object_id *oid);
+
+int odb_source_loose_write_object(struct odb_source *source,
+				  const void *buf, unsigned long len,
+				  enum object_type type, struct object_id *oid,
+				  struct object_id *compat_oid_in, unsigned flags);
+
+int odb_source_loose_write_stream(struct odb_source *source,
+				  struct odb_write_stream *stream, size_t len,
+				  struct object_id *oid);
+
 /*
  * Populate and return the loose object cache array corresponding to the
  * given object ID.
  */
-struct oidtree *odb_loose_cache(struct odb_source *source,
-				const struct object_id *oid);
-
-/* Empty the loose object cache for the specified object directory. */
-void odb_clear_loose_cache(struct odb_source *source);
+struct oidtree *odb_source_loose_cache(struct odb_source *source,
+				       const struct object_id *oid);
 
 /*
  * Put in `buf` the name of the file in the local object database that
@@ -45,17 +90,6 @@ const char *odb_loose_path(struct odb_source *source,
 			   const struct object_id *oid);
 
 /*
- * Return true iff an object database source has a loose object
- * with the specified name.  This function does not respect replace
- * references.
- */
-int has_loose_object(struct odb_source *source,
-		     const struct object_id *oid);
-
-void *map_loose_object(struct repository *r, const struct object_id *oid,
-		       unsigned long *size);
-
-/*
  * Iterate over the files in the loose-object parts of the object
  * directory "path", triggering the following callbacks:
  *
@@ -93,16 +127,17 @@ int for_each_loose_file_in_source(struct odb_source *source,
 				  void *data);
 
 /*
- * Iterate over all accessible loose objects without respect to
- * reachability. By default, this includes both local and alternate objects.
- * The order in which objects are visited is unspecified.
- *
- * Any flags specific to packs are ignored.
+ * Iterate through all loose objects in the given object database source and
+ * invoke the callback function for each of them. If an object info request is
+ * given, then the object info will be read for every individual object and
+ * passed to the callback as if `odb_source_loose_read_object_info()` was
+ * called for the object.
  */
-int for_each_loose_object(struct object_database *odb,
-			  each_loose_object_fn, void *,
-			  enum for_each_object_flags flags);
-
+int odb_source_loose_for_each_object(struct odb_source *source,
+				     const struct object_info *request,
+				     odb_for_each_object_cb cb,
+				     void *cb_data,
+				     unsigned flags);
 
 /**
  * format_object_header() is a thin wrapper around s xsnprintf() that
@@ -112,55 +147,6 @@ int for_each_loose_object(struct object_database *odb,
 int format_object_header(char *str, size_t size, enum object_type type,
 			 size_t objsize);
 
-/**
- * unpack_loose_header() initializes the data stream needed to unpack
- * a loose object header.
- *
- * Returns:
- *
- * - ULHR_OK on success
- * - ULHR_BAD on error
- * - ULHR_TOO_LONG if the header was too long
- *
- * It will only parse up to MAX_HEADER_LEN bytes.
- */
-enum unpack_loose_header_result {
-	ULHR_OK,
-	ULHR_BAD,
-	ULHR_TOO_LONG,
-};
-enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,
-						    unsigned char *map,
-						    unsigned long mapsize,
-						    void *buffer,
-						    unsigned long bufsiz);
-
-/**
- * parse_loose_header() parses the starting "<type> <len>\0" of an
- * object. If it doesn't follow that format -1 is returned. To check
- * the validity of the <type> populate the "typep" in the "struct
- * object_info". It will be OBJ_BAD if the object type is unknown. The
- * parsed <len> can be retrieved via "oi->sizep", and from there
- * passed to unpack_loose_rest().
- */
-struct object_info;
-int parse_loose_header(const char *hdr, struct object_info *oi);
-
-int write_object_file(struct odb_source *source,
-		      const void *buf, unsigned long len,
-		      enum object_type type, struct object_id *oid,
-		      struct object_id *compat_oid_in, unsigned flags);
-
-struct input_stream {
-	const void *(*read)(struct input_stream *, unsigned long *len);
-	void *data;
-	int is_finished;
-};
-
-int stream_loose_object(struct odb_source *source,
-			struct input_stream *in_stream, size_t len,
-			struct object_id *oid);
-
 int force_object_loose(struct odb_source *source,
 		       const struct object_id *oid, time_t mtime);
 
@@ -180,11 +166,9 @@ int check_object_signature(struct repository *r, const struct object_id *oid,
  * Try reading the object named with "oid" using
  * the streaming interface and rehash it to do the same.
  */
-int stream_object_signature(struct repository *r, const struct object_id *oid);
-
-int loose_object_info(struct repository *r,
-		      const struct object_id *oid,
-		      struct object_info *oi, int flags);
+int stream_object_signature(struct repository *r,
+			    struct odb_read_stream *stream,
+			    const struct object_id *oid);
 
 enum finalize_object_file_flags {
 	FOF_SKIP_COLLISION_CHECK = 1,
@@ -222,16 +206,10 @@ struct odb_transaction;
 
 /*
  * Tell the object database to optimize for adding
- * multiple objects. object_file_transaction_commit must be called
+ * multiple objects. odb_transaction_files_commit must be called
  * to make new objects visible. If a transaction is already
  * pending, NULL is returned.
  */
-struct odb_transaction *object_file_transaction_begin(struct odb_source *source);
-
-/*
- * Tell the object database to make any objects from the
- * current transaction visible.
- */
-void object_file_transaction_commit(struct odb_transaction *transaction);
+struct odb_transaction *odb_transaction_files_begin(struct odb_source *source);
 
 #endif /* OBJECT_FILE_H */
diff --git a/object-name.c b/object-name.c
index 766c757..7b14c3b 100644
--- a/object-name.c
+++ b/object-name.c
@@ -116,7 +116,7 @@ static void find_short_object_filename(struct disambiguate_state *ds)
 	struct odb_source *source;
 
 	for (source = ds->repo->objects->sources; source && !ds->ambiguous; source = source->next)
-		oidtree_each(odb_loose_cache(source, &ds->bin_pfx),
+		oidtree_each(odb_source_loose_cache(source, &ds->bin_pfx),
 				&ds->bin_pfx, ds->len, match_prefix, ds);
 }
 
@@ -449,7 +449,7 @@ static int show_ambiguous_object(const struct object_id *oid, void *data)
 	} else if (type == OBJ_TAG) {
 		struct tag *tag = lookup_tag(ds->repo, oid);
 
-		if (!parse_tag(tag) && tag->tag) {
+		if (!parse_tag(ds->repo, tag) && tag->tag) {
 			/*
 			 * TRANSLATORS: This is a line of ambiguous
 			 * tag object output. E.g.:
@@ -1281,7 +1281,7 @@ static int peel_onion(struct repository *r, const char *name, int len,
 		commit_list_insert((struct commit *)o, &list);
 		ret = get_oid_oneline(r, prefix, oid, list);
 
-		free_commit_list(list);
+		commit_list_free(list);
 		free(prefix);
 		return ret;
 	}
@@ -1446,18 +1446,16 @@ struct handle_one_ref_cb {
 	struct commit_list **list;
 };
 
-static int handle_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,
-			  int flag UNUSED,
-			  void *cb_data)
+static int handle_one_ref(const struct reference *ref, void *cb_data)
 {
 	struct handle_one_ref_cb *cb = cb_data;
 	struct commit_list **list = cb->list;
-	struct object *object = parse_object(cb->repo, oid);
+	struct object *object = parse_object(cb->repo, ref->oid);
 	if (!object)
 		return 0;
 	if (object->type == OBJ_TAG) {
-		object = deref_tag(cb->repo, object, path,
-				   strlen(path));
+		object = deref_tag(cb->repo, object, ref->name,
+				   strlen(ref->name));
 		if (!object)
 			return 0;
 	}
@@ -1625,7 +1623,7 @@ int repo_get_oid_mb(struct repository *r,
 	if (!two)
 		return -1;
 	if (repo_get_merge_bases(r, one, two, &mbs) < 0) {
-		free_commit_list(mbs);
+		commit_list_free(mbs);
 		return -1;
 	}
 	if (!mbs || mbs->next)
@@ -1634,7 +1632,7 @@ int repo_get_oid_mb(struct repository *r,
 		st = 0;
 		oidcpy(oid, &mbs->item->object.oid);
 	}
-	free_commit_list(mbs);
+	commit_list_free(mbs);
 	return st;
 }
 
@@ -1758,7 +1756,7 @@ int repo_interpret_branch_name(struct repository *r,
 			       struct strbuf *buf,
 			       const struct interpret_branch_name_options *options)
 {
-	char *at;
+	const char *at;
 	const char *start;
 	int len;
 
@@ -2054,7 +2052,7 @@ static enum get_oid_result get_oid_with_context_1(struct repository *repo,
 			refs_head_ref(get_main_ref_store(repo), handle_one_ref, &cb);
 			ret = get_oid_oneline(repo, name + 2, oid, list);
 
-			free_commit_list(list);
+			commit_list_free(list);
 			return ret;
 		}
 		if (namelen < 3 ||
diff --git a/object.c b/object.c
index 986114a..465902e 100644
--- a/object.c
+++ b/object.c
@@ -6,6 +6,7 @@
 #include "object.h"
 #include "replace-object.h"
 #include "object-file.h"
+#include "odb/streaming.h"
 #include "blob.h"
 #include "statinfo.h"
 #include "tree.h"
@@ -207,9 +208,11 @@ struct object *lookup_object_by_type(struct repository *r,
 	}
 }
 
-enum peel_status peel_object(struct repository *r,
-			     const struct object_id *name,
-			     struct object_id *oid)
+enum peel_status peel_object_ext(struct repository *r,
+				 const struct object_id *name,
+				 struct object_id *oid,
+				 unsigned flags,
+				 enum object_type *typep)
 {
 	struct object *o = lookup_unknown_object(r, name);
 
@@ -219,17 +222,42 @@ enum peel_status peel_object(struct repository *r,
 			return PEEL_INVALID;
 	}
 
-	if (o->type != OBJ_TAG)
+	if (o->type != OBJ_TAG) {
+		*typep = o->type;
 		return PEEL_NON_TAG;
+	}
 
-	o = deref_tag_noverify(r, o);
+	while (o && o->type == OBJ_TAG) {
+		o = parse_object(r, &o->oid);
+		if (o && o->type == OBJ_TAG && ((struct tag *)o)->tagged) {
+			o = ((struct tag *)o)->tagged;
+
+			if (flags & PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE) {
+				int type = odb_read_object_info(r->objects, &o->oid, NULL);
+				if (type < 0 || !object_as_type(o, type, 0))
+					return PEEL_INVALID;
+			}
+		} else {
+			o = NULL;
+		}
+	}
 	if (!o)
 		return PEEL_INVALID;
 
 	oidcpy(oid, &o->oid);
+	*typep = o->type;
 	return PEEL_PEELED;
 }
 
+enum peel_status peel_object(struct repository *r,
+			     const struct object_id *name,
+			     struct object_id *oid,
+			     unsigned flags)
+{
+	enum object_type dummy;
+	return peel_object_ext(r, name, oid, flags, &dummy);
+}
+
 struct object *parse_object_buffer(struct repository *r, const struct object_id *oid, enum object_type type, unsigned long size, void *buffer, int *eaten_p)
 {
 	struct object *obj;
@@ -314,11 +342,23 @@ struct object *parse_object_with_flags(struct repository *r,
 			return &commit->object;
 	}
 
-	if ((!obj || obj->type == OBJ_BLOB) &&
+	if ((!obj || obj->type == OBJ_NONE || obj->type == OBJ_BLOB) &&
 	    odb_read_object_info(r->objects, oid, NULL) == OBJ_BLOB) {
-		if (!skip_hash && stream_object_signature(r, repl) < 0) {
-			error(_("hash mismatch %s"), oid_to_hex(oid));
-			return NULL;
+		if (!skip_hash) {
+			struct odb_read_stream *stream = odb_read_stream_open(r->objects, oid, NULL);
+
+			if (!stream) {
+				error(_("unable to open object stream for %s"), oid_to_hex(oid));
+				return NULL;
+			}
+
+			if (stream_object_signature(r, stream, repl) < 0) {
+				error(_("hash mismatch %s"), oid_to_hex(oid));
+				odb_read_stream_close(stream);
+				return NULL;
+			}
+
+			odb_read_stream_close(stream);
 		}
 		parse_blob_buffer(lookup_blob(r, oid));
 		return lookup_object(r, oid);
@@ -330,7 +370,7 @@ struct object *parse_object_with_flags(struct repository *r,
 	 * have the on-disk object with the correct type.
 	 */
 	if (skip_hash && discard_tree &&
-	    (!obj || obj->type == OBJ_TREE) &&
+	    (!obj || obj->type == OBJ_NONE || obj->type == OBJ_TREE) &&
 	    odb_read_object_info(r->objects, oid, NULL) == OBJ_TREE) {
 		return &lookup_tree(r, oid)->object;
 	}
diff --git a/object.h b/object.h
index fa504a0..d814647 100644
--- a/object.h
+++ b/object.h
@@ -64,7 +64,7 @@ void object_array_init(struct object_array *array);
 
 /*
  * object flag allocation:
- * revision.h:               0---------10         15               23------27
+ * revision.h:               0---------10         15               23--------28
  * fetch-pack.c:             01    67
  * negotiator/default.c:       2--5
  * walker.c:                 0-2
@@ -80,14 +80,13 @@ void object_array_init(struct object_array *array);
  * list-objects-filter.c:                                      21
  * bloom.c:                                                    2122
  * builtin/fsck.c:           0--3
- * builtin/gc.c:             0
  * builtin/index-pack.c:                                     2021
  * reflog.c:                           10--12
  * builtin/show-branch.c:    0-------------------------------------------26
  * builtin/unpack-objects.c:                                 2021
  * pack-bitmap.h:                                              2122
  */
-#define FLAG_BITS  28
+#define FLAG_BITS  29
 
 #define TYPE_BITS 3
 
@@ -288,6 +287,17 @@ enum peel_status {
 	PEEL_BROKEN = -4
 };
 
+enum peel_object_flags {
+	/*
+	 * Always verify the object type of the tagged object, even in the case
+	 * where the looked-up object already has an object type. This can be
+	 * useful when the tagged object type may be invalid. One such case is
+	 * when looking up objects via tags, where we blindly trust the object
+	 * type declared by the tag.
+	 */
+	PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE = (1 << 0),
+};
+
 /*
  * Peel the named object; i.e., if the object is a tag, resolve the
  * tag recursively until a non-tag is found.  If successful, store the
@@ -296,7 +306,14 @@ enum peel_status {
  * and leave oid unchanged.
  */
 enum peel_status peel_object(struct repository *r,
-			     const struct object_id *name, struct object_id *oid);
+			     const struct object_id *name,
+			     struct object_id *oid,
+			     unsigned flags);
+enum peel_status peel_object_ext(struct repository *r,
+				 const struct object_id *name,
+				 struct object_id *oid,
+				 unsigned flags,
+				 enum object_type *typep);
 
 struct object_list *object_list_insert(struct object *item,
 				       struct object_list **list_p);
diff --git a/odb.c b/odb.c
index 00a6e71..84a3108 100644
--- a/odb.c
+++ b/odb.c
@@ -9,6 +9,7 @@
 #include "khash.h"
 #include "lockfile.h"
 #include "loose.h"
+#include "midx.h"
 #include "object-file-convert.h"
 #include "object-file.h"
 #include "odb.h"
@@ -22,6 +23,7 @@
 #include "strbuf.h"
 #include "strvec.h"
 #include "submodule.h"
+#include "tmp-objdir.h"
 #include "trace2.h"
 #include "write-or-die.h"
 
@@ -86,18 +88,20 @@ int odb_mkstemp(struct object_database *odb,
 /*
  * Return non-zero iff the path is usable as an alternate object database.
  */
-static int alt_odb_usable(struct object_database *o,
-			  struct strbuf *path,
-			  const char *normalized_objdir, khiter_t *pos)
+static bool odb_is_source_usable(struct object_database *o, const char *path)
 {
 	int r;
+	struct strbuf normalized_objdir = STRBUF_INIT;
+	bool usable = false;
+
+	strbuf_realpath(&normalized_objdir, o->sources->path, 1);
 
 	/* Detect cases where alternate disappeared */
-	if (!is_directory(path->buf)) {
+	if (!is_directory(path)) {
 		error(_("object directory %s does not exist; "
 			"check .git/objects/info/alternates"),
-		      path->buf);
-		return 0;
+		      path);
+		goto out;
 	}
 
 	/*
@@ -113,202 +117,133 @@ static int alt_odb_usable(struct object_database *o,
 		assert(r == 1); /* never used */
 		kh_value(o->source_by_path, p) = o->sources;
 	}
-	if (fspatheq(path->buf, normalized_objdir))
-		return 0;
-	*pos = kh_put_odb_path_map(o->source_by_path, path->buf, &r);
-	/* r: 0 = exists, 1 = never used, 2 = deleted */
-	return r == 0 ? 0 : 1;
+
+	if (fspatheq(path, normalized_objdir.buf))
+		goto out;
+
+	if (kh_get_odb_path_map(o->source_by_path, path) < kh_end(o->source_by_path))
+		goto out;
+
+	usable = true;
+
+out:
+	strbuf_release(&normalized_objdir);
+	return usable;
 }
 
-/*
- * Prepare alternate object database registry.
- *
- * The variable alt_odb_list points at the list of struct
- * odb_source.  The elements on this list come from
- * non-empty elements from colon separated ALTERNATE_DB_ENVIRONMENT
- * environment variable, and $GIT_OBJECT_DIRECTORY/info/alternates,
- * whose contents is similar to that environment variable but can be
- * LF separated.  Its base points at a statically allocated buffer that
- * contains "/the/directory/corresponding/to/.git/objects/...", while
- * its name points just after the slash at the end of ".git/objects/"
- * in the example above, and has enough space to hold all hex characters
- * of the object ID, an extra slash for the first level indirection, and
- * the terminating NUL.
- */
-static void read_info_alternates(struct object_database *odb,
-				 const char *relative_base,
-				 int depth);
+void parse_alternates(const char *string,
+		      int sep,
+		      const char *relative_base,
+		      struct strvec *out)
+{
+	struct strbuf pathbuf = STRBUF_INIT;
+	struct strbuf buf = STRBUF_INIT;
 
-static struct odb_source *link_alt_odb_entry(struct object_database *odb,
-					     const char *dir,
-					     const char *relative_base,
-					     int depth)
+	if (!string || !*string)
+		return;
+
+	while (*string) {
+		const char *end;
+
+		strbuf_reset(&buf);
+		strbuf_reset(&pathbuf);
+
+		if (*string == '#') {
+			/* comment; consume up to next separator */
+			end = strchrnul(string, sep);
+		} else if (*string == '"' && !unquote_c_style(&buf, string, &end)) {
+			/*
+			 * quoted path; unquote_c_style has copied the
+			 * data for us and set "end". Broken quoting (e.g.,
+			 * an entry that doesn't end with a quote) falls
+			 * back to the unquoted case below.
+			 */
+		} else {
+			/* normal, unquoted path */
+			end = strchrnul(string, sep);
+			strbuf_add(&buf, string, end - string);
+		}
+
+		if (*end)
+			end++;
+		string = end;
+
+		if (!buf.len)
+			continue;
+
+		if (!is_absolute_path(buf.buf) && relative_base) {
+			strbuf_realpath(&pathbuf, relative_base, 1);
+			strbuf_addch(&pathbuf, '/');
+		}
+		strbuf_addbuf(&pathbuf, &buf);
+
+		strbuf_reset(&buf);
+		if (!strbuf_realpath(&buf, pathbuf.buf, 0)) {
+			error(_("unable to normalize alternate object path: %s"),
+			      pathbuf.buf);
+			continue;
+		}
+
+		/*
+		 * The trailing slash after the directory name is given by
+		 * this function at the end. Remove duplicates.
+		 */
+		while (buf.len && buf.buf[buf.len - 1] == '/')
+			strbuf_setlen(&buf, buf.len - 1);
+
+		strvec_push(out, buf.buf);
+	}
+
+	strbuf_release(&pathbuf);
+	strbuf_release(&buf);
+}
+
+static struct odb_source *odb_add_alternate_recursively(struct object_database *odb,
+							const char *source,
+							int depth)
 {
 	struct odb_source *alternate = NULL;
-	struct strbuf pathbuf = STRBUF_INIT;
-	struct strbuf tmp = STRBUF_INIT;
+	struct strvec sources = STRVEC_INIT;
 	khiter_t pos;
+	int ret;
 
-	if (!is_absolute_path(dir) && relative_base) {
-		strbuf_realpath(&pathbuf, relative_base, 1);
-		strbuf_addch(&pathbuf, '/');
-	}
-	strbuf_addstr(&pathbuf, dir);
-
-	if (!strbuf_realpath(&tmp, pathbuf.buf, 0)) {
-		error(_("unable to normalize alternate object path: %s"),
-		      pathbuf.buf);
-		goto error;
-	}
-	strbuf_swap(&pathbuf, &tmp);
-
-	/*
-	 * The trailing slash after the directory name is given by
-	 * this function at the end. Remove duplicates.
-	 */
-	while (pathbuf.len && pathbuf.buf[pathbuf.len - 1] == '/')
-		strbuf_setlen(&pathbuf, pathbuf.len - 1);
-
-	strbuf_reset(&tmp);
-	strbuf_realpath(&tmp, odb->sources->path, 1);
-
-	if (!alt_odb_usable(odb, &pathbuf, tmp.buf, &pos))
+	if (!odb_is_source_usable(odb, source))
 		goto error;
 
-	CALLOC_ARRAY(alternate, 1);
-	alternate->odb = odb;
-	alternate->local = false;
-	/* pathbuf.buf is already in r->objects->source_by_path */
-	alternate->path = strbuf_detach(&pathbuf, NULL);
+	alternate = odb_source_new(odb, source, false);
 
 	/* add the alternate entry */
 	*odb->sources_tail = alternate;
 	odb->sources_tail = &(alternate->next);
-	alternate->next = NULL;
-	assert(odb->source_by_path);
+
+	pos = kh_put_odb_path_map(odb->source_by_path, alternate->path, &ret);
+	if (!ret)
+		BUG("source must not yet exist");
 	kh_value(odb->source_by_path, pos) = alternate;
 
 	/* recursively add alternates */
-	read_info_alternates(odb, alternate->path, depth + 1);
+	odb_source_read_alternates(alternate, &sources);
+	if (sources.nr && depth + 1 > 5) {
+		error(_("%s: ignoring alternate object stores, nesting too deep"),
+		      source);
+	} else {
+		for (size_t i = 0; i < sources.nr; i++)
+			odb_add_alternate_recursively(odb, sources.v[i], depth + 1);
+	}
 
  error:
-	strbuf_release(&tmp);
-	strbuf_release(&pathbuf);
+	strvec_clear(&sources);
 	return alternate;
 }
 
-static const char *parse_alt_odb_entry(const char *string,
-				       int sep,
-				       struct strbuf *out)
-{
-	const char *end;
-
-	strbuf_reset(out);
-
-	if (*string == '#') {
-		/* comment; consume up to next separator */
-		end = strchrnul(string, sep);
-	} else if (*string == '"' && !unquote_c_style(out, string, &end)) {
-		/*
-		 * quoted path; unquote_c_style has copied the
-		 * data for us and set "end". Broken quoting (e.g.,
-		 * an entry that doesn't end with a quote) falls
-		 * back to the unquoted case below.
-		 */
-	} else {
-		/* normal, unquoted path */
-		end = strchrnul(string, sep);
-		strbuf_add(out, string, end - string);
-	}
-
-	if (*end)
-		end++;
-	return end;
-}
-
-static void link_alt_odb_entries(struct object_database *odb, const char *alt,
-				 int sep, const char *relative_base, int depth)
-{
-	struct strbuf dir = STRBUF_INIT;
-
-	if (!alt || !*alt)
-		return;
-
-	if (depth > 5) {
-		error(_("%s: ignoring alternate object stores, nesting too deep"),
-				relative_base);
-		return;
-	}
-
-	while (*alt) {
-		alt = parse_alt_odb_entry(alt, sep, &dir);
-		if (!dir.len)
-			continue;
-		link_alt_odb_entry(odb, dir.buf, relative_base, depth);
-	}
-	strbuf_release(&dir);
-}
-
-static void read_info_alternates(struct object_database *odb,
-				 const char *relative_base,
-				 int depth)
-{
-	char *path;
-	struct strbuf buf = STRBUF_INIT;
-
-	path = xstrfmt("%s/info/alternates", relative_base);
-	if (strbuf_read_file(&buf, path, 1024) < 0) {
-		warn_on_fopen_errors(path);
-		free(path);
-		return;
-	}
-
-	link_alt_odb_entries(odb, buf.buf, '\n', relative_base, depth);
-	strbuf_release(&buf);
-	free(path);
-}
-
 void odb_add_to_alternates_file(struct object_database *odb,
 				const char *dir)
 {
-	struct lock_file lock = LOCK_INIT;
-	char *alts = repo_git_path(odb->repo, "objects/info/alternates");
-	FILE *in, *out;
-	int found = 0;
-
-	hold_lock_file_for_update(&lock, alts, LOCK_DIE_ON_ERROR);
-	out = fdopen_lock_file(&lock, "w");
-	if (!out)
-		die_errno(_("unable to fdopen alternates lockfile"));
-
-	in = fopen(alts, "r");
-	if (in) {
-		struct strbuf line = STRBUF_INIT;
-
-		while (strbuf_getline(&line, in) != EOF) {
-			if (!strcmp(dir, line.buf)) {
-				found = 1;
-				break;
-			}
-			fprintf_or_die(out, "%s\n", line.buf);
-		}
-
-		strbuf_release(&line);
-		fclose(in);
-	}
-	else if (errno != ENOENT)
-		die_errno(_("unable to read alternates file"));
-
-	if (found) {
-		rollback_lock_file(&lock);
-	} else {
-		fprintf_or_die(out, "%s\n", dir);
-		if (commit_lock_file(&lock))
-			die_errno(_("unable to move new alternates file into place"));
-		if (odb->loaded_alternates)
-			link_alt_odb_entries(odb, dir, '\n', NULL, 0);
-	}
-	free(alts);
+	int ret = odb_source_write_alternate(odb->sources, dir);
+	if (ret < 0)
+		die(NULL);
+	if (odb->loaded_alternates)
+		odb_add_alternate_recursively(odb, dir, 0);
 }
 
 struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
@@ -319,7 +254,7 @@ struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
 	 * overwritten when they are.
 	 */
 	odb_prepare_alternates(odb);
-	return link_alt_odb_entry(odb, dir, NULL, 0);
+	return odb_add_alternate_recursively(odb, dir, 0);
 }
 
 struct odb_source *odb_set_temporary_primary_source(struct object_database *odb,
@@ -337,29 +272,19 @@ struct odb_source *odb_set_temporary_primary_source(struct object_database *odb,
 	 * Make a new primary odb and link the old primary ODB in as an
 	 * alternate
 	 */
-	source = xcalloc(1, sizeof(*source));
-	source->odb = odb;
-	source->path = xstrdup(dir);
+	source = odb_source_new(odb, dir, false);
 
 	/*
 	 * Disable ref updates while a temporary odb is active, since
 	 * the objects in the database may roll back.
 	 */
-	source->disable_ref_updates = 1;
+	odb->repo->disable_ref_updates = true;
 	source->will_destroy = will_destroy;
 	source->next = odb->sources;
 	odb->sources = source;
 	return source->next;
 }
 
-static void free_object_directory(struct odb_source *source)
-{
-	free(source->path);
-	odb_clear_loose_cache(source);
-	loose_object_map_clear(&source->loose_map);
-	free(source);
-}
-
 void odb_restore_primary_source(struct object_database *odb,
 				struct odb_source *restore_source,
 				const char *old_path)
@@ -373,8 +298,9 @@ void odb_restore_primary_source(struct object_database *odb,
 	if (cur_source->next != restore_source)
 		BUG("we expect the old primary object store to be the first alternate");
 
+	odb->repo->disable_ref_updates = false;
 	odb->sources = restore_source;
-	free_object_directory(cur_source);
+	odb_source_free(cur_source);
 }
 
 char *compute_alternate_path(const char *path, struct strbuf *err)
@@ -592,13 +518,19 @@ int odb_for_each_alternate(struct object_database *odb,
 
 void odb_prepare_alternates(struct object_database *odb)
 {
+	struct strvec sources = STRVEC_INIT;
+
 	if (odb->loaded_alternates)
 		return;
 
-	link_alt_odb_entries(odb, odb->alternate_db, PATH_SEP, NULL, 0);
+	parse_alternates(odb->alternate_db, PATH_SEP, NULL, &sources);
+	odb_source_read_alternates(odb->sources, &sources);
+	for (size_t i = 0; i < sources.nr; i++)
+		odb_add_alternate_recursively(odb, sources.v[i], 0);
 
-	read_info_alternates(odb, odb->sources->path, 0);
 	odb->loaded_alternates = 1;
+
+	strvec_clear(&sources);
 }
 
 int odb_has_alternates(struct object_database *odb)
@@ -651,52 +583,55 @@ static int do_oid_object_info_extended(struct object_database *odb,
 				       const struct object_id *oid,
 				       struct object_info *oi, unsigned flags)
 {
-	static struct object_info blank_oi = OBJECT_INFO_INIT;
 	const struct cached_object *co;
-	struct pack_entry e;
-	int rtype;
 	const struct object_id *real = oid;
 	int already_retried = 0;
 
-
 	if (flags & OBJECT_INFO_LOOKUP_REPLACE)
 		real = lookup_replace_object(odb->repo, oid);
 
 	if (is_null_oid(real))
 		return -1;
 
-	if (!oi)
-		oi = &blank_oi;
-
 	co = find_cached_object(odb, real);
 	if (co) {
-		if (oi->typep)
-			*(oi->typep) = co->type;
-		if (oi->sizep)
-			*(oi->sizep) = co->size;
-		if (oi->disk_sizep)
-			*(oi->disk_sizep) = 0;
-		if (oi->delta_base_oid)
-			oidclr(oi->delta_base_oid, odb->repo->hash_algo);
-		if (oi->contentp)
-			*oi->contentp = xmemdupz(co->buf, co->size);
-		oi->whence = OI_CACHED;
+		if (oi) {
+			if (oi->typep)
+				*(oi->typep) = co->type;
+			if (oi->sizep)
+				*(oi->sizep) = co->size;
+			if (oi->disk_sizep)
+				*(oi->disk_sizep) = 0;
+			if (oi->delta_base_oid)
+				oidclr(oi->delta_base_oid, odb->repo->hash_algo);
+			if (oi->contentp)
+				*oi->contentp = xmemdupz(co->buf, co->size);
+			if (oi->mtimep)
+				*oi->mtimep = 0;
+			oi->whence = OI_CACHED;
+		}
 		return 0;
 	}
 
+	odb_prepare_alternates(odb);
+
 	while (1) {
-		if (find_pack_entry(odb->repo, real, &e))
-			break;
+		struct odb_source *source;
 
-		/* Most likely it's a loose object. */
-		if (!loose_object_info(odb->repo, real, oi, flags))
-			return 0;
+		for (source = odb->sources; source; source = source->next)
+			if (!odb_source_read_object_info(source, real, oi, flags))
+				return 0;
 
-		/* Not a loose object; someone else may have just packed it. */
+		/*
+		 * When the object hasn't been found we try a second read and
+		 * tell the sources so. This may cause them to invalidate
+		 * caches or reload on-disk state.
+		 */
 		if (!(flags & OBJECT_INFO_QUICK)) {
-			odb_reprepare(odb->repo->objects);
-			if (find_pack_entry(odb->repo, real, &e))
-				break;
+			for (source = odb->sources; source; source = source->next)
+				if (!odb_source_read_object_info(source, real, oi,
+								 flags | OBJECT_INFO_SECOND_READ))
+					return 0;
 		}
 
 		/*
@@ -729,25 +664,6 @@ static int do_oid_object_info_extended(struct object_database *odb,
 		}
 		return -1;
 	}
-
-	if (oi == &blank_oi)
-		/*
-		 * We know that the caller doesn't actually need the
-		 * information below, so return early.
-		 */
-		return 0;
-	rtype = packed_object_info(odb->repo, e.p, e.offset, oi);
-	if (rtype < 0) {
-		mark_bad_packed_object(e.p, real);
-		return do_oid_object_info_extended(odb, real, oi, 0);
-	} else if (oi->whence == OI_PACKED) {
-		oi->u.packed.offset = e.offset;
-		oi->u.packed.pack = e.p;
-		oi->u.packed.is_delta = (rtype == OBJ_REF_DELTA ||
-					 rtype == OBJ_OFS_DELTA);
-	}
-
-	return 0;
 }
 
 static int oid_object_info_convert(struct repository *r,
@@ -833,7 +749,7 @@ static int oid_object_info_convert(struct repository *r,
 int odb_read_object_info_extended(struct object_database *odb,
 				  const struct object_id *oid,
 				  struct object_info *oi,
-				  unsigned flags)
+				  enum object_info_flags flags)
 {
 	int ret;
 
@@ -955,7 +871,7 @@ void *odb_read_object_peeled(struct object_database *odb,
 }
 
 int odb_has_object(struct object_database *odb, const struct object_id *oid,
-	       unsigned flags)
+		   enum has_object_flags flags)
 {
 	unsigned object_info_flags = 0;
 
@@ -969,6 +885,38 @@ int odb_has_object(struct object_database *odb, const struct object_id *oid,
 	return odb_read_object_info_extended(odb, oid, NULL, object_info_flags) >= 0;
 }
 
+int odb_freshen_object(struct object_database *odb,
+		       const struct object_id *oid)
+{
+	struct odb_source *source;
+	odb_prepare_alternates(odb);
+	for (source = odb->sources; source; source = source->next)
+		if (odb_source_freshen_object(source, oid))
+			return 1;
+	return 0;
+}
+
+int odb_for_each_object(struct object_database *odb,
+			const struct object_info *request,
+			odb_for_each_object_cb cb,
+			void *cb_data,
+			unsigned flags)
+{
+	int ret;
+
+	odb_prepare_alternates(odb);
+	for (struct odb_source *source = odb->sources; source; source = source->next) {
+		if (flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY && !source->local)
+			continue;
+
+		ret = odb_source_for_each_object(source, request, cb, cb_data, flags);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
 void odb_assert_oid_type(struct object_database *odb,
 			 const struct object_id *oid, enum object_type expect)
 {
@@ -987,58 +935,81 @@ int odb_write_object_ext(struct object_database *odb,
 			 struct object_id *compat_oid,
 			 unsigned flags)
 {
-	return write_object_file(odb->sources, buf, len, type, oid, compat_oid, flags);
+	return odb_source_write_object(odb->sources, buf, len, type,
+				       oid, compat_oid, flags);
 }
 
-struct object_database *odb_new(struct repository *repo)
+int odb_write_object_stream(struct object_database *odb,
+			    struct odb_write_stream *stream, size_t len,
+			    struct object_id *oid)
+{
+	return odb_source_write_object_stream(odb->sources, stream, len, oid);
+}
+
+struct object_database *odb_new(struct repository *repo,
+				const char *primary_source,
+				const char *secondary_sources)
 {
 	struct object_database *o = xmalloc(sizeof(*o));
+	char *to_free = NULL;
 
 	memset(o, 0, sizeof(*o));
 	o->repo = repo;
-	o->packfiles = packfile_store_new(o);
 	pthread_mutex_init(&o->replace_mutex, NULL);
 	string_list_init_dup(&o->submodule_source_paths);
+
+	if (!primary_source)
+		primary_source = to_free = xstrfmt("%s/objects", repo->commondir);
+	o->sources = odb_source_new(o, primary_source, true);
+	o->sources_tail = &o->sources->next;
+	o->alternate_db = xstrdup_or_null(secondary_sources);
+
+	free(to_free);
+
 	return o;
 }
 
-static void free_object_directories(struct object_database *o)
+void odb_close(struct object_database *o)
+{
+	struct odb_source *source;
+	for (source = o->sources; source; source = source->next)
+		odb_source_close(source);
+	close_commit_graph(o);
+}
+
+static void odb_free_sources(struct object_database *o)
 {
 	while (o->sources) {
 		struct odb_source *next;
 
 		next = o->sources->next;
-		free_object_directory(o->sources);
+		odb_source_free(o->sources);
 		o->sources = next;
 	}
 	kh_destroy_odb_path_map(o->source_by_path);
 	o->source_by_path = NULL;
 }
 
-void odb_clear(struct object_database *o)
+void odb_free(struct object_database *o)
 {
-	FREE_AND_NULL(o->alternate_db);
+	if (!o)
+		return;
+
+	free(o->alternate_db);
 
 	oidmap_clear(&o->replace_map, 1);
 	pthread_mutex_destroy(&o->replace_mutex);
 
-	free_commit_graph(o->commit_graph);
-	o->commit_graph = NULL;
-	o->commit_graph_attempted = 0;
-
-	free_object_directories(o);
-	o->sources_tail = NULL;
-	o->loaded_alternates = 0;
+	odb_close(o);
+	odb_free_sources(o);
 
 	for (size_t i = 0; i < o->cached_object_nr; i++)
 		free((char *) o->cached_objects[i].value.buf);
-	FREE_AND_NULL(o->cached_objects);
-
-	close_object_store(o);
-	packfile_store_free(o->packfiles);
-	o->packfiles = NULL;
+	free(o->cached_objects);
 
 	string_list_clear(&o->submodule_source_paths, 0);
+
+	free(o);
 }
 
 void odb_reprepare(struct object_database *o)
@@ -1057,21 +1028,34 @@ void odb_reprepare(struct object_database *o)
 	odb_prepare_alternates(o);
 
 	for (source = o->sources; source; source = source->next)
-		odb_clear_loose_cache(source);
+		odb_source_reprepare(source);
 
 	o->approximate_object_count_valid = 0;
 
-	packfile_store_reprepare(o->packfiles);
-
 	obj_read_unlock();
 }
 
 struct odb_transaction *odb_transaction_begin(struct object_database *odb)
 {
-	return object_file_transaction_begin(odb->sources);
+	if (odb->transaction)
+		return NULL;
+
+	odb->transaction = odb_transaction_files_begin(odb->sources);
+
+	return odb->transaction;
 }
 
 void odb_transaction_commit(struct odb_transaction *transaction)
 {
-	object_file_transaction_commit(transaction);
+	if (!transaction)
+		return;
+
+	/*
+	 * Ensure the transaction ending matches the pending transaction.
+	 */
+	ASSERT(transaction == transaction->source->odb->transaction);
+
+	transaction->commit(transaction);
+	transaction->source->odb->transaction = NULL;
+	free(transaction);
 }
diff --git a/odb.h b/odb.h
index e6602dd..86e0365 100644
--- a/odb.h
+++ b/odb.h
@@ -3,6 +3,7 @@
 
 #include "hashmap.h"
 #include "object.h"
+#include "odb/source.h"
 #include "oidset.h"
 #include "oidmap.h"
 #include "string-list.h"
@@ -15,6 +16,14 @@ struct repository;
 struct multi_pack_index;
 
 /*
+ * Set this to 0 to prevent odb_read_object_info_extended() from fetching missing
+ * blobs. This has a difference only if extensions.partialClone is set.
+ *
+ * Its default value is 1.
+ */
+extern int fetch_if_missing;
+
+/*
  * Compute the exact path an alternate is at and returns it. In case of
  * error NULL is returned and the human readable error is added to `err`
  * `path` may be relative and should point to $GIT_DIR.
@@ -22,77 +31,27 @@ struct multi_pack_index;
  */
 char *compute_alternate_path(const char *path, struct strbuf *err);
 
-/*
- * The source is the part of the object database that stores the actual
- * objects. It thus encapsulates the logic to read and write the specific
- * on-disk format. An object database can have multiple sources:
- *
- *   - The primary source, which is typically located in "$GIT_DIR/objects".
- *     This is where new objects are usually written to.
- *
- *   - Alternate sources, which are configured via "objects/info/alternates" or
- *     via the GIT_ALTERNATE_OBJECT_DIRECTORIES environment variable. These
- *     alternate sources are only used to read objects.
- */
-struct odb_source {
-	struct odb_source *next;
-
-	/* Object database that owns this object source. */
-	struct object_database *odb;
-
-	/*
-	 * Used to store the results of readdir(3) calls when we are OK
-	 * sacrificing accuracy due to races for speed. That includes
-	 * object existence with OBJECT_INFO_QUICK, as well as
-	 * our search for unique abbreviated hashes. Don't use it for tasks
-	 * requiring greater accuracy!
-	 *
-	 * Be sure to call odb_load_loose_cache() before using.
-	 */
-	uint32_t loose_objects_subdir_seen[8]; /* 256 bits */
-	struct oidtree *loose_objects_cache;
-
-	/* Map between object IDs for loose objects. */
-	struct loose_object_map *loose_map;
-
-	/*
-	 * private data
-	 *
-	 * should only be accessed directly by packfile.c and midx.c
-	 */
-	struct multi_pack_index *midx;
-
-	/*
-	 * Figure out whether this is the local source of the owning
-	 * repository, which would typically be its ".git/objects" directory.
-	 * This local object directory is usually where objects would be
-	 * written to.
-	 */
-	bool local;
-
-	/*
-	 * This is a temporary object store created by the tmp_objdir
-	 * facility. Disable ref updates since the objects in the store
-	 * might be discarded on rollback.
-	 */
-	int disable_ref_updates;
-
-	/*
-	 * This object store is ephemeral, so there is no need to fsync.
-	 */
-	int will_destroy;
-
-	/*
-	 * Path to the source. If this is a relative path, it is relative to
-	 * the current working directory.
-	 */
-	char *path;
-};
-
 struct packed_git;
 struct packfile_store;
 struct cached_object_entry;
+
+/*
+ * A transaction may be started for an object database prior to writing new
+ * objects via odb_transaction_begin(). These objects are not committed until
+ * odb_transaction_commit() is invoked. Only a single transaction may be pending
+ * at a time.
+ *
+ * Each ODB source is expected to implement its own transaction handling.
+ */
 struct odb_transaction;
+typedef void (*odb_transaction_commit_fn)(struct odb_transaction *transaction);
+struct odb_transaction {
+	/* The ODB source the transaction is opened against. */
+	struct odb_source *source;
+
+	/* The ODB source specific callback invoked to commit a transaction. */
+	odb_transaction_commit_fn commit;
+};
 
 /*
  * The object database encapsulates access to objects in a repository. It
@@ -139,9 +98,6 @@ struct object_database {
 	struct commit_graph *commit_graph;
 	unsigned commit_graph_attempted : 1; /* if loading has been attempted */
 
-	/* Should only be accessed directly by packfile.c and midx.c. */
-	struct packfile_store *packfiles;
-
 	/*
 	 * This is meant to hold a *small* number of objects that you would
 	 * want odb_read_object() to be able to return, but yet you do not want
@@ -166,8 +122,30 @@ struct object_database {
 	struct string_list submodule_source_paths;
 };
 
-struct object_database *odb_new(struct repository *repo);
-void odb_clear(struct object_database *o);
+/*
+ * Create a new object database for the given repository.
+ *
+ * If the primary source parameter is set it will override the usual primary
+ * object directory derived from the repository's common directory. The
+ * alternate sources are expected to be a PATH_SEP-separated list of secondary
+ * sources. Note that these alternate sources will be added in addition to, not
+ * instead of, the alternates identified by the primary source.
+ *
+ * Returns the newly created object database.
+ */
+struct object_database *odb_new(struct repository *repo,
+				const char *primary_source,
+				const char *alternate_sources);
+
+/* Free the object database and release all resources. */
+void odb_free(struct object_database *o);
+
+/*
+ * Close the object database and all of its sources so that any held resources
+ * will be released. The database can still be used after closing it, in which
+ * case these resources may be reallocated.
+ */
+void odb_close(struct object_database *o);
 
 /*
  * Clear caches, reload alternates and then reload object sources so that new
@@ -314,12 +292,24 @@ struct object_info {
 	struct object_id *delta_base_oid;
 	void **contentp;
 
+	/*
+	 * The time the given looked-up object has been last modified.
+	 *
+	 * Note: the mtime may be ambiguous in case the object exists multiple
+	 * times in the object database. It is thus _not_ recommended to use
+	 * this field outside of contexts where you would read every instance
+	 * of the object, like for example with `odb_for_each_object()`. As it
+	 * is impossible to say at the ODB level what the intent of the caller
+	 * is (e.g. whether to find the oldest or newest object), it is the
+	 * responsibility of the caller to disambiguate the mtimes.
+	 */
+	time_t *mtimep;
+
 	/* Response */
 	enum {
 		OI_CACHED,
 		OI_LOOSE,
 		OI_PACKED,
-		OI_DBCACHED
 	} whence;
 	union {
 		/*
@@ -333,7 +323,12 @@ struct object_info {
 		struct {
 			struct packed_git *pack;
 			off_t offset;
-			unsigned int is_delta;
+			enum packed_object_type {
+				PACKED_OBJECT_TYPE_UNKNOWN,
+				PACKED_OBJECT_TYPE_FULL,
+				PACKED_OBJECT_TYPE_OFS_DELTA,
+				PACKED_OBJECT_TYPE_REF_DELTA,
+			} type;
 		} packed;
 	} u;
 };
@@ -344,24 +339,6 @@ struct object_info {
  */
 #define OBJECT_INFO_INIT { 0 }
 
-/* Invoke lookup_replace_object() on the given hash */
-#define OBJECT_INFO_LOOKUP_REPLACE 1
-/* Do not retry packed storage after checking packed and loose storage */
-#define OBJECT_INFO_QUICK 8
-/*
- * Do not attempt to fetch the object if missing (even if fetch_is_missing is
- * nonzero).
- */
-#define OBJECT_INFO_SKIP_FETCH_OBJECT 16
-/*
- * This is meant for bulk prefetching of missing blobs in a partial
- * clone. Implies OBJECT_INFO_SKIP_FETCH_OBJECT and OBJECT_INFO_QUICK
- */
-#define OBJECT_INFO_FOR_PREFETCH (OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_QUICK)
-
-/* Die if object corruption (not just an object being missing) was detected. */
-#define OBJECT_INFO_DIE_IF_CORRUPT 32
-
 /*
  * Read object info from the object database and populate the `object_info`
  * structure. Returns 0 on success, a negative error code otherwise.
@@ -369,7 +346,7 @@ struct object_info {
 int odb_read_object_info_extended(struct object_database *odb,
 				  const struct object_id *oid,
 				  struct object_info *oi,
-				  unsigned flags);
+				  enum object_info_flags flags);
 
 /*
  * Read a subset of object info for the given object ID. Returns an `enum
@@ -381,7 +358,7 @@ int odb_read_object_info(struct object_database *odb,
 			 const struct object_id *oid,
 			 unsigned long *sizep);
 
-enum {
+enum has_object_flags {
 	/* Retry packed storage after checking packed and loose storage */
 	HAS_OBJECT_RECHECK_PACKED = (1 << 0),
 	/* Allow fetching the object in case the repository has a promisor remote. */
@@ -394,7 +371,10 @@ enum {
  */
 int odb_has_object(struct object_database *odb,
 		   const struct object_id *oid,
-		   unsigned flags);
+		   enum has_object_flags flags);
+
+int odb_freshen_object(struct object_database *odb,
+		       const struct object_id *oid);
 
 void odb_assert_oid_type(struct object_database *odb,
 			 const struct object_id *oid, enum object_type expect);
@@ -431,26 +411,47 @@ static inline void obj_read_unlock(void)
 	if(obj_read_use_lock)
 		pthread_mutex_unlock(&obj_read_mutex);
 }
+
 /* Flags for for_each_*_object(). */
-enum for_each_object_flags {
+enum odb_for_each_object_flags {
 	/* Iterate only over local objects, not alternates. */
-	FOR_EACH_OBJECT_LOCAL_ONLY = (1<<0),
+	ODB_FOR_EACH_OBJECT_LOCAL_ONLY = (1<<0),
 
 	/* Only iterate over packs obtained from the promisor remote. */
-	FOR_EACH_OBJECT_PROMISOR_ONLY = (1<<1),
+	ODB_FOR_EACH_OBJECT_PROMISOR_ONLY = (1<<1),
 
 	/*
 	 * Visit objects within a pack in packfile order rather than .idx order
 	 */
-	FOR_EACH_OBJECT_PACK_ORDER = (1<<2),
+	ODB_FOR_EACH_OBJECT_PACK_ORDER = (1<<2),
 
 	/* Only iterate over packs that are not marked as kept in-core. */
-	FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3),
+	ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3),
 
 	/* Only iterate over packs that do not have .keep files. */
-	FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),
+	ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),
 };
 
+/*
+ * Iterate through all objects contained in the object database. Note that
+ * objects may be iterated over multiple times in case they are either stored
+ * in different backends or in case they are stored in multiple sources.
+ * If an object info request is given, then the object info will be read and
+ * passed to the callback as if `odb_read_object_info()` was called for the
+ * object.
+ *
+ * Returning a non-zero error code from the callback function will cause
+ * iteration to abort. The error code will be propagated.
+ *
+ * Returns 0 on success, a negative error code in case a failure occurred, or
+ * an arbitrary non-zero error code returned by the callback itself.
+ */
+int odb_for_each_object(struct object_database *odb,
+			const struct object_info *request,
+			odb_for_each_object_cb cb,
+			void *cb_data,
+			unsigned flags);
+
 enum {
 	/*
 	 * By default, `odb_write_object()` does not actually write anything
@@ -489,4 +490,19 @@ static inline int odb_write_object(struct object_database *odb,
 	return odb_write_object_ext(odb, buf, len, type, oid, NULL, 0);
 }
 
+struct odb_write_stream {
+	const void *(*read)(struct odb_write_stream *, unsigned long *len);
+	void *data;
+	int is_finished;
+};
+
+int odb_write_object_stream(struct object_database *odb,
+			    struct odb_write_stream *stream, size_t len,
+			    struct object_id *oid);
+
+void parse_alternates(const char *string,
+		      int sep,
+		      const char *relative_base,
+		      struct strvec *out);
+
 #endif /* ODB_H */
diff --git a/odb/source-files.c b/odb/source-files.c
new file mode 100644
index 0000000..14cb9ad
--- /dev/null
+++ b/odb/source-files.c
@@ -0,0 +1,239 @@
+#include "git-compat-util.h"
+#include "abspath.h"
+#include "chdir-notify.h"
+#include "gettext.h"
+#include "lockfile.h"
+#include "object-file.h"
+#include "odb.h"
+#include "odb/source.h"
+#include "odb/source-files.h"
+#include "packfile.h"
+#include "strbuf.h"
+#include "write-or-die.h"
+
+static void odb_source_files_reparent(const char *name UNUSED,
+				      const char *old_cwd,
+				      const char *new_cwd,
+				      void *cb_data)
+{
+	struct odb_source_files *files = cb_data;
+	char *path = reparent_relative_path(old_cwd, new_cwd,
+					    files->base.path);
+	free(files->base.path);
+	files->base.path = path;
+}
+
+static void odb_source_files_free(struct odb_source *source)
+{
+	struct odb_source_files *files = odb_source_files_downcast(source);
+	chdir_notify_unregister(NULL, odb_source_files_reparent, files);
+	odb_source_loose_free(files->loose);
+	packfile_store_free(files->packed);
+	odb_source_release(&files->base);
+	free(files);
+}
+
+static void odb_source_files_close(struct odb_source *source)
+{
+	struct odb_source_files *files = odb_source_files_downcast(source);
+	packfile_store_close(files->packed);
+}
+
+static void odb_source_files_reprepare(struct odb_source *source)
+{
+	struct odb_source_files *files = odb_source_files_downcast(source);
+	odb_source_loose_reprepare(&files->base);
+	packfile_store_reprepare(files->packed);
+}
+
+static int odb_source_files_read_object_info(struct odb_source *source,
+					     const struct object_id *oid,
+					     struct object_info *oi,
+					     enum object_info_flags flags)
+{
+	struct odb_source_files *files = odb_source_files_downcast(source);
+
+	if (!packfile_store_read_object_info(files->packed, oid, oi, flags) ||
+	    !odb_source_loose_read_object_info(source, oid, oi, flags))
+		return 0;
+
+	return -1;
+}
+
+static int odb_source_files_read_object_stream(struct odb_read_stream **out,
+					       struct odb_source *source,
+					       const struct object_id *oid)
+{
+	struct odb_source_files *files = odb_source_files_downcast(source);
+	if (!packfile_store_read_object_stream(out, files->packed, oid) ||
+	    !odb_source_loose_read_object_stream(out, source, oid))
+		return 0;
+	return -1;
+}
+
+static int odb_source_files_for_each_object(struct odb_source *source,
+					    const struct object_info *request,
+					    odb_for_each_object_cb cb,
+					    void *cb_data,
+					    unsigned flags)
+{
+	struct odb_source_files *files = odb_source_files_downcast(source);
+	int ret;
+
+	if (!(flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) {
+		ret = odb_source_loose_for_each_object(source, request, cb, cb_data, flags);
+		if (ret)
+			return ret;
+	}
+
+	ret = packfile_store_for_each_object(files->packed, request, cb, cb_data, flags);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int odb_source_files_freshen_object(struct odb_source *source,
+					   const struct object_id *oid)
+{
+	struct odb_source_files *files = odb_source_files_downcast(source);
+	if (packfile_store_freshen_object(files->packed, oid) ||
+	    odb_source_loose_freshen_object(source, oid))
+		return 1;
+	return 0;
+}
+
+static int odb_source_files_write_object(struct odb_source *source,
+					 const void *buf, unsigned long len,
+					 enum object_type type,
+					 struct object_id *oid,
+					 struct object_id *compat_oid,
+					 unsigned flags)
+{
+	return odb_source_loose_write_object(source, buf, len, type,
+					     oid, compat_oid, flags);
+}
+
+static int odb_source_files_write_object_stream(struct odb_source *source,
+						struct odb_write_stream *stream,
+						size_t len,
+						struct object_id *oid)
+{
+	return odb_source_loose_write_stream(source, stream, len, oid);
+}
+
+static int odb_source_files_begin_transaction(struct odb_source *source,
+					      struct odb_transaction **out)
+{
+	struct odb_transaction *tx = odb_transaction_files_begin(source);
+	if (!tx)
+		return -1;
+	*out = tx;
+	return 0;
+}
+
+static int odb_source_files_read_alternates(struct odb_source *source,
+					    struct strvec *out)
+{
+	struct strbuf buf = STRBUF_INIT;
+	char *path;
+
+	path = xstrfmt("%s/info/alternates", source->path);
+	if (strbuf_read_file(&buf, path, 1024) < 0) {
+		warn_on_fopen_errors(path);
+		free(path);
+		return 0;
+	}
+	parse_alternates(buf.buf, '\n', source->path, out);
+
+	strbuf_release(&buf);
+	free(path);
+	return 0;
+}
+
+static int odb_source_files_write_alternate(struct odb_source *source,
+					    const char *alternate)
+{
+	struct lock_file lock = LOCK_INIT;
+	char *path = xstrfmt("%s/%s", source->path, "info/alternates");
+	FILE *in, *out;
+	int found = 0;
+	int ret;
+
+	hold_lock_file_for_update(&lock, path, LOCK_DIE_ON_ERROR);
+	out = fdopen_lock_file(&lock, "w");
+	if (!out) {
+		ret = error_errno(_("unable to fdopen alternates lockfile"));
+		goto out;
+	}
+
+	in = fopen(path, "r");
+	if (in) {
+		struct strbuf line = STRBUF_INIT;
+
+		while (strbuf_getline(&line, in) != EOF) {
+			if (!strcmp(alternate, line.buf)) {
+				found = 1;
+				break;
+			}
+			fprintf_or_die(out, "%s\n", line.buf);
+		}
+
+		strbuf_release(&line);
+		fclose(in);
+	} else if (errno != ENOENT) {
+		ret = error_errno(_("unable to read alternates file"));
+		goto out;
+	}
+
+	if (found) {
+		rollback_lock_file(&lock);
+	} else {
+		fprintf_or_die(out, "%s\n", alternate);
+		if (commit_lock_file(&lock)) {
+			ret = error_errno(_("unable to move new alternates file into place"));
+			goto out;
+		}
+	}
+
+	ret = 0;
+
+out:
+	free(path);
+	return ret;
+}
+
+struct odb_source_files *odb_source_files_new(struct object_database *odb,
+					      const char *path,
+					      bool local)
+{
+	struct odb_source_files *files;
+
+	CALLOC_ARRAY(files, 1);
+	odb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local);
+	files->loose = odb_source_loose_new(&files->base);
+	files->packed = packfile_store_new(&files->base);
+
+	files->base.free = odb_source_files_free;
+	files->base.close = odb_source_files_close;
+	files->base.reprepare = odb_source_files_reprepare;
+	files->base.read_object_info = odb_source_files_read_object_info;
+	files->base.read_object_stream = odb_source_files_read_object_stream;
+	files->base.for_each_object = odb_source_files_for_each_object;
+	files->base.freshen_object = odb_source_files_freshen_object;
+	files->base.write_object = odb_source_files_write_object;
+	files->base.write_object_stream = odb_source_files_write_object_stream;
+	files->base.begin_transaction = odb_source_files_begin_transaction;
+	files->base.read_alternates = odb_source_files_read_alternates;
+	files->base.write_alternate = odb_source_files_write_alternate;
+
+	/*
+	 * Ideally, we would only ever store absolute paths in the source. This
+	 * is not (yet) possible though because we access and assume relative
+	 * paths in the primary ODB source in some user-facing functionality.
+	 */
+	if (!is_absolute_path(path))
+		chdir_notify_register(NULL, odb_source_files_reparent, files);
+
+	return files;
+}
diff --git a/odb/source-files.h b/odb/source-files.h
new file mode 100644
index 0000000..23a3b4e
--- /dev/null
+++ b/odb/source-files.h
@@ -0,0 +1,35 @@
+#ifndef ODB_SOURCE_FILES_H
+#define ODB_SOURCE_FILES_H
+
+#include "odb/source.h"
+
+struct odb_source_loose;
+struct packfile_store;
+
+/*
+ * The files object database source uses a combination of loose objects and
+ * packfiles. It is the default backend used by Git to store objects.
+ */
+struct odb_source_files {
+	struct odb_source base;
+	struct odb_source_loose *loose;
+	struct packfile_store *packed;
+};
+
+/* Allocate and initialize a new object source. */
+struct odb_source_files *odb_source_files_new(struct object_database *odb,
+					      const char *path,
+					      bool local);
+
+/*
+ * Cast the given object database source to the files backend. This will cause
+ * a BUG in case the source doesn't use this backend.
+ */
+static inline struct odb_source_files *odb_source_files_downcast(struct odb_source *source)
+{
+	if (source->type != ODB_SOURCE_FILES)
+		BUG("trying to downcast source of type '%d' to files", source->type);
+	return container_of(source, struct odb_source_files, base);
+}
+
+#endif
diff --git a/odb/source.c b/odb/source.c
new file mode 100644
index 0000000..7993dcb
--- /dev/null
+++ b/odb/source.c
@@ -0,0 +1,38 @@
+#include "git-compat-util.h"
+#include "object-file.h"
+#include "odb/source-files.h"
+#include "odb/source.h"
+#include "packfile.h"
+
+struct odb_source *odb_source_new(struct object_database *odb,
+				  const char *path,
+				  bool local)
+{
+	return &odb_source_files_new(odb, path, local)->base;
+}
+
+void odb_source_init(struct odb_source *source,
+		     struct object_database *odb,
+		     enum odb_source_type type,
+		     const char *path,
+		     bool local)
+{
+	source->odb = odb;
+	source->type = type;
+	source->local = local;
+	source->path = xstrdup(path);
+}
+
+void odb_source_free(struct odb_source *source)
+{
+	if (!source)
+		return;
+	source->free(source);
+}
+
+void odb_source_release(struct odb_source *source)
+{
+	if (!source)
+		return;
+	free(source->path);
+}
diff --git a/odb/source.h b/odb/source.h
new file mode 100644
index 0000000..caac558
--- /dev/null
+++ b/odb/source.h
@@ -0,0 +1,468 @@
+#ifndef ODB_SOURCE_H
+#define ODB_SOURCE_H
+
+#include "object.h"
+
+enum odb_source_type {
+	/*
+	 * The "unknown" type, which should never be in use. This type mostly
+	 * exists to catch cases where the type field remains zeroed out.
+	 */
+	ODB_SOURCE_UNKNOWN,
+
+	/* The "files" backend that uses loose objects and packfiles. */
+	ODB_SOURCE_FILES,
+};
+
+/* Flags that can be passed to `odb_read_object_info_extended()`. */
+enum object_info_flags {
+	/* Invoke lookup_replace_object() on the given hash. */
+	OBJECT_INFO_LOOKUP_REPLACE = (1 << 0),
+
+	/* Do not reprepare object sources when the first lookup has failed. */
+	OBJECT_INFO_QUICK = (1 << 1),
+
+	/*
+	 * Do not attempt to fetch the object if missing (even if fetch_is_missing is
+	 * nonzero).
+	 */
+	OBJECT_INFO_SKIP_FETCH_OBJECT = (1 << 2),
+
+	/* Die if object corruption (not just an object being missing) was detected. */
+	OBJECT_INFO_DIE_IF_CORRUPT = (1 << 3),
+
+	/*
+	 * We have already tried reading the object, but it couldn't be found
+	 * via any of the attached sources, and are now doing a second read.
+	 * This second read asks the individual sources to also evaluate
+	 * whether any on-disk state may have changed that may have caused the
+	 * object to appear.
+	 *
+	 * This flag is for internal use, only. The second read only occurs
+	 * when `OBJECT_INFO_QUICK` was not passed.
+	 */
+	OBJECT_INFO_SECOND_READ = (1 << 4),
+
+	/*
+	 * This is meant for bulk prefetching of missing blobs in a partial
+	 * clone. Implies OBJECT_INFO_SKIP_FETCH_OBJECT and OBJECT_INFO_QUICK.
+	 */
+	OBJECT_INFO_FOR_PREFETCH = (OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_QUICK),
+};
+
+struct object_id;
+struct object_info;
+struct odb_read_stream;
+struct odb_transaction;
+struct odb_write_stream;
+struct strvec;
+
+/*
+ * A callback function that can be used to iterate through objects. If given,
+ * the optional `oi` parameter will be populated the same as if you would call
+ * `odb_read_object_info()`.
+ *
+ * Returning a non-zero error code will cause iteration to abort. The error
+ * code will be propagated.
+ */
+typedef int (*odb_for_each_object_cb)(const struct object_id *oid,
+				      struct object_info *oi,
+				      void *cb_data);
+
+/*
+ * The source is the part of the object database that stores the actual
+ * objects. It thus encapsulates the logic to read and write the specific
+ * on-disk format. An object database can have multiple sources:
+ *
+ *   - The primary source, which is typically located in "$GIT_DIR/objects".
+ *     This is where new objects are usually written to.
+ *
+ *   - Alternate sources, which are configured via "objects/info/alternates" or
+ *     via the GIT_ALTERNATE_OBJECT_DIRECTORIES environment variable. These
+ *     alternate sources are only used to read objects.
+ */
+struct odb_source {
+	struct odb_source *next;
+
+	/* Object database that owns this object source. */
+	struct object_database *odb;
+
+	/* The type used by this source. */
+	enum odb_source_type type;
+
+	/*
+	 * Figure out whether this is the local source of the owning
+	 * repository, which would typically be its ".git/objects" directory.
+	 * This local object directory is usually where objects would be
+	 * written to.
+	 */
+	bool local;
+
+	/*
+	 * This object store is ephemeral, so there is no need to fsync.
+	 */
+	int will_destroy;
+
+	/*
+	 * Path to the source. If this is a relative path, it is relative to
+	 * the current working directory.
+	 */
+	char *path;
+
+	/*
+	 * This callback is expected to free the underlying object database source and
+	 * all associated resources. The function will never be called with a NULL pointer.
+	 */
+	void (*free)(struct odb_source *source);
+
+	/*
+	 * This callback is expected to close any open resources, like for
+	 * example file descriptors or connections. The source is expected to
+	 * still be usable after it has been closed. Closed resources may need
+	 * to be reopened in that case.
+	 */
+	void (*close)(struct odb_source *source);
+
+	/*
+	 * This callback is expected to clear underlying caches of the object
+	 * database source. The function is called when the repository has for
+	 * example just been repacked so that new objects will become visible.
+	 */
+	void (*reprepare)(struct odb_source *source);
+
+	/*
+	 * This callback is expected to read object information from the object
+	 * database source. The object info will be partially populated with
+	 * pointers for each bit of information that was requested by the
+	 * caller.
+	 *
+	 * The flags field is a combination of `OBJECT_INFO` flags. Only the
+	 * following fields need to be handled by the backend:
+	 *
+	 *   - `OBJECT_INFO_QUICK` indicates it is fine to use caches without
+	 *     re-verifying the data.
+	 *
+	 *   - `OBJECT_INFO_SECOND_READ` indicates that the initial object
+	 *     lookup has failed and that the object sources should check
+	 *     whether any of its on-disk state has changed that may have
+	 *     caused the object to appear. Sources are free to ignore the
+	 *     second read in case they know that the first read would have
+	 *     already surfaced the object without reloading any on-disk state.
+	 *
+	 * The callback is expected to return a negative error code in case
+	 * reading the object has failed, 0 otherwise.
+	 */
+	int (*read_object_info)(struct odb_source *source,
+				const struct object_id *oid,
+				struct object_info *oi,
+				enum object_info_flags flags);
+
+	/*
+	 * This callback is expected to create a new read stream that can be
+	 * used to stream the object identified by the given ID.
+	 *
+	 * The callback is expected to return a negative error code in case
+	 * creating the object stream has failed, 0 otherwise.
+	 */
+	int (*read_object_stream)(struct odb_read_stream **out,
+				  struct odb_source *source,
+				  const struct object_id *oid);
+
+	/*
+	 * This callback is expected to iterate over all objects stored in this
+	 * source and invoke the callback function for each of them. It is
+	 * valid to yield the same object multiple time. A non-zero exit code
+	 * from the object callback shall abort iteration.
+	 *
+	 * The optional `request` structure should serve as a template for
+	 * looking up object info for every individual iterated object. It
+	 * should not be modified directly and should instead be copied into a
+	 * separate `struct object_info` that gets passed to the callback. If
+	 * the caller passes a `NULL` pointer then the object itself shall not
+	 * be read.
+	 *
+	 * The callback is expected to return a negative error code in case the
+	 * iteration has failed to read all objects, 0 otherwise. When the
+	 * callback function returns a non-zero error code then that error code
+	 * should be returned.
+	 */
+	int (*for_each_object)(struct odb_source *source,
+			       const struct object_info *request,
+			       odb_for_each_object_cb cb,
+			       void *cb_data,
+			       unsigned flags);
+
+	/*
+	 * This callback is expected to freshen the given object so that its
+	 * last access time is set to the current time. This is used to ensure
+	 * that objects that are recent will not get garbage collected even if
+	 * they were unreachable.
+	 *
+	 * Returns 0 in case the object does not exist, 1 in case the object
+	 * has been freshened.
+	 */
+	int (*freshen_object)(struct odb_source *source,
+			      const struct object_id *oid);
+
+	/*
+	 * This callback is expected to persist the given object into the
+	 * object source. In case the object already exists it shall be
+	 * freshened.
+	 *
+	 * The flags field is a combination of `WRITE_OBJECT` flags.
+	 *
+	 * The resulting object ID (and optionally the compatibility object ID)
+	 * shall be written into the out pointers. The callback is expected to
+	 * return 0 on success, a negative error code otherwise.
+	 */
+	int (*write_object)(struct odb_source *source,
+			    const void *buf, unsigned long len,
+			    enum object_type type,
+			    struct object_id *oid,
+			    struct object_id *compat_oid,
+			    unsigned flags);
+
+	/*
+	 * This callback is expected to persist the given object stream into
+	 * the object source.
+	 *
+	 * The resulting object ID shall be written into the out pointer. The
+	 * callback is expected to return 0 on success, a negative error code
+	 * otherwise.
+	 */
+	int (*write_object_stream)(struct odb_source *source,
+				   struct odb_write_stream *stream, size_t len,
+				   struct object_id *oid);
+
+	/*
+	 * This callback is expected to create a new transaction that can be
+	 * used to write objects to. The objects shall only be persisted into
+	 * the object database when the transcation's commit function is
+	 * called. Otherwise, the objects shall be discarded.
+	 *
+	 * Returns 0 on success, in which case the `*out` pointer will have
+	 * been populated with the object database transaction. Returns a
+	 * negative error code otherwise.
+	 */
+	int (*begin_transaction)(struct odb_source *source,
+				 struct odb_transaction **out);
+
+	/*
+	 * This callback is expected to read the list of alternate object
+	 * database sources connected to it and write them into the `strvec`.
+	 *
+	 * The result is expected to be paths to the alternates. All paths must
+	 * be resolved to absolute paths.
+	 *
+	 * The callback is expected to return 0 on success, a negative error
+	 * code otherwise.
+	 */
+	int (*read_alternates)(struct odb_source *source,
+			       struct strvec *out);
+
+	/*
+	 * This callback is expected to persist the singular alternate passed
+	 * to it into its list of alternates. Any pre-existing alternates are
+	 * expected to remain active. Subsequent calls to `read_alternates` are
+	 * thus expected to yield the pre-existing list of alternates plus the
+	 * newly added alternate appended to its end.
+	 *
+	 * The callback is expected to return 0 on success, a negative error
+	 * code otherwise.
+	 */
+	int (*write_alternate)(struct odb_source *source,
+			       const char *alternate);
+};
+
+/*
+ * Allocate and initialize a new source for the given object database located
+ * at `path`. `local` indicates whether or not the source is the local and thus
+ * primary object source of the object database.
+ */
+struct odb_source *odb_source_new(struct object_database *odb,
+				  const char *path,
+				  bool local);
+
+/*
+ * Initialize the source for the given object database located at `path`.
+ * `local` indicates whether or not the source is the local and thus primary
+ * object source of the object database.
+ *
+ * This function is only supposed to be called by specific object source
+ * implementations.
+ */
+void odb_source_init(struct odb_source *source,
+		     struct object_database *odb,
+		     enum odb_source_type type,
+		     const char *path,
+		     bool local);
+
+/*
+ * Free the object database source, releasing all associated resources and
+ * freeing the structure itself.
+ */
+void odb_source_free(struct odb_source *source);
+
+/*
+ * Release the object database source, releasing all associated resources.
+ *
+ * This function is only supposed to be called by specific object source
+ * implementations.
+ */
+void odb_source_release(struct odb_source *source);
+
+/*
+ * Close the object database source without releasing he underlying data. The
+ * source can still be used going forward, but it first needs to be reopened.
+ * This can be useful to reduce resource usage.
+ */
+static inline void odb_source_close(struct odb_source *source)
+{
+	source->close(source);
+}
+
+/*
+ * Reprepare the object database source and clear any caches. Depending on the
+ * backend used this may have the effect that concurrently-written objects
+ * become visible.
+ */
+static inline void odb_source_reprepare(struct odb_source *source)
+{
+	source->reprepare(source);
+}
+
+/*
+ * Read an object from the object database source identified by its object ID.
+ * Returns 0 on success, a negative error code otherwise.
+ */
+static inline int odb_source_read_object_info(struct odb_source *source,
+					      const struct object_id *oid,
+					      struct object_info *oi,
+					      enum object_info_flags flags)
+{
+	return source->read_object_info(source, oid, oi, flags);
+}
+
+/*
+ * Create a new read stream for the given object ID. Returns 0 on success, a
+ * negative error code otherwise.
+ */
+static inline int odb_source_read_object_stream(struct odb_read_stream **out,
+						struct odb_source *source,
+						const struct object_id *oid)
+{
+	return source->read_object_stream(out, source, oid);
+}
+
+/*
+ * Iterate through all objects contained in the given source and invoke the
+ * callback function for each of them. Returning a non-zero code from the
+ * callback function aborts iteration. There is no guarantee that objects
+ * are only iterated over once.
+ *
+ * The optional `request` structure serves as a template for retrieving the
+ * object info for each indvidual iterated object and will be populated as if
+ * `odb_source_read_object_info()` was called on the object. It will not be
+ * modified, the callback will instead be invoked with a separate `struct
+ * object_info` for every object. Object info will not be read when passing a
+ * `NULL` pointer.
+ *
+ * The flags is a bitfield of `ODB_FOR_EACH_OBJECT_*` flags. Not all flags may
+ * apply to a specific backend, so whether or not they are honored is defined
+ * by the implementation.
+ *
+ * Returns 0 when all objects have been iterated over, a negative error code in
+ * case iteration has failed, or a non-zero value returned from the callback.
+ */
+static inline int odb_source_for_each_object(struct odb_source *source,
+					     const struct object_info *request,
+					     odb_for_each_object_cb cb,
+					     void *cb_data,
+					     unsigned flags)
+{
+	return source->for_each_object(source, request, cb, cb_data, flags);
+}
+
+/*
+ * Freshen an object in the object database by updating its timestamp.
+ * Returns 1 in case the object has been freshened, 0 in case the object does
+ * not exist.
+ */
+static inline int odb_source_freshen_object(struct odb_source *source,
+					    const struct object_id *oid)
+{
+	return source->freshen_object(source, oid);
+}
+
+/*
+ * Write an object into the object database source. Returns 0 on success, a
+ * negative error code otherwise. Populates the given out pointers for the
+ * object ID and the compatibility object ID, if non-NULL.
+ */
+static inline int odb_source_write_object(struct odb_source *source,
+					  const void *buf, unsigned long len,
+					  enum object_type type,
+					  struct object_id *oid,
+					  struct object_id *compat_oid,
+					  unsigned flags)
+{
+	return source->write_object(source, buf, len, type, oid,
+				    compat_oid, flags);
+}
+
+/*
+ * Write an object into the object database source via a stream. The overall
+ * length of the object must be known in advance.
+ *
+ * Return 0 on success, a negative error code otherwise. Populates the given
+ * out pointer for the object ID.
+ */
+static inline int odb_source_write_object_stream(struct odb_source *source,
+						 struct odb_write_stream *stream,
+						 size_t len,
+						 struct object_id *oid)
+{
+	return source->write_object_stream(source, stream, len, oid);
+}
+
+/*
+ * Read the list of alternative object database sources from the given backend
+ * and populate the `strvec` with them. The listing is not recursive -- that
+ * is, if any of the yielded alternate sources has alternates itself, those
+ * will not be yielded as part of this function call.
+ *
+ * Return 0 on success, a negative error code otherwise.
+ */
+static inline int odb_source_read_alternates(struct odb_source *source,
+					     struct strvec *out)
+{
+	return source->read_alternates(source, out);
+}
+
+/*
+ * Write and persist a new alternate object database source for the given
+ * source. Any preexisting alternates are expected to stay valid, and the new
+ * alternate shall be appended to the end of the list.
+ *
+ * Returns 0 on success, a negative error code otherwise.
+ */
+static inline int odb_source_write_alternate(struct odb_source *source,
+					      const char *alternate)
+{
+	return source->write_alternate(source, alternate);
+}
+
+/*
+ * Create a new transaction that can be used to write objects into a temporary
+ * staging area. The objects will only be persisted when the transaction is
+ * committed.
+ *
+ * Returns 0 on success, a negative error code otherwise.
+ */
+static inline int odb_source_begin_transaction(struct odb_source *source,
+					       struct odb_transaction **out)
+{
+	return source->begin_transaction(source, out);
+}
+
+#endif
diff --git a/odb/streaming.c b/odb/streaming.c
new file mode 100644
index 0000000..a4355cd
--- /dev/null
+++ b/odb/streaming.c
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+
+#include "git-compat-util.h"
+#include "convert.h"
+#include "environment.h"
+#include "repository.h"
+#include "odb.h"
+#include "odb/streaming.h"
+#include "replace-object.h"
+
+#define FILTER_BUFFER (1024*16)
+
+/*****************************************************************
+ *
+ * Filtered stream
+ *
+ *****************************************************************/
+
+struct odb_filtered_read_stream {
+	struct odb_read_stream base;
+	struct odb_read_stream *upstream;
+	struct stream_filter *filter;
+	char ibuf[FILTER_BUFFER];
+	char obuf[FILTER_BUFFER];
+	int i_end, i_ptr;
+	int o_end, o_ptr;
+	int input_finished;
+};
+
+static int close_istream_filtered(struct odb_read_stream *_fs)
+{
+	struct odb_filtered_read_stream *fs = (struct odb_filtered_read_stream *)_fs;
+	free_stream_filter(fs->filter);
+	return odb_read_stream_close(fs->upstream);
+}
+
+static ssize_t read_istream_filtered(struct odb_read_stream *_fs, char *buf,
+				     size_t sz)
+{
+	struct odb_filtered_read_stream *fs = (struct odb_filtered_read_stream *)_fs;
+	size_t filled = 0;
+
+	while (sz) {
+		/* do we already have filtered output? */
+		if (fs->o_ptr < fs->o_end) {
+			size_t to_move = fs->o_end - fs->o_ptr;
+			if (sz < to_move)
+				to_move = sz;
+			memcpy(buf + filled, fs->obuf + fs->o_ptr, to_move);
+			fs->o_ptr += to_move;
+			sz -= to_move;
+			filled += to_move;
+			continue;
+		}
+		fs->o_end = fs->o_ptr = 0;
+
+		/* do we have anything to feed the filter with? */
+		if (fs->i_ptr < fs->i_end) {
+			size_t to_feed = fs->i_end - fs->i_ptr;
+			size_t to_receive = FILTER_BUFFER;
+			if (stream_filter(fs->filter,
+					  fs->ibuf + fs->i_ptr, &to_feed,
+					  fs->obuf, &to_receive))
+				return -1;
+			fs->i_ptr = fs->i_end - to_feed;
+			fs->o_end = FILTER_BUFFER - to_receive;
+			continue;
+		}
+
+		/* tell the filter to drain upon no more input */
+		if (fs->input_finished) {
+			size_t to_receive = FILTER_BUFFER;
+			if (stream_filter(fs->filter,
+					  NULL, NULL,
+					  fs->obuf, &to_receive))
+				return -1;
+			fs->o_end = FILTER_BUFFER - to_receive;
+			if (!fs->o_end)
+				break;
+			continue;
+		}
+		fs->i_end = fs->i_ptr = 0;
+
+		/* refill the input from the upstream */
+		if (!fs->input_finished) {
+			fs->i_end = odb_read_stream_read(fs->upstream, fs->ibuf, FILTER_BUFFER);
+			if (fs->i_end < 0)
+				return -1;
+			if (fs->i_end)
+				continue;
+		}
+		fs->input_finished = 1;
+	}
+	return filled;
+}
+
+static struct odb_read_stream *attach_stream_filter(struct odb_read_stream *st,
+						    struct stream_filter *filter)
+{
+	struct odb_filtered_read_stream *fs;
+
+	CALLOC_ARRAY(fs, 1);
+	fs->base.close = close_istream_filtered;
+	fs->base.read = read_istream_filtered;
+	fs->upstream = st;
+	fs->filter = filter;
+	fs->base.size = -1; /* unknown */
+	fs->base.type = st->type;
+
+	return &fs->base;
+}
+
+/*****************************************************************
+ *
+ * In-core stream
+ *
+ *****************************************************************/
+
+struct odb_incore_read_stream {
+	struct odb_read_stream base;
+	char *buf; /* from odb_read_object_info_extended() */
+	unsigned long read_ptr;
+};
+
+static int close_istream_incore(struct odb_read_stream *_st)
+{
+	struct odb_incore_read_stream *st = (struct odb_incore_read_stream *)_st;
+	free(st->buf);
+	return 0;
+}
+
+static ssize_t read_istream_incore(struct odb_read_stream *_st, char *buf, size_t sz)
+{
+	struct odb_incore_read_stream *st = (struct odb_incore_read_stream *)_st;
+	size_t read_size = sz;
+	size_t remainder = st->base.size - st->read_ptr;
+
+	if (remainder <= read_size)
+		read_size = remainder;
+	if (read_size) {
+		memcpy(buf, st->buf + st->read_ptr, read_size);
+		st->read_ptr += read_size;
+	}
+	return read_size;
+}
+
+static int open_istream_incore(struct odb_read_stream **out,
+			       struct object_database *odb,
+			       const struct object_id *oid)
+{
+	struct object_info oi = OBJECT_INFO_INIT;
+	struct odb_incore_read_stream stream = {
+		.base.close = close_istream_incore,
+		.base.read = read_istream_incore,
+	};
+	struct odb_incore_read_stream *st;
+	int ret;
+
+	oi.typep = &stream.base.type;
+	oi.sizep = &stream.base.size;
+	oi.contentp = (void **)&stream.buf;
+	ret = odb_read_object_info_extended(odb, oid, &oi,
+					    OBJECT_INFO_DIE_IF_CORRUPT);
+	if (ret)
+		return ret;
+
+	CALLOC_ARRAY(st, 1);
+	*st = stream;
+	*out = &st->base;
+
+	return 0;
+}
+
+/*****************************************************************************
+ * static helpers variables and functions for users of streaming interface
+ *****************************************************************************/
+
+static int istream_source(struct odb_read_stream **out,
+			  struct object_database *odb,
+			  const struct object_id *oid)
+{
+	struct odb_source *source;
+
+	odb_prepare_alternates(odb);
+	for (source = odb->sources; source; source = source->next)
+		if (!odb_source_read_object_stream(out, source, oid))
+			return 0;
+
+	return open_istream_incore(out, odb, oid);
+}
+
+/****************************************************************
+ * Users of streaming interface
+ ****************************************************************/
+
+int odb_read_stream_close(struct odb_read_stream *st)
+{
+	int r = st->close(st);
+	free(st);
+	return r;
+}
+
+ssize_t odb_read_stream_read(struct odb_read_stream *st, void *buf, size_t sz)
+{
+	return st->read(st, buf, sz);
+}
+
+struct odb_read_stream *odb_read_stream_open(struct object_database *odb,
+					     const struct object_id *oid,
+					     struct stream_filter *filter)
+{
+	struct odb_read_stream *st;
+	const struct object_id *real = lookup_replace_object(odb->repo, oid);
+	int ret = istream_source(&st, odb, real);
+
+	if (ret)
+		return NULL;
+
+	if (filter) {
+		/* Add "&& !is_null_stream_filter(filter)" for performance */
+		struct odb_read_stream *nst = attach_stream_filter(st, filter);
+		if (!nst) {
+			odb_read_stream_close(st);
+			return NULL;
+		}
+		st = nst;
+	}
+
+	return st;
+}
+
+int odb_stream_blob_to_fd(struct object_database *odb,
+			  int fd,
+			  const struct object_id *oid,
+			  struct stream_filter *filter,
+			  int can_seek)
+{
+	struct odb_read_stream *st;
+	ssize_t kept = 0;
+	int result = -1;
+
+	st = odb_read_stream_open(odb, oid, filter);
+	if (!st) {
+		if (filter)
+			free_stream_filter(filter);
+		return result;
+	}
+	if (st->type != OBJ_BLOB)
+		goto close_and_exit;
+	for (;;) {
+		char buf[1024 * 16];
+		ssize_t wrote, holeto;
+		ssize_t readlen = odb_read_stream_read(st, buf, sizeof(buf));
+
+		if (readlen < 0)
+			goto close_and_exit;
+		if (!readlen)
+			break;
+		if (can_seek && sizeof(buf) == readlen) {
+			for (holeto = 0; holeto < readlen; holeto++)
+				if (buf[holeto])
+					break;
+			if (readlen == holeto) {
+				kept += holeto;
+				continue;
+			}
+		}
+
+		if (kept && lseek(fd, kept, SEEK_CUR) == (off_t) -1)
+			goto close_and_exit;
+		else
+			kept = 0;
+		wrote = write_in_full(fd, buf, readlen);
+
+		if (wrote < 0)
+			goto close_and_exit;
+	}
+	if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 ||
+		     xwrite(fd, "", 1) != 1))
+		goto close_and_exit;
+	result = 0;
+
+ close_and_exit:
+	odb_read_stream_close(st);
+	return result;
+}
diff --git a/odb/streaming.h b/odb/streaming.h
new file mode 100644
index 0000000..c7861f7
--- /dev/null
+++ b/odb/streaming.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#ifndef STREAMING_H
+#define STREAMING_H 1
+
+#include "object.h"
+
+struct object_database;
+struct odb_read_stream;
+struct stream_filter;
+
+typedef int (*odb_read_stream_close_fn)(struct odb_read_stream *);
+typedef ssize_t (*odb_read_stream_read_fn)(struct odb_read_stream *, char *, size_t);
+
+/*
+ * A stream that can be used to read an object from the object database without
+ * loading all of it into memory.
+ */
+struct odb_read_stream {
+	odb_read_stream_close_fn close;
+	odb_read_stream_read_fn read;
+	enum object_type type;
+	unsigned long size; /* inflated size of full object */
+};
+
+/*
+ * Create a new object stream for the given object database. An optional filter
+ * can be used to transform the object's content.
+ *
+ * Returns the stream on success, a `NULL` pointer otherwise.
+ */
+struct odb_read_stream *odb_read_stream_open(struct object_database *odb,
+					     const struct object_id *oid,
+					     struct stream_filter *filter);
+
+/*
+ * Close the given read stream and release all resources associated with it.
+ * Returns 0 on success, a negative error code otherwise.
+ */
+int odb_read_stream_close(struct odb_read_stream *stream);
+
+/*
+ * Read data from the stream into the buffer. Returns 0 on EOF and the number
+ * of bytes read on success. Returns a negative error code in case reading from
+ * the stream fails.
+ */
+ssize_t odb_read_stream_read(struct odb_read_stream *stream, void *buf, size_t len);
+
+/*
+ * Look up the object by its ID and write the full contents to the file
+ * descriptor. The object must be a blob, or the function will fail. When
+ * provided, the filter is used to transform the blob contents.
+ *
+ * `can_seek` should be set to 1 in case the given file descriptor can be
+ * seek(3p)'d on. This is used to support files with holes in case a
+ * significant portion of the blob contains NUL bytes.
+ *
+ * Returns a negative error code on failure, 0 on success.
+ */
+int odb_stream_blob_to_fd(struct object_database *odb,
+			  int fd,
+			  const struct object_id *oid,
+			  struct stream_filter *filter,
+			  int can_seek);
+
+#endif /* STREAMING_H */
diff --git a/oidmap.c b/oidmap.c
index 508d6c7..a1ef535 100644
--- a/oidmap.c
+++ b/oidmap.c
@@ -24,11 +24,28 @@ void oidmap_init(struct oidmap *map, size_t initial_size)
 
 void oidmap_clear(struct oidmap *map, int free_entries)
 {
-	if (!map)
+	oidmap_clear_with_free(map,
+		free_entries ? free : NULL);
+}
+
+void oidmap_clear_with_free(struct oidmap *map,
+			    oidmap_free_fn free_fn)
+{
+	struct hashmap_iter iter;
+	struct hashmap_entry *e;
+
+	if (!map || !map->map.cmpfn)
 		return;
 
-	/* TODO: make oidmap itself not depend on struct layouts */
-	hashmap_clear_(&map->map, free_entries ? 0 : -1);
+	hashmap_iter_init(&map->map, &iter);
+	while ((e = hashmap_iter_next(&iter))) {
+		struct oidmap_entry *entry =
+			container_of(e, struct oidmap_entry, internal_entry);
+		if (free_fn)
+			free_fn(entry);
+	}
+
+	hashmap_clear(&map->map);
 }
 
 void *oidmap_get(const struct oidmap *map, const struct object_id *key)
diff --git a/oidmap.h b/oidmap.h
index 67fb322..acddcae 100644
--- a/oidmap.h
+++ b/oidmap.h
@@ -36,6 +36,21 @@ struct oidmap {
 void oidmap_init(struct oidmap *map, size_t initial_size);
 
 /*
+ * Function type for functions that free oidmap entries.
+ */
+typedef void (*oidmap_free_fn)(void *);
+
+/*
+ * Clear an oidmap, freeing any allocated memory. The map is empty and
+ * can be reused without another explicit init.
+ *
+ * The `free_fn`, if not NULL, is called for each oidmap entry in the map
+ * to free any user data associated with the entry.
+ */
+void oidmap_clear_with_free(struct oidmap *map,
+			    oidmap_free_fn free_fn);
+
+/*
  * Clear an oidmap, freeing any allocated memory. The map is empty and
  * can be reused without another explicit init.
  *
diff --git a/oidset.c b/oidset.c
index 8d36aef8..c8ff0b3 100644
--- a/oidset.c
+++ b/oidset.c
@@ -16,6 +16,22 @@ int oidset_contains(const struct oidset *set, const struct object_id *oid)
 	return pos != kh_end(&set->set);
 }
 
+bool oidset_equal(const struct oidset *a, const struct oidset *b)
+{
+	struct oidset_iter iter;
+	struct object_id *a_oid;
+
+	if (oidset_size(a) != oidset_size(b))
+		return false;
+
+	oidset_iter_init(a, &iter);
+	while ((a_oid = oidset_iter_next(&iter)))
+		if (!oidset_contains(b, a_oid))
+			return false;
+
+	return true;
+}
+
 int oidset_insert(struct oidset *set, const struct object_id *oid)
 {
 	int added;
diff --git a/oidset.h b/oidset.h
index 0106b6f..e0f1a6f 100644
--- a/oidset.h
+++ b/oidset.h
@@ -39,6 +39,11 @@ void oidset_init(struct oidset *set, size_t initial_size);
 int oidset_contains(const struct oidset *set, const struct object_id *oid);
 
 /**
+ * Returns true iff `a` and `b` contain the exact same OIDs.
+ */
+bool oidset_equal(const struct oidset *a, const struct oidset *b);
+
+/**
  * Insert the oid into the set; a copy is made, so "oid" does not need
  * to persist after this function is called.
  *
@@ -94,11 +99,11 @@ void oidset_parse_file_carefully(struct oidset *set, const char *path,
 				 oidset_parse_tweak_fn fn, void *cbdata);
 
 struct oidset_iter {
-	kh_oid_set_t *set;
+	const kh_oid_set_t *set;
 	khiter_t iter;
 };
 
-static inline void oidset_iter_init(struct oidset *set,
+static inline void oidset_iter_init(const struct oidset *set,
 				    struct oidset_iter *iter)
 {
 	iter->set = &set->set;
diff --git a/oidtree.c b/oidtree.c
index 151568f..324de94 100644
--- a/oidtree.c
+++ b/oidtree.c
@@ -10,7 +10,7 @@ struct oidtree_iter_data {
 	oidtree_iter fn;
 	void *arg;
 	size_t *last_nibble_at;
-	int algo;
+	uint32_t algo;
 	uint8_t last_byte;
 };
 
diff --git a/oss-fuzz/fuzz-commit-graph.c b/oss-fuzz/fuzz-commit-graph.c
index fb8b878..59bbb84 100644
--- a/oss-fuzz/fuzz-commit-graph.c
+++ b/oss-fuzz/fuzz-commit-graph.c
@@ -10,6 +10,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
 {
 	struct commit_graph *g;
 
+	memset(the_repository, 0, sizeof(*the_repository));
 	initialize_repository(the_repository);
 
 	/*
diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c
index 4404921..8338d72 100644
--- a/pack-bitmap-write.c
+++ b/pack-bitmap-write.c
@@ -306,7 +306,7 @@ struct bb_commit {
 
 static void clear_bb_commit(struct bb_commit *commit)
 {
-	free_commit_list(commit->reverse_edges);
+	commit_list_free(commit->reverse_edges);
 	bitmap_free(commit->commit_mask);
 	bitmap_free(commit->bitmap);
 }
@@ -315,8 +315,7 @@ define_commit_slab(bb_data, struct bb_commit);
 
 struct bitmap_builder {
 	struct bb_data data;
-	struct commit **commits;
-	size_t commits_nr, commits_alloc;
+	struct commit_stack commits;
 };
 
 static void bitmap_builder_init(struct bitmap_builder *bb,
@@ -329,8 +328,8 @@ static void bitmap_builder_init(struct bitmap_builder *bb,
 	struct commit_list *r;
 	unsigned int i, num_maximal = 0;
 
-	memset(bb, 0, sizeof(*bb));
 	init_bb_data(&bb->data);
+	commit_stack_init(&bb->commits);
 
 	reset_revision_walk();
 	repo_init_revisions(writer->to_pack->repo, &revs, NULL);
@@ -390,8 +389,7 @@ static void bitmap_builder_init(struct bitmap_builder *bb,
 
 		if (c_ent->maximal) {
 			num_maximal++;
-			ALLOC_GROW(bb->commits, bb->commits_nr + 1, bb->commits_alloc);
-			bb->commits[bb->commits_nr++] = commit;
+			commit_stack_push(&bb->commits, commit);
 		}
 
 		if (p) {
@@ -416,7 +414,7 @@ static void bitmap_builder_init(struct bitmap_builder *bb,
 				p_ent->maximal = 1;
 			else {
 				p_ent->maximal = 0;
-				free_commit_list(p_ent->reverse_edges);
+				commit_list_free(p_ent->reverse_edges);
 				p_ent->reverse_edges = NULL;
 			}
 
@@ -438,8 +436,7 @@ static void bitmap_builder_init(struct bitmap_builder *bb,
 	}
 
 	for (r = reusable; r; r = r->next) {
-		ALLOC_GROW(bb->commits, bb->commits_nr + 1, bb->commits_alloc);
-		bb->commits[bb->commits_nr++] = r->item;
+		commit_stack_push(&bb->commits, r->item);
 	}
 
 	trace2_data_intmax("pack-bitmap-write", writer->repo,
@@ -448,14 +445,13 @@ static void bitmap_builder_init(struct bitmap_builder *bb,
 			   "num_maximal_commits", num_maximal);
 
 	release_revisions(&revs);
-	free_commit_list(reusable);
+	commit_list_free(reusable);
 }
 
 static void bitmap_builder_clear(struct bitmap_builder *bb)
 {
 	deep_clear_bb_data(&bb->data, clear_bb_commit);
-	free(bb->commits);
-	bb->commits_nr = bb->commits_alloc = 0;
+	commit_stack_clear(&bb->commits);
 }
 
 static int fill_bitmap_tree(struct bitmap_writer *writer,
@@ -478,7 +474,7 @@ static int fill_bitmap_tree(struct bitmap_writer *writer,
 		return 0;
 	bitmap_set(bitmap, pos);
 
-	if (parse_tree(tree) < 0)
+	if (repo_parse_tree(writer->repo, tree) < 0)
 		die("unable to load tree object %s",
 		    oid_to_hex(&tree->object.oid));
 	init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size);
@@ -630,8 +626,8 @@ int bitmap_writer_build(struct bitmap_writer *writer)
 		mapping = NULL;
 
 	bitmap_builder_init(&bb, writer, old_bitmap);
-	for (i = bb.commits_nr; i > 0; i--) {
-		struct commit *commit = bb.commits[i-1];
+	for (i = bb.commits.nr; i > 0; i--) {
+		struct commit *commit = bb.commits.items[i-1];
 		struct bb_commit *ent = bb_data_at(&bb.data, commit);
 		struct commit *child;
 		int reused = 0;
diff --git a/pack-bitmap.c b/pack-bitmap.c
index 291e1a9..22419bf 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -213,6 +213,28 @@ static uint32_t bitmap_num_objects(struct bitmap_index *index)
 	return index->pack->num_objects;
 }
 
+static uint32_t bitmap_name_hash(struct bitmap_index *index, uint32_t pos)
+{
+	if (bitmap_is_midx(index)) {
+		while (index && pos < index->midx->num_objects_in_base) {
+			ASSERT(bitmap_is_midx(index));
+			index = index->base;
+		}
+
+		if (!index)
+			BUG("NULL base bitmap for object position: %"PRIu32, pos);
+
+		pos -= index->midx->num_objects_in_base;
+		if (pos >= index->midx->num_objects)
+			BUG("out-of-bounds midx bitmap object at %"PRIu32, pos);
+	}
+
+	if (!index->hashes)
+		return 0;
+
+	return get_be32(index->hashes + pos);
+}
+
 static struct repository *bitmap_repo(struct bitmap_index *bitmap_git)
 {
 	if (bitmap_is_midx(bitmap_git))
@@ -1724,8 +1746,7 @@ static void show_objects_for_type(
 				pack = bitmap_git->pack;
 			}
 
-			if (bitmap_git->hashes)
-				hash = get_be32(bitmap_git->hashes + index_pos);
+			hash = bitmap_name_hash(bitmap_git, index_pos);
 
 			show_reach(&oid, object_type, 0, hash, pack, ofs, payload);
 		}
@@ -1855,8 +1876,7 @@ static unsigned long get_size_by_pos(struct bitmap_index *bitmap_git,
 			ofs = pack_pos_to_offset(pack, pos);
 		}
 
-		if (packed_object_info(bitmap_repo(bitmap_git), pack, ofs,
-				       &oi) < 0) {
+		if (packed_object_info(pack, ofs, &oi) < 0) {
 			struct object_id oid;
 			nth_bitmap_object_oid(bitmap_git, &oid,
 					      pack_pos_to_index(pack, pos));
@@ -3124,8 +3144,8 @@ uint32_t *create_bitmap_mapping(struct bitmap_index *bitmap_git,
 
 		if (oe) {
 			reposition[i] = oe_in_pack_pos(mapping, oe) + 1;
-			if (bitmap_git->hashes && !oe->hash)
-				oe->hash = get_be32(bitmap_git->hashes + index_pos);
+			if (!oe->hash)
+				oe->hash = bitmap_name_hash(bitmap_git, index_pos);
 		}
 	}
 
@@ -3294,7 +3314,7 @@ int bitmap_is_midx(struct bitmap_index *bitmap_git)
 	return !!bitmap_git->midx;
 }
 
-const struct string_list *bitmap_preferred_tips(struct repository *r)
+static const struct string_list *bitmap_preferred_tips(struct repository *r)
 {
 	const struct string_list *dest;
 
@@ -3303,6 +3323,34 @@ const struct string_list *bitmap_preferred_tips(struct repository *r)
 	return NULL;
 }
 
+void for_each_preferred_bitmap_tip(struct repository *repo,
+				   refs_for_each_cb cb, void *cb_data)
+{
+	struct refs_for_each_ref_options opts = { 0 };
+	struct string_list_item *item;
+	const struct string_list *preferred_tips;
+	struct strbuf buf = STRBUF_INIT;
+
+	preferred_tips = bitmap_preferred_tips(repo);
+	if (!preferred_tips)
+		return;
+
+	for_each_string_list_item(item, preferred_tips) {
+		opts.prefix = item->string;
+
+		if (!ends_with(opts.prefix, "/")) {
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "%s/", opts.prefix);
+			opts.prefix = buf.buf;
+		}
+
+		refs_for_each_ref_ext(get_main_ref_store(repo),
+				      cb, cb_data, &opts);
+	}
+
+	strbuf_release(&buf);
+}
+
 int bitmap_is_preferred_refname(struct repository *r, const char *refname)
 {
 	const struct string_list *preferred_tips = bitmap_preferred_tips(r);
diff --git a/pack-bitmap.h b/pack-bitmap.h
index 1bd7a79..a95e1c2 100644
--- a/pack-bitmap.h
+++ b/pack-bitmap.h
@@ -5,6 +5,7 @@
 #include "khash.h"
 #include "pack.h"
 #include "pack-objects.h"
+#include "refs.h"
 #include "string-list.h"
 
 struct commit;
@@ -99,6 +100,13 @@ int for_each_bitmapped_object(struct bitmap_index *bitmap_git,
 			      show_reachable_fn show_reach,
 			      void *payload);
 
+/*
+ * Iterate over all references that are configured as preferred bitmap tips via
+ * "pack.preferBitmapTips" and invoke the callback on each function.
+ */
+void for_each_preferred_bitmap_tip(struct repository *repo,
+				   refs_for_each_cb cb, void *cb_data);
+
 #define GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL \
 	"GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL"
 
@@ -182,7 +190,6 @@ char *pack_bitmap_filename(struct packed_git *p);
 
 int bitmap_is_midx(struct bitmap_index *bitmap_git);
 
-const struct string_list *bitmap_preferred_tips(struct repository *r);
 int bitmap_is_preferred_refname(struct repository *r, const char *refname);
 
 int verify_bitmap_files(struct repository *r);
diff --git a/pack-check.c b/pack-check.c
index 67cb2cf..7378c80 100644
--- a/pack-check.c
+++ b/pack-check.c
@@ -9,6 +9,7 @@
 #include "packfile.h"
 #include "object-file.h"
 #include "odb.h"
+#include "odb/streaming.h"
 
 struct idx_entry {
 	off_t                offset;
@@ -104,6 +105,7 @@ static int verify_packfile(struct repository *r,
 	QSORT(entries, nr_objects, compare_entries);
 
 	for (i = 0; i < nr_objects; i++) {
+		struct odb_read_stream *stream = NULL;
 		void *data;
 		struct object_id oid;
 		enum object_type type;
@@ -152,7 +154,9 @@ static int verify_packfile(struct repository *r,
 							type) < 0)
 			err = error("packed %s from %s is corrupt",
 				    oid_to_hex(&oid), p->pack_name);
-		else if (!data && stream_object_signature(r, &oid) < 0)
+		else if (!data &&
+			 (packfile_read_object_stream(&stream, &oid, p, entries[i].offset) < 0 ||
+			  stream_object_signature(r, stream, &oid) < 0))
 			err = error("packed %s from %s is corrupt",
 				    oid_to_hex(&oid), p->pack_name);
 		else if (fn) {
@@ -163,12 +167,14 @@ static int verify_packfile(struct repository *r,
 		}
 		if (((base_count + i) & 1023) == 0)
 			display_progress(progress, base_count + i);
-		free(data);
 
+		if (stream)
+			odb_read_stream_close(stream);
+		free(data);
 	}
+
 	display_progress(progress, base_count + i);
 	free(entries);
-
 	return err;
 }
 
diff --git a/pack-refs.c b/pack-refs.c
index 1a5e07d..eb6b2ba 100644
--- a/pack-refs.c
+++ b/pack-refs.c
@@ -14,10 +14,10 @@ int pack_refs_core(int argc,
 {
 	struct ref_exclusions excludes = REF_EXCLUSIONS_INIT;
 	struct string_list included_refs = STRING_LIST_INIT_NODUP;
-	struct pack_refs_opts pack_refs_opts = {
+	struct refs_optimize_opts optimize_opts = {
 		.exclusions = &excludes,
 		.includes = &included_refs,
-		.flags = PACK_REFS_PRUNE,
+		.flags = REFS_OPTIMIZE_PRUNE,
 	};
 	struct string_list option_excluded_refs = STRING_LIST_INIT_NODUP;
 	struct string_list_item *item;
@@ -26,9 +26,9 @@ int pack_refs_core(int argc,
 
 	struct option opts[] = {
 		OPT_BOOL(0, "all",   &pack_all, N_("pack everything")),
-		OPT_BIT(0, "prune", &pack_refs_opts.flags, N_("prune loose refs (default)"), PACK_REFS_PRUNE),
-		OPT_BIT(0, "auto", &pack_refs_opts.flags, N_("auto-pack refs as needed"), PACK_REFS_AUTO),
-		OPT_STRING_LIST(0, "include", pack_refs_opts.includes, N_("pattern"),
+		OPT_BIT(0, "prune", &optimize_opts.flags, N_("prune loose refs (default)"), REFS_OPTIMIZE_PRUNE),
+		OPT_BIT(0, "auto", &optimize_opts.flags, N_("auto-pack refs as needed"), REFS_OPTIMIZE_AUTO),
+		OPT_STRING_LIST(0, "include", optimize_opts.includes, N_("pattern"),
 			N_("references to include")),
 		OPT_STRING_LIST(0, "exclude", &option_excluded_refs, N_("pattern"),
 			N_("references to exclude")),
@@ -39,15 +39,15 @@ int pack_refs_core(int argc,
 		usage_with_options(usage_opts, opts);
 
 	for_each_string_list_item(item, &option_excluded_refs)
-		add_ref_exclusion(pack_refs_opts.exclusions, item->string);
+		add_ref_exclusion(optimize_opts.exclusions, item->string);
 
 	if (pack_all)
-		string_list_append(pack_refs_opts.includes, "*");
+		string_list_append(optimize_opts.includes, "*");
 
-	if (!pack_refs_opts.includes->nr)
-		string_list_append(pack_refs_opts.includes, "refs/tags/*");
+	if (!optimize_opts.includes->nr)
+		string_list_append(optimize_opts.includes, "refs/tags/*");
 
-	ret = refs_optimize(get_main_ref_store(repo), &pack_refs_opts);
+	ret = refs_optimize(get_main_ref_store(repo), &optimize_opts);
 
 	clear_ref_exclusions(&excludes);
 	string_list_clear(&included_refs, 0);
diff --git a/pack-revindex.c b/pack-revindex.c
index d0791cc..1fe0afe 100644
--- a/pack-revindex.c
+++ b/pack-revindex.c
@@ -75,7 +75,7 @@ static void sort_revindex(struct revindex_entry *entries, unsigned n, off_t max)
 	for (bits = 0; max >> bits; bits += DIGIT_SIZE) {
 		unsigned i;
 
-		memset(pos, 0, BUCKETS * sizeof(*pos));
+		MEMZERO_ARRAY(pos, BUCKETS);
 
 		/*
 		 * We want pos[i] to store the index of the last element that
@@ -277,6 +277,10 @@ int load_pack_revindex_from_disk(struct packed_git *p)
 {
 	char *revindex_name;
 	int ret;
+
+	if (p->revindex_data)
+		return 0;
+
 	if (open_pack_index(p))
 		return -1;
 
@@ -544,7 +548,7 @@ static int midx_key_to_pack_pos(struct multi_pack_index *m,
 				struct midx_pack_key *key,
 				uint32_t *pos)
 {
-	uint32_t *found;
+	const uint32_t *found;
 
 	if (key->pack >= m->num_packs + m->num_packs_in_base)
 		BUG("MIDX pack lookup out of bounds (%"PRIu32" >= %"PRIu32")",
diff --git a/pack-revindex.h b/pack-revindex.h
index 422c248..0042892 100644
--- a/pack-revindex.h
+++ b/pack-revindex.h
@@ -72,7 +72,8 @@ int verify_pack_revindex(struct packed_git *p);
  * multi-pack index by mmap-ing it and assigning pointers in the
  * multi_pack_index to point at it.
  *
- * A negative number is returned on error.
+ * A negative number is returned on error. A positive number is returned in
+ * case the multi-pack-index does not have a reverse index.
  */
 int load_midx_revindex(struct multi_pack_index *m);
 
diff --git a/packfile.c b/packfile.c
index 1ae2b2f..215a23e 100644
--- a/packfile.c
+++ b/packfile.c
@@ -20,6 +20,7 @@
 #include "tree.h"
 #include "object-file.h"
 #include "odb.h"
+#include "odb/streaming.h"
 #include "midx.h"
 #include "commit-graph.h"
 #include "pack-revindex.h"
@@ -47,6 +48,89 @@ static size_t pack_mapped;
 #define SZ_FMT PRIuMAX
 static inline uintmax_t sz_fmt(size_t s) { return s; }
 
+void packfile_list_clear(struct packfile_list *list)
+{
+	struct packfile_list_entry *e, *next;
+
+	for (e = list->head; e; e = next) {
+		next = e->next;
+		free(e);
+	}
+
+	list->head = list->tail = NULL;
+}
+
+static struct packfile_list_entry *packfile_list_remove_internal(struct packfile_list *list,
+								 struct packed_git *pack)
+{
+	struct packfile_list_entry *e, *prev;
+
+	for (e = list->head, prev = NULL; e; prev = e, e = e->next) {
+		if (e->pack != pack)
+			continue;
+
+		if (prev)
+			prev->next = e->next;
+		if (list->head == e)
+			list->head = e->next;
+		if (list->tail == e)
+			list->tail = prev;
+
+		return e;
+	}
+
+	return NULL;
+}
+
+void packfile_list_remove(struct packfile_list *list, struct packed_git *pack)
+{
+	free(packfile_list_remove_internal(list, pack));
+}
+
+void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack)
+{
+	struct packfile_list_entry *entry;
+
+	entry = packfile_list_remove_internal(list, pack);
+	if (!entry) {
+		entry = xmalloc(sizeof(*entry));
+		entry->pack = pack;
+	}
+	entry->next = list->head;
+
+	list->head = entry;
+	if (!list->tail)
+		list->tail = entry;
+}
+
+void packfile_list_append(struct packfile_list *list, struct packed_git *pack)
+{
+	struct packfile_list_entry *entry;
+
+	entry = packfile_list_remove_internal(list, pack);
+	if (!entry) {
+		entry = xmalloc(sizeof(*entry));
+		entry->pack = pack;
+	}
+	entry->next = NULL;
+
+	if (list->tail) {
+		list->tail->next = entry;
+		list->tail = entry;
+	} else {
+		list->head = list->tail = entry;
+	}
+}
+
+struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,
+					  const struct object_id *oid)
+{
+	for (; packs; packs = packs->next)
+		if (find_pack_entry_one(oid, packs->pack))
+			return packs->pack;
+	return NULL;
+}
+
 void pack_report(struct repository *repo)
 {
 	fprintf(stderr,
@@ -271,15 +355,19 @@ static void scan_windows(struct packed_git *p,
 	}
 }
 
-static int unuse_one_window(struct packed_git *current)
+static int unuse_one_window(struct object_database *odb)
 {
-	struct packed_git *p, *lru_p = NULL;
+	struct odb_source *source;
+	struct packfile_list_entry *e;
+	struct packed_git *lru_p = NULL;
 	struct pack_window *lru_w = NULL, *lru_l = NULL;
 
-	if (current)
-		scan_windows(current, &lru_p, &lru_w, &lru_l);
-	for (p = current->repo->objects->packfiles->packs; p; p = p->next)
-		scan_windows(p, &lru_p, &lru_w, &lru_l);
+	for (source = odb->sources; source; source = source->next) {
+		struct odb_source_files *files = odb_source_files_downcast(source);
+		for (e = files->packed->packs.head; e; e = e->next)
+			scan_windows(e->pack, &lru_p, &lru_w, &lru_l);
+	}
+
 	if (lru_p) {
 		munmap(lru_w->base, lru_w->len);
 		pack_mapped -= lru_w->len;
@@ -359,21 +447,6 @@ void close_pack(struct packed_git *p)
 	oidset_clear(&p->bad_objects);
 }
 
-void close_object_store(struct object_database *o)
-{
-	struct odb_source *source;
-
-	packfile_store_close(o->packfiles);
-
-	for (source = o->sources; source; source = source->next) {
-		if (source->midx)
-			close_midx(source->midx);
-		source->midx = NULL;
-	}
-
-	close_commit_graph(o);
-}
-
 void unlink_pack_path(const char *pack_name, int force_delete)
 {
 	static const char *exts[] = {".idx", ".pack", ".rev", ".keep", ".bitmap", ".promisor", ".mtimes"};
@@ -459,14 +532,19 @@ static void find_lru_pack(struct packed_git *p, struct packed_git **lru_p, struc
 
 static int close_one_pack(struct repository *r)
 {
-	struct packed_git *p, *lru_p = NULL;
+	struct odb_source *source;
+	struct packfile_list_entry *e;
+	struct packed_git *lru_p = NULL;
 	struct pack_window *mru_w = NULL;
 	int accept_windows_inuse = 1;
 
-	for (p = r->objects->packfiles->packs; p; p = p->next) {
-		if (p->pack_fd == -1)
-			continue;
-		find_lru_pack(p, &lru_p, &mru_w, &accept_windows_inuse);
+	for (source = r->objects->sources; source; source = source->next) {
+		struct odb_source_files *files = odb_source_files_downcast(source);
+		for (e = files->packed->packs.head; e; e = e->next) {
+			if (e->pack->pack_fd == -1)
+				continue;
+			find_lru_pack(e->pack, &lru_p, &mru_w, &accept_windows_inuse);
+		}
 	}
 
 	if (lru_p)
@@ -669,8 +747,8 @@ unsigned char *use_pack(struct packed_git *p,
 			win->len = (size_t)len;
 			pack_mapped += win->len;
 
-			while (settings->packed_git_limit < pack_mapped
-				&& unuse_one_window(p))
+			while (settings->packed_git_limit < pack_mapped &&
+			       unuse_one_window(p->repo->objects))
 				; /* nothing */
 			win->base = xmmap_gently(NULL, win->len,
 				PROT_READ, MAP_PRIVATE,
@@ -785,11 +863,8 @@ void packfile_store_add_pack(struct packfile_store *store,
 	if (pack->pack_fd != -1)
 		pack_open_fds++;
 
-	pack->next = store->packs;
-	store->packs = pack;
-
-	hashmap_entry_init(&pack->packmap_ent, strhash(pack->pack_name));
-	hashmap_add(&store->map, &pack->packmap_ent);
+	packfile_list_append(&store->packs, pack);
+	strmap_put(&store->packs_by_path, pack->pack_name, pack);
 }
 
 struct packed_git *packfile_store_load_pack(struct packfile_store *store,
@@ -806,10 +881,9 @@ struct packed_git *packfile_store_load_pack(struct packfile_store *store,
 	strbuf_strip_suffix(&key, ".idx");
 	strbuf_addstr(&key, ".pack");
 
-	p = hashmap_get_entry_from_hash(&store->map, strhash(key.buf), key.buf,
-					struct packed_git, packmap_ent);
+	p = strmap_get(&store->packs_by_path, key.buf);
 	if (!p) {
-		p = add_packed_git(store->odb->repo, idx_path,
+		p = add_packed_git(store->source->odb->repo, idx_path,
 				   strlen(idx_path), local);
 		if (p)
 			packfile_store_add_pack(store, p);
@@ -908,23 +982,23 @@ void for_each_file_in_pack_dir(const char *objdir,
 }
 
 struct prepare_pack_data {
-	struct repository *r;
+	struct odb_source *source;
 	struct string_list *garbage;
-	int local;
-	struct multi_pack_index *m;
 };
 
 static void prepare_pack(const char *full_name, size_t full_name_len,
 			 const char *file_name, void *_data)
 {
 	struct prepare_pack_data *data = (struct prepare_pack_data *)_data;
+	struct odb_source_files *files = odb_source_files_downcast(data->source);
 	size_t base_len = full_name_len;
 
 	if (strip_suffix_mem(full_name, &base_len, ".idx") &&
-	    !(data->m && midx_contains_pack(data->m, file_name))) {
+	    !(files->packed->midx &&
+	      midx_contains_pack(files->packed->midx, file_name))) {
 		char *trimmed_path = xstrndup(full_name, full_name_len);
-		packfile_store_load_pack(data->r->objects->packfiles,
-					 trimmed_path, data->local);
+		packfile_store_load_pack(files->packed,
+					 trimmed_path, data->source->local);
 		free(trimmed_path);
 	}
 
@@ -953,10 +1027,8 @@ static void prepare_packed_git_one(struct odb_source *source)
 {
 	struct string_list garbage = STRING_LIST_INIT_DUP;
 	struct prepare_pack_data data = {
-		.m = source->midx,
-		.r = source->odb->repo,
+		.source = source,
 		.garbage = &garbage,
-		.local = source->local,
 	};
 
 	for_each_file_in_pack_dir(source->path, prepare_pack, &data);
@@ -965,9 +1037,10 @@ static void prepare_packed_git_one(struct odb_source *source)
 	string_list_clear(data.garbage, 0);
 }
 
-DEFINE_LIST_SORT(static, sort_packs, struct packed_git, next);
+DEFINE_LIST_SORT(static, sort_packs, struct packfile_list_entry, next);
 
-static int sort_pack(const struct packed_git *a, const struct packed_git *b)
+static int sort_pack(const struct packfile_list_entry *a,
+		     const struct packfile_list_entry *b)
 {
 	int st;
 
@@ -977,7 +1050,7 @@ static int sort_pack(const struct packed_git *a, const struct packed_git *b)
 	 * remote ones could be on a network mounted filesystem.
 	 * Favor local ones for these reasons.
 	 */
-	st = a->pack_local - b->pack_local;
+	st = a->pack->pack_local - b->pack->pack_local;
 	if (st)
 		return -st;
 
@@ -986,38 +1059,26 @@ static int sort_pack(const struct packed_git *a, const struct packed_git *b)
 	 * and more recent objects tend to get accessed more
 	 * often.
 	 */
-	if (a->mtime < b->mtime)
+	if (a->pack->mtime < b->pack->mtime)
 		return 1;
-	else if (a->mtime == b->mtime)
+	else if (a->pack->mtime == b->pack->mtime)
 		return 0;
 	return -1;
 }
 
-static void packfile_store_prepare_mru(struct packfile_store *store)
-{
-	struct packed_git *p;
-
-	INIT_LIST_HEAD(&store->mru);
-
-	for (p = store->packs; p; p = p->next)
-		list_add_tail(&p->mru, &store->mru);
-}
-
 void packfile_store_prepare(struct packfile_store *store)
 {
-	struct odb_source *source;
-
 	if (store->initialized)
 		return;
 
-	odb_prepare_alternates(store->odb);
-	for (source = store->odb->sources; source; source = source->next) {
-		prepare_multi_pack_index_one(source);
-		prepare_packed_git_one(source);
-	}
-	sort_packs(&store->packs, sort_pack);
+	prepare_multi_pack_index_one(store->source);
+	prepare_packed_git_one(store->source);
 
-	packfile_store_prepare_mru(store);
+	sort_packs(&store->packs.head, sort_pack);
+	for (struct packfile_list_entry *e = store->packs.head; e; e = e->next)
+		if (!e->next)
+			store->packs.tail = e;
+
 	store->initialized = true;
 }
 
@@ -1027,25 +1088,17 @@ void packfile_store_reprepare(struct packfile_store *store)
 	packfile_store_prepare(store);
 }
 
-struct packed_git *packfile_store_get_packs(struct packfile_store *store)
+struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *store)
 {
 	packfile_store_prepare(store);
 
-	for (struct odb_source *source = store->odb->sources; source; source = source->next) {
-		struct multi_pack_index *m = source->midx;
-		if (!m)
-			continue;
+	if (store->midx) {
+		struct multi_pack_index *m = store->midx;
 		for (uint32_t i = 0; i < m->num_packs + m->num_packs_in_base; i++)
 			prepare_midx_pack(m, i);
 	}
 
-	return store->packs;
-}
-
-struct list_head *packfile_store_get_packs_mru(struct packfile_store *store)
-{
-	packfile_store_prepare(store);
-	return &store->mru;
+	return store->packs.head;
 }
 
 /*
@@ -1062,16 +1115,16 @@ unsigned long repo_approximate_object_count(struct repository *r)
 		unsigned long count = 0;
 		struct packed_git *p;
 
-		packfile_store_prepare(r->objects->packfiles);
+		odb_prepare_alternates(r->objects);
 
 		for (source = r->objects->sources; source; source = source->next) {
 			struct multi_pack_index *m = get_multi_pack_index(source);
 			if (m)
-				count += m->num_objects;
+				count += m->num_objects + m->num_objects_in_base;
 		}
 
-		for (p = r->objects->packfiles->packs; p; p = p->next) {
-			if (open_pack_index(p))
+		repo_for_each_pack(r, p) {
+			if (p->multi_pack_index || open_pack_index(p))
 				continue;
 			count += p->num_objects;
 		}
@@ -1195,11 +1248,17 @@ void mark_bad_packed_object(struct packed_git *p, const struct object_id *oid)
 const struct packed_git *has_packed_and_bad(struct repository *r,
 					    const struct object_id *oid)
 {
-	struct packed_git *p;
+	struct odb_source *source;
 
-	for (p = r->objects->packfiles->packs; p; p = p->next)
-		if (oidset_contains(&p->bad_objects, oid))
-			return p;
+	for (source = r->objects->sources; source; source = source->next) {
+		struct odb_source_files *files = odb_source_files_downcast(source);
+		struct packfile_list_entry *e;
+
+		for (e = files->packed->packs.head; e; e = e->next)
+			if (oidset_contains(&e->pack->bad_objects, oid))
+				return e->pack;
+	}
+
 	return NULL;
 }
 
@@ -1525,24 +1584,26 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,
 	hashmap_add(&delta_base_cache, &ent->ent);
 }
 
-int packed_object_info(struct repository *r, struct packed_git *p,
-		       off_t obj_offset, struct object_info *oi)
+static int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset,
+					     uint32_t *maybe_index_pos, struct object_info *oi)
 {
 	struct pack_window *w_curs = NULL;
 	unsigned long size;
 	off_t curpos = obj_offset;
-	enum object_type type;
+	enum object_type type = OBJ_NONE;
+	uint32_t pack_pos;
+	int ret;
 
 	/*
 	 * We always get the representation type, but only convert it to
 	 * a "real" type later if the caller is interested.
 	 */
 	if (oi->contentp) {
-		*oi->contentp = cache_or_unpack_entry(r, p, obj_offset, oi->sizep,
+		*oi->contentp = cache_or_unpack_entry(p->repo, p, obj_offset, oi->sizep,
 						      &type);
 		if (!*oi->contentp)
 			type = OBJ_BAD;
-	} else {
+	} else if (oi->sizep || oi->typep || oi->delta_base_oid) {
 		type = unpack_object_header(p, &w_curs, &curpos, &size);
 	}
 
@@ -1552,12 +1613,12 @@ int packed_object_info(struct repository *r, struct packed_git *p,
 			off_t base_offset = get_delta_base(p, &w_curs, &tmp_pos,
 							   type, obj_offset);
 			if (!base_offset) {
-				type = OBJ_BAD;
+				ret = -1;
 				goto out;
 			}
 			*oi->sizep = get_size_from_delta(p, &w_curs, tmp_pos);
 			if (*oi->sizep == 0) {
-				type = OBJ_BAD;
+				ret = -1;
 				goto out;
 			}
 		} else {
@@ -1565,26 +1626,45 @@ int packed_object_info(struct repository *r, struct packed_git *p,
 		}
 	}
 
-	if (oi->disk_sizep) {
-		uint32_t pos;
-		if (offset_to_pack_pos(p, obj_offset, &pos) < 0) {
+	if (oi->disk_sizep || (oi->mtimep && p->is_cruft)) {
+		if (offset_to_pack_pos(p, obj_offset, &pack_pos) < 0) {
 			error("could not find object at offset %"PRIuMAX" "
 			      "in pack %s", (uintmax_t)obj_offset, p->pack_name);
-			type = OBJ_BAD;
+			ret = -1;
 			goto out;
 		}
+	}
 
-		*oi->disk_sizep = pack_pos_to_offset(p, pos + 1) - obj_offset;
+	if (oi->disk_sizep)
+		*oi->disk_sizep = pack_pos_to_offset(p, pack_pos + 1) - obj_offset;
+
+	if (oi->mtimep) {
+		if (p->is_cruft) {
+			uint32_t index_pos;
+
+			if (load_pack_mtimes(p) < 0)
+				die(_("could not load .mtimes for cruft pack '%s'"),
+				    pack_basename(p));
+
+			if (maybe_index_pos)
+				index_pos = *maybe_index_pos;
+			else
+				index_pos = pack_pos_to_index(p, pack_pos);
+
+			*oi->mtimep = nth_packed_mtime(p, index_pos);
+		} else {
+			*oi->mtimep = p->mtime;
+		}
 	}
 
 	if (oi->typep) {
 		enum object_type ptot;
-		ptot = packed_to_object_type(r, p, obj_offset,
+		ptot = packed_to_object_type(p->repo, p, obj_offset,
 					     type, &w_curs, curpos);
 		if (oi->typep)
 			*oi->typep = ptot;
 		if (ptot < 0) {
-			type = OBJ_BAD;
+			ret = -1;
 			goto out;
 		}
 	}
@@ -1594,19 +1674,43 @@ int packed_object_info(struct repository *r, struct packed_git *p,
 			if (get_delta_base_oid(p, &w_curs, curpos,
 					       oi->delta_base_oid,
 					       type, obj_offset) < 0) {
-				type = OBJ_BAD;
+				ret = -1;
 				goto out;
 			}
 		} else
 			oidclr(oi->delta_base_oid, p->repo->hash_algo);
 	}
 
-	oi->whence = in_delta_base_cache(p, obj_offset) ? OI_DBCACHED :
-							  OI_PACKED;
+	oi->whence = OI_PACKED;
+	oi->u.packed.offset = obj_offset;
+	oi->u.packed.pack = p;
+
+	switch (type) {
+	case OBJ_NONE:
+		oi->u.packed.type = PACKED_OBJECT_TYPE_UNKNOWN;
+		break;
+	case OBJ_REF_DELTA:
+		oi->u.packed.type = PACKED_OBJECT_TYPE_REF_DELTA;
+		break;
+	case OBJ_OFS_DELTA:
+		oi->u.packed.type = PACKED_OBJECT_TYPE_OFS_DELTA;
+		break;
+	default:
+		oi->u.packed.type = PACKED_OBJECT_TYPE_FULL;
+		break;
+	}
+
+	ret = 0;
 
 out:
 	unuse_pack(&w_curs);
-	return type;
+	return ret;
+}
+
+int packed_object_info(struct packed_git *p, off_t obj_offset,
+		       struct object_info *oi)
+{
+	return packed_object_info_with_index_pos(p, obj_offset, NULL, oi);
 }
 
 static void *unpack_compressed_entry(struct packed_git *p,
@@ -2007,19 +2111,6 @@ int is_pack_valid(struct packed_git *p)
 	return !open_packed_git(p);
 }
 
-struct packed_git *find_oid_pack(const struct object_id *oid,
-				 struct packed_git *packs)
-{
-	struct packed_git *p;
-
-	for (p = packs; p; p = p->next) {
-		if (find_pack_entry_one(oid, p))
-			return p;
-	}
-	return NULL;
-
-}
-
 static int fill_pack_entry(const struct object_id *oid,
 			   struct pack_entry *e,
 			   struct packed_git *p)
@@ -2048,48 +2139,100 @@ static int fill_pack_entry(const struct object_id *oid,
 	return 1;
 }
 
-int find_pack_entry(struct repository *r, const struct object_id *oid, struct pack_entry *e)
+static int find_pack_entry(struct packfile_store *store,
+			   const struct object_id *oid,
+			   struct pack_entry *e)
 {
-	struct list_head *pos;
+	struct packfile_list_entry *l;
 
-	packfile_store_prepare(r->objects->packfiles);
+	packfile_store_prepare(store);
+	if (store->midx && fill_midx_entry(store->midx, oid, e))
+		return 1;
 
-	for (struct odb_source *source = r->objects->sources; source; source = source->next)
-		if (source->midx && fill_midx_entry(source->midx, oid, e))
-			return 1;
+	for (l = store->packs.head; l; l = l->next) {
+		struct packed_git *p = l->pack;
 
-	if (!r->objects->packfiles->packs)
-		return 0;
-
-	list_for_each(pos, &r->objects->packfiles->mru) {
-		struct packed_git *p = list_entry(pos, struct packed_git, mru);
 		if (!p->multi_pack_index && fill_pack_entry(oid, e, p)) {
-			list_move(&p->mru, &r->objects->packfiles->mru);
+			if (!store->skip_mru_updates)
+				packfile_list_prepend(&store->packs, p);
 			return 1;
 		}
 	}
+
 	return 0;
 }
 
-static void maybe_invalidate_kept_pack_cache(struct repository *r,
-					     unsigned flags)
+int packfile_store_freshen_object(struct packfile_store *store,
+				  const struct object_id *oid)
 {
-	if (!r->objects->packfiles->kept_cache.packs)
-		return;
-	if (r->objects->packfiles->kept_cache.flags == flags)
-		return;
-	FREE_AND_NULL(r->objects->packfiles->kept_cache.packs);
-	r->objects->packfiles->kept_cache.flags = 0;
+	struct pack_entry e;
+	if (!find_pack_entry(store, oid, &e))
+		return 0;
+	if (e.p->is_cruft)
+		return 0;
+	if (e.p->freshened)
+		return 1;
+	if (utime(e.p->pack_name, NULL))
+		return 0;
+	e.p->freshened = 1;
+	return 1;
 }
 
-struct packed_git **kept_pack_cache(struct repository *r, unsigned flags)
+int packfile_store_read_object_info(struct packfile_store *store,
+				    const struct object_id *oid,
+				    struct object_info *oi,
+				    enum object_info_flags flags)
 {
-	maybe_invalidate_kept_pack_cache(r, flags);
+	struct pack_entry e;
+	int ret;
 
-	if (!r->objects->packfiles->kept_cache.packs) {
+	/*
+	 * In case the first read didn't surface the object, we have to reload
+	 * packfiles. This may cause us to discover new packfiles that have
+	 * been added since the last time we have prepared the packfile store.
+	 */
+	if (flags & OBJECT_INFO_SECOND_READ)
+		packfile_store_reprepare(store);
+
+	if (!find_pack_entry(store, oid, &e))
+		return 1;
+
+	/*
+	 * We know that the caller doesn't actually need the
+	 * information below, so return early.
+	 */
+	if (!oi)
+		return 0;
+
+	ret = packed_object_info(e.p, e.offset, oi);
+	if (ret < 0) {
+		mark_bad_packed_object(e.p, oid);
+		return -1;
+	}
+
+	return 0;
+}
+
+static void maybe_invalidate_kept_pack_cache(struct packfile_store *store,
+					     unsigned flags)
+{
+	if (!store->kept_cache.packs)
+		return;
+	if (store->kept_cache.flags == flags)
+		return;
+	FREE_AND_NULL(store->kept_cache.packs);
+	store->kept_cache.flags = 0;
+}
+
+struct packed_git **packfile_store_get_kept_pack_cache(struct packfile_store *store,
+						       unsigned flags)
+{
+	maybe_invalidate_kept_pack_cache(store, flags);
+
+	if (!store->kept_cache.packs) {
 		struct packed_git **packs = NULL;
+		struct packfile_list_entry *e;
 		size_t nr = 0, alloc = 0;
-		struct packed_git *p;
 
 		/*
 		 * We want "all" packs here, because we need to cover ones that
@@ -2099,9 +2242,11 @@ struct packed_git **kept_pack_cache(struct repository *r, unsigned flags)
 		 * covers, one kept and one not kept, but the midx returns only
 		 * the non-kept version.
 		 */
-		repo_for_each_pack(r, p) {
-			if ((p->pack_keep && (flags & ON_DISK_KEEP_PACKS)) ||
-			    (p->pack_keep_in_core && (flags & IN_CORE_KEEP_PACKS))) {
+		for (e = packfile_store_get_packs(store); e; e = e->next) {
+			struct packed_git *p = e->pack;
+
+			if ((p->pack_keep && (flags & KEPT_PACK_ON_DISK)) ||
+			    (p->pack_keep_in_core && (flags & KEPT_PACK_IN_CORE))) {
 				ALLOC_GROW(packs, nr + 1, alloc);
 				packs[nr++] = p;
 			}
@@ -2109,50 +2254,59 @@ struct packed_git **kept_pack_cache(struct repository *r, unsigned flags)
 		ALLOC_GROW(packs, nr + 1, alloc);
 		packs[nr] = NULL;
 
-		r->objects->packfiles->kept_cache.packs = packs;
-		r->objects->packfiles->kept_cache.flags = flags;
+		store->kept_cache.packs = packs;
+		store->kept_cache.flags = flags;
 	}
 
-	return r->objects->packfiles->kept_cache.packs;
+	return store->kept_cache.packs;
 }
 
-int find_kept_pack_entry(struct repository *r,
-			 const struct object_id *oid,
-			 unsigned flags,
-			 struct pack_entry *e)
+int has_object_pack(struct repository *r, const struct object_id *oid)
 {
-	struct packed_git **cache;
+	struct odb_source *source;
+	struct pack_entry e;
 
-	for (cache = kept_pack_cache(r, flags); *cache; cache++) {
-		struct packed_git *p = *cache;
-		if (fill_pack_entry(oid, e, p))
-			return 1;
+	odb_prepare_alternates(r->objects);
+	for (source = r->objects->sources; source; source = source->next) {
+		struct odb_source_files *files = odb_source_files_downcast(source);
+		int ret = find_pack_entry(files->packed, oid, &e);
+		if (ret)
+			return ret;
 	}
 
 	return 0;
 }
 
-int has_object_pack(struct repository *r, const struct object_id *oid)
-{
-	struct pack_entry e;
-	return find_pack_entry(r, oid, &e);
-}
-
 int has_object_kept_pack(struct repository *r, const struct object_id *oid,
 			 unsigned flags)
 {
+	struct odb_source *source;
 	struct pack_entry e;
-	return find_kept_pack_entry(r, oid, flags, &e);
+
+	for (source = r->objects->sources; source; source = source->next) {
+		struct odb_source_files *files = odb_source_files_downcast(source);
+		struct packed_git **cache;
+
+		cache = packfile_store_get_kept_pack_cache(files->packed, flags);
+
+		for (; *cache; cache++) {
+			struct packed_git *p = *cache;
+			if (fill_pack_entry(oid, &e, p))
+				return 1;
+		}
+	}
+
+	return 0;
 }
 
 int for_each_object_in_pack(struct packed_git *p,
 			    each_packed_object_fn cb, void *data,
-			    enum for_each_object_flags flags)
+			    unsigned flags)
 {
 	uint32_t i;
 	int r = 0;
 
-	if (flags & FOR_EACH_OBJECT_PACK_ORDER) {
+	if (flags & ODB_FOR_EACH_OBJECT_PACK_ORDER) {
 		if (load_pack_revindex(p->repo, p))
 			return -1;
 	}
@@ -2173,7 +2327,7 @@ int for_each_object_in_pack(struct packed_git *p,
 		 *   - in pack-order, it is pack position, which we must
 		 *     convert to an index position in order to get the oid.
 		 */
-		if (flags & FOR_EACH_OBJECT_PACK_ORDER)
+		if (flags & ODB_FOR_EACH_OBJECT_PACK_ORDER)
 			index_pos = pack_pos_to_index(p, i);
 		else
 			index_pos = i;
@@ -2189,57 +2343,114 @@ int for_each_object_in_pack(struct packed_git *p,
 	return r;
 }
 
-int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,
-			   void *data, enum for_each_object_flags flags)
-{
-	struct packed_git *p;
-	int r = 0;
-	int pack_errors = 0;
+struct packfile_store_for_each_object_wrapper_data {
+	struct packfile_store *store;
+	const struct object_info *request;
+	odb_for_each_object_cb cb;
+	void *cb_data;
+};
 
-	repo_for_each_pack(repo, p) {
-		if ((flags & FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)
+static int packfile_store_for_each_object_wrapper(const struct object_id *oid,
+						  struct packed_git *pack,
+						  uint32_t index_pos,
+						  void *cb_data)
+{
+	struct packfile_store_for_each_object_wrapper_data *data = cb_data;
+
+	if (data->request) {
+		off_t offset = nth_packed_object_offset(pack, index_pos);
+		struct object_info oi = *data->request;
+
+		if (packed_object_info_with_index_pos(pack, offset,
+						      &index_pos, &oi) < 0) {
+			mark_bad_packed_object(pack, oid);
+			return -1;
+		}
+
+		return data->cb(oid, &oi, data->cb_data);
+	} else {
+		return data->cb(oid, NULL, data->cb_data);
+	}
+}
+
+int packfile_store_for_each_object(struct packfile_store *store,
+				   const struct object_info *request,
+				   odb_for_each_object_cb cb,
+				   void *cb_data,
+				   unsigned flags)
+{
+	struct packfile_store_for_each_object_wrapper_data data = {
+		.store = store,
+		.request = request,
+		.cb = cb,
+		.cb_data = cb_data,
+	};
+	struct packfile_list_entry *e;
+	int pack_errors = 0, ret;
+
+	store->skip_mru_updates = true;
+
+	for (e = packfile_store_get_packs(store); e; e = e->next) {
+		struct packed_git *p = e->pack;
+
+		if ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)
 			continue;
-		if ((flags & FOR_EACH_OBJECT_PROMISOR_ONLY) &&
+		if ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&
 		    !p->pack_promisor)
 			continue;
-		if ((flags & FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&
+		if ((flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&
 		    p->pack_keep_in_core)
 			continue;
-		if ((flags & FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&
+		if ((flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&
 		    p->pack_keep)
 			continue;
 		if (open_pack_index(p)) {
 			pack_errors = 1;
 			continue;
 		}
-		r = for_each_object_in_pack(p, cb, data, flags);
-		if (r)
-			break;
+
+		ret = for_each_object_in_pack(p, packfile_store_for_each_object_wrapper,
+					      &data, flags);
+		if (ret)
+			goto out;
 	}
-	return r ? r : pack_errors;
+
+	ret = 0;
+
+out:
+	store->skip_mru_updates = false;
+
+	if (!ret && pack_errors)
+		ret = -1;
+	return ret;
 }
 
+struct add_promisor_object_data {
+	struct repository *repo;
+	struct oidset *set;
+};
+
 static int add_promisor_object(const struct object_id *oid,
-			       struct packed_git *pack,
-			       uint32_t pos UNUSED,
-			       void *set_)
+			       struct object_info *oi UNUSED,
+			       void *cb_data)
 {
-	struct oidset *set = set_;
+	struct add_promisor_object_data *data = cb_data;
 	struct object *obj;
 	int we_parsed_object;
 
-	obj = lookup_object(pack->repo, oid);
+	obj = lookup_object(data->repo, oid);
 	if (obj && obj->parsed) {
 		we_parsed_object = 0;
 	} else {
 		we_parsed_object = 1;
-		obj = parse_object(pack->repo, oid);
+		obj = parse_object_with_flags(data->repo, oid,
+					      PARSE_OBJECT_SKIP_HASH_CHECK);
 	}
 
 	if (!obj)
 		return 1;
 
-	oidset_insert(set, oid);
+	oidset_insert(data->set, oid);
 
 	/*
 	 * If this is a tree, commit, or tag, the objects it refers
@@ -2257,19 +2468,19 @@ static int add_promisor_object(const struct object_id *oid,
 			 */
 			return 0;
 		while (tree_entry_gently(&desc, &entry))
-			oidset_insert(set, &entry.oid);
+			oidset_insert(data->set, &entry.oid);
 		if (we_parsed_object)
 			free_tree_buffer(tree);
 	} else if (obj->type == OBJ_COMMIT) {
 		struct commit *commit = (struct commit *) obj;
 		struct commit_list *parents = commit->parents;
 
-		oidset_insert(set, get_commit_tree_oid(commit));
+		oidset_insert(data->set, get_commit_tree_oid(commit));
 		for (; parents; parents = parents->next)
-			oidset_insert(set, &parents->item->object.oid);
+			oidset_insert(data->set, &parents->item->object.oid);
 	} else if (obj->type == OBJ_TAG) {
 		struct tag *tag = (struct tag *) obj;
-		oidset_insert(set, get_tagged_oid(tag));
+		oidset_insert(data->set, get_tagged_oid(tag));
 	}
 	return 0;
 }
@@ -2281,10 +2492,13 @@ int is_promisor_object(struct repository *r, const struct object_id *oid)
 
 	if (!promisor_objects_prepared) {
 		if (repo_has_promisor_remote(r)) {
-			for_each_packed_object(r, add_promisor_object,
-					       &promisor_objects,
-					       FOR_EACH_OBJECT_PROMISOR_ONLY |
-					       FOR_EACH_OBJECT_PACK_ORDER);
+			struct add_promisor_object_data data = {
+				.repo = r,
+				.set = &promisor_objects,
+			};
+
+			odb_for_each_object(r->objects, NULL, add_promisor_object, &data,
+					    ODB_FOR_EACH_OBJECT_PROMISOR_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER);
 		}
 		promisor_objects_prepared = 1;
 	}
@@ -2311,45 +2525,169 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l
 	return 0;
 }
 
-static int pack_map_entry_cmp(const void *cmp_data UNUSED,
-			      const struct hashmap_entry *entry,
-			      const struct hashmap_entry *entry2,
-			      const void *keydata)
-{
-	const char *key = keydata;
-	const struct packed_git *pg1, *pg2;
-
-	pg1 = container_of(entry, const struct packed_git, packmap_ent);
-	pg2 = container_of(entry2, const struct packed_git, packmap_ent);
-
-	return strcmp(pg1->pack_name, key ? key : pg2->pack_name);
-}
-
-struct packfile_store *packfile_store_new(struct object_database *odb)
+struct packfile_store *packfile_store_new(struct odb_source *source)
 {
 	struct packfile_store *store;
 	CALLOC_ARRAY(store, 1);
-	store->odb = odb;
-	INIT_LIST_HEAD(&store->mru);
-	hashmap_init(&store->map, pack_map_entry_cmp, NULL, 0);
+	store->source = source;
+	strmap_init(&store->packs_by_path);
 	return store;
 }
 
 void packfile_store_free(struct packfile_store *store)
 {
-	for (struct packed_git *p = store->packs, *next; p; p = next) {
-		next = p->next;
-		free(p);
-	}
-	hashmap_clear(&store->map);
+	for (struct packfile_list_entry *e = store->packs.head; e; e = e->next)
+		free(e->pack);
+	packfile_list_clear(&store->packs);
+
+	strmap_clear(&store->packs_by_path, 0);
 	free(store);
 }
 
 void packfile_store_close(struct packfile_store *store)
 {
-	for (struct packed_git *p = store->packs; p; p = p->next) {
-		if (p->do_not_close)
+	for (struct packfile_list_entry *e = store->packs.head; e; e = e->next) {
+		if (e->pack->do_not_close)
 			BUG("want to close pack marked 'do-not-close'");
-		close_pack(p);
+		close_pack(e->pack);
 	}
+	if (store->midx)
+		close_midx(store->midx);
+	store->midx = NULL;
+}
+
+struct odb_packed_read_stream {
+	struct odb_read_stream base;
+	struct packed_git *pack;
+	git_zstream z;
+	enum {
+		ODB_PACKED_READ_STREAM_UNINITIALIZED,
+		ODB_PACKED_READ_STREAM_INUSE,
+		ODB_PACKED_READ_STREAM_DONE,
+		ODB_PACKED_READ_STREAM_ERROR,
+	} z_state;
+	off_t pos;
+};
+
+static ssize_t read_istream_pack_non_delta(struct odb_read_stream *_st, char *buf,
+					   size_t sz)
+{
+	struct odb_packed_read_stream *st = (struct odb_packed_read_stream *)_st;
+	size_t total_read = 0;
+
+	switch (st->z_state) {
+	case ODB_PACKED_READ_STREAM_UNINITIALIZED:
+		memset(&st->z, 0, sizeof(st->z));
+		git_inflate_init(&st->z);
+		st->z_state = ODB_PACKED_READ_STREAM_INUSE;
+		break;
+	case ODB_PACKED_READ_STREAM_DONE:
+		return 0;
+	case ODB_PACKED_READ_STREAM_ERROR:
+		return -1;
+	case ODB_PACKED_READ_STREAM_INUSE:
+		break;
+	}
+
+	while (total_read < sz) {
+		int status;
+		struct pack_window *window = NULL;
+		unsigned char *mapped;
+
+		mapped = use_pack(st->pack, &window,
+				  st->pos, &st->z.avail_in);
+
+		st->z.next_out = (unsigned char *)buf + total_read;
+		st->z.avail_out = sz - total_read;
+		st->z.next_in = mapped;
+		status = git_inflate(&st->z, Z_FINISH);
+
+		st->pos += st->z.next_in - mapped;
+		total_read = st->z.next_out - (unsigned char *)buf;
+		unuse_pack(&window);
+
+		if (status == Z_STREAM_END) {
+			git_inflate_end(&st->z);
+			st->z_state = ODB_PACKED_READ_STREAM_DONE;
+			break;
+		}
+
+		/*
+		 * Unlike the loose object case, we do not have to worry here
+		 * about running out of input bytes and spinning infinitely. If
+		 * we get Z_BUF_ERROR due to too few input bytes, then we'll
+		 * replenish them in the next use_pack() call when we loop. If
+		 * we truly hit the end of the pack (i.e., because it's corrupt
+		 * or truncated), then use_pack() catches that and will die().
+		 */
+		if (status != Z_OK && status != Z_BUF_ERROR) {
+			git_inflate_end(&st->z);
+			st->z_state = ODB_PACKED_READ_STREAM_ERROR;
+			return -1;
+		}
+	}
+	return total_read;
+}
+
+static int close_istream_pack_non_delta(struct odb_read_stream *_st)
+{
+	struct odb_packed_read_stream *st = (struct odb_packed_read_stream *)_st;
+	if (st->z_state == ODB_PACKED_READ_STREAM_INUSE)
+		git_inflate_end(&st->z);
+	return 0;
+}
+
+int packfile_read_object_stream(struct odb_read_stream **out,
+				const struct object_id *oid,
+				struct packed_git *pack,
+				off_t offset)
+{
+	struct odb_packed_read_stream *stream;
+	struct pack_window *window = NULL;
+	enum object_type in_pack_type;
+	unsigned long size;
+
+	in_pack_type = unpack_object_header(pack, &window, &offset, &size);
+	unuse_pack(&window);
+
+	if (repo_settings_get_big_file_threshold(pack->repo) >= size)
+		return -1;
+
+	switch (in_pack_type) {
+	default:
+		return -1; /* we do not do deltas for now */
+	case OBJ_BAD:
+		mark_bad_packed_object(pack, oid);
+		return -1;
+	case OBJ_COMMIT:
+	case OBJ_TREE:
+	case OBJ_BLOB:
+	case OBJ_TAG:
+		break;
+	}
+
+	CALLOC_ARRAY(stream, 1);
+	stream->base.close = close_istream_pack_non_delta;
+	stream->base.read = read_istream_pack_non_delta;
+	stream->base.type = in_pack_type;
+	stream->base.size = size;
+	stream->z_state = ODB_PACKED_READ_STREAM_UNINITIALIZED;
+	stream->pack = pack;
+	stream->pos = offset;
+
+	*out = &stream->base;
+
+	return 0;
+}
+
+int packfile_store_read_object_stream(struct odb_read_stream **out,
+				      struct packfile_store *store,
+				      const struct object_id *oid)
+{
+	struct pack_entry e;
+
+	if (!find_pack_entry(store, oid, &e))
+		return -1;
+
+	return packfile_read_object_stream(out, oid, e.p, e.offset);
 }
diff --git a/packfile.h b/packfile.h
index c9d0b93..8b04a25 100644
--- a/packfile.h
+++ b/packfile.h
@@ -4,15 +4,16 @@
 #include "list.h"
 #include "object.h"
 #include "odb.h"
+#include "odb/source-files.h"
 #include "oidset.h"
+#include "repository.h"
+#include "strmap.h"
 
 /* in odb.h */
 struct object_info;
+struct odb_read_stream;
 
 struct packed_git {
-	struct hashmap_entry packmap_ent;
-	struct packed_git *next;
-	struct list_head mru;
 	struct pack_window *windows;
 	off_t pack_size;
 	const void *index_data;
@@ -52,53 +53,91 @@ struct packed_git {
 	char pack_name[FLEX_ARRAY]; /* more */
 };
 
+struct packfile_list {
+	struct packfile_list_entry *head, *tail;
+};
+
+struct packfile_list_entry {
+	struct packfile_list_entry *next;
+	struct packed_git *pack;
+};
+
+void packfile_list_clear(struct packfile_list *list);
+void packfile_list_remove(struct packfile_list *list, struct packed_git *pack);
+void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack);
+void packfile_list_append(struct packfile_list *list, struct packed_git *pack);
+
+/*
+ * Find the pack within the "packs" list whose index contains the object
+ * "oid". For general object lookups, you probably don't want this; use
+ * find_pack_entry() instead.
+ */
+struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,
+					  const struct object_id *oid);
+
 /*
  * A store that manages packfiles for a given object database.
  */
 struct packfile_store {
-	struct object_database *odb;
+	struct odb_source *source;
 
 	/*
-	 * The list of packfiles in the order in which they are being added to
-	 * the store.
+	 * The list of packfiles in the order in which they have been most
+	 * recently used.
 	 */
-	struct packed_git *packs;
+	struct packfile_list packs;
 
 	/*
 	 * Cache of packfiles which are marked as "kept", either because there
 	 * is an on-disk ".keep" file or because they are marked as "kept" in
 	 * memory.
 	 *
-	 * Should not be accessed directly, but via `kept_pack_cache()`. The
-	 * list of packs gets invalidated when the stored flags and the flags
-	 * passed to `kept_pack_cache()` mismatch.
+	 * Should not be accessed directly, but via
+	 * `packfile_store_get_kept_pack_cache()`. The list of packs gets
+	 * invalidated when the stored flags and the flags passed to
+	 * `packfile_store_get_kept_pack_cache()` mismatch.
 	 */
 	struct {
 		struct packed_git **packs;
 		unsigned flags;
 	} kept_cache;
 
-	/* A most-recently-used ordered version of the packs list. */
-	struct list_head mru;
+	/* The multi-pack index that belongs to this specific packfile store. */
+	struct multi_pack_index *midx;
 
 	/*
 	 * A map of packfile names to packed_git structs for tracking which
 	 * packs have been loaded already.
 	 */
-	struct hashmap map;
+	struct strmap packs_by_path;
 
 	/*
 	 * Whether packfiles have already been populated with this store's
 	 * packs.
 	 */
 	bool initialized;
+
+	/*
+	 * Usually, packfiles will be reordered to the front of the `packs`
+	 * list whenever an object is looked up via them. This has the effect
+	 * that packs that contain a lot of accessed objects will be located
+	 * towards the front.
+	 *
+	 * This is usually desireable, but there are exceptions. One exception
+	 * is when the looking up multiple objects in a loop for each packfile.
+	 * In that case, we may easily end up with an infinite loop as the
+	 * packfiles get reordered to the front repeatedly.
+	 *
+	 * Setting this field to `true` thus disables these reorderings.
+	 */
+	bool skip_mru_updates;
 };
 
 /*
  * Allocate and initialize a new empty packfile store for the given object
- * database.
+ * database source.
  */
-struct packfile_store *packfile_store_new(struct object_database *odb);
+struct packfile_store *packfile_store_new(struct odb_source *source);
 
 /*
  * Free the packfile store and all its associated state. All packfiles
@@ -137,23 +176,81 @@ void packfile_store_add_pack(struct packfile_store *store,
 			     struct packed_git *pack);
 
 /*
+ * Get all packs managed by the given store, including packfiles that are
+ * referenced by multi-pack indices.
+ */
+struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *store);
+
+struct repo_for_each_pack_data {
+	struct odb_source *source;
+	struct packfile_list_entry *entry;
+};
+
+static inline struct repo_for_each_pack_data repo_for_eack_pack_data_init(struct repository *repo)
+{
+	struct repo_for_each_pack_data data = { 0 };
+
+	odb_prepare_alternates(repo->objects);
+
+	for (struct odb_source *source = repo->objects->sources; source; source = source->next) {
+		struct odb_source_files *files = odb_source_files_downcast(source);
+		struct packfile_list_entry *entry = packfile_store_get_packs(files->packed);
+		if (!entry)
+			continue;
+		data.source = source;
+		data.entry = entry;
+		break;
+	}
+
+	return data;
+}
+
+static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data *data)
+{
+	struct odb_source *source;
+
+	data->entry = data->entry->next;
+	if (data->entry)
+		return;
+
+	for (source = data->source->next; source; source = source->next) {
+		struct odb_source_files *files = odb_source_files_downcast(source);
+		struct packfile_list_entry *entry = packfile_store_get_packs(files->packed);
+		if (!entry)
+			continue;
+		data->source = source;
+		data->entry = entry;
+		return;
+	}
+
+	data->source = NULL;
+	data->entry = NULL;
+}
+
+/*
  * Load and iterate through all packs of the given repository. This helper
  * function will yield packfiles from all object sources connected to the
  * repository.
  */
 #define repo_for_each_pack(repo, p) \
-	for (p = packfile_store_get_packs(repo->objects->packfiles); p; p = p->next)
+	for (struct repo_for_each_pack_data eack_pack_data = repo_for_eack_pack_data_init(repo); \
+	     ((p) = (eack_pack_data.entry ? eack_pack_data.entry->pack : NULL)); \
+	     repo_for_each_pack_data_next(&eack_pack_data))
+
+int packfile_store_read_object_stream(struct odb_read_stream **out,
+				      struct packfile_store *store,
+				      const struct object_id *oid);
 
 /*
- * Get all packs managed by the given store, including packfiles that are
- * referenced by multi-pack indices.
+ * Try to read the object identified by its ID from the object store and
+ * populate the object info with its data. Returns 1 in case the object was
+ * not found, 0 if it was and read successfully, and a negative error code in
+ * case the object was corrupted.
  */
-struct packed_git *packfile_store_get_packs(struct packfile_store *store);
-
-/*
- * Get all packs in most-recently-used order.
- */
-struct list_head *packfile_store_get_packs_mru(struct packfile_store *store);
+int packfile_store_read_object_info(struct packfile_store *store,
+				    const struct object_id *oid,
+				    struct object_info *oi,
+				    enum object_info_flags flags);
 
 /*
  * Open the packfile and add it to the store if it isn't yet known. Returns
@@ -163,6 +260,22 @@ struct list_head *packfile_store_get_packs_mru(struct packfile_store *store);
 struct packed_git *packfile_store_load_pack(struct packfile_store *store,
 					    const char *idx_path, int local);
 
+int packfile_store_freshen_object(struct packfile_store *store,
+				  const struct object_id *oid);
+
+enum kept_pack_type {
+	KEPT_PACK_ON_DISK = (1 << 0),
+	KEPT_PACK_IN_CORE = (1 << 1),
+};
+
+/*
+ * Retrieve the cache of kept packs from the given packfile store. Accepts a
+ * combination of `kept_pack_type` flags. The cache is computed on demand and
+ * will be recomputed whenever the flags change.
+ */
+struct packed_git **packfile_store_get_kept_pack_cache(struct packfile_store *store,
+						       unsigned flags);
+
 struct pack_window {
 	struct pack_window *next;
 	unsigned char *base;
@@ -229,9 +342,22 @@ typedef int each_packed_object_fn(const struct object_id *oid,
 				  void *data);
 int for_each_object_in_pack(struct packed_git *p,
 			    each_packed_object_fn, void *data,
-			    enum for_each_object_flags flags);
-int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,
-			   void *data, enum for_each_object_flags flags);
+			    unsigned flags);
+
+/*
+ * Iterate through all packed objects in the given packfile store and invoke
+ * the callback function for each of them. If an object info request is given,
+ * then the object info will be read for every individual object and passed to
+ * the callback as if `packfile_store_read_object_info()` was called for the
+ * object.
+ *
+ * The flags parameter is a combination of `odb_for_each_object_flags`.
+ */
+int packfile_store_for_each_object(struct packfile_store *store,
+				   const struct object_info *request,
+				   odb_for_each_object_cb cb,
+				   void *cb_data,
+				   unsigned flags);
 
 /* A hook to report invalid files in pack directory */
 #define PACKDIR_FILE_PACK 1
@@ -245,14 +371,6 @@ extern void (*report_garbage)(unsigned seen_bits, const char *path);
  */
 unsigned long repo_approximate_object_count(struct repository *r);
 
-/*
- * Find the pack within the "packs" list whose index contains the object "oid".
- * For general object lookups, you probably don't want this; use
- * find_pack_entry() instead.
- */
-struct packed_git *find_oid_pack(const struct object_id *oid,
-				 struct packed_git *packs);
-
 void pack_report(struct repository *repo);
 
 /*
@@ -276,7 +394,6 @@ struct object_database;
 unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned long *);
 void close_pack_windows(struct packed_git *);
 void close_pack(struct packed_git *);
-void close_object_store(struct object_database *o);
 void unuse_pack(struct pack_window **);
 void clear_delta_base_cache(void);
 struct packed_git *add_packed_git(struct repository *r, const char *path,
@@ -335,34 +452,30 @@ off_t get_delta_base(struct packed_git *p, struct pack_window **w_curs,
 		     off_t *curpos, enum object_type type,
 		     off_t delta_obj_offset);
 
+int packfile_read_object_stream(struct odb_read_stream **out,
+				const struct object_id *oid,
+				struct packed_git *pack,
+				off_t offset);
+
 void release_pack_memory(size_t);
 
 /* global flag to enable extra checks when accessing packed objects */
 extern int do_check_packed_object_crc;
 
-int packed_object_info(struct repository *r,
-		       struct packed_git *pack,
+/*
+ * Look up the object info for a specific offset in the packfile.
+ * Returns zero on success, a negative error code otherwise.
+ */
+int packed_object_info(struct packed_git *pack,
 		       off_t offset, struct object_info *);
 
 void mark_bad_packed_object(struct packed_git *, const struct object_id *);
 const struct packed_git *has_packed_and_bad(struct repository *, const struct object_id *);
 
-#define ON_DISK_KEEP_PACKS 1
-#define IN_CORE_KEEP_PACKS 2
-
-/*
- * Iff a pack file in the given repository contains the object named by sha1,
- * return true and store its location to e.
- */
-int find_pack_entry(struct repository *r, const struct object_id *oid, struct pack_entry *e);
-int find_kept_pack_entry(struct repository *r, const struct object_id *oid, unsigned flags, struct pack_entry *e);
-
 int has_object_pack(struct repository *r, const struct object_id *oid);
 int has_object_kept_pack(struct repository *r, const struct object_id *oid,
 			 unsigned flags);
 
-struct packed_git **kept_pack_cache(struct repository *r, unsigned flags);
-
 /*
  * Return 1 if an object in a promisor packfile is or refers to the given
  * object, 0 otherwise.
diff --git a/parallel-checkout.c b/parallel-checkout.c
index fba6aa6..0bf4bd6 100644
--- a/parallel-checkout.c
+++ b/parallel-checkout.c
@@ -13,7 +13,7 @@
 #include "read-cache-ll.h"
 #include "run-command.h"
 #include "sigchain.h"
-#include "streaming.h"
+#include "odb/streaming.h"
 #include "symlinks.h"
 #include "thread-utils.h"
 #include "trace2.h"
@@ -281,7 +281,8 @@ static int write_pc_item_to_fd(struct parallel_checkout_item *pc_item, int fd,
 
 	filter = get_stream_filter_ca(&pc_item->ca, &pc_item->ce->oid);
 	if (filter) {
-		if (stream_blob_to_fd(fd, &pc_item->ce->oid, filter, 1)) {
+		if (odb_stream_blob_to_fd(the_repository->objects, fd,
+					  &pc_item->ce->oid, filter, 1)) {
 			/* On error, reset fd to try writing without streaming */
 			if (reset_fd(fd, path))
 				return -1;
diff --git a/parse-options.c b/parse-options.c
index c9cafc2..a676da8 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -5,6 +5,7 @@
 #include "gettext.h"
 #include "strbuf.h"
 #include "string-list.h"
+#include "strmap.h"
 #include "utf8.h"
 
 static int disallow_abbreviated_options;
@@ -641,6 +642,7 @@ static void check_typos(const char *arg, const struct option *options)
 static void parse_options_check(const struct option *opts)
 {
 	char short_opts[128];
+	bool saw_number_option = false;
 	void *subcommand_value = NULL;
 
 	memset(short_opts, '\0', sizeof(short_opts));
@@ -655,6 +657,11 @@ static void parse_options_check(const struct option *opts)
 			else if (short_opts[opts->short_name]++)
 				optbug(opts, "short name already used");
 		}
+		if (opts->type == OPTION_NUMBER) {
+			if (saw_number_option)
+				optbug(opts, "duplicate numerical option");
+			saw_number_option = true;
+		}
 		if (opts->flags & PARSE_OPT_NODASH &&
 		    ((opts->flags & PARSE_OPT_OPTARG) ||
 		     !(opts->flags & PARSE_OPT_NOARG) ||
@@ -714,6 +721,20 @@ static void parse_options_check(const struct option *opts)
 	BUG_if_bug("invalid 'struct option'");
 }
 
+static void parse_options_check_harder(const struct option *opts)
+{
+	struct strset long_names = STRSET_INIT;
+
+	for (; opts->type != OPTION_END; opts++) {
+		if (opts->long_name) {
+			if (!strset_add(&long_names, opts->long_name))
+				optbug(opts, "long name already used");
+		}
+	}
+	BUG_if_bug("invalid 'struct option'");
+	strset_clear(&long_names);
+}
+
 static int has_subcommands(const struct option *options)
 {
 	for (; options->type != OPTION_END; options++)
@@ -1339,6 +1360,8 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t
 	const char *prefix = usage_prefix;
 	int saw_empty_line = 0;
 
+	parse_options_check_harder(opts);
+
 	if (!usagestr)
 		return PARSE_OPT_HELP;
 
diff --git a/path-walk.c b/path-walk.c
index f1ceed9..364e4cf 100644
--- a/path-walk.c
+++ b/path-walk.c
@@ -137,7 +137,7 @@ static int add_tree_entries(struct path_walk_context *ctx,
 		error(_("failed to walk children of tree %s: not found"),
 		      oid_to_hex(oid));
 		return -1;
-	} else if (parse_tree_gently(tree, 1)) {
+	} else if (repo_parse_tree_gently(ctx->repo, tree, 1)) {
 		error("bad tree object %s", oid_to_hex(oid));
 		return -1;
 	}
diff --git a/path.c b/path.c
index 7f56eaf..c285357 100644
--- a/path.c
+++ b/path.c
@@ -4,7 +4,6 @@
 
 #include "git-compat-util.h"
 #include "abspath.h"
-#include "environment.h"
 #include "gettext.h"
 #include "repository.h"
 #include "strbuf.h"
@@ -59,7 +58,7 @@ static void strbuf_cleanup_path(struct strbuf *sb)
 
 static int dir_prefix(const char *buf, const char *dir)
 {
-	int len = strlen(dir);
+	size_t len = strlen(dir);
 	return !strncmp(buf, dir, len) &&
 		(is_dir_sep(buf[len]) || buf[len] == '\0');
 }
@@ -486,17 +485,16 @@ const char *mkpath(const char *fmt, ...)
 	return cleanup_path(pathname->buf);
 }
 
-const char *worktree_git_path(struct repository *r,
-			      const struct worktree *wt, const char *fmt, ...)
+const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...)
 {
 	struct strbuf *pathname = get_pathname();
 	va_list args;
 
-	if (wt && wt->repo != r)
-		BUG("worktree not connected to expected repository");
+	if (!wt)
+		BUG("%s() called with NULL worktree", __func__);
 
 	va_start(args, fmt);
-	repo_git_pathv(r, wt, pathname, fmt, args);
+	repo_git_pathv(wt->repo, wt, pathname, fmt, args);
 	va_end(args);
 	return pathname->buf;
 }
@@ -738,122 +736,22 @@ char *interpolate_path(const char *path, int real_home)
 	return NULL;
 }
 
-/*
- * First, one directory to try is determined by the following algorithm.
- *
- * (0) If "strict" is given, the path is used as given and no DWIM is
- *     done. Otherwise:
- * (1) "~/path" to mean path under the running user's home directory;
- * (2) "~user/path" to mean path under named user's home directory;
- * (3) "relative/path" to mean cwd relative directory; or
- * (4) "/absolute/path" to mean absolute directory.
- *
- * Unless "strict" is given, we check "%s/.git", "%s", "%s.git/.git", "%s.git"
- * in this order. We select the first one that is a valid git repository, and
- * chdir() to it. If none match, or we fail to chdir, we return NULL.
- *
- * If all goes well, we return the directory we used to chdir() (but
- * before ~user is expanded), avoiding getcwd() resolving symbolic
- * links.  User relative paths are also returned as they are given,
- * except DWIM suffixing.
- */
-const char *enter_repo(const char *path, unsigned flags)
-{
-	static struct strbuf validated_path = STRBUF_INIT;
-	static struct strbuf used_path = STRBUF_INIT;
-
-	if (!path)
-		return NULL;
-
-	if (!(flags & ENTER_REPO_STRICT)) {
-		static const char *suffix[] = {
-			"/.git", "", ".git/.git", ".git", NULL,
-		};
-		const char *gitfile;
-		int len = strlen(path);
-		int i;
-		while ((1 < len) && (path[len-1] == '/'))
-			len--;
-
-		/*
-		 * We can handle arbitrary-sized buffers, but this remains as a
-		 * sanity check on untrusted input.
-		 */
-		if (PATH_MAX <= len)
-			return NULL;
-
-		strbuf_reset(&used_path);
-		strbuf_reset(&validated_path);
-		strbuf_add(&used_path, path, len);
-		strbuf_add(&validated_path, path, len);
-
-		if (used_path.buf[0] == '~') {
-			char *newpath = interpolate_path(used_path.buf, 0);
-			if (!newpath)
-				return NULL;
-			strbuf_attach(&used_path, newpath, strlen(newpath),
-				      strlen(newpath));
-		}
-		for (i = 0; suffix[i]; i++) {
-			struct stat st;
-			size_t baselen = used_path.len;
-			strbuf_addstr(&used_path, suffix[i]);
-			if (!stat(used_path.buf, &st) &&
-			    (S_ISREG(st.st_mode) ||
-			    (S_ISDIR(st.st_mode) && is_git_directory(used_path.buf)))) {
-				strbuf_addstr(&validated_path, suffix[i]);
-				break;
-			}
-			strbuf_setlen(&used_path, baselen);
-		}
-		if (!suffix[i])
-			return NULL;
-		gitfile = read_gitfile(used_path.buf);
-		if (!(flags & ENTER_REPO_ANY_OWNER_OK))
-			die_upon_dubious_ownership(gitfile, NULL, used_path.buf);
-		if (gitfile) {
-			strbuf_reset(&used_path);
-			strbuf_addstr(&used_path, gitfile);
-		}
-		if (chdir(used_path.buf))
-			return NULL;
-		path = validated_path.buf;
-	}
-	else {
-		const char *gitfile = read_gitfile(path);
-		if (!(flags & ENTER_REPO_ANY_OWNER_OK))
-			die_upon_dubious_ownership(gitfile, NULL, path);
-		if (gitfile)
-			path = gitfile;
-		if (chdir(path))
-			return NULL;
-	}
-
-	if (is_git_directory(".")) {
-		set_git_dir(".", 0);
-		check_repository_format(NULL);
-		return path;
-	}
-
-	return NULL;
-}
-
 int calc_shared_perm(struct repository *repo,
 		     int mode)
 {
 	int tweak;
-
-	if (repo_settings_get_shared_repository(repo) < 0)
-		tweak = -repo_settings_get_shared_repository(repo);
+	int shared_repo = repo_settings_get_shared_repository(repo);
+	if (shared_repo < 0)
+		tweak = -shared_repo;
 	else
-		tweak = repo_settings_get_shared_repository(repo);
+		tweak = shared_repo;
 
 	if (!(mode & S_IWUSR))
 		tweak &= ~0222;
 	if (mode & S_IXUSR)
 		/* Copy read bits to execute bits */
 		tweak |= (tweak & 0444) >> 2;
-	if (repo_settings_get_shared_repository(repo) < 0)
+	if (shared_repo < 0)
 		mode = (mode & ~0777) | tweak;
 	else
 		mode |= tweak;
@@ -1212,6 +1110,14 @@ const char *remove_leading_path(const char *in, const char *prefix)
  * end with a '/', then the callers need to be fixed up accordingly.
  *
  */
+
+static const char *skip_slashes(const char *p)
+{
+	while (is_dir_sep(*p))
+		p++;
+	return p;
+}
+
 int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
 {
 	char *dst0;
@@ -1229,8 +1135,7 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
 	}
 	dst0 = dst;
 
-	while (is_dir_sep(*src))
-		src++;
+	src = skip_slashes(src);
 
 	for (;;) {
 		char c = *src;
@@ -1250,8 +1155,7 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
 			} else if (is_dir_sep(src[1])) {
 				/* (2) */
 				src += 2;
-				while (is_dir_sep(*src))
-					src++;
+				src = skip_slashes(src);
 				continue;
 			} else if (src[1] == '.') {
 				if (!src[2]) {
@@ -1261,8 +1165,7 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
 				} else if (is_dir_sep(src[2])) {
 					/* (4) */
 					src += 3;
-					while (is_dir_sep(*src))
-						src++;
+					src = skip_slashes(src);
 					goto up_one;
 				}
 			}
@@ -1282,6 +1185,8 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
 
 	up_one:
 		/*
+		 * strip the last component
+		 *
 		 * dst0..dst is prefix portion, and dst[-1] is '/';
 		 * go up one level.
 		 */
diff --git a/path.h b/path.h
index e67348f..cbcad25 100644
--- a/path.h
+++ b/path.h
@@ -66,13 +66,11 @@ const char *repo_git_path_replace(struct repository *repo,
 
 /*
  * Similar to repo_git_path() but can produce paths for a specified
- * worktree instead of current one. When no worktree is given, then the path is
- * computed relative to main worktree of the given repository.
+ * worktree instead of current one.
  */
-const char *worktree_git_path(struct repository *r,
-			      const struct worktree *wt,
+const char *worktree_git_path(const struct worktree *wt,
 			      const char *fmt, ...)
-	__attribute__((format (printf, 3, 4)));
+	__attribute__((format (printf, 2, 3)));
 
 /*
  * The `repo_worktree_path` family of functions will construct a path into a
@@ -146,21 +144,6 @@ int adjust_shared_perm(struct repository *repo, const char *path);
 
 char *interpolate_path(const char *path, int real_home);
 
-/* The bits are as follows:
- *
- * - ENTER_REPO_STRICT: callers that require exact paths (as opposed
- *   to allowing known suffixes like ".git", ".git/.git" to be
- *   omitted) can set this bit.
- *
- * - ENTER_REPO_ANY_OWNER_OK: callers that are willing to run without
- *   ownership check can set this bit.
- */
-enum {
-	ENTER_REPO_STRICT = (1<<0),
-	ENTER_REPO_ANY_OWNER_OK = (1<<1),
-};
-
-const char *enter_repo(const char *path, unsigned flags);
 const char *remove_leading_path(const char *in, const char *prefix);
 const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);
 int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
diff --git a/pkt-line.c b/pkt-line.c
index fc583fe..3fc3e9e 100644
--- a/pkt-line.c
+++ b/pkt-line.c
@@ -384,10 +384,10 @@ int packet_length(const char lenbuf_hex[4], size_t size)
 		hexval(lenbuf_hex[3]);
 }
 
-static char *find_packfile_uri_path(const char *buffer)
+static const char *find_packfile_uri_path(const char *buffer)
 {
 	const char *URI_MARK = "://";
-	char *path;
+	const char *path;
 	int len;
 
 	/* First char is sideband mark */
@@ -417,7 +417,7 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,
 {
 	int len;
 	char linelen[4];
-	char *uri_path_start;
+	const char *uri_path_start;
 
 	if (get_packet_data(fd, src_buffer, src_len, linelen, 4, options) < 0) {
 		*pktlen = -1;
diff --git a/po/bg.po b/po/bg.po
index 047febf..aa96f8b 100644
--- a/po/bg.po
+++ b/po/bg.po
@@ -1,7 +1,7 @@
 # Bulgarian translation of git po-file.
-# Copyright (C) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025 Alexander Shopov <ash@kambanaria.org>.
+# Copyright (C) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026 Alexander Shopov <ash@kambanaria.org>.
 # This file is distributed under the same license as the git package.
-# Alexander Shopov <ash@kambanaria.org>, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025.
+# Alexander Shopov <ash@kambanaria.org>, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026.
 # ========================
 # DICTIONARY TO MERGE IN GIT GUI
 # ------------------------
@@ -244,6 +244,15 @@
 # initial branch първоначален клон
 # garbage повредени данни
 # keep file файл за запазване на директория
+# commit transaction завършвам транзакция
+# ref transaction транзакция за указатели
+# refspec указател на версия
+# stage добавям в индекса
+# unstage изваждам от индекса
+# upstream branch следен клон
+# revert отменям
+#
+#
 #
 # ------------------------
 # „$var“ - може да не сработва за shell има gettext и eval_gettext - проверка - намират се лесно по „$
@@ -270,10 +279,10 @@
 # for i in `sort -u FILES`; do cnt=`grep $i FILES | wc -l`; echo $cnt $i ;done | sort -n
 msgid ""
 msgstr ""
-"Project-Id-Version: git v2.52.0-rc1\n"
+"Project-Id-Version: git v2.53.0-rc0\n"
 "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2025-11-06 23:58+0000\n"
-"PO-Revision-Date: 2025-11-09 18:23+0100\n"
+"POT-Creation-Date: 2026-01-23 18:13+0100\n"
+"PO-Revision-Date: 2026-01-24 11:18+0100\n"
 "Last-Translator: Alexander Shopov <ash@kambanaria.org>\n"
 "Language-Team: Bulgarian <dict@fsa-bg.org>\n"
 "Language: bg\\n\"\n"
@@ -1053,7 +1062,7 @@
 "modifications.\n"
 msgstr ""
 "Следните пътища са преместени извън дефинициите за частично\n"
-"изтегляне, но вече не са частични заради локални промени.\n"
+"изтегляне, но вече не са частични заради локални промѐни.\n"
 
 msgid ""
 "To correct the sparsity of these paths, do the following:\n"
@@ -2051,11 +2060,11 @@
 msgid "'%s' is not a valid branch name"
 msgstr "„%s“ не е позволено име за клон"
 
-msgid "See `man git check-ref-format`"
+msgid "See 'git help check-ref-format'"
 msgstr ""
-"Вижте страницата в ръководството:\n"
+"Вижте:\n"
 "\n"
-"    git check-ref-format"
+"    git help check-ref-format"
 
 #, c-format
 msgid "a branch named '%s' already exists"
@@ -2988,6 +2997,18 @@
 msgstr "трябва да завършва с цвят"
 
 #, c-format
+msgid "unknown value for config '%s': %s"
+msgstr "непозната стойност за настройката „%s“: „%s“"
+
+msgid ""
+"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and "
+"\"histogram\""
+msgstr ""
+"опцията приема следните варианти за алгоритъм за разлики: „myers“ (по "
+"Майерс), „minimal“ (минимизиране на разликите), „patience“ (пасианс) и "
+"„histogram“ (хистограмен)"
+
+#, c-format
 msgid "cannot find revision %s to ignore"
 msgstr "версията за прескачане „%s“ липсва"
 
@@ -3053,6 +3074,12 @@
 msgid "ignore whitespace differences"
 msgstr "без разлики в знаците за интервали"
 
+msgid "<algorithm>"
+msgstr "АЛГОРИТЪМ"
+
+msgid "choose a diff algorithm"
+msgstr "избор на АЛГОРИТЪМа за разлики"
+
 msgid "rev"
 msgstr "ВЕРС"
 
@@ -3069,7 +3096,7 @@
 msgid "color lines by age"
 msgstr "оцветяване на редовете по възраст"
 
-msgid "spend extra cycles to find better match"
+msgid "spend extra cycles to find a better match"
 msgstr "допълнителни изчисления за по-добри резултати"
 
 msgid "use revisions from <file> instead of calling git-rev-list"
@@ -3301,13 +3328,13 @@
 msgstr "да не се ползва"
 
 msgid "upstream"
-msgstr "клон-източник"
+msgstr "КЛОН_ИЗТОЧНИК"
 
 msgid "change the upstream info"
 msgstr "смяна на клона-източник"
 
 msgid "unset the upstream info"
-msgstr "изчистване на информацията за клон-източник"
+msgstr "изчистване на информацията за КЛОНа_ИЗТОЧНИК"
 
 msgid "use colored output"
 msgstr "цветен изход"
@@ -4287,8 +4314,8 @@
 msgstr "липсва име на клон, използвайте опцията „-%c“"
 
 #, c-format
-msgid "could not resolve %s"
-msgstr "„%s“ не може да се проследи"
+msgid "could not resolve '%s'"
+msgstr "„%s“ липсва"
 
 msgid "invalid path specification"
 msgstr "указан е неправилен път"
@@ -4696,7 +4723,7 @@
 msgstr "задаване на настройките на новото хранилище"
 
 msgid "server-specific"
-msgstr "специфични за сървъра"
+msgstr "СПЕЦИФИЧНИ_ЗА_СЪРВЪРА"
 
 msgid "option to transmit"
 msgstr "опция за пренос"
@@ -4944,7 +4971,7 @@
 
 msgid "maximum ratio between two levels of a split commit-graph"
 msgstr ""
-"максимално отношение на броя подавания в две последователни нива в раздробен "
+"максимално отношение на броя подавания в две последователни нива̀ в раздробен "
 "граф"
 
 msgid "only expire files older than a given date-time"
@@ -5578,7 +5605,8 @@
 "<name>"
 msgstr ""
 "git config get [ОПЦИЯ_ЗА_ФАЙЛ] [ОПЦИЯ_ЗА_ИЗВЕЖДАНЕ] [--includes] [--all] [--"
-"regexp=РЕГ_ИЗР][--value=ШАБЛОН] [--fixed-value] [--default=СТАНДАРТНО] ИМЕ"
+"regexp=РЕГУЛЯРЕН_ИЗРАЗ] [--value=ШАБЛОН] [--fixed-value] [--"
+"default=СТАНДАРТНО] ИМЕ"
 
 msgid ""
 "git config set [<file-option>] [--type=<type>] [--comment=<message>] [--all] "
@@ -5814,21 +5842,31 @@
 #, c-format
 msgid ""
 "cannot overwrite multiple values with a single value\n"
-"       Use a regexp, --add or --replace-all to change %s."
+"       Use --value=<pattern>, --append or --all to change %s."
 msgstr ""
-"множество стойности не може да се замени с една.\n"
-"За да променѝте „%s“, ползвайте регулярен израз или опциите „--add“ и „--"
-"replace-all“."
+"множество стойности не може да се заменят с една.\n"
+"    За да променѝте „%s“, ползвайте  --value=РЕГУЛЯРЕН_ИЗРАЗ, „--append“ или "
+"„--all“."
+
+msgid "unset all multi-valued config options"
+msgstr ""
+"изчистване на всички стойности за конфигурационните опции, които поддържат "
+"много стойности"
+
+msgid "unset multi-valued config options with matching values"
+msgstr ""
+"изчистване на всички напасващи стойности за конфигурационните опции, които "
+"поддържат много стойности"
 
 #, c-format
 msgid "no such section: %s"
 msgstr "такъв раззел няма: %s"
 
 msgid "editing stdin is not supported"
-msgstr "не се поддържа редактиране на стандартния вход"
+msgstr "редактирането на стандартния вход не се поддържа"
 
 msgid "editing blobs is not supported"
-msgstr "не се поддържа редактиране на обекти-BLOB"
+msgstr "редактирането на обекти-BLOB не се поддържа"
 
 #, c-format
 msgid "cannot create configuration file %s"
@@ -5910,6 +5948,15 @@
 "опцията „--comment“ е съвместима само с действията „add“ (добавяне)/„set“ "
 "(задаване)/„replace“ (замяна)"
 
+#, c-format
+msgid ""
+"cannot overwrite multiple values with a single value\n"
+"       Use a regexp, --add or --replace-all to change %s."
+msgstr ""
+"множество стойности не може да се замени с една.\n"
+"За да променѝте „%s“, ползвайте регулярен израз или опциите „--add“ и „--"
+"replace-all“."
+
 msgid "print sizes in human readable format"
 msgstr "извеждане на размерите на обектите във формат четим от хора"
 
@@ -6119,16 +6166,16 @@
 msgstr "опцията „-z“ е задължителна"
 
 msgid "pathspec arguments not supported"
-msgstr "не се поддържат аргументи с шаблони за пътища"
+msgstr "аргументи с шаблони за пътища не се поддържат"
 
 msgid "revision arguments not allowed"
-msgstr "не се поддържат опции за версии"
+msgstr "опциите за версии не се поддържат"
 
 msgid "invalid raw diff input"
 msgstr "неправилен необработен вход за разлики"
 
 msgid "tree objects not supported"
-msgstr "не се поддържат обекти-дървета"
+msgstr "обектите-дървета не се поддържат"
 
 msgid "got EOF while reading path"
 msgstr "край на файл (EOF) при изчитане на път"
@@ -6325,12 +6372,6 @@
 "reencode=[yes|no]“ за обработка"
 
 #, c-format
-msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it"
-msgstr ""
-"получено е подписано подаване „%s“ — ползвайте „--signed-commits=РЕЖИМ“ за "
-"обработка"
-
-#, c-format
 msgid "exporting %<PRIuMAX> signature(s) for commit %s"
 msgstr "изнасят се %<PRIuMAX> подпис(а) за подаването „%s“"
 
@@ -6339,6 +6380,19 @@
 msgstr "пропускане на подписите към подаването „%s“"
 
 #, c-format
+msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it"
+msgstr ""
+"получено е подписано подаване „%s“ — ползвайте „--signed-commits=РЕЖИМ“ за "
+"обработка"
+
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-export with --signed-"
+"commits=<mode>"
+msgstr ""
+"„strip-if-invalid“ е грешен РЕЖИМ за „git fast-export“ с опцията „--signed-"
+"commits=РЕЖИМ“"
+
+#, c-format
 msgid ""
 "omitting tag %s,\n"
 "since tags of trees (or tags of tags of trees, etc.) are not supported."
@@ -6352,11 +6406,6 @@
 msgstr "етикетът „%s“ не може да се прочете"
 
 #, c-format
-msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it"
-msgstr ""
-"получен е подписан етикет „%s“ — ползвайте „--signed-tags=РЕЖИМ“ за обработка"
-
-#, c-format
 msgid "exporting signed tag %s"
 msgstr "изнасяне на подписан етикет „%s“"
 
@@ -6365,6 +6414,18 @@
 msgstr "пропускане на подписите към етикета „%s“"
 
 #, c-format
+msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it"
+msgstr ""
+"получен е подписан етикет „%s“ — ползвайте „--signed-tags=РЕЖИМ“ за обработка"
+
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-export with --signed-"
+"tags=<mode>"
+msgstr ""
+"„strip-if-invalid“ е грешен РЕЖИМ за „git fast-export“ с опцията „--signed-"
+"tags=РЕЖИМ“"
+
+#, c-format
 msgid ""
 "tag %s tags unexported object; use --tag-of-filtered-object=<mode> to handle "
 "it"
@@ -6756,6 +6817,33 @@
 msgid "parse_one_signature() returned unknown hash algo"
 msgstr "„parse_one_signature()“ върна непознат алгоритъм за контролна сума"
 
+msgid "unknown"
+msgstr "непознат"
+
+#, c-format
+msgid ""
+"stripping invalid signature for commit '%.100s...'\n"
+"  allegedly by %s"
+msgstr ""
+"пропускане на грешния подпис към подаването „%.100s…“,\n"
+"  вероятно е на %s"
+
+#, c-format
+msgid ""
+"stripping invalid signature for commit '%.*s'\n"
+"  allegedly by %s"
+msgstr ""
+"пропускане на грешния подпис към подаването „%.*s“,\n"
+"  вероятно е на %s"
+
+#, c-format
+msgid ""
+"stripping invalid signature for commit\n"
+"  allegedly by %s"
+msgstr ""
+"пропускане на грешен подпис към подаване,\n"
+"  вероятно е на %s"
+
 msgid "expected committer but didn't get one"
 msgstr "очакваше се подаващ, но такъв липсва"
 
@@ -6770,10 +6858,6 @@
 msgid "importing a commit signature verbatim"
 msgstr "дословно внасяне на подпис на подаване"
 
-msgid "encountered signed tag; use --signed-tags=<mode> to handle it"
-msgstr ""
-"получен е подписан етикет — ползвайте „--signed-tags=РЕЖИМ“ за обработка"
-
 #, c-format
 msgid "importing a tag signature verbatim for tag '%s'"
 msgstr "дословно внасяне на подпис на етикет за „%s“"
@@ -6782,6 +6866,17 @@
 msgid "stripping a tag signature for tag '%s'"
 msgstr "пропускане на подписа на етикета „%s“"
 
+msgid "encountered signed tag; use --signed-tags=<mode> to handle it"
+msgstr ""
+"получен е подписан етикет — ползвайте „--signed-tags=РЕЖИМ“ за обработка"
+
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-import with --signed-"
+"tags=<mode>"
+msgstr ""
+"„strip-if-invalid“ е грешен РЕЖИМ за „git fast-import“ с опцията „--signed-"
+"tags=РЕЖИМ“"
+
 #, c-format
 msgid "expected 'from' command, got '%s'"
 msgstr "очаква се команда „from“, а бе получена: „%s“"
@@ -6941,7 +7036,7 @@
 msgid "git fetch [<options>] <group>"
 msgstr "git fetch [ОПЦИЯ…] ГРУПА"
 
-msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]"
+msgid "git fetch --multiple [<options>] [(<repository>|<group>)...]"
 msgstr "git fetch --multiple [ОПЦИЯ…] [(ХРАНИЛИЩЕ|ГРУПА)…]"
 
 msgid "git fetch --all [<options>]"
@@ -7188,7 +7283,7 @@
 msgstr "изискване на атомарни операции за обновяване на указателите"
 
 msgid "path to upload pack on remote end"
-msgstr "отдалечен път, където да се качи пакетът"
+msgstr "отдалечен ПЪТ, където да се качи пакетът"
 
 msgid "force overwrite of local reference"
 msgstr "принудително презаписване на локален указател"
@@ -7219,7 +7314,7 @@
 "хранилище и презаписване на променените"
 
 msgid "on-demand"
-msgstr "ПРИ НУЖДА"
+msgstr "ПРИ_НУЖДА"
 
 msgid "control recursive fetching of submodules"
 msgstr "управление на рекурсивното доставяне на подмодулите"
@@ -7436,9 +7531,6 @@
 msgid "got bad config --config=%s"
 msgstr "получена е неправилна настройка „--config=%s“"
 
-msgid "unknown"
-msgstr "непознат"
-
 #. TRANSLATORS: e.g. error in tree 01bfda: <more explanation>
 #, c-format
 msgid "error in %s %s: %s"
@@ -7533,8 +7625,12 @@
 msgid "%s: not a commit"
 msgstr "%s: не е подаване!"
 
+#, c-format
+msgid "invalid parameter: expected sha1, got '%s'"
+msgstr "неправилен параметър: очаква се SHA1, а бе получено: „%s“"
+
 msgid "notice: No default references"
-msgstr "внимание: няма указатели по подразбиране"
+msgstr "ВНИМАНИЕ: няма указатели по подразбиране"
 
 #, c-format
 msgid "%s: hash-path mismatch, found at: %s"
@@ -7559,26 +7655,6 @@
 msgstr "Проверка на директориите с обекти"
 
 #, c-format
-msgid "Checking %s link"
-msgstr "Проверка на връзките на „%s“"
-
-#, c-format
-msgid "invalid %s"
-msgstr "неправилен указател „%s“"
-
-#, c-format
-msgid "%s points to something strange (%s)"
-msgstr "„%s“ сочи към нещо необичайно (%s)"
-
-#, c-format
-msgid "%s: detached HEAD points at nothing"
-msgstr "%s: отделеният връх „HEAD“ не сочи към нищо"
-
-#, c-format
-msgid "notice: %s points to an unborn branch (%s)"
-msgstr "предупреждение: „%s“ сочи към неродѐн клон (%s)"
-
-#, c-format
 msgid "Checking cache tree of %s"
 msgstr "Проверка на кеша на обектите-дървета на „%s“"
 
@@ -7657,14 +7733,6 @@
 msgid "Checking objects"
 msgstr "Проверка на обектите"
 
-#, c-format
-msgid "%s: object missing"
-msgstr "„%s“: липсващ обект"
-
-#, c-format
-msgid "invalid parameter: expected sha1, got '%s'"
-msgstr "неправилен параметър: очаква се SHA1, а бе получено: „%s“"
-
 msgid "git fsmonitor--daemon start [<options>]"
 msgstr "git fsmonitor--daemon start [ОПЦИЯ…]"
 
@@ -8056,6 +8124,9 @@
 msgid "failed to add repo to global config"
 msgstr "неуспешно добавяне на хранилище към файла с глобални настройки"
 
+msgid "check a specific task"
+msgstr "проверка на определена задача"
+
 msgid "git maintenance <subcommand> [<options>]"
 msgstr "git maintenance ПОДКОМАНДА [ОПЦИЯ…]"
 
@@ -8467,7 +8538,7 @@
 
 #, c-format
 msgid "pack version %<PRIu32> unsupported"
-msgstr "не се поддържа пакетиране вeрсия „%<PRIu32>“"
+msgstr "пакетирането на вeрсия „%<PRIu32>“ не се поддържа"
 
 #, c-format
 msgid "pack has bad object at offset %<PRIuMAX>: %s"
@@ -8520,6 +8591,10 @@
 msgstr "грешка при проверката на пакетирани обекти"
 
 #, c-format
+msgid "invalid %s"
+msgstr "неправилен указател „%s“"
+
+#, c-format
 msgid "Not all child objects of %s are reachable"
 msgstr "Някои обекти, наследници на „%s“, не може да бъдат достигнати"
 
@@ -8665,6 +8740,9 @@
 msgid "--verify with no packfile name given"
 msgstr "опцията „--verify“ изисква име на пакетен файл"
 
+msgid "cannot perform queued object checks outside of a repository"
+msgstr "проверките на обекта в опашката не може да се изпълнят извън хранилище"
+
 msgid "fsck error in pack objects"
 msgstr "грешка при проверка с „fsck“ на пакетните обекти"
 
@@ -9442,14 +9520,6 @@
 "git merge-file [ОПЦИЯ…] [-L ИМЕ_1 [-L ОРИГИНАЛ [-L ИМЕ_2]]] ФАЙЛ_1 ОРИГ_ФАЙЛ "
 "ФАЙЛ_2"
 
-msgid ""
-"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and "
-"\"histogram\""
-msgstr ""
-"опцията приема следните варианти за алгоритъм за разлики: „myers“ (по "
-"Майерс), „minimal“ (минимизиране на разликите), „patience“ (пасианс) и "
-"„histogram“ (хистограмен)"
-
 msgid "send results to standard output"
 msgstr "извеждане на резултатите на стандартния изход"
 
@@ -9462,12 +9532,6 @@
 msgid "use a zealous diff3 based merge"
 msgstr "засилено сливане на базата на „diff3“"
 
-msgid "<algorithm>"
-msgstr "АЛГОРИТЪМ"
-
-msgid "choose a diff algorithm"
-msgstr "избор на АЛГОРИТЪМа за разлики"
-
 msgid "for conflicts, use this marker size"
 msgstr "при конфликти да се ползва маркер с такъв БРОЙ знаци"
 
@@ -9860,7 +9924,7 @@
 
 #, c-format
 msgid "When finished, apply stashed changes with `git stash pop`\n"
-msgstr "При завършване скатаните промени да се приложат с „git stash pop“\n"
+msgstr "При завършване скатаните промѐни да се приложат с „git stash pop“\n"
 
 #, c-format
 msgid "warning: tag input does not pass fsck: %s"
@@ -10572,6 +10636,11 @@
 msgstr "видът на обекта „%s“ в пакет „%s“ не може да се определи"
 
 #, c-format
+msgid "packfile %s is a promisor but --exclude-promisor-objects was given"
+msgstr ""
+"пакетът „%s“ е гарантиращ, но е зададена опцията „--exclude-promisor-objects“"
+
+#, c-format
 msgid "could not find pack '%s'"
 msgstr "пакетът „%s“ не може да се открие"
 
@@ -10850,11 +10919,11 @@
 msgid "git patch-id [--stable | --unstable | --verbatim]"
 msgstr "git patch-id [--stable|--unstable|--verbatim]"
 
-msgid "use the unstable patch-id algorithm"
-msgstr "използване на нестабилния алгоритъм за идентифициране на кръпка"
+msgid "use the unstable patch ID algorithm"
+msgstr "използване на нестабилния алгоритъм за идентифициране на кръпки"
 
-msgid "use the stable patch-id algorithm"
-msgstr "използване на стабилния алгоритъм за идентифициране на кръпка"
+msgid "use the stable patch ID algorithm"
+msgstr "използване на стабилния алгоритъм за идентифициране на кръпки"
 
 msgid "don't strip whitespace from the patch"
 msgstr "без махане на празните знаци в кръпката"
@@ -10877,41 +10946,6 @@
 msgid "git pull [<options>] [<repository> [<refspec>...]]"
 msgstr "git push [ОПЦИЯ…] [ХРАНИЛИЩЕ [УКАЗАТЕЛ_НА_ВЕРСИЯ…]]"
 
-msgid "control for recursive fetching of submodules"
-msgstr "управление на рекурсивното доставяне на подмодулите"
-
-msgid "Options related to merging"
-msgstr "Опции при сливане"
-
-msgid "incorporate changes by rebasing rather than merging"
-msgstr "внасяне на промѐните чрез пребазиране, а не чрез сливане"
-
-msgid "allow fast-forward"
-msgstr "позволяване на превъртания"
-
-msgid "control use of pre-merge-commit and commit-msg hooks"
-msgstr ""
-"дали куките преди подаване и сливане и при промяна на съобщението за "
-"подаване (pre-merge-commit и commit-msg) да се изпълнят"
-
-msgid "automatically stash/stash pop before and after"
-msgstr "автоматично скатаване/прилагане на скатаното преди и след пребазиране"
-
-msgid "Options related to fetching"
-msgstr "Опции при доставяне"
-
-msgid "force overwrite of local branch"
-msgstr "принудително презаписване на локалния клон"
-
-msgid "number of submodules pulled in parallel"
-msgstr "брой подмодули издърпани паралелно"
-
-msgid "use IPv4 addresses only"
-msgstr "само адреси IPv4"
-
-msgid "use IPv6 addresses only"
-msgstr "само адреси IPv6"
-
 msgid ""
 "There is no candidate for rebasing against among the refs that you just "
 "fetched."
@@ -11016,6 +11050,41 @@
 "използвайте опциите „--rebase“, „--no-rebase“, „--ff-only“.  Те са с\n"
 "приоритет пред настройките.\n"
 
+msgid "control for recursive fetching of submodules"
+msgstr "управление на рекурсивното доставяне на подмодулите"
+
+msgid "Options related to merging"
+msgstr "Опции при сливане"
+
+msgid "incorporate changes by rebasing rather than merging"
+msgstr "внасяне на промѐните чрез пребазиране, а не чрез сливане"
+
+msgid "allow fast-forward"
+msgstr "позволяване на превъртания"
+
+msgid "control use of pre-merge-commit and commit-msg hooks"
+msgstr ""
+"дали куките преди подаване и сливане и при промяна на съобщението за "
+"подаване (pre-merge-commit и commit-msg) да се изпълнят"
+
+msgid "automatically stash/stash pop before and after"
+msgstr "автоматично скатаване/прилагане на скатаното преди и след пребазиране"
+
+msgid "Options related to fetching"
+msgstr "Опции при доставяне"
+
+msgid "force overwrite of local branch"
+msgstr "принудително презаписване на локалния клон"
+
+msgid "number of submodules pulled in parallel"
+msgstr "брой подмодули издърпани паралелно"
+
+msgid "use IPv4 addresses only"
+msgstr "само адреси IPv4"
+
+msgid "use IPv6 addresses only"
+msgstr "само адреси IPv6"
+
 msgid "Updating an unborn branch with changes added to the index."
 msgstr "Обновяване на неродѐн клон с промѐните от индекса"
 
@@ -11247,7 +11316,7 @@
 "See the 'Note about fast-forwards' in 'git push --help' for details."
 msgstr ""
 "Обновяването е отхвърлено, защото върхът на отдалечения клон съдържа "
-"промени\n"
+"промѐни\n"
 "след последното изтегляне.  Внесете отдалечените промѐни (напр. с командата\n"
 "„git pull…“), преди отново да изтласкате промѐните.\n"
 "За повече информация вижте раздела „Note about fast-forwards“ в страницата\n"
@@ -11266,7 +11335,7 @@
 "instead"
 msgstr ""
 "рекурсивно обхождане на подмодулите чрез „push.recurseSubmodules=only“.  "
-"Вместо това се ползва ПРИ НУЖДА"
+"Вместо това се ползва ПРИ_НУЖДА"
 
 #, c-format
 msgid "invalid value for '%s'"
@@ -12941,6 +13010,10 @@
 msgid "only one pattern can be given with -l"
 msgstr "опцията „-l“ приема точно един шаблон"
 
+#, c-format
+msgid "'%s' is not a valid commit-ish for %s"
+msgstr "„%s“ не прилича на подаване за %s"
+
 msgid "need some commits to replay"
 msgstr "необходимо е да има подавания за прилагане отново"
 
@@ -12957,27 +13030,16 @@
 "цели с множество източници не може да се придвижат напред, защото подредбата "
 "не е добре дефинирана"
 
-msgid ""
-"cannot implicitly determine whether this is an --advance or --onto operation"
-msgstr ""
-"не може да се определи дали това действие е за „--advance“ или „--onto“"
-
-msgid ""
-"cannot advance target with multiple source branches because ordering would "
-"be ill-defined"
-msgstr ""
-"цели с множество клони-източници не може да се придвижат напред, защото "
-"подредбата не е добре дефинирана"
-
-msgid "cannot implicitly determine correct base for --onto"
-msgstr "правилната база за „--onto“ не може да се определи"
+#, c-format
+msgid "invalid %s value: '%s'"
+msgstr "неправилна стойност за „%s“: „%s“"
 
 msgid ""
 "(EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance "
-"<branch>) <revision-range>..."
+"<branch>) [--ref-action[=<mode>]] <revision-range>"
 msgstr ""
 "(ЕКСПЕРИМЕНТАЛНО!) git replay ([--contained] --onto НОВА_БАЗА | --advance "
-"КЛОН) ДИАПАЗОН_ПОДАВАНИЯ…"
+"КЛОН) [--ref-action[=РЕЖИМ]] ДИАПАЗОН_ПОДАВАНИЯ…"
 
 msgid "make replay advance given branch"
 msgstr "прилагането наново придвижва дадения КЛОН напред"
@@ -12985,8 +13047,14 @@
 msgid "replay onto given commit"
 msgstr "прилагането наново върху даденото ПОДАВАНЕ"
 
-msgid "advance all branches contained in revision-range"
-msgstr "придвижване на всички КЛОНи в ДИАПАЗОНа_ПОДАВАНИЯ"
+msgid "update all branches that point at commits in <revision-range>"
+msgstr ""
+"обновяване на всички клони, които сочат към подавания в ДИАПАЗОНа_ПОДАВАНИЯ"
+
+msgid "control ref update behavior (update|print)"
+msgstr ""
+"поведение при обновяването на указател: „update“ (обновяване)/„print“ "
+"(извеждане)"
 
 msgid "option --onto or --advance is mandatory"
 msgstr "изисква се някоя от опциите „--onto“ или „--advance“"
@@ -12999,14 +13067,26 @@
 "някои опции за проследяване на указатели ще бъдат променени, защото битът "
 "„%s“ в структурата „struct rev_info“ има превес"
 
+#, c-format
+msgid "failed to begin ref transaction: %s"
+msgstr "неуспешно начало на транзакция за указатели: %s"
+
 msgid "error preparing revisions"
 msgstr "грешка при подготовката на версии"
 
-msgid "replaying down to root commit is not supported yet!"
-msgstr "не се поддържа прилагане наново и на началното подаване!"
+msgid "replaying down from root commit is not supported yet!"
+msgstr "прилагането наново от началното подаване не се поддържа"
 
 msgid "replaying merge commits is not supported yet!"
-msgstr "не се поддържа прилагане наново и на подавания със сливане!"
+msgstr "прилагането наново на подавания със сливане не се поддържа!"
+
+#, c-format
+msgid "failed to update ref '%s': %s"
+msgstr "указателят „%s“ не може да се обнови: %s"
+
+#, c-format
+msgid "failed to commit ref transaction: %s"
+msgstr "транзакцията за указатели не може да се завърши: %s"
 
 #, c-format
 msgid "key '%s' not found"
@@ -13022,9 +13102,15 @@
 msgid "synonym for --format=nul"
 msgstr "псевдоним на „--format=nul“"
 
+msgid "print all keys/values"
+msgstr "извеждане на всички ключове/стойности"
+
 msgid "unsupported output format"
 msgstr "неподдържан изходен формат"
 
+msgid "--all and <key> cannot be used together"
+msgstr "опцията „--all“ и изричен КЛЮЧ са несъвместими"
+
 msgid "References"
 msgstr "Указатели"
 
@@ -13055,6 +13141,12 @@
 msgid "Blobs"
 msgstr "Обекти-BLOB"
 
+msgid "Inflated size"
+msgstr "Декомпресиран размер"
+
+msgid "Disk size"
+msgstr "Размер на диска"
+
 msgid "Repository structure"
 msgstr "Структура на хранилището"
 
@@ -14309,7 +14401,7 @@
 
 #, c-format
 msgid "failed to update remote for submodule '%s'"
-msgstr "отдалеченият адрес на подмодула „%s“ не може да се промени"
+msgstr "отдалеченият адрес на подмодула „%s“ не може да се променѝ"
 
 msgid "suppress output of synchronizing submodule url"
 msgstr "без извеждане на информация при синхронизирането на подмодул"
@@ -15604,6 +15696,10 @@
 msgid "bundle list at '%s' has no mode"
 msgstr "липсва режим в списъка от пратки „%s“"
 
+#, c-format
+msgid "bundle list at '%s': bundle '%s' has no uri"
+msgstr "списък от пратки при „%s“: липсва адрес за пратка „%s“"
+
 msgid "failed to create temporary file"
 msgstr "не може да се създаде временен файл"
 
@@ -15627,6 +15723,10 @@
 "преминато е ограничениeто за рекурсивно обхождане на адреси на пратки (%d)"
 
 #, c-format
+msgid "bundle '%s' has no uri"
+msgstr "липсва адрес за пратка „%s“"
+
+#, c-format
 msgid "failed to download bundle from URI '%s'"
 msgstr "неуспешно изтегляне на пратка от адрес: „%s“"
 
@@ -16041,7 +16141,7 @@
 msgstr "Стандартната помощна програма за „git-merge-index“"
 
 msgid "Perform merge without touching index or working tree"
-msgstr "Извършване на сливането без промени по индекса или работното време"
+msgstr "Извършване на сливането без промѐни по индекса или работното време"
 
 msgid "Run merge conflict resolution tools to resolve merge conflicts"
 msgstr "Изпълнение на програмите за коригиране на конфликтите при сливане"
@@ -16135,8 +16235,8 @@
 msgid "Reuse recorded resolution of conflicted merges"
 msgstr "Преизползване на вече запазено коригиране на конфликт при сливане"
 
-msgid "Reset current HEAD to the specified state"
-msgstr "Привеждане на указателя „HEAD“ към зададеното състояние"
+msgid "Set `HEAD` or the index to a known state"
+msgstr "Задаване на указателя „HEAD“ или индекса към известно състояние"
 
 msgid "Restore working tree files"
 msgstr "Възстановяване на файловете в работното дърво"
@@ -16488,7 +16588,7 @@
 msgstr "Изчистване на отбелязванията на подаванията в гра̀фа с подаванията"
 
 msgid "Computing commit graph topological levels"
-msgstr "Изчисляване на топологичните нива в гра̀фа с подаванията"
+msgstr "Изчисляване на топологичните нива̀ в гра̀фа с подаванията"
 
 msgid "Computing commit graph generation numbers"
 msgstr "Изчисляване на номерата на поколенията в гра̀фа с подаванията"
@@ -17717,10 +17817,6 @@
 msgstr "Непозната стойност „%s“ за настройката „diff.submodule“"
 
 #, c-format
-msgid "unknown value for config '%s': %s"
-msgstr "непозната стойност за настройката „%s“: „%s“"
-
-#, c-format
 msgid ""
 "Found errors in 'diff.dirstat' config variable:\n"
 "%s"
@@ -17797,7 +17893,7 @@
 
 #, c-format
 msgid "invalid mode '%s' in --color-moved-ws"
-msgstr "неправилен режим „%s“ за „ --color-moved-ws“"
+msgstr "грешен режим „%s“ за „ --color-moved-ws“"
 
 #, c-format
 msgid "invalid argument to %s"
@@ -18309,7 +18405,7 @@
 
 #, c-format
 msgid "invalid mode for object creation: %s"
-msgstr "неправилен режим за създаването на обекти: %s"
+msgstr "грешен режим за създаването на обекти: %s"
 
 #, c-format
 msgid "malformed value for %s"
@@ -19425,7 +19521,7 @@
 msgstr ""
 " ⁃ преминаване към подмодула (%s), след които или де се слее подаването\n"
 "   „%s“, или да се обнови към съществуващо подаване, в които\n"
-"   тези промени са слети\n"
+"   тези промѐни са слети\n"
 
 #, c-format
 msgid ""
@@ -19923,6 +20019,9 @@
 msgid "%s: failed to insert into database"
 msgstr "„%s“ не може да се вмъкне в базата от данни"
 
+msgid "cannot add a submodule of a different hash algorithm"
+msgstr "не може да добавите подмодул с различен алгоритъм за контролни суми"
+
 #, c-format
 msgid "%s: unsupported file type"
 msgstr "неподдържан вид файл: „%s“"
@@ -20772,7 +20871,7 @@
 msgstr "неуспешно изчистване на буферите при запис на пакет"
 
 msgid "protocol error: impossibly long line"
-msgstr "протоколна грешка: прекалено дълъг ред"
+msgstr "ПРОТОКОЛНА ГРЕШКА: прекалено дълъг ред"
 
 msgid "packet write with format failed"
 msgstr "неуспешен запис на пакет с формат"
@@ -20794,15 +20893,15 @@
 
 #, c-format
 msgid "protocol error: bad line length character: %.4s"
-msgstr "протоколна грешка: неправилeн знак за дължина на ред: %.4s"
+msgstr "ПРОТОКОЛНА ГРЕШКА: неправилeн знак за дължина на ред: %.4s"
 
 #, c-format
 msgid "protocol error: bad line length %d"
-msgstr "протоколна грешка: неправилна дължина на ред: %d"
+msgstr "ПРОТОКОЛНА ГРЕШКА: неправилна дължина на ред: %d"
 
 #, c-format
 msgid "remote error: %s"
-msgstr "отдалечена грешка: %s"
+msgstr "ОТДАЛЕЧЕНА ГРЕШКА: %s"
 
 msgid "Refreshing index"
 msgstr "Обновяване на индекса"
@@ -21436,6 +21535,10 @@
 msgstr ""
 "опцията „--format=%.*s“ е несъвместима с „--python“, „--shell“, „--tcl“"
 
+#, c-format
+msgid "parse_object_buffer failed on %s for %s"
+msgstr "неуспешно анализиране чрез „parse_object_buffer“ на „%s“ за „%s“"
+
 msgid "failed to run 'describe'"
 msgstr "неуспешно изпълнение на „describe“"
 
@@ -21467,10 +21570,6 @@
 msgstr "обектът „%s“ липсва за „%s“"
 
 #, c-format
-msgid "parse_object_buffer failed on %s for %s"
-msgstr "неуспешно анализиране чрез „parse_object_buffer“ на „%s“ за „%s“"
-
-#, c-format
 msgid "malformed object at '%s'"
 msgstr "обект със сгрешен формат при „%s“"
 
@@ -21777,6 +21876,18 @@
 msgstr "името на указател „%s“ е символен указател, не може да се копира"
 
 #, c-format
+msgid "reftable stack for worktree '%s' is broken"
+msgstr "таблицата с указатели за работното дърво в „%s“ е повредена"
+
+#, c-format
+msgid "could not create iterator for worktree '%s'"
+msgstr "не може да се създаде итератор за работното дърво в „%s“"
+
+#, c-format
+msgid "could not read record for worktree '%s'"
+msgstr "не може да се създаде запис за работното дърво в „%s“"
+
+#, c-format
 msgid "pattern '%s' has no '*'"
 msgstr "шаблонът „%s“ не съдържа „*“"
 
@@ -21878,14 +21989,14 @@
 
 #, c-format
 msgid "protocol error: expected sha/ref, got '%s'"
-msgstr "протоколна грешка: очаква се SHA1 или указател, а бе получено: „%s“"
+msgstr "ПРОТОКОЛНА ГРЕШКА: очаква се SHA1 или указател, а бе получено: „%s“"
 
 #, c-format
 msgid "http transport does not support %s"
 msgstr "транспортът по http не поддържа „%s“"
 
 msgid "protocol error: expected '<url> <path>', missing space"
-msgstr "протоколна грешка: очаква се „АДРЕС ПЪТ“, липсва интервал"
+msgstr "ПРОТОКОЛНА ГРЕШКА: очаква се „АДРЕС ПЪТ“, липсва интервал"
 
 #, c-format
 msgid "failed to download file at URL '%s'"
@@ -22212,6 +22323,11 @@
 "командата „pack-objects“ не може да завърши за препакетирането на "
 "гарантиращите обекти"
 
+msgid "could not start pack-objects to repack promisor packs"
+msgstr ""
+"командата „pack-objects“ не може да се стартира за препакетирането на "
+"гарантиращите обекти"
+
 #, c-format
 msgid "pack prefix %s does not begin with objdir %s"
 msgstr "името на пакетния файл „%s“ не започва с директорията за обекти с „%s“"
@@ -22502,7 +22618,7 @@
 msgstr "(enable=включване|disable=изключване|keep=запазване)"
 
 msgid "signal how to adjust background maintenance"
-msgstr "как да се промени поддръжката на заден фон"
+msgstr "как да се променѝ поддръжката на заден фон"
 
 msgid ""
 "scalar reconfigure [--maintenance=(enable|disable|keep)] [--all | "
@@ -23044,7 +23160,7 @@
 "'merge -c' on the commit"
 msgstr ""
 "„reword“ не приема подаване със сливане.  Ако искате да приложите\n"
-"сливането наново и да промените съобщението при подаване, изпълнете\n"
+"сливането наново и да променѝте съобщението при подаване, изпълнете\n"
 "\n"
 "    git merge -C ПОДАВАНЕ"
 
@@ -23265,10 +23381,6 @@
 msgid "illegal label name: '%.*s'"
 msgstr "неправилно име на етикет: „%.*s“"
 
-#, c-format
-msgid "could not resolve '%s'"
-msgstr "„%s“ липсва"
-
 msgid "writing fake root commit"
 msgstr "запазване на фалшиво начално подаване"
 
@@ -23370,7 +23482,7 @@
 msgstr "указателят за автоматично скатано e файл с указател"
 
 msgid "could not detach HEAD"
-msgstr "указателят „HEAD“ не може да се отдели"
+msgstr "указателят „HEAD“ не може да се отделѝ"
 
 #, c-format
 msgid "Stopped at HEAD\n"
@@ -23762,49 +23874,68 @@
 msgid "bad %s format: %%%.*s"
 msgstr "неправилен формат за „%s“: %%%.*s"
 
-#. TRANSLATORS: IEC 80000-13:2008 gibibyte
 #, c-format
-msgid "%u.%2.2u GiB"
-msgstr "%u.%2.2u GiB"
+msgid "%u.%2.2u"
+msgstr "%u.%2.2u"
 
-#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second
-#, c-format
-msgid "%u.%2.2u GiB/s"
-msgstr "%u.%2.2u GiB/s"
+#. TRANSLATORS: SI decimal prefix symbol for 10^9
+msgid "G"
+msgstr "G"
 
-#. TRANSLATORS: IEC 80000-13:2008 mebibyte
-#, c-format
-msgid "%u.%2.2u MiB"
-msgstr "%u.%2.2u MiB"
+#. TRANSLATORS: SI decimal prefix symbol for 10^6
+msgid "M"
+msgstr "M"
 
-#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second
-#, c-format
-msgid "%u.%2.2u MiB/s"
-msgstr "%u.%2.2u MiB/s"
+#. TRANSLATORS: SI decimal prefix symbol for 10^3
+msgid "k"
+msgstr "k"
 
-#. TRANSLATORS: IEC 80000-13:2008 kibibyte
-#, c-format
-msgid "%u.%2.2u KiB"
-msgstr "%u.%2.2u KiB"
+#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte
+msgid "GiB/s"
+msgstr "GiB/s"
 
-#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second
-#, c-format
-msgid "%u.%2.2u KiB/s"
-msgstr "%u.%2.2u KiB/s"
+msgid "GiB"
+msgstr "GiB"
 
-#. TRANSLATORS: IEC 80000-13:2008 byte
-#, c-format
-msgid "%u byte"
-msgid_plural "%u bytes"
-msgstr[0] "%u байт"
-msgstr[1] "%u байта"
+#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte
+msgid "MiB/s"
+msgstr "MiB/s"
+
+msgid "MiB"
+msgstr "MiB"
+
+#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte
+msgid "KiB/s"
+msgstr "KiB/s"
+
+msgid "KiB"
+msgstr "KiB"
+
+#. TRANSLATORS: IEC 80000-13:2008 byte/second and byte
+msgid "B/s"
+msgstr "B/s"
+
+msgid "B"
+msgstr "B"
 
 #. TRANSLATORS: IEC 80000-13:2008 byte/second
+msgid "byte/s"
+msgid_plural "bytes/s"
+msgstr[0] "byte/s"
+msgstr[1] "byte/s"
+
+#. TRANSLATORS: IEC 80000-13:2008 byte
+msgid "byte"
+msgid_plural "bytes"
+msgstr[0] "байт"
+msgstr[1] "байта"
+
+#. TRANSLATORS: The first argument is the number string. The second
+#. argument is the unit string (i.e. "12.34 MiB/s").
+#.
 #, c-format
-msgid "%u byte/s"
-msgid_plural "%u bytes/s"
-msgstr[0] "%u байт/сек."
-msgstr[1] "%u байта/сек."
+msgid "%s %s"
+msgstr "%s %s"
 
 #, c-format
 msgid "ignoring suspicious submodule name: %s"
@@ -23821,7 +23952,7 @@
 
 #, c-format
 msgid "Could not update .gitmodules entry %s"
-msgstr "Записът „%s“ във файла „.gitmodules“ не може да се промени"
+msgstr "Записът „%s“ във файла „.gitmodules“ не може да се променѝ"
 
 msgid "Cannot change unmerged .gitmodules, resolve merge conflicts first"
 msgstr ""
@@ -24102,9 +24233,6 @@
 msgid "number of requests per thread"
 msgstr "брой заявки на нишка"
 
-msgid "byte"
-msgstr "байта"
-
 msgid "ballast character"
 msgstr "знаци за пращане"
 
@@ -24666,16 +24794,16 @@
 msgstr "ГРЕШКА: съобщението не може да се форматира: %s\n"
 
 msgid "usage: "
-msgstr "употреба: "
+msgstr "УПОТРЕБА: "
 
 msgid "fatal: "
-msgstr "фатална грешка: "
+msgstr "ФАТАЛНА ГРЕШКА: "
 
 msgid "error: "
 msgstr "ГРЕШКА: "
 
 msgid "warning: "
-msgstr "предупреждение: "
+msgstr "ПРЕДУПРЕЖДЕНИЕ: "
 
 #, c-format
 msgid "'%s' is nominated for removal.\n"
@@ -25611,7 +25739,7 @@
 
 #, perl-format
 msgid "Outlook reassigned Message-ID to: %s\n"
-msgstr "Outlook промени „Message-ID“ да е: %s\n"
+msgstr "Outlook променѝ „Message-ID“ да е: %s\n"
 
 msgid "Warning: Could not retrieve Message-ID from server response.\n"
 msgstr "ПРЕДУПРЕЖДЕНИЕ: в отговора на сървъра липсва „Message-ID“.\n"
diff --git a/po/fr.po b/po/fr.po
index 6364071..780bc7b 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -87,8 +87,8 @@
 msgstr ""
 "Project-Id-Version: git\n"
 "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2025-11-07 21:01+0100\n"
-"PO-Revision-Date: 2025-11-09 14:56+0100\n"
+"POT-Creation-Date: 2026-01-22 23:57+0000\n"
+"PO-Revision-Date: 2026-01-28 19:43+0100\n"
 "Last-Translator: Cédric Malard <c.malard-git@valdun.net>\n"
 "Language-Team: Jean-Noël Avila <jn.avila@free.fr>\n"
 "Language: fr\n"
@@ -1851,8 +1851,8 @@
 msgid "'%s' is not a valid branch name"
 msgstr "'%s' n'est pas un nom de branche valide"
 
-msgid "See `man git check-ref-format`"
-msgstr "Voir `man git check-ref-format`"
+msgid "See 'git help check-ref-format'"
+msgstr "Voir 'git help check-ref-format'"
 
 #, c-format
 msgid "a branch named '%s' already exists"
@@ -2766,6 +2766,17 @@
 msgstr "doit finir avec une couleur"
 
 #, c-format
+msgid "unknown value for config '%s': %s"
+msgstr "valeur inconnue pour la config '%s' : %s"
+
+msgid ""
+"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and "
+"\"histogram\""
+msgstr ""
+"l'option diff-algorithm accept \"myers\", \"minimal\", \"patience\" et "
+"\"histogram\""
+
+#, c-format
 msgid "cannot find revision %s to ignore"
 msgstr "référence à ignorer %s introuvable"
 
@@ -2822,6 +2833,12 @@
 msgid "ignore whitespace differences"
 msgstr "ignorer les différences d'espace"
 
+msgid "<algorithm>"
+msgstr "<algorithme>"
+
+msgid "choose a diff algorithm"
+msgstr "choisir un algorithme de différence"
+
 msgid "rev"
 msgstr "rév"
 
@@ -2838,7 +2855,7 @@
 msgid "color lines by age"
 msgstr "colorier les lignes par âge"
 
-msgid "spend extra cycles to find better match"
+msgid "spend extra cycles to find a better match"
 msgstr ""
 "dépenser des cycles supplémentaires pour trouver une meilleure correspondance"
 
@@ -4054,8 +4071,8 @@
 msgstr "nom de branche manquant ; essayez -%c"
 
 #, c-format
-msgid "could not resolve %s"
-msgstr "impossible de résoudre %s"
+msgid "could not resolve '%s'"
+msgstr "impossible de résoudre '%s'"
 
 msgid "invalid path specification"
 msgstr "spécification de chemin invalide"
@@ -5561,10 +5578,18 @@
 #, c-format
 msgid ""
 "cannot overwrite multiple values with a single value\n"
-"       Use a regexp, --add or --replace-all to change %s."
+"       Use --value=<pattern>, --append or --all to change %s."
 msgstr ""
 "impossible de surcharger des valeurs multiples avec une seule valeur\n"
-"       Utilisez une regex, --add ou --replace-all pour modifier %s."
+"       Utilisez --value=<motif>, --append ou --all pour modifier %s."
+
+msgid "unset all multi-valued config options"
+msgstr "réinitialiser toutes les options de configuration multi-valeurs"
+
+msgid "unset multi-valued config options with matching values"
+msgstr ""
+"réinitialiser toutes les options de configuration multi-valeurs ayant des "
+"valeurs correspondantes"
 
 #, c-format
 msgid "no such section: %s"
@@ -5651,6 +5676,14 @@
 msgid "--comment is only applicable to add/set/replace operations"
 msgstr "--comment n'est applicable qu'avec les opérations add/set/replace"
 
+#, c-format
+msgid ""
+"cannot overwrite multiple values with a single value\n"
+"       Use a regexp, --add or --replace-all to change %s."
+msgstr ""
+"impossible de surcharger des valeurs multiples avec une seule valeur\n"
+"       Utilisez une regex, --add ou --replace-all pour modifier %s."
+
 msgid "print sizes in human readable format"
 msgstr "affiche les tailles dans un format humainement lisible"
 
@@ -6058,11 +6091,6 @@
 "--reencode=(yes|no) pour le gérer"
 
 #, c-format
-msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it"
-msgstr ""
-"commit signé %s rencontré ; utilisez --signed-commits=<mode> pour le gérer"
-
-#, c-format
 msgid "exporting %<PRIuMAX> signature(s) for commit %s"
 msgstr "export de %<PRIuMAX> signature(s) pour le commit %s"
 
@@ -6071,6 +6099,18 @@
 msgstr "suppression de signature(s) du commit %s"
 
 #, c-format
+msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it"
+msgstr ""
+"commit signé %s rencontré ; utilisez --signed-commits=<mode> pour le gérer"
+
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-export with --signed-"
+"commits=<mode>"
+msgstr ""
+"'strip-if-invalid' n'est pas un mode valide pour git fast-export avec --"
+"signed-commits=<mode>"
+
+#, c-format
 msgid ""
 "omitting tag %s,\n"
 "since tags of trees (or tags of tags of trees, etc.) are not supported."
@@ -6084,11 +6124,6 @@
 msgstr "impossible de lire l'étiquette %s"
 
 #, c-format
-msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it"
-msgstr ""
-"étiquette signée %s rencontrée ; utilisez --signed-tags=<mode> pour la gérer"
-
-#, c-format
 msgid "exporting signed tag %s"
 msgstr "export de l'étiquette signée %s"
 
@@ -6097,6 +6132,18 @@
 msgstr "suppression de la signature dans l'étiquette %s"
 
 #, c-format
+msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it"
+msgstr ""
+"étiquette signée %s rencontrée ; utilisez --signed-tags=<mode> pour la gérer"
+
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-export with --signed-"
+"tags=<mode>"
+msgstr ""
+"'strip-if-invalid' n'est pas un mode valide pour git fast-export avec --"
+"signed-tags=<mode>"
+
+#, c-format
 msgid ""
 "tag %s tags unexported object; use --tag-of-filtered-object=<mode> to handle "
 "it"
@@ -6491,6 +6538,33 @@
 msgid "parse_one_signature() returned unknown hash algo"
 msgstr "parse_one_signature() a renvoyé un algo d'empreinte inconnu"
 
+msgid "unknown"
+msgstr "inconnu"
+
+#, c-format
+msgid ""
+"stripping invalid signature for commit '%.100s...'\n"
+"  allegedly by %s"
+msgstr ""
+"suppression de la signature invalide pour le commit '%.100s...'\n"
+"  prétendument par %s"
+
+#, c-format
+msgid ""
+"stripping invalid signature for commit '%.*s'\n"
+"  allegedly by %s"
+msgstr ""
+"suppression de la signature invalide pour le commit '%.*s'\n"
+"  prétendument par %s"
+
+#, c-format
+msgid ""
+"stripping invalid signature for commit\n"
+"  allegedly by %s"
+msgstr ""
+"suppression de la signature invalide pour le commit\n"
+"  prétendument par %s"
+
 msgid "expected committer but didn't get one"
 msgstr "validateur attendu mais aucune information fournie"
 
@@ -6504,10 +6578,6 @@
 msgid "importing a commit signature verbatim"
 msgstr "import d'une signature de commit verbatim"
 
-msgid "encountered signed tag; use --signed-tags=<mode> to handle it"
-msgstr ""
-"étiquette signée rencontrée ; utilisez --signed-tags=<mode> pour la gérer"
-
 #, c-format
 msgid "importing a tag signature verbatim for tag '%s'"
 msgstr "import d'une signature d'étiquette verbatim pour l'étiquette '%s'"
@@ -6516,6 +6586,17 @@
 msgid "stripping a tag signature for tag '%s'"
 msgstr "suppression d'une signature d'étiquette de l'étiquette '%s'"
 
+msgid "encountered signed tag; use --signed-tags=<mode> to handle it"
+msgstr ""
+"étiquette signée rencontrée ; utilisez --signed-tags=<mode> pour la gérer"
+
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-import with --signed-"
+"tags=<mode>"
+msgstr ""
+"'strip-if-invalid' n'est pas un mode valide pour git fast-import avec --"
+"signed-tags=<mode>"
+
 #, c-format
 msgid "expected 'from' command, got '%s'"
 msgstr "Commande 'from' attendue, '%s' trouvé"
@@ -6675,8 +6756,8 @@
 msgid "git fetch [<options>] <group>"
 msgstr "git fetch [<options>] <groupe>"
 
-msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]"
-msgstr "git fetch --multiple [<options>] [(<dépôt> | <groupe>)...]"
+msgid "git fetch --multiple [<options>] [(<repository>|<group>)...]"
+msgstr "git fetch --multiple [<options>] [(<dépôt>|<groupe>)...]"
 
 msgid "git fetch --all [<options>]"
 msgstr "git fetch --all [<options>]"
@@ -7159,9 +7240,6 @@
 msgid "got bad config --config=%s"
 msgstr "config incorrecte --config=%s"
 
-msgid "unknown"
-msgstr "inconnu"
-
 #. TRANSLATORS: e.g. error in tree 01bfda: <more explanation>
 #, c-format
 msgid "error in %s %s: %s"
@@ -7256,6 +7334,10 @@
 msgid "%s: not a commit"
 msgstr "l'objet %s n'est pas un commit"
 
+#, c-format
+msgid "invalid parameter: expected sha1, got '%s'"
+msgstr "paramètre invalide : sha-1 attendu, '%s' trouvé"
+
 msgid "notice: No default references"
 msgstr "note : pas de référence par défaut"
 
@@ -7282,26 +7364,6 @@
 msgstr "Vérification des répertoires d'objet"
 
 #, c-format
-msgid "Checking %s link"
-msgstr "Vérification du lien %s"
-
-#, c-format
-msgid "invalid %s"
-msgstr "%s invalide"
-
-#, c-format
-msgid "%s points to something strange (%s)"
-msgstr "%s pointe sur quelque chose bizarre (%s)"
-
-#, c-format
-msgid "%s: detached HEAD points at nothing"
-msgstr "%s : la HEAD détachée ne pointe sur rien"
-
-#, c-format
-msgid "notice: %s points to an unborn branch (%s)"
-msgstr "note : %s pointe sur une branche non-née (%s)"
-
-#, c-format
 msgid "Checking cache tree of %s"
 msgstr "Vérification de l'arbre cache de %s"
 
@@ -7381,14 +7443,6 @@
 msgid "Checking objects"
 msgstr "Vérification des objets"
 
-#, c-format
-msgid "%s: object missing"
-msgstr "%s : objet manquant"
-
-#, c-format
-msgid "invalid parameter: expected sha1, got '%s'"
-msgstr "paramètre invalide : sha-1 attendu, '%s' trouvé"
-
 msgid "git fsmonitor--daemon start [<options>]"
 msgstr "git fsmonitor--daemon start [<options>]"
 
@@ -7777,6 +7831,9 @@
 msgid "failed to add repo to global config"
 msgstr "échec de l'ajout du dépôt à la config globale"
 
+msgid "check a specific task"
+msgstr "vérifier une tâche spécifique"
+
 msgid "git maintenance <subcommand> [<options>]"
 msgstr "git maintenance <subcommand> [<options>]"
 
@@ -8238,6 +8295,10 @@
 msgstr "erreur de fsck dans l'objet empaqueté"
 
 #, c-format
+msgid "invalid %s"
+msgstr "%s invalide"
+
+#, c-format
 msgid "Not all child objects of %s are reachable"
 msgstr "Tous les objets enfants de %s ne sont pas accessibles"
 
@@ -8380,6 +8441,11 @@
 msgid "--verify with no packfile name given"
 msgstr "--verify sans nom de fichier paquet donné"
 
+msgid "cannot perform queued object checks outside of a repository"
+msgstr ""
+"impossible d'effectuer les vérifications des objets en attentes hors d'un "
+"dépôt"
+
 msgid "fsck error in pack objects"
 msgstr "erreur de fsck dans les objets paquets"
 
@@ -9155,13 +9221,6 @@
 "git merge-file [<options>] [-L <nom1> [-L <orig> [-L <nom2>]]] <fichier1> "
 "<fichier-orig> <fichier2>"
 
-msgid ""
-"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and "
-"\"histogram\""
-msgstr ""
-"l'option diff-algorithm accept \"myers\", \"minimal\", \"patience\" et "
-"\"histogram\""
-
 msgid "send results to standard output"
 msgstr "envoyer les résultats sur la sortie standard"
 
@@ -9174,12 +9233,6 @@
 msgid "use a zealous diff3 based merge"
 msgstr "utiliser une fusion basée sur un diff3 zélée"
 
-msgid "<algorithm>"
-msgstr "<algorithme>"
-
-msgid "choose a diff algorithm"
-msgstr "choisir un algorithme de différence"
-
 msgid "for conflicts, use this marker size"
 msgstr "pour les conflits, utiliser cette taille de marqueur"
 
@@ -10271,6 +10324,12 @@
 msgstr "impossible d'obtenir le type de l'objet %s dans le paquet %s"
 
 #, c-format
+msgid "packfile %s is a promisor but --exclude-promisor-objects was given"
+msgstr ""
+"le fichier paquet %s est un prometteur mais --exclude-promisor-objects a été "
+"spécifé"
+
+#, c-format
 msgid "could not find pack '%s'"
 msgstr "impossible de trouver le paquet '%s'"
 
@@ -10539,10 +10598,10 @@
 msgid "git patch-id [--stable | --unstable | --verbatim]"
 msgstr "git patch-id [--stable | --unstable | --verbatim]"
 
-msgid "use the unstable patch-id algorithm"
-msgstr "utiliser l'algorithme instable patch-id"
+msgid "use the unstable patch ID algorithm"
+msgstr "utiliser l'algorithme patch-id instable"
 
-msgid "use the stable patch-id algorithm"
+msgid "use the stable patch ID algorithm"
 msgstr "utiliser l'algorithme stable patch-id"
 
 msgid "don't strip whitespace from the patch"
@@ -10566,39 +10625,6 @@
 msgid "git pull [<options>] [<repository> [<refspec>...]]"
 msgstr "git pull [<options>] [<dépôt> [<spécification-de-référence>...]]"
 
-msgid "control for recursive fetching of submodules"
-msgstr "contrôler la récupération récursive dans les sous-modules"
-
-msgid "Options related to merging"
-msgstr "Options relatives à la fusion"
-
-msgid "incorporate changes by rebasing rather than merging"
-msgstr "incorporer les modifications en rebasant plutôt qu'en fusionnant"
-
-msgid "allow fast-forward"
-msgstr "autoriser l'avance rapide"
-
-msgid "control use of pre-merge-commit and commit-msg hooks"
-msgstr "contrôler l'utilisation des crochets pre-merge-commit et commit-msg"
-
-msgid "automatically stash/stash pop before and after"
-msgstr "remiser et réappliquer automatiquement avant et après"
-
-msgid "Options related to fetching"
-msgstr "Options relatives au rapatriement"
-
-msgid "force overwrite of local branch"
-msgstr "forcer l'écrasement de la branche locale"
-
-msgid "number of submodules pulled in parallel"
-msgstr "nombre de sous-modules tirés en parallèle"
-
-msgid "use IPv4 addresses only"
-msgstr "n'utiliser que des adresses IPv4"
-
-msgid "use IPv6 addresses only"
-msgstr "n'utiliser que des adresses IPv6"
-
 msgid ""
 "There is no candidate for rebasing against among the refs that you just "
 "fetched."
@@ -10705,6 +10731,39 @@
 "passer --rebase, --no-rebase ou --ff-only sur la ligne de commande pour\n"
 "remplacer à l'invocation la valeur par défaut configurée.\n"
 
+msgid "control for recursive fetching of submodules"
+msgstr "contrôler la récupération récursive dans les sous-modules"
+
+msgid "Options related to merging"
+msgstr "Options relatives à la fusion"
+
+msgid "incorporate changes by rebasing rather than merging"
+msgstr "incorporer les modifications en rebasant plutôt qu'en fusionnant"
+
+msgid "allow fast-forward"
+msgstr "autoriser l'avance rapide"
+
+msgid "control use of pre-merge-commit and commit-msg hooks"
+msgstr "contrôler l'utilisation des crochets pre-merge-commit et commit-msg"
+
+msgid "automatically stash/stash pop before and after"
+msgstr "remiser et réappliquer automatiquement avant et après"
+
+msgid "Options related to fetching"
+msgstr "Options relatives au rapatriement"
+
+msgid "force overwrite of local branch"
+msgstr "forcer l'écrasement de la branche locale"
+
+msgid "number of submodules pulled in parallel"
+msgstr "nombre de sous-modules tirés en parallèle"
+
+msgid "use IPv4 addresses only"
+msgstr "n'utiliser que des adresses IPv4"
+
+msgid "use IPv6 addresses only"
+msgstr "n'utiliser que des adresses IPv6"
+
 msgid "Updating an unborn branch with changes added to the index."
 msgstr ""
 "Mise à jour d'une branche non encore créée avec les changements ajoutés dans "
@@ -12600,6 +12659,10 @@
 msgid "only one pattern can be given with -l"
 msgstr "-l n'accepte qu'un motifs"
 
+#, c-format
+msgid "'%s' is not a valid commit-ish for %s"
+msgstr "'%s' n'est pas un commit-esque valide pour l'option %s"
+
 msgid "need some commits to replay"
 msgstr "commits requis pour pouvoir rejouer"
 
@@ -12616,28 +12679,16 @@
 "impossible d'avancer la cible avec des sources multiples parce l'ordre ne "
 "serait pas total"
 
-msgid ""
-"cannot implicitly determine whether this is an --advance or --onto operation"
-msgstr ""
-"impossible de déterminer implicitement s'il y a une opération --advance ou --"
-"onto"
-
-msgid ""
-"cannot advance target with multiple source branches because ordering would "
-"be ill-defined"
-msgstr ""
-"impossible d'avancer la cible sur des branches sources multiples parce que "
-"l'ordre ne serait pas total"
-
-msgid "cannot implicitly determine correct base for --onto"
-msgstr "impossible de déterminer implicitement une base correcte pour --onto"
+#, c-format
+msgid "invalid %s value: '%s'"
+msgstr "valeur invalide de %s : '%s'"
 
 msgid ""
 "(EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance "
-"<branch>) <revision-range>..."
+"<branch>) [--ref-action[=<mode>]] <revision-range>"
 msgstr ""
 "(EXPERIMENTAL!) git replay ([--contained] --onto <nouvelle-base> | --advance "
-"<branche>) <plage-de-révision>..."
+"<branche>) [--ref-action[=<mode>]] <plage-de-révision>"
 
 msgid "make replay advance given branch"
 msgstr "faire rejouer en avançant la branche indiquée"
@@ -12645,8 +12696,13 @@
 msgid "replay onto given commit"
 msgstr "rejouer par-dessus le commit indiqué"
 
-msgid "advance all branches contained in revision-range"
-msgstr "avancer toutes les branches contenues dans la plage-de-révisions"
+msgid "update all branches that point at commits in <revision-range>"
+msgstr ""
+"mettre à jour les branches qui pointent sur les commits dans <plage-de-"
+"révisions>"
+
+msgid "control ref update behavior (update|print)"
+msgstr "contrôler le comportement de mise à jour de ref (update|print)"
 
 msgid "option --onto or --advance is mandatory"
 msgstr "une option --onto ou --advance est obligatoire"
@@ -12659,16 +12715,28 @@
 "certaines options de parcours de révs seront surchargées car le bit '%s' "
 "dans 'struct rev_info' sera forcé"
 
+#, c-format
+msgid "failed to begin ref transaction: %s"
+msgstr "impossible de commencer la transaction de réf ; %s"
+
 msgid "error preparing revisions"
 msgstr "erreur lors de la préparation des révisions"
 
-msgid "replaying down to root commit is not supported yet!"
-msgstr "rejouer jusqu'au commit racine n'est pas encore géré !"
+msgid "replaying down from root commit is not supported yet!"
+msgstr "rejouer depuis le commit racine n'est pas encore géré !"
 
 msgid "replaying merge commits is not supported yet!"
 msgstr "rejouer des commits de fusion n'est pas encore géré !"
 
 #, c-format
+msgid "failed to update ref '%s': %s"
+msgstr "échec de la mise à jour de la réf '%s' : %s"
+
+#, c-format
+msgid "failed to commit ref transaction: %s"
+msgstr "impossible de valider la transaction de réf : %s"
+
+#, c-format
 msgid "key '%s' not found"
 msgstr "clé '%s' non trouvée"
 
@@ -12682,9 +12750,15 @@
 msgid "synonym for --format=nul"
 msgstr "synonymes de --format=nul"
 
+msgid "print all keys/values"
+msgstr "afficher toutes les clés/valeurs"
+
 msgid "unsupported output format"
 msgstr "format de sortie non géré"
 
+msgid "--all and <key> cannot be used together"
+msgstr "--all et <clé> ne peuvent pas être utilisées ensemble"
+
 msgid "References"
 msgstr "Références"
 
@@ -12715,6 +12789,12 @@
 msgid "Blobs"
 msgstr "Blobs"
 
+msgid "Inflated size"
+msgstr "taille décompressée"
+
+msgid "Disk size"
+msgstr "Taille sur disque"
+
 msgid "Repository structure"
 msgstr "Structure de dépôt"
 
@@ -15271,6 +15351,10 @@
 msgid "bundle list at '%s' has no mode"
 msgstr "la liste de colis n'a pas de mode à '%s'"
 
+#, c-format
+msgid "bundle list at '%s': bundle '%s' has no uri"
+msgstr "la liste de colis à '%s' ; le colis '%s' n'a pas d'URI"
+
 msgid "failed to create temporary file"
 msgstr "impossible de créer un fichier temporaire"
 
@@ -15293,6 +15377,10 @@
 msgstr "limite de récursion d'URI de colis dépassée (%d)"
 
 #, c-format
+msgid "bundle '%s' has no uri"
+msgstr "le colis '%s' n'a pas d'URI"
+
+#, c-format
 msgid "failed to download bundle from URI '%s'"
 msgstr "impossible de télécharger le colis depuis l'URI '%s'"
 
@@ -15805,8 +15893,8 @@
 msgid "Reuse recorded resolution of conflicted merges"
 msgstr "Réutiliser une résolution enregistrée de fusions conflictuelles"
 
-msgid "Reset current HEAD to the specified state"
-msgstr "Réinitialiser la HEAD courante à l'état spécifié"
+msgid "Set `HEAD` or the index to a known state"
+msgstr "Positionner `HEAD` ou l'index à un état connu"
 
 msgid "Restore working tree files"
 msgstr "Restaurer les fichiers l'arbre de travail"
@@ -17376,10 +17464,6 @@
 "Valeur inconnue pour la variable de configuration 'diff.submodule' : '%s'"
 
 #, c-format
-msgid "unknown value for config '%s': %s"
-msgstr "valeur inconnue pour la config '%s' : %s"
-
-#, c-format
 msgid ""
 "Found errors in 'diff.dirstat' config variable:\n"
 "%s"
@@ -19555,6 +19639,10 @@
 msgid "%s: failed to insert into database"
 msgstr "%s : échec de l'insertion dans la base de données"
 
+msgid "cannot add a submodule of a different hash algorithm"
+msgstr ""
+"impossible d'ajouter un sous-module ayant un algorithme d'empreinte différent"
+
 #, c-format
 msgid "%s: unsupported file type"
 msgstr "%s : type de fichier non supporté"
@@ -21037,6 +21125,10 @@
 msgid "--format=%.*s cannot be used with --python, --shell, --tcl"
 msgstr "--format=%.*s ne peut pas être utilisé avec --python, --shell, --tcl"
 
+#, c-format
+msgid "parse_object_buffer failed on %s for %s"
+msgstr "échec de parse_object_buffer sur %s pour %s"
+
 msgid "failed to run 'describe'"
 msgstr "échec pour lancer 'describe'"
 
@@ -21068,10 +21160,6 @@
 msgstr "objet manquant %s pour %s"
 
 #, c-format
-msgid "parse_object_buffer failed on %s for %s"
-msgstr "échec de parse_object_buffer sur %s pour %s"
-
-#, c-format
 msgid "malformed object at '%s'"
 msgstr "objet malformé à '%s'"
 
@@ -21377,6 +21465,18 @@
 "le nom de réf %s est une réf symbolique, la copie n'est pas prise en charge"
 
 #, c-format
+msgid "reftable stack for worktree '%s' is broken"
+msgstr "la pile de reftable pour l'arbre-de-travail '%s' est cassée"
+
+#, c-format
+msgid "could not create iterator for worktree '%s'"
+msgstr "impossible de créer l'itérateur l'arbre-de-travail '%s'"
+
+#, c-format
+msgid "could not read record for worktree '%s'"
+msgstr "impossible de lire l'enregistrement pour l'arbre-de-travail '%s'"
+
+#, c-format
 msgid "pattern '%s' has no '*'"
 msgstr "la valeur '%s' du motif n'a pas de '*'"
 
@@ -21800,10 +21900,11 @@
 
 msgid "could not start pack-objects to repack promisor objects"
 msgstr ""
-"ne pas démarrer pack-objects pour ré-empaqueter les objects de prometteur"
+"échec du démarrage de pack-objects pour ré-empaqueter les objets de "
+"prometteur"
 
 msgid "failed to feed promisor objects to pack-objects"
-msgstr "Échéc de la fourniture les objets du prometteur à pack-objects"
+msgstr "échec de la fourniture les objets du prometteur à pack-objects"
 
 msgid "repack: Expecting full hex object ID lines only from pack-objects."
 msgstr ""
@@ -21815,6 +21916,11 @@
 "impossible de terminer pack-objects  pour ré-empaqueter les objets de "
 "prometteur"
 
+msgid "could not start pack-objects to repack promisor packs"
+msgstr ""
+"échec du démarrage de  pack-objects pour ré-empaqueter les paquets de "
+"prometteur"
+
 #, c-format
 msgid "pack prefix %s does not begin with objdir %s"
 msgstr "le préfixe %s ne commence pas avec objdir %s"
@@ -22820,10 +22926,6 @@
 msgid "illegal label name: '%.*s'"
 msgstr "nom de label illégal '%.*s'"
 
-#, c-format
-msgid "could not resolve '%s'"
-msgstr "impossible de résoudre '%s'"
-
 msgid "writing fake root commit"
 msgstr "écriture d'un commit racine bidon"
 
@@ -23309,49 +23411,68 @@
 msgid "bad %s format: %%%.*s"
 msgstr "mauvais format %s : %%%.*s"
 
-#. TRANSLATORS: IEC 80000-13:2008 gibibyte
 #, c-format
-msgid "%u.%2.2u GiB"
-msgstr "%u.%2.2u Gio"
+msgid "%u.%2.2u"
+msgstr "%u.%2.2u"
 
-#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second
-#, c-format
-msgid "%u.%2.2u GiB/s"
-msgstr "%u.%2.2u Gio/s"
+#. TRANSLATORS: SI decimal prefix symbol for 10^9
+msgid "G"
+msgstr "G"
 
-#. TRANSLATORS: IEC 80000-13:2008 mebibyte
-#, c-format
-msgid "%u.%2.2u MiB"
-msgstr "%u.%2.2u Mio"
+#. TRANSLATORS: SI decimal prefix symbol for 10^6
+msgid "M"
+msgstr "M"
 
-#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second
-#, c-format
-msgid "%u.%2.2u MiB/s"
-msgstr "%u.%2.2u Mio/s"
+#. TRANSLATORS: SI decimal prefix symbol for 10^3
+msgid "k"
+msgstr "k"
 
-#. TRANSLATORS: IEC 80000-13:2008 kibibyte
-#, c-format
-msgid "%u.%2.2u KiB"
-msgstr "%u.%2.2u Kio"
+#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte
+msgid "GiB/s"
+msgstr "GiO/s"
 
-#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second
-#, c-format
-msgid "%u.%2.2u KiB/s"
-msgstr "%u.%2.2u Kio/s"
+msgid "GiB"
+msgstr "GiO"
 
-#. TRANSLATORS: IEC 80000-13:2008 byte
-#, c-format
-msgid "%u byte"
-msgid_plural "%u bytes"
-msgstr[0] "%u octet"
-msgstr[1] "%u octets"
+#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte
+msgid "MiB/s"
+msgstr "MiO/s"
+
+msgid "MiB"
+msgstr "MiO"
+
+#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte
+msgid "KiB/s"
+msgstr "KiO/s"
+
+msgid "KiB"
+msgstr "KiO"
+
+#. TRANSLATORS: IEC 80000-13:2008 byte/second and byte
+msgid "B/s"
+msgstr "Octets/s"
+
+msgid "B"
+msgstr "Octets"
 
 #. TRANSLATORS: IEC 80000-13:2008 byte/second
+msgid "byte/s"
+msgid_plural "bytes/s"
+msgstr[0] "octet/s"
+msgstr[1] "octets/s"
+
+#. TRANSLATORS: IEC 80000-13:2008 byte
+msgid "byte"
+msgid_plural "bytes"
+msgstr[0] "octet"
+msgstr[1] "octets"
+
+#. TRANSLATORS: The first argument is the number string. The second
+#. argument is the unit string (i.e. "12.34 MiB/s").
+#.
 #, c-format
-msgid "%u byte/s"
-msgid_plural "%u bytes/s"
-msgstr[0] "%u octet/s"
-msgstr[1] "%u octets/s"
+msgid "%s %s"
+msgstr "%s %s"
 
 #, c-format
 msgid "ignoring suspicious submodule name: %s"
@@ -23651,9 +23772,6 @@
 msgid "number of requests per thread"
 msgstr "nombre de requêtes par fil d'exécution"
 
-msgid "byte"
-msgstr "octet"
-
 msgid "ballast character"
 msgstr "caractère ballast"
 
@@ -25268,63 +25386,3 @@
 #, perl-format
 msgid "Do you really want to send %s? [y|N]: "
 msgstr "Souhaitez-vous réellement envoyer %s ?[y|N] : "
-
-#~ msgid "No previous hunk"
-#~ msgstr "Pas de section précédente"
-
-#~ msgid "No next hunk"
-#~ msgstr "Pas de section suivante"
-
-#~ msgid "git for-each-ref [<options>] [<pattern>]"
-#~ msgstr "git for-each-ref [<options>] [<motif>]"
-
-#~ msgid "git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]"
-#~ msgstr "git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]"
-
-#~ msgid "git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"
-#~ msgstr "git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"
-
-#~ msgid "git for-each-ref [--start-after <marker>]"
-#~ msgstr "git for-each-ref [--start-after <marqueur>]"
-
-#~ msgid ""
-#~ "git pack-refs [--all] [--no-prune] [--auto] [--include <pattern>] [--"
-#~ "exclude <pattern>]"
-#~ msgstr ""
-#~ "git pack-refs [--all] [--no-prune] [--auto] [--include <motif>] [--"
-#~ "exclude <motif>]"
-
-#, c-format
-#~ msgid "deleting '%s' failed"
-#~ msgstr "échec de suppression de '%s'"
-
-#, c-format
-#~ msgid "creating '%s' failed"
-#~ msgstr "échec de création de '%s'"
-
-#, c-format
-#~ msgid "could not open index for %s"
-#~ msgstr "impossible d'ouvrir l'index pour %s"
-
-#~ msgid "cannot handle pushes this big"
-#~ msgstr "impossible de gérer des poussées aussi grosses"
-
-#, c-format
-#~ msgid ""
-#~ "'%s' is nominated for removal.\n"
-#~ "If you still use this command, please add an extra\n"
-#~ "option, '--i-still-use-this', on the command line\n"
-#~ "and let us know you still use it by sending an e-mail\n"
-#~ "to <git@vger.kernel.org>.  Thanks.\n"
-#~ msgstr ""
-#~ "La suppression  de '%s' est prévue.\n"
-#~ "Si vous utilisez cette commande, veuillez ajouter\n"
-#~ "une option supplémentaire, '--i-still-use-this',\n"
-#~ "sur la ligne de commande pour nous avertir par\n"
-#~ "un courriel à <git@vger.kernel.org>. Merci.\n"
-
-#~ msgid "start-after"
-#~ msgstr "start-after"
-
-#~ msgid "compact-summary"
-#~ msgstr "résumé-compact"
diff --git a/po/ga.po b/po/ga.po
index bed2ea2..4c05a25 100644
--- a/po/ga.po
+++ b/po/ga.po
@@ -7,8 +7,8 @@
 msgstr ""
 "Project-Id-Version: Git\n"
 "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2025-11-06 23:58+0000\n"
-"PO-Revision-Date: 2025-11-07 21:39+0000\n"
+"POT-Creation-Date: 2026-01-22 23:57+0000\n"
+"PO-Revision-Date: 2026-01-23 10:47+0000\n"
 "Last-Translator: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>\n"
 "Language-Team: none\n"
 "Language: ga\n"
@@ -16,7 +16,7 @@
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2;\n"
-"X-Generator: Poedit 3.7\n"
+"X-Generator: Poedit 3.8\n"
 
 #, c-format
 msgid "%s cannot be negative"
@@ -382,8 +382,8 @@
 #, c-format
 msgid "Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "
 msgstr ""
-"An bhfuil an píosa beag seo le fáil réidh ón innéacs agus ón gcrann oibre "
-"[y,n,q,a,d%s,?]? "
+"An bhfuil an píosa beag seo le fáil réidh ón innéacs agus ón gcrann oibre [y,"
+"n,q,a,d%s,?]? "
 
 msgid ""
 "y - discard this hunk from index and worktree\n"
@@ -402,8 +402,8 @@
 #, c-format
 msgid "Apply mode change to index and worktree [y,n,q,a,d%s,?]? "
 msgstr ""
-"Cuir athrú mód i bhfeidhm ar an innéacs agus ar an gcrann oibre "
-"[y,n,q,a,d%s,?]? "
+"Cuir athrú mód i bhfeidhm ar an innéacs agus ar an gcrann oibre [y,n,q,a,"
+"d%s,?]? "
 
 #, c-format
 msgid "Apply deletion to index and worktree [y,n,q,a,d%s,?]? "
@@ -413,14 +413,14 @@
 #, c-format
 msgid "Apply addition to index and worktree [y,n,q,a,d%s,?]? "
 msgstr ""
-"Cuir an breiseán i bhfeidhm ar an innéacs agus ar an gcrann oibre "
-"[y,n,q,a,d%s,?]? "
+"Cuir an breiseán i bhfeidhm ar an innéacs agus ar an gcrann oibre [y,n,q,a,"
+"d%s,?]? "
 
 #, c-format
 msgid "Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "
 msgstr ""
-"Cuir an píosa seo i bhfeidhm ar an innéacs agus ar an gcrann oibre "
-"[y,n,q,a,d%s,?]? "
+"Cuir an píosa seo i bhfeidhm ar an innéacs agus ar an gcrann oibre [y,n,q,a,"
+"d%s,?]? "
 
 msgid ""
 "y - apply this hunk to index and worktree\n"
@@ -618,9 +618,9 @@
 #, c-format
 msgid "Sorry, only %d hunk available."
 msgid_plural "Sorry, only %d hunks available."
-msgstr[0] "Tá brón orm, níl ach %d píosa ar fáil."
-msgstr[1] "Tá brón orm, níl ach %d hunks ar fáil."
-msgstr[2] "Tá brón orm, níl ach %d hunks ar fáil."
+msgstr[0] "Ár leithscéal, níl ach %d píosa ar fáil."
+msgstr[1] "Ár leithscéal, níl ach %d hunks ar fáil."
+msgstr[2] "Ár leithscéal, níl ach %d hunks ar fáil."
 
 msgid "No other hunks to search"
 msgstr "Níl aon phíosa eile le cuardach"
@@ -636,14 +636,14 @@
 msgstr "Níl aon hunk ag teacht leis an bpatrún tugtha"
 
 msgid "Sorry, cannot split this hunk"
-msgstr "Tá brón orainn, ní féidir an hunk seo a roinnt"
+msgstr "Ár leithscéal, ní féidir an hunk seo a roinnt"
 
 #, c-format
 msgid "Split into %d hunks."
 msgstr "Roinn ina %d hunks."
 
 msgid "Sorry, cannot edit this hunk"
-msgstr "Tá brón orainn, ní féidir an hunk seo a chur in eagar"
+msgstr "Ár leithscéal, ní féidir an hunk seo a chur in eagar"
 
 #, c-format
 msgid "Unknown command '%s' (use '?' for help)"
@@ -1755,12 +1755,10 @@
 msgid "not tracking: ambiguous information for ref '%s'"
 msgstr "gan rianú: faisnéis dhébhríoch le haghaidh tagairt '%s'"
 
-#. #-#-#-#-#  branch.c.po  #-#-#-#-#
 #. TRANSLATORS: This is a line listing a remote with duplicate
 #. refspecs in the advice message below. For RTL languages you'll
 #. probably want to swap the "%s" and leading "  " space around.
 #.
-#. #-#-#-#-#  object-name.c.po  #-#-#-#-#
 #. TRANSLATORS: This is line item of ambiguous object output
 #. from describe_ambiguous_object() above. For RTL languages
 #. you'll probably want to swap the "%s" and leading " " space
@@ -1798,8 +1796,8 @@
 msgid "'%s' is not a valid branch name"
 msgstr "Ní ainm brainse bailí é '%s'"
 
-msgid "See `man git check-ref-format`"
-msgstr "Féach `man git check-ref-format`"
+msgid "See 'git help check-ref-format'"
+msgstr "Féach 'git help check-ref-format'"
 
 #, c-format
 msgid "a branch named '%s' already exists"
@@ -2715,6 +2713,16 @@
 msgstr "caithfidh deireadh a chur le dath"
 
 #, c-format
+msgid "unknown value for config '%s': %s"
+msgstr "luach anaithnid do chumraíocht '%s': %s"
+
+msgid ""
+"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and "
+"\"histogram\""
+msgstr ""
+"glacann difr-algartam rogha le “myers”, “íosta”, “foighne” agus “histogram”"
+
+#, c-format
 msgid "cannot find revision %s to ignore"
 msgstr "ní féidir athbhreithniú %s a fháil le neamhair"
 
@@ -2769,6 +2777,12 @@
 msgid "ignore whitespace differences"
 msgstr "neamhaird a dhéanamh ar dhifríochtaí spás b"
 
+msgid "<algorithm>"
+msgstr "<algorithm>"
+
+msgid "choose a diff algorithm"
+msgstr "roghnaigh algartam diff"
+
 msgid "rev"
 msgstr "rev"
 
@@ -2784,8 +2798,8 @@
 msgid "color lines by age"
 msgstr "línte datha de réir aois"
 
-msgid "spend extra cycles to find better match"
-msgstr "timthriallta breise a chaitheamh chun meaitseáil níos fearr"
+msgid "spend extra cycles to find a better match"
+msgstr "caith timthriallta breise chun meaitseáil níos fearr a aimsiú"
 
 msgid "use revisions from <file> instead of calling git-rev-list"
 msgstr "athbhreithnithe a úsáid as in <file>ionad glaoch ar git-rev-list"
@@ -3100,11 +3114,11 @@
 msgstr "Ní fhaightear CEAD thíos na refs/heads!"
 
 msgid ""
-"branch with --recurse-submodules can only be used if "
-"submodule.propagateBranches is enabled"
+"branch with --recurse-submodules can only be used if submodule."
+"propagateBranches is enabled"
 msgstr ""
-"ní féidir brainse le --recurse-submodules a úsáid ach amháin má tá "
-"submodule.propagateBranches cumasaithe"
+"ní féidir brainse le --recurse-submodules a úsáid ach amháin má tá submodule."
+"propagateBranches cumasaithe"
 
 msgid "--recurse-submodules can only be used to create branches"
 msgstr "Ní féidir --recurse-submodules a úsáid ach chun brainsí a chruthú"
@@ -3997,8 +4011,8 @@
 msgstr "ainm brainse ar iarraidh; iarracht -%c"
 
 #, c-format
-msgid "could not resolve %s"
-msgstr "ní fhéadfaí %s a réiteach"
+msgid "could not resolve '%s'"
+msgstr "ní fhéadfaí '%s' a réiteach"
 
 msgid "invalid path specification"
 msgstr "sonraíocht chosáin neamhbhailí"
@@ -5493,10 +5507,17 @@
 #, c-format
 msgid ""
 "cannot overwrite multiple values with a single value\n"
-"       Use a regexp, --add or --replace-all to change %s."
+"       Use --value=<pattern>, --append or --all to change %s."
 msgstr ""
-"ní féidir luachanna iolracha a fhorscríobh le luach amháin\n"
-" Úsáid regexp, --add nó --replace-all chun %s a athrú."
+"ní féidir luachanna iolracha a athscríobh le luach amháin. \n"
+"       Úsáid --value=<patrún>, --append nó --all chun %s a athrú."
+
+msgid "unset all multi-valued config options"
+msgstr "dísocraigh gach rogha cumraíochta illuachmhar"
+
+msgid "unset multi-valued config options with matching values"
+msgstr ""
+"roghanna cumraíochta illuachacha a dhíshuiteáil le luachanna comhoiriúnacha"
 
 #, c-format
 msgid "no such section: %s"
@@ -5583,6 +5604,14 @@
 msgstr ""
 "Níl --comment infheidhme ach le hoibríochtaí a chur leis /socraí/athsholáthar"
 
+#, c-format
+msgid ""
+"cannot overwrite multiple values with a single value\n"
+"       Use a regexp, --add or --replace-all to change %s."
+msgstr ""
+"ní féidir luachanna iolracha a fhorscríobh le luach amháin\n"
+" Úsáid regexp, --add nó --replace-all chun %s a athrú."
+
 msgid "print sizes in human readable format"
 msgstr "méideanna priontála i bhformáid inléite don duine"
 
@@ -5988,12 +6017,6 @@
 "as --reencode=[yes|no] chun é a láimhseáil"
 
 #, c-format
-msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it"
-msgstr ""
-"bhuail an tiomantas sínithe %s; bain úsáid as --signed-commits=<mode> chun é "
-"a láimhseáil"
-
-#, c-format
 msgid "exporting %<PRIuMAX> signature(s) for commit %s"
 msgstr "ag easpórtáil %<PRIuMAX> sínithe le haghaidh tiomnú %s"
 
@@ -6002,6 +6025,19 @@
 msgstr "ag baint sínithe ó thiomnadh %s"
 
 #, c-format
+msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it"
+msgstr ""
+"bhuail an tiomantas sínithe %s; bain úsáid as --signed-commits=<mode> chun é "
+"a láimhseáil"
+
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-export with --signed-"
+"commits=<mode>"
+msgstr ""
+"'strip-if-invalid' ní mód bailí é seo le haghaidh easpórtáil le haghaidh git fast-export le --signed-"
+"commits=<mód>"
+
+#, c-format
 msgid ""
 "omitting tag %s,\n"
 "since tags of trees (or tags of tags of trees, etc.) are not supported."
@@ -6014,12 +6050,6 @@
 msgstr "níorbh fhéidir an clib %s a léamh"
 
 #, c-format
-msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it"
-msgstr ""
-"bhuail mé le clib shínithe %s; bain úsáid as --signed-tags=<mode> chun é a "
-"láimhseáil"
-
-#, c-format
 msgid "exporting signed tag %s"
 msgstr "clib shínithe %s á onnmhairiú"
 
@@ -6028,6 +6058,19 @@
 msgstr "ag baint síniú ón gclib %s"
 
 #, c-format
+msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it"
+msgstr ""
+"bhuail mé le clib shínithe %s; bain úsáid as --signed-tags=<mode> chun é a "
+"láimhseáil"
+
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-export with --signed-"
+"tags=<mode>"
+msgstr ""
+"'strip-if-invalid' ní mód bailí é seo le haghaidh git fast-export le --signed-"
+"tags=<mode>"
+
+#, c-format
 msgid ""
 "tag %s tags unexported object; use --tag-of-filtered-object=<mode> to handle "
 "it"
@@ -6419,6 +6462,33 @@
 msgid "parse_one_signature() returned unknown hash algo"
 msgstr "d’fhill parse_one_signature() algartam haise anaithnid"
 
+msgid "unknown"
+msgstr "anaithnid"
+
+#, c-format
+msgid ""
+"stripping invalid signature for commit '%.100s...'\n"
+"  allegedly by %s"
+msgstr ""
+"ag baint síniú neamhbhailí don tiomantas '%.100s...' \n"
+"  de réir dealraimh le %s"
+
+#, c-format
+msgid ""
+"stripping invalid signature for commit '%.*s'\n"
+"  allegedly by %s"
+msgstr ""
+"ag baint síniú neamhbhailí don tiomantas '%.*s'\n"
+"  de réir dealraimh le %s"
+
+#, c-format
+msgid ""
+"stripping invalid signature for commit\n"
+"  allegedly by %s"
+msgstr ""
+"ag baint síniú neamhbhailí don tiomnú\n"
+"  de réir dealraimh ag %s"
+
 msgid "expected committer but didn't get one"
 msgstr "bhí mé ag súil le gealltóir ach ní bhfuair mé ceann"
 
@@ -6433,11 +6503,6 @@
 msgid "importing a commit signature verbatim"
 msgstr "síniú tiomantais a allmhairiú focal ar fhocal"
 
-msgid "encountered signed tag; use --signed-tags=<mode> to handle it"
-msgstr ""
-"bhuail mé le clib shínithe; bain úsáid as --signed-tags=<mode> chun é a "
-"láimhseáil"
-
 #, c-format
 msgid "importing a tag signature verbatim for tag '%s'"
 msgstr "ag allmhairiú síniú clibe focal ar fhocal don chlib '%s'"
@@ -6446,6 +6511,18 @@
 msgid "stripping a tag signature for tag '%s'"
 msgstr "ag baint síniú clibe don chlib '%s'"
 
+msgid "encountered signed tag; use --signed-tags=<mode> to handle it"
+msgstr ""
+"bhuail mé le clib shínithe; bain úsáid as --signed-tags=<mode> chun é a "
+"láimhseáil"
+
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-import with --signed-"
+"tags=<mode>"
+msgstr ""
+"Ní mód bailí é 'strip-if-invalid' le haghaidh git fast-import le --signed-"
+"tags=<mode>"
+
 #, c-format
 msgid "expected 'from' command, got '%s'"
 msgstr "ag súil le hordú 'from', fuarthas '%s'"
@@ -6603,8 +6680,8 @@
 msgid "git fetch [<options>] <group>"
 msgstr "git fetch [<options>] <group>"
 
-msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]"
-msgstr "git fetch --multiple [<options>] [(<repository> | <group>)...]"
+msgid "git fetch --multiple [<options>] [(<repository>|<group>)...]"
+msgstr "git fetch --multiple [<roghanna>] [(<stór>|<grúpa>)...]"
 
 msgid "git fetch --all [<options>]"
 msgstr "git fetch --all [<options>]"
@@ -6971,8 +7048,8 @@
 msgstr "ní thacaíonn an prótacal le --negotiate-only, ag scoir"
 
 msgid ""
-"--filter can only be used with the remote configured in "
-"extensions.partialclone"
+"--filter can only be used with the remote configured in extensions."
+"partialclone"
 msgstr ""
 "--filter Ní féidir ach an scagaire a úsáid ach leis an iargúlta cumraithe in "
 "extensions.partialclone"
@@ -7085,9 +7162,6 @@
 msgid "got bad config --config=%s"
 msgstr "fuair cumraíocht go dona --config=%s"
 
-msgid "unknown"
-msgstr "anaithnid"
-
 #. TRANSLATORS: e.g. error in tree 01bfda: <more explanation>
 #, c-format
 msgid "error in %s %s: %s"
@@ -7182,6 +7256,10 @@
 msgid "%s: not a commit"
 msgstr "%s: ní gealltanas"
 
+#, c-format
+msgid "invalid parameter: expected sha1, got '%s'"
+msgstr "paraiméadar neamhbhailí: súil le sha1, fuair '%s'"
+
 msgid "notice: No default references"
 msgstr "fógra: Gan aon tagairtí réamhshocraithe"
 
@@ -7208,26 +7286,6 @@
 msgstr "Seiceáil eolairí réada"
 
 #, c-format
-msgid "Checking %s link"
-msgstr "Nasc %s a sheiceáil"
-
-#, c-format
-msgid "invalid %s"
-msgstr "%s neamhbhailí"
-
-#, c-format
-msgid "%s points to something strange (%s)"
-msgstr "Léiríonn %s rud éigin aisteach (%s)"
-
-#, c-format
-msgid "%s: detached HEAD points at nothing"
-msgstr "%s: pointí HEAD scoite ag aon rud"
-
-#, c-format
-msgid "notice: %s points to an unborn branch (%s)"
-msgstr "fógra: Díríonn %s chuig brainse neamhbhreithe (%s)"
-
-#, c-format
 msgid "Checking cache tree of %s"
 msgstr "Crann taisce de %s a sheiceáil"
 
@@ -7306,14 +7364,6 @@
 msgid "Checking objects"
 msgstr "Rud a sheiceáil"
 
-#, c-format
-msgid "%s: object missing"
-msgstr "%s: réad ar iarraidh"
-
-#, c-format
-msgid "invalid parameter: expected sha1, got '%s'"
-msgstr "paraiméadar neamhbhailí: súil le sha1, fuair '%s'"
-
 msgid "git fsmonitor--daemon start [<options>]"
 msgstr "git fsmonitor--daemon start [<options>]"
 
@@ -7534,8 +7584,8 @@
 msgid ""
 "skipping incremental-repack task because core.multiPackIndex is disabled"
 msgstr ""
-"ag scipeáil an tasc athphacála incriminteach mar go bhfuil "
-"core.multiPackIndex díchumasaithe"
+"ag scipeáil an tasc athphacála incriminteach mar go bhfuil core."
+"multiPackIndex díchumasaithe"
 
 msgid "failed to perform geometric repack"
 msgstr "theip ar athphacáil gheoiméadrach a dhéanamh"
@@ -7698,6 +7748,9 @@
 msgid "failed to add repo to global config"
 msgstr "theip ar repo a chur le cumraíocht domhanda"
 
+msgid "check a specific task"
+msgstr "seiceáil tasc ar leith"
+
 msgid "git maintenance <subcommand> [<options>]"
 msgstr "git maintenance <subcommand> [<options>]"
 
@@ -7712,7 +7765,6 @@
 msgid "invalid number of threads specified (%d) for %s"
 msgstr "líon neamhbhailí na snáitheanna a shonraítear (%d) do %s"
 
-#. #-#-#-#-#  grep.c.po  #-#-#-#-#
 #. TRANSLATORS: %s is the configuration
 #. variable for tweaking threads, currently
 #. grep.threads
@@ -8156,6 +8208,10 @@
 msgstr "earráid fsck i réad pacáilte"
 
 #, c-format
+msgid "invalid %s"
+msgstr "%s neamhbhailí"
+
+#, c-format
 msgid "Not all child objects of %s are reachable"
 msgstr "Ní féidir rochtain a fháil ar gach réad linbh de %s"
 
@@ -8299,6 +8355,9 @@
 msgid "--verify with no packfile name given"
 msgstr "--verify gan ainm comhaid pacáiste tugtha"
 
+msgid "cannot perform queued object checks outside of a repository"
+msgstr "ní féidir seiceálacha réada scuaine a dhéanamh lasmuigh de stórlann"
+
 msgid "fsck error in pack objects"
 msgstr "fsck earráid i rudaí pacáiste"
 
@@ -9055,12 +9114,6 @@
 "git merge-file [<options>] [-L <name1> [-L <orig> [-L <name2>]]] <file1> "
 "<orig-file> <file2>"
 
-msgid ""
-"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and "
-"\"histogram\""
-msgstr ""
-"glacann difr-algartam rogha le “myers”, “íosta”, “foighne” agus “histogram”"
-
 msgid "send results to standard output"
 msgstr "torthaí a sheoladh chuig aschur caigh"
 
@@ -9073,12 +9126,6 @@
 msgid "use a zealous diff3 based merge"
 msgstr "bain úsáid as cumaisc díograiseach bunaithe ar diff3"
 
-msgid "<algorithm>"
-msgstr "<algorithm>"
-
-msgid "choose a diff algorithm"
-msgstr "roghnaigh algartam diff"
-
 msgid "for conflicts, use this marker size"
 msgstr "le haghaidh coinbhleachtaí, bain úsáid as an méid marcóra"
 
@@ -10050,8 +10097,8 @@
 
 msgid "disabling bitmap writing, packs are split due to pack.packSizeLimit"
 msgstr ""
-"scríobh bitmap a dhíchumasú, roinntear pacáistí mar gheall ar "
-"pack.packSizeLimit"
+"scríobh bitmap a dhíchumasú, roinntear pacáistí mar gheall ar pack."
+"packSizeLimit"
 
 msgid "Writing objects"
 msgstr "Rudaí a scríobh"
@@ -10158,6 +10205,10 @@
 msgstr "ní fhéadfaí cineál réada %s a fháil i bpacáiste %s"
 
 #, c-format
+msgid "packfile %s is a promisor but --exclude-promisor-objects was given"
+msgstr "is gealltóir é an comhad paca %s ach tugadh --exclude-promisor-objects"
+
+#, c-format
 msgid "could not find pack '%s'"
 msgstr "ní raibh an pacáiste '%s' in ann a aimsiú"
 
@@ -10417,11 +10468,11 @@
 msgid "git patch-id [--stable | --unstable | --verbatim]"
 msgstr "git patch-id [--seasmhach | --éagobhsaí | --verbatim]"
 
-msgid "use the unstable patch-id algorithm"
-msgstr "bain úsáid as an algartam paith-id éagobhsaí"
+msgid "use the unstable patch ID algorithm"
+msgstr "bain úsáid as an algartam aitheantais paiste éagobhsaí"
 
-msgid "use the stable patch-id algorithm"
-msgstr "bain úsáid as an algartam paith-id cobhsaí"
+msgid "use the stable patch ID algorithm"
+msgstr "bain úsáid as an algartam ID paiste cobhsaí"
 
 msgid "don't strip whitespace from the patch"
 msgstr "ná tarraingt spás bán ón bpaiste"
@@ -10444,39 +10495,6 @@
 msgid "git pull [<options>] [<repository> [<refspec>...]]"
 msgstr "git pull [<options>] [<repository> [<refspec>...]]"
 
-msgid "control for recursive fetching of submodules"
-msgstr "rialú maidir le fo-mhodúil a fháil athshlánach"
-
-msgid "Options related to merging"
-msgstr "Roghanna a bhaineann le cumasc"
-
-msgid "incorporate changes by rebasing rather than merging"
-msgstr "athruithe a ionchorprú trí athbhunú seachas cumasc"
-
-msgid "allow fast-forward"
-msgstr "ligean go tapa ar aghaidh"
-
-msgid "control use of pre-merge-commit and commit-msg hooks"
-msgstr "úsáid crúcaí réamh-chumaisc agus comh-msg a rialú"
-
-msgid "automatically stash/stash pop before and after"
-msgstr "pop a stash/stash go huathoibríoch roimh agus tar éis"
-
-msgid "Options related to fetching"
-msgstr "Roghanna a bhaineann le tarraingt"
-
-msgid "force overwrite of local branch"
-msgstr "forscríobh fórsa ar bhrainse áitiúil"
-
-msgid "number of submodules pulled in parallel"
-msgstr "líon na bhfo-mhodúil tarraingthe go comhthreom"
-
-msgid "use IPv4 addresses only"
-msgstr "bain úsáid as seoltaí IPv4 amháin"
-
-msgid "use IPv6 addresses only"
-msgstr "bain úsáid as seoltaí IPv6 amháin"
-
 msgid ""
 "There is no candidate for rebasing against among the refs that you just "
 "fetched."
@@ -10585,6 +10603,39 @@
 "shárú\n"
 "ionghairm.\n"
 
+msgid "control for recursive fetching of submodules"
+msgstr "rialú maidir le fo-mhodúil a fháil athshlánach"
+
+msgid "Options related to merging"
+msgstr "Roghanna a bhaineann le cumasc"
+
+msgid "incorporate changes by rebasing rather than merging"
+msgstr "athruithe a ionchorprú trí athbhunú seachas cumasc"
+
+msgid "allow fast-forward"
+msgstr "ligean go tapa ar aghaidh"
+
+msgid "control use of pre-merge-commit and commit-msg hooks"
+msgstr "úsáid crúcaí réamh-chumaisc agus comh-msg a rialú"
+
+msgid "automatically stash/stash pop before and after"
+msgstr "pop a stash/stash go huathoibríoch roimh agus tar éis"
+
+msgid "Options related to fetching"
+msgstr "Roghanna a bhaineann le tarraingt"
+
+msgid "force overwrite of local branch"
+msgstr "forscríobh fórsa ar bhrainse áitiúil"
+
+msgid "number of submodules pulled in parallel"
+msgstr "líon na bhfo-mhodúil tarraingthe go comhthreom"
+
+msgid "use IPv4 addresses only"
+msgstr "bain úsáid as seoltaí IPv4 amháin"
+
+msgid "use IPv6 addresses only"
+msgstr "bain úsáid as seoltaí IPv6 amháin"
+
 msgid "Updating an unborn branch with changes added to the index."
 msgstr "Brainse breithe a nuashonrú le hathruithe curtha leis an innéacs."
 
@@ -11853,8 +11904,8 @@
 msgstr ""
 "Tá tagairtí contrártha sa\n"
 "tagarmharc sprice nua ag an gcianrialtán atá tú ag iarraidh a athainmniú. Is "
-"dóichí gur mar gheall ar iarracht a dhéanamh cianrialtán a neadú ann féin, "
-"e.g. trí 'tuismitheoir' a athainmniú go 'tuismitheoir/leanbh'\n"
+"dóichí gur mar gheall ar iarracht a dhéanamh cianrialtán a neadú ann féin, e."
+"g. trí 'tuismitheoir' a athainmniú go 'tuismitheoir/leanbh'\n"
 "nó trí chianrialtán a dhí-neadú, e.g. an bealach eile.\n"
 "\n"
 "Más amhlaidh atá, is féidir leat é seo a réiteach tríd an\n"
@@ -12463,6 +12514,10 @@
 msgid "only one pattern can be given with -l"
 msgstr "ní féidir ach patrún amháin a thabhairt le -l"
 
+#, c-format
+msgid "'%s' is not a valid commit-ish for %s"
+msgstr "Ní comhartha bailí commit-ish é '%s' do %s"
+
 msgid "need some commits to replay"
 msgstr "teastaíonn roinnt gealltanais chun athsheinm"
 
@@ -12479,27 +12534,16 @@
 "ní féidir leis an sprioc a chur chun cinn le foinsí iolracha toisc go mbeadh "
 "ordú"
 
-msgid ""
-"cannot implicitly determine whether this is an --advance or --onto operation"
-msgstr ""
-"ní féidir a chinneadh go hinneach an oibríocht --advance nó --onto é seo"
-
-msgid ""
-"cannot advance target with multiple source branches because ordering would "
-"be ill-defined"
-msgstr ""
-"ní féidir leis an sprioc a chur chun cinn le brainsí foinse iolracha mar go "
-"mbeadh ordú"
-
-msgid "cannot implicitly determine correct base for --onto"
-msgstr "ní féidir leis an mbonn ceart do --onto a chinneadh go hinneach"
+#, c-format
+msgid "invalid %s value: '%s'"
+msgstr "luach neamhbhailí %s: '%s'"
 
 msgid ""
 "(EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance "
-"<branch>) <revision-range>..."
+"<branch>) [--ref-action[=<mode>]] <revision-range>"
 msgstr ""
-"(TURGNAMHACH!) git replay ([--contained] --onto <newbase> | --advance "
-"<branch>) <revision-range>..."
+"(TURGNAMHACH!) athsheinm git ([--contained] --onto <newbase> | --advance "
+"<brainse>) [--ref-action[=<mód>]] <raon-athbhreithnithe>"
 
 msgid "make replay advance given branch"
 msgstr "athsheoladh a dhéanamh roimh ré brainse ar leith"
@@ -12507,8 +12551,12 @@
 msgid "replay onto given commit"
 msgstr "athsheoladh ar thiomantas a thugtar"
 
-msgid "advance all branches contained in revision-range"
-msgstr "gach brainse atá sa raon athbhreithnithe a chur chun cinn"
+msgid "update all branches that point at commits in <revision-range>"
+msgstr ""
+"nuashonraigh na brainsí uile a dhíríonn ar thiomnuithe i <revision-range>"
+
+msgid "control ref update behavior (update|print)"
+msgstr "iompar nuashonraithe tag rialaithe (nuashonrú|priontáil)"
 
 msgid "option --onto or --advance is mandatory"
 msgstr "tá rogha --onto nó --advance éigeantach"
@@ -12521,16 +12569,28 @@
 "cuirfear roinnt roghanna siúil rev a athshealbhú mar go gcuirfear giotán "
 "'%s' i 'struct rev_info' iallach"
 
+#, c-format
+msgid "failed to begin ref transaction: %s"
+msgstr "theip ar thosú an idirbhirt tagartha: %s"
+
 msgid "error preparing revisions"
 msgstr "earráid ag ullmhú athbhreith"
 
-msgid "replaying down to root commit is not supported yet!"
-msgstr "ní thacaítear le athsheinm síos go dtí tiomantas fréimhe fós!"
+msgid "replaying down from root commit is not supported yet!"
+msgstr "ní thacaítear le hathsheinm anuas ó fréamh-thiomantas go fóill!"
 
 msgid "replaying merge commits is not supported yet!"
 msgstr "ní thacaítear le gealltanna cumaisc athsheinm fós!"
 
 #, c-format
+msgid "failed to update ref '%s': %s"
+msgstr "theip ar an tagairt '%s' a nuashonrú: %s"
+
+#, c-format
+msgid "failed to commit ref transaction: %s"
+msgstr "theip ar an idirbheart tagartha a dhéanamh: %s"
+
+#, c-format
 msgid "key '%s' not found"
 msgstr "níor aimsíodh an eochair '%s'"
 
@@ -12544,9 +12604,15 @@
 msgid "synonym for --format=nul"
 msgstr "comhchiallach le --format=nul"
 
+msgid "print all keys/values"
+msgstr "priontáil na heochracha/luachanna go léir"
+
 msgid "unsupported output format"
 msgstr "formáid aschuir neamhthacaithe"
 
+msgid "--all and <key> cannot be used together"
+msgstr "Ní féidir --all agus <eochair> a úsáid le chéile"
+
 msgid "References"
 msgstr "Tagairtí"
 
@@ -12577,6 +12643,12 @@
 msgid "Blobs"
 msgstr "Blobaí"
 
+msgid "Inflated size"
+msgstr "Méid insilte"
+
+msgid "Disk size"
+msgstr "Méid diosca"
+
 msgid "Repository structure"
 msgstr "Struchtúr an stórais"
 
@@ -15087,6 +15159,10 @@
 msgid "bundle list at '%s' has no mode"
 msgstr "níl aon mhodh ag liosta beartán ag '%s'"
 
+#, c-format
+msgid "bundle list at '%s': bundle '%s' has no uri"
+msgstr "liosta beartán ag '%s': níl aon uri ag an mbeartán '%s'"
+
 msgid "failed to create temporary file"
 msgstr "theip ar chomhad sealadach a chruthú"
 
@@ -15109,6 +15185,10 @@
 msgstr "sháraigh teorainn athshlánaithe URI beartán (%d)"
 
 #, c-format
+msgid "bundle '%s' has no uri"
+msgstr "níl aon URI ag an bpacáiste '%s'"
+
+#, c-format
 msgid "failed to download bundle from URI '%s'"
 msgstr "theip ar an mbeartán a íoslódáil ó URI '%s'"
 
@@ -15615,8 +15695,8 @@
 msgid "Reuse recorded resolution of conflicted merges"
 msgstr "Réiteach taifeadta ar chumaisc coinbhleachta a athúsáid"
 
-msgid "Reset current HEAD to the specified state"
-msgstr "Athshocraigh HEAD reatha go dtí an stát sonraithe"
+msgid "Set `HEAD` or the index to a known state"
+msgstr "Socraigh `HEAD` nó an t-innéacs go staid aitheanta"
 
 msgid "Restore working tree files"
 msgstr "Athchóirigh comhaid crann oibre"
@@ -16034,11 +16114,11 @@
 
 #, c-format
 msgid ""
-"attempting to write a commit-graph, but 'commitGraph.changedPathsVersion' "
-"(%d) is not supported"
+"attempting to write a commit-graph, but 'commitGraph."
+"changedPathsVersion' (%d) is not supported"
 msgstr ""
-"ag iarraidh commit-graph a scríobh, ach tá 'commitGraph.changedPathsVersion' "
-"(%d) ní thacaítear leis"
+"ag iarraidh commit-graph a scríobh, ach tá 'commitGraph."
+"changedPathsVersion' (%d) ní thacaítear leis"
 
 msgid "too many commits to write graph"
 msgstr "an iomarca gealltanais graf a scríobh"
@@ -17131,10 +17211,6 @@
 msgstr "Luach anaithnid d'athróg cumraithe 'diff.submodule': '%s'"
 
 #, c-format
-msgid "unknown value for config '%s': %s"
-msgstr "luach anaithnid do chumraíocht '%s': %s"
-
-#, c-format
 msgid ""
 "Found errors in 'diff.dirstat' config variable:\n"
 "%s"
@@ -18190,8 +18266,8 @@
 "given pattern contains NULL byte (via -f <file>). This is only supported "
 "with -P under PCRE v2"
 msgstr ""
-"tá byte NULL (trí -f<file>) i bpatrún tugtha. Ní thacaítear leis seo ach le "
-"-P faoi PCRE v2"
+"tá byte NULL (trí -f<file>) i bpatrún tugtha. Ní thacaítear leis seo ach le -"
+"P faoi PCRE v2"
 
 #, c-format
 msgid "'%s': unable to read %s"
@@ -18357,8 +18433,8 @@
 msgstr ""
 "Rinneadh neamhaird ar an gcroca '%s' toisc nach bhfuil sé socraithe mar "
 "infheidhmithe.\n"
-"Is féidir leat an rabhadh seo a dhíchumasú le `git config set "
-"advice.ignoredHook false `."
+"Is féidir leat an rabhadh seo a dhíchumasú le `git config set advice."
+"ignoredHook false `."
 
 msgid "not a git repository"
 msgstr "ní stór git é"
@@ -19282,6 +19358,9 @@
 msgid "%s: failed to insert into database"
 msgstr "%s: theip ort a chur isteach sa bhunachar sonraí"
 
+msgid "cannot add a submodule of a different hash algorithm"
+msgstr "ní féidir fomhodúl d'algartam haise difriúil a chur leis"
+
 #, c-format
 msgid "%s: unsupported file type"
 msgstr "%s: cineál comhaid gan tacaíocht"
@@ -20757,6 +20836,10 @@
 msgid "--format=%.*s cannot be used with --python, --shell, --tcl"
 msgstr "--format=%.*s ní féidir é a úsáid le --python, --shell, --tcl"
 
+#, c-format
+msgid "parse_object_buffer failed on %s for %s"
+msgstr "theip ar parse_object_buffer ar %s do %s"
+
 msgid "failed to run 'describe'"
 msgstr "theip ar 'cur síos' a rith"
 
@@ -20788,10 +20871,6 @@
 msgstr "réad atá ar iarraidh %s do %s"
 
 #, c-format
-msgid "parse_object_buffer failed on %s for %s"
-msgstr "theip ar parse_object_buffer ar %s do %s"
-
-#, c-format
 msgid "malformed object at '%s'"
 msgstr "réad mífhoirmithe ag '%s'"
 
@@ -21099,6 +21178,18 @@
 msgstr "rs tagairt siombalach é refname %s, ní thacaítear leis a chóipeáil"
 
 #, c-format
+msgid "reftable stack for worktree '%s' is broken"
+msgstr "tá an stac ath-inúsáidte don chrann oibre '%s' briste"
+
+#, c-format
+msgid "could not create iterator for worktree '%s'"
+msgstr "níorbh fhéidir athrá a chruthú don chrann oibre '%s'"
+
+#, c-format
+msgid "could not read record for worktree '%s'"
+msgstr "níorbh fhéidir taifead a léamh don chrann oibre '%s'"
+
+#, c-format
 msgid "pattern '%s' has no '*'"
 msgstr "níl aon '*' ag patrún '%s'"
 
@@ -21537,6 +21628,10 @@
 msgstr ""
 "ní fhéadfadh sé rudaí pacáiste a chríochnú chun rudaí geallta a athphacáil"
 
+msgid "could not start pack-objects to repack promisor packs"
+msgstr ""
+"níorbh fhéidir pacáistí-réada a thosú chun pacáistí gealltanais a athphacáil"
+
 #, c-format
 msgid "pack prefix %s does not begin with objdir %s"
 msgstr "ní thosaíonn réimír pacáiste %s le objdir %s"
@@ -22540,10 +22635,6 @@
 msgid "illegal label name: '%.*s'"
 msgstr "ainm lipéid neamhdhleathach: '%.*s'"
 
-#, c-format
-msgid "could not resolve '%s'"
-msgstr "ní fhéadfaí '%s' a réiteach"
-
 msgid "writing fake root commit"
 msgstr "tiomantas fréamh bréige a scríobh"
 
@@ -23029,51 +23120,70 @@
 msgid "bad %s format: %%%.*s"
 msgstr "fhormáid %s olc: %%%.*s"
 
-#. TRANSLATORS: IEC 80000-13:2008 gibibyte
 #, c-format
-msgid "%u.%2.2u GiB"
-msgstr "%u.%2.2u GiB"
+msgid "%u.%2.2u"
+msgstr "%u.%2.2u"
 
-#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second
-#, c-format
-msgid "%u.%2.2u GiB/s"
-msgstr "%u.%2.2u GiB/s"
+#. TRANSLATORS: SI decimal prefix symbol for 10^9
+msgid "G"
+msgstr "G"
 
-#. TRANSLATORS: IEC 80000-13:2008 mebibyte
-#, c-format
-msgid "%u.%2.2u MiB"
-msgstr "%u.%2.2u MiB"
+#. TRANSLATORS: SI decimal prefix symbol for 10^6
+msgid "M"
+msgstr "M"
 
-#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second
-#, c-format
-msgid "%u.%2.2u MiB/s"
-msgstr "%u.%2.2u MiB/s"
+#. TRANSLATORS: SI decimal prefix symbol for 10^3
+msgid "k"
+msgstr "k"
 
-#. TRANSLATORS: IEC 80000-13:2008 kibibyte
-#, c-format
-msgid "%u.%2.2u KiB"
-msgstr "%u.%2.2u KiB"
+#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte
+msgid "GiB/s"
+msgstr "GiB/s"
 
-#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second
-#, c-format
-msgid "%u.%2.2u KiB/s"
-msgstr "%u.%2.2u KiB/s"
+msgid "GiB"
+msgstr "GiB"
 
-#. TRANSLATORS: IEC 80000-13:2008 byte
-#, c-format
-msgid "%u byte"
-msgid_plural "%u bytes"
-msgstr[0] "%u beart"
-msgstr[1] "%u beart"
-msgstr[2] "%u beart"
+#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte
+msgid "MiB/s"
+msgstr "MiB/s"
+
+msgid "MiB"
+msgstr "MiB"
+
+#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte
+msgid "KiB/s"
+msgstr "KiB/s"
+
+msgid "KiB"
+msgstr "KiB"
+
+#. TRANSLATORS: IEC 80000-13:2008 byte/second and byte
+msgid "B/s"
+msgstr "B/s"
+
+msgid "B"
+msgstr "B"
 
 #. TRANSLATORS: IEC 80000-13:2008 byte/second
+msgid "byte/s"
+msgid_plural "bytes/s"
+msgstr[0] "beart/s"
+msgstr[1] "bearta/s"
+msgstr[2] "bearta/s"
+
+#. TRANSLATORS: IEC 80000-13:2008 byte
+msgid "byte"
+msgid_plural "bytes"
+msgstr[0] "beart"
+msgstr[1] "bearta"
+msgstr[2] "bearta"
+
+#. TRANSLATORS: The first argument is the number string. The second
+#. argument is the unit string (i.e. "12.34 MiB/s").
+#.
 #, c-format
-msgid "%u byte/s"
-msgid_plural "%u bytes/s"
-msgstr[0] "%u beart/s"
-msgstr[1] "%u beart/s"
-msgstr[2] "%u beart/s"
+msgid "%s %s"
+msgstr "%s %s"
 
 #, c-format
 msgid "ignoring suspicious submodule name: %s"
@@ -23368,9 +23478,6 @@
 msgid "number of requests per thread"
 msgstr "líon iarratais in aghaidh an snáithe"
 
-msgid "byte"
-msgstr "beart"
-
 msgid "ballast character"
 msgstr "carachtar balasta"
 
@@ -24989,6 +25096,86 @@
 msgid "Do you really want to send %s? [y|N]: "
 msgstr "An bhfuil tú ag iarraidh %s a sheoladh i ndáiríre? [y|N]: "
 
+#, c-format
+#~ msgid "could not resolve %s"
+#~ msgstr "ní fhéadfaí %s a réiteach"
+
+#, c-format
+#~ msgid "Checking %s link"
+#~ msgstr "Nasc %s a sheiceáil"
+
+#, c-format
+#~ msgid "%s points to something strange (%s)"
+#~ msgstr "Léiríonn %s rud éigin aisteach (%s)"
+
+#, c-format
+#~ msgid "%s: detached HEAD points at nothing"
+#~ msgstr "%s: pointí HEAD scoite ag aon rud"
+
+#, c-format
+#~ msgid "notice: %s points to an unborn branch (%s)"
+#~ msgstr "fógra: Díríonn %s chuig brainse neamhbhreithe (%s)"
+
+#, c-format
+#~ msgid "%s: object missing"
+#~ msgstr "%s: réad ar iarraidh"
+
+#~ msgid ""
+#~ "cannot implicitly determine whether this is an --advance or --onto "
+#~ "operation"
+#~ msgstr ""
+#~ "ní féidir a chinneadh go hinneach an oibríocht --advance nó --onto é seo"
+
+#~ msgid ""
+#~ "cannot advance target with multiple source branches because ordering "
+#~ "would be ill-defined"
+#~ msgstr ""
+#~ "ní féidir leis an sprioc a chur chun cinn le brainsí foinse iolracha mar "
+#~ "go mbeadh ordú"
+
+#~ msgid "cannot implicitly determine correct base for --onto"
+#~ msgstr "ní féidir leis an mbonn ceart do --onto a chinneadh go hinneach"
+
+#~ msgid "advance all branches contained in revision-range"
+#~ msgstr "gach brainse atá sa raon athbhreithnithe a chur chun cinn"
+
+#~ msgid "Reset current HEAD to the specified state"
+#~ msgstr "Athshocraigh HEAD reatha go dtí an stát sonraithe"
+
+#, c-format
+#~ msgid "%u.%2.2u GiB/s"
+#~ msgstr "%u.%2.2u GiB/s"
+
+#, c-format
+#~ msgid "%u.%2.2u MiB"
+#~ msgstr "%u.%2.2u MiB"
+
+#, c-format
+#~ msgid "%u.%2.2u MiB/s"
+#~ msgstr "%u.%2.2u MiB/s"
+
+#, c-format
+#~ msgid "%u.%2.2u KiB"
+#~ msgstr "%u.%2.2u KiB"
+
+#, c-format
+#~ msgid "%u.%2.2u KiB/s"
+#~ msgstr "%u.%2.2u KiB/s"
+
+#, c-format
+#~ msgid "%u byte"
+#~ msgid_plural "%u bytes"
+#~ msgstr[0] "%u beart"
+#~ msgstr[1] "%u beart"
+#~ msgstr[2] "%u beart"
+
+#, c-format
+#~ msgid "%u byte/s"
+#~ msgid_plural "%u bytes/s"
+#~ msgstr[0] "%u beart/s"
+#~ msgstr[1] "%u beart/s"
+#~ msgstr[2] "%u beart/s"
+
 #~ msgid "No previous hunk"
 #~ msgstr "Níl aon hunk roimhe seo"
 
diff --git a/po/id.po b/po/id.po
index b85b11c..8a171cc 100644
--- a/po/id.po
+++ b/po/id.po
@@ -7,8 +7,8 @@
 msgstr ""
 "Project-Id-Version: Git\n"
 "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2025-11-06 23:58+0000\n"
-"PO-Revision-Date: 2025-11-13 08:58+0700\n"
+"POT-Creation-Date: 2026-01-22 23:57+0000\n"
+"PO-Revision-Date: 2025-01-27 09:10+0700\n"
 "Last-Translator: Bagas Sanjaya <bagasdotme@gmail.com>\n"
 "Language-Team: Indonesian\n"
 "Language: id\n"
@@ -957,9 +957,9 @@
 #: builtin/fast-export.c builtin/fetch.c builtin/help.c builtin/index-pack.c
 #: builtin/init-db.c builtin/log.c builtin/ls-files.c builtin/merge-base.c
 #: builtin/merge-tree.c builtin/merge.c builtin/rebase.c builtin/repack.c
-#: builtin/replay.c builtin/reset.c builtin/rev-parse.c builtin/show-branch.c
-#: builtin/stash.c builtin/submodule--helper.c builtin/tag.c builtin/worktree.c
-#: parse-options.c range-diff.c revision.c
+#: builtin/reset.c builtin/rev-parse.c builtin/show-branch.c builtin/stash.c
+#: builtin/submodule--helper.c builtin/tag.c builtin/worktree.c parse-options.c
+#: range-diff.c revision.c
 #, c-format
 msgid "options '%s' and '%s' cannot be used together"
 msgstr "Opsi '%s' dan '%s' tidak dapat digunakan bersamaan"
@@ -2134,8 +2134,8 @@
 msgstr "'%s' bukan nama cabang valid"
 
 #: branch.c builtin/branch.c
-msgid "See `man git check-ref-format`"
-msgstr "Lihat `man git check-ref-format`"
+msgid "See 'git help check-ref-format'"
+msgstr "Lihat `git help check-ref-format`"
 
 #: branch.c
 #, c-format
@@ -3276,6 +3276,19 @@
 msgid "must end with a color"
 msgstr "harus berakhir dengan warna"
 
+#: builtin/blame.c diff.c merge-ort.c transport.c
+#, c-format
+msgid "unknown value for config '%s': %s"
+msgstr "nilai tidak dikenal untuk konfigurasi '%s': %s"
+
+#: builtin/blame.c builtin/merge-file.c diff.c
+msgid ""
+"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and "
+"\"histogram\""
+msgstr ""
+"opsi diff-algorithm terima \"myers\", \"minimal\", \"patience\" dan "
+"\"histogram\""
+
 #: builtin/blame.c
 #, c-format
 msgid "cannot find revision %s to ignore"
@@ -3347,6 +3360,14 @@
 msgid "ignore whitespace differences"
 msgstr "abaikan perbedaan spasi putih"
 
+#: builtin/blame.c builtin/merge-file.c diff.c
+msgid "<algorithm>"
+msgstr "<algoritma>"
+
+#: builtin/blame.c builtin/merge-file.c diff.c
+msgid "choose a diff algorithm"
+msgstr "pilih algoritma diff"
+
 #: builtin/blame.c builtin/clone.c builtin/log.c
 msgid "rev"
 msgstr "revisi"
@@ -3368,8 +3389,8 @@
 msgstr "warnai baris oleh umur"
 
 #: builtin/blame.c
-msgid "spend extra cycles to find better match"
-msgstr "perlihatkan siklus ekstra untuk menemukan cocokan yang lebih baik"
+msgid "spend extra cycles to find a better match"
+msgstr "habiskan siklus tambahan untuk mencari pasangan yang lebih baik"
 
 #: builtin/blame.c
 msgid "use revisions from <file> instead of calling git-rev-list"
@@ -3922,7 +3943,7 @@
 "Anda dapat menghapus baris-baris yang Anda tidak ingin dibagi.\n"
 
 #: builtin/bugreport.c builtin/commit.c builtin/fast-export.c
-#: builtin/pack-objects.c builtin/rebase.c parse-options.h
+#: builtin/pack-objects.c builtin/rebase.c builtin/replay.c parse-options.h
 msgid "mode"
 msgstr "mode"
 
@@ -4857,10 +4878,10 @@
 msgid "missing branch name; try -%c"
 msgstr "kehilangan nama cabang; coba -%c"
 
-#: builtin/checkout.c
+#: builtin/checkout.c sequencer.c
 #, c-format
-msgid "could not resolve %s"
-msgstr "tidak dapat menyelesaikan %s"
+msgid "could not resolve '%s'"
+msgstr "tidak dapat menguraikan '%s'"
 
 #: builtin/checkout.c
 msgid "invalid path specification"
@@ -6715,10 +6736,18 @@
 #, c-format
 msgid ""
 "cannot overwrite multiple values with a single value\n"
-"       Use a regexp, --add or --replace-all to change %s."
+"       Use --value=<pattern>, --append or --all to change %s."
 msgstr ""
 "tidak dapat menimpa banyak nilai dengan nilai tunggal\n"
-"       Gunakan regexp, --add atau --replace-all untuk mengubah %s."
+"       Gunakan --value=<pola>, --append atau --all untuk mengubah %s."
+
+#: builtin/config.c
+msgid "unset all multi-valued config options"
+msgstr "batal setel semua opsi konfigurasi multi-nilai"
+
+#: builtin/config.c
+msgid "unset multi-valued config options with matching values"
+msgstr "batal setel opsi konfigurasi multi-nilai dengan nilai yang sepadan"
 
 #: builtin/config.c
 #, c-format
@@ -6832,6 +6861,15 @@
 "--comment hanya dapat diterapkan pada operasi penambahan/penyetelan/"
 "penggantian"
 
+#: builtin/config.c
+#, c-format
+msgid ""
+"cannot overwrite multiple values with a single value\n"
+"       Use a regexp, --add or --replace-all to change %s."
+msgstr ""
+"tidak dapat menimpa banyak nilai dengan nilai tunggal\n"
+"       Gunakan regexp, --add atau --replace-all untuk mengubah %s."
+
 #: builtin/count-objects.c
 msgid "print sizes in human readable format"
 msgstr "cetak ukuran dalam format yang bisa dibaca manusia"
@@ -7344,13 +7382,6 @@
 
 #: builtin/fast-export.c
 #, c-format
-msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it"
-msgstr ""
-"menemukan komit bertandatangan %s; gunakan --signed-commits=<mode> untuk "
-"menanganinya"
-
-#: builtin/fast-export.c
-#, c-format
 msgid "exporting %<PRIuMAX> signature(s) for commit %s"
 msgstr "mengekspor %<PRIuMAX> tandatangan untuk komit %s"
 
@@ -7361,6 +7392,21 @@
 
 #: builtin/fast-export.c
 #, c-format
+msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it"
+msgstr ""
+"menemukan komit bertandatangan %s; gunakan --signed-commits=<mode> untuk "
+"menanganinya"
+
+#: builtin/fast-export.c
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-export with --signed-"
+"commits=<mode>"
+msgstr ""
+"'strip-if-invalid' bukan mode valid untuk git fast-export dengan --signed-"
+"commits=<mode>"
+
+#: builtin/fast-export.c
+#, c-format
 msgid ""
 "omitting tag %s,\n"
 "since tags of trees (or tags of tags of trees, etc.) are not supported."
@@ -7375,13 +7421,6 @@
 
 #: builtin/fast-export.c
 #, c-format
-msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it"
-msgstr ""
-"menemukan tag bertandatangan %s; gunakan --signed-tags=<mode> untuk "
-"menanganinya"
-
-#: builtin/fast-export.c
-#, c-format
 msgid "exporting signed tag %s"
 msgstr "mengekspor tag bertandatangan %s"
 
@@ -7392,6 +7431,21 @@
 
 #: builtin/fast-export.c
 #, c-format
+msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it"
+msgstr ""
+"menemukan tag bertandatangan %s; gunakan --signed-tags=<mode> untuk "
+"menanganinya"
+
+#: builtin/fast-export.c
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-export with --signed-"
+"tags=<mode>"
+msgstr ""
+"'strip-if-invalid' bukan mode valid untuk git fast-export dengan --signed-"
+"tags=<mode>"
+
+#: builtin/fast-export.c
+#, c-format
 msgid ""
 "tag %s tags unexported object; use --tag-of-filtered-object=<mode> to handle "
 "it"
@@ -7883,6 +7937,37 @@
 msgid "parse_one_signature() returned unknown hash algo"
 msgstr "parse_one_signature() mengembalikan algoritma hash tak dikenal"
 
+#: builtin/fast-import.c builtin/fsck.c
+msgid "unknown"
+msgstr "tidak dikenal"
+
+#: builtin/fast-import.c
+#, c-format
+msgid ""
+"stripping invalid signature for commit '%.100s...'\n"
+"  allegedly by %s"
+msgstr ""
+"mengupas tandatangan tidak valid untuk komit '%.100s...'\n"
+"  yang diduga dibuat oleh %s"
+
+#: builtin/fast-import.c
+#, c-format
+msgid ""
+"stripping invalid signature for commit '%.*s'\n"
+"  allegedly by %s"
+msgstr ""
+"mengupas tandatangan tidak valid untuk komit '%.*s'\n"
+"  yang diduga dibuat oleh %s"
+
+#: builtin/fast-import.c
+#, c-format
+msgid ""
+"stripping invalid signature for commit\n"
+"  allegedly by %s"
+msgstr ""
+"mengupas tandatangan tidak valid untuk komit\n"
+"  yang diduga dibuat oleh %s"
+
 #: builtin/fast-import.c
 msgid "expected committer but didn't get one"
 msgstr "pengkomit diharapkan tapi tidak mendapatkannya"
@@ -7902,11 +7987,6 @@
 msgstr "mengimpor tandatangan komit secara verbatim"
 
 #: builtin/fast-import.c
-msgid "encountered signed tag; use --signed-tags=<mode> to handle it"
-msgstr ""
-"menemukan tag bertandatangan; gunakan --signed-tags=<mode> untuk menanganinya"
-
-#: builtin/fast-import.c
 #, c-format
 msgid "importing a tag signature verbatim for tag '%s'"
 msgstr "mengimpor tag secara verbatim untuk tag '%s'"
@@ -7917,6 +7997,19 @@
 msgstr "mengupas tandatangan tag untuk tag '%s'"
 
 #: builtin/fast-import.c
+msgid "encountered signed tag; use --signed-tags=<mode> to handle it"
+msgstr ""
+"menemukan tag bertandatangan; gunakan --signed-tags=<mode> untuk menanganinya"
+
+#: builtin/fast-import.c
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-import with --signed-"
+"tags=<mode>"
+msgstr ""
+"'strip-if-invalid' bukan mode valid untuk git fast-import dengan --signed-"
+"tags=<mode>"
+
+#: builtin/fast-import.c
 #, c-format
 msgid "expected 'from' command, got '%s'"
 msgstr "perintah 'from' diharapkan, dapat '%s'"
@@ -8114,8 +8207,8 @@
 msgstr "git fetch [<opsi>] <grup>"
 
 #: builtin/fetch.c
-msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]"
-msgstr "git fetch --multiple [<opsi>] [(<repositori> | <grup>)]"
+msgid "git fetch --multiple [<options>] [(<repository>|<group>)...]"
+msgstr "git fetch --multiple [<opsi>] [(<repositori> | <grup>)...]"
 
 #: builtin/fetch.c
 msgid "git fetch --all [<options>]"
@@ -8708,10 +8801,6 @@
 msgid "got bad config --config=%s"
 msgstr "dapat konfigurasi jelek --config=%s"
 
-#: builtin/fsck.c
-msgid "unknown"
-msgstr "tidak dikenal"
-
 #. TRANSLATORS: e.g. error in tree 01bfda: <more explanation>
 #: builtin/fsck.c
 #, c-format
@@ -8831,6 +8920,11 @@
 msgstr "%s: bukan sebuah komit"
 
 #: builtin/fsck.c
+#, c-format
+msgid "invalid parameter: expected sha1, got '%s'"
+msgstr "parameter tidak valid: sha1 diharapkan, dapat '%s'"
+
+#: builtin/fsck.c
 msgid "notice: No default references"
 msgstr "catatan: Tidak ada referensi asali"
 
@@ -8864,31 +8958,6 @@
 
 #: builtin/fsck.c
 #, c-format
-msgid "Checking %s link"
-msgstr "Memeriksa tautan %s"
-
-#: builtin/fsck.c builtin/index-pack.c
-#, c-format
-msgid "invalid %s"
-msgstr "%s tidak valid"
-
-#: builtin/fsck.c
-#, c-format
-msgid "%s points to something strange (%s)"
-msgstr "%s menunjuk ke sesuatu yang aneh (%s)"
-
-#: builtin/fsck.c
-#, c-format
-msgid "%s: detached HEAD points at nothing"
-msgstr "%s: HEAD terpisah tidak menunjuk ke apapun"
-
-#: builtin/fsck.c
-#, c-format
-msgid "notice: %s points to an unborn branch (%s)"
-msgstr "catatan: %s menunjuk ke cabang yang belum lahir (%s)"
-
-#: builtin/fsck.c
-#, c-format
 msgid "Checking cache tree of %s"
 msgstr "Memeriksa pohon tembolok %s"
 
@@ -8988,16 +9057,6 @@
 msgid "Checking objects"
 msgstr "Memeriksa objek"
 
-#: builtin/fsck.c
-#, c-format
-msgid "%s: object missing"
-msgstr "%s: objek hilang"
-
-#: builtin/fsck.c
-#, c-format
-msgid "invalid parameter: expected sha1, got '%s'"
-msgstr "parameter tidak valid: sha1 diharapkan, dapat '%s'"
-
 #: builtin/fsmonitor--daemon.c
 msgid "git fsmonitor--daemon start [<options>]"
 msgstr "git fsmonitor--daemon start [<opsi>]"
@@ -9478,6 +9537,10 @@
 msgstr "gagal menambahkan repositori ke konfigurasi global"
 
 #: builtin/gc.c
+msgid "check a specific task"
+msgstr "periksa tugas yang spesifik"
+
+#: builtin/gc.c
 msgid "git maintenance <subcommand> [<options>]"
 msgstr "git maintenance <subperintah> [<opsi>]"
 
@@ -10060,6 +10123,11 @@
 
 #: builtin/index-pack.c
 #, c-format
+msgid "invalid %s"
+msgstr "%s tidak valid"
+
+#: builtin/index-pack.c
+#, c-format
 msgid "Not all child objects of %s are reachable"
 msgstr "Tidak semua objek anak dari %s bisa dicapai"
 
@@ -10234,6 +10302,11 @@
 msgid "--verify with no packfile name given"
 msgstr "--verify tanpa nama berkas paket diberikan"
 
+#: builtin/index-pack.c
+msgid "cannot perform queued object checks outside of a repository"
+msgstr ""
+"tidak dapat melakukan pemeriksaan objek yang mengantre di luar repositori"
+
 #: builtin/index-pack.c builtin/unpack-objects.c
 msgid "fsck error in pack objects"
 msgstr "kesalahan fsck dalam objek paket"
@@ -11208,14 +11281,6 @@
 "git merge-file [<opsi>] [-L <nama 1> [-L <asli> [-L <nama 2>]]] <berkas 1> "
 "<berkas asli> <berkas 2>"
 
-#: builtin/merge-file.c diff.c
-msgid ""
-"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and "
-"\"histogram\""
-msgstr ""
-"opsi diff-algorithm terima \"myers\", \"minimal\", \"patience\" dan "
-"\"histogram\""
-
 #: builtin/merge-file.c
 msgid "send results to standard output"
 msgstr "kirim hasil ke keluaran standar"
@@ -11232,14 +11297,6 @@
 msgid "use a zealous diff3 based merge"
 msgstr "gunakan penggabungan berdasarkan diff3 yang bersemangat"
 
-#: builtin/merge-file.c diff.c
-msgid "<algorithm>"
-msgstr "<algoritma>"
-
-#: builtin/merge-file.c diff.c
-msgid "choose a diff algorithm"
-msgstr "pilih algoritma diff"
-
 #: builtin/merge-file.c
 msgid "for conflicts, use this marker size"
 msgstr "untuk konflik, gunakan ukuran penanda ini"
@@ -12594,6 +12651,11 @@
 
 #: builtin/pack-objects.c
 #, c-format
+msgid "packfile %s is a promisor but --exclude-promisor-objects was given"
+msgstr "berkas pak %s adalah pejanji tapi --exclude-promisor-objects diberikan"
+
+#: builtin/pack-objects.c
+#, c-format
 msgid "could not find pack '%s'"
 msgstr "tidak dapat menemukan pak '%s'"
 
@@ -12930,12 +12992,12 @@
 msgstr "git patch-id [--stable | --unstable | --verbatim]"
 
 #: builtin/patch-id.c
-msgid "use the unstable patch-id algorithm"
-msgstr "gunakan algoritma id tambalan tidak stabil"
+msgid "use the unstable patch ID algorithm"
+msgstr "gunakan algoritma ID tambalan yang tidak stabil"
 
 #: builtin/patch-id.c
-msgid "use the stable patch-id algorithm"
-msgstr "gunakan algoritma id tambalan stabil"
+msgid "use the stable patch ID algorithm"
+msgstr "gunakan algoritma ID tambalan yang stabil"
 
 #: builtin/patch-id.c
 msgid "don't strip whitespace from the patch"
@@ -12966,50 +13028,6 @@
 msgstr "git pull [<opsi>] [<repositori> [<spek referensi>]]"
 
 #: builtin/pull.c
-msgid "control for recursive fetching of submodules"
-msgstr "kontrol pengambilan rekursif submodul"
-
-#: builtin/pull.c
-msgid "Options related to merging"
-msgstr "Opsi yang berkaitan dengan penggabungan"
-
-#: builtin/pull.c
-msgid "incorporate changes by rebasing rather than merging"
-msgstr "masukkan perubahan dengan pendasaran ulang daripada penggabungan"
-
-#: builtin/pull.c builtin/revert.c
-msgid "allow fast-forward"
-msgstr "perbolehkan maju cepat"
-
-#: builtin/pull.c
-msgid "control use of pre-merge-commit and commit-msg hooks"
-msgstr "kontrol penggunaan kail pre-merge-commit dan commit-msg"
-
-#: builtin/pull.c parse-options.h
-msgid "automatically stash/stash pop before and after"
-msgstr "stash/stash pop otomatis sebelum dan sesudah"
-
-#: builtin/pull.c
-msgid "Options related to fetching"
-msgstr "Opsi yang berkaitan dengan pengambilan"
-
-#: builtin/pull.c
-msgid "force overwrite of local branch"
-msgstr "paksa timpa cabang lokal"
-
-#: builtin/pull.c
-msgid "number of submodules pulled in parallel"
-msgstr "nomor submodul ditarik dalam paralel"
-
-#: builtin/pull.c parse-options.h
-msgid "use IPv4 addresses only"
-msgstr "gunakan hanya alamat IPv4"
-
-#: builtin/pull.c parse-options.h
-msgid "use IPv6 addresses only"
-msgstr "gunakan hanya alamat IPv6"
-
-#: builtin/pull.c
 msgid ""
 "There is no candidate for rebasing against among the refs that you just "
 "fetched."
@@ -13133,6 +13151,50 @@
 "asali terkonfigurasi untuk setiap invokasi.\n"
 
 #: builtin/pull.c
+msgid "control for recursive fetching of submodules"
+msgstr "kontrol pengambilan rekursif submodul"
+
+#: builtin/pull.c
+msgid "Options related to merging"
+msgstr "Opsi yang berkaitan dengan penggabungan"
+
+#: builtin/pull.c
+msgid "incorporate changes by rebasing rather than merging"
+msgstr "masukkan perubahan dengan pendasaran ulang daripada penggabungan"
+
+#: builtin/pull.c builtin/revert.c
+msgid "allow fast-forward"
+msgstr "perbolehkan maju cepat"
+
+#: builtin/pull.c
+msgid "control use of pre-merge-commit and commit-msg hooks"
+msgstr "kontrol penggunaan kail pre-merge-commit dan commit-msg"
+
+#: builtin/pull.c parse-options.h
+msgid "automatically stash/stash pop before and after"
+msgstr "stash/stash pop otomatis sebelum dan sesudah"
+
+#: builtin/pull.c
+msgid "Options related to fetching"
+msgstr "Opsi yang berkaitan dengan pengambilan"
+
+#: builtin/pull.c
+msgid "force overwrite of local branch"
+msgstr "paksa timpa cabang lokal"
+
+#: builtin/pull.c
+msgid "number of submodules pulled in parallel"
+msgstr "nomor submodul ditarik dalam paralel"
+
+#: builtin/pull.c parse-options.h
+msgid "use IPv4 addresses only"
+msgstr "gunakan hanya alamat IPv4"
+
+#: builtin/pull.c parse-options.h
+msgid "use IPv6 addresses only"
+msgstr "gunakan hanya alamat IPv6"
+
+#: builtin/pull.c
 msgid "Updating an unborn branch with changes added to the index."
 msgstr ""
 "Memperbarui cabang yang belum lahir dengan perubahan yang ditambahkan ke "
@@ -15421,6 +15483,11 @@
 msgstr "hanya satu pola yang dapat diberikan dengan -l"
 
 #: builtin/replay.c
+#, c-format
+msgid "'%s' is not a valid commit-ish for %s"
+msgstr "'%s' bukan mirip komit yang valid untuk %s"
+
+#: builtin/replay.c
 msgid "need some commits to replay"
 msgstr "butuh beberapa komit untuk dimainkan ulang"
 
@@ -15441,31 +15508,17 @@
 "menjadi tidak jelas"
 
 #: builtin/replay.c
-msgid ""
-"cannot implicitly determine whether this is an --advance or --onto operation"
-msgstr ""
-"tidak dapat menentukan secara tidak langsung apakah ini operasi --advance "
-"atau --onto"
-
-#: builtin/replay.c
-msgid ""
-"cannot advance target with multiple source branches because ordering would "
-"be ill-defined"
-msgstr ""
-"tidak dapat memajukan target dengan banyak cabang sumber karena "
-"pengurutannya akan menjadi tidak jelas"
-
-#: builtin/replay.c
-msgid "cannot implicitly determine correct base for --onto"
-msgstr "tidak dapat menentukan secara tidak langsung dasar untuk --onto"
+#, c-format
+msgid "invalid %s value: '%s'"
+msgstr "nilai %s tidak valid: '%s'"
 
 #: builtin/replay.c
 msgid ""
 "(EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance "
-"<branch>) <revision-range>..."
+"<branch>) [--ref-action[=<mode>]] <revision-range>"
 msgstr ""
 "(EKSPERIMENTAL!) git replay ([--contained] --onto <dasar baru> | --advance "
-"<cabang>) <rentang revisi>..."
+"<cabang>) [--ref-action[=<mode>]] <rentang revisi>"
 
 #: builtin/replay.c
 msgid "make replay advance given branch"
@@ -15476,8 +15529,13 @@
 msgstr "mainkan ulang pada komit yang diberikan"
 
 #: builtin/replay.c
-msgid "advance all branches contained in revision-range"
-msgstr "majukan semua cabang yang berada pada rentang komit"
+msgid "update all branches that point at commits in <revision-range>"
+msgstr ""
+"perbarui semua cabang yang menunjuk pada komit di dalam <rentang revisi>"
+
+#: builtin/replay.c
+msgid "control ref update behavior (update|print)"
+msgstr "atur perilaku pembaruan referensi (update|print)"
 
 #: builtin/replay.c
 msgid "option --onto or --advance is mandatory"
@@ -15493,17 +15551,32 @@
 "rev_info' akan dipaksakan"
 
 #: builtin/replay.c
+#, c-format
+msgid "failed to begin ref transaction: %s"
+msgstr "gagal memulai transaksi referensi: %s"
+
+#: builtin/replay.c
 msgid "error preparing revisions"
 msgstr "kesalahan menyiapkan revisi"
 
 #: builtin/replay.c
-msgid "replaying down to root commit is not supported yet!"
-msgstr "memainkan ulang ke komit akar belum didukung!"
+msgid "replaying down from root commit is not supported yet!"
+msgstr "memainkan ulang dari komit akar belum didukung!"
 
 #: builtin/replay.c
 msgid "replaying merge commits is not supported yet!"
 msgstr "memainkan ulang komit penggabungan belum didukung!"
 
+#: builtin/replay.c
+#, c-format
+msgid "failed to update ref '%s': %s"
+msgstr "gagal memperbarui referensi '%s': %s"
+
+#: builtin/replay.c
+#, c-format
+msgid "failed to commit ref transaction: %s"
+msgstr "gagal mengkomit transaksi referensi: %s"
+
 #: builtin/repo.c
 #, c-format
 msgid "key '%s' not found"
@@ -15523,10 +15596,18 @@
 msgstr "sinonim untuk --format=nul"
 
 #: builtin/repo.c
+msgid "print all keys/values"
+msgstr "cetak semua kunci/nilai"
+
+#: builtin/repo.c
 msgid "unsupported output format"
 msgstr "format keluaran tidak didukung"
 
 #: builtin/repo.c
+msgid "--all and <key> cannot be used together"
+msgstr "--all dan <kunci> tidak dapat digunakan bersamaan"
+
+#: builtin/repo.c
 msgid "References"
 msgstr "Referensi"
 
@@ -15567,6 +15648,14 @@
 msgstr "Blob"
 
 #: builtin/repo.c
+msgid "Inflated size"
+msgstr "Ukuran yang berkembang"
+
+#: builtin/repo.c
+msgid "Disk size"
+msgstr "Ukuran disk"
+
+#: builtin/repo.c
 msgid "Repository structure"
 msgstr "Struktur repositori"
 
@@ -17604,7 +17693,7 @@
 msgid "'%s' already exists in the index and is not a submodule"
 msgstr "'%s' sudah ada di dalam indeks dan bukan submodul"
 
-#: builtin/submodule--helper.c read-cache.c
+#: builtin/submodule--helper.c object-file.c
 #, c-format
 msgid "'%s' does not have a commit checked out"
 msgstr "'%s' tidak punya sebuah komit tercheckout"
@@ -18675,6 +18764,11 @@
 msgstr "daftar bundel pada '%s' tidak punya mode"
 
 #: bundle-uri.c
+#, c-format
+msgid "bundle list at '%s': bundle '%s' has no uri"
+msgstr "daftar bundel pada '%s': bundel '%s' tidak punya uri"
+
+#: bundle-uri.c
 msgid "failed to create temporary file"
 msgstr "tidak dapat membuat berkas sementara"
 
@@ -18703,6 +18797,11 @@
 
 #: bundle-uri.c
 #, c-format
+msgid "bundle '%s' has no uri"
+msgstr "bundel '%s' tidak punya uri"
+
+#: bundle-uri.c
+#, c-format
 msgid "failed to download bundle from URI '%s'"
 msgstr "gagal mengunduh bundel dari URI '%s'"
 
@@ -19357,8 +19456,8 @@
 msgstr "Gunakan ulang resolusi konflik penggabungan terekam"
 
 #: command-list.h
-msgid "Reset current HEAD to the specified state"
-msgstr "Setel ulang HEAD saat ini ke keadaan yang disebutkan"
+msgid "Set `HEAD` or the index to a known state"
+msgstr "Setel `HEAD` atau indeks ke keadaan yang diketahui"
 
 #: command-list.h
 msgid "Restore working tree files"
@@ -21243,11 +21342,6 @@
 msgid "Unknown value for 'diff.submodule' config variable: '%s'"
 msgstr "Nilai tidak dikenal untuk variabel konfigurasi 'diff.submodule': '%s'"
 
-#: diff.c merge-ort.c transport.c
-#, c-format
-msgid "unknown value for config '%s': %s"
-msgstr "nilai tidak dikenal untuk konfigurasi '%s': %s"
-
 #: diff.c
 #, c-format
 msgid ""
@@ -23872,6 +23966,10 @@
 msgstr "%s: gagal memasukkan ke dalam basis data"
 
 #: object-file.c
+msgid "cannot add a submodule of a different hash algorithm"
+msgstr "tidak dapat menambahkan submodul dari algoritma hash yang berbeda"
+
+#: object-file.c
 #, c-format
 msgid "%s: unsupported file type"
 msgstr "%s: tipe berkas tidak didukung"
@@ -25642,6 +25740,11 @@
 msgstr "--format=%.*s tidak dapat digunakan dengan --python, --shell, --tcl"
 
 #: ref-filter.c
+#, c-format
+msgid "parse_object_buffer failed on %s for %s"
+msgstr "parse_object_buffer gagal pada %s untuk %s"
+
+#: ref-filter.c
 msgid "failed to run 'describe'"
 msgstr "gagal menjalankan 'describe'"
 
@@ -25681,11 +25784,6 @@
 
 #: ref-filter.c
 #, c-format
-msgid "parse_object_buffer failed on %s for %s"
-msgstr "parse_object_buffer gagal pada %s untuk %s"
-
-#: ref-filter.c
-#, c-format
 msgid "malformed object at '%s'"
 msgstr "objek rusak pada '%s'"
 
@@ -26046,6 +26144,21 @@
 msgid "refname %s is a symbolic ref, copying it is not supported"
 msgstr "nama referensi %s simbolik, menyalinnya tidak didukung"
 
+#: refs/reftable-backend.c
+#, c-format
+msgid "reftable stack for worktree '%s' is broken"
+msgstr "tumpukan tabel referensi untuk pohon kerja '%s' rusak"
+
+#: refs/reftable-backend.c
+#, c-format
+msgid "could not create iterator for worktree '%s'"
+msgstr "tidak dapat membuat iterator untuk pohon kerja '%s'"
+
+#: refs/reftable-backend.c
+#, c-format
+msgid "could not read record for worktree '%s'"
+msgstr "tidak dapat membaca rekaman untuk pohon kerja '%s'"
+
 #: refspec.c
 #, c-format
 msgid "pattern '%s' has no '*'"
@@ -26559,6 +26672,10 @@
 msgstr ""
 "tidak dapat menyelesaikan pack-objects untuk mengepak ulang objek pejanji"
 
+#: repack-promisor.c
+msgid "could not start pack-objects to repack promisor packs"
+msgstr "tidak dapat memulai pack-objects untuk mengepak ulang pak pejanji"
+
 #: repack.c
 #, c-format
 msgid "pack prefix %s does not begin with objdir %s"
@@ -27785,11 +27902,6 @@
 msgstr "nama label ilegal: '%.*s'"
 
 #: sequencer.c
-#, c-format
-msgid "could not resolve '%s'"
-msgstr "tidak dapat menguraikan '%s'"
-
-#: sequencer.c
 msgid "writing fake root commit"
 msgstr "menulis komit akar palsu"
 
@@ -28373,57 +28485,83 @@
 msgid "bad %s format: %%%.*s"
 msgstr "format %s jelek: %%%.*s"
 
-#. TRANSLATORS: IEC 80000-13:2008 gibibyte
 #: strbuf.c
 #, c-format
-msgid "%u.%2.2u GiB"
-msgstr "%u.%2.2u gibibita"
+msgid "%u.%2.2u"
+msgstr "%u.%2.2u"
 
-#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second
+#. TRANSLATORS: SI decimal prefix symbol for 10^9
 #: strbuf.c
-#, c-format
-msgid "%u.%2.2u GiB/s"
-msgstr "%u.%2.2u gibibita/detik"
+msgid "G"
+msgstr "G"
 
-#. TRANSLATORS: IEC 80000-13:2008 mebibyte
+#. TRANSLATORS: SI decimal prefix symbol for 10^6
 #: strbuf.c
-#, c-format
-msgid "%u.%2.2u MiB"
-msgstr "%u.%2.2u mebibita"
+msgid "M"
+msgstr "M"
 
-#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second
+#. TRANSLATORS: SI decimal prefix symbol for 10^3
 #: strbuf.c
-#, c-format
-msgid "%u.%2.2u MiB/s"
-msgstr "%u.%2.2u mebibita/detik"
+msgid "k"
+msgstr "k"
 
-#. TRANSLATORS: IEC 80000-13:2008 kibibyte
+#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte
 #: strbuf.c
-#, c-format
-msgid "%u.%2.2u KiB"
-msgstr "%u.%2.2u kibibita"
+msgid "GiB/s"
+msgstr "GiB/detik"
 
-#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second
 #: strbuf.c
-#, c-format
-msgid "%u.%2.2u KiB/s"
-msgstr "%u.%2.2u kibibita/detik"
+msgid "GiB"
+msgstr "GiB"
 
-#. TRANSLATORS: IEC 80000-13:2008 byte
+#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte
 #: strbuf.c
-#, c-format
-msgid "%u byte"
-msgid_plural "%u bytes"
-msgstr[0] "%u bita"
-msgstr[1] "%u bita"
+msgid "MiB/s"
+msgstr "MiB/detik"
+
+#: strbuf.c
+msgid "MiB"
+msgstr "MiB"
+
+#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte
+#: strbuf.c
+msgid "KiB/s"
+msgstr "KiB/detik"
+
+#: strbuf.c
+msgid "KiB"
+msgstr "KiB"
+
+#. TRANSLATORS: IEC 80000-13:2008 byte/second and byte
+#: strbuf.c
+msgid "B/s"
+msgstr "B/detik"
+
+#: strbuf.c
+msgid "B"
+msgstr "B"
 
 #. TRANSLATORS: IEC 80000-13:2008 byte/second
 #: strbuf.c
+msgid "byte/s"
+msgid_plural "bytes/s"
+msgstr[0] "bita/detik"
+msgstr[1] "bita/detik"
+
+#. TRANSLATORS: IEC 80000-13:2008 byte
+#: strbuf.c
+msgid "byte"
+msgid_plural "bytes"
+msgstr[0] "bita"
+msgstr[1] "bita"
+
+#. TRANSLATORS: The first argument is the number string. The second
+#. argument is the unit string (i.e. "12.34 MiB/s").
+#.
+#: strbuf.c
 #, c-format
-msgid "%u byte/s"
-msgid_plural "%u bytes/s"
-msgstr[0] "%u bita/detik"
-msgstr[1] "%u bita/detik"
+msgid "%s %s"
+msgstr "%s %s"
 
 #: submodule-config.c
 #, c-format
@@ -28790,10 +28928,6 @@
 msgstr "jumlah permintaan tiap utas"
 
 #: t/helper/test-simple-ipc.c
-msgid "byte"
-msgstr "bita"
-
-#: t/helper/test-simple-ipc.c
 msgid "ballast character"
 msgstr "karakter pemberat"
 
diff --git a/po/sv.po b/po/sv.po
index a7ae972..7e2e441 100644
--- a/po/sv.po
+++ b/po/sv.po
@@ -1,14 +1,14 @@
 # Swedish translations for Git.
-# Copyright (C) 2010-2025 Peter Krefting <peter@softwolves.pp.se>
+# Copyright (C) 2010-2026 Peter Krefting <peter@softwolves.pp.se>
 # This file is distributed under the same license as the Git package.
-# Peter Krefting <peter@softwolves.pp.se>, 2010-2025.
+# Peter Krefting <peter@softwolves.pp.se>, 2010-2026.
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: git 2.52.0\n"
+"Project-Id-Version: git 2.53.0\n"
 "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2025-11-06 23:58+0000\n"
-"PO-Revision-Date: 2025-11-07 15:54+0100\n"
+"POT-Creation-Date: 2026-01-22 23:57+0000\n"
+"PO-Revision-Date: 2026-01-27 19:33+0100\n"
 "Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n"
 "Language-Team: Svenska <tp-sv@listor.tp-sv.se>\n"
 "Language: sv\n"
@@ -552,7 +552,7 @@
 "j - gå till nästa obestämda stycke, börja om vid slutet\n"
 "J - gå till nästa stycke, börja om vid slutet\n"
 "k - gå till föregående obestämda stycke, börja om vid början\n"
-"K - gå till föregående styckem, börja om vid början\n"
+"K - gå till föregående stycken, börja om vid början\n"
 "g - välj ett stycke att gå till\n"
 "/ - sök efter stycke som motsvarar angivet reguljärt uttryck\n"
 "s - dela aktuellt stycke i mindre styckens\n"
@@ -1733,8 +1733,8 @@
 msgid "'%s' is not a valid branch name"
 msgstr "”%s” är inte ett giltigt grennamn"
 
-msgid "See `man git check-ref-format`"
-msgstr "Se ”man git check-ref-format”"
+msgid "See 'git help check-ref-format'"
+msgstr "Se ”git help check-ref-format”"
 
 #, c-format
 msgid "a branch named '%s' already exists"
@@ -2628,6 +2628,16 @@
 msgstr "måste sluta med en färg"
 
 #, c-format
+msgid "unknown value for config '%s': %s"
+msgstr "okänt värde för inställningen ”%s”: %s"
+
+msgid ""
+"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and "
+"\"histogram\""
+msgstr ""
+"flaggan diff-algorithm godtar ”myers”, ”minimal”, ”patience” och ”histogram”"
+
+#, c-format
 msgid "cannot find revision %s to ignore"
 msgstr "kan inte hitta revision %s att ignorera"
 
@@ -2679,6 +2689,12 @@
 msgid "ignore whitespace differences"
 msgstr "ignorera ändringar i blanksteg"
 
+msgid "<algorithm>"
+msgstr "<algoritm>"
+
+msgid "choose a diff algorithm"
+msgstr "välj en diff-algoritm"
+
 msgid "rev"
 msgstr "incheckning"
 
@@ -2694,8 +2710,8 @@
 msgid "color lines by age"
 msgstr "färglägg rader efter ålder"
 
-msgid "spend extra cycles to find better match"
-msgstr "slösa extra cykler med att hitta bättre träff"
+msgid "spend extra cycles to find a better match"
+msgstr "slösa extra cykler med att hitta en bättre träff"
 
 msgid "use revisions from <file> instead of calling git-rev-list"
 msgstr "använd revisioner från <fil> istället för att anropa git-rev-list"
@@ -3877,8 +3893,8 @@
 msgstr "grennamn saknas; försök med -%c"
 
 #, c-format
-msgid "could not resolve %s"
-msgstr "kunde inte upplösa %s"
+msgid "could not resolve '%s'"
+msgstr "kunde inte upplösa ”%s”"
 
 msgid "invalid path specification"
 msgstr "felaktig sökvägsangivelse"
@@ -5341,10 +5357,16 @@
 #, c-format
 msgid ""
 "cannot overwrite multiple values with a single value\n"
-"       Use a regexp, --add or --replace-all to change %s."
+"       Use --value=<pattern>, --append or --all to change %s."
 msgstr ""
 "kan inte skriva över flera värden med ett ensamt värde\n"
-"       Använd en regexp, --add eller --replace-all för att ändra %s."
+"       Använd --value=<mönster>, --append eller --all för att ändra %s."
+
+msgid "unset all multi-valued config options"
+msgstr "ta bort alla värden för konfigurationsflagga med flera värden"
+
+msgid "unset multi-valued config options with matching values"
+msgstr "ta bort konfigurationsflagga med flera värden för träffade värden"
 
 #, c-format
 msgid "no such section: %s"
@@ -5429,6 +5451,14 @@
 msgid "--comment is only applicable to add/set/replace operations"
 msgstr "--comment gäller bara för ”get”/”set”/”replace”-operationerna"
 
+#, c-format
+msgid ""
+"cannot overwrite multiple values with a single value\n"
+"       Use a regexp, --add or --replace-all to change %s."
+msgstr ""
+"kan inte skriva över flera värden med ett ensamt värde\n"
+"       Använd en regexp, --add eller --replace-all för att ändra %s."
+
 msgid "print sizes in human readable format"
 msgstr "skriv storlekar i människoläsbart format"
 
@@ -5788,7 +5818,7 @@
 
 #, c-format
 msgid "unknown %s mode: %s"
-msgstr "okänd läge för %s: %s"
+msgstr "okänt läge för %s: %s"
 
 #, c-format
 msgid "unknown tag-of-filtered mode: %s"
@@ -5830,14 +5860,8 @@
 "encountered commit-specific encoding %.*s in commit %s; use --reencode=[yes|"
 "no] to handle it"
 msgstr ""
-"påträffade incheckningsspecifik teckenkodning %.*s i incheckningen %s; "
-"använd --reencode=[yes|no] för att hantera den"
-
-#, c-format
-msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it"
-msgstr ""
-"påträffade en signerad incheckning %s; använd --signed-commits=<läge> för "
-"att hantera den"
+"påträffade den incheckningsspecifika teckenkodningen %.*s i incheckningen "
+"%s; använd --reencode=[yes|no] för att hantera den"
 
 #, c-format
 msgid "exporting %<PRIuMAX> signature(s) for commit %s"
@@ -5848,6 +5872,19 @@
 msgstr "tar bort signatur(er) från incheckning %s"
 
 #, c-format
+msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it"
+msgstr ""
+"påträffade den signerade incheckningen %s; använd --signed-commits=<läge> "
+"för att hantera den"
+
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-export with --signed-"
+"commits=<mode>"
+msgstr ""
+"”strip-if-invalid” är inte ett giltigt läge för git fast-export med --signed-"
+"commits=<läge>"
+
+#, c-format
 msgid ""
 "omitting tag %s,\n"
 "since tags of trees (or tags of tags of trees, etc.) are not supported."
@@ -5860,12 +5897,6 @@
 msgstr "kunde inte läsa taggen %s"
 
 #, c-format
-msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it"
-msgstr ""
-"påträffade signerade taggen %s; använd --signed-tags=<läge> för att hantera "
-"den"
-
-#, c-format
 msgid "exporting signed tag %s"
 msgstr "exporterar signerad tagg %s"
 
@@ -5874,6 +5905,19 @@
 msgstr "tar bort signatur från taggen %s"
 
 #, c-format
+msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it"
+msgstr ""
+"påträffade den signerade taggen %s; använd --signed-tags=<läge> för att "
+"hantera den"
+
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-export with --signed-"
+"tags=<mode>"
+msgstr ""
+"”strip-if-invalid” är inte ett giltigt läge för git fast-export med --signed-"
+"tags=<läge>"
+
+#, c-format
 msgid ""
 "tag %s tags unexported object; use --tag-of-filtered-object=<mode> to handle "
 "it"
@@ -6261,6 +6305,33 @@
 msgid "parse_one_signature() returned unknown hash algo"
 msgstr "parse_one_signature() returnerade okänd hashningsalgoritm"
 
+msgid "unknown"
+msgstr "okänd"
+
+#, c-format
+msgid ""
+"stripping invalid signature for commit '%.100s...'\n"
+"  allegedly by %s"
+msgstr ""
+"tar bort ogiltig signatur från incheckning ”%.100s...”\n"
+"  påstås vara av %s"
+
+#, c-format
+msgid ""
+"stripping invalid signature for commit '%.*s'\n"
+"  allegedly by %s"
+msgstr ""
+"tar bort ogiltig signatur från incheckning ”%.*s”\n"
+"  påstås vara av %s"
+
+#, c-format
+msgid ""
+"stripping invalid signature for commit\n"
+"  allegedly by %s"
+msgstr ""
+"tar bort ogiltig signatur från incheckning\n"
+"  påstås vara av %s"
+
 msgid "expected committer but didn't get one"
 msgstr "förväntade incheckare men fick ingen"
 
@@ -6275,10 +6346,6 @@
 msgid "importing a commit signature verbatim"
 msgstr "importerar en incheckningssignatur oförändrad"
 
-msgid "encountered signed tag; use --signed-tags=<mode> to handle it"
-msgstr ""
-"upptäckte en signerad tagg; använd --signed-tags=<läge> för att hantera den"
-
 #, c-format
 msgid "importing a tag signature verbatim for tag '%s'"
 msgstr "importerar en taggsignatur oförändrad för taggen ”%s”"
@@ -6287,6 +6354,17 @@
 msgid "stripping a tag signature for tag '%s'"
 msgstr "tar bort en taggsignatur för taggen ”%s”"
 
+msgid "encountered signed tag; use --signed-tags=<mode> to handle it"
+msgstr ""
+"upptäckte en signerad tagg; använd --signed-tags=<läge> för att hantera den"
+
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-import with --signed-"
+"tags=<mode>"
+msgstr ""
+"”strip-if-invalid” är inte ett giltigt läge för git fast-import med --signed-"
+"tags=<läge>"
+
 #, c-format
 msgid "expected 'from' command, got '%s'"
 msgstr "förväntade ”from”-kommando, fick %s"
@@ -6443,8 +6521,8 @@
 msgid "git fetch [<options>] <group>"
 msgstr "git fetch [<flaggor>] <grupp>"
 
-msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]"
-msgstr "git fetch --multiple [<flaggor>] [(<arkiv> | <grupp>)...]"
+msgid "git fetch --multiple [<options>] [(<repository>|<group>)...]"
+msgstr "git fetch --multiple [<flaggor>] [(<arkiv>|<grupp>)...]"
 
 msgid "git fetch --all [<options>]"
 msgstr "git fetch --all [<flaggor>]"
@@ -6912,9 +6990,6 @@
 msgid "got bad config --config=%s"
 msgstr "fick felaktig konfiguration --config=%s"
 
-msgid "unknown"
-msgstr "okänd"
-
 #. TRANSLATORS: e.g. error in tree 01bfda: <more explanation>
 #, c-format
 msgid "error in %s %s: %s"
@@ -7013,6 +7088,10 @@
 msgid "%s: not a commit"
 msgstr "%s: inte en incheckning!"
 
+#, c-format
+msgid "invalid parameter: expected sha1, got '%s'"
+msgstr "ogiltig parameter: förväntade sha1, fick ”%s”"
+
 msgid "notice: No default references"
 msgstr "obs: Inga förvalda referenser"
 
@@ -7039,26 +7118,6 @@
 msgstr "Kontrollerar objektkataloger"
 
 #, c-format
-msgid "Checking %s link"
-msgstr "Kontrollerar %s-länk"
-
-#, c-format
-msgid "invalid %s"
-msgstr "ogiltigt %s"
-
-#, c-format
-msgid "%s points to something strange (%s)"
-msgstr "%s pekar på något konstigt (%s)"
-
-#, c-format
-msgid "%s: detached HEAD points at nothing"
-msgstr "%s: frånkopplat HEAD pekar på ingenting"
-
-#, c-format
-msgid "notice: %s points to an unborn branch (%s)"
-msgstr "obs: %s pekar på en ofödd gren (%s)"
-
-#, c-format
 msgid "Checking cache tree of %s"
 msgstr "Kontrollerar cacheträd för %s"
 
@@ -7139,14 +7198,6 @@
 msgid "Checking objects"
 msgstr "Kontrollerar objekt"
 
-#, c-format
-msgid "%s: object missing"
-msgstr "%s: objekt saknas"
-
-#, c-format
-msgid "invalid parameter: expected sha1, got '%s'"
-msgstr "ogiltig parameter: förväntade sha1, fick ”%s”"
-
 msgid "git fsmonitor--daemon start [<options>]"
 msgstr "git fsmonitor--daemon start [<flaggor>]"
 
@@ -7523,6 +7574,9 @@
 msgid "failed to add repo to global config"
 msgstr "misslyckades lägga till arkiv till global konfiguration"
 
+msgid "check a specific task"
+msgstr "kontrollera en specifik uppgift"
+
 msgid "git maintenance <subcommand> [<options>]"
 msgstr "git maintenance <underkommando> [<flaggor>]"
 
@@ -7972,6 +8026,10 @@
 msgstr "fsck-fel i packat objekt"
 
 #, c-format
+msgid "invalid %s"
+msgstr "ogiltigt %s"
+
+#, c-format
 msgid "Not all child objects of %s are reachable"
 msgstr "Inte alla barnobjekt för %s kan nås"
 
@@ -8110,6 +8168,9 @@
 msgid "--verify with no packfile name given"
 msgstr "--verify angavs utan paketfilnamn"
 
+msgid "cannot perform queued object checks outside of a repository"
+msgstr "kan inte utföra köade objektkontroller utanför ett arkiv"
+
 msgid "fsck error in pack objects"
 msgstr "fsck-fel i packat objekt"
 
@@ -8361,7 +8422,7 @@
 msgstr "tokigt in-reply-to: %s"
 
 msgid "git format-patch [<options>] [<since> | <revision-range>]"
-msgstr "git format-patch [<flaggor>] [<sedan> | <revisionsintervall>]"
+msgstr "git format-patch [<flaggor>] [<sedan> | <revisions-intervall>]"
 
 msgid "two output directories?"
 msgstr "två utdatakataloger?"
@@ -8852,12 +8913,6 @@
 "git merge-file [<alternativ>] [-L <namn1> [-L <orig> [-L <namn2>]]] <fil1> "
 "<origfil> <fil2>"
 
-msgid ""
-"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and "
-"\"histogram\""
-msgstr ""
-"flaggan diff-algorithm godtar ”myers”, ”minimal”, ”patience” och ”histogram”"
-
 msgid "send results to standard output"
 msgstr "sänd resultat till standard ut"
 
@@ -8870,12 +8925,6 @@
 msgid "use a zealous diff3 based merge"
 msgstr "använd nitisk diff3-baserad sammanslagning"
 
-msgid "<algorithm>"
-msgstr "<algoritm>"
-
-msgid "choose a diff algorithm"
-msgstr "välj en diff-algoritm"
-
 msgid "for conflicts, use this marker size"
 msgstr "för konflikter, använd denna markörstorlek"
 
@@ -9943,6 +9992,10 @@
 msgstr "kunde inte hämta typ för objektet %s i paketet %s"
 
 #, c-format
+msgid "packfile %s is a promisor but --exclude-promisor-objects was given"
+msgstr "packfilen %s är ett kontrakt men --exclude-promisor-objects angavs"
+
+#, c-format
 msgid "could not find pack '%s'"
 msgstr "kunde inte hitta paketet ”%s”"
 
@@ -10199,11 +10252,11 @@
 msgid "git patch-id [--stable | --unstable | --verbatim]"
 msgstr "git patch-id [--stable | --unstable | --verbatim]"
 
-msgid "use the unstable patch-id algorithm"
-msgstr "använd den instabila patch-id-algoritmen"
+msgid "use the unstable patch ID algorithm"
+msgstr "använd den instabila patch ID-algoritmen"
 
-msgid "use the stable patch-id algorithm"
-msgstr "använd den stabila patch-id-algoritmen"
+msgid "use the stable patch ID algorithm"
+msgstr "använd den stabila patch ID-algoritmen"
 
 msgid "don't strip whitespace from the patch"
 msgstr "ta inte bort blanksteg från patchen"
@@ -10226,39 +10279,6 @@
 msgid "git pull [<options>] [<repository> [<refspec>...]]"
 msgstr "git pull [<flaggor>] [<arkiv> [<refspec>...]]"
 
-msgid "control for recursive fetching of submodules"
-msgstr "styrning för rekursiv hämtning av undermoduler"
-
-msgid "Options related to merging"
-msgstr "Alternativ gällande sammanslagning"
-
-msgid "incorporate changes by rebasing rather than merging"
-msgstr "inlemma ändringar genom ombasering i stället för sammanslagning"
-
-msgid "allow fast-forward"
-msgstr "tillåt snabbspolning"
-
-msgid "control use of pre-merge-commit and commit-msg hooks"
-msgstr "styr användning av pre-merge-commit- och commit-msg-krokar"
-
-msgid "automatically stash/stash pop before and after"
-msgstr "utför automatiskt stash/stash pop före och efter"
-
-msgid "Options related to fetching"
-msgstr "Alternativ gällande hämtningar"
-
-msgid "force overwrite of local branch"
-msgstr "tvinga överskrivning av lokal gren"
-
-msgid "number of submodules pulled in parallel"
-msgstr "antal undermoduler som hämtas parallellt"
-
-msgid "use IPv4 addresses only"
-msgstr "använd endast IPv4-adresser"
-
-msgid "use IPv6 addresses only"
-msgstr "använd endast IPv6-adresser"
-
 msgid ""
 "There is no candidate for rebasing against among the refs that you just "
 "fetched."
@@ -10361,6 +10381,39 @@
 "eller --ff-only på kommandoraden för att överstyra det konfigurerade\n"
 "förvalet vid körning.\n"
 
+msgid "control for recursive fetching of submodules"
+msgstr "styrning för rekursiv hämtning av undermoduler"
+
+msgid "Options related to merging"
+msgstr "Alternativ gällande sammanslagning"
+
+msgid "incorporate changes by rebasing rather than merging"
+msgstr "inlemma ändringar genom ombasering i stället för sammanslagning"
+
+msgid "allow fast-forward"
+msgstr "tillåt snabbspolning"
+
+msgid "control use of pre-merge-commit and commit-msg hooks"
+msgstr "styr användning av pre-merge-commit- och commit-msg-krokar"
+
+msgid "automatically stash/stash pop before and after"
+msgstr "utför automatiskt stash/stash pop före och efter"
+
+msgid "Options related to fetching"
+msgstr "Alternativ gällande hämtningar"
+
+msgid "force overwrite of local branch"
+msgstr "tvinga överskrivning av lokal gren"
+
+msgid "number of submodules pulled in parallel"
+msgstr "antal undermoduler som hämtas parallellt"
+
+msgid "use IPv4 addresses only"
+msgstr "använd endast IPv4-adresser"
+
+msgid "use IPv6 addresses only"
+msgstr "använd endast IPv6-adresser"
+
 msgid "Updating an unborn branch with changes added to the index."
 msgstr "Uppdaterar en ofödd gren med ändringar som lagts till i indexet."
 
@@ -11344,7 +11397,7 @@
 
 #, c-format
 msgid "reflog could not be found: '%s'"
-msgstr "refernsloggen hittades inte: ”%s”"
+msgstr "referensloggen hittades inte: ”%s”"
 
 msgid "no reflog specified to delete"
 msgstr "ingen referenslogg att ta bort angavs"
@@ -12206,6 +12259,10 @@
 msgid "only one pattern can be given with -l"
 msgstr "endast ett mönster kan anges med -l"
 
+#, c-format
+msgid "'%s' is not a valid commit-ish for %s"
+msgstr "”%s” är inte en giltig incheckning-igt för %s"
+
 msgid "need some commits to replay"
 msgstr "behöver några incheckningar för omspelning"
 
@@ -12222,27 +12279,16 @@
 "kan inte flytta målet framåt när det finns flera källor eftersom ordningen "
 "inte kan fastställas"
 
-msgid ""
-"cannot implicitly determine whether this is an --advance or --onto operation"
-msgstr ""
-"kan inte avgöra om den underförstådda processen är --advance eller --onto"
-
-msgid ""
-"cannot advance target with multiple source branches because ordering would "
-"be ill-defined"
-msgstr ""
-"kan inte flytta målet framåt när det finns flera källgrenar eftersom "
-"ordningen inte kan fastställas"
-
-msgid "cannot implicitly determine correct base for --onto"
-msgstr "kan inte avgöra den underförstådda basen för --onto"
+#, c-format
+msgid "invalid %s value: '%s'"
+msgstr "ogiltigt värde för %s: ”%s”"
 
 msgid ""
 "(EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance "
-"<branch>) <revision-range>..."
+"<branch>) [--ref-action[=<mode>]] <revision-range>"
 msgstr ""
 "(EXPERIMENTELLT!) git replay ([--contained] --onto <nybas> | --advance "
-"<gren>) <revisions-intervall>..."
+"<gren>) [--ref-action[=<läge>]] <revisions-intervall>"
 
 msgid "make replay advance given branch"
 msgstr "låt omspelningen flytta den givna grenen framåt"
@@ -12250,8 +12296,12 @@
 msgid "replay onto given commit"
 msgstr "spela om ovanpå en given incheckning"
 
-msgid "advance all branches contained in revision-range"
-msgstr "flytta alla grenar som finns i revisionsintervallet framåt"
+msgid "update all branches that point at commits in <revision-range>"
+msgstr ""
+"uppdatera alla grenar som pekar på incheckningar i <revisions-intervall>"
+
+msgid "control ref update behavior (update|print)"
+msgstr "styr beteende för referensuppdatering (update|print)"
 
 msgid "option --onto or --advance is mandatory"
 msgstr "flaggan --onto eller --advance måste anges"
@@ -12264,16 +12314,28 @@
 "några flaggor för revisionstraversering kommer överstyras eftersom ”%s”-"
 "biten i ”struct rev_info” kommer att tvingas"
 
+#, c-format
+msgid "failed to begin ref transaction: %s"
+msgstr "misslyckades påbörja ref-transaktion: %s"
+
 msgid "error preparing revisions"
 msgstr "fel när revisioner skulle förberedas"
 
-msgid "replaying down to root commit is not supported yet!"
-msgstr "kan ännu inte spela om hela vägen ned till rotincheckningen!"
+msgid "replaying down from root commit is not supported yet!"
+msgstr "kan ännu inte spela om hela vägen ned från rotincheckningen!"
 
 msgid "replaying merge commits is not supported yet!"
 msgstr "kan ännu inte spela om sammanslagningsincheckningar!"
 
 #, c-format
+msgid "failed to update ref '%s': %s"
+msgstr "misslyckades uppdatera referensen ”%s”: %s"
+
+#, c-format
+msgid "failed to commit ref transaction: %s"
+msgstr "misslyckades checka in referenstransaktionen: %s"
+
+#, c-format
 msgid "key '%s' not found"
 msgstr "nyckeln ”%s” hittades inte"
 
@@ -12287,9 +12349,15 @@
 msgid "synonym for --format=nul"
 msgstr "synonym till --format=nul"
 
+msgid "print all keys/values"
+msgstr "visa alla nycklar/värden"
+
 msgid "unsupported output format"
 msgstr "utdataformatet stöds ej"
 
+msgid "--all and <key> cannot be used together"
+msgstr "--all och <nyckel> kan inte användas samtidigt"
+
 msgid "References"
 msgstr "Referenser"
 
@@ -12320,6 +12388,12 @@
 msgid "Blobs"
 msgstr "Blob:ar"
 
+msgid "Inflated size"
+msgstr "Uppackad storlek"
+
+msgid "Disk size"
+msgstr "Diskstorlek"
+
 msgid "Repository structure"
 msgstr "Arkivstruktur"
 
@@ -14786,6 +14860,10 @@
 msgid "bundle list at '%s' has no mode"
 msgstr "buntlistan på ”%s” har inget läge"
 
+#, c-format
+msgid "bundle list at '%s': bundle '%s' has no uri"
+msgstr "buntlistan på ”%s”: bunten ”%s” har ingen uri"
+
 msgid "failed to create temporary file"
 msgstr "misslyckades skapa temporär fil"
 
@@ -14808,6 +14886,10 @@
 msgstr "överskred buntens URI-rekursionsgräns (%d)"
 
 #, c-format
+msgid "bundle '%s' has no uri"
+msgstr "buntlistan ”%s” har ingen uri"
+
+#, c-format
 msgid "failed to download bundle from URI '%s'"
 msgstr "misslyckades hämta bunt från URI:en ”%s”"
 
@@ -15302,8 +15384,8 @@
 msgid "Reuse recorded resolution of conflicted merges"
 msgstr "Återanvänd sparad lösning av sammanslagningskonflikter"
 
-msgid "Reset current HEAD to the specified state"
-msgstr "Återställ aktuell HEAD till angivet tillstånd"
+msgid "Set `HEAD` or the index to a known state"
+msgstr "Sätt ”HEAD” eller indexet till ett känt tillstånd"
 
 msgid "Restore working tree files"
 msgstr "Återställ filer i arbetskatalogen"
@@ -16812,10 +16894,6 @@
 msgstr "Okänt värde för konfigurationsvariabeln ”diff.submodule”: ”%s”"
 
 #, c-format
-msgid "unknown value for config '%s': %s"
-msgstr "okänt värde för inställningen ”%s”: %s"
-
-#, c-format
 msgid ""
 "Found errors in 'diff.dirstat' config variable:\n"
 "%s"
@@ -18921,6 +18999,9 @@
 msgid "%s: failed to insert into database"
 msgstr "%s: misslyckades lägga in i databasen"
 
+msgid "cannot add a submodule of a different hash algorithm"
+msgstr "kan inte lägga till submodul med annan hashningsalgoritm"
+
 #, c-format
 msgid "%s: unsupported file type"
 msgstr "%s: filtypen stöds ej"
@@ -20371,6 +20452,10 @@
 msgid "--format=%.*s cannot be used with --python, --shell, --tcl"
 msgstr "--format=%.*s kan inte användas med --python, --shell, --tcl"
 
+#, c-format
+msgid "parse_object_buffer failed on %s for %s"
+msgstr "parse_object_buffer misslyckades på %s för %s"
+
 msgid "failed to run 'describe'"
 msgstr "misslyckades att köra ”describe”"
 
@@ -20402,10 +20487,6 @@
 msgstr "objektet %s saknas för %s"
 
 #, c-format
-msgid "parse_object_buffer failed on %s for %s"
-msgstr "parse_object_buffer misslyckades på %s för %s"
-
-#, c-format
 msgid "malformed object at '%s'"
 msgstr "felformat objekt vid ”%s”"
 
@@ -20700,6 +20781,18 @@
 msgstr "referensnamnet %s är en symbolisk referens, kopiering stöds inte"
 
 #, c-format
+msgid "reftable stack for worktree '%s' is broken"
+msgstr "referensstacken för arbetskatalogen ”%s” är trasig"
+
+#, c-format
+msgid "could not create iterator for worktree '%s'"
+msgstr "kunde inte skapa iterator för arbetskatalogen ”%s”"
+
+#, c-format
+msgid "could not read record for worktree '%s'"
+msgstr "kunde inte läsa posten för arbetskatalogen ”%s”"
+
+#, c-format
 msgid "pattern '%s' has no '*'"
 msgstr "mönstret ”%s” innehåller ingen ”*”"
 
@@ -21116,6 +21209,9 @@
 msgid "could not finish pack-objects to repack promisor objects"
 msgstr "kunde inte avsluta pack-objects för att packa om kontraktsobjekt"
 
+msgid "could not start pack-objects to repack promisor packs"
+msgstr "kunde inte starta pack-objects för att packa om kontraktspaket"
+
 #, c-format
 msgid "pack prefix %s does not begin with objdir %s"
 msgstr "paketprefixet %s börjar inte med objkat %s"
@@ -21904,7 +22000,7 @@
 msgstr ""
 "”reword” tar inte en sammanslagningsincheckning. Om du\n"
 "ville spela upp sammanslagningen på nytt och ändra texten\n"
-"i incheckningsmeddelandet använder du ”merge -C” på\n"
+"i incheckningsmeddelandet använder du ”merge -c” på\n"
 "incheckningen"
 
 #. TRANSLATORS: 'edit', 'merge -C' and 'break' should
@@ -22114,10 +22210,6 @@
 msgid "illegal label name: '%.*s'"
 msgstr "ogiltigt etikettnamn: ”%.*s”"
 
-#, c-format
-msgid "could not resolve '%s'"
-msgstr "kunde inte upplösa ”%s”"
-
 msgid "writing fake root commit"
 msgstr "skriver fejkad rotincheckning"
 
@@ -22592,49 +22684,68 @@
 msgid "bad %s format: %%%.*s"
 msgstr "felaktigt %s-format: %%%.*s"
 
-#. TRANSLATORS: IEC 80000-13:2008 gibibyte
 #, c-format
-msgid "%u.%2.2u GiB"
-msgstr "%u,%2.2u GiB"
+msgid "%u.%2.2u"
+msgstr "%u,%2.2u"
 
-#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second
-#, c-format
-msgid "%u.%2.2u GiB/s"
-msgstr "%u,%2.2u GiB/s"
+#. TRANSLATORS: SI decimal prefix symbol for 10^9
+msgid "G"
+msgstr "G"
 
-#. TRANSLATORS: IEC 80000-13:2008 mebibyte
-#, c-format
-msgid "%u.%2.2u MiB"
-msgstr "%u,%2.2u MiB"
+#. TRANSLATORS: SI decimal prefix symbol for 10^6
+msgid "M"
+msgstr "M"
 
-#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second
-#, c-format
-msgid "%u.%2.2u MiB/s"
-msgstr "%u,%2.2u MiB/s"
+#. TRANSLATORS: SI decimal prefix symbol for 10^3
+msgid "k"
+msgstr "k"
 
-#. TRANSLATORS: IEC 80000-13:2008 kibibyte
-#, c-format
-msgid "%u.%2.2u KiB"
-msgstr "%u,%2.2u KiB"
+#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte
+msgid "GiB/s"
+msgstr "GiB/s"
 
-#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second
-#, c-format
-msgid "%u.%2.2u KiB/s"
-msgstr "%u,%2.2u KiB/s"
+msgid "GiB"
+msgstr "GiB"
 
-#. TRANSLATORS: IEC 80000-13:2008 byte
-#, c-format
-msgid "%u byte"
-msgid_plural "%u bytes"
-msgstr[0] "%u byte"
-msgstr[1] "%u bytes"
+#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte
+msgid "MiB/s"
+msgstr "MiB/s"
+
+msgid "MiB"
+msgstr "MiB"
+
+#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte
+msgid "KiB/s"
+msgstr "KiB/s"
+
+msgid "KiB"
+msgstr "KiB"
+
+#. TRANSLATORS: IEC 80000-13:2008 byte/second and byte
+msgid "B/s"
+msgstr "B/s"
+
+msgid "B"
+msgstr "B"
 
 #. TRANSLATORS: IEC 80000-13:2008 byte/second
+msgid "byte/s"
+msgid_plural "bytes/s"
+msgstr[0] "byte/s"
+msgstr[1] "byte/s"
+
+#. TRANSLATORS: IEC 80000-13:2008 byte
+msgid "byte"
+msgid_plural "bytes"
+msgstr[0] "byte"
+msgstr[1] "byte"
+
+#. TRANSLATORS: The first argument is the number string. The second
+#. argument is the unit string (i.e. "12.34 MiB/s").
+#.
 #, c-format
-msgid "%u byte/s"
-msgid_plural "%u bytes/s"
-msgstr[0] "%u byte/s"
-msgstr[1] "%u bytes/s"
+msgid "%s %s"
+msgstr "%s %s"
 
 #, c-format
 msgid "ignoring suspicious submodule name: %s"
@@ -22926,9 +23037,6 @@
 msgid "number of requests per thread"
 msgstr "antal frågor per tråd"
 
-msgid "byte"
-msgstr "byte"
-
 msgid "ballast character"
 msgstr "ballasttecken"
 
diff --git a/po/tr.po b/po/tr.po
index 7af591b..c459b4a 100644
--- a/po/tr.po
+++ b/po/tr.po
@@ -97,8 +97,8 @@
 msgstr ""
 "Project-Id-Version: Git Turkish Localization Project\n"
 "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2025-11-15 01:30+0300\n"
-"PO-Revision-Date: 2025-11-15 02:00+0300\n"
+"POT-Creation-Date: 2026-01-27 22:54+0300\n"
+"PO-Revision-Date: 2026-01-27 22:00+0300\n"
 "Last-Translator: Emir SARI <emir_sari@icloud.com>\n"
 "Language-Team: Turkish <kde-i18n-doc@kde.org>\n"
 "Language: tr\n"
@@ -642,16 +642,16 @@
 "P - print the current hunk using the pager\n"
 "? - print help\n"
 msgstr ""
-"j - bu parça için sonra karar ver, bir sonraki karar verilmemiş parçayı gör\n"
-"J - bu parça için sonra karar ver, bir sonraki parçayı gör\n"
-"k - bu parça için sonra karar ver, bir önceki karar verilmemiş parçayı gör\n"
-"K - bu parça için sonra karar ver, bir önceki parçayı gör\n"
+"j - sonraki karar verilmemiş parçaya git, bitince başa dön\n"
+"J - sonraki parçaya git, bitince başa dön\n"
+"k - önceki karar verilmemiş parçaya git, bitince sona dön\n"
+"K - önceki parçaya git, bitince sona dön\n"
 "g - gidilecek bir parça seç\n"
 "/ - verilen düzenli ifade ile eşleşen bir parça ara\n"
 "s - geçerli parçayı daha ufak parçalara böl\n"
 "e - geçerli parçayı elle düzenle\n"
 "p - geçerli parçayı yazdır\n"
-"P - geçerli sayfayı sayfalayıcı kullanarak yazdır\n"
+"P - geçerli sayfayı sayfalayıcıyı kullanarak yazdır\n"
 "? - yardımı yazdır\n"
 
 #, c-format
@@ -1821,26 +1821,26 @@
 msgid "'%s' is not a valid branch name"
 msgstr "'%s' geçerli bir dal adı değil"
 
-msgid "See `man git check-ref-format`"
-msgstr "'man git check-ref-format' kılavuz sayfasına bakın"
+msgid "See 'git help check-ref-format'"
+msgstr "\"git help check-ref-format\" bölümüne bakın"
 
 #, c-format
 msgid "a branch named '%s' already exists"
-msgstr "'%s' adında bir dal halihazırda var"
+msgstr "\"%s\" adında bir dal halihazırda var"
 
 #, c-format
 msgid "cannot force update the branch '%s' used by worktree at '%s'"
 msgstr ""
-"şuradaki çalışma ağacı tarafından kullanılan '%s' dalı zorla "
-"güncellenemiyor: '%s'"
+"\"%2$s\" konumundaki çalışma ağacı tarafından kullanılan \"%1$s\" dalı zorla "
+"güncellenemiyor"
 
 #, c-format
 msgid "cannot set up tracking information; starting point '%s' is not a branch"
-msgstr "izleme bilgisi ayarlanamıyor; başlangıç noktası '%s' bir dal değil"
+msgstr "izleme bilgisi ayarlanamıyor; başlangıç noktası \"%s\" bir dal değil"
 
 #, c-format
 msgid "the requested upstream branch '%s' does not exist"
-msgstr "istenen üstkaynak dalı '%s' yok"
+msgstr "istenen üstkaynak dalı \"%s\" yok"
 
 msgid ""
 "\n"
@@ -2720,6 +2720,17 @@
 msgstr "bir renk ile bitmeli"
 
 #, c-format
+msgid "unknown value for config '%s': %s"
+msgstr "'%s' yapılandırması için bilinmeyen değer: %s"
+
+msgid ""
+"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and "
+"\"histogram\""
+msgstr ""
+"diff-algorithm seçeneği şunları kabul eder: \"myers\", \"minimal\", "
+"\"patience\" ve \"histogram\""
+
+#, c-format
 msgid "cannot find revision %s to ignore"
 msgstr "yok saymak için %s revizyonu bulunamıyor"
 
@@ -2771,6 +2782,12 @@
 msgid "ignore whitespace differences"
 msgstr "boşluk ayrımlarını yok say"
 
+msgid "<algorithm>"
+msgstr "<algoritma>"
+
+msgid "choose a diff algorithm"
+msgstr "bir diff algoritması seç"
+
 msgid "rev"
 msgstr "revizyon"
 
@@ -2786,7 +2803,7 @@
 msgid "color lines by age"
 msgstr "satırları yaşına göre renklendir"
 
-msgid "spend extra cycles to find better match"
+msgid "spend extra cycles to find a better match"
 msgstr "daha iyi eşleşme bulmak için ek döngüler harca"
 
 msgid "use revisions from <file> instead of calling git-rev-list"
@@ -3971,8 +3988,8 @@
 msgstr "eksik dal adı; -%c deneyin"
 
 #, c-format
-msgid "could not resolve %s"
-msgstr "%s çözülemedi"
+msgid "could not resolve '%s'"
+msgstr "'%s' çözülemedi"
 
 msgid "invalid path specification"
 msgstr "geçersiz yol belirtimi"
@@ -5452,11 +5469,16 @@
 #, c-format
 msgid ""
 "cannot overwrite multiple values with a single value\n"
-"       Use a regexp, --add or --replace-all to change %s."
+"       Use --value=<pattern>, --append or --all to change %s."
 msgstr ""
 "Tek bir değer ile birden çok değerin üzerine yazılamıyor.\n"
-"       %s değerini değiştirmek için bir düzenli ifade, --add veya --replace-"
-"all kullanın."
+"    %s değişikliği için --value=<dizgi>, --append veya --all kullanın."
+
+msgid "unset all multi-valued config options"
+msgstr "birden çok değerli yapılandırma seçeneklerinin tüm değerlerini kaldır"
+
+msgid "unset multi-valued config options with matching values"
+msgstr "birden çok değerli yapılandırma seçeneğini eşleşen değerlerle değiştir"
 
 #, c-format
 msgid "no such section: %s"
@@ -5542,6 +5564,15 @@
 msgid "--comment is only applicable to add/set/replace operations"
 msgstr "--comment yalnızca ekle/ayarla/değiştir işlemlerine uygulanabilir"
 
+#, c-format
+msgid ""
+"cannot overwrite multiple values with a single value\n"
+"       Use a regexp, --add or --replace-all to change %s."
+msgstr ""
+"Tek bir değer ile birden çok değerin üzerine yazılamıyor.\n"
+"       %s değerini değiştirmek için bir düzenli ifade, --add veya --replace-"
+"all kullanın."
+
 msgid "print sizes in human readable format"
 msgstr "yazdırma boyutları kişi tarafından okunabilir biçimde"
 
@@ -5945,12 +5976,6 @@
 "reencode[yes|no] kullanın: %s"
 
 #, c-format
-msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it"
-msgstr ""
-"imzalanmış %s işlemesine denk gelindi; işlemek için --signed-commits=<kip> "
-"kullanın"
-
-#, c-format
 msgid "exporting %<PRIuMAX> signature(s) for commit %s"
 msgstr "şu işleme için %<PRIuMAX> imza dışa aktarılıyor: %s"
 
@@ -5959,6 +5984,19 @@
 msgstr "%s işlemesinden imzalar soyuluyor"
 
 #, c-format
+msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it"
+msgstr ""
+"imzalanmış %s işlemesine denk gelindi; işlemek için --signed-commits=<kip> "
+"kullanın"
+
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-export with --signed-"
+"commits=<mode>"
+msgstr ""
+"\"strip-if-invalid\", git fast-export için --signed-commits=<kip> ile "
+"geçerli bir kip değil"
+
+#, c-format
 msgid ""
 "omitting tag %s,\n"
 "since tags of trees (or tags of tags of trees, etc.) are not supported."
@@ -5971,12 +6009,6 @@
 msgstr "%s etiketi okunamadı"
 
 #, c-format
-msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it"
-msgstr ""
-"imzalanmış %s etiketinee denk gelindi; işlemek için --signed-tags=<kip> "
-"kullanın"
-
-#, c-format
 msgid "exporting signed tag %s"
 msgstr "imzalanmış %s etiketi dışa aktarılıyor"
 
@@ -5985,6 +6017,19 @@
 msgstr "%s etiketinden imza soyuluyor"
 
 #, c-format
+msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it"
+msgstr ""
+"imzalanmış %s etiketinee denk gelindi; işlemek için --signed-tags=<kip> "
+"kullanın"
+
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-export with --signed-"
+"tags=<mode>"
+msgstr ""
+"\"strip-if-invalid\", git fast-export için --signed-tags=<kip> ile geçerli "
+"bir kip değil"
+
+#, c-format
 msgid ""
 "tag %s tags unexported object; use --tag-of-filtered-object=<mode> to handle "
 "it"
@@ -6374,6 +6419,33 @@
 msgid "parse_one_signature() returned unknown hash algo"
 msgstr "parse_one_signature(), bilinmeyen bir sağlama algoritması döndürdü"
 
+msgid "unknown"
+msgstr "bilinmeyen"
+
+#, c-format
+msgid ""
+"stripping invalid signature for commit '%.100s...'\n"
+"  allegedly by %s"
+msgstr ""
+"\"%.100s...\" işlemesi için olan geçersiz imzalar soyuluyor;\n"
+"  görünüşe göre %s tarafından"
+
+#, c-format
+msgid ""
+"stripping invalid signature for commit '%.*s'\n"
+"  allegedly by %s"
+msgstr ""
+"\"%.*s...\" işlemesi için olan geçersiz imza soyuluyor;\n"
+"  görünüşe göre %s tarafından"
+
+#, c-format
+msgid ""
+"stripping invalid signature for commit\n"
+"  allegedly by %s"
+msgstr ""
+"işleme için olan geçersiz imza soyuluyor;\n"
+"  görünüşe göre %s tarafından"
+
 msgid "expected committer but didn't get one"
 msgstr "işlemeci bekleniyordu; ancak bir tane bulunamadı"
 
@@ -6387,10 +6459,6 @@
 msgid "importing a commit signature verbatim"
 msgstr "bir işleme imzası olduğu gibi içe aktarılıyor"
 
-msgid "encountered signed tag; use --signed-tags=<mode> to handle it"
-msgstr ""
-"imzalanmış etikete denk gelindi; işlemek için --signed-tags=<kip> kullanın"
-
 #, c-format
 msgid "importing a tag signature verbatim for tag '%s'"
 msgstr "\"%s\" etiketi için bir etiket imzası olduğu gibi içe aktarılıyor"
@@ -6399,6 +6467,17 @@
 msgid "stripping a tag signature for tag '%s'"
 msgstr "\"%s\" etiketi için bir etiket imzası soyuluyor"
 
+msgid "encountered signed tag; use --signed-tags=<mode> to handle it"
+msgstr ""
+"imzalanmış etikete denk gelindi; işlemek için --signed-tags=<kip> kullanın"
+
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-import with --signed-"
+"tags=<mode>"
+msgstr ""
+"\"strip-if-invalid\", git fast-import için --signed-tags=<kip> ile geçerli "
+"bir kip değil"
+
 #, c-format
 msgid "expected 'from' command, got '%s'"
 msgstr "\"from\" komutu bekleniyordu, \"%s\" alındı"
@@ -6555,8 +6634,8 @@
 msgid "git fetch [<options>] <group>"
 msgstr "git fetch [<seçenekler>] <grup>"
 
-msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]"
-msgstr "git fetch --multiple [<seçenekler>] [(<depo> | <grup>)...]"
+msgid "git fetch --multiple [<options>] [(<repository>|<group>)...]"
+msgstr "git fetch --multiple [<seçenekler>] [(<depo>|<grup>)...]"
 
 msgid "git fetch --all [<options>]"
 msgstr "git fetch --all [<seçenekler>]"
@@ -7031,9 +7110,6 @@
 msgid "got bad config --config=%s"
 msgstr "hatayı yapılandırma alındı, --config=%s"
 
-msgid "unknown"
-msgstr "bilinmeyen"
-
 #. TRANSLATORS: e.g. error in tree 01bfda: <more explanation>
 #, c-format
 msgid "error in %s %s: %s"
@@ -7128,6 +7204,10 @@
 msgid "%s: not a commit"
 msgstr "%s: bir işleme değil"
 
+#, c-format
+msgid "invalid parameter: expected sha1, got '%s'"
+msgstr "geçersiz parametre: sha1 bekleniyordu, '%s' alındı"
+
 msgid "notice: No default references"
 msgstr "Uyarı: Öntanımlı başvurular yok"
 
@@ -7154,26 +7234,6 @@
 msgstr "Nesne dizinleri denetleniyor"
 
 #, c-format
-msgid "Checking %s link"
-msgstr "%s bağ denetleniyor"
-
-#, c-format
-msgid "invalid %s"
-msgstr "geçersiz %s"
-
-#, c-format
-msgid "%s points to something strange (%s)"
-msgstr "%s garip bir şeye işaret ediyor (%s)"
-
-#, c-format
-msgid "%s: detached HEAD points at nothing"
-msgstr "%s: ayrık HEAD bir şeye işaret etmiyor"
-
-#, c-format
-msgid "notice: %s points to an unborn branch (%s)"
-msgstr "Uyarı: %s henüz doğmamış bir dala işaret ediyor (%s)"
-
-#, c-format
 msgid "Checking cache tree of %s"
 msgstr "%s ögesinin önbellek ağacı denetleniyor"
 
@@ -7252,14 +7312,6 @@
 msgid "Checking objects"
 msgstr "Nesneler denetleniyor"
 
-#, c-format
-msgid "%s: object missing"
-msgstr "%s: nesne kayıp"
-
-#, c-format
-msgid "invalid parameter: expected sha1, got '%s'"
-msgstr "geçersiz parametre: sha1 bekleniyordu, '%s' alındı"
-
 msgid "git fsmonitor--daemon start [<options>]"
 msgstr "git fsmonitor--daemon start [<seçenekler>]"
 
@@ -7637,6 +7689,9 @@
 msgid "failed to add repo to global config"
 msgstr "depo, global yapılandırmaya eklenemedi"
 
+msgid "check a specific task"
+msgstr "belirli bir görevi denetle"
+
 msgid "git maintenance <subcommand> [<options>]"
 msgstr "git maintenance <altkomut> [<seçenekler>]"
 
@@ -8092,6 +8147,10 @@
 msgstr "paketlenmiş nesne içinde fsck hatası"
 
 #, c-format
+msgid "invalid %s"
+msgstr "geçersiz %s"
+
+#, c-format
 msgid "Not all child objects of %s are reachable"
 msgstr "%s ögesinin tüm alt ögeleri ulaşılabilir değil"
 
@@ -8231,6 +8290,9 @@
 msgid "--verify with no packfile name given"
 msgstr "--verify ile bir paket dosyası adı verilmedi"
 
+msgid "cannot perform queued object checks outside of a repository"
+msgstr "depo dışında kuyruğa alınmış nesne denetimleri gerçekleştirilemez"
+
 msgid "fsck error in pack objects"
 msgstr "paket nesnelerinde fsck hatası"
 
@@ -8978,13 +9040,6 @@
 "git merge-file [<seçenekler>] [-L <ad1> [-L <orij> [-L <ad2>]]] <dosya1> "
 "<orij-dosya> <dosya2>"
 
-msgid ""
-"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and "
-"\"histogram\""
-msgstr ""
-"diff-algorithm seçeneği şunları kabul eder: \"myers\", \"minimal\", "
-"\"patience\" ve \"histogram\""
-
 msgid "send results to standard output"
 msgstr "sonuçları standart çıktıya gönder"
 
@@ -8997,12 +9052,6 @@
 msgid "use a zealous diff3 based merge"
 msgstr "gayretli bir diff3 tabanlı birleştirme kullan"
 
-msgid "<algorithm>"
-msgstr "<algoritma>"
-
-msgid "choose a diff algorithm"
-msgstr "bir diff algoritması seç"
-
 msgid "for conflicts, use this marker size"
 msgstr "çakışmalarda bu imleyici boyutunu kullan"
 
@@ -10067,6 +10116,10 @@
 msgstr "%s nesnesinin türü alınamıyor (%s paketinde)"
 
 #, c-format
+msgid "packfile %s is a promisor but --exclude-promisor-objects was given"
+msgstr "paket dosyası %s bir vaatçi; ancak --exclude-promisor-objects verildi"
+
+#, c-format
 msgid "could not find pack '%s'"
 msgstr "'%s' paketi bulunamadı"
 
@@ -10323,10 +10376,10 @@
 msgid "git patch-id [--stable | --unstable | --verbatim]"
 msgstr "git patch-id [--stable | --unstable | --verbatim]"
 
-msgid "use the unstable patch-id algorithm"
-msgstr "kararlı olmayan yama kimliği algoritmasını kullan"
+msgid "use the unstable patch ID algorithm"
+msgstr "kararsız yama kimliği algoritmasını kullan"
 
-msgid "use the stable patch-id algorithm"
+msgid "use the stable patch ID algorithm"
 msgstr "kararlı yama kimliği algoritmasını kullan"
 
 msgid "don't strip whitespace from the patch"
@@ -10351,39 +10404,6 @@
 msgid "git pull [<options>] [<repository> [<refspec>...]]"
 msgstr "git pull [<seçenekler>] [<depo> [<bşvr-blrtç>...]]"
 
-msgid "control for recursive fetching of submodules"
-msgstr "altmodüllerin özyineli getirilmesi için denetleme"
-
-msgid "Options related to merging"
-msgstr "Birleştirme ile ilgili seçenekler"
-
-msgid "incorporate changes by rebasing rather than merging"
-msgstr "değişiklikleri birleştirme yerine yeniden temellendirme ile kat"
-
-msgid "allow fast-forward"
-msgstr "ileri sarıma izin ver"
-
-msgid "control use of pre-merge-commit and commit-msg hooks"
-msgstr "pre-merge-commit ve commit-msg kancalarının kullanımını denetle"
-
-msgid "automatically stash/stash pop before and after"
-msgstr "öncesinde ve sonrasında kendiliğinden zulala/zulaları patlat"
-
-msgid "Options related to fetching"
-msgstr "Getirme ile ilgili seçenekler"
-
-msgid "force overwrite of local branch"
-msgstr "zorla yerel dalın üzerine yaz"
-
-msgid "number of submodules pulled in parallel"
-msgstr "paralelde çekilen altmodüllerin sayısı"
-
-msgid "use IPv4 addresses only"
-msgstr "yalnızca IPv4 adresleri kullan"
-
-msgid "use IPv6 addresses only"
-msgstr "yalnızca IPv6 adresleri kullan"
-
 msgid ""
 "There is no candidate for rebasing against among the refs that you just "
 "fetched."
@@ -10485,6 +10505,39 @@
 "yapılandırmayı yürütme sırasında --rebase, --no-rebase veya\n"
 "--ff-only ile bir kerelik geçersiz kılabilirsiniz.\n"
 
+msgid "control for recursive fetching of submodules"
+msgstr "altmodüllerin özyineli getirilmesi için denetleme"
+
+msgid "Options related to merging"
+msgstr "Birleştirme ile ilgili seçenekler"
+
+msgid "incorporate changes by rebasing rather than merging"
+msgstr "değişiklikleri birleştirme yerine yeniden temellendirme ile kat"
+
+msgid "allow fast-forward"
+msgstr "ileri sarıma izin ver"
+
+msgid "control use of pre-merge-commit and commit-msg hooks"
+msgstr "pre-merge-commit ve commit-msg kancalarının kullanımını denetle"
+
+msgid "automatically stash/stash pop before and after"
+msgstr "öncesinde ve sonrasında kendiliğinden zulala/zulaları patlat"
+
+msgid "Options related to fetching"
+msgstr "Getirme ile ilgili seçenekler"
+
+msgid "force overwrite of local branch"
+msgstr "zorla yerel dalın üzerine yaz"
+
+msgid "number of submodules pulled in parallel"
+msgstr "paralelde çekilen altmodüllerin sayısı"
+
+msgid "use IPv4 addresses only"
+msgstr "yalnızca IPv4 adresleri kullan"
+
+msgid "use IPv6 addresses only"
+msgstr "yalnızca IPv6 adresleri kullan"
+
 msgid "Updating an unborn branch with changes added to the index."
 msgstr "İndekse eklenen değişikliklerle henüz doğmamış bir dal güncelleniyor."
 
@@ -12346,6 +12399,10 @@
 msgid "only one pattern can be given with -l"
 msgstr "-l ile yalnızca bir dizgi verilebilir"
 
+#, c-format
+msgid "'%s' is not a valid commit-ish for %s"
+msgstr "\"%s\", %s için geçerli bir işlememsi değil"
+
 msgid "need some commits to replay"
 msgstr "yeniden oynatmak için birkaç işleme gerekli"
 
@@ -12362,27 +12419,16 @@
 "birden çok kaynaklı hedef ilerletilemiyor; çünkü sıralama hatalı tanımlanmış "
 "olurdu"
 
-msgid ""
-"cannot implicitly determine whether this is an --advance or --onto operation"
-msgstr ""
-"bunun --advance veya --onto işlemi olup olmadığı örtük olarak algılanamıyor"
-
-msgid ""
-"cannot advance target with multiple source branches because ordering would "
-"be ill-defined"
-msgstr ""
-"birden çok kaynak dallı hedef ilerletilemiyor; çünkü sıralama hatalı "
-"tanımlanmış olurdu"
-
-msgid "cannot implicitly determine correct base for --onto"
-msgstr "--onto için olan doğru temel örtük olarak algılanamıyor"
+#, c-format
+msgid "invalid %s value: '%s'"
+msgstr "geçersiz %s değeri: \"%s\""
 
 msgid ""
 "(EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance "
-"<branch>) <revision-range>..."
+"<branch>) [--ref-action[=<mode>]] <revision-range>"
 msgstr ""
 "(DENEYSEL!) git replay ([--contained] --onto <yeni-temel> | --advance <dal>) "
-"<revizyon-erimi>..."
+"[--ref-action[=<kip>]] <revizyon-erimi>..."
 
 msgid "make replay advance given branch"
 msgstr "verilen dalı önceden yeniden oynat"
@@ -12390,8 +12436,11 @@
 msgid "replay onto given commit"
 msgstr "verilen işlemeye yeniden oynat"
 
-msgid "advance all branches contained in revision-range"
-msgstr "revizyon eriminde içerilen tüm dalları ilerlet"
+msgid "update all branches that point at commits in <revision-range>"
+msgstr "<revizyon-erimi>'ndeki işlemelere işaret eden tüm dalları güncelle"
+
+msgid "control ref update behavior (update|print)"
+msgstr "başvuru güncelleme davranışını denetle (update|print)"
 
 msgid "option --onto or --advance is mandatory"
 msgstr "--onto veya --advance seçeneğinin kullanımı zorunlu"
@@ -12404,16 +12453,28 @@
 "'struct rev_info' içindeki '%s' biti zorlanacağından kimi revizyon yürütme "
 "seçenekleri geçersiz kılınacak"
 
+#, c-format
+msgid "failed to begin ref transaction: %s"
+msgstr "başvuru işlemine başlanılamadı: %s"
+
 msgid "error preparing revisions"
 msgstr "revizyonlar hazırlanırken hata"
 
-msgid "replaying down to root commit is not supported yet!"
-msgstr "kök işlemeye kadar yeniden oynatma henüz desteklenmiyor!"
+msgid "replaying down from root commit is not supported yet!"
+msgstr "kök işlemeden aşağı yeniden oynatma henüz desteklenmiyor!"
 
 msgid "replaying merge commits is not supported yet!"
 msgstr "birleştirme işlemelerini yeniden oynatma henüz desteklenmiyor!"
 
 #, c-format
+msgid "failed to update ref '%s': %s"
+msgstr "\"%s\" başvurusu güncellenemedi: %s"
+
+#, c-format
+msgid "failed to commit ref transaction: %s"
+msgstr "başvuru işlemi işlenemedi: %s"
+
+#, c-format
 msgid "key '%s' not found"
 msgstr "'%s' anahtarı bulunamadı"
 
@@ -12427,9 +12488,15 @@
 msgid "synonym for --format=nul"
 msgstr "--format=nul eşanlamlısı"
 
+msgid "print all keys/values"
+msgstr "tüm anahtarları/değerleri yazdır"
+
 msgid "unsupported output format"
 msgstr "desteklenmeyen çıktı biçimi"
 
+msgid "--all and <key> cannot be used together"
+msgstr "--all ve <anahtar> birlikte kullanılamaz"
+
 msgid "References"
 msgstr "Başvurular"
 
@@ -12460,6 +12527,12 @@
 msgid "Blobs"
 msgstr "İkili geniş nesneler"
 
+msgid "Inflated size"
+msgstr "Şişirilmiş boyut"
+
+msgid "Disk size"
+msgstr "Disk boyutu"
+
 msgid "Repository structure"
 msgstr "Depo yapısı"
 
@@ -14920,11 +14993,15 @@
 
 #, c-format
 msgid "could not parse bundle list key %s with value '%s'"
-msgstr "demet liste anahtarı %s ile '%s' değeri ayrıştırılamıyor"
+msgstr "demet liste anahtarı %s ile \"%s\" değeri ayrıştırılamıyor"
 
 #, c-format
 msgid "bundle list at '%s' has no mode"
-msgstr "'%s' konumundaki demet listesinin kipi yok"
+msgstr "\"%s\" konumundaki demet listesinin kipi yok"
+
+#, c-format
+msgid "bundle list at '%s': bundle '%s' has no uri"
+msgstr "\"%s\" konumundaki demet: \"%s\" demetinde URI yok"
 
 msgid "failed to create temporary file"
 msgstr "geçici dosya oluşturulamadı"
@@ -14948,16 +15025,20 @@
 msgstr "demet URI özyineleme sınırı aşıldı (%d)"
 
 #, c-format
+msgid "bundle '%s' has no uri"
+msgstr "\"%s\" demetinde URI yok"
+
+#, c-format
 msgid "failed to download bundle from URI '%s'"
-msgstr "'%s' URI'sinden demet indirilemedi"
+msgstr "\"%s\" URI'sinden demet indirilemedi"
 
 #, c-format
 msgid "file at URI '%s' is not a bundle or bundle list"
-msgstr "'%s' URI'sindeki dosya bir demet veya demet listesi değil"
+msgstr "\"%s\" URI'sindeki dosya bir demet veya demet listesi değil"
 
 #, c-format
 msgid "bundle-uri: unexpected argument: '%s'"
-msgstr "bundle-uri: beklenmedik argüman: '%s'"
+msgstr "bundle-uri: beklenmedik argüman: \"%s\""
 
 msgid "bundle-uri: expected flush after arguments"
 msgstr "bundle-uri: argümanlardan sonra floş bekleniyordu"
@@ -15439,8 +15520,8 @@
 msgid "Reuse recorded resolution of conflicted merges"
 msgstr "Çakışmaların kayıtlı çözümlerini yeniden kullan"
 
-msgid "Reset current HEAD to the specified state"
-msgstr "Geçerli HEAD'i belirtilen duruma sıfırla"
+msgid "Set `HEAD` or the index to a known state"
+msgstr "\"HEAD\"i veya indeksi bilinen bir duruma ayarla"
 
 msgid "Restore working tree files"
 msgstr "Çalışma ağacı dosyalarını eski durumuna getir"
@@ -16951,10 +17032,6 @@
 msgstr "'diff.submodule' yapılandırma değişkeni için bilinmeyen değer: '%s'"
 
 #, c-format
-msgid "unknown value for config '%s': %s"
-msgstr "'%s' yapılandırması için bilinmeyen değer: %s"
-
-#, c-format
 msgid ""
 "Found errors in 'diff.dirstat' config variable:\n"
 "%s"
@@ -19065,6 +19142,9 @@
 msgid "%s: failed to insert into database"
 msgstr "%s: veritabanına ekleme başarısız"
 
+msgid "cannot add a submodule of a different hash algorithm"
+msgstr "başka bir sağlama algoritmalı altmodül eklenemiyor"
+
 #, c-format
 msgid "%s: unsupported file type"
 msgstr "%s: desteklenmeyen dosya türü"
@@ -20510,6 +20590,10 @@
 msgid "--format=%.*s cannot be used with --python, --shell, --tcl"
 msgstr "--format=%.*s, --python, --shell ve --tcl ile kullanılamaz"
 
+#, c-format
+msgid "parse_object_buffer failed on %s for %s"
+msgstr "parse_object_buffer %s üzerinde başarısız oldu (%s için)"
+
 msgid "failed to run 'describe'"
 msgstr "'describe' çalıştırılamadı"
 
@@ -20541,10 +20625,6 @@
 msgstr "eksik nesne %s (%s için)"
 
 #, c-format
-msgid "parse_object_buffer failed on %s for %s"
-msgstr "parse_object_buffer %s üzerinde başarısız oldu (%s için)"
-
-#, c-format
 msgid "malformed object at '%s'"
 msgstr "'%s' konumunda hatalı oluşturulmuş nesne"
 
@@ -20845,16 +20925,28 @@
 msgstr "başvuru adı %s bir sembolik bağ, onu kopyalamak desteklenmiyor"
 
 #, c-format
+msgid "reftable stack for worktree '%s' is broken"
+msgstr "\"%s\" çalışma ağacı için başvuru tablosu yığını bozuk"
+
+#, c-format
+msgid "could not create iterator for worktree '%s'"
+msgstr "\"%s\" çalışma ağacı için yineleyici oluşturulamadı"
+
+#, c-format
+msgid "could not read record for worktree '%s'"
+msgstr "\"%s\" çalışma ağacı için kayıt okunamadı"
+
+#, c-format
 msgid "pattern '%s' has no '*'"
-msgstr "'%s' dizgisinde '*' yok"
+msgstr "\"%s\" dizgisinde \"*\" yok"
 
 #, c-format
 msgid "replacement '%s' has no '*'"
-msgstr "'%s' yedeğinde '*' yok"
+msgstr "\"%s\" ikamesinde \"*\" yok"
 
 #, c-format
 msgid "invalid quoting in push-option value: '%s'"
-msgstr "push-option değerinde geçersiz tırnak içine alım: '%s'"
+msgstr "push-option değerinde geçersiz tırnak içine alım: \"%s\""
 
 #, c-format
 msgid "unknown value for object-format: %s"
@@ -21267,6 +21359,9 @@
 msgid "could not finish pack-objects to repack promisor objects"
 msgstr "vaatçi nesneleri yeniden paketleme için pack-objects bitirilemedi"
 
+msgid "could not start pack-objects to repack promisor packs"
+msgstr "vaatçi paketleri yeniden paketlemek üzere pack-objects başlatılamadı"
+
 #, c-format
 msgid "pack prefix %s does not begin with objdir %s"
 msgstr "paket öneki %s, nesne dizini %s ile başlamıyor"
@@ -22261,10 +22356,6 @@
 msgid "illegal label name: '%.*s'"
 msgstr "izin verilmeyen etiket adı: '%.*s'"
 
-#, c-format
-msgid "could not resolve '%s'"
-msgstr "'%s' çözülemedi"
-
 msgid "writing fake root commit"
 msgstr "sahte kök işlemesi yazılıyor"
 
@@ -22739,49 +22830,68 @@
 msgid "bad %s format: %%%.*s"
 msgstr "hatalı %s biçimi: %%%.*s"
 
-#. TRANSLATORS: IEC 80000-13:2008 gibibyte
 #, c-format
-msgid "%u.%2.2u GiB"
-msgstr "%u.%2.2u GiB"
+msgid "%u.%2.2u"
+msgstr "%u.%2.2u"
 
-#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second
-#, c-format
-msgid "%u.%2.2u GiB/s"
-msgstr "%u.%2.2u GiB/sn"
+#. TRANSLATORS: SI decimal prefix symbol for 10^9
+msgid "G"
+msgstr "G"
 
-#. TRANSLATORS: IEC 80000-13:2008 mebibyte
-#, c-format
-msgid "%u.%2.2u MiB"
-msgstr "%u.%2.2u MiB"
+#. TRANSLATORS: SI decimal prefix symbol for 10^6
+msgid "M"
+msgstr "M"
 
-#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second
-#, c-format
-msgid "%u.%2.2u MiB/s"
-msgstr "%u.%2.2u MiB/sn"
+#. TRANSLATORS: SI decimal prefix symbol for 10^3
+msgid "k"
+msgstr "k"
 
-#. TRANSLATORS: IEC 80000-13:2008 kibibyte
-#, c-format
-msgid "%u.%2.2u KiB"
-msgstr "%u.%2.2u KiB"
+#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte
+msgid "GiB/s"
+msgstr "GiB/sn"
 
-#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second
-#, c-format
-msgid "%u.%2.2u KiB/s"
-msgstr "%u.%2.2u KiB/sn"
+msgid "GiB"
+msgstr "GiB"
 
-#. TRANSLATORS: IEC 80000-13:2008 byte
-#, c-format
-msgid "%u byte"
-msgid_plural "%u bytes"
-msgstr[0] "%u bayt"
-msgstr[1] "%u bayt"
+#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte
+msgid "MiB/s"
+msgstr "MiB/sn"
+
+msgid "MiB"
+msgstr "MiB"
+
+#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte
+msgid "KiB/s"
+msgstr "KiB/sn"
+
+msgid "KiB"
+msgstr "KiB"
+
+#. TRANSLATORS: IEC 80000-13:2008 byte/second and byte
+msgid "B/s"
+msgstr "B/sn"
+
+msgid "B"
+msgstr "B"
 
 #. TRANSLATORS: IEC 80000-13:2008 byte/second
+msgid "byte/s"
+msgid_plural "bytes/s"
+msgstr[0] "bayt/sn"
+msgstr[1] "bayt/sn"
+
+#. TRANSLATORS: IEC 80000-13:2008 byte
+msgid "byte"
+msgid_plural "bytes"
+msgstr[0] "bayt"
+msgstr[1] "bayt"
+
+#. TRANSLATORS: The first argument is the number string. The second
+#. argument is the unit string (i.e. "12.34 MiB/s").
+#.
 #, c-format
-msgid "%u byte/s"
-msgid_plural "%u bytes/s"
-msgstr[0] "%u bayt/sn"
-msgstr[1] "%u bayt/sn"
+msgid "%s %s"
+msgstr "%s %s"
 
 #, c-format
 msgid "ignoring suspicious submodule name: %s"
@@ -23072,9 +23182,6 @@
 msgid "number of requests per thread"
 msgstr "iş parçacığı başına düşen istek sayısı"
 
-msgid "byte"
-msgstr "bayt"
-
 msgid "ballast character"
 msgstr "dengeleyici karakter"
 
diff --git a/po/zh_CN.po b/po/zh_CN.po
index db7518c..2e249de 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -1,6 +1,7 @@
 # Chinese translations for Git package
 # Git 软件包的简体中文翻译.
 # Copyright (C) 2012,2013 Jiang Xin <worldhello.net AT gmail.com>
+# Copyright (C) 2021-2023 Fangyi Zhou <me@fangyi.io>
 # This file is distributed under the same license as the Git package.
 # Contributors:
 #   - Fangyi Zhou <me AT fangyi.io>
@@ -16,15 +17,15 @@
 #   - Zhuang Ya <zhuangya AT me.com>
 #   - Teng Long <dyroneteng AT gmail.com>
 #
-#  Git glossary for Chinese translators
+# Git glossary for Chinese translators
 #
 #   English                          |  Chinese
 #   ---------------------------------+--------------------------------------
-#   3-way merge                      |  三方合并
+#   3-way merge                      |  三路合并
 #   abbreviate                       |  简写(的 SHA-1 值)
 #   alternate object database        |  备用对象库
 #   attribute source                 |  属性来源
-#   amend                            |  修补
+#   amend                            |  修订
 #   ancestor                         |  祖先,祖先提交
 #   annotated tag                    |  附注标签
 #   bare repository                  |  纯仓库
@@ -33,9 +34,9 @@
 #   blob object                      |  数据对象
 #   bloom filter                     |  布隆过滤器
 #   branch                           |  分支
-#   bundle                           |  归档包
+#   bundle                           |  捆绑包
 #   bypass                           |  绕过
-#   cache                            |  索引(的别称)
+#   cache                            |  缓存(索引的别称)
 #   chain                            |  (提交)链
 #   changeset                        |  变更集
 #   checkout                         |  检出
@@ -48,7 +49,7 @@
 #   commit object                    |  提交对象
 #   commit-graph                     |  提交图
 #   commit-ish (also committish)     |  提交号
-#   cone                             |  锥形(稀疏检出模型);锥(稀疏检出)
+#   cone                             |  锥形模式(稀疏检出);锥(稀疏检出)
 #   conflict                         |  冲突
 #   core Git                         |  核心 Git 工具
 #   cover letter                     |  附函
@@ -59,28 +60,29 @@
 #   dirty                            |  脏(的工作区)
 #   dumb HTTP protocol               |  哑 HTTP 协议
 #   enlistment                       |  登记(在 scalar 中使用)
-#   evil merge                       |  坏合并(合并引入了父提交没有的修改)
+#   evil merge                       |  恶意合并(合并引入了额外变更,即父提交没有的修改)
 #   fast-forward                     |  快进
 #   fetch                            |  获取
 #   file system                      |  文件系统
 #   fork                             |  派生
 #   Git archive                      |  仓库(对于 arch 用户)
 #   gitfile                          |  gitfile(仓库链接文件)
-#   grafts                           |  (提交)移植
+#   grafts                           |  (提交)嫁接
 #   hash                             |  哈希值
 #   HEAD                             |  HEAD(头指针,亦即当前分支)
 #   head                             |  头、分支
 #   head ref                         |  分支
 #   header                           |  头信息
 #   hook                             |  钩子
-#   hunk                             |  补丁片段
 #   index                            |  索引
 #   index entry                      |  索引条目
 #   loose object                     |  松散对象
 #   loose refs                       |  松散引用
-#   magic                            |  神奇前缀(路径规格支持的一种前缀表达式)
-#   master                           |  master(默认分支名)
+#   magic                            |  魔法前缀(路径规格支持的一种前缀表达式)
+#   master                           |  master(传统默认分支名,将被 main 替代)
 #   merge                            |  合并
+#   merge-base                       |  合并基线
+#   note                             |  注解
 #   object                           |  对象
 #   object database                  |  对象库
 #   object identifier                |  对象标识符
@@ -95,11 +97,12 @@
 #   parent                           |  父提交
 #   partial clone                    |  部分克隆
 #   patch                            |  补丁
+#   patch hunk                       |  补丁块
 #   pathspec                         |  路径规格
 #   pattern                          |  模式
 #   pickaxe                          |  挖掘
-#   plumbing                         |  管件(Git 底层核心命令的别称)
-#   porcelain                        |  瓷件(Git 上层封装命令的别称)
+#   plumbing                         |  管件(Git 底层命令的别称)
+#   porcelain                        |  瓷件(Git 高层命令的别称)
 #   precious-objects repo            |  珍品仓库
 #   preferred pack                   |  首选包(多包索引中引入的首选包概念)
 #   promisor                         |  承诺者
@@ -130,7 +133,7 @@
 #   squash                           |  挤压
 #   stage                            |  n. 暂存区(即索引); v. 暂存
 #   stale                            |  过期的
-#   stash                            |  n. 贮藏区; v. 贮藏
+#   stash                            |  n. 储藏区; v. 储藏
 #   submodule                        |  子模组
 #   symref                           |  符号引用
 #   tag                              |  n. 标签; v. 打标签
@@ -146,16 +149,28 @@
 #   unpack                           |  解包
 #   unreachable object               |  不可达对象
 #   unstage                          |  取消暂存
-#   upstream                         |  上游
-#   upstream branch                  |  上游分支
+#   upstream                         |  上游(分支/仓库)
 #   working tree                     |  工作区
-# Fangyi Zhou <me@fangyi.io>, 2021-2023.
+#
+#  Preferred Chinese Terms
+#
+#   - 默认 (not 缺省)
+#   - 其他 (not 其它)
+#   - 作为 (not 做为)
+#   - 当作 (not 当做)
+#   - 登录 (not 登陆)
+#   - 账户 (not 帐户)
+#   - 密码 (not 口令)
+#   - 撤销 (not 撤消)
+#   - 吗? (not 么?)   # 疑问助词
+#   - 无法 (not 不能):客观。客观障碍、技术限制、物理规律,非主观
+#   - 不能 (not 无法):主观。能力不足、规则限制、逻辑冲突、设计限制、意愿拒绝
 #
 msgid ""
 msgstr ""
 "Project-Id-Version: Git\n"
 "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2025-11-15 22:02+0800\n"
+"POT-Creation-Date: 2026-01-29 19:17+0800\n"
 "PO-Revision-Date: 2025-11-16 01:03+0800\n"
 "Last-Translator: Teng Long <dyroneteng@gmail.com>\n"
 "Language-Team: GitHub <https://github.com/dyrone/git/>\n"
@@ -178,7 +193,7 @@
 
 #: add-interactive.c builtin/merge.c builtin/rebase.c reset.c sequencer.c
 msgid "could not read index"
-msgstr "不能读取索引"
+msgstr "无法读取索引"
 
 #: add-interactive.c
 msgid "binary"
@@ -199,11 +214,11 @@
 #: add-interactive.c
 #, c-format
 msgid "could not stage '%s'"
-msgstr "不能暂存 '%s'"
+msgstr "无法暂存 '%s'"
 
 #: add-interactive.c builtin/stash.c reset.c sequencer.c
 msgid "could not write index"
-msgstr "不能写入索引"
+msgstr "无法写入索引"
 
 #: add-interactive.c
 #, c-format
@@ -215,7 +230,7 @@
 #: add-interactive.c
 #, c-format
 msgid "note: %s is untracked now.\n"
-msgstr "说明:%s 现已成为未跟踪的。\n"
+msgstr "注意:%s 现在未被跟踪。\n"
 
 #: add-interactive.c apply.c builtin/checkout.c builtin/reset.c
 #, c-format
@@ -228,7 +243,7 @@
 
 #: add-interactive.c
 msgid "Could not parse HEAD^{tree}"
-msgstr "不能解析 HEAD^{tree}"
+msgstr "无法解析 HEAD^{tree}"
 
 #: add-interactive.c
 #, c-format
@@ -274,7 +289,7 @@
 
 #: add-interactive.c
 msgid "Review diff"
-msgstr "检视 diff"
+msgstr "查看差异"
 
 #: add-interactive.c
 msgid "show paths with changes"
@@ -350,11 +365,11 @@
 
 #: add-interactive.c
 msgid "staged"
-msgstr "缓存"
+msgstr "已暂存"
 
 #: add-interactive.c
 msgid "unstaged"
-msgstr "未缓存"
+msgstr "未暂存"
 
 #: add-interactive.c apply.c builtin/am.c builtin/bugreport.c builtin/clone.c
 #: builtin/diagnose.c builtin/fetch.c builtin/hook.c builtin/merge.c
@@ -364,7 +379,7 @@
 
 #: add-interactive.c
 msgid "could not refresh index"
-msgstr "不能刷新索引"
+msgstr "无法刷新索引"
 
 #: add-interactive.c builtin/clean.c
 #, c-format
@@ -379,12 +394,12 @@
 #: add-patch.c
 #, c-format
 msgid "Stage deletion [y,n,q,a,d%s,?]? "
-msgstr "暂存删除动作 [y,n,q,a,d%s,?]? "
+msgstr "暂存删除变更 [y,n,q,a,d%s,?]? "
 
 #: add-patch.c
 #, c-format
 msgid "Stage addition [y,n,q,a,d%s,?]? "
-msgstr "暂存添加动作 [y,n,q,a,d%s,?]? "
+msgstr "暂存新增变更 [y,n,q,a,d%s,?]? "
 
 #: add-patch.c
 #, c-format
@@ -414,28 +429,28 @@
 #: add-patch.c
 #, c-format
 msgid "Stash mode change [y,n,q,a,d%s,?]? "
-msgstr "贮藏模式变更 [y,n,q,a,d%s,?]? "
+msgstr "储藏模式变更 [y,n,q,a,d%s,?]? "
 
 #: add-patch.c
 #, c-format
 msgid "Stash deletion [y,n,q,a,d%s,?]? "
-msgstr "贮藏删除动作 [y,n,q,a,d%s,?]? "
+msgstr "储藏删除变更 [y,n,q,a,d%s,?]? "
 
 #: add-patch.c
 #, c-format
 msgid "Stash addition [y,n,q,a,d%s,?]? "
-msgstr "贮藏添加动作 [y,n,q,a,d%s,?]? "
+msgstr "储藏新增变更 [y,n,q,a,d%s,?]? "
 
 #: add-patch.c
 #, c-format
 msgid "Stash this hunk [y,n,q,a,d%s,?]? "
-msgstr "贮藏该块 [y,n,q,a,d%s,?]? "
+msgstr "储藏该块 [y,n,q,a,d%s,?]? "
 
 #: add-patch.c
 msgid ""
 "If the patch applies cleanly, the edited hunk will immediately be marked for "
 "stashing."
-msgstr "如果补丁能正确地应用,编辑块将立即标记为贮藏。"
+msgstr "如果补丁能正确地应用,编辑块将立即标记为储藏。"
 
 #: add-patch.c
 msgid ""
@@ -445,11 +460,11 @@
 "a - stash this hunk and all later hunks in the file\n"
 "d - do not stash this hunk or any of the later hunks in the file\n"
 msgstr ""
-"y - 贮藏该块\n"
-"n - 不要贮藏该块\n"
-"q - 退出。不贮藏该块及后面的全部块\n"
-"a - 贮藏该块和本文件中后面的全部块\n"
-"d - 不贮藏该块和本文件中后面的全部块\n"
+"y - 储藏该块\n"
+"n - 不要储藏该块\n"
+"q - 退出。不储藏该块及后面的全部块\n"
+"a - 储藏该块和本文件中后面的全部块\n"
+"d - 不储藏该块和本文件中后面的全部块\n"
 
 #: add-patch.c
 #, c-format
@@ -459,12 +474,12 @@
 #: add-patch.c
 #, c-format
 msgid "Unstage deletion [y,n,q,a,d%s,?]? "
-msgstr "取消暂存删除动作 [y,n,q,a,d%s,?]? "
+msgstr "取消暂存删除变更 [y,n,q,a,d%s,?]? "
 
 #: add-patch.c
 #, c-format
 msgid "Unstage addition [y,n,q,a,d%s,?]? "
-msgstr "取消暂存添加动作 [y,n,q,a,d%s,?]? "
+msgstr "取消暂存新增变更 [y,n,q,a,d%s,?]? "
 
 #: add-patch.c
 #, c-format
@@ -475,7 +490,7 @@
 msgid ""
 "If the patch applies cleanly, the edited hunk will immediately be marked for "
 "unstaging."
-msgstr "如果补丁能正确地应用,编辑块将立即标记为未暂存。"
+msgstr "如果补丁能正确地应用,编辑块将立即标记为取消暂存。"
 
 #: add-patch.c
 msgid ""
@@ -539,12 +554,12 @@
 #: add-patch.c
 #, c-format
 msgid "Discard deletion from worktree [y,n,q,a,d%s,?]? "
-msgstr "从工作区中丢弃删除动作 [y,n,q,a,d%s,?]? "
+msgstr "从工作区中丢弃删除变更 [y,n,q,a,d%s,?]? "
 
 #: add-patch.c
 #, c-format
 msgid "Discard addition from worktree [y,n,q,a,d%s,?]? "
-msgstr "从工作区中丢弃添加动作 [y,n,q,a,d%s,?]? "
+msgstr "从工作区中丢弃新增变更 [y,n,q,a,d%s,?]? "
 
 #: add-patch.c
 #, c-format
@@ -579,12 +594,12 @@
 #: add-patch.c
 #, c-format
 msgid "Discard deletion from index and worktree [y,n,q,a,d%s,?]? "
-msgstr "从索引和工作区中丢弃删除动作 [y,n,q,a,d%s,?]? "
+msgstr "从索引和工作区中丢弃删除变更 [y,n,q,a,d%s,?]? "
 
 #: add-patch.c
 #, c-format
 msgid "Discard addition from index and worktree [y,n,q,a,d%s,?]? "
-msgstr "从索引和工作区中丢弃添加动作 [y,n,q,a,d%s,?]? "
+msgstr "从索引和工作区中丢弃新增变更 [y,n,q,a,d%s,?]? "
 
 #: add-patch.c
 #, c-format
@@ -680,11 +695,11 @@
 
 #: add-patch.c
 msgid "could not parse diff"
-msgstr "不能解析差异信息"
+msgstr "无法解析差异信息"
 
 #: add-patch.c
 msgid "could not parse colored diff"
-msgstr "不能解析彩色差异信息"
+msgstr "无法解析彩色差异信息"
 
 #: add-patch.c
 #, c-format
@@ -707,7 +722,7 @@
 "expected context line #%d in\n"
 "%.*s"
 msgstr ""
-"预期上下文行 #%d 于\n"
+"预期上下文行 #%d 位于\n"
 "%.*s"
 
 #: add-patch.c
@@ -720,7 +735,7 @@
 msgstr ""
 "块不重叠:\n"
 "%.*s\n"
-"\t不是结尾于:\n"
+"\t未以以下内容结尾:\n"
 "%.*s"
 
 #: add-patch.c
@@ -746,7 +761,7 @@
 "edit again.  If all lines of the hunk are removed, then the edit is\n"
 "aborted and the hunk is left unchanged.\n"
 msgstr ""
-"如果不能干净地应用,您将有机会重新编辑。如果该块的全部内容删除,则\n"
+"如果无法干净地应用,您将有机会重新编辑。如果该块的全部内容删除,则\n"
 "此次编辑被终止,该块不会被修改。\n"
 
 #: add-patch.c
@@ -766,15 +781,15 @@
 #: add-patch.c
 msgid ""
 "Your edited hunk does not apply. Edit again (saying \"no\" discards!) [y/n]? "
-msgstr "您的编辑块不能被应用。重新编辑(选择 \"no\" 丢弃!) [y/n]? "
+msgstr "您的编辑块无法被应用。重新编辑(选择 \"no\" 丢弃!) [y/n]? "
 
 #: add-patch.c
 msgid "The selected hunks do not apply to the index!"
-msgstr "选中的块不能应用到索引!"
+msgstr "选中的块无法应用到索引!"
 
 #: add-patch.c
 msgid "Apply them to the worktree anyway? "
-msgstr "无论如何都要应用到工作区么?"
+msgstr "仍要应用到工作区吗?"
 
 #: add-patch.c
 msgid "Nothing was applied.\n"
@@ -813,15 +828,15 @@
 
 #: add-patch.c
 msgid "No other hunk"
-msgstr "没有其它块"
+msgstr "没有其他块"
 
 #: add-patch.c
 msgid "No other undecided hunk"
-msgstr "没有其它未决定的块"
+msgstr "没有其他未决定的块"
 
 #: add-patch.c
 msgid "No other hunks to goto"
-msgstr "没有其它可供跳转的块"
+msgstr "没有其他可供跳转的块"
 
 #: add-patch.c
 msgid "go to which hunk (<ret> to see more)? "
@@ -845,7 +860,7 @@
 
 #: add-patch.c
 msgid "No other hunks to search"
-msgstr "没有其它可供查找的块"
+msgstr "没有其他可供查找的块"
 
 #: add-patch.c
 msgid "search for regex? "
@@ -862,7 +877,7 @@
 
 #: add-patch.c
 msgid "Sorry, cannot split this hunk"
-msgstr "对不起,不能拆分这个块"
+msgstr "对不起,无法拆分这个块"
 
 #: add-patch.c
 #, c-format
@@ -871,7 +886,7 @@
 
 #: add-patch.c
 msgid "Sorry, cannot edit this hunk"
-msgstr "对不起,不能编辑这个块"
+msgstr "对不起,无法编辑这个块"
 
 #: add-patch.c
 #, c-format
@@ -922,7 +937,7 @@
 
 #: advice.c
 msgid "Reverting is not possible because you have unmerged files."
-msgstr "无法回退,因为您有未合并的文件。"
+msgstr "无法还原,因为您有未合并的文件。"
 
 #: advice.c
 msgid "Rebasing is not possible because you have unmerged files."
@@ -1049,7 +1064,7 @@
 "* Use \"git add --sparse <paths>\" to update the index\n"
 "* Use \"git sparse-checkout reapply\" to apply the sparsity rules"
 msgstr ""
-"为了修正这些的路径的稀疏性,请运行下列命令:\n"
+"为了修正这些路径的稀疏性,请运行下列命令:\n"
 "* 使用 \"git add --sparse <路径>\" 来更新索引\n"
 "* 使用 \"git sparse-checkout reapply\" 来应用稀疏规则"
 
@@ -1083,9 +1098,9 @@
 #: builtin/fast-export.c builtin/fetch.c builtin/help.c builtin/index-pack.c
 #: builtin/init-db.c builtin/log.c builtin/ls-files.c builtin/merge-base.c
 #: builtin/merge-tree.c builtin/merge.c builtin/rebase.c builtin/repack.c
-#: builtin/replay.c builtin/reset.c builtin/rev-parse.c builtin/show-branch.c
-#: builtin/stash.c builtin/submodule--helper.c builtin/tag.c builtin/worktree.c
-#: parse-options.c range-diff.c revision.c
+#: builtin/reset.c builtin/rev-parse.c builtin/show-branch.c builtin/stash.c
+#: builtin/submodule--helper.c builtin/tag.c builtin/worktree.c parse-options.c
+#: range-diff.c revision.c
 #, c-format
 msgid "options '%s' and '%s' cannot be used together"
 msgstr "选项 '%s' 和 '%s' 不能同时使用"
@@ -1116,7 +1131,7 @@
 #: apply.c
 #, c-format
 msgid "unable to find filename in patch at line %d"
-msgstr "不能在补丁的第 %d 行找到文件名"
+msgstr "无法在补丁的第 %d 行找到文件名"
 
 #: apply.c
 #, c-format
@@ -1225,7 +1240,7 @@
 #: apply.c
 #, c-format
 msgid "unable to open or read %s"
-msgstr "不能打开或读取 %s"
+msgstr "无法打开或读取 %s"
 
 #: apply.c
 #, c-format
@@ -1250,7 +1265,7 @@
 "while searching for:\n"
 "%.*s"
 msgstr ""
-"当查询:\n"
+"在搜索:\n"
 "%.*s"
 
 #: apply.c
@@ -1261,12 +1276,12 @@
 #: apply.c
 #, c-format
 msgid "cannot reverse-apply a binary patch without the reverse hunk to '%s'"
-msgstr "不能反向应用一个缺少到 '%s' 的反向数据块的二进制补丁"
+msgstr "无法反向应用一个缺少到 '%s' 的反向数据块的二进制补丁"
 
 #: apply.c
 #, c-format
 msgid "cannot apply binary patch to '%s' without full index line"
-msgstr "不能在 '%s' 上应用没有完整索引行的二进制补丁"
+msgstr "无法在 '%s' 上应用没有完整索引行的二进制补丁"
 
 #: apply.c
 #, c-format
@@ -1302,7 +1317,7 @@
 #: apply.c builtin/mv.c
 #, c-format
 msgid "cannot checkout %s"
-msgstr "不能检出 %s"
+msgstr "无法检出 %s"
 
 #: apply.c midx.c pack-mtimes.c pack-revindex.c setup.c
 #, c-format
@@ -1312,7 +1327,7 @@
 #: apply.c
 #, c-format
 msgid "reading from '%s' beyond a symbolic link"
-msgstr "读取位于符号链接中的 '%s'"
+msgstr "从 '%s' 读取时越过符号链接"
 
 #: apply.c
 #, c-format
@@ -1331,12 +1346,12 @@
 
 #: apply.c
 msgid "repository lacks the necessary blob to perform 3-way merge."
-msgstr "仓库缺乏执行三方合并所必需的数据对象。"
+msgstr "仓库缺乏执行三路合并所必需的数据对象。"
 
 #: apply.c
 #, c-format
 msgid "Performing three-way merge...\n"
-msgstr "执行三方合并...\n"
+msgstr "执行三路合并...\n"
 
 #: apply.c
 #, c-format
@@ -1346,12 +1361,12 @@
 #: apply.c
 #, c-format
 msgid "Failed to perform three-way merge...\n"
-msgstr "无法执行三方合并...\n"
+msgstr "无法执行三路合并...\n"
 
 #: apply.c
 #, c-format
 msgid "Applied patch to '%s' with conflicts.\n"
-msgstr "应用补丁到 '%s' 存在冲突。\n"
+msgstr "应用补丁到 '%s' 时发生冲突。\n"
 
 #: apply.c
 #, c-format
@@ -1361,7 +1376,7 @@
 #: apply.c
 #, c-format
 msgid "Falling back to direct application...\n"
-msgstr "回落到直接应用...\n"
+msgstr "改为直接应用...\n"
 
 #: apply.c
 msgid "removal patch leaves file contents"
@@ -1405,7 +1420,7 @@
 #: apply.c
 #, c-format
 msgid "affected file '%s' is beyond a symbolic link"
-msgstr "受影响的文件 '%s' 位于符号链接中"
+msgstr "受影响的文件 '%s' 位于符号链接之外"
 
 #: apply.c
 #, c-format
@@ -1435,32 +1450,32 @@
 #: apply.c
 #, c-format
 msgid "could not add %s to temporary index"
-msgstr "不能在临时索引中添加 %s"
+msgstr "无法在临时索引中添加 %s"
 
 #: apply.c
 #, c-format
 msgid "could not write temporary index to %s"
-msgstr "不能把临时索引写入到 %s"
+msgstr "无法把临时索引写入到 %s"
 
 #: apply.c
 #, c-format
 msgid "unable to remove %s from index"
-msgstr "不能从索引中移除 %s"
+msgstr "无法从索引中移除 %s"
 
 #: apply.c
 #, c-format
 msgid "corrupt patch for submodule %s"
-msgstr "子模组 %s 损坏的补丁"
+msgstr "针对子模组 %s 的补丁已损坏"
 
 #: apply.c
 #, c-format
 msgid "unable to stat newly created file '%s'"
-msgstr "不能对新建文件 '%s' 调用 stat"
+msgstr "无法对新建文件 '%s' 调用 stat"
 
 #: apply.c
 #, c-format
 msgid "unable to create backing store for newly created file %s"
-msgstr "不能为新建文件 %s 创建后端存储"
+msgstr "无法为新建文件 %s 创建后端存储"
 
 #: apply.c
 #, c-format
@@ -1480,7 +1495,7 @@
 #: apply.c
 #, c-format
 msgid "unable to write file '%s' mode %o"
-msgstr "不能写文件 '%s' 权限 %o"
+msgstr "无法写文件 '%s' 权限 %o"
 
 #: apply.c
 #, c-format
@@ -1495,37 +1510,37 @@
 #, c-format
 msgid "Applying patch %%s with %d reject..."
 msgid_plural "Applying patch %%s with %d rejects..."
-msgstr[0] "应用 %%s 个补丁,其中 %d 个被拒绝..."
-msgstr[1] "应用 %%s 个补丁,其中 %d 个被拒绝..."
+msgstr[0] "应用补丁 %%s,其中 %d 个被拒绝..."
+msgstr[1] "应用补丁 %%s,其中 %d 个被拒绝..."
 
 #: apply.c
 #, c-format
 msgid "cannot open %s"
-msgstr "不能打开 %s"
+msgstr "无法打开 %s"
 
 #: apply.c rerere.c
 #, c-format
 msgid "cannot unlink '%s'"
-msgstr "不能删除 '%s'"
+msgstr "无法删除 '%s'"
 
 #: apply.c
 #, c-format
 msgid "Hunk #%d applied cleanly."
-msgstr "第 #%d 个片段成功应用。"
+msgstr "第 #%d 个块成功应用。"
 
 #: apply.c
 #, c-format
 msgid "Rejected hunk #%d."
-msgstr "拒绝第 #%d 个片段。"
+msgstr "拒绝第 #%d 个块。"
 
 #: apply.c
 #, c-format
 msgid "Skipped patch '%s'."
-msgstr "略过补丁 '%s'。"
+msgstr "跳过补丁 '%s'。"
 
 #: apply.c
 msgid "No valid patches in input (allow with \"--allow-empty\")"
-msgstr "输入中没有合法的补丁 (使用 \"--allow-empty\" 来允许)"
+msgstr "输入中没有合法补丁(可用 \"--allow-empty\" 允许)"
 
 #: apply.c t/helper/test-cache-tree.c
 msgid "unable to read index file"
@@ -1534,14 +1549,14 @@
 #: apply.c
 #, c-format
 msgid "can't open patch '%s': %s"
-msgstr "不能打开补丁 '%s':%s"
+msgstr "无法打开补丁 '%s':%s"
 
 #: apply.c
 #, c-format
 msgid "squelched %d whitespace error"
 msgid_plural "squelched %d whitespace errors"
-msgstr[0] "抑制下仍有 %d 个空白字符误用"
-msgstr[1] "抑制下仍有 %d 个空白字符误用"
+msgstr[0] "已抑制 %d 个空白字符错误"
+msgstr[1] "已抑制 %d 个空白字符错误"
 
 #: apply.c
 #, c-format
@@ -1563,11 +1578,11 @@
 
 #: apply.c
 msgid "don't apply changes matching the given path"
-msgstr "不要应用与给出路径向匹配的变更"
+msgstr "不要应用与给定路径相匹配的变更"
 
 #: apply.c
 msgid "apply changes matching the given path"
-msgstr "应用与给出路径向匹配的变更"
+msgstr "应用与给定路径相匹配的变更"
 
 #: apply.c builtin/am.c
 msgid "num"
@@ -1579,7 +1594,7 @@
 
 #: apply.c
 msgid "ignore additions made by the patch"
-msgstr "忽略补丁中的添加的文件"
+msgstr "忽略补丁新增的内容"
 
 #: apply.c
 msgid "instead of applying the patch, output diffstat for the input"
@@ -1619,7 +1634,7 @@
 
 #: apply.c
 msgid "attempt three-way merge, fall back on normal patch if that fails"
-msgstr "尝试三路合并,如果失败则回落至正常补丁模式"
+msgstr "尝试三路合并,如果失败则回落到普通补丁模式"
 
 #: apply.c builtin/merge-file.c
 msgid "for conflicts, use our version"
@@ -1635,7 +1650,7 @@
 
 #: apply.c
 msgid "build a temporary index based on embedded index information"
-msgstr "创建一个临时索引基于嵌入的索引信息"
+msgstr "基于嵌入的索引信息创建临时索引"
 
 #: apply.c builtin/checkout-index.c
 msgid "paths are separated with NUL character"
@@ -1652,7 +1667,7 @@
 
 #: apply.c
 msgid "detect new or modified lines that have whitespace errors"
-msgstr "检查新增和修改的行中间的空白字符滥用"
+msgstr "检测新增或修改的行是否存在空白错误"
 
 #: apply.c
 msgid "ignore changes in whitespace when finding context"
@@ -1668,19 +1683,19 @@
 
 #: apply.c
 msgid "leave the rejected hunks in corresponding *.rej files"
-msgstr "将拒绝的补丁片段保存在对应的 *.rej 文件中"
+msgstr "将被拒绝的块保存在对应的 *.rej 文件中"
 
 #: apply.c
 msgid "allow overlapping hunks"
-msgstr "允许重叠的补丁片段"
+msgstr "允许重叠的块"
 
 #: apply.c
 msgid "tolerate incorrectly detected missing new-line at the end of file"
-msgstr "允许不正确的文件末尾换行符"
+msgstr "容忍误判的文件末尾缺失换行符"
 
 #: apply.c
 msgid "do not trust the line counts in the hunk headers"
-msgstr "不信任补丁片段的头信息中的行号"
+msgstr "不信任块头信息中的行数"
 
 #: apply.c builtin/am.c
 msgid "root"
@@ -1701,12 +1716,12 @@
 #: archive-tar.c archive-zip.c
 #, c-format
 msgid "cannot stream blob %s"
-msgstr "不能打开数据对象 %s"
+msgstr "无法流式传输数据对象 %s"
 
 #: archive-tar.c archive-zip.c
 #, c-format
 msgid "unsupported file mode: 0%o (SHA1: %s)"
-msgstr "不支持的文件模式:0%o (SHA1: %s)"
+msgstr "不支持的文件模式:0%o (SHA1:%s)"
 
 #: archive-tar.c archive-zip.c builtin/pack-objects.c
 #, c-format
@@ -1759,12 +1774,12 @@
 #: archive.c builtin/fast-import.c builtin/gc.c builtin/notes.c builtin/tag.c
 #, c-format
 msgid "cannot read '%s'"
-msgstr "不能读取 '%s'"
+msgstr "无法读取 '%s'"
 
 #: archive.c
 #, c-format
 msgid "pathspec '%s' matches files outside the current directory"
-msgstr "路径规格 '%s' 匹配了当前目录外的文件'"
+msgstr "路径规格 '%s' 匹配了当前目录外的文件"
 
 #: archive.c builtin/add.c builtin/rm.c
 #, c-format
@@ -1881,7 +1896,7 @@
 
 #: archive.c builtin/archive.c
 msgid "retrieve the archive from remote repository <repo>"
-msgstr "从远程仓库(<仓库>)提取归档文件"
+msgstr "从远程仓库 <仓库> 提取归档文件"
 
 #: archive.c builtin/archive.c builtin/difftool.c builtin/notes.c
 msgid "command"
@@ -1929,7 +1944,7 @@
 
 #: attr.c
 msgid "unable to add additional attribute"
-msgstr "不能添加额外属性"
+msgstr "无法添加额外属性"
 
 #: attr.c
 #, c-format
@@ -1946,7 +1961,7 @@
 "Negative patterns are ignored in git attributes\n"
 "Use '\\!' for literal leading exclamation."
 msgstr ""
-"负值模版在 git attributes 中被忽略\n"
+"否定模式在 git attributes 中被忽略\n"
 "当字符串确实要以感叹号开始时,使用 '\\!'。"
 
 #: attr.c
@@ -1981,7 +1996,7 @@
 #: builtin/pack-objects.c combine-diff.c object-file.c rerere.c
 #, c-format
 msgid "unable to read %s"
-msgstr "不能读 %s"
+msgstr "无法读取 %s"
 
 #: bisect.c
 #, c-format
@@ -2004,7 +2019,7 @@
 "The merge base %s is bad.\n"
 "This means the bug has been fixed between %s and [%s].\n"
 msgstr ""
-"合并基线 %s 是坏的。\n"
+"合并基线 %s 是有问题的。\n"
 "这意味着介于 %s 和 [%s] 之间的 bug 已经被修复。\n"
 
 #: bisect.c
@@ -2043,14 +2058,14 @@
 "So we cannot be sure the first %s commit is between %s and %s.\n"
 "We continue anyway."
 msgstr ""
-"介于 %s 和 [%s] 的合并基线一定被忽略了。\n"
+"介于 %s 和 [%s] 的合并基线必须被跳过。\n"
 "所以我们无法确认第一个 %s 提交是否介于 %s 和 %s 之间。\n"
-"我们仍旧继续。"
+"我们仍然继续。"
 
 #: bisect.c
 #, c-format
 msgid "Bisecting: a merge base must be tested\n"
-msgstr "二分查找中:合并基线必须是经过测试的\n"
+msgstr "二分查找中:合并基线必须经过测试\n"
 
 #: bisect.c
 #, c-format
@@ -2060,17 +2075,17 @@
 #: bisect.c
 #, c-format
 msgid "could not create file '%s'"
-msgstr "不能创建文件 '%s'"
+msgstr "无法创建文件 '%s'"
 
 #: bisect.c builtin/notes.c
 #, c-format
 msgid "unable to start 'show' for object '%s'"
-msgstr "不能为对象 '%s' 开始 'show'"
+msgstr "无法对对象 '%s' 启动 'show'"
 
 #: bisect.c builtin/merge.c
 #, c-format
 msgid "could not read file '%s'"
-msgstr "不能读取文件 '%s'"
+msgstr "无法读取文件 '%s'"
 
 #: bisect.c
 msgid "reading bisect refs failed"
@@ -2134,7 +2149,7 @@
 #: blame.c
 #, c-format
 msgid "cannot read blob %s for path %s"
-msgstr "不能为路径 %2$s 读取数据对象 %1$s"
+msgstr "无法为路径 %2$s 读取数据对象 %1$s"
 
 #: branch.c
 msgid ""
@@ -2173,7 +2188,7 @@
 "the remote tracking information by invoking:"
 msgstr ""
 "\n"
-"在修复错误后,您可以通过执行以下命令来尝试修改远程跟踪分支:"
+"在修复错误后,您可以通过执行以下命令来尝试修复远程跟踪信息:"
 
 #: branch.c
 #, c-format
@@ -2188,7 +2203,7 @@
 #: branch.c
 #, c-format
 msgid "not tracking: ambiguous information for ref '%s'"
-msgstr "不在跟踪中:引用 '%s' 有歧义的信息"
+msgstr "未跟踪:引用 '%s' 的信息含糊不清"
 
 #  译者:为保证在输出中对齐,注意调整句中空格!
 #. #-#-#-#-#  branch.c.po  #-#-#-#-#
@@ -2222,11 +2237,11 @@
 "different remotes' fetch refspecs map into different\n"
 "tracking namespaces."
 msgstr ""
-"有多个远程的获取引用规格映射到了追踪引用 '%s':\n"
+"有多个远程的获取引用规格映射到了跟踪引用 '%s':\n"
 "%s\n"
 "这一般是个配置错误。\n"
 "\n"
-"如果要支持设置追踪分支,请保证不同远程的获取引用规格映射至不同的追踪命名空"
+"如果要支持设置跟踪分支,请保证不同远程的获取引用规格映射至不同的跟踪命名空"
 "间。"
 
 #: branch.c
@@ -2235,8 +2250,8 @@
 msgstr "'%s' 不是一个有效的分支名称"
 
 #: branch.c builtin/branch.c
-msgid "See `man git check-ref-format`"
-msgstr "查阅 `man git check-ref-format`"
+msgid "See 'git help check-ref-format'"
+msgstr "参阅 'git help check-ref-format'"
 
 #: branch.c
 #, c-format
@@ -2302,13 +2317,13 @@
 "You may try updating the submodules using 'git checkout --no-recurse-"
 "submodules %s && git submodule update --init'"
 msgstr ""
-"你可以用 'git checkout --no-recurse-submodules %s && git submodule update --"
+"您可以用 'git checkout --no-recurse-submodules %s && git submodule update --"
 "init' 来尝试更新子模组"
 
 #: branch.c
 #, c-format
 msgid "submodule '%s': cannot create branch '%s'"
-msgstr "子模组 '%s':不能创建分支 '%s'"
+msgstr "子模组 '%s':无法创建分支 '%s'"
 
 #: branch.c
 #, c-format
@@ -2322,7 +2337,7 @@
 #: builtin/add.c
 #, c-format
 msgid "cannot chmod %cx '%s'"
-msgstr "不能 chmod %cx '%s'"
+msgstr "无法 chmod %cx '%s'"
 
 #: builtin/add.c
 msgid "Unstaged changes after refreshing the index:"
@@ -2330,7 +2345,7 @@
 
 #: builtin/add.c
 msgid "could not read the index"
-msgstr "不能读取索引"
+msgstr "无法读取索引"
 
 #: builtin/add.c
 msgid "editing patch failed"
@@ -2339,16 +2354,16 @@
 #: builtin/add.c read-cache.c
 #, c-format
 msgid "could not stat '%s'"
-msgstr "不能对 '%s' 调用 stat"
+msgstr "无法对 '%s' 调用 stat"
 
 #: builtin/add.c
 msgid "empty patch. aborted"
-msgstr "空补丁。异常终止"
+msgstr "空补丁。已中止"
 
 #: builtin/add.c
 #, c-format
 msgid "could not apply '%s'"
-msgstr "不能应用 '%s'"
+msgstr "无法应用 '%s'"
 
 #: builtin/add.c
 msgid "The following paths are ignored by one of your .gitignore files:\n"
@@ -2358,13 +2373,13 @@
 #: builtin/prune-packed.c builtin/pull.c builtin/push.c builtin/remote.c
 #: builtin/rm.c builtin/send-pack.c builtin/sparse-checkout.c
 msgid "dry run"
-msgstr "演习"
+msgstr "试运行"
 
 #: builtin/add.c builtin/check-ignore.c builtin/commit.c
 #: builtin/count-objects.c builtin/fsck.c builtin/log.c builtin/mv.c
 #: builtin/read-tree.c builtin/refs.c
 msgid "be verbose"
-msgstr "冗长输出"
+msgstr "输出更详细"
 
 #: builtin/add.c
 msgid "interactive picking"
@@ -2392,11 +2407,11 @@
 
 #: builtin/add.c
 msgid "record only the fact that the path will be added later"
-msgstr "只记录,该路径稍后再添加"
+msgstr "只记录该路径稍后将被添加"
 
 #: builtin/add.c
 msgid "add changes from all tracked and untracked files"
-msgstr "添加所有改变的已跟踪文件和未跟踪文件"
+msgstr "添加所有已跟踪和未跟踪文件的变更"
 
 #: builtin/add.c
 msgid "ignore paths removed in the working tree (same as --no-all)"
@@ -2408,11 +2423,11 @@
 
 #: builtin/add.c
 msgid "just skip files which cannot be added because of errors"
-msgstr "跳过因出错不能添加的文件"
+msgstr "跳过因出错无法添加的文件"
 
 #: builtin/add.c
 msgid "check if - even missing - files are ignored in dry run"
-msgstr "检查在演习模式下文件(即使不存在)是否被忽略"
+msgstr "检查试运行时文件(即使不存在)是否被忽略"
 
 #: builtin/add.c builtin/mv.c builtin/rm.c
 msgid "allow updating entries outside of the sparse-checkout cone"
@@ -2424,7 +2439,7 @@
 
 #: builtin/add.c
 msgid "warn when adding an embedded repository"
-msgstr "创建一个嵌入式仓库时给予警告"
+msgstr "添加嵌入式仓库时给予警告"
 
 #: builtin/add.c
 #, c-format
@@ -2443,7 +2458,7 @@
 "\n"
 "See \"git help submodule\" for more information."
 msgstr ""
-"您在当前仓库中添加了另一个Git仓库。克隆外层的仓库将不包含嵌入仓库的\n"
+"您在当前仓库中添加了另一个 Git 仓库。外层仓库的克隆将不包含嵌入式仓库的\n"
 "内容,并且不知道该如何获取它。如果您要添加一个子模组,使用:\n"
 "\n"
 "\tgit submodule add <url> %s\n"
@@ -2508,7 +2523,7 @@
 #: builtin/am.c builtin/mailinfo.c mailinfo.c
 #, c-format
 msgid "bad action '%s' for '%s'"
-msgstr "'%2$s' 的错误动作 '%1$s'"
+msgstr "用于 '%2$s' 的动作 '%1$s' 无效"
 
 #: builtin/am.c builtin/blame.c builtin/fetch.c builtin/pack-objects.c
 #: builtin/pull.c builtin/revert.c diff-merges.c diff.c environment.c
@@ -2520,16 +2535,16 @@
 #: builtin/am.c builtin/commit.c builtin/merge.c sequencer.c
 #, c-format
 msgid "could not read '%s'"
-msgstr "不能读取 '%s'"
+msgstr "无法读取 '%s'"
 
 #: builtin/am.c
 msgid "could not parse author script"
-msgstr "不能解析作者脚本"
+msgstr "无法解析作者脚本"
 
 #: builtin/am.c builtin/replace.c commit.c sequencer.c
 #, c-format
 msgid "could not parse %s"
-msgstr "不能解析 %s"
+msgstr "无法解析 %s"
 
 #: builtin/am.c
 #, c-format
@@ -2632,15 +2647,15 @@
 #: builtin/am.c builtin/checkout.c builtin/clone.c commit-graph.c
 #, c-format
 msgid "unable to parse commit %s"
-msgstr "不能解析提交 %s"
+msgstr "无法解析提交 %s"
 
 #: builtin/am.c
 msgid "Repository lacks necessary blobs to fall back on 3-way merge."
-msgstr "仓库缺乏必要的数据对象以进行三方合并。"
+msgstr "仓库缺乏必要的数据对象,无法回落到三路合并。"
 
 #: builtin/am.c
 msgid "Using index info to reconstruct a base tree..."
-msgstr "使用索引来重建一个(三方合并的)基础目录树..."
+msgstr "使用索引来重建一个(三路合并的)基础目录树..."
 
 #: builtin/am.c
 msgid ""
@@ -2652,7 +2667,7 @@
 
 #: builtin/am.c
 msgid "Falling back to patching base and 3-way merge..."
-msgstr "回落到基础版本上打补丁及进行三方合并..."
+msgstr "回落到在基础版本上打补丁并进行三路合并..."
 
 #: builtin/am.c
 msgid "Failed to merge in the changes."
@@ -2695,7 +2710,7 @@
 #: builtin/am.c
 #, c-format
 msgid "Dirty index: cannot apply patches (dirty: %s)"
-msgstr "脏索引:不能应用补丁(脏文件:%s)"
+msgstr "脏索引:无法应用补丁(脏文件:%s)"
 
 #: builtin/am.c
 #, c-format
@@ -2740,7 +2755,7 @@
 "already introduced the same changes; you might want to skip this patch."
 msgstr ""
 "没有变更 —— 您是不是忘了执行 'git add'?\n"
-"如果没有什么要添加到暂存区的,则很可能是其它提交已经引入了相同的变更。\n"
+"如果没有什么要添加到暂存区的,则很可能是其他提交已经引入了相同的变更。\n"
 "您也许想要跳过这个补丁。"
 
 #: builtin/am.c
@@ -2751,13 +2766,13 @@
 "You might run `git rm` on a file to accept \"deleted by them\" for it."
 msgstr ""
 "在您的索引中仍存在未合并的路径。\n"
-"您应该对已经冲突解决的每一个文件执行 'git add' 来标记已经完成。 \n"
+"您应该对已经冲突解决的每一个文件执行 'git add' 来标记已经完成。\n"
 "您可以对 \"由他们删除\" 的文件执行 `git rm` 命令。"
 
 #: builtin/am.c builtin/reset.c
 #, c-format
 msgid "Could not parse object '%s'."
-msgstr "不能解析对象 '%s'。"
+msgstr "无法解析对象 '%s'。"
 
 #: builtin/am.c
 msgid "failed to clean index"
@@ -2796,7 +2811,7 @@
 
 #: builtin/am.c
 msgid "allow fall back on 3way merging if needed"
-msgstr "如果必要,允许使用三方合并。"
+msgstr "如有需要,允许回落到三路合并"
 
 #: builtin/am.c builtin/init-db.c builtin/prune-packed.c builtin/repack.c
 #: builtin/stash.c
@@ -2859,7 +2874,7 @@
 
 #: builtin/am.c
 msgid "override error message when patch failure occurs"
-msgstr "打补丁失败时显示的错误信息"
+msgstr "覆盖打补丁失败时的错误信息"
 
 #: builtin/am.c
 msgid "continue applying patches after resolving a conflict"
@@ -2895,7 +2910,7 @@
 
 #: builtin/am.c
 msgid "lie about committer date"
-msgstr "将作者日期作为提交日期"
+msgstr "伪造提交者日期"
 
 #: builtin/am.c
 msgid "use current timestamp for author date"
@@ -2904,7 +2919,7 @@
 #: builtin/am.c builtin/commit-tree.c builtin/commit.c builtin/merge.c
 #: builtin/pull.c builtin/rebase.c builtin/revert.c builtin/tag.c
 msgid "key-id"
-msgstr "key-id"
+msgstr "密钥 ID"
 
 #: builtin/am.c builtin/rebase.c
 msgid "GPG-sign commits"
@@ -2941,7 +2956,7 @@
 "Stray %s directory found.\n"
 "Use \"git am --abort\" to remove it."
 msgstr ""
-"发现了错误的 %s 目录。\n"
+"发现了残留的 %s 目录。\n"
 "使用 \"git am --abort\" 删除它。"
 
 #: builtin/am.c
@@ -2958,7 +2973,7 @@
 
 #: builtin/archive.c diagnose.c
 msgid "could not redirect output"
-msgstr "不能重定向输出"
+msgstr "无法重定向输出"
 
 #: builtin/archive.c
 msgid "git archive: expected ACK/NAK, got a flush packet"
@@ -3030,17 +3045,17 @@
 #: builtin/bisect.c
 #, c-format
 msgid "cannot open file '%s' in mode '%s'"
-msgstr "不能以 '%2$s' 模式打开文件 '%1$s'"
+msgstr "无法以 '%2$s' 模式打开文件 '%1$s'"
 
 #: builtin/bisect.c
 #, c-format
 msgid "could not write to file '%s'"
-msgstr "不能写入文件 '%s'"
+msgstr "无法写入文件 '%s'"
 
 #: builtin/bisect.c
 #, c-format
 msgid "cannot open file '%s' for reading"
-msgstr "不能打开文件 '%s' 来读取"
+msgstr "无法打开文件 '%s' 来读取"
 
 #: builtin/bisect.c
 #, c-format
@@ -3050,12 +3065,12 @@
 #: builtin/bisect.c
 #, c-format
 msgid "can't use the builtin command '%s' as a term"
-msgstr "不能使用内置命令 '%s' 作为术语"
+msgstr "无法使用内置命令 '%s' 作为术语"
 
 #: builtin/bisect.c
 #, c-format
 msgid "can't change the meaning of the term '%s'"
-msgstr "不能修改术语 '%s' 的含义"
+msgstr "无法修改术语 '%s' 的含义"
 
 #: builtin/bisect.c
 msgid "please use two different terms"
@@ -3075,12 +3090,12 @@
 #, c-format
 msgid ""
 "could not check out original HEAD '%s'. Try 'git bisect reset <commit>'."
-msgstr "不能检出原始 HEAD '%s'。尝试 'git bisect reset <提交>'。"
+msgstr "无法检出原始 HEAD '%s'。尝试 'git bisect reset <提交>'。"
 
 #: builtin/bisect.c
 #, c-format
 msgid "Bad bisect_write argument: %s"
-msgstr "坏的 bisect_write 参数:%s"
+msgstr "错误的 bisect_write 参数:%s"
 
 #: builtin/bisect.c
 #, c-format
@@ -3128,7 +3143,7 @@
 #.
 #: builtin/bisect.c
 msgid "Are you sure [Y/n]? "
-msgstr "您确认么[Y/n]? "
+msgstr "您确认吗 [Y/n]? "
 
 #: builtin/bisect.c
 msgid "status: waiting for both good and bad commits\n"
@@ -3186,7 +3201,7 @@
 
 #: builtin/bisect.c
 msgid "bad HEAD - I need a HEAD"
-msgstr "坏的 HEAD - 我需要一个 HEAD"
+msgstr "无效的 HEAD - 需要一个 HEAD"
 
 #: builtin/bisect.c
 #, c-format
@@ -3195,7 +3210,7 @@
 
 #: builtin/bisect.c
 msgid "bad HEAD - strange symbolic ref"
-msgstr "坏的 HEAD - 奇怪的符号引用"
+msgstr "无效的 HEAD - 奇怪的符号引用"
 
 #: builtin/bisect.c
 #, c-format
@@ -3212,7 +3227,7 @@
 #.
 #: builtin/bisect.c
 msgid "Do you want me to do it for you [Y/n]? "
-msgstr "您想让我为您这样做么[Y/n]? "
+msgstr "您想让我为您这样做吗[Y/n]? "
 
 #: builtin/bisect.c
 msgid "Please call `--bisect-state` with at least one argument"
@@ -3226,12 +3241,12 @@
 #: builtin/bisect.c
 #, c-format
 msgid "Bad rev input: %s"
-msgstr "坏的版本输入:%s"
+msgstr "错误的版本输入:%s"
 
 #: builtin/bisect.c
 #, c-format
 msgid "Bad rev input (not a commit): %s"
-msgstr "坏的版本输入(不是提交):%s"
+msgstr "错误的版本输入(不是提交):%s"
 
 #: builtin/bisect.c
 msgid "We are not bisecting."
@@ -3240,12 +3255,12 @@
 #: builtin/bisect.c
 #, c-format
 msgid "'%s'?? what are you talking about?"
-msgstr "'%s'?? 您在说什么?"
+msgstr "'%s'??您在说什么?"
 
 #: builtin/bisect.c
 #, c-format
 msgid "cannot read file '%s' for replaying"
-msgstr "不能读取文件 '%s' 来重放"
+msgstr "无法读取文件 '%s' 来重放"
 
 #: builtin/bisect.c
 #, c-format
@@ -3278,7 +3293,7 @@
 
 #: builtin/bisect.c
 msgid "bisect run cannot continue any more"
-msgstr "二分查找不能继续运行"
+msgstr "二分查找无法继续运行"
 
 #: builtin/bisect.c
 msgid "bisect run success"
@@ -3341,16 +3356,29 @@
 #: builtin/blame.c
 #, c-format
 msgid "expecting a color: %s"
-msgstr "期望一个颜色:%s"
+msgstr "期望是颜色:%s"
 
 #: builtin/blame.c
 msgid "must end with a color"
 msgstr "必须以一个颜色结尾"
 
+#: builtin/blame.c diff.c merge-ort.c transport.c
+#, c-format
+msgid "unknown value for config '%s': %s"
+msgstr "配置 '%s' 未知的取值:%s"
+
+#: builtin/blame.c builtin/merge-file.c diff.c
+msgid ""
+"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and "
+"\"histogram\""
+msgstr ""
+"选项 diff-algorithm 接受参数 \"myers\"、\"minimal\"、\"patience\" 和 "
+"\"histogram\""
+
 #: builtin/blame.c
 #, c-format
 msgid "cannot find revision %s to ignore"
-msgstr "不能找到要忽略的版本 %s"
+msgstr "无法找到要忽略的版本 %s"
 
 #: builtin/blame.c
 msgid "show blame entries as we find them, incrementally"
@@ -3392,7 +3420,7 @@
 
 #: builtin/blame.c
 msgid "show porcelain format with per-line commit information"
-msgstr "为每一行显示机器适用的提交信息"
+msgstr "以 porcelain 格式显示每行的提交信息"
 
 #: builtin/blame.c
 msgid "use the same output mode as git-annotate (Default: off)"
@@ -3418,6 +3446,14 @@
 msgid "ignore whitespace differences"
 msgstr "忽略空白差异"
 
+#: builtin/blame.c builtin/merge-file.c diff.c
+msgid "<algorithm>"
+msgstr "<算法>"
+
+#: builtin/blame.c builtin/merge-file.c diff.c
+msgid "choose a diff algorithm"
+msgstr "选择一个差异算法"
+
 #: builtin/blame.c builtin/clone.c builtin/log.c
 msgid "rev"
 msgstr "版本"
@@ -3432,15 +3468,15 @@
 
 #: builtin/blame.c
 msgid "color redundant metadata from previous line differently"
-msgstr "使用颜色间隔输出与前一行不同的重复元信息"
+msgstr "用不同颜色标示与前一行重复的元信息"
 
 #: builtin/blame.c
 msgid "color lines by age"
 msgstr "依据时间着色"
 
 #: builtin/blame.c
-msgid "spend extra cycles to find better match"
-msgstr "花费额外的循环来找到更好的匹配"
+msgid "spend extra cycles to find a better match"
+msgstr "花费更多计算来找到更好的匹配"
 
 #: builtin/blame.c
 msgid "use revisions from <file> instead of calling git-rev-list"
@@ -3448,7 +3484,7 @@
 
 #: builtin/blame.c
 msgid "use <file>'s contents as the final image"
-msgstr "使用 <文件> 的内容作为最终的镜像"
+msgstr "使用 <文件> 的内容作为最终结果"
 
 #: builtin/blame.c
 msgid "score"
@@ -3472,7 +3508,7 @@
 
 #: builtin/blame.c
 msgid "--progress can't be used with --incremental or porcelain formats"
-msgstr "--progress 不能和 --incremental 或机器内部格式一起使用"
+msgstr "--progress 不能和 --incremental 或 porcelain 格式一起使用"
 
 #. TRANSLATORS: This string is used to tell us the
 #. maximum display width for a relative timestamp in
@@ -3521,7 +3557,7 @@
 
 #: builtin/branch.c
 msgid "git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"
-msgstr "git branch [<选项>] (-c | -C) [<老分支>] <新分支>"
+msgstr "git branch [<选项>] (-c | -C) [<旧分支>] <新分支>"
 
 #: builtin/branch.c
 msgid "git branch [<options>] [-r | -a] [--points-at]"
@@ -3577,7 +3613,7 @@
 #: builtin/branch.c
 #, c-format
 msgid "cannot delete branch '%s' used by worktree at '%s'"
-msgstr "无法强制更新被工作区 '%2$s' 所使用的分支 '%1$s'"
+msgstr "无法删除被工作区 '%2$s' 使用的分支 '%1$s'"
 
 #: builtin/branch.c
 #, c-format
@@ -3596,7 +3632,7 @@
 #: builtin/branch.c
 #, c-format
 msgid "branch '%s' not found"
-msgstr "分支 '%s' 未发现"
+msgstr "分支 '%s' 未找到"
 
 #: builtin/branch.c
 #, c-format
@@ -3610,11 +3646,11 @@
 
 #: builtin/branch.c builtin/tag.c
 msgid "unable to parse format string"
-msgstr "不能解析格式化字符串"
+msgstr "无法解析格式化字符串"
 
 #: builtin/branch.c
 msgid "could not resolve HEAD"
-msgstr "不能解析 HEAD 提交"
+msgstr "无法解析 HEAD"
 
 #: builtin/branch.c
 #, c-format
@@ -3629,12 +3665,12 @@
 #: builtin/branch.c
 #, c-format
 msgid "branch %s is being bisected at %s"
-msgstr "分支 %s 正被二分查找于 %s"
+msgstr "分支 %s 正在 %s 处进行二分查找"
 
 #: builtin/branch.c
 #, c-format
 msgid "HEAD of working tree %s is not updated"
-msgstr "工作区 %s 的 HEAD 指向没有被更新"
+msgstr "工作区 %s 的 HEAD 未更新"
 
 #: builtin/branch.c
 #, c-format
@@ -3691,7 +3727,7 @@
 msgstr ""
 "请编辑分支的描述\n"
 "  %s\n"
-"以 '%s' 开头的行将被过滤。\n"
+"以 '%s' 开头的行将被移除。\n"
 
 #: builtin/branch.c
 msgid "Generic options"
@@ -3743,7 +3779,7 @@
 
 #: builtin/branch.c
 msgid "Specific git-branch actions:"
-msgstr "具体的 git-branch 动作:"
+msgstr "具体的 git-branch 操作:"
 
 #: builtin/branch.c
 msgid "list both remote-tracking and local branches"
@@ -3791,7 +3827,7 @@
 
 #: builtin/branch.c
 msgid "edit the description for the branch"
-msgstr "标记分支的描述"
+msgstr "编辑分支的描述"
 
 #: builtin/branch.c
 msgid "force creation, move/rename, deletion"
@@ -3819,7 +3855,7 @@
 
 #: builtin/branch.c builtin/for-each-ref.c builtin/tag.c
 msgid "sorting and filtering are case insensitive"
-msgstr "排序和过滤属于大小写不敏感"
+msgstr "排序和过滤不区分大小写"
 
 #: builtin/branch.c builtin/ls-files.c
 msgid "recurse through submodules"
@@ -3836,7 +3872,7 @@
 
 #: builtin/branch.c builtin/clone.c
 msgid "HEAD not found below refs/heads!"
-msgstr "HEAD 没有位于 /refs/heads 之下!"
+msgstr "HEAD 没有位于 refs/heads 之下!"
 
 #: builtin/branch.c
 msgid ""
@@ -3855,11 +3891,11 @@
 
 #: builtin/branch.c
 msgid "cannot give description to detached HEAD"
-msgstr "不能向分离头指针提供描述"
+msgstr "无法向分离头指针提供描述"
 
 #: builtin/branch.c
 msgid "cannot edit description of more than one branch"
-msgstr "不能为一个以上的分支编辑描述"
+msgstr "无法为一个以上的分支编辑描述"
 
 #: builtin/branch.c
 msgid "cannot copy the current branch while not on any"
@@ -3903,7 +3939,7 @@
 
 #: builtin/branch.c
 msgid "could not unset upstream of HEAD when it does not point to any branch"
-msgstr "无法取消 HEAD 的上游设置因为它没有指向一个分支"
+msgstr "无法取消 HEAD 的上游设置,因为它没有指向任何分支"
 
 #: builtin/branch.c
 #, c-format
@@ -3946,9 +3982,9 @@
 "              [(-s | --suffix) <format> | --no-suffix]\n"
 "              [--diagnose[=<mode>]]"
 msgstr ""
-"git bugreport [-o | --output-directory <文件>]\n"
+"git bugreport [-o | --output-directory <路径>]\n"
 "              [(-s | --suffix) <格式> | --no-suffix]\n"
-"              [--diagnose[=<模式>]"
+"              [--diagnose[=<模式>]]"
 
 #: builtin/bugreport.c
 msgid ""
@@ -3979,13 +4015,13 @@
 "\n"
 "您所期望的与实际发生的有什么不同?\n"
 "\n"
-"您想要补充的其它内容:\n"
+"您想要补充的其他内容:\n"
 "\n"
 "请检查下面错误报告中余下的内容。\n"
 "您可以删除任何您不想共享的内容。\n"
 
 #: builtin/bugreport.c builtin/commit.c builtin/fast-export.c
-#: builtin/pack-objects.c builtin/rebase.c parse-options.h
+#: builtin/pack-objects.c builtin/rebase.c builtin/replay.c parse-options.h
 msgid "mode"
 msgstr "模式"
 
@@ -4010,12 +4046,12 @@
 #: builtin/bugreport.c builtin/diagnose.c
 #, c-format
 msgid "could not create leading directories for '%s'"
-msgstr "不能为 '%s' 创建先导目录"
+msgstr "无法为 '%s' 创建上级目录"
 
 #: builtin/bugreport.c builtin/diagnose.c
 #, c-format
 msgid "unable to create diagnostics archive %s"
-msgstr "不能创建诊断归档包 %s"
+msgstr "无法创建诊断归档包 %s"
 
 #: builtin/bugreport.c
 msgid "System Info"
@@ -4061,11 +4097,11 @@
 
 #: builtin/bundle.c builtin/pack-objects.c
 msgid "do not show progress meter"
-msgstr "不显示进度表"
+msgstr "不显示进度条"
 
 #: builtin/bundle.c builtin/pack-objects.c
 msgid "show progress meter"
-msgstr "显示进度表"
+msgstr "显示进度条"
 
 #: builtin/bundle.c
 msgid "historical; same as --progress"
@@ -4077,28 +4113,28 @@
 
 #: builtin/bundle.c
 msgid "specify bundle format version"
-msgstr "指定归档包的格式版本"
+msgstr "指定捆绑包的格式版本"
 
 #: builtin/bundle.c
 msgid "Need a repository to create a bundle."
-msgstr "需要一个仓库来创建归档包。"
+msgstr "需要一个仓库来创建捆绑包。"
 
 #: builtin/bundle.c
 msgid "do not show bundle details"
-msgstr "不显示归档包的细节"
+msgstr "不显示捆绑包的细节"
 
 #: builtin/bundle.c bundle.c
 msgid "need a repository to verify a bundle"
-msgstr "需要一个仓库来校验一个归档包"
+msgstr "需要一个仓库来校验一个捆绑包"
 
 #: builtin/bundle.c
 #, c-format
 msgid "%s is okay\n"
-msgstr "%s 可以\n"
+msgstr "%s 没问题\n"
 
 #: builtin/bundle.c
 msgid "Need a repository to unbundle."
-msgstr "需要一个仓库来解开归档包。"
+msgstr "需要一个仓库来解开捆绑包。"
 
 #: builtin/bundle.c
 msgid "Unbundling objects"
@@ -4107,7 +4143,7 @@
 #: builtin/cat-file.c
 #, c-format
 msgid "cannot read object %s '%s'"
-msgstr "不能读取对象 %s '%s'"
+msgstr "无法读取对象 %s '%s'"
 
 #: builtin/cat-file.c
 msgid "flush is only for --buffer mode"
@@ -4178,7 +4214,7 @@
 
 #: builtin/cat-file.c
 msgid "Emit [broken] object attributes"
-msgstr "输出 [坏的] 对象属性"
+msgstr "输出 [损坏的] 对象属性"
 
 #: builtin/cat-file.c
 msgid "show object type (one of 'blob', 'tree', 'commit', 'tag', ...)"
@@ -4206,11 +4242,11 @@
 
 #: builtin/cat-file.c
 msgid "stdin is NUL-terminated"
-msgstr "标准输入以 NUL 字符分隔"
+msgstr "标准输入以 NUL 字符终止"
 
 #: builtin/cat-file.c
 msgid "stdin and stdout is NUL-terminated"
-msgstr "标准输入和标准输出以 NUL 字符分隔"
+msgstr "标准输入和标准输出以 NUL 字符终止"
 
 #: builtin/cat-file.c
 msgid "read commands from stdin"
@@ -4256,7 +4292,7 @@
 
 #: builtin/cat-file.c
 msgid "use a <path> for (--textconv | --filters); Not with 'batch'"
-msgstr "(--textconv | --filters) 使用 <路径>;而不是 'batch'"
+msgstr "为 (--textconv | --filters) 使用 <路径>;不能与 'batch' 同用"
 
 #: builtin/cat-file.c
 msgid "objects filter only supported in batch mode"
@@ -4421,7 +4457,7 @@
 
 #: builtin/checkout-index.c
 msgid "stage should be between 1 and 3 or all"
-msgstr "索引值应该取值 1 到 3 或者 all"
+msgstr "暂存区编号应为 1 到 3 或 all"
 
 #: builtin/checkout-index.c
 msgid "check out all files in the index"
@@ -4437,7 +4473,7 @@
 
 #: builtin/checkout-index.c
 msgid "no warning for existing files and files not in index"
-msgstr "存在或不在索引中的文件都没有警告"
+msgstr "对已存在文件和不在索引中的文件不发出警告"
 
 #: builtin/checkout-index.c
 msgid "don't checkout new files"
@@ -4498,7 +4534,7 @@
 #: builtin/checkout.c
 #, c-format
 msgid "path '%s': cannot merge"
-msgstr "path '%s':无法合并"
+msgstr "路径 '%s':无法合并"
 
 #: builtin/checkout.c
 #, c-format
@@ -4534,7 +4570,7 @@
 #: builtin/checkout.c
 #, c-format
 msgid "Cannot update paths and switch to branch '%s' at the same time."
-msgstr "不能同时更新路径并切换到分支'%s'。"
+msgstr "不能同时更新路径并切换到分支 '%s'。"
 
 #: builtin/checkout.c
 #, c-format
@@ -4577,13 +4613,13 @@
 "cannot continue with staged changes in the following files:\n"
 "%s"
 msgstr ""
-"不能继续,下列文件有暂存的修改:\n"
+"无法继续,下列文件有暂存的修改:\n"
 "%s"
 
 #: builtin/checkout.c
 #, c-format
 msgid "Can not do reflog for '%s': %s\n"
-msgstr "不能对 '%s' 执行 reflog 操作:%s\n"
+msgstr "无法对 '%s' 执行 reflog 操作:%s\n"
 
 #: builtin/checkout.c
 msgid "HEAD is now at"
@@ -4591,7 +4627,7 @@
 
 #: builtin/checkout.c builtin/clone.c
 msgid "unable to update HEAD"
-msgstr "不能更新 HEAD"
+msgstr "无法更新 HEAD"
 
 #: builtin/checkout.c
 #, c-format
@@ -4622,7 +4658,7 @@
 #: builtin/checkout.c
 #, c-format
 msgid " ... and %d more.\n"
-msgstr " ... 及其它 %d 个。\n"
+msgstr " ... 及其他 %d 个。\n"
 
 #: builtin/checkout.c
 #, c-format
@@ -4709,8 +4745,8 @@
 "\n"
 "    git checkout --track origin/<名称>\n"
 "\n"
-"如果您总是喜欢使用模糊的简短分支名 <名称>,而不喜欢如 'origin' 的远程\n"
-"名称,可以在配置中设置 checkout.defaultRemote=origin。"
+"如果您希望在检出模糊的简短分支名 <名称> 时总是偏好某个远程(如 'origin'),\n"
+"可以在配置中设置 checkout.defaultRemote=origin。"
 
 #: builtin/checkout.c
 #, c-format
@@ -4759,7 +4795,7 @@
 #: builtin/checkout.c
 msgid ""
 "If you want to detach HEAD at the commit, try again with the --detach option."
-msgstr "如果你在本提交分离头指针,使用 --detach 选项重试。"
+msgstr "如果您想在该提交处分离 HEAD,请使用 --detach 选项重试。"
 
 #: builtin/checkout.c
 msgid ""
@@ -4832,7 +4868,7 @@
 #: builtin/checkout.c
 #, c-format
 msgid "Cannot switch branch to a non-commit '%s'"
-msgstr "不能切换分支到一个非提交 '%s'"
+msgstr "无法切换分支到一个非提交 '%s'"
 
 #: builtin/checkout.c
 msgid "missing branch or commit argument"
@@ -4845,7 +4881,7 @@
 
 #: builtin/checkout.c
 msgid "perform a 3-way merge with the new branch"
-msgstr "和新的分支执行三方合并"
+msgstr "和新的分支执行三路合并"
 
 #: builtin/checkout.c builtin/log.c builtin/range-diff.c parse-options.h
 msgid "style"
@@ -4869,7 +4905,7 @@
 
 #: builtin/checkout.c
 msgid "new unborn branch"
-msgstr "新的未诞生的分支"
+msgstr "新的尚未诞生分支"
 
 #: builtin/checkout.c builtin/merge.c
 msgid "update ignored files (default)"
@@ -4889,12 +4925,12 @@
 
 #: builtin/checkout.c
 msgid "do not limit pathspecs to sparse entries only"
-msgstr "对路径不做稀疏检出的限制"
+msgstr "对路径规格不只限于稀疏条目"
 
 #: builtin/checkout.c
 #, c-format
 msgid "options '-%c', '-%c', and '%s' cannot be used together"
-msgstr "选项 '-%c'、'-%c' 和 ‘%s' 不能同时使用"
+msgstr "选项 '-%c'、'-%c' 和 '%s' 不能同时使用"
 
 #: builtin/checkout.c
 msgid "--track needs a branch name"
@@ -4905,10 +4941,10 @@
 msgid "missing branch name; try -%c"
 msgstr "缺少分支名,尝试 -%c"
 
-#: builtin/checkout.c
+#: builtin/checkout.c sequencer.c
 #, c-format
-msgid "could not resolve %s"
-msgstr "无法解析 %s"
+msgid "could not resolve '%s'"
+msgstr "无法解析 '%s'"
 
 #: builtin/checkout.c
 msgid "invalid path specification"
@@ -4917,12 +4953,12 @@
 #: builtin/checkout.c
 #, c-format
 msgid "'%s' is not a commit and a branch '%s' cannot be created from it"
-msgstr "'%s' 不是一个提交,不能基于它创建分支 '%s'"
+msgstr "'%s' 不是一个提交,无法基于它创建分支 '%s'"
 
 #: builtin/checkout.c
 #, c-format
 msgid "git checkout: --detach does not take a path argument '%s'"
-msgstr "git checkout:--detach 不能接收路径参数 '%s'"
+msgstr "git checkout:--detach 不接收路径参数 '%s'"
 
 #: builtin/checkout.c
 msgid ""
@@ -4978,7 +5014,7 @@
 
 #: builtin/checkout.c
 msgid "which tree-ish to checkout from"
-msgstr "要检出哪一个树"
+msgstr "要从哪个树对象检出"
 
 #: builtin/checkout.c
 msgid "restore the index"
@@ -5001,7 +5037,7 @@
 "git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] "
 "[<pathspec>...]"
 msgstr ""
-"git clean [-d] [-f] [-i] [-n] [-q] [-e <模式>] [-x | -X] [--] <路径规格>..."
+"git clean [-d] [-f] [-i] [-n] [-q] [-e <模式>] [-x | -X] [--] [<路径规格>...]"
 
 #: builtin/clean.c builtin/sparse-checkout.c
 #, c-format
@@ -5016,22 +5052,22 @@
 #: builtin/clean.c
 #, c-format
 msgid "Skipping repository %s\n"
-msgstr "忽略仓库 %s\n"
+msgstr "跳过仓库 %s\n"
 
 #: builtin/clean.c
 #, c-format
 msgid "Would skip repository %s\n"
-msgstr "将忽略仓库 %s\n"
+msgstr "将跳过仓库 %s\n"
 
 #: builtin/clean.c midx.c
 #, c-format
 msgid "failed to remove %s"
-msgstr "无法删除 '%s'"
+msgstr "无法删除 %s"
 
 #: builtin/clean.c
 #, c-format
 msgid "could not lstat %s\n"
-msgstr "不能对 %s 调用 lstat\n"
+msgstr "无法对 %s 调用 lstat\n"
 
 #: builtin/clean.c
 msgid "Refusing to remove current working directory\n"
@@ -5083,7 +5119,7 @@
 #: builtin/clean.c
 #, c-format
 msgid "Input ignore patterns>> "
-msgstr "输入模版以排除条目>> "
+msgstr "输入忽略模式>> "
 
 #: builtin/clean.c
 #, c-format
@@ -5111,7 +5147,7 @@
 "?                   - help for prompt selection"
 msgstr ""
 "clean               - 开始清理\n"
-"filter by pattern   - 通过模版排除要删除的条目\n"
+"filter by pattern   - 通过模式排除要删除的条目\n"
 "select by numbers   - 通过数字选择要删除的条目\n"
 "ask each            - 针对删除逐一询问(就像 \"rm -i\")\n"
 "quit                - 停止删除并退出\n"
@@ -5169,7 +5205,7 @@
 #: builtin/clone.c
 #, c-format
 msgid "info: Could not add alternate for '%s': %s\n"
-msgstr "info: 不能为 '%s' 添加一个备用:%s\n"
+msgstr "info:无法为 '%s' 添加一个备用:%s\n"
 
 #: builtin/clone.c builtin/diff.c builtin/rm.c grep.c setup.c
 #, c-format
@@ -5248,7 +5284,7 @@
 #: builtin/clone.c
 #, c-format
 msgid "unable to update %s"
-msgstr "不能更新 %s"
+msgstr "无法更新 %s"
 
 #: builtin/clone.c
 msgid "failed to initialize sparse-checkout"
@@ -5260,7 +5296,7 @@
 
 #: builtin/clone.c
 msgid "unable to checkout working tree"
-msgstr "不能检出工作区"
+msgstr "无法检出工作区"
 
 #: builtin/clone.c
 msgid "unable to write parameters to config file"
@@ -5320,7 +5356,7 @@
 
 #: builtin/clone.c builtin/init-db.c
 msgid "directory from which templates will be used"
-msgstr "模板目录将被使用"
+msgstr "将从该目录使用模板"
 
 #: builtin/clone.c builtin/submodule--helper.c
 msgid "reference repository"
@@ -5370,7 +5406,7 @@
 
 #: builtin/clone.c builtin/fetch.c builtin/pull.c
 msgid "deepen history of shallow clone, excluding ref"
-msgstr "深化浅克隆的历史,除了给定的引用"
+msgstr "深化浅克隆的历史,不包含指定引用"
 
 #: builtin/clone.c builtin/submodule--helper.c
 msgid "clone only one branch, HEAD or --branch"
@@ -5382,15 +5418,15 @@
 
 #: builtin/clone.c
 msgid "any cloned submodules will be shallow"
-msgstr "子模组将以浅下载模式克隆"
+msgstr "所有被克隆的子模组都将是浅克隆"
 
 #: builtin/clone.c builtin/init-db.c
 msgid "gitdir"
-msgstr "git目录"
+msgstr "git 目录"
 
 #: builtin/clone.c builtin/init-db.c
 msgid "separate git dir from working tree"
-msgstr "git目录和工作区分离"
+msgstr "将 git 目录与工作区分离"
 
 #: builtin/clone.c builtin/init-db.c builtin/submodule--helper.c
 msgid "specify the reference format to use"
@@ -5398,7 +5434,7 @@
 
 #: builtin/clone.c
 msgid "key=value"
-msgstr "key=value"
+msgstr "键=值"
 
 #: builtin/clone.c
 msgid "set config inside the new repository"
@@ -5407,7 +5443,7 @@
 #: builtin/clone.c builtin/fetch.c builtin/ls-remote.c builtin/pull.c
 #: builtin/push.c builtin/send-pack.c
 msgid "server-specific"
-msgstr "server-specific"
+msgstr "服务端特定"
 
 #: builtin/clone.c builtin/fetch.c builtin/ls-remote.c builtin/pull.c
 #: builtin/push.c builtin/send-pack.c
@@ -5424,15 +5460,15 @@
 
 #: builtin/clone.c
 msgid "initialize sparse-checkout file to include only files at root"
-msgstr "初始化稀疏检出文件,只包含根目录文件"
+msgstr "初始化稀疏检出文件,只包含根目录中的文件"
 
 #: builtin/clone.c
 msgid "uri"
-msgstr "uri"
+msgstr "URI"
 
 #: builtin/clone.c
 msgid "a URI for downloading bundles before fetching from origin remote"
-msgstr "用于在从 origin 远程获取之前下载归档包的 URI"
+msgstr "用于在从 origin 远程获取之前下载捆绑包的 URI"
 
 #: builtin/clone.c
 msgid "git clone [<options>] [--] <repo> [<dir>]"
@@ -5480,12 +5516,12 @@
 #: builtin/clone.c builtin/difftool.c builtin/log.c builtin/worktree.c
 #, c-format
 msgid "could not create leading directories of '%s'"
-msgstr "不能为 '%s' 创建先导目录"
+msgstr "无法为 '%s' 创建上级目录"
 
 #: builtin/clone.c
 #, c-format
 msgid "could not create work tree dir '%s'"
-msgstr "不能创建工作区目录 '%s'"
+msgstr "无法创建工作区目录 '%s'"
 
 #: builtin/clone.c
 #, c-format
@@ -5538,20 +5574,20 @@
 
 #: builtin/clone.c
 msgid "cannot clone from filtered bundle"
-msgstr "无法从经过过滤的归档包克隆"
+msgstr "无法从经过过滤的捆绑包克隆"
 
 #: builtin/clone.c
 msgid "failed to initialize the repo, skipping bundle URI"
-msgstr "无法初始化仓库,跳过归档包 URI"
+msgstr "无法初始化仓库,跳过捆绑包 URI"
 
 #: builtin/clone.c
 #, c-format
 msgid "failed to fetch objects from bundle URI '%s'"
-msgstr "无法从归档包 URI '%s' 获取对象"
+msgstr "无法从捆绑包 URI '%s' 获取对象"
 
 #: builtin/clone.c
 msgid "failed to fetch advertised bundles"
-msgstr "无法获取公布的归档包"
+msgstr "无法获取公布的捆绑包"
 
 #: builtin/clone.c
 msgid "remote transport reported error"
@@ -5560,7 +5596,7 @@
 #: builtin/clone.c
 #, c-format
 msgid "Remote branch %s not found in upstream %s"
-msgstr "远程分支 %s 在上游 %s 未发现"
+msgstr "远程分支 %s 在上游 %s 未找到"
 
 #: builtin/clone.c
 #, c-format
@@ -5625,7 +5661,7 @@
 msgstr ""
 "git commit-graph write [--object-dir <目录>] [--append]\n"
 "                       [--split[=<策略>]] [--reachable | --stdin-packs | --"
-"stdin-commits] \n"
+"stdin-commits]\n"
 "                       [--changed-paths] [--[no-]max-new-filters <n>] [--"
 "[no-]progress]\n"
 "                       <切分选项>"
@@ -5641,7 +5677,7 @@
 
 #: builtin/commit-graph.c
 msgid "if the commit-graph is split, only verify the tip file"
-msgstr "如果提交图被拆分,只验证头一个文件"
+msgstr "如果提交图被拆分,只验证顶端文件"
 
 #: builtin/commit-graph.c
 #, c-format
@@ -5687,7 +5723,7 @@
 
 #: builtin/commit-graph.c
 msgid "include all commits already in the commit-graph file"
-msgstr "包含 commit-graph 文件中已有所有提交"
+msgstr "包含 commit-graph 文件中已有的所有提交"
 
 #: builtin/commit-graph.c
 msgid "enable computation for changed paths"
@@ -5730,7 +5766,7 @@
 "git commit-tree [(-p <parent>)...] [-S[<keyid>]] [(-m <message>)...]\n"
 "                [(-F <file>)...] <tree>"
 msgstr ""
-"git commit-tree [(-p <父提交>)...] [-S[<私钥 ID>]] [(-m <消息>)...]\n"
+"git commit-tree [(-p <父提交>)...] [-S[<密钥 ID>]] [(-m <消息>)...]\n"
 "                [(-F <文件>)...] <树>"
 
 #: builtin/commit-tree.c
@@ -5777,7 +5813,7 @@
 #: builtin/commit-tree.c builtin/commit.c builtin/merge.c builtin/pull.c
 #: builtin/revert.c
 msgid "GPG sign commit"
-msgstr "GPG 提交签名"
+msgstr "GPG 签名提交"
 
 #: builtin/commit-tree.c
 msgid "must give exactly one tree"
@@ -5806,7 +5842,7 @@
 "           [--allow-empty-message] [--no-verify] [-e] [--author=<作者>]\n"
 "           [--date=<日期>] [--cleanup=<模式>] [--[no-]status]\n"
 "           [-i | -o] [--pathspec-from-file=<文件> [--pathspec-file-nul]]\n"
-"           [(--trailer <键>[(=|:)<值>])...] [-S[<私钥 ID>]]\n"
+"           [(--trailer <键>[(=|:)<值>])...] [-S[<密钥 ID>]]\n"
 "           [--] [<路径规格>...]"
 
 #: builtin/commit.c
@@ -5819,8 +5855,8 @@
 "it empty. You can repeat your command with --allow-empty, or you can\n"
 "remove the commit entirely with \"git reset HEAD^\".\n"
 msgstr ""
-"您要修补最近的提交,但这么做会让它成为空提交。您可以重复您的命令并带上\n"
-"--allow-empty 选项,或者您可用命令 \"git reset HEAD^\" 整个删除该提交。\n"
+"您要修订最近的提交,但这么做会让它成为空提交。您可以重复您的命令并带上\n"
+"--allow-empty 选项,或者您可用命令 \"git reset HEAD^\" 彻底删除该提交。\n"
 
 #: builtin/commit.c
 msgid ""
@@ -5875,11 +5911,11 @@
 
 #: builtin/commit.c
 msgid "No paths with --include/--only does not make sense."
-msgstr "参数 --include/--only 不跟路径没有意义。"
+msgstr "在未指定路径时使用 --include/--only 没有意义。"
 
 #: builtin/commit.c
 msgid "unable to create temporary index"
-msgstr "不能创建临时索引"
+msgstr "无法创建临时索引"
 
 #: builtin/commit.c
 msgid "interactive add failed"
@@ -5891,7 +5927,7 @@
 
 #: builtin/commit.c
 msgid "Failed to update main cache tree"
-msgstr "不能更新树的主缓存"
+msgstr "无法更新主缓存树"
 
 #: builtin/commit.c
 msgid "cannot do a partial commit during a merge."
@@ -5941,7 +5977,7 @@
 #: builtin/commit.c
 #, c-format
 msgid "could not lookup commit '%s'"
-msgstr "不能查询提交 '%s'"
+msgstr "无法查询提交 '%s'"
 
 #: builtin/commit.c builtin/shortlog.c
 #, c-format
@@ -5950,12 +5986,12 @@
 
 #: builtin/commit.c
 msgid "could not read log from standard input"
-msgstr "不能从标准输入中读取日志信息"
+msgstr "无法从标准输入中读取日志信息"
 
 #: builtin/commit.c
 #, c-format
 msgid "could not read log file '%s'"
-msgstr "不能读取日志文件 '%s'"
+msgstr "无法读取日志文件 '%s'"
 
 #: builtin/commit.c
 #, c-format
@@ -5964,11 +6000,11 @@
 
 #: builtin/commit.c
 msgid "could not read SQUASH_MSG"
-msgstr "不能读取 SQUASH_MSG"
+msgstr "无法读取 SQUASH_MSG"
 
 #: builtin/commit.c
 msgid "could not read MERGE_MSG"
-msgstr "不能读取 MERGE_MSG"
+msgstr "无法读取 MERGE_MSG"
 
 #: builtin/commit.c bundle.c rerere.c sequencer.c
 #, c-format
@@ -5977,7 +6013,7 @@
 
 #: builtin/commit.c
 msgid "could not write commit template"
-msgstr "不能写提交模版"
+msgstr "无法写提交模板"
 
 #: builtin/commit.c
 #, c-format
@@ -6092,11 +6128,11 @@
 
 #: builtin/commit.c
 msgid "You are in the middle of a merge -- cannot reword."
-msgstr "您正处于一个合并过程中 -- 无法改写说明。"
+msgstr "您正处于一个合并过程中 -- 不能改写说明。"
 
 #: builtin/commit.c
 msgid "You are in the middle of a cherry-pick -- cannot reword."
-msgstr "您正处于一个拣选过程中 -- 无法改写说明。"
+msgstr "您正处于一个拣选过程中 -- 不能改写说明。"
 
 #: builtin/commit.c
 #, c-format
@@ -6110,19 +6146,19 @@
 
 #: builtin/commit.c
 msgid "You have nothing to amend."
-msgstr "您没有可修补的提交。"
+msgstr "您没有可修订的提交。"
 
 #: builtin/commit.c
 msgid "You are in the middle of a merge -- cannot amend."
-msgstr "您正处于一个合并过程中 -- 无法修补提交。"
+msgstr "您正处于一个合并过程中 -- 不能修订提交。"
 
 #: builtin/commit.c
 msgid "You are in the middle of a cherry-pick -- cannot amend."
-msgstr "您正处于一个拣选过程中 -- 无法修补提交。"
+msgstr "您正处于一个拣选过程中 -- 不能修订提交。"
 
 #: builtin/commit.c
 msgid "You are in the middle of a rebase -- cannot amend."
-msgstr "您正处于一个变基过程中 -- 无法修补提交。"
+msgstr "您正处于一个变基过程中 -- 不能修订提交。"
 
 #: builtin/commit.c
 msgid "--reset-author can be used only with -C, -c or --amend."
@@ -6136,7 +6172,7 @@
 #: builtin/commit.c
 #, c-format
 msgid "paths '%s ...' with -a does not make sense"
-msgstr "路径  '%s ...' 和 -a 选项同时使用没有意义"
+msgstr "路径 '%s ...' 和 -a 选项同时使用没有意义"
 
 #: builtin/commit.c
 msgid "show status concisely"
@@ -6148,7 +6184,7 @@
 
 #: builtin/commit.c
 msgid "show stash information"
-msgstr "显示贮藏区信息"
+msgstr "显示储藏信息"
 
 #: builtin/commit.c
 msgid "compute full ahead/behind values"
@@ -6202,7 +6238,7 @@
 
 #: builtin/commit.c
 msgid "detect renames, optionally set similarity index"
-msgstr "检测重命名,可以设置索引相似度"
+msgstr "检测重命名,可设置相似度索引"
 
 #: builtin/commit.c
 msgid "Unsupported combination of ignored and untracked-files arguments"
@@ -6262,7 +6298,7 @@
 #: builtin/commit.c
 msgid ""
 "use autosquash formatted message to fixup or amend/reword specified commit"
-msgstr "使用自动挤压格式的提交说明对指定的提交进行修正、修补或改写说明"
+msgstr "使用自动挤压格式的提交说明对指定的提交进行修正、修订或改写说明"
 
 #: builtin/commit.c
 msgid "use autosquash formatted message to squash specified commit"
@@ -6339,7 +6375,7 @@
 
 #: builtin/commit.c
 msgid "ok to record an empty change"
-msgstr "允许一个空提交"
+msgstr "允许记录空变更"
 
 #: builtin/commit.c
 msgid "ok to record a change with an empty message"
@@ -6347,7 +6383,7 @@
 
 #: builtin/commit.c sequencer.c
 msgid "could not parse HEAD commit"
-msgstr "不能解析 HEAD 提交"
+msgstr "无法解析 HEAD 提交"
 
 #: builtin/commit.c
 #, c-format
@@ -6356,22 +6392,22 @@
 
 #: builtin/commit.c
 msgid "could not read MERGE_MODE"
-msgstr "不能读取 MERGE_MODE"
+msgstr "无法读取 MERGE_MODE"
 
 #: builtin/commit.c
 #, c-format
 msgid "could not read commit message: %s"
-msgstr "不能读取提交说明:%s"
+msgstr "无法读取提交说明:%s"
 
 #: builtin/commit.c
 #, c-format
 msgid "Aborting commit due to empty commit message.\n"
-msgstr "终止提交因为提交说明为空。\n"
+msgstr "由于提交说明为空,终止提交。\n"
 
 #: builtin/commit.c
 #, c-format
 msgid "Aborting commit; you did not edit the message.\n"
-msgstr "终止提交;您未更改来自模版的提交说明。\n"
+msgstr "终止提交;您未更改来自模板的提交说明。\n"
 
 #: builtin/commit.c
 #, c-format
@@ -6522,7 +6558,7 @@
 
 #: builtin/config.c
 msgid "terminate values with NUL byte"
-msgstr "终止值是 NUL 字节"
+msgstr "以 NUL 字节终止值"
 
 #: builtin/config.c
 msgid "show variable names only"
@@ -6547,7 +6583,7 @@
 
 #: builtin/config.c
 msgid "only one type at a time"
-msgstr "一次只能一个类型"
+msgstr "一次只能指定一个类型"
 
 #: builtin/config.c
 #, c-format
@@ -6557,7 +6593,7 @@
 #: builtin/config.c
 #, c-format
 msgid "wrong number of arguments, should be from %d to %d"
-msgstr "错误的参数个数,应该为从 %d 个到 %d 个"
+msgstr "错误的参数个数,应为 %d 到 %d 个"
 
 #: builtin/config.c
 #, c-format
@@ -6585,7 +6621,7 @@
 
 #: builtin/config.c
 msgid "not in a git directory"
-msgstr "不在 git 仓库中"
+msgstr "不在 git 目录中"
 
 #: builtin/config.c
 msgid "writing to stdin is not supported"
@@ -6604,9 +6640,9 @@
 "#\tname = %s\n"
 "#\temail = %s\n"
 msgstr ""
-"# This is Git's per-user configuration file.\n"
+"# 这是 Git 的用户级配置文件。\n"
 "[user]\n"
-"# Please adapt and uncomment the following lines:\n"
+"# 请根据需要修改并取消注释以下行:\n"
 "#\tname = %s\n"
 "#\temail = %s\n"
 
@@ -6641,7 +6677,7 @@
 
 #: builtin/config.c
 msgid "Other"
-msgstr "其它"
+msgstr "其他"
 
 #: builtin/config.c
 msgid "respect include directives on lookup"
@@ -6694,7 +6730,7 @@
 
 #: builtin/config.c
 msgid "--fixed-value only applies with 'value-pattern'"
-msgstr "--fixed-value 仅适用于有 '值模式'"
+msgstr "--fixed-value 仅适用于 '值模式'"
 
 #: builtin/config.c
 msgid "--default= cannot be used with --all or --url="
@@ -6732,10 +6768,18 @@
 #, c-format
 msgid ""
 "cannot overwrite multiple values with a single value\n"
-"       Use a regexp, --add or --replace-all to change %s."
+"       Use --value=<pattern>, --append or --all to change %s."
 msgstr ""
-"无法用一个值覆盖多个值\n"
-"       使用一个正则表达式、--add 或 --replace-all 来修改 %s。"
+"无法用单个值覆盖多个值\n"
+"       使用 --value=<模式>, --append 或 --all 来修改 %s。"
+
+#: builtin/config.c
+msgid "unset all multi-valued config options"
+msgstr "取消设置所有多值配置选项"
+
+#: builtin/config.c
+msgid "unset multi-valued config options with matching values"
+msgstr "取消设置匹配值的多值配置选项"
 
 #: builtin/config.c
 #, c-format
@@ -6753,7 +6797,7 @@
 #: builtin/config.c
 #, c-format
 msgid "cannot create configuration file %s"
-msgstr "不能创建配置文件 %s"
+msgstr "无法创建配置文件 %s"
 
 #: builtin/config.c
 msgid "Action"
@@ -6845,9 +6889,18 @@
 msgid "--comment is only applicable to add/set/replace operations"
 msgstr "--comment 仅适用于 add/set/replace 操作"
 
+#: builtin/config.c
+#, c-format
+msgid ""
+"cannot overwrite multiple values with a single value\n"
+"       Use a regexp, --add or --replace-all to change %s."
+msgstr ""
+"无法用一个值覆盖多个值\n"
+"       使用一个正则表达式、--add 或 --replace-all 来修改 %s。"
+
 #: builtin/count-objects.c
 msgid "print sizes in human readable format"
-msgstr "以用户可读的格式显示大小"
+msgstr "以人类可读的格式显示大小"
 
 #: builtin/credential-cache--daemon.c
 #, c-format
@@ -6910,7 +6963,7 @@
 #: builtin/describe.c
 #, c-format
 msgid "annotated tag %s not available"
-msgstr "附注标签 %s 无效"
+msgstr "附注标签 %s 不可用"
 
 #: builtin/describe.c
 #, c-format
@@ -6930,7 +6983,7 @@
 #: builtin/describe.c
 #, c-format
 msgid "finished search at %s\n"
-msgstr "完成搜索 %s\n"
+msgstr "在 %s 处完成搜索\n"
 
 #: builtin/describe.c
 #, c-format
@@ -6999,7 +7052,7 @@
 
 #: builtin/describe.c
 msgid "use any tag, even unannotated"
-msgstr "使用任意标签,即使未附带注释"
+msgstr "使用任意标签,即使未附注"
 
 #: builtin/describe.c
 msgid "always use long format"
@@ -7073,12 +7126,12 @@
 #: builtin/diff-pairs.c
 #, c-format
 msgid "unable to parse mode: %s"
-msgstr "不能解析模式:%s"
+msgstr "无法解析模式:%s"
 
 #: builtin/diff-pairs.c
 #, c-format
 msgid "unable to parse object id: %s"
-msgstr "不能解析对象 ID:%s"
+msgstr "无法解析对象 ID:%s"
 
 #: builtin/diff-pairs.c
 msgid "git diff-pairs -z [<diff-options>]"
@@ -7177,7 +7230,7 @@
 #: builtin/diff.c
 #, c-format
 msgid "%s...%s: multiple merge bases, using %s"
-msgstr "%s...%s:多条合并基线,使用 %s"
+msgstr "%s...%s:多个合并基线,使用 %s"
 
 #: builtin/difftool.c
 msgid "git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"
@@ -7213,12 +7266,12 @@
 
 #: builtin/difftool.c
 msgid "working tree file has been left."
-msgstr "工作区文件被留了下来。"
+msgstr "工作区文件已保留。"
 
 #: builtin/difftool.c sequencer.c
 #, c-format
 msgid "could not copy '%s' to '%s'"
-msgstr "不能拷贝 '%s' 至 '%s'"
+msgstr "无法拷贝 '%s' 至 '%s'"
 
 #: builtin/difftool.c
 #, c-format
@@ -7346,13 +7399,7 @@
 msgid ""
 "encountered commit-specific encoding %.*s in commit %s; use --reencode=[yes|"
 "no] to handle it"
-msgstr ""
-"在提交 %3$s 中遇到提交特定编码 %2$.*1$s;使用 --reencode=[yes|no] 来处理"
-
-#: builtin/fast-export.c
-#, c-format
-msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it"
-msgstr "遇到已签名提交 %s;使用 --signed-commits=<模式> 来处理"
+msgstr "遇到提交特定编码 %.*s 的提交为 %s;使用 --reencode=[yes|no] 来处理"
 
 #: builtin/fast-export.c
 #, c-format
@@ -7366,6 +7413,19 @@
 
 #: builtin/fast-export.c
 #, c-format
+msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it"
+msgstr "遇到已签名提交 %s;使用 --signed-commits=<模式> 来处理"
+
+#: builtin/fast-export.c
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-export with --signed-"
+"commits=<mode>"
+msgstr ""
+"在 git fast-export 的 --signed-commits=<模式> 中,'strip-if-invalid' 不是有效"
+"模式"
+
+#: builtin/fast-export.c
+#, c-format
 msgid ""
 "omitting tag %s,\n"
 "since tags of trees (or tags of tags of trees, etc.) are not supported."
@@ -7380,11 +7440,6 @@
 
 #: builtin/fast-export.c
 #, c-format
-msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it"
-msgstr "遇到已签名标签 %s;使用 --signed-tags=<模式> 来处理"
-
-#: builtin/fast-export.c
-#, c-format
 msgid "exporting signed tag %s"
 msgstr "正在导出已签名标签 %s"
 
@@ -7395,6 +7450,19 @@
 
 #: builtin/fast-export.c
 #, c-format
+msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it"
+msgstr "遇到已签名标签 %s;使用 --signed-tags=<模式> 来处理"
+
+#: builtin/fast-export.c
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-export with --signed-"
+"tags=<mode>"
+msgstr ""
+"在 git fast-export 的 --signed-tags=<模式> 中,'strip-if-invalid' 不是有效模"
+"式"
+
+#: builtin/fast-export.c
+#, c-format
 msgid ""
 "tag %s tags unexported object; use --tag-of-filtered-object=<mode> to handle "
 "it"
@@ -7488,7 +7556,7 @@
 
 #: builtin/fast-export.c
 msgid "fake a tagger when tags lack one"
-msgstr "当标签缺少标记人字段时,假装提供一个"
+msgstr "当标签缺少打标签者时,伪造一个"
 
 #: builtin/fast-export.c
 msgid "output full tree for each commit"
@@ -7516,7 +7584,7 @@
 
 #: builtin/fast-export.c
 msgid "from:to"
-msgstr "from:to"
+msgstr "从:到"
 
 #: builtin/fast-export.c
 msgid "convert <from> to <to> in anonymized output"
@@ -7524,7 +7592,7 @@
 
 #: builtin/fast-export.c
 msgid "reference parents which are not in fast-export stream by object id"
-msgstr "引用父对象 ID 不在 fast-export 流中"
+msgstr "通过对象 ID 引用不在 fast-export 流中的父对象"
 
 #: builtin/fast-export.c
 msgid "show original object ids of blobs/commits"
@@ -7532,7 +7600,7 @@
 
 #: builtin/fast-export.c
 msgid "label tags with mark ids"
-msgstr "对带有标记 ID 的标签做标记"
+msgstr "用标记 ID 标注标签"
 
 #: builtin/fast-import.c
 #, c-format
@@ -7649,7 +7717,7 @@
 #: sequencer.c
 #, c-format
 msgid "unable to create leading directories of %s"
-msgstr "不能为 %s 创建先导目录"
+msgstr "无法为 %s 创建上级目录"
 
 #: builtin/fast-import.c
 #, c-format
@@ -7798,7 +7866,7 @@
 #: builtin/fast-import.c
 #, c-format
 msgid "%s not found: %s"
-msgstr "未找到%s:%s"
+msgstr "未找到 %s:%s"
 
 #: builtin/fast-import.c
 msgid "tree"
@@ -7816,7 +7884,7 @@
 
 #: builtin/fast-import.c
 msgid "can't add a note on empty branch."
-msgstr "不能在空分支上添加注释。"
+msgstr "无法在空分支上添加注解。"
 
 #: builtin/fast-import.c
 #, c-format
@@ -7883,9 +7951,40 @@
 msgid "parse_one_signature() returned unknown hash algo"
 msgstr "parse_one_signature() 返回了未知的哈希算法"
 
+#: builtin/fast-import.c builtin/fsck.c
+msgid "unknown"
+msgstr "未知"
+
+#: builtin/fast-import.c
+#, c-format
+msgid ""
+"stripping invalid signature for commit '%.100s...'\n"
+"  allegedly by %s"
+msgstr ""
+"正在剥离提交 '%.100s...' 的无效签名\n"
+"  据称由 %s 提供"
+
+#: builtin/fast-import.c
+#, c-format
+msgid ""
+"stripping invalid signature for commit '%.*s'\n"
+"  allegedly by %s"
+msgstr ""
+"正在剥离提交 '%.*s' 的无效签名\n"
+"  据称由 %s 提供"
+
+#: builtin/fast-import.c
+#, c-format
+msgid ""
+"stripping invalid signature for commit\n"
+"  allegedly by %s"
+msgstr ""
+"正在剥离提交的无效签名\n"
+"  据称由 %s 提供"
+
 #: builtin/fast-import.c
 msgid "expected committer but didn't get one"
-msgstr "预期提交者但未获取"
+msgstr "预期提交者但未获得"
 
 #: builtin/fast-import.c
 msgid "encountered signed commit; use --signed-commits=<mode> to handle it"
@@ -7900,10 +7999,6 @@
 msgstr "逐字导入提交签名"
 
 #: builtin/fast-import.c
-msgid "encountered signed tag; use --signed-tags=<mode> to handle it"
-msgstr "遇到已签名标签;使用 --signed-tags=<模式> 来处理它"
-
-#: builtin/fast-import.c
 #, c-format
 msgid "importing a tag signature verbatim for tag '%s'"
 msgstr "逐字导入标签 '%s' 的签名"
@@ -7914,6 +8009,18 @@
 msgstr "正在剥离标签 '%s' 的签名"
 
 #: builtin/fast-import.c
+msgid "encountered signed tag; use --signed-tags=<mode> to handle it"
+msgstr "遇到已签名标签;使用 --signed-tags=<模式> 来处理它"
+
+#: builtin/fast-import.c
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-import with --signed-"
+"tags=<mode>"
+msgstr ""
+"在 git fast-import 的 --signed-tags=<模式> 中,'strip-if-invalid' 不是有效模"
+"式"
+
+#: builtin/fast-import.c
 #, c-format
 msgid "expected 'from' command, got '%s'"
 msgstr "预期 'from' 命令,实际:'%s'"
@@ -8100,7 +8207,7 @@
 #: builtin/fetch-pack.c
 #, c-format
 msgid "Lockfile created but not reported: %s"
-msgstr "Lockfile 已创建但未报告:%s"
+msgstr "锁文件已创建但未报告:%s"
 
 #: builtin/fetch.c
 msgid "git fetch [<options>] [<repository> [<refspec>...]]"
@@ -8111,8 +8218,8 @@
 msgstr "git fetch [<选项>] <组>"
 
 #: builtin/fetch.c
-msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]"
-msgstr "git fetch --multiple [<选项>] [(<仓库> | <组>)...]"
+msgid "git fetch --multiple [<options>] [(<repository>|<group>)...]"
+msgstr "git fetch --multiple [<选项>] [(<仓库>|<组>)...]"
 
 #: builtin/fetch.c
 msgid "git fetch --all [<options>]"
@@ -8134,7 +8241,7 @@
 #: builtin/fetch.c
 #, c-format
 msgid "object %s not found"
-msgstr "对象 %s 未发现"
+msgstr "对象 %s 未找到"
 
 #: builtin/fetch.c
 msgid "[up to date]"
@@ -8154,11 +8261,11 @@
 
 #: builtin/fetch.c
 msgid "unable to update local ref"
-msgstr "不能更新本地引用"
+msgstr "无法更新本地引用"
 
 #: builtin/fetch.c
 msgid "would clobber existing tag"
-msgstr "会破坏现有的标签"
+msgstr "会覆盖现有的标签"
 
 #: builtin/fetch.c
 msgid "[new tag]"
@@ -8183,7 +8290,7 @@
 #: builtin/fetch.c builtin/grep.c sequencer.c
 #, c-format
 msgid "cannot open '%s'"
-msgstr "不能打开 '%s'"
+msgstr "无法打开 '%s'"
 
 #: builtin/fetch.c
 msgid ""
@@ -8237,7 +8344,7 @@
 #: builtin/fetch.c
 #, c-format
 msgid "option \"%s\" is ignored for %s"
-msgstr "选项 \"%s\" 为 %s 所忽略"
+msgstr "选项 \"%s\" 对 %s 将被忽略"
 
 #: builtin/fetch.c odb.c
 #, c-format
@@ -8260,7 +8367,7 @@
 msgstr ""
 "运行 'git remote set-head %s %s' 同步变更,或通过配置选项\n"
 "'remote.%s.followRemoteHEAD' 设置为其他值以屏蔽此提示。具体可通过\n"
-"运行命令 'git config remote.%s.followRemoteHEAD warn-if-not-branch-%s'\n"
+"运行命令 'git config set remote.%s.followRemoteHEAD warn-if-not-branch-%s'\n"
 "以禁用该警告,直到远程将 HEAD 更改为其他内容。"
 
 #: builtin/fetch.c
@@ -8295,7 +8402,7 @@
 "some local refs could not be updated; try running\n"
 " 'git remote prune %s' to remove any old, conflicting branches"
 msgstr ""
-"一些本地引用不能被更新;尝试运行\n"
+"一些本地引用无法被更新;尝试运行\n"
 " 'git remote prune %s' 来删除旧的、有冲突的分支"
 
 #: builtin/fetch.c
@@ -8342,7 +8449,7 @@
 #: builtin/fetch.c
 #, c-format
 msgid "could not fetch %s"
-msgstr "不能获取 %s"
+msgstr "无法获取 %s"
 
 #: builtin/fetch.c
 #, c-format
@@ -8405,11 +8512,11 @@
 
 #: builtin/fetch.c builtin/pull.c
 msgid "prune remote-tracking branches no longer on remote"
-msgstr "清除远程已经不存在的分支的跟踪分支"
+msgstr "清除远程已不存在的远程跟踪分支"
 
 #: builtin/fetch.c
 msgid "prune local tags no longer on remote and clobber changed tags"
-msgstr "清除远程不存在的本地标签,并且替换变更标签"
+msgstr "清除远程已不存在的本地标签,并覆盖已变更的标签"
 
 #  译者:可选值,不能翻译
 #: builtin/fetch.c builtin/pull.c
@@ -8456,7 +8563,7 @@
 msgid ""
 "default for recursive fetching of submodules (lower priority than config "
 "files)"
-msgstr "递归获取子模组的缺省值(比配置文件优先级低)"
+msgstr "递归获取子模组的默认值(比配置文件优先级低)"
 
 #: builtin/fetch.c builtin/pull.c
 msgid "accept refs that update .git/shallow"
@@ -8513,7 +8620,7 @@
 #: builtin/fetch.c
 #, c-format
 msgid "failed to fetch bundles from '%s'"
-msgstr "无法从 '%s' 获取归档包"
+msgstr "无法从 '%s' 获取捆绑包"
 
 #: builtin/fetch.c
 msgid "fetch --all does not take a repository argument"
@@ -8585,19 +8692,19 @@
 
 #: builtin/for-each-ref.c
 msgid "quote placeholders suitably for shells"
-msgstr "引用占位符适用于 shells"
+msgstr "为 shell 适当引用占位符"
 
 #: builtin/for-each-ref.c
 msgid "quote placeholders suitably for perl"
-msgstr "引用占位符适用于 perl"
+msgstr "为 perl 适当引用占位符"
 
 #: builtin/for-each-ref.c
 msgid "quote placeholders suitably for python"
-msgstr "引用占位符适用于 python"
+msgstr "为 python 适当引用占位符"
 
 #: builtin/for-each-ref.c
 msgid "quote placeholders suitably for Tcl"
-msgstr "引用占位符适用于 Tcl"
+msgstr "为 Tcl 适当引用占位符"
 
 #: builtin/for-each-ref.c
 msgid "show only <n> matched refs"
@@ -8684,10 +8791,6 @@
 msgid "got bad config --config=%s"
 msgstr "发现错误的配置行 --config=%s"
 
-#: builtin/fsck.c
-msgid "unknown"
-msgstr "未知"
-
 #. TRANSLATORS: e.g. error in tree 01bfda: <more explanation>
 #: builtin/fsck.c
 #, c-format
@@ -8739,18 +8842,18 @@
 
 #: builtin/fsck.c
 msgid "could not create lost-found"
-msgstr "不能创建 lost-found"
+msgstr "无法创建 lost-found"
 
 #: builtin/fsck.c builtin/gc.c builtin/rebase.c rebase-interactive.c rerere.c
 #: sequencer.c
 #, c-format
 msgid "could not write '%s'"
-msgstr "不能写入 '%s'"
+msgstr "无法写入 '%s'"
 
 #: builtin/fsck.c
 #, c-format
 msgid "could not finish '%s'"
-msgstr "不能完成 '%s'"
+msgstr "无法完成 '%s'"
 
 #: builtin/fsck.c
 #, c-format
@@ -8807,6 +8910,11 @@
 msgstr "%s:不是一个提交"
 
 #: builtin/fsck.c
+#, c-format
+msgid "invalid parameter: expected sha1, got '%s'"
+msgstr "无效的参数:期望 sha1,得到 '%s'"
+
+#: builtin/fsck.c
 msgid "notice: No default references"
 msgstr "注意:无默认引用"
 
@@ -8823,7 +8931,7 @@
 #: builtin/fsck.c
 #, c-format
 msgid "%s: object could not be parsed: %s"
-msgstr "%s:不能解析对象:%s"
+msgstr "%s:无法解析对象:%s"
 
 #: builtin/fsck.c
 #, c-format
@@ -8840,31 +8948,6 @@
 
 #: builtin/fsck.c
 #, c-format
-msgid "Checking %s link"
-msgstr "正在检查 %s 链接"
-
-#: builtin/fsck.c builtin/index-pack.c
-#, c-format
-msgid "invalid %s"
-msgstr "无效的 %s"
-
-#: builtin/fsck.c
-#, c-format
-msgid "%s points to something strange (%s)"
-msgstr "%s 指向奇怪的东西(%s)"
-
-#: builtin/fsck.c
-#, c-format
-msgid "%s: detached HEAD points at nothing"
-msgstr "%s:分离头指针的指向不存在"
-
-#: builtin/fsck.c
-#, c-format
-msgid "notice: %s points to an unborn branch (%s)"
-msgstr "注意:%s 指向一个尚未诞生的分支(%s)"
-
-#: builtin/fsck.c
-#, c-format
 msgid "Checking cache tree of %s"
 msgstr "正在检查缓存树 %s"
 
@@ -8964,16 +9047,6 @@
 msgid "Checking objects"
 msgstr "正在检查对象"
 
-#: builtin/fsck.c
-#, c-format
-msgid "%s: object missing"
-msgstr "%s:对象缺失"
-
-#: builtin/fsck.c
-#, c-format
-msgid "invalid parameter: expected sha1, got '%s'"
-msgstr "无效的参数:期望 sha1,得到 '%s'"
-
 #: builtin/fsmonitor--daemon.c
 msgid "git fsmonitor--daemon start [<options>]"
 msgstr "git fsmonitor--daemon start [<选项>]"
@@ -9010,7 +9083,7 @@
 #: builtin/fsmonitor--daemon.c
 #, c-format
 msgid "fsmonitor: cookie_result '%d' != SEEN"
-msgstr "fsmonitor: cookie_result '%d' != SEEN"
+msgstr "fsmonitor:cookie_result '%d' != SEEN"
 
 #: builtin/fsmonitor--daemon.c
 #, c-format
@@ -9036,7 +9109,7 @@
 #: builtin/fsmonitor--daemon.c
 #, c-format
 msgid "could not cd home '%s'"
-msgstr "不能切换至家目录 '%s'"
+msgstr "无法切换至家目录 '%s'"
 
 #: builtin/fsmonitor--daemon.c
 #, c-format
@@ -9108,7 +9181,7 @@
 #: builtin/gc.c setup.c
 #, c-format
 msgid "cannot stat '%s'"
-msgstr "不能对 '%s' 调用 stat"
+msgstr "无法对 '%s' 调用 stat"
 
 #: builtin/gc.c
 #, c-format
@@ -9154,7 +9227,7 @@
 
 #: builtin/gc.c
 msgid "repack all other packs except the largest pack"
-msgstr "除了最大的包之外,对所有其它包文件重新打包"
+msgstr "除了最大的包之外,对所有其他包文件重新打包"
 
 #: builtin/gc.c builtin/repack.c
 msgid "pack prefix to store a pack containing pruned objects"
@@ -9276,7 +9349,7 @@
 #: builtin/gc.c
 #, c-format
 msgid "task '%s' cannot be selected multiple times"
-msgstr "任务 '%s' 不能被多次选择"
+msgstr "任务 '%s' 无法被多次选择"
 
 #: builtin/gc.c
 msgid "run tasks based on the state of the repository"
@@ -9284,7 +9357,7 @@
 
 #: builtin/gc.c
 msgid "perform maintenance in the background"
-msgstr "在后台执行运维"
+msgstr "在后台执行维护"
 
 #: builtin/gc.c
 msgid "frequency"
@@ -9296,7 +9369,7 @@
 
 #: builtin/gc.c
 msgid "do not report progress or other information over stderr"
-msgstr "不通过标准错误报告进度或其它信息"
+msgstr "不通过标准错误报告进度或其他信息"
 
 #: builtin/gc.c
 msgid "task"
@@ -9445,6 +9518,10 @@
 msgstr "无法将仓库添加到全局配置"
 
 #: builtin/gc.c
+msgid "check a specific task"
+msgstr "检查特定任务"
+
+#: builtin/gc.c
 msgid "git maintenance <subcommand> [<options>]"
 msgstr "git maintenance <子命令> [<选项>]"
 
@@ -9521,7 +9598,7 @@
 
 #: builtin/grep.c
 msgid "process binary files as text"
-msgstr "把二进制文件当做文本处理"
+msgstr "把二进制文件当作文本处理"
 
 #: builtin/grep.c
 msgid "don't match patterns in binary files"
@@ -9629,7 +9706,7 @@
 
 #: builtin/grep.c
 msgid "shortcut for -C NUM"
-msgstr "快捷键 -C 数字"
+msgstr "-C NUM 的快捷方式"
 
 #: builtin/grep.c
 msgid "show a line with the function name before matches"
@@ -9661,7 +9738,7 @@
 
 #: builtin/grep.c
 msgid "pager"
-msgstr "分页"
+msgstr "分页器"
 
 #: builtin/grep.c
 msgid "show matching files in the pager"
@@ -9686,7 +9763,7 @@
 #: builtin/grep.c
 #, c-format
 msgid "unable to resolve revision: %s"
-msgstr "不能解析版本:%s"
+msgstr "无法解析版本:%s"
 
 #: builtin/grep.c
 msgid "--untracked not supported with --recurse-submodules"
@@ -9768,7 +9845,7 @@
 
 #: builtin/help.c
 msgid "exclude guides"
-msgstr "排除向导"
+msgstr "排除指南"
 
 #: builtin/help.c
 msgid "show man page"
@@ -9969,7 +10046,7 @@
 
 #: builtin/index-pack.c
 msgid "offset value overflow for delta base object"
-msgstr "偏移值覆盖了 delta 基准对象"
+msgstr "delta 基准对象的偏移值溢出"
 
 #: builtin/index-pack.c
 msgid "delta base offset is out of bound"
@@ -10003,12 +10080,12 @@
 #: builtin/index-pack.c
 #, c-format
 msgid "cannot read existing object info %s"
-msgstr "不能读取现存对象信息 %s"
+msgstr "无法读取现存对象信息 %s"
 
 #: builtin/index-pack.c
 #, c-format
 msgid "cannot read existing object %s"
-msgstr "不能读取现存对象 %s"
+msgstr "无法读取现存对象 %s"
 
 #: builtin/index-pack.c
 #, c-format
@@ -10021,6 +10098,11 @@
 
 #: builtin/index-pack.c
 #, c-format
+msgid "invalid %s"
+msgstr "无效的 %s"
+
+#: builtin/index-pack.c
+#, c-format
 msgid "Not all child objects of %s are reachable"
 msgstr "%s 的所有子对象并非都可达"
 
@@ -10038,11 +10120,11 @@
 
 #: builtin/index-pack.c
 msgid "pack is corrupted (SHA1 mismatch)"
-msgstr "包冲突(SHA1 不匹配)"
+msgstr "包已损坏(SHA1 不匹配)"
 
 #: builtin/index-pack.c
 msgid "cannot fstat packfile"
-msgstr "不能对包文件调用 fstat"
+msgstr "无法对包文件调用 fstat"
 
 #: builtin/index-pack.c
 msgid "pack has junk at the end"
@@ -10059,7 +10141,7 @@
 #: builtin/index-pack.c builtin/pack-objects.c
 #, c-format
 msgid "unable to create thread: %s"
-msgstr "不能创建线程:%s"
+msgstr "无法创建线程:%s"
 
 #: builtin/index-pack.c
 msgid "confusion beyond insanity"
@@ -10087,7 +10169,7 @@
 #: builtin/index-pack.c
 #, c-format
 msgid "unable to deflate appended object (%d)"
-msgstr "不能压缩附加对象(%d)"
+msgstr "无法压缩附加对象(%d)"
 
 #: builtin/index-pack.c
 #, c-format
@@ -10112,7 +10194,7 @@
 #: builtin/index-pack.c
 #, c-format
 msgid "unable to rename temporary '*.%s' file to '%s'"
-msgstr "不能重命名临时文件 '*.%s' 为 '%s'"
+msgstr "无法重命名临时文件 '*.%s' 为 '%s'"
 
 #: builtin/index-pack.c
 msgid "error while closing pack file"
@@ -10144,8 +10226,8 @@
 #, c-format
 msgid "chain length = %d: %lu object"
 msgid_plural "chain length = %d: %lu objects"
-msgstr[0] "链长 = %d: %lu 对象"
-msgstr[1] "链长 = %d: %lu 对象"
+msgstr[0] "链长 = %d:%lu 对象"
+msgstr[1] "链长 = %d:%lu 对象"
 
 #: builtin/index-pack.c
 msgid "could not start pack-objects to repack local links"
@@ -10176,7 +10258,7 @@
 #: builtin/index-pack.c
 #, c-format
 msgid "bad %s"
-msgstr "错误选项 %s"
+msgstr "错误的 %s"
 
 #: builtin/index-pack.c builtin/init-db.c setup.c
 #, c-format
@@ -10185,7 +10267,7 @@
 
 #: builtin/index-pack.c
 msgid "--promisor cannot be used with a pack name"
-msgstr "--promisor 无法与包名称一起使用"
+msgstr "--promisor 不能与包名称一起使用"
 
 #: builtin/index-pack.c
 msgid "--stdin requires a git repository"
@@ -10195,6 +10277,10 @@
 msgid "--verify with no packfile name given"
 msgstr "--verify 没有提供包文件名参数"
 
+#: builtin/index-pack.c
+msgid "cannot perform queued object checks outside of a repository"
+msgstr "无法在仓库之外执行排队的对象检查"
+
 #: builtin/index-pack.c builtin/unpack-objects.c
 msgid "fsck error in pack objects"
 msgstr "在打包对象中 fsck 检查出错"
@@ -10227,7 +10313,7 @@
 
 #: builtin/init-db.c builtin/verify-pack.c
 msgid "hash"
-msgstr "hash"
+msgstr "哈希"
 
 #: builtin/init-db.c builtin/show-index.c builtin/verify-pack.c
 msgid "specify the hash algorithm to use"
@@ -10236,12 +10322,12 @@
 #: builtin/init-db.c
 #, c-format
 msgid "cannot mkdir %s"
-msgstr "不能创建目录 %s"
+msgstr "无法创建目录 %s"
 
 #: builtin/init-db.c
 #, c-format
 msgid "cannot chdir to %s"
-msgstr "不能切换目录到 %s"
+msgstr "无法切换目录到 %s"
 
 #: builtin/init-db.c
 #, c-format
@@ -10253,11 +10339,11 @@
 #: builtin/init-db.c
 #, c-format
 msgid "Cannot access work tree '%s'"
-msgstr "不能访问工作区 '%s'"
+msgstr "无法访问工作区 '%s'"
 
 #: builtin/init-db.c
 msgid "--separate-git-dir incompatible with bare repository"
-msgstr "--separate-git-dir 不能用于纯仓库"
+msgstr "--separate-git-dir 和纯仓库不兼容"
 
 #: builtin/interpret-trailers.c
 msgid ""
@@ -10272,7 +10358,7 @@
 #: builtin/interpret-trailers.c wrapper.c
 #, c-format
 msgid "could not stat %s"
-msgstr "不能对 %s 调用 stat"
+msgstr "无法对 %s 调用 stat"
 
 #: builtin/interpret-trailers.c
 #, c-format
@@ -10286,21 +10372,21 @@
 
 #: builtin/interpret-trailers.c
 msgid "could not open temporary file"
-msgstr "不能打开临时文件"
+msgstr "无法打开临时文件"
 
 #: builtin/interpret-trailers.c
 #, c-format
 msgid "could not read input file '%s'"
-msgstr "不能读取输入文件 '%s'"
+msgstr "无法读取输入文件 '%s'"
 
 #: builtin/interpret-trailers.c builtin/mktag.c imap-send.c
 msgid "could not read from stdin"
-msgstr "不能自标准输入读取"
+msgstr "无法自标准输入读取"
 
 #: builtin/interpret-trailers.c
 #, c-format
 msgid "could not rename temporary file to %s"
-msgstr "不能重命名临时文件为 %s"
+msgstr "无法重命名临时文件为 %s"
 
 #: builtin/interpret-trailers.c
 msgid "edit files in place"
@@ -10431,7 +10517,7 @@
 
 #: builtin/log.c
 msgid "-L<range>:<file> cannot be used with pathspec"
-msgstr "-L<范围>:<文件> 不能和路径表达式共用"
+msgstr "-L<范围>:<文件> 不能和路径规格共用"
 
 #: builtin/log.c
 msgid ""
@@ -10452,7 +10538,7 @@
 #: builtin/log.c
 #, c-format
 msgid "git show %s: bad file"
-msgstr "git show %s: 损坏的文件"
+msgstr "git show %s:损坏的文件"
 
 #: builtin/log.c
 #, c-format
@@ -10524,7 +10610,7 @@
 
 #: builtin/log.c
 msgid "could not find exact merge base"
-msgstr "不能找到准确的合并基线"
+msgstr "无法找到准确的合并基线"
 
 #: builtin/log.c
 msgid ""
@@ -10550,7 +10636,7 @@
 
 #: builtin/log.c
 msgid "cannot get patch id"
-msgstr "无法得到补丁 id"
+msgstr "无法得到补丁 ID"
 
 #: builtin/log.c
 msgid "failed to infer range-diff origin of current series"
@@ -10595,7 +10681,7 @@
 
 #: builtin/log.c
 msgid "reroll-count"
-msgstr "重制-计数"
+msgstr "重制次数"
 
 #: builtin/log.c
 msgid "mark the series as Nth re-roll"
@@ -10651,7 +10737,7 @@
 
 #: builtin/log.c
 msgid "show patch format instead of default (patch + stat)"
-msgstr "显示纯补丁格式而非默认的(补丁+状态)"
+msgstr "显示纯补丁格式而非默认的(补丁+统计)"
 
 #: builtin/log.c
 msgid "Messaging"
@@ -10659,7 +10745,7 @@
 
 #: builtin/log.c
 msgid "header"
-msgstr "header"
+msgstr "头信息"
 
 #: builtin/log.c
 msgid "add email header"
@@ -10683,7 +10769,7 @@
 
 #: builtin/log.c
 msgid "set From address to <ident> (or committer ident if absent)"
-msgstr "将 From 地址设置为 <标识>(如若不提供,则用提交者 ID 做为地址)"
+msgstr "将 From 地址设置为 <标识>(如若不提供,则用提交者 ID 作为地址)"
 
 #: builtin/log.c
 msgid "message-id"
@@ -10777,7 +10863,7 @@
 #: builtin/log.c builtin/submodule--helper.c rerere.c submodule.c
 #, c-format
 msgid "could not create directory '%s'"
-msgstr "不能创建目录 '%s'"
+msgstr "无法创建目录 '%s'"
 
 #: builtin/log.c
 msgid "--interdiff requires --cover-letter or single patch"
@@ -10826,7 +10912,7 @@
 #, c-format
 msgid ""
 "Could not find a tracked remote branch, please specify <upstream> manually.\n"
-msgstr "不能找到跟踪的远程分支,请手工指定 <上游>。\n"
+msgstr "无法找到跟踪的远程分支,请手工指定 <上游>。\n"
 
 #: builtin/ls-files.c builtin/ls-tree.c
 #, c-format
@@ -10867,7 +10953,7 @@
 
 #: builtin/ls-files.c
 msgid "show other files in the output"
-msgstr "显示其它文件"
+msgstr "显示其他文件"
 
 #: builtin/ls-files.c
 msgid "show ignored files in the output"
@@ -10969,7 +11055,7 @@
 
 #: builtin/ls-remote.c builtin/rebase.c
 msgid "exec"
-msgstr "exec"
+msgstr "可执行命令"
 
 #: builtin/ls-remote.c
 msgid "path of git-upload-pack on the remote host"
@@ -10997,7 +11083,7 @@
 
 #: builtin/ls-remote.c
 msgid "exit with exit code 2 if no matching refs are found"
-msgstr "若未找到匹配的引用则以退出码2退出"
+msgstr "若未找到匹配的引用则以退出码 2 退出"
 
 #: builtin/ls-remote.c
 msgid "show underlying ref in addition to the object pointed by it"
@@ -11131,11 +11217,11 @@
 
 #: builtin/merge-base.c
 msgid "list revs not reachable from others"
-msgstr "显示不能被其他访问到的版本"
+msgstr "显示无法被其他访问到的版本"
 
 #: builtin/merge-base.c
 msgid "is the first one ancestor of the other?"
-msgstr "第一个是其他的祖先提交么?"
+msgstr "第一个是其他的祖先提交吗?"
 
 #: builtin/merge-base.c
 msgid "find where <commit> forked from reflog of <ref>"
@@ -11149,14 +11235,6 @@
 "git merge-file [<选项>] [-L <名字1> [-L <初始名字> [-L <名字2>]]] <文件1> <初"
 "始文件> <文件2>"
 
-#: builtin/merge-file.c diff.c
-msgid ""
-"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and "
-"\"histogram\""
-msgstr ""
-"选项 diff-algorithm 接受参数 \"myers\"、\"minimal\"、\"patience\" 和 "
-"\"histogram\""
-
 #: builtin/merge-file.c
 msgid "send results to standard output"
 msgstr "将结果发送到标准输出"
@@ -11173,14 +11251,6 @@
 msgid "use a zealous diff3 based merge"
 msgstr "使用基于狂热 diff3(zealous diff3)的合并"
 
-#: builtin/merge-file.c diff.c
-msgid "<algorithm>"
-msgstr "<算法>"
-
-#: builtin/merge-file.c diff.c
-msgid "choose a diff algorithm"
-msgstr "选择一个差异算法"
-
 #: builtin/merge-file.c
 msgid "for conflicts, use this marker size"
 msgstr "如果冲突,使用指定长度的标记"
@@ -11200,12 +11270,12 @@
 
 #: builtin/merge-file.c
 msgid "Could not write object file"
-msgstr "不能写入对象文件"
+msgstr "无法写入对象文件"
 
 #: builtin/merge-recursive.c
 #, c-format
 msgid "could not parse object '%s'"
-msgstr "不能解析对象 '%s'"
+msgstr "无法解析对象 '%s'"
 
 #: builtin/merge-recursive.c
 #, c-format
@@ -11216,7 +11286,7 @@
 
 #: builtin/merge-recursive.c
 msgid "not handling anything other than two heads merge."
-msgstr "不能处理两个头合并之外的任何操作。"
+msgstr "无法处理两个头合并之外的任何操作。"
 
 #: builtin/merge-recursive.c
 #, c-format
@@ -11287,7 +11357,7 @@
 
 #: builtin/merge-tree.c builtin/merge.c builtin/pull.c
 msgid "option=value"
-msgstr "option=value"
+msgstr "选项=值"
 
 #: builtin/merge-tree.c builtin/merge.c builtin/pull.c
 msgid "option for selected merge strategy"
@@ -11323,7 +11393,7 @@
 #: builtin/merge.c
 #, c-format
 msgid "Could not find merge strategy '%s'.\n"
-msgstr "不能找到合并策略 '%s'。\n"
+msgstr "无法找到合并策略 '%s'。\n"
 
 #: builtin/merge.c
 #, c-format
@@ -11373,7 +11443,7 @@
 
 #: builtin/merge.c builtin/pull.c
 msgid "abort if fast-forward is not possible"
-msgstr "如果不能快进就放弃合并"
+msgstr "如果无法快进就放弃合并"
 
 #: builtin/merge.c builtin/pull.c
 msgid "verify that the named commit has a valid GPG signature"
@@ -11415,11 +11485,11 @@
 
 #: builtin/merge.c
 msgid "could not run stash."
-msgstr "不能运行贮藏。"
+msgstr "无法运行储藏。"
 
 #: builtin/merge.c
 msgid "stash failed"
-msgstr "贮藏失败"
+msgstr "储藏失败"
 
 #: builtin/merge.c
 msgid "read-tree failed"
@@ -11455,7 +11525,7 @@
 
 #: builtin/merge.c merge-ort-wrappers.c
 msgid "Unable to write index."
-msgstr "不能写入索引。"
+msgstr "无法写入索引。"
 
 #: builtin/merge.c
 msgid "Not handling anything other than two heads merge."
@@ -11464,12 +11534,12 @@
 #: builtin/merge.c builtin/sparse-checkout.c
 #, c-format
 msgid "unable to write %s"
-msgstr "不能写 %s"
+msgstr "无法写 %s"
 
 #: builtin/merge.c
 #, c-format
 msgid "Could not read from '%s'"
-msgstr "不能从 '%s' 读取"
+msgstr "无法从 '%s' 读取"
 
 #: builtin/merge.c
 #, c-format
@@ -11536,12 +11606,12 @@
 #: builtin/merge.c editor.c read-cache.c wrapper.c
 #, c-format
 msgid "could not close '%s'"
-msgstr "不能关闭 '%s'"
+msgstr "无法关闭 '%s'"
 
 #: builtin/merge.c
 #, c-format
 msgid "not something we can merge in %s: %s"
-msgstr "不能在 %s 中合并:%s"
+msgstr "无法在 %s 中合并:%s"
 
 #: builtin/merge.c
 msgid "--abort expects no arguments"
@@ -11598,7 +11668,7 @@
 #: builtin/merge.c
 #, c-format
 msgid "%s - not something we can merge"
-msgstr "%s - 不能被合并"
+msgstr "%s - 无法被合并"
 
 #: builtin/merge.c
 msgid "Can merge only exactly one commit into empty head"
@@ -11626,7 +11696,7 @@
 #: builtin/merge.c
 #, c-format
 msgid "Nope.\n"
-msgstr "无。\n"
+msgstr "不行。\n"
 
 #: builtin/merge.c
 #, c-format
@@ -11661,7 +11731,7 @@
 #: builtin/merge.c
 #, c-format
 msgid "When finished, apply stashed changes with `git stash pop`\n"
-msgstr "在完成后,使用 `git stash pop` 应用贮藏的变更\n"
+msgstr "在完成后,使用 `git stash pop` 应用储藏的变更\n"
 
 #: builtin/mktag.c
 #, c-format
@@ -11676,7 +11746,7 @@
 #: builtin/mktag.c
 #, c-format
 msgid "could not read tagged object '%s'"
-msgstr "不能读取被标记的对象 '%s'"
+msgstr "无法读取被标记的对象 '%s'"
 
 #: builtin/mktag.c
 #, c-format
@@ -11780,7 +11850,7 @@
 
 #: builtin/mv.c
 msgid "Please stage your changes to .gitmodules or stash them to proceed"
-msgstr "请将您的修改暂存到 .gitmodules 中或贮藏后再继续"
+msgstr "请将您的修改暂存到 .gitmodules 中或储藏后再继续"
 
 #: builtin/mv.c
 #, c-format
@@ -11789,7 +11859,7 @@
 
 #: builtin/mv.c
 msgid "force move/rename even if target exists"
-msgstr "强制移动/重命令,即使目标存在"
+msgstr "强制移动/重命名,即使目标存在"
 
 #: builtin/mv.c
 msgid "skip move/rename errors"
@@ -11807,7 +11877,7 @@
 
 #: builtin/mv.c
 msgid "bad source"
-msgstr "坏的源"
+msgstr "错误的源"
 
 #: builtin/mv.c
 msgid "destination exists"
@@ -11815,7 +11885,7 @@
 
 #: builtin/mv.c
 msgid "can not move directory into itself"
-msgstr "不能将目录移动到自身"
+msgstr "无法将目录移动到自身"
 
 #: builtin/mv.c
 msgid "destination already exists"
@@ -11840,7 +11910,7 @@
 
 #: builtin/mv.c
 msgid "Cannot overwrite"
-msgstr "不能覆盖"
+msgstr "无法覆盖"
 
 #: builtin/mv.c
 msgid "multiple sources for the same target"
@@ -11916,7 +11986,7 @@
 
 #: builtin/name-rev.c
 msgid "allow to print `undefined` names (default)"
-msgstr "允许打印 `未定义` 的名称(默认)"
+msgstr "允许打印 `undefined` 的名称(默认)"
 
 #: builtin/name-rev.c
 msgid "dereference tags in the input (internal use)"
@@ -11961,7 +12031,7 @@
 #: builtin/notes.c
 msgid ""
 "git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"
-msgstr "git notes [--ref <注解引用>] merge [-v | -q] [-s <策略> ] <注解引用>"
+msgstr "git notes [--ref <注解引用>] merge [-v | -q] [-s <策略>] <注解引用>"
 
 #: builtin/notes.c
 msgid "git notes [--ref <notes-ref>] remove [<object>...]"
@@ -12029,7 +12099,7 @@
 
 #: builtin/notes.c
 msgid "could not read 'show' output"
-msgstr "不能读取 'show' 的输出"
+msgstr "无法读取 'show' 的输出"
 
 #: builtin/notes.c
 #, c-format
@@ -12042,7 +12112,7 @@
 
 #: builtin/notes.c
 msgid "unable to write note object"
-msgstr "不能写注解对象"
+msgstr "无法写注解对象"
 
 #: builtin/notes.c
 #, c-format
@@ -12052,7 +12122,7 @@
 #: builtin/notes.c builtin/tag.c
 #, c-format
 msgid "could not open or read '%s'"
-msgstr "不能打开或读取 '%s'"
+msgstr "无法打开或读取 '%s'"
 
 #: builtin/notes.c
 #, c-format
@@ -12067,7 +12137,7 @@
 #: builtin/notes.c
 #, c-format
 msgid "cannot read note data from non-blob object '%s'."
-msgstr "不能从非数据对象 '%s' 中读取注解数据。"
+msgstr "无法从非数据对象 '%s' 中读取注解数据。"
 
 #: builtin/notes.c
 #, c-format
@@ -12080,7 +12150,7 @@
 #: builtin/notes.c
 #, c-format
 msgid "refusing to %s notes in %s (outside of refs/notes/)"
-msgstr "拒绝向 %2$s(在 refs/notes/ 之外)%1$s注解"
+msgstr "拒绝向 %2$s(在 refs/notes/ 之外)%1$s 注解"
 
 #: builtin/notes.c
 #, c-format
@@ -12093,7 +12163,7 @@
 
 #: builtin/notes.c
 msgid "note contents in a file"
-msgstr "注解内容到一个文件中"
+msgstr "注解内容在一个文件中"
 
 #: builtin/notes.c
 msgid "reuse and edit specified note object"
@@ -12101,7 +12171,7 @@
 
 #: builtin/notes.c
 msgid "edit note message in editor"
-msgstr "在编辑器中编辑注释说明"
+msgstr "在编辑器中编辑注解说明"
 
 #: builtin/notes.c
 msgid "reuse specified note object"
@@ -12109,7 +12179,7 @@
 
 #: builtin/notes.c
 msgid "allow storing empty note"
-msgstr "允许保存空白注释"
+msgstr "允许保存空注解"
 
 #: builtin/notes.c
 msgid "replace existing notes"
@@ -12121,7 +12191,7 @@
 
 #: builtin/notes.c
 msgid "insert <paragraph-break> between paragraphs"
-msgstr "在段落之间插入<分段符>"
+msgstr "在段落之间插入 <分段符>"
 
 #: builtin/notes.c
 msgid "remove unnecessary whitespace"
@@ -12132,7 +12202,7 @@
 msgid ""
 "Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite "
 "existing notes"
-msgstr "不能添加注解。发现对象 %s 已存在注解。使用 '-f' 覆盖现存注解"
+msgstr "无法添加注解。发现对象 %s 已存在注解。使用 '-f' 覆盖现存注解"
 
 #: builtin/notes.c
 #, c-format
@@ -12161,12 +12231,12 @@
 msgid ""
 "Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite "
 "existing notes"
-msgstr "不能拷贝注解。发现对象 %s 已存在注解。使用 '-f' 覆盖现存注解"
+msgstr "无法拷贝注解。发现对象 %s 已存在注解。使用 '-f' 覆盖现存注解"
 
 #: builtin/notes.c
 #, c-format
 msgid "missing notes on source object %s. Cannot copy."
-msgstr "源对象 %s 缺少注解。不能拷贝。"
+msgstr "源对象 %s 缺少注解。无法拷贝。"
 
 #: builtin/notes.c
 #, c-format
@@ -12238,7 +12308,7 @@
 
 #: builtin/notes.c
 msgid "Aborting notes merge resolution"
-msgstr "中止注解合并的方案"
+msgstr "中止注解合并的解决过程"
 
 #: builtin/notes.c
 msgid "abort notes merge"
@@ -12274,7 +12344,7 @@
 "'git notes merge --commit', or abort the merge with 'git notes merge --"
 "abort'.\n"
 msgstr ""
-"自动合并说明失败。修改 %s 中的冲突并且使用命令 'git notes merge --commit' 提"
+"自动注解合并失败。修改 %s 中的冲突并且使用命令 'git notes merge --commit' 提"
 "交结果,或者使用命令 'git notes merge --abort' 终止合并。\n"
 
 #: builtin/notes.c builtin/tag.c
@@ -12335,7 +12405,7 @@
 "                 [--revs [--unpacked | --all]] [--keep-pack=<包名>]\n"
 "                 [--cruft] [--cruft-expiration=<时间>]\n"
 "                 [--stdout [--filter=<过滤规格>] | <基线名称>]\n"
-"                 [--shallow] [--keep-true-parents] [--[no-]sparse]\"\n"
+"                 [--shallow] [--keep-true-parents] [--[no-]sparse]\n"
 "                 [--name-hash-version=<n>] [--path-walk] < <对象列表>"
 
 #: builtin/pack-objects.c
@@ -12414,7 +12484,7 @@
 #: builtin/pack-objects.c
 #, c-format
 msgid "delta base offset overflow in pack for %s"
-msgstr "%s 压缩中 delta 基准偏移越界"
+msgstr "包 %s 中 delta 基准偏移溢出"
 
 #: builtin/pack-objects.c
 #, c-format
@@ -12510,8 +12580,13 @@
 
 #: builtin/pack-objects.c
 #, c-format
+msgid "packfile %s is a promisor but --exclude-promisor-objects was given"
+msgstr "包文件 %s 是承诺者,但已给出 --exclude-promisor-objects"
+
+#: builtin/pack-objects.c
+#, c-format
 msgid "could not find pack '%s'"
-msgstr "不能找到包 '%s'"
+msgstr "无法找到包 '%s'"
 
 #: builtin/pack-objects.c
 #, c-format
@@ -12550,7 +12625,7 @@
 
 #: builtin/pack-objects.c reachable.c
 msgid "could not load cruft pack .mtimes"
-msgstr "不能载入废弃包 .mtimes"
+msgstr "无法载入废弃包 .mtimes"
 
 #: builtin/pack-objects.c
 msgid "cannot open pack index"
@@ -12577,7 +12652,7 @@
 #: builtin/pack-objects.c builtin/rev-parse.c
 #, c-format
 msgid "bad revision '%s'"
-msgstr "坏的版本 '%s'"
+msgstr "错误的版本 '%s'"
 
 #: builtin/pack-objects.c
 msgid "unable to add recent objects"
@@ -12727,7 +12802,7 @@
 
 #: builtin/pack-objects.c
 msgid "ignore this pack"
-msgstr "忽略该 pack"
+msgstr "忽略该包"
 
 #: builtin/pack-objects.c
 msgid "pack compression level"
@@ -12735,7 +12810,7 @@
 
 #: builtin/pack-objects.c
 msgid "do not hide commits by grafts"
-msgstr "显示被移植隐藏的提交"
+msgstr "显示被嫁接隐藏的提交"
 
 #: builtin/pack-objects.c
 msgid "use a bitmap index if available to speed up counting objects"
@@ -12828,7 +12903,7 @@
 "reused %<PRIu32> (from %<PRIuMAX>)"
 msgstr ""
 "总共 %<PRIu32>(差异 %<PRIu32>),复用 %<PRIu32>(差异 %<PRIu32>),包复用 "
-"%<PRIu32>(来自  %<PRIuMAX> 个包)"
+"%<PRIu32>(来自 %<PRIuMAX> 个包)"
 
 #: builtin/pack-refs.c
 msgid "git pack-refs "
@@ -12839,12 +12914,12 @@
 msgstr "git patch-id [--stable | --unstable | --verbatim]"
 
 #: builtin/patch-id.c
-msgid "use the unstable patch-id algorithm"
-msgstr "使用不稳定的 patch-id 算法"
+msgid "use the unstable patch ID algorithm"
+msgstr "使用不稳定的 patch ID 算法"
 
 #: builtin/patch-id.c
-msgid "use the stable patch-id algorithm"
-msgstr "使用稳定的 patch-id 算法"
+msgid "use the stable patch ID algorithm"
+msgstr "使用稳定的 patch ID 算法"
 
 #: builtin/patch-id.c
 msgid "don't strip whitespace from the patch"
@@ -12875,50 +12950,6 @@
 msgstr "git pull [<选项>] [<仓库> [<引用规格>...]]"
 
 #: builtin/pull.c
-msgid "control for recursive fetching of submodules"
-msgstr "控制子模组的递归获取"
-
-#: builtin/pull.c
-msgid "Options related to merging"
-msgstr "和合并相关的选项"
-
-#: builtin/pull.c
-msgid "incorporate changes by rebasing rather than merging"
-msgstr "使用变基操作取代合并操作以合入修改"
-
-#: builtin/pull.c builtin/revert.c
-msgid "allow fast-forward"
-msgstr "允许快进式"
-
-#: builtin/pull.c
-msgid "control use of pre-merge-commit and commit-msg hooks"
-msgstr "控制 pre-merge-commit 和 commit-msg 钩子的使用"
-
-#: builtin/pull.c parse-options.h
-msgid "automatically stash/stash pop before and after"
-msgstr "在操作前后执行自动贮藏和弹出贮藏"
-
-#: builtin/pull.c
-msgid "Options related to fetching"
-msgstr "和获取相关的参数"
-
-#: builtin/pull.c
-msgid "force overwrite of local branch"
-msgstr "强制覆盖本地分支"
-
-#: builtin/pull.c
-msgid "number of submodules pulled in parallel"
-msgstr "并发拉取的子模组的数量"
-
-#: builtin/pull.c parse-options.h
-msgid "use IPv4 addresses only"
-msgstr "只使用 IPv4 地址"
-
-#: builtin/pull.c parse-options.h
-msgid "use IPv6 addresses only"
-msgstr "只使用 IPv6 地址"
-
-#: builtin/pull.c
 msgid ""
 "There is no candidate for rebasing against among the refs that you just "
 "fetched."
@@ -13025,8 +13056,52 @@
 "  git config pull.ff only       # 仅快进\n"
 "\n"
 "您可以将 \"git config\" 替换为 \"git config --global\" 以便为所有仓库设置\n"
-"缺省的配置项。您也可以在每次执行 pull 命令时添加 --rebase、--no-rebase,\n"
-"或者 --ff-only 参数覆盖缺省设置。\n"
+"默认的配置项。您也可以在每次执行 pull 命令时添加 --rebase、--no-rebase,\n"
+"或者 --ff-only 参数覆盖默认设置。\n"
+
+#: builtin/pull.c
+msgid "control for recursive fetching of submodules"
+msgstr "控制子模组的递归获取"
+
+#: builtin/pull.c
+msgid "Options related to merging"
+msgstr "和合并相关的选项"
+
+#: builtin/pull.c
+msgid "incorporate changes by rebasing rather than merging"
+msgstr "使用变基操作取代合并操作以合入修改"
+
+#: builtin/pull.c builtin/revert.c
+msgid "allow fast-forward"
+msgstr "允许快进"
+
+#: builtin/pull.c
+msgid "control use of pre-merge-commit and commit-msg hooks"
+msgstr "控制 pre-merge-commit 和 commit-msg 钩子的使用"
+
+#: builtin/pull.c parse-options.h
+msgid "automatically stash/stash pop before and after"
+msgstr "在操作前后执行自动储藏和弹出储藏"
+
+#: builtin/pull.c
+msgid "Options related to fetching"
+msgstr "和获取相关的参数"
+
+#: builtin/pull.c
+msgid "force overwrite of local branch"
+msgstr "强制覆盖本地分支"
+
+#: builtin/pull.c
+msgid "number of submodules pulled in parallel"
+msgstr "并发拉取的子模组的数量"
+
+#: builtin/pull.c parse-options.h
+msgid "use IPv4 addresses only"
+msgstr "只使用 IPv4 地址"
+
+#: builtin/pull.c parse-options.h
+msgid "use IPv6 addresses only"
+msgstr "只使用 IPv6 地址"
 
 #: builtin/pull.c
 msgid "Updating an unborn branch with changes added to the index."
@@ -13038,7 +13113,7 @@
 
 #: builtin/pull.c builtin/rebase.c
 msgid "Please commit or stash them."
-msgstr "请提交或贮藏修改。"
+msgstr "请提交或储藏修改。"
 
 #: builtin/pull.c
 #, c-format
@@ -13085,7 +13160,7 @@
 
 #: builtin/pull.c
 msgid "cannot rebase with locally recorded submodule modifications"
-msgstr "本地子模组中有修改,无法变基"
+msgstr "本地子模组中有修改,不能变基"
 
 #: builtin/push.c
 msgid "git push [<options>] [<repository> [<refspec>...]]"
@@ -13300,7 +13375,7 @@
 
 #: builtin/push.c
 msgid "push tags (can't be used with --all or --branches or --mirror)"
-msgstr "推送标签(不能使用 --all or --branches or --mirror)"
+msgstr "推送标签(不能与 --all、--branches 或 --mirror 同时使用)"
 
 #: builtin/push.c builtin/send-pack.c
 msgid "force updates"
@@ -13361,7 +13436,7 @@
 #: builtin/push.c t/helper/test-bundle-uri.c
 #, c-format
 msgid "bad repository '%s'"
-msgstr "坏的仓库 '%s'"
+msgstr "错误的仓库 '%s'"
 
 #: builtin/push.c
 msgid ""
@@ -13489,11 +13564,11 @@
 
 #: builtin/read-tree.c
 msgid "3-way merge if no file level merging required"
-msgstr "如果没有文件级合并需要,执行三方合并"
+msgstr "如果没有文件级合并需要,执行三路合并"
 
 #: builtin/read-tree.c
 msgid "3-way merge in presence of adds and removes"
-msgstr "存在添加和删除时,也执行三方合并"
+msgstr "存在添加和删除时,也执行三路合并"
 
 #: builtin/read-tree.c
 msgid "same as -m, but discard unmerged entries"
@@ -13517,7 +13592,7 @@
 
 #: builtin/read-tree.c
 msgid "allow explicitly ignored files to be overwritten"
-msgstr "允许忽略文件中设定的文件可以被覆盖"
+msgstr "允许显式忽略的文件被覆盖"
 
 #: builtin/read-tree.c
 msgid "don't check the working tree after merging"
@@ -13560,7 +13635,7 @@
 #: builtin/rebase.c sequencer.c
 #, c-format
 msgid "could not read '%s'."
-msgstr "不能读取 '%s'。"
+msgstr "无法读取 '%s'。"
 
 #: builtin/rebase.c
 #, c-format
@@ -13701,7 +13776,7 @@
 
 #: builtin/rebase.c
 msgid "use the merge-base of upstream and branch as the current base"
-msgstr "使用上游和分支的合并基线做为当前基线"
+msgstr "使用上游和分支的合并基线作为当前基线"
 
 #: builtin/rebase.c
 msgid "allow pre-rebase hook to run"
@@ -13877,7 +13952,7 @@
 
 #: builtin/rebase.c
 msgid "Cannot read HEAD"
-msgstr "不能读取 HEAD"
+msgstr "无法读取 HEAD"
 
 #: builtin/rebase.c
 msgid ""
@@ -13947,7 +14022,7 @@
 
 #: builtin/rebase.c
 msgid "Could not create new root commit"
-msgstr "不能创建新的根提交"
+msgstr "无法创建新的根提交"
 
 #: builtin/rebase.c
 #, c-format
@@ -13961,7 +14036,7 @@
 
 #: builtin/rebase.c
 msgid "Could not resolve HEAD to a commit"
-msgstr "不能解析 HEAD 至一个提交"
+msgstr "无法解析 HEAD 至一个提交"
 
 #: builtin/rebase.c
 #, c-format
@@ -14095,7 +14170,7 @@
 
 #: builtin/reflog.c
 msgid "git reflog write <ref> <old-oid> <new-oid> <message>"
-msgstr "git reflog write <引用> <旧oid> <新oid> <消息>"
+msgstr "git reflog write <引用> <旧OID> <新OID> <消息>"
 
 #: builtin/reflog.c
 msgid ""
@@ -14233,7 +14308,7 @@
 #: builtin/reflog.c
 #, c-format
 msgid "cannot start transaction: %s"
-msgstr "不能启动事务:%s"
+msgstr "无法启动事务:%s"
 
 #: builtin/reflog.c
 #, c-format
@@ -14255,7 +14330,7 @@
 
 #: builtin/refs.c
 msgid "git refs exists <ref>"
-msgstr "git reflog exists <引用>"
+msgstr "git refs exists <引用>"
 
 #: builtin/refs.c
 msgid "git refs optimize "
@@ -14292,7 +14367,7 @@
 
 #: builtin/refs.c
 msgid "git refs list "
-msgstr "git reflog list"
+msgstr "git refs list "
 
 #: builtin/refs.c
 msgid "'git refs exists' requires a reference"
@@ -14391,7 +14466,7 @@
 #: builtin/remote.c
 #, c-format
 msgid "Could not fetch %s"
-msgstr "不能获取 %s"
+msgstr "无法获取 %s"
 
 #: builtin/remote.c
 msgid ""
@@ -14482,12 +14557,12 @@
 #: builtin/remote.c
 #, c-format
 msgid "could not set '%s'"
-msgstr "不能设置 '%s'"
+msgstr "无法设置 '%s'"
 
 #: builtin/remote.c config.c
 #, c-format
 msgid "could not unset '%s'"
-msgstr "不能取消设置 '%s'"
+msgstr "无法取消设置 '%s'"
 
 #: builtin/remote.c
 #, c-format
@@ -14498,7 +14573,7 @@
 msgstr ""
 "配置(%s)remote.pushDefault 位于:\n"
 "\t%s:%d\n"
-"现在在为不存在的远程名 '%s' 命名"
+"现在指向不存在的远程名 '%s'"
 
 #: builtin/remote.c
 msgid ""
@@ -14524,7 +14599,7 @@
 #: builtin/remote.c
 #, c-format
 msgid "Could not rename config section '%s' to '%s'"
-msgstr "不能重命名配置小节 '%s' 到 '%s'"
+msgstr "无法重命名配置小节 '%s' 到 '%s'"
 
 #: builtin/remote.c
 msgid "Renaming remote references"
@@ -14564,7 +14639,7 @@
 #: builtin/remote.c
 #, c-format
 msgid "Could not remove config section '%s'"
-msgstr "不能移除配置小节 '%s'"
+msgstr "无法移除配置小节 '%s'"
 
 #: builtin/remote.c
 #, c-format
@@ -14590,7 +14665,7 @@
 #: builtin/remote.c
 #, c-format
 msgid "invalid branch.%s.merge; cannot rebase onto > 1 branch"
-msgstr "无效的 branch.%s.merge,不能变基到一个以上的分支"
+msgstr "无效的 branch.%s.merge,无法变基到一个以上的分支"
 
 #: builtin/remote.c
 #, c-format
@@ -14620,7 +14695,7 @@
 #: builtin/remote.c
 #, c-format
 msgid "%-*s    and with remote %s\n"
-msgstr "%-*s    以及和远程 %s\n"
+msgstr "%-*s    以及与远程 %s\n"
 
 #: builtin/remote.c
 msgid "create"
@@ -14793,7 +14868,7 @@
 #: builtin/remote.c
 #, c-format
 msgid "Could not set up %s"
-msgstr "不能设置 %s"
+msgstr "无法设置 %s"
 
 #: builtin/remote.c
 #, c-format
@@ -14842,7 +14917,7 @@
 
 #: builtin/remote.c
 msgid "manipulate push URLs"
-msgstr "操作推送 URLS"
+msgstr "管理推送 URL"
 
 #: builtin/remote.c
 msgid "add URL"
@@ -14850,7 +14925,7 @@
 
 #: builtin/remote.c
 msgid "delete URLs"
-msgstr "删除 URLS"
+msgstr "删除 URL"
 
 #: builtin/remote.c
 msgid "--add --delete doesn't make sense"
@@ -14859,7 +14934,7 @@
 #: builtin/remote.c
 #, c-format
 msgid "Invalid old URL pattern: %s"
-msgstr "无效的旧 URL 匹配模版:%s"
+msgstr "无效的旧 URL 匹配模板:%s"
 
 #: builtin/remote.c
 #, c-format
@@ -14955,7 +15030,7 @@
 
 #: builtin/repack.c
 msgid "with -A, do not loosen objects older than this"
-msgstr "使用 -A,不要将早于给定时间的对象过期"
+msgstr "使用 -A,不要将早于给定时间的对象变为松散对象"
 
 #: builtin/repack.c
 msgid "with -a, repack unreachable objects"
@@ -14995,7 +15070,7 @@
 
 #: builtin/repack.c
 msgid "find a geometric progression with factor <N>"
-msgstr "使用因子 <n> 查找几何级数"
+msgstr "使用因子 <N> 查找几何级数"
 
 #: builtin/repack.c
 msgid "write a multi-pack index of the resulting packs"
@@ -15165,12 +15240,12 @@
 #: builtin/replace.c
 #, c-format
 msgid "could not write replacement commit for: '%s'"
-msgstr "不能为 '%s' 写替换提交"
+msgstr "无法为 '%s' 写替换提交"
 
 #: builtin/replace.c
 #, c-format
 msgid "graft for '%s' unnecessary"
-msgstr "对 '%s' 移植没有必要"
+msgstr "对 '%s' 嫁接没有必要"
 
 #: builtin/replace.c
 #, c-format
@@ -15183,7 +15258,7 @@
 "could not convert the following graft(s):\n"
 "%s"
 msgstr ""
-"不能转换下列移植:\n"
+"无法转换下列嫁接:\n"
 "%s"
 
 #: builtin/replace.c
@@ -15204,7 +15279,7 @@
 
 #: builtin/replace.c
 msgid "convert existing graft file"
-msgstr "转换现存的移植文件"
+msgstr "转换现存的嫁接文件"
 
 #: builtin/replace.c
 msgid "replace the ref if it exists"
@@ -15255,6 +15330,11 @@
 msgstr "只能为 -l 提供一个模式"
 
 #: builtin/replay.c
+#, c-format
+msgid "'%s' is not a valid commit-ish for %s"
+msgstr "'%s' 不是 %s 的有效提交号"
+
+#: builtin/replay.c
 msgid "need some commits to replay"
 msgstr "需要一些提交来重放"
 
@@ -15270,30 +15350,20 @@
 msgid ""
 "cannot advance target with multiple sources because ordering would be ill-"
 "defined"
-msgstr "不能使用多个源推进目标,因为无法明确如何排序"
+msgstr "无法使用多个源推进目标,因为无法明确如何排序"
 
 #: builtin/replay.c
-msgid ""
-"cannot implicitly determine whether this is an --advance or --onto operation"
-msgstr "不能隐式地确定这是 --advance 还是 --onto 的操作"
-
-#: builtin/replay.c
-msgid ""
-"cannot advance target with multiple source branches because ordering would "
-"be ill-defined"
-msgstr "不能使用多个源分支推进目标,因为无法明确如何排序"
-
-#: builtin/replay.c
-msgid "cannot implicitly determine correct base for --onto"
-msgstr "不能隐式地确定 --onto 正确的基线"
+#, c-format
+msgid "invalid %s value: '%s'"
+msgstr "无效的 %s 值:'%s'"
 
 #: builtin/replay.c
 msgid ""
 "(EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance "
-"<branch>) <revision-range>..."
+"<branch>) [--ref-action[=<mode>]] <revision-range>"
 msgstr ""
-"(试验中!)git replay ([--contained] --onto <新基线> | --advance <分支>) <版"
-"本范围>..."
+"(试验中!)git replay ([--contained] --onto <新基线> | --advance <分支>) [--"
+"ref-action[=<模式>]] <版本范围>"
 
 #: builtin/replay.c
 msgid "make replay advance given branch"
@@ -15304,8 +15374,12 @@
 msgstr "重放到给定提交"
 
 #: builtin/replay.c
-msgid "advance all branches contained in revision-range"
-msgstr "演进版本范围中包含的所有分支"
+msgid "update all branches that point at commits in <revision-range>"
+msgstr "更新指向 <版本范围> 中提交的所有分支"
+
+#: builtin/replay.c
+msgid "control ref update behavior (update|print)"
+msgstr "控制引用更新行为(update|print)"
 
 #: builtin/replay.c
 msgid "option --onto or --advance is mandatory"
@@ -15320,16 +15394,31 @@
 "一些版本遍历选项将被覆盖,如 'struct rev_info' 中的 '%s' 位将被强制设定"
 
 #: builtin/replay.c
+#, c-format
+msgid "failed to begin ref transaction: %s"
+msgstr "无法开始引用事务:%s"
+
+#: builtin/replay.c
 msgid "error preparing revisions"
 msgstr "准备版本时错误"
 
 #: builtin/replay.c
-msgid "replaying down to root commit is not supported yet!"
-msgstr "目前还不支持重放到根提交!"
+msgid "replaying down from root commit is not supported yet!"
+msgstr "目前还不支持从根提交向下重放!"
 
 #: builtin/replay.c
 msgid "replaying merge commits is not supported yet!"
-msgstr "目前还不支持重放到合并提交!"
+msgstr "目前还不支持重放合并提交!"
+
+#: builtin/replay.c
+#, c-format
+msgid "failed to update ref '%s': %s"
+msgstr "无法更新引用 '%s':%s"
+
+#: builtin/replay.c
+#, c-format
+msgid "failed to commit ref transaction: %s"
+msgstr "无法提交引用事务:%s"
 
 #: builtin/repo.c
 #, c-format
@@ -15350,10 +15439,18 @@
 msgstr "和 --format=nul 同义"
 
 #: builtin/repo.c
+msgid "print all keys/values"
+msgstr "打印所有键/值"
+
+#: builtin/repo.c
 msgid "unsupported output format"
 msgstr "不支持的输出格式"
 
 #: builtin/repo.c
+msgid "--all and <key> cannot be used together"
+msgstr "--all 不能和 <key> 一起使用"
+
+#: builtin/repo.c
 msgid "References"
 msgstr "引用"
 
@@ -15375,7 +15472,7 @@
 
 #: builtin/repo.c
 msgid "Others"
-msgstr "其它"
+msgstr "其他"
 
 #: builtin/repo.c
 msgid "Reachable objects"
@@ -15394,6 +15491,14 @@
 msgstr "数据对象"
 
 #: builtin/repo.c
+msgid "Inflated size"
+msgstr "展开后大小"
+
+#: builtin/repo.c
+msgid "Disk size"
+msgstr "磁盘大小"
+
+#: builtin/repo.c
 msgid "Repository structure"
 msgstr "仓库结构"
 
@@ -15431,7 +15536,7 @@
 
 #: builtin/reset.c
 msgid "git reset [-q] [<tree-ish>] [--] <pathspec>..."
-msgstr "git reset [-q] [<树对象>] [--] <路径表达式>..."
+msgstr "git reset [-q] [<树对象>] [--] <路径规格>..."
 
 #: builtin/reset.c
 msgid ""
@@ -15440,7 +15545,7 @@
 
 #: builtin/reset.c
 msgid "git reset --patch [<tree-ish>] [--] [<pathspec>...]"
-msgstr "git reset --patch [<树对象>] [--] [<路径表达式>...]"
+msgstr "git reset --patch [<树对象>] [--] [<路径规格>...]"
 
 #: builtin/reset.c
 msgid "mixed"
@@ -15532,13 +15637,13 @@
 #: builtin/reset.c
 #, c-format
 msgid "Cannot do %s reset with paths."
-msgstr "不能带路径进行%s重置。"
+msgstr "无法带路径进行%s重置。"
 
 #  译者:汉字之间无空格,故删除%s前后空格
 #: builtin/reset.c
 #, c-format
 msgid "%s reset is not allowed in a bare repository"
-msgstr "不能对纯仓库进行%s重置"
+msgstr "不允许对纯仓库进行%s重置"
 
 #: builtin/reset.c
 msgid "Unstaged changes after reset:"
@@ -15554,11 +15659,11 @@
 #: builtin/reset.c
 #, c-format
 msgid "Could not reset index file to revision '%s'."
-msgstr "不能重置索引文件至版本 '%s'。"
+msgstr "无法重置索引文件至版本 '%s'。"
 
 #: builtin/reset.c
 msgid "Could not write new index file."
-msgstr "不能写入新的索引文件。"
+msgstr "无法写入新的索引文件。"
 
 #: builtin/rev-list.c
 #, c-format
@@ -15636,7 +15741,7 @@
 #: builtin/rev-parse.c
 #, c-format
 msgid "not a gitdir '%s'"
-msgstr "不是一个git目录 '%s'"
+msgstr "不是一个 git 目录 '%s'"
 
 #: builtin/rev-parse.c
 msgid "--git-path requires an argument"
@@ -15683,7 +15788,7 @@
 
 #: builtin/rev-parse.c
 msgid "Could not read the index"
-msgstr "不能读取索引"
+msgstr "无法读取索引"
 
 #: builtin/rev-parse.c
 #, c-format
@@ -15725,15 +15830,15 @@
 
 #: builtin/revert.c
 msgid "end revert or cherry-pick sequence"
-msgstr "终止反转或拣选操作"
+msgstr "结束还原或拣选操作"
 
 #: builtin/revert.c
 msgid "resume revert or cherry-pick sequence"
-msgstr "继续反转或拣选操作"
+msgstr "继续还原或拣选操作"
 
 #: builtin/revert.c
 msgid "cancel revert or cherry-pick sequence"
-msgstr "取消反转或拣选操作"
+msgstr "取消还原或拣选操作"
 
 #: builtin/revert.c
 msgid "skip current commit and continue"
@@ -15865,7 +15970,7 @@
 
 #: builtin/rm.c
 msgid "please stage your changes to .gitmodules or stash them to proceed"
-msgstr "请将您的修改暂存到 .gitmodules 中或贮藏后再继续"
+msgstr "请将您的修改暂存到 .gitmodules 中或储藏后再继续"
 
 #: builtin/rm.c
 #, c-format
@@ -15875,7 +15980,7 @@
 #: builtin/rm.c
 #, c-format
 msgid "git rm: unable to remove %s"
-msgstr "git rm:不能删除 %s"
+msgstr "git rm:无法删除 %s"
 
 #: builtin/send-pack.c
 msgid ""
@@ -16033,11 +16138,11 @@
 
 #: builtin/show-branch.c
 msgid "show possible merge bases"
-msgstr "显示可能合并的基线"
+msgstr "显示可能的合并基线"
 
 #: builtin/show-branch.c
 msgid "show refs unreachable from any other ref"
-msgstr "显示没有任何引用的的引用"
+msgstr "显示任何其他引用都不可达的引用"
 
 #: builtin/show-branch.c
 msgid "show commits in topological order"
@@ -16087,8 +16192,8 @@
 #, c-format
 msgid "cannot handle more than %d rev."
 msgid_plural "cannot handle more than %d revs."
-msgstr[0] "不能处理 %d 个以上的版本。"
-msgstr[1] "不能处理 %d 个以上的版本。"
+msgstr[0] "无法处理 %d 个以上的版本。"
+msgstr[1] "无法处理 %d 个以上的版本。"
 
 #: builtin/show-branch.c
 #, c-format
@@ -16098,7 +16203,7 @@
 #: builtin/show-branch.c
 #, c-format
 msgid "cannot find commit %s (%s)"
-msgstr "不能找到提交 %s(%s)"
+msgstr "无法找到提交 %s(%s)"
 
 #: builtin/show-index.c
 msgid "hash-algorithm"
@@ -16158,7 +16263,7 @@
 
 #: builtin/show-ref.c
 msgid "dereference tags into object IDs"
-msgstr "转换标签到对象 ID"
+msgstr "将标签解引用为对象 ID"
 
 #: builtin/show-ref.c
 msgid "only show SHA1 hash using <n> digits"
@@ -16207,7 +16312,7 @@
 #: builtin/sparse-checkout.c
 #, c-format
 msgid "unable to fdopen %s"
-msgstr "不能 fdopen %s"
+msgstr "无法 fdopen %s"
 
 #: builtin/sparse-checkout.c
 msgid "failed to initialize worktree config"
@@ -16363,11 +16468,11 @@
 
 #: builtin/sparse-checkout.c
 msgid "when used with --rules-file interpret patterns as cone mode patterns"
-msgstr "通过 --rules-file 选项传递的模型将被作为锥形(稀疏检出模型)进行解析"
+msgstr "通过 --rules-file 选项传递的模式将被作为锥形模式(稀疏检出)进行解析"
 
 #: builtin/sparse-checkout.c
 msgid "use patterns in <file> instead of the current ones."
-msgstr "从 <文件> 参数中读取模式,而不是从标准输入"
+msgstr "使用 <文件> 中的模式替代当前模式。"
 
 #: builtin/stash.c
 msgid "git stash list [<log-options>]"
@@ -16379,23 +16484,23 @@
 "options>] [<stash>]"
 msgstr ""
 "git stash show [-u | --include-untracked | --only-untracked] [<差异选项>] [<"
-"贮存>]"
+"储藏>]"
 
 #: builtin/stash.c
 msgid "git stash drop [-q | --quiet] [<stash>]"
-msgstr "git stash drop [-q | --quiet] [<贮存>]"
+msgstr "git stash drop [-q | --quiet] [<储藏>]"
 
 #: builtin/stash.c
 msgid "git stash pop [--index] [-q | --quiet] [<stash>]"
-msgstr "git stash pop [--index] [-q | --quiet] [<贮存>]"
+msgstr "git stash pop [--index] [-q | --quiet] [<储藏>]"
 
 #: builtin/stash.c
 msgid "git stash apply [--index] [-q | --quiet] [<stash>]"
-msgstr "git stash apply [--index] [-q | --quiet] [<贮存>]"
+msgstr "git stash apply [--index] [-q | --quiet] [<储藏>]"
 
 #: builtin/stash.c
 msgid "git stash branch <branchname> [<stash>]"
-msgstr "git stash branch <分支名> [<stash>]"
+msgstr "git stash branch <分支名> [<储藏>]"
 
 #: builtin/stash.c
 msgid "git stash store [(-m | --message) <message>] [-q | --quiet] <commit>"
@@ -16412,7 +16517,7 @@
 msgstr ""
 "git stash [push [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q "
 "| --quiet]\n"
-"          [-u | --include-untracked] [-a | --all] [(-m | --message <消息>]\n"
+"          [-u | --include-untracked] [-a | --all] [(-m | --message) <消息>]\n"
 "          [--pathspec-from-file=<文件> [--pathspec-file-nul]]\n"
 "          [--] [<路径规格>...]]"
 
@@ -16432,7 +16537,7 @@
 
 #: builtin/stash.c
 msgid "git stash export (--print | --to-ref <ref>) [<stash>...]"
-msgstr "git stash export (--print | --to-ref <引用>) [<贮藏>...]"
+msgstr "git stash export (--print | --to-ref <引用>) [<储藏>...]"
 
 #: builtin/stash.c
 msgid "git stash import <commit>"
@@ -16441,11 +16546,11 @@
 #: builtin/stash.c
 #, c-format
 msgid "'%s' is not a stash-like commit"
-msgstr "'%s' 不像是一个贮藏提交"
+msgstr "'%s' 不像是一个储藏提交"
 
 #: builtin/stash.c
 msgid "No stash entries found."
-msgstr "未发现贮藏条目。"
+msgstr "未发现储藏条目。"
 
 #: builtin/stash.c
 #, c-format
@@ -16474,7 +16579,7 @@
 
 #: builtin/stash.c
 msgid "cannot apply a stash in the middle of a merge"
-msgstr "无法在合并过程中应用贮藏"
+msgstr "无法在合并过程中应用储藏"
 
 #: builtin/stash.c
 #, c-format
@@ -16487,7 +16592,7 @@
 
 #: builtin/stash.c
 msgid "could not save index tree"
-msgstr "不能保存索引树"
+msgstr "无法保存索引树"
 
 #: builtin/stash.c
 #, c-format
@@ -16496,11 +16601,11 @@
 
 #: builtin/stash.c
 msgid "Index was not unstashed."
-msgstr "索引未从贮藏中恢复。"
+msgstr "索引未从储藏中恢复。"
 
 #: builtin/stash.c
 msgid "could not restore untracked files from stash"
-msgstr "无法从贮藏条目中恢复未跟踪文件"
+msgstr "无法从储藏条目中恢复未跟踪文件"
 
 #: builtin/stash.c
 msgid "attempt to recreate the index"
@@ -16514,16 +16619,16 @@
 #: builtin/stash.c
 #, c-format
 msgid "%s: Could not drop stash entry"
-msgstr "%s:无法丢弃贮藏条目"
+msgstr "%s:无法丢弃储藏条目"
 
 #: builtin/stash.c
 #, c-format
 msgid "'%s' is not a stash reference"
-msgstr "'%s' 不是一个贮藏引用"
+msgstr "'%s' 不是一个储藏引用"
 
 #: builtin/stash.c
 msgid "The stash entry is kept in case you need it again."
-msgstr "贮藏条目被保留以备您再次需要。"
+msgstr "储藏条目被保留以备您再次需要。"
 
 #: builtin/stash.c
 msgid "No branch name specified"
@@ -16539,11 +16644,11 @@
 
 #: builtin/stash.c
 msgid "include untracked files in the stash"
-msgstr "在贮藏中包含未跟踪文件"
+msgstr "在储藏中包含未跟踪文件"
 
 #: builtin/stash.c
 msgid "only show untracked files in the stash"
-msgstr "仅显示贮藏中的未跟踪文件"
+msgstr "仅显示储藏中的未跟踪文件"
 
 #: builtin/stash.c
 #, c-format
@@ -16552,7 +16657,7 @@
 
 #: builtin/stash.c
 msgid "stash message"
-msgstr "贮藏说明"
+msgstr "储藏说明"
 
 #: builtin/stash.c
 msgid "\"git stash store\" requires one <commit> argument"
@@ -16588,7 +16693,7 @@
 
 #: builtin/stash.c
 msgid "Cannot record working tree state"
-msgstr "不能记录工作区状态"
+msgstr "无法记录工作区状态"
 
 #: builtin/stash.c
 msgid "Can't use --patch and --include-untracked or --all at the same time"
@@ -16608,7 +16713,7 @@
 
 #: builtin/stash.c
 msgid "Cannot initialize stash"
-msgstr "无法初始化贮藏"
+msgstr "无法初始化储藏"
 
 #: builtin/stash.c
 msgid "Cannot save the current status"
@@ -16629,11 +16734,11 @@
 
 #: builtin/stash.c
 msgid "stash staged changes only"
-msgstr "只贮藏暂存的变更"
+msgstr "只储藏暂存的变更"
 
 #: builtin/stash.c
 msgid "stash in patch mode"
-msgstr "以补丁模式贮藏"
+msgstr "以补丁模式储藏"
 
 #: builtin/stash.c
 msgid "quiet mode"
@@ -16641,7 +16746,7 @@
 
 #: builtin/stash.c
 msgid "include untracked files in stash"
-msgstr "贮藏中包含未跟踪文件"
+msgstr "储藏中包含未跟踪文件"
 
 #: builtin/stash.c
 msgid "include ignore files"
@@ -16650,7 +16755,7 @@
 #: builtin/stash.c
 #, c-format
 msgid "cannot parse commit %s"
-msgstr "不能解析提交 %s"
+msgstr "无法解析提交 %s"
 
 #: builtin/stash.c
 #, c-format
@@ -16674,7 +16779,7 @@
 #: builtin/stash.c
 #, c-format
 msgid "%s is not a valid exported stash commit"
-msgstr "%s 不是一个有效的且已导出的贮藏提交"
+msgstr "%s 不是一个有效的且已导出的储藏提交"
 
 #: builtin/stash.c
 #, c-format
@@ -16684,7 +16789,7 @@
 #: builtin/stash.c
 #, c-format
 msgid "found stash commit %s without expected prefix"
-msgstr "发现贮藏提交 %s 缺少预期前缀"
+msgstr "发现储藏提交 %s 缺少预期前缀"
 
 #: builtin/stash.c
 #, c-format
@@ -16694,7 +16799,7 @@
 #: builtin/stash.c
 #, c-format
 msgid "%s does not look like a stash commit"
-msgstr "%s 看起来不像是一个贮藏提交"
+msgstr "%s 看起来不像是一个储藏提交"
 
 #: builtin/stash.c
 #, c-format
@@ -16704,7 +16809,7 @@
 #: builtin/stash.c
 #, c-format
 msgid "cannot save the stash for %s"
-msgstr "无法保存 %s 的贮藏"
+msgstr "无法保存 %s 的储藏"
 
 #: builtin/stash.c
 msgid "unable to write base commit"
@@ -16713,7 +16818,7 @@
 #: builtin/stash.c
 #, c-format
 msgid "unable to find stash entry %s"
-msgstr "无法找到贮藏条目 %s"
+msgstr "无法找到储藏条目 %s"
 
 #: builtin/stash.c
 msgid "print the object ID instead of writing it to a ref"
@@ -16863,7 +16968,7 @@
 #: builtin/submodule--helper.c
 #, c-format
 msgid "couldn't hash object from '%s'"
-msgstr "不能从 '%s' 创建哈希对象"
+msgstr "无法从 '%s' 创建哈希对象"
 
 #: builtin/submodule--helper.c
 #, c-format
@@ -16892,7 +16997,7 @@
 
 #: builtin/submodule--helper.c
 msgid "could not fetch a revision for HEAD"
-msgstr "不能为 HEAD 获取一个版本"
+msgstr "无法为 HEAD 获取一个版本"
 
 #: builtin/submodule--helper.c
 #, c-format
@@ -16946,7 +17051,7 @@
 #: builtin/submodule--helper.c
 #, c-format
 msgid "could not create empty submodule directory %s"
-msgstr "不能创建空的子模组目录 %s"
+msgstr "无法创建空的子模组目录 %s"
 
 #: builtin/submodule--helper.c
 #, c-format
@@ -16991,17 +17096,17 @@
 #: builtin/submodule--helper.c
 #, c-format
 msgid "submodule '%s' cannot add alternate: %s"
-msgstr "子模组 '%s' 不能添加仓库备选:%s"
+msgstr "子模组 '%s' 无法添加仓库备选:%s"
 
 #: builtin/submodule--helper.c
 #, c-format
 msgid "Value '%s' for submodule.alternateErrorStrategy is not recognized"
-msgstr "不能识别 submodule.alternateErrorStrategy 的取值 '%s'"
+msgstr "无法识别 submodule.alternateErrorStrategy 的取值 '%s'"
 
 #: builtin/submodule--helper.c
 #, c-format
 msgid "Value '%s' for submodule.alternateLocation is not recognized"
-msgstr "不能识别 submodule.alternateLocation 的取值 '%s'"
+msgstr "无法识别 submodule.alternateLocation 的取值 '%s'"
 
 #: builtin/submodule--helper.c submodule.c
 #, c-format
@@ -17378,7 +17483,7 @@
 msgid "'%s' already exists in the index and is not a submodule"
 msgstr "'%s' 已经存在于索引中且不是一个子模组"
 
-#: builtin/submodule--helper.c read-cache.c
+#: builtin/submodule--helper.c object-file.c
 #, c-format
 msgid "'%s' does not have a commit checked out"
 msgstr "'%s' 没有检出一个提交"
@@ -17550,7 +17655,7 @@
 
 #: builtin/tag.c
 msgid "bad object type."
-msgstr "坏的对象类型。"
+msgstr "错误的对象类型。"
 
 #: builtin/tag.c
 msgid "no tag message?"
@@ -17913,7 +18018,7 @@
 
 #: builtin/update-index.c
 msgid "Untracked cache disabled"
-msgstr "缓存未跟踪文件被禁用"
+msgstr "未跟踪文件缓存已禁用"
 
 #: builtin/update-index.c
 msgid ""
@@ -17926,7 +18031,7 @@
 #: builtin/update-index.c
 #, c-format
 msgid "Untracked cache enabled for '%s'"
-msgstr "缓存未跟踪文件在 '%s' 启用"
+msgstr "已为 '%s' 启用未跟踪文件缓存"
 
 #: builtin/update-index.c
 msgid "core.fsmonitor is unset; set it if you really want to enable fsmonitor"
@@ -18110,7 +18215,7 @@
 #: builtin/worktree.c
 #, c-format
 msgid "Removing %s/%s: %s"
-msgstr "删除 %s/%s: %s"
+msgstr "删除 %s/%s:%s"
 
 #: builtin/worktree.c
 msgid "report pruned working trees"
@@ -18166,7 +18271,7 @@
 #: builtin/worktree.c
 #, c-format
 msgid "could not create directory of '%s'"
-msgstr "不能创建目录 '%s'"
+msgstr "无法创建目录 '%s'"
 
 #: builtin/worktree.c
 msgid "initializing"
@@ -18204,7 +18309,7 @@
 "HEAD path: '%s'\n"
 "HEAD contents: '%s'"
 msgstr ""
-"HEAD 指向了一个无用的(或孤立的)引用。\n"
+"HEAD 指向了一个无效的(或孤立的)引用。\n"
 "HEAD 路径: '%s'\n"
 "HEAD 内容: '%s'"
 
@@ -18218,7 +18323,7 @@
 
 #: builtin/worktree.c
 msgid "checkout <branch> even if already checked out in other worktree"
-msgstr "检出 <分支>,即使已经被检出到其它工作区"
+msgstr "检出 <分支>,即使已经被检出到其他工作区"
 
 #: builtin/worktree.c
 msgid "create a new branch"
@@ -18280,7 +18385,7 @@
 
 #: builtin/worktree.c
 msgid "add 'prunable' annotation to worktrees older than <time>"
-msgstr "向早于 <时间> 的工作区添添加“可修剪”注释"
+msgstr "向早于 <时间> 的工作区添加“可修剪”注释"
 
 #: builtin/worktree.c
 msgid "terminate records with a NUL character"
@@ -18312,7 +18417,7 @@
 
 #: builtin/worktree.c
 msgid "working trees containing submodules cannot be moved or removed"
-msgstr "不能移动或删除包含子模组的工作区"
+msgstr "无法移动或删除包含子模组的工作区"
 
 #: builtin/worktree.c
 msgid "force move even if worktree is dirty or locked"
@@ -18425,12 +18530,17 @@
 #: bundle-uri.c
 #, c-format
 msgid "could not parse bundle list key %s with value '%s'"
-msgstr "不能解析归档包列表键 %s 的值 '%s'"
+msgstr "无法解析捆绑包列表键 %s 的值 '%s'"
 
 #: bundle-uri.c
 #, c-format
 msgid "bundle list at '%s' has no mode"
-msgstr "在 '%s' 的归档包列表没有模式"
+msgstr "在 '%s' 的捆绑包列表没有模式"
+
+#: bundle-uri.c
+#, c-format
+msgid "bundle list at '%s': bundle '%s' has no uri"
+msgstr "在 '%s' 的捆绑包列表中,捆绑包 '%s' 没有 URI"
 
 #: bundle-uri.c
 msgid "failed to create temporary file"
@@ -18438,12 +18548,12 @@
 
 #: bundle-uri.c
 msgid "insufficient capabilities"
-msgstr "缺乏能力"
+msgstr "能力不足"
 
 #: bundle-uri.c
 #, c-format
 msgid "file downloaded from '%s' is not a bundle"
-msgstr "下载自 '%s' 的文件不是归档包"
+msgstr "下载自 '%s' 的文件不是捆绑包"
 
 #: bundle-uri.c
 msgid "failed to store maximum creation token"
@@ -18452,7 +18562,7 @@
 #: bundle-uri.c
 #, c-format
 msgid "unrecognized bundle mode from URI '%s'"
-msgstr "不可辨认的归档包模式来自 URI '%s'"
+msgstr "不可辨认的捆绑包模式来自 URI '%s'"
 
 #: bundle-uri.c
 #, c-format
@@ -18461,39 +18571,44 @@
 
 #: bundle-uri.c
 #, c-format
+msgid "bundle '%s' has no uri"
+msgstr "捆绑包 '%s' 没有 URI"
+
+#: bundle-uri.c
+#, c-format
 msgid "failed to download bundle from URI '%s'"
-msgstr "无法从 URI '%s' 下载归档包"
+msgstr "无法从 URI '%s' 下载捆绑包"
 
 #: bundle-uri.c
 #, c-format
 msgid "file at URI '%s' is not a bundle or bundle list"
-msgstr "位于 URI '%s' 的文件不是归档包或归档包列表"
+msgstr "位于 URI '%s' 的文件不是捆绑包或捆绑包列表"
 
 #: bundle-uri.c
 #, c-format
 msgid "bundle-uri: unexpected argument: '%s'"
-msgstr "bundle-uri: 意外的参数:'%s'"
+msgstr "bundle-uri:意外的参数:'%s'"
 
 #: bundle-uri.c
 msgid "bundle-uri: expected flush after arguments"
-msgstr "bundle-uri: 在参数之后应有一个 flush"
+msgstr "bundle-uri:在参数之后应有一个 flush"
 
 #: bundle-uri.c
 msgid "bundle-uri: got an empty line"
-msgstr "bundle-uri: 获得了空行"
+msgstr "bundle-uri:获得了空行"
 
 #: bundle-uri.c
 msgid "bundle-uri: line is not of the form 'key=value'"
-msgstr "bundle-uri: 行不是以 'key=value' 格式"
+msgstr "bundle-uri:行不是 'key=value' 格式"
 
 #: bundle-uri.c
 msgid "bundle-uri: line has empty key or value"
-msgstr "bundle-uri: 行有空的键或值"
+msgstr "bundle-uri:行有空的键或值"
 
 #: bundle.c
 #, c-format
 msgid "unrecognized bundle hash algorithm: %s"
-msgstr "未能识别的归档包哈希算法:%s"
+msgstr "未能识别的捆绑包哈希算法:%s"
 
 #: bundle.c
 #, c-format
@@ -18503,7 +18618,7 @@
 #: bundle.c
 #, c-format
 msgid "'%s' does not look like a v2 or v3 bundle file"
-msgstr "'%s' 不像是一个 v2 或 v3 版本的归档包文件"
+msgstr "'%s' 不像是一个 v2 或 v3 版本的捆绑包文件"
 
 #: bundle.c
 #, c-format
@@ -18524,37 +18639,37 @@
 #, c-format
 msgid "The bundle contains this ref:"
 msgid_plural "The bundle contains these %<PRIuMAX> refs:"
-msgstr[0] "这个归档包中含有这个引用:"
-msgstr[1] "这个归档包中含有 %<PRIuMAX> 个引用:"
+msgstr[0] "这个捆绑包中含有这个引用:"
+msgstr[1] "这个捆绑包中含有 %<PRIuMAX> 个引用:"
 
 #: bundle.c
 msgid "The bundle records a complete history."
-msgstr "这个归档包记录一个完整历史。"
+msgstr "这个捆绑包记录一个完整历史。"
 
 #: bundle.c
 #, c-format
 msgid "The bundle requires this ref:"
 msgid_plural "The bundle requires these %<PRIuMAX> refs:"
-msgstr[0] "这个归档包需要这个引用:"
-msgstr[1] "这个归档包需要 %<PRIuMAX> 个引用:"
+msgstr[0] "这个捆绑包需要这个引用:"
+msgstr[1] "这个捆绑包需要 %<PRIuMAX> 个引用:"
 
 #: bundle.c
 #, c-format
 msgid "The bundle uses this hash algorithm: %s"
-msgstr "该归档包使用的哈希算法:%s"
+msgstr "该捆绑包使用的哈希算法:%s"
 
 #: bundle.c
 #, c-format
 msgid "The bundle uses this filter: %s"
-msgstr "归档包使用了过滤器:%s"
+msgstr "捆绑包使用了过滤器:%s"
 
 #: bundle.c
 msgid "unable to dup bundle descriptor"
-msgstr "无法复制归档包描述符"
+msgstr "无法复制捆绑包描述符"
 
 #: bundle.c
 msgid "Could not spawn pack-objects"
-msgstr "不能生成 pack-objects 进程"
+msgstr "无法生成 pack-objects 进程"
 
 #: bundle.c
 msgid "pack-objects died"
@@ -18568,21 +18683,21 @@
 #: bundle.c
 #, c-format
 msgid "unsupported bundle version %d"
-msgstr "不支持的归档包版本 %d"
+msgstr "不支持的捆绑包版本 %d"
 
 #: bundle.c
 #, c-format
 msgid "cannot write bundle version %d with algorithm %s"
-msgstr "不能写入,归档包版本 %d 不支持算法 %s"
+msgstr "无法写入,捆绑包版本 %d 不支持算法 %s"
 
 #: bundle.c
 msgid "Refusing to create empty bundle."
-msgstr "不能创建空的归档包。"
+msgstr "无法创建空的捆绑包。"
 
 #: bundle.c
 #, c-format
 msgid "cannot create '%s'"
-msgstr "不能创建 '%s'"
+msgstr "无法创建 '%s'"
 
 #: bundle.c
 msgid "index-pack died"
@@ -18719,7 +18834,7 @@
 
 #: command-list.h
 msgid "Copy files from the index to the working tree"
-msgstr "从索引拷贝文件到工作区"
+msgstr "从索引复制文件到工作区"
 
 #: command-list.h
 msgid "Find commits yet to be applied to upstream"
@@ -18851,7 +18966,7 @@
 
 #: command-list.h
 msgid "Output information on each ref"
-msgstr "对每一个引用输出信息 "
+msgstr "对每一个引用输出信息"
 
 #: command-list.h
 msgid "Run a Git command on a list of repositories"
@@ -18903,11 +19018,11 @@
 
 #: command-list.h
 msgid "Push objects over HTTP/DAV to another repository"
-msgstr "通过 HTTP/DAV 推送对象另一个仓库"
+msgstr "通过 HTTP/DAV 推送对象到另一个仓库"
 
 #: command-list.h
 msgid "Send a collection of patches from stdin to an IMAP folder"
-msgstr "从标准输入将一组补丁发送到IMAP文件夹"
+msgstr "从标准输入将一组补丁发送到 IMAP 文件夹"
 
 #: command-list.h
 msgid "Build pack index file for an existing packed archive"
@@ -18975,7 +19090,7 @@
 
 #: command-list.h
 msgid "The standard helper program to use with git-merge-index"
-msgstr "与 git-merge-index 一起使用的标准向导程序"
+msgstr "与 git-merge-index 一起使用的标准辅助程序"
 
 #: command-list.h
 msgid "Perform merge without touching index or working tree"
@@ -19008,7 +19123,7 @@
 
 #: command-list.h
 msgid "Add or inspect object notes"
-msgstr "添加或检查对象注释"
+msgstr "添加或检查对象注解"
 
 #: command-list.h
 msgid "Import from and submit to Perforce repositories"
@@ -19024,7 +19139,7 @@
 
 #: command-list.h
 msgid "Pack heads and tags for efficient repository access"
-msgstr "打包头和标签以实现高效的仓库访问"
+msgstr "打包分支和标签以实现高效的仓库访问"
 
 #: command-list.h
 msgid "Compute unique ID for a patch"
@@ -19048,7 +19163,7 @@
 
 #: command-list.h
 msgid "Applies a quilt patchset onto the current branch"
-msgstr "将一个 quilt 补丁集应用到当前分支。"
+msgstr "将一个 quilt 补丁集应用到当前分支"
 
 #: command-list.h
 msgid "Compare two commit ranges (e.g. two versions of a branch)"
@@ -19076,7 +19191,7 @@
 
 #: command-list.h
 msgid "Manage set of tracked repositories"
-msgstr "管理已跟踪仓库"
+msgstr "管理已跟踪的仓库集合"
 
 #: command-list.h
 msgid "Pack unpacked objects in a repository"
@@ -19088,7 +19203,7 @@
 
 #: command-list.h
 msgid "EXPERIMENTAL: Replay commits on a new base, works with bare repos too"
-msgstr "试验中:基于一个新基线重放提交,同样适用于纯仓库"
+msgstr "试验中:在新基线上重放提交,也适用于纯仓库"
 
 #: command-list.h
 msgid "Retrieve information about the repository"
@@ -19096,15 +19211,15 @@
 
 #: command-list.h
 msgid "Generates a summary of pending changes"
-msgstr "生成待定更改的摘要"
+msgstr "生成待处理变更的摘要"
 
 #: command-list.h
 msgid "Reuse recorded resolution of conflicted merges"
-msgstr "重用冲突合并的解决方案记录"
+msgstr "重用已记录的冲突合并解决方案"
 
 #: command-list.h
-msgid "Reset current HEAD to the specified state"
-msgstr "重置当前 HEAD 到指定状态"
+msgid "Set `HEAD` or the index to a known state"
+msgstr "将 `HEAD` 或索引设置为已知状态"
 
 #: command-list.h
 msgid "Restore working tree files"
@@ -19112,7 +19227,7 @@
 
 #: command-list.h
 msgid "Lists commit objects in reverse chronological order"
-msgstr "按时间顺序列出提交对象"
+msgstr "按时间逆序列出提交对象"
 
 #: command-list.h
 msgid "Pick out and massage parameters"
@@ -19120,7 +19235,7 @@
 
 #: command-list.h
 msgid "Revert some existing commits"
-msgstr "回退一些现存提交"
+msgstr "还原一些现有提交"
 
 #: command-list.h
 msgid "Remove files from the working tree and from the index"
@@ -19144,11 +19259,11 @@
 
 #: command-list.h
 msgid "Restricted login shell for Git-only SSH access"
-msgstr "只允许 Git SSH 访问的受限登录shell"
+msgstr "只允许 Git SSH 访问的受限登录 shell"
 
 #: command-list.h
 msgid "Summarize 'git log' output"
-msgstr "'git log' 输出摘要"
+msgstr "汇总 'git log' 输出"
 
 #: command-list.h
 msgid "Show various types of objects"
@@ -19177,7 +19292,7 @@
 
 #: command-list.h
 msgid "Stash the changes in a dirty working directory away"
-msgstr "贮藏脏工作区中的修改"
+msgstr "储藏脏工作区中的修改"
 
 #: command-list.h
 msgid "Show the working tree status"
@@ -19193,7 +19308,7 @@
 
 #: command-list.h
 msgid "Bidirectional operation between a Subversion repository and Git"
-msgstr "Subersion 仓库和 Git 之间的双向操作"
+msgstr "Subversion 仓库和 Git 之间的双向操作"
 
 #: command-list.h
 msgid "Switch branches"
@@ -19225,7 +19340,7 @@
 
 #: command-list.h
 msgid "Update auxiliary info file to help dumb servers"
-msgstr "更新辅助信息文件以帮助哑协议服务"
+msgstr "更新辅助信息文件以帮助哑协议服务器"
 
 #: command-list.h
 msgid "Send archive back to git-archive"
@@ -19237,15 +19352,15 @@
 
 #: command-list.h
 msgid "Show a Git logical variable"
-msgstr "显示一个Git逻辑变量"
+msgstr "显示一个 Git 逻辑变量"
 
 #: command-list.h
 msgid "Check the GPG signature of commits"
-msgstr "检查 GPG 提交签名"
+msgstr "检查提交的 GPG 签名"
 
 #: command-list.h
 msgid "Validate packed Git archive files"
-msgstr "校验打包的Git存仓文件"
+msgstr "校验打包的 Git 归档文件"
 
 #: command-list.h
 msgid "Check the GPG signature of tags"
@@ -19281,7 +19396,7 @@
 
 #: command-list.h
 msgid "Providing usernames and passwords to Git"
-msgstr "为 Git 提供用户名和口令"
+msgstr "为 Git 提供用户名和密码"
 
 #: command-list.h
 msgid "Git for CVS users"
@@ -19293,7 +19408,7 @@
 
 #: command-list.h
 msgid "A useful minimum set of commands for Everyday Git"
-msgstr "每一天 Git 的一组有用的最小命令集合"
+msgstr "日常 Git 的一组有用的最小命令集合"
 
 #: command-list.h
 msgid "Frequently asked questions about using Git"
@@ -19301,7 +19416,7 @@
 
 #: command-list.h
 msgid "The bundle file format"
-msgstr "归档包文件格式"
+msgstr "捆绑包文件格式"
 
 #: command-list.h
 msgid "Chunk-based file formats"
@@ -19349,11 +19464,11 @@
 
 #: command-list.h
 msgid "Git namespaces"
-msgstr "Git 名字空间"
+msgstr "Git 命名空间"
 
 #: command-list.h
 msgid "Protocol v0 and v1 capabilities"
-msgstr "协议 v0 和 v1 能力"
+msgstr "协议 v0 和 v1 功能"
 
 #: command-list.h
 msgid "Things common to various protocols"
@@ -19365,15 +19480,15 @@
 
 #: command-list.h
 msgid "How packs are transferred over-the-wire"
-msgstr "包在线路中传输的方式"
+msgstr "包在网络上传输的方式"
 
 #: command-list.h
 msgid "Git Wire Protocol, Version 2"
-msgstr "Git 传输协议,第二版"
+msgstr "Git 传输协议,第 2 版"
 
 #: command-list.h
 msgid "Helper programs to interact with remote repositories"
-msgstr "与远程仓库交互的助手程序"
+msgstr "与远程仓库交互的辅助程序"
 
 #: command-list.h
 msgid "Git Repository Layout"
@@ -19381,11 +19496,11 @@
 
 #: command-list.h
 msgid "Specifying revisions and ranges for Git"
-msgstr "指定 Git 的版本和版本范围"
+msgstr "指定 Git 的版本和范围"
 
 #: command-list.h
 msgid "Mounting one repository inside another"
-msgstr "将一个仓库安装到另外一个仓库中"
+msgstr "将一个仓库挂载到另一个仓库中"
 
 #: command-list.h
 msgid "A tutorial introduction to Git"
@@ -19397,7 +19512,7 @@
 
 #: command-list.h
 msgid "Git web interface (web frontend to Git repositories)"
-msgstr "Git web 界面(Git 仓库的 web 前端)"
+msgstr "Git Web 界面(Git 仓库的 Web 前端)"
 
 #: command-list.h
 msgid "An overview of recommended workflows with Git"
@@ -19440,7 +19555,7 @@
 msgid ""
 "ignoring too-small changed-path chunk (%<PRIuMAX> < %<PRIuMAX>) in commit-"
 "graph file"
-msgstr "忽略提交图文件中过小的更改路径块(%<PRIuMAX> < %<PRIuMAX>)"
+msgstr "忽略提交图文件中过小的变更路径块(%<PRIuMAX> < %<PRIuMAX>)"
 
 #: commit-graph.c
 #, c-format
@@ -19554,7 +19669,7 @@
 
 #: commit-graph.c
 msgid "Computing commit changed paths Bloom filters"
-msgstr "计算提交变更路径的布隆过滤器"
+msgstr "正在计算提交变更路径的布隆过滤器"
 
 #: commit-graph.c
 msgid "Collecting referenced commits"
@@ -19579,7 +19694,7 @@
 
 #: commit-graph.c
 msgid "Finding commits for commit graph among packed objects"
-msgstr "正在打包对象中查找提交图的提交"
+msgstr "正在已打包对象中查找提交图的提交"
 
 #: commit-graph.c
 msgid "Finding extra edges in commit graph"
@@ -19602,8 +19717,8 @@
 #, c-format
 msgid "Writing out commit graph in %d pass"
 msgid_plural "Writing out commit graph in %d passes"
-msgstr[0] "正在用 %d 步写出提交图"
-msgstr[1] "正在用 %d 步写出提交图"
+msgstr[0] "正在用 %d 次写出提交图"
+msgstr[1] "正在用 %d 次写出提交图"
 
 #: commit-graph.c
 msgid "unable to open commit-graph chain file"
@@ -19625,7 +19740,7 @@
 #: commit-graph.c
 #, c-format
 msgid "cannot merge graph %s, too many commits: %<PRIuMAX>"
-msgstr "无法合并提交图 %s, 提交过多:%<PRIuMAX>"
+msgstr "无法合并提交图 %s,提交过多:%<PRIuMAX>"
 
 #: commit-graph.c
 msgid "Scanning merged commits"
@@ -19637,7 +19752,7 @@
 
 #: commit-graph.c
 msgid "attempting to write a commit-graph, but 'core.commitGraph' is disabled"
-msgstr "正尝试写提交图,但是 'core.commitGraph' 被禁用"
+msgstr "正在尝试写入提交图,但 'core.commitGraph' 被禁用"
 
 #: commit-graph.c
 #, c-format
@@ -19648,7 +19763,7 @@
 
 #: commit-graph.c
 msgid "too many commits to write graph"
-msgstr "提交太多不能画图"
+msgstr "提交太多,无法写入提交图"
 
 #: commit-graph.c
 msgid "the commit-graph file has incorrect checksum and is likely corrupt"
@@ -19718,7 +19833,7 @@
 #: commit-reach.c sequencer.c
 #, c-format
 msgid "could not parse commit %s"
-msgstr "不能解析提交 %s"
+msgstr "无法解析提交 %s"
 
 #: commit.c
 #, c-format
@@ -19737,10 +19852,10 @@
 "\"git config set advice.graftFileDeprecated false\""
 msgstr ""
 "对 <GIT_DIR>/info/grafts 的支持已过时,并将在\n"
-"未来的Git版本中被移除。\n"
+"未来的 Git 版本中被移除。\n"
 "\n"
 "请使用 \"git replace --convert-graft-file\" 将\n"
-"(提交)移植转换为替换引用。\n"
+"嫁接转换为替换引用。\n"
 "\n"
 "运行 \"git config set advice.graftFileDeprecated false\"\n"
 "可关闭本消息"
@@ -19768,7 +19883,7 @@
 #: commit.c
 #, c-format
 msgid "Commit %s has a good GPG signature by %s\n"
-msgstr "提交 %s 有一个来自 %s 的好的 GPG 签名。\n"
+msgstr "提交 %s 有一个来自 %s 的有效 GPG 签名。\n"
 
 #: commit.c
 msgid ""
@@ -19777,7 +19892,7 @@
 "variable i18n.commitEncoding to the encoding your project uses.\n"
 msgstr ""
 "警告:提交说明不符合 UTF-8 字符编码。\n"
-"您可以通过修补提交来改正提交说明,或者将配置变量 i18n.commitEncoding\n"
+"您可以通过修订提交来改正提交说明,或者将配置变量 i18n.commitEncoding\n"
 "设置为您项目所用的字符编码。\n"
 
 #: compat/compiler.h
@@ -19791,7 +19906,7 @@
 #: compat/disk.h
 #, c-format
 msgid "could not determine free disk size for '%s'"
-msgstr "不能确定 '%s' 的空余磁盘空间"
+msgstr "无法确定 '%s' 的空余磁盘空间"
 
 #: compat/disk.h
 #, c-format
@@ -19801,7 +19916,7 @@
 #: compat/fsmonitor/fsm-health-win32.c
 #, c-format
 msgid "[GLE %ld] health thread could not open '%ls'"
-msgstr "[GLE %ld] 健康监测线程不能打开 '%ls'"
+msgstr "[GLE %ld] 健康监测线程无法打开 '%ls'"
 
 #: compat/fsmonitor/fsm-health-win32.c
 #, c-format
@@ -19811,7 +19926,7 @@
 #: compat/fsmonitor/fsm-health-win32.c compat/fsmonitor/fsm-listen-win32.c
 #, c-format
 msgid "could not convert to wide characters: '%s'"
-msgstr "不能转换至宽字符:'%s'"
+msgstr "无法转换至宽字符:'%s'"
 
 #: compat/fsmonitor/fsm-health-win32.c
 #, c-format
@@ -19831,7 +19946,7 @@
 #: compat/fsmonitor/fsm-ipc-darwin.c
 #, c-format
 msgid "Invalid path: %s"
-msgstr "无效路径: %s"
+msgstr "无效路径:%s"
 
 #: compat/fsmonitor/fsm-listen-darwin.c
 msgid "Unable to create FSEventStream."
@@ -19864,7 +19979,7 @@
 #: compat/fsmonitor/fsm-listen-win32.c
 #, c-format
 msgid "GetOverlappedResult failed on '%s' [GLE %ld]"
-msgstr "GetOverlappedResult 失败于 '%s' [GLE %ld]"
+msgstr "GetOverlappedResult 在 '%s' 上失败 [GLE %ld]"
 
 #: compat/fsmonitor/fsm-listen-win32.c
 #, c-format
@@ -19899,12 +20014,12 @@
 #: compat/fsmonitor/fsm-path-utils-win32.c
 #, c-format
 msgid "[GLE %ld] unable to get protocol information for '%ls'"
-msgstr "[GLE %ld] 无法获取 '%ls' 的协议信息 "
+msgstr "[GLE %ld] 无法获取 '%ls' 的协议信息"
 
 #: compat/mingw.c
 #, c-format
 msgid "failed to copy SID (%ld)"
-msgstr "无法拷贝 SID (%ld)"
+msgstr "无法复制 SID (%ld)"
 
 #: compat/mingw.c
 #, c-format
@@ -19941,7 +20056,7 @@
 
 #: compat/regex/regcomp.c
 msgid "Invalid back reference"
-msgstr "无效的反向索引"
+msgstr "无效的反向引用"
 
 #: compat/regex/regcomp.c
 msgid "Unmatched [ or [^"
@@ -20066,7 +20181,7 @@
 "remote URLs cannot be configured in file directly or indirectly included by "
 "includeIf.hasconfig:remote.*.url"
 msgstr ""
-"远程 URL 不能在文件中配置,不管直接地还是通过 "
+"远程 URL 无法在文件中配置,不管直接地还是通过 "
 "includeIf.hasconfig:remote.*.url 间接地包含"
 
 #: config.c
@@ -20082,7 +20197,7 @@
 #: config.c
 #, c-format
 msgid "missing environment variable '%s' for configuration '%.*s'"
-msgstr "缺少环境变量 '%s' 于配置 '%.*s' "
+msgstr "配置 '%3$.*2$s' 缺少环境变量 '%1$s'"
 
 #: config.c
 #, c-format
@@ -20111,7 +20226,7 @@
 #: config.c
 #, c-format
 msgid "bogus config parameter: %s"
-msgstr "伪配置参数:%s"
+msgstr "无效的配置参数:%s"
 
 #: config.c
 #, c-format
@@ -20121,7 +20236,7 @@
 #: config.c
 #, c-format
 msgid "bogus count in %s"
-msgstr "%s 中错误计数"
+msgstr "%s 中计数错误"
 
 #: config.c
 #, c-format
@@ -20214,7 +20329,7 @@
 #: config.c
 #, c-format
 msgid "bad boolean config value '%s' for '%s'"
-msgstr "'%2$s' 的错误的布尔取值 '%1$s'"
+msgstr "配置变量 '%2$s' 的布尔值 '%1$s' 无效"
 
 #: config.c
 #, c-format
@@ -20239,7 +20354,7 @@
 #: config.c
 #, c-format
 msgid "unable to resolve config blob '%s'"
-msgstr "不能解析配置对象 '%s'"
+msgstr "无法解析配置对象 '%s'"
 
 #: config.c
 msgid "unable to parse command-line config"
@@ -20334,7 +20449,7 @@
 #: config.c
 #, c-format
 msgid "could not lock config file %s"
-msgstr "不能锁定配置文件 %s"
+msgstr "无法锁定配置文件 %s"
 
 #: config.c
 #, c-format
@@ -20354,7 +20469,7 @@
 #: config.c
 #, c-format
 msgid "unable to mmap '%s'%s"
-msgstr "不能 mmap '%s'%s"
+msgstr "无法 mmap '%s'%s"
 
 #: config.c
 #, c-format
@@ -20364,12 +20479,12 @@
 #: config.c
 #, c-format
 msgid "could not write config file %s"
-msgstr "不能写入配置文件 %s"
+msgstr "无法写入配置文件 %s"
 
 #: config.c
 #, c-format
 msgid "could not set '%s' to '%s'"
-msgstr "不能设置 '%s' 为 '%s'"
+msgstr "无法设置 '%s' 为 '%s'"
 
 #: config.c
 #, c-format
@@ -20379,7 +20494,7 @@
 #: config.c
 #, c-format
 msgid "refusing to work with overly long line in '%s' on line %<PRIuMAX>"
-msgstr "拒绝支持内容过长的行,位于文件 '%s' 中的第 %<PRIuMAX> 行"
+msgstr "拒绝处理内容过长的行,位于文件 '%s' 中的第 %<PRIuMAX> 行"
 
 #: config.c
 #, c-format
@@ -20450,7 +20565,7 @@
 #: connect.c
 #, c-format
 msgid "error on bundle-uri response line %d: %s"
-msgstr "于 bundle-uri 响应行 %d 发生错误:%s"
+msgstr "在 bundle-uri 响应行 %d 发生错误:%s"
 
 #: connect.c
 msgid "expected flush after bundle-uri listing"
@@ -20535,11 +20650,11 @@
 #: connect.c
 #, c-format
 msgid "cannot start proxy %s"
-msgstr "不能启动代理 %s"
+msgstr "无法启动代理 %s"
 
 #: connect.c
 msgid "no path specified; see 'git help pull' for valid url syntax"
-msgstr "未指定路径,执行 'git help pull' 查看有效的 url 语法"
+msgstr "未指定路径,执行 'git help pull' 查看有效的 URL 语法"
 
 #: connect.c
 msgid "newline is forbidden in git:// hosts and repo paths"
@@ -20568,7 +20683,7 @@
 
 #: connected.c
 msgid "Could not run 'git rev-list'"
-msgstr "不能执行 'git rev-list'"
+msgstr "无法执行 'git rev-list'"
 
 #: connected.c
 msgid "failed write to rev-list"
@@ -20593,7 +20708,7 @@
 msgid ""
 "in the working copy of '%s', CRLF will be replaced by LF the next time Git "
 "touches it"
-msgstr "在 '%s' 的工作拷贝中,下次 Git 接触时 CRLF 将被 LF 替换"
+msgstr "在 '%s' 的工作拷贝中,下次 Git 处理时 CRLF 将被 LF 替换"
 
 #: convert.c
 #, c-format
@@ -20605,7 +20720,7 @@
 msgid ""
 "in the working copy of '%s', LF will be replaced by CRLF the next time Git "
 "touches it"
-msgstr "在 '%s' 的工作拷贝中,下次 Git 接触时 LF 将被 CRLF 替换"
+msgstr "在 '%s' 的工作拷贝中,下次 Git 处理时 LF 将被 CRLF 替换"
 
 #: convert.c
 #, c-format
@@ -20631,7 +20746,7 @@
 "The file '%s' is missing a byte order mark (BOM). Please use UTF-%sBE or UTF-"
 "%sLE (depending on the byte order) as working-tree-encoding."
 msgstr ""
-"文件 '%s' 缺失一个字节顺序标记(BOM)。请使用 UTF-%sBE or UTF-%sLE(取决于字"
+"文件 '%s' 缺失一个字节顺序标记(BOM)。请使用 UTF-%sBE 或 UTF-%sLE(取决于字"
 "节序)作为工作区编码。"
 
 #: convert.c
@@ -20642,22 +20757,22 @@
 #: convert.c
 #, c-format
 msgid "encoding '%s' from %s to %s and back is not the same"
-msgstr "将'%s' 的编码从 %s 到 %s 来回转换不一致"
+msgstr "将 '%s' 的编码从 %s 到 %s 来回转换不一致"
 
 #: convert.c
 #, c-format
 msgid "cannot fork to run external filter '%s'"
-msgstr "不能 fork 以执行外部过滤器 '%s'"
+msgstr "无法 fork 以执行外部过滤器 '%s'"
 
 #: convert.c
 #, c-format
 msgid "cannot feed the input to external filter '%s'"
-msgstr "不能将输入传递给外部过滤器 '%s'"
+msgstr "无法将输入传递给外部过滤器 '%s'"
 
 #: convert.c
 #, c-format
 msgid "external filter '%s' failed %d"
-msgstr "外部过滤器 '%s' 失败码 %d"
+msgstr "外部过滤器 '%s' 失败,退出码 %d"
 
 #: convert.c
 #, c-format
@@ -20719,12 +20834,12 @@
 #: credential.c
 #, c-format
 msgid "url has no scheme: %s"
-msgstr "URL 没有 scheme:%s"
+msgstr "URL 缺少协议:%s"
 
 #: credential.c
 #, c-format
 msgid "credential url cannot be parsed: %s"
-msgstr "不能解析凭据 URL:%s"
+msgstr "无法解析凭据 URL:%s"
 
 #: daemon.c
 #, c-format
@@ -20799,8 +20914,8 @@
 #, c-format
 msgid "%s, %<PRIuMAX> month ago"
 msgid_plural "%s, %<PRIuMAX> months ago"
-msgstr[0] "%s %<PRIuMAX> 个月前"
-msgstr[1] "%s %<PRIuMAX> 个月前"
+msgstr[0] "%s,%<PRIuMAX> 个月前"
+msgstr[1] "%s,%<PRIuMAX> 个月前"
 
 #: date.c
 #, c-format
@@ -20816,7 +20931,7 @@
 #: delta-islands.c
 #, c-format
 msgid "bad tree object %s"
-msgstr "坏的树对象 %s"
+msgstr "无效的树对象 %s"
 
 #: delta-islands.c
 #, c-format
@@ -20826,12 +20941,12 @@
 #: delta-islands.c
 #, c-format
 msgid "island regex from config has too many capture groups (max=%d)"
-msgstr "来自 config 的数据岛正则表达式有太多的捕获组(最多 %d 个)"
+msgstr "来自配置的数据岛正则表达式有太多的捕获组(最多 %d 个)"
 
 #: delta-islands.c
 #, c-format
 msgid "Marked %d islands, done.\n"
-msgstr "已标记 %d 个数据岛,结束。\n"
+msgstr "已标记 %d 个数据岛,完成。\n"
 
 #: diagnose.c
 #, c-format
@@ -20860,7 +20975,7 @@
 #: diagnose.c
 #, c-format
 msgid "could not add directory '%s' to archiver"
-msgstr "无法添加目录 '%s' 至归档器"
+msgstr "无法添加目录 '%s' 到归档器"
 
 #: diagnose.c
 msgid "failed to write archive"
@@ -20880,7 +20995,7 @@
 
 #: diff-lib.c
 msgid "unable to get HEAD"
-msgstr "不能解析 HEAD"
+msgstr "无法解析 HEAD"
 
 #: diff-lib.c
 msgid "no merge base found"
@@ -20912,7 +21027,7 @@
 msgid ""
 "Limiting comparison with pathspecs is only supported if both paths are "
 "directories."
-msgstr "仅当两个路径均为目录时,才支持使用路径表达式限制比较范围。"
+msgstr "仅当两个路径均为目录时,才支持使用路径规格限制比较范围。"
 
 #  译者:注意保持前导空格
 #: diff.c
@@ -20947,18 +21062,13 @@
 msgid ""
 "color-moved-ws: allow-indentation-change cannot be combined with other "
 "whitespace modes"
-msgstr "color-moved-ws:allow-indentation-change 不能与其它空白字符模式共用"
+msgstr "color-moved-ws:allow-indentation-change 不能与其他空白字符模式共用"
 
 #: diff.c
 #, c-format
 msgid "Unknown value for 'diff.submodule' config variable: '%s'"
 msgstr "配置变量 'diff.submodule' 未知的取值:'%s'"
 
-#: diff.c merge-ort.c transport.c
-#, c-format
-msgid "unknown value for config '%s': %s"
-msgstr "配置 '%s' 未知的取值:%s"
-
 #: diff.c
 #, c-format
 msgid ""
@@ -20975,12 +21085,12 @@
 
 #: diff.c
 msgid "--follow requires exactly one pathspec"
-msgstr "--follow 明确要求只跟一个路径规格"
+msgstr "--follow 要求恰好一个路径规格"
 
 #: diff.c
 #, c-format
 msgid "pathspec magic not supported by --follow: %s"
-msgstr "指定 --follow: %s 的同时,不支持在路径规格中使用神奇前缀"
+msgstr "--follow 不支持在路径规格中使用魔法前缀:%s"
 
 #: diff.c parse-options.c
 #, c-format
@@ -21030,7 +21140,7 @@
 #: diff.c
 #, c-format
 msgid "unable to resolve '%s'"
-msgstr "不能解析 '%s'"
+msgstr "无法解析 '%s'"
 
 #: diff.c
 #, c-format
@@ -21045,12 +21155,12 @@
 #: diff.c
 #, c-format
 msgid "bad --color-moved argument: %s"
-msgstr "坏的 --color-moved 参数:%s"
+msgstr "无效的 --color-moved 参数:%s"
 
 #: diff.c
 #, c-format
 msgid "invalid mode '%s' in --color-moved-ws"
-msgstr "--color-moved-ws 中的无效模式 '%s' "
+msgstr "--color-moved-ws 中的无效模式 '%s'"
 
 #: diff.c
 #, c-format
@@ -21078,11 +21188,11 @@
 #: diff.c
 #, c-format
 msgid "bad --word-diff argument: %s"
-msgstr "坏的 --word-diff 参数:%s"
+msgstr "无效的 --word-diff 参数:%s"
 
 #: diff.c
 msgid "Diff output format options"
-msgstr "差异输出格式化选项"
+msgstr "差异输出格式选项"
 
 #: diff.c
 msgid "generate patch"
@@ -21163,15 +21273,15 @@
 
 #: diff.c
 msgid "generate diffstat with a given width"
-msgstr "使用给定的长度生成差异统计"
+msgstr "使用给定的宽度生成差异统计"
 
 #: diff.c
 msgid "generate diffstat with a given name width"
-msgstr "使用给定的文件名长度生成差异统计"
+msgstr "使用给定的文件名宽度生成差异统计"
 
 #: diff.c
 msgid "generate diffstat with a given graph width"
-msgstr "使用给定的图形长度生成差异统计"
+msgstr "使用给定的图形宽度生成差异统计"
 
 #: diff.c
 msgid "<count>"
@@ -21212,8 +21322,7 @@
 "do not munge pathnames and use NULs as output field terminators in --raw or "
 "--numstat"
 msgstr ""
-"在 --raw 或者 --numstat 中,不对路径字符转码并使用 NUL 字符做为输出字段的分隔"
-"符"
+"在 --raw 或者 --numstat 中,不对路径名转码并使用 NUL 字符作为输出字段的分隔符"
 
 #: diff.c
 msgid "<prefix>"
@@ -21221,11 +21330,11 @@
 
 #: diff.c
 msgid "show the given source prefix instead of \"a/\""
-msgstr "显示给定的源前缀取代 \"a/\""
+msgstr "显示给定的源前缀替代 \"a/\""
 
 #: diff.c
 msgid "show the given destination prefix instead of \"b/\""
-msgstr "显示给定的目标前缀取代 \"b/\""
+msgstr "显示给定的目标前缀替代 \"b/\""
 
 #: diff.c
 msgid "prepend an additional prefix to every line of output"
@@ -21269,7 +21378,7 @@
 
 #: diff.c
 msgid "break complete rewrite changes into pairs of delete and create"
-msgstr "将完全重写的变更打破为成对的删除和创建"
+msgstr "将完全重写的变更拆分为成对的删除和创建"
 
 #: diff.c
 msgid "detect renames"
@@ -21285,7 +21394,7 @@
 
 #: diff.c
 msgid "use unmodified files as source to find copies"
-msgstr "使用未修改的文件做为发现拷贝的源"
+msgstr "使用未修改的文件作为发现拷贝的源"
 
 #: diff.c
 msgid "disable rename detection"
@@ -21293,7 +21402,7 @@
 
 #: diff.c
 msgid "use empty blobs as rename source"
-msgstr "使用空的数据对象做为重命名的源"
+msgstr "使用空的数据对象作为重命名的源"
 
 #: diff.c
 msgid "continue listing the history of a file beyond renames"
@@ -21339,11 +21448,11 @@
 
 #: diff.c
 msgid "ignore changes whose all lines match <regex>"
-msgstr "忽略所有行都和正则表达式匹配的变更"
+msgstr "忽略所有行都匹配 <正则> 的变更"
 
 #: diff.c
 msgid "heuristic to shift diff hunk boundaries for easy reading"
-msgstr "启发式转换差异边界以便阅读"
+msgstr "启发式调整差异块边界以便阅读"
 
 #: diff.c
 msgid "generate diff using the \"patience diff\" algorithm"
@@ -21387,7 +21496,7 @@
 
 #: diff.c
 msgid "Other diff options"
-msgstr "其它差异选项"
+msgstr "其他差异选项"
 
 #: diff.c
 msgid "when run from subdir, exclude changes outside and show relative paths"
@@ -21395,7 +21504,7 @@
 
 #: diff.c
 msgid "treat all files as text"
-msgstr "把所有文件当做文本处理"
+msgstr "把所有文件当作文本处理"
 
 #: diff.c
 msgid "swap two inputs, reverse the diff"
@@ -21411,7 +21520,7 @@
 
 #: diff.c
 msgid "allow an external diff helper to be executed"
-msgstr "允许执行一个外置的差异助手"
+msgstr "允许执行一个外部差异辅助程序"
 
 #: diff.c
 msgid "run external text conversion filters when comparing binary files"
@@ -21439,7 +21548,7 @@
 
 #: diff.c
 msgid "treat 'git add -N' entries as real in the index"
-msgstr "将索引中 'git add -N' 条目当做真实的"
+msgstr "将索引中 'git add -N' 条目当作真实的"
 
 #: diff.c
 msgid "<string>"
@@ -21459,11 +21568,11 @@
 
 #: diff.c
 msgid "show all changes in the changeset with -S or -G"
-msgstr "显示使用 -S 或 -G 的变更集的所有变更"
+msgstr "显示由 -S 或 -G 选中的变更集内的全部变更"
 
 #: diff.c
 msgid "treat <string> in -S as extended POSIX regular expression"
-msgstr "将 -S 的 <string> 当做扩展的 POSIX 正则表达式"
+msgstr "将 -S 的 <string> 当作扩展的 POSIX 正则表达式"
 
 #: diff.c
 msgid "control the order in which files appear in the output"
@@ -21536,7 +21645,7 @@
 
 #: diffcore-rename.c
 msgid "Performing inexact rename detection"
-msgstr "正在进行非精确的重命名探测"
+msgstr "正在进行非精确的重命名检测"
 
 #: diffcore-rotate.c
 #, c-format
@@ -21546,7 +21655,7 @@
 #: dir.c
 #, c-format
 msgid "pathspec '%s' did not match any file(s) known to git"
-msgstr "路径规格 '%s' 未匹配任何 git 已知文件"
+msgstr "路径规格 '%s' 未匹配任何 Git 已知文件"
 
 #: dir.c
 #, c-format
@@ -21565,12 +21674,12 @@
 
 #: dir.c
 msgid "disabling cone pattern matching"
-msgstr "停用锥形模式匹配"
+msgstr "停用锥形模式(稀疏检出)匹配"
 
 #: dir.c
 #, c-format
 msgid "cannot use %s as an exclude file"
-msgstr "不能将 %s 用作排除文件"
+msgstr "无法将 %s 用作排除文件"
 
 #: dir.c
 msgid "failed to get kernel name and information"
@@ -21578,7 +21687,7 @@
 
 #: dir.c
 msgid "untracked cache is disabled on this system or location"
-msgstr "缓存未跟踪文件在本系统或位置中被禁用"
+msgstr "本系统或位置已禁用未跟踪文件缓存"
 
 #: dir.c
 msgid ""
@@ -21596,12 +21705,12 @@
 #: dir.c
 #, c-format
 msgid "could not create directories for %s"
-msgstr "不能为 %s 创建目录"
+msgstr "无法为 %s 创建目录"
 
 #: dir.c
 #, c-format
 msgid "could not migrate git directory from '%s' to '%s'"
-msgstr "不能从 '%s' 迁移 git 目录到 '%s'"
+msgstr "无法从 '%s' 迁移 Git 目录到 '%s'"
 
 #: editor.c
 #, c-format
@@ -21611,12 +21720,12 @@
 #: editor.c sequencer.c wrapper.c
 #, c-format
 msgid "could not write to '%s'"
-msgstr "不能写入 '%s'"
+msgstr "无法写入 '%s'"
 
 #: editor.c
 #, c-format
 msgid "could not edit '%s'"
-msgstr "不能编辑 '%s'"
+msgstr "无法编辑 '%s'"
 
 #: entry.c
 msgid "Filtering content"
@@ -21625,12 +21734,12 @@
 #: entry.c
 #, c-format
 msgid "could not stat file '%s'"
-msgstr "不能对文件 '%s' 调用 stat"
+msgstr "无法对文件 '%s' 调用 stat"
 
 #: environment.c
 #, c-format
 msgid "bad git namespace path \"%s\""
-msgstr "错误的 git 名字空间路径 \"%s\""
+msgstr "错误的 git 命名空间路径 \"%s\""
 
 #: environment.c
 #, c-format
@@ -21669,7 +21778,7 @@
 
 #: environment.c
 msgid "core.fsyncObjectFiles is deprecated; use core.fsync instead"
-msgstr "core.fsyncObjectFiles 已经被弃用;取而代之使用 core.fsync"
+msgstr "core.fsyncObjectFiles 已经被弃用;改用 core.fsync"
 
 #: environment.c
 #, c-format
@@ -21706,7 +21815,7 @@
 msgstr ""
 "您正在尝试获取 %s,它位于提交图文件中,但不在对象数据库中。\n"
 "这可能是由于仓库损坏造成的。\n"
-"如果您尝试通过重新获取丢失的对象来修复此仓库损坏,请对丢失的对象使用 'git "
+"如果您尝试通过重新获取丢失的对象来修复此仓库损坏,请对缺失的对象执行 'git "
 "fetch --refetch'。"
 
 #: fetch-pack.c
@@ -21728,21 +21837,21 @@
 
 #: fetch-pack.c
 msgid "unable to write to remote"
-msgstr "无法写到远程"
+msgstr "无法写入远程"
 
 #: fetch-pack.c
 msgid "Server supports filter"
-msgstr "服务器支持 filter"
+msgstr "服务器支持过滤器"
 
 #: fetch-pack.c
 #, c-format
 msgid "invalid shallow line: %s"
-msgstr "无效的 shallow 信息:%s"
+msgstr "无效的 shallow 行:%s"
 
 #: fetch-pack.c
 #, c-format
 msgid "invalid unshallow line: %s"
-msgstr "无效的 unshallow 信息:%s"
+msgstr "无效的 unshallow 行:%s"
 
 #: fetch-pack.c
 #, c-format
@@ -21752,7 +21861,7 @@
 #: fetch-pack.c
 #, c-format
 msgid "no shallow found: %s"
-msgstr "未发现 shallow:%s"
+msgstr "未找到 shallow:%s"
 
 #: fetch-pack.c
 #, c-format
@@ -21794,11 +21903,11 @@
 
 #: fetch-pack.c
 msgid "fetch-pack: unable to fork off sideband demultiplexer"
-msgstr "fetch-pack:无法派生 sideband 多路输出"
+msgstr "fetch-pack:无法派生 sideband 多路分解器"
 
 #: fetch-pack.c
 msgid "protocol error: bad pack header"
-msgstr "协议错误:坏的包头"
+msgstr "协议错误:无效的包头"
 
 #: fetch-pack.c
 #, c-format
@@ -21816,12 +21925,12 @@
 
 #: fetch-pack.c
 msgid "error in sideband demultiplexer"
-msgstr "sideband 多路输出出错"
+msgstr "sideband 多路分解器出错"
 
 #: fetch-pack.c
 #, c-format
 msgid "Server version is %.*s"
-msgstr "服务器版本 %.*s"
+msgstr "服务器版本为 %.*s"
 
 #: fetch-pack.c
 #, c-format
@@ -21830,7 +21939,7 @@
 
 #: fetch-pack.c
 msgid "Server does not support shallow clients"
-msgstr "服务器不支持浅客户端"
+msgstr "服务器不支持浅克隆客户端"
 
 #: fetch-pack.c
 msgid "Server does not support --shallow-since"
@@ -21908,7 +22017,7 @@
 #: fetch-pack.c
 #, c-format
 msgid "expected no other sections to be sent after no '%s'"
-msgstr "在没有 '%s' 后不应该发送其它小节"
+msgstr "在未发送 '%s' 后不应该再发送其他小节"
 
 #: fetch-pack.c
 #, c-format
@@ -21936,11 +22045,11 @@
 
 #: fetch-pack.c
 msgid "no matching remote head"
-msgstr "没有匹配的远程分支"
+msgstr "没有匹配的远程头引用"
 
 #: fetch-pack.c
 msgid "unexpected 'ready' from remote"
-msgstr "来自远程的意外的 'ready'"
+msgstr "来自远程的意外 'ready'"
 
 #: fetch-pack.c
 #, c-format
@@ -21955,12 +22064,12 @@
 #: fsmonitor-ipc.c
 #, c-format
 msgid "fsmonitor_ipc__send_query: invalid path '%s'"
-msgstr "fsmonitor_ipc__send_query: 无效路径 '%s'"
+msgstr "fsmonitor_ipc__send_query:无效路径 '%s'"
 
 #: fsmonitor-ipc.c
 #, c-format
 msgid "fsmonitor_ipc__send_query: unspecified error on '%s'"
-msgstr "fsmonitor_ipc__send_query: 未知错误于 '%s'"
+msgstr "fsmonitor_ipc__send_query:在 '%s' 上发生未知错误"
 
 #: fsmonitor-ipc.c
 msgid "fsmonitor--daemon is not running"
@@ -21969,7 +22078,7 @@
 #: fsmonitor-ipc.c
 #, c-format
 msgid "could not send '%s' command to fsmonitor--daemon"
-msgstr "无法发送 '%s' 命令至 fsmonitor--daemon"
+msgstr "无法发送 '%s' 命令到 fsmonitor--daemon"
 
 #: fsmonitor-settings.c
 #, c-format
@@ -22011,8 +22120,8 @@
 msgstr ""
 "git [-v | --version] [-h | --help] [-C <路径>] [-c <名称>=<取值>]\n"
 "           [--exec-path[=<路径>]] [--html-path] [--man-path] [--info-path]\n"
-"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [\"--"
-"no-lazy-fetch]\n"
+"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-"
+"lazy-fetch]\n"
 "           [--no-optional-locks] [--no-advice] [--bare] [--git-dir=<路径>]\n"
 "           [--work-tree=<路径>] [--namespace=<名称>]\n"
 "           [--config-env=<名称>=<环境变量>] <命令> [<参数>]"
@@ -22052,7 +22161,7 @@
 #: git.c
 #, c-format
 msgid "no config key given for --config-env\n"
-msgstr "没有为 --config-env 提供配置名称\n"
+msgstr "没有为 --config-env 提供配置键名\n"
 
 #: git.c
 #, c-format
@@ -22075,8 +22184,8 @@
 "alias '%s' changes environment variables.\n"
 "You can use '!git' in the alias to do this"
 msgstr ""
-"别名 '%s' 修改环境变量。您可以使用在别名中\n"
-"使用 '!git'"
+"别名 '%s' 会修改环境变量。\n"
+"您可以在别名中使用 '!git' 来做到这一点"
 
 #: git.c
 #, c-format
@@ -22091,7 +22200,7 @@
 #: git.c
 #, c-format
 msgid "alias loop detected: expansion of '%s' does not terminate:%s"
-msgstr "检测到别名循环:'%s'的扩展未终止:%s"
+msgstr "检测到别名循环:'%s' 的扩展未终止:%s"
 
 #: git.c
 msgid "write failure on standard output"
@@ -22108,7 +22217,7 @@
 #: git.c
 #, c-format
 msgid "cannot handle %s as a builtin"
-msgstr "不能作为内置命令处理 %s"
+msgstr "无法作为内置命令处理 %s"
 
 #: git.c
 #, c-format
@@ -22122,7 +22231,7 @@
 #: git.c
 #, c-format
 msgid "expansion of alias '%s' failed; '%s' is not a git command\n"
-msgstr "展开别名命令 '%s' 失败,'%s' 不是一个 git 命令\n"
+msgstr "展开别名命令 '%s' 失败,'%s' 不是一个 Git 命令\n"
 
 #: git.c
 #, c-format
@@ -22131,7 +22240,7 @@
 
 #: gpg-interface.c
 msgid "could not create temporary file"
-msgstr "不能创建临时文件"
+msgstr "无法创建临时文件"
 
 #: gpg-interface.c
 #, c-format
@@ -22149,28 +22258,28 @@
 "ssh-keygen -Y find-principals/verify is needed for ssh signature "
 "verification (available in openssh version 8.2p1+)"
 msgstr ""
-"ssh 签名验证需要 ssh-keygen -Y find-principals/verify \n"
-"(openssh 8.2p1+ 版本可用)"
+"ssh 签名验证需要 ssh-keygen -Y find-principals/verify\n"
+"(OpenSSH 8.2p1+ 版本可用)"
 
 #: gpg-interface.c
 #, c-format
 msgid "ssh signing revocation file configured but not found: %s"
-msgstr "设置了 ssh 签名吊销文件但无法找到:%s"
+msgstr "已设置 SSH 签名吊销文件但无法找到:%s"
 
 #: gpg-interface.c
 #, c-format
 msgid "bad/incompatible signature '%s'"
-msgstr "坏的/不兼容的签名 '%s‘"
+msgstr "无效/不兼容的签名 '%s'"
 
 #: gpg-interface.c
 #, c-format
 msgid "failed to get the ssh fingerprint for key '%s'"
-msgstr "无法得到密钥 '%s' 的 ssh 指纹"
+msgstr "无法得到密钥 '%s' 的 SSH 指纹"
 
 #: gpg-interface.c
 #, c-format
 msgid "failed to get the ssh fingerprint for key %s"
-msgstr "无法得到密钥 '%s' 的 ssh 指纹"
+msgstr "无法得到密钥 %s 的 SSH 指纹"
 
 #: gpg-interface.c
 msgid ""
@@ -22219,12 +22328,12 @@
 msgid ""
 "ssh-keygen -Y sign is needed for ssh signing (available in openssh version "
 "8.2p1+)"
-msgstr "ssh 签名需要 ssh-keygen -Y sign (openssh 8.2p1+ 版本可用)"
+msgstr "ssh 签名需要 ssh-keygen -Y sign(OpenSSH 8.2p1+ 版本可用)"
 
 #: gpg-interface.c
 #, c-format
 msgid "failed reading ssh signing data buffer from '%s'"
-msgstr "无法从 '%s' 读入 ssh 签名数据"
+msgstr "无法从 '%s' 读入 SSH 签名数据"
 
 #: graph.c
 #, c-format
@@ -22275,23 +22384,23 @@
 
 #: help.c
 msgid "Ancillary Commands / Manipulators"
-msgstr "辅助命令/操作者"
+msgstr "辅助命令/操作类"
 
 #: help.c
 msgid "Ancillary Commands / Interrogators"
-msgstr "辅助命令/询问者"
+msgstr "辅助命令/查询类"
 
 #: help.c
 msgid "Interacting with Others"
-msgstr "与其它系统交互"
+msgstr "与其他系统交互"
 
 #: help.c
 msgid "Low-level Commands / Manipulators"
-msgstr "低级命令/操作者"
+msgstr "低级命令/操作类"
 
 #: help.c
 msgid "Low-level Commands / Interrogators"
-msgstr "低级命令/询问者"
+msgstr "低级命令/查询类"
 
 #: help.c
 msgid "Low-level Commands / Syncing Repositories"
@@ -22299,7 +22408,7 @@
 
 #: help.c
 msgid "Low-level Commands / Internal Helpers"
-msgstr "低级命令/内部助手"
+msgstr "低级命令/内部辅助程序"
 
 #: help.c
 msgid "User-facing repository, command and file interfaces"
@@ -22344,7 +22453,7 @@
 
 #: help.c
 msgid "See 'git help <command>' to read about a specific subcommand"
-msgstr "执行 'git help <command>' 来了解特定子命令"
+msgstr "执行 'git help <command>' 来了解特定子命令"
 
 #: help.c
 #, c-format
@@ -22362,7 +22471,7 @@
 
 #: help.c
 msgid "Uh oh. Your system reports no Git commands at all."
-msgstr "唉呀,您的系统中未发现 Git 命令。"
+msgstr "哎呀,您的系统中未发现 Git 命令。"
 
 #: help.c
 #, c-format
@@ -22416,10 +22525,10 @@
 "Did you mean one of these?"
 msgstr[0] ""
 "\n"
-"您指的是这个么?"
+"您指的是这个吗?"
 msgstr[1] ""
 "\n"
-"您指的是这其中的某一个么?"
+"您指的是这其中的某一个吗?"
 
 #: hook.c
 #, c-format
@@ -22432,7 +22541,7 @@
 
 #: http-fetch.c
 msgid "not a git repository"
-msgstr "不是 git 仓库"
+msgstr "不是 Git 仓库"
 
 #: http-fetch.c
 #, c-format
@@ -22465,7 +22574,7 @@
 #: http.c
 #, c-format
 msgid "Could not set SSL backend to '%s': cURL was built without SSL backends"
-msgstr "无法设置 SSL 后端为 '%s':cURL: cURL 没有使用 SSL 后端构建"
+msgstr "无法设置 SSL 后端为 '%s':cURL 没有使用 SSL 后端构建"
 
 #: http.c
 #, c-format
@@ -22474,7 +22583,7 @@
 
 #: http.c
 msgid "refusing to read cookies from http.cookiefile '-'"
-msgstr "拒绝从 http.cookiefile '-' 读取 cookies"
+msgstr "拒绝从 http.cookiefile '-' 读取 Cookie"
 
 #: http.c
 msgid "ignoring http.savecookies for empty http.cookiefile"
@@ -22487,7 +22596,7 @@
 "  asked for: %s\n"
 "   redirect: %s"
 msgstr ""
-"不能更新重定向的 url base:\n"
+"无法更新重定向后的 URL 基址:\n"
 "     请求:%s\n"
 "   重定向:%s"
 
@@ -22527,7 +22636,7 @@
 "  git config --global user.email \"you@example.com\"\n"
 "  git config --global user.name \"Your Name\"\n"
 "\n"
-"来设置您账号的缺省身份标识。\n"
+"来设置您账号的默认身份标识。\n"
 "如果仅在本仓库设置身份标识,则省略 --global 参数。\n"
 "\n"
 
@@ -22594,7 +22703,7 @@
 
 #: list-objects-filter-options.c
 msgid "sparse:path filters support has been dropped"
-msgstr "sparse:path 过滤器支持已被删除"
+msgstr "sparse:path 过滤器支持已被移除"
 
 #: list-objects-filter-options.c
 #, c-format
@@ -22634,7 +22743,7 @@
 #: list-objects-filter.c
 #, c-format
 msgid "unable to access sparse blob in '%s'"
-msgstr "不能访问 '%s' 中的稀疏数据对象"
+msgstr "无法访问 '%s' 中的稀疏数据对象"
 
 #: list-objects-filter.c
 #, c-format
@@ -22669,15 +22778,15 @@
 msgstr ""
 "无法创建 '%s.lock':%s。\n"
 "\n"
-"似乎另外一个 git 进程在这个仓库中运行,例如:'git commit' 命令打\n"
+"似乎另一个 Git 进程在这个仓库中运行,例如:'git commit' 命令打\n"
 "开了一个编辑器。请确认所有进程都已经关闭然后重试。如果仍然报错,\n"
-"可能之前有一个 git 进程在这个仓库中异常退出:\n"
+"可能之前有一个 Git 进程在这个仓库中异常退出:\n"
 "手动删除这个文件再继续。"
 
 #: lockfile.c
 #, c-format
 msgid "Unable to create '%s.lock': %s"
-msgstr "不能创建 '%s.lock':%s"
+msgstr "无法创建 '%s.lock':%s"
 
 #: log-tree.c
 msgid "unable to create temporary object directory"
@@ -22686,7 +22795,7 @@
 #: loose.c
 #, c-format
 msgid "could not write loose object index %s"
-msgstr "不能写入松散对象索引 %s"
+msgstr "无法写入松散对象索引 %s"
 
 #: loose.c
 #, c-format
@@ -22719,17 +22828,17 @@
 #: merge-ort-wrappers.c
 #, c-format
 msgid "Could not parse object '%s'"
-msgstr "不能解析对象 '%s'"
+msgstr "无法解析对象 '%s'"
 
 #: merge-ort.c
 #, c-format
 msgid "Failed to merge submodule %s (not checked out)"
-msgstr "无法合并子模组 %s (没有检出)"
+msgstr "无法合并子模组 %s(没有检出)"
 
 #: merge-ort.c
 #, c-format
 msgid "Failed to merge submodule %s (no merge base)"
-msgstr "无法合并子模组 %s (没有合并基线)"
+msgstr "无法合并子模组 %s(没有合并基线)"
 
 #: merge-ort.c
 #, c-format
@@ -22744,7 +22853,7 @@
 #: merge-ort.c
 #, c-format
 msgid "Failed to merge submodule %s (commits don't follow merge-base)"
-msgstr "无法合并子模组 %s (提交未跟随合并基线)"
+msgstr "无法合并子模组 %s(提交未跟随合并基线)"
 
 #: merge-ort.c
 #, c-format
@@ -22779,7 +22888,7 @@
 #: merge-ort.c
 #, c-format
 msgid "error: unable to add %s to database"
-msgstr "错误:不能添加 %s 至对象库"
+msgstr "错误:无法将 %s 添加到对象库"
 
 #: merge-ort.c
 #, c-format
@@ -22792,8 +22901,8 @@
 "CONFLICT (implicit dir rename): Existing file/dir at %s in the way of "
 "implicit directory rename(s) putting the following path(s) there: %s."
 msgstr ""
-"冲突(隐式目录重命名):处于隐式目录重命名的现存文件/目录 %s,将以下路径放"
-"在:%s。"
+"冲突(隐式目录重命名):隐式目录重命名要把以下路径放到 %s,但该处已有文件/目"
+"录:%s。"
 
 #: merge-ort.c
 #, c-format
@@ -22880,7 +22989,7 @@
 #: merge-ort.c
 #, c-format
 msgid "error: cannot read object %s"
-msgstr "错误:不能读取对象 %s"
+msgstr "错误:无法读取对象 %s"
 
 #: merge-ort.c
 #, c-format
@@ -22892,7 +23001,7 @@
 msgid ""
 "CONFLICT (file/directory): directory in the way of %s from %s; moving it to "
 "%s instead."
-msgstr "冲突(文件/目录):目录已存在于 %2$s 中的 %1$s,将其移动到 %3$s。"
+msgstr "冲突(文件/目录):目录阻挡了来自 %2$s 的 %1$s,改为将其移动到 %3$s。"
 
 #: merge-ort.c
 #, c-format
@@ -22909,8 +23018,8 @@
 "CONFLICT (distinct types): %s had different types on each side; renamed one "
 "of them so each can be recorded somewhere."
 msgstr ""
-"冲突(不同类型):%s 在两侧有不同的类型,将其中之一重命名以便它们能记录在不同"
-"位置。"
+"冲突(不同类型):%s 在两侧类型不同,已重命名其中之一,以便两者都能记录在某"
+"处。"
 
 #: merge-ort.c
 msgid "content"
@@ -23012,7 +23121,7 @@
 #: midx-write.c
 #, c-format
 msgid "could not parse line: %s"
-msgstr "不能解析行:%s"
+msgstr "无法解析行:%s"
 
 #: midx-write.c
 #, c-format
@@ -23021,7 +23130,7 @@
 
 #: midx-write.c
 msgid "could not load pack"
-msgstr "不能载入包"
+msgstr "无法载入包"
 
 #: midx-write.c
 #, c-format
@@ -23059,7 +23168,7 @@
 #: midx-write.c
 #, c-format
 msgid "cannot select preferred pack %s with no objects"
-msgstr "不能选择没有对象的首选包 %s"
+msgstr "无法选择没有对象的首选包 %s"
 
 #: midx-write.c
 #, c-format
@@ -23105,7 +23214,7 @@
 
 #: midx-write.c
 msgid "cannot expire packs from an incremental multi-pack-index"
-msgstr "增量多包索引中的包不能过期"
+msgstr "增量多包索引中的包无法过期"
 
 #: midx-write.c
 msgid "Counting referenced objects"
@@ -23121,11 +23230,11 @@
 
 #: midx-write.c
 msgid "could not start pack-objects"
-msgstr "不能开始 pack-objects"
+msgstr "无法开始 pack-objects"
 
 #: midx-write.c
 msgid "could not finish pack-objects"
-msgstr "不能结束 pack-objects"
+msgstr "无法结束 pack-objects"
 
 #: midx.c
 msgid "multi-pack-index OID fanout is of the wrong size"
@@ -23158,7 +23267,7 @@
 #: midx.c
 #, c-format
 msgid "multi-pack-index version %d not recognized"
-msgstr "multi-pack-index 版本 %d 不能被识别"
+msgstr "multi-pack-index 版本 %d 未被识别"
 
 #: midx.c
 #, c-format
@@ -23207,7 +23316,7 @@
 #: midx.c
 #, c-format
 msgid "invalid multi-pack-index chain: line '%s' not a hash"
-msgstr "无效的多包索引链:第 '%s' 行不是哈希值"
+msgstr "无效的多包索引链:行 '%s' 不是哈希值"
 
 #: midx.c
 msgid "unable to find all multi-pack index files"
@@ -23220,7 +23329,7 @@
 #: midx.c
 #, c-format
 msgid "bad pack-int-id: %u (%u total packs)"
-msgstr "错的 pack-int-id:%u(共有 %u 个包)"
+msgstr "无效的 pack-int-id:%u(共有 %u 个包)"
 
 #: midx.c
 msgid "MIDX does not contain the BTMP chunk"
@@ -23229,11 +23338,11 @@
 #: midx.c
 #, c-format
 msgid "could not load bitmapped pack %<PRIu32>"
-msgstr "不能打开已被位图索引的包 %<PRIu32>"
+msgstr "无法加载已被位图索引的包 %<PRIu32>"
 
 #: midx.c
 msgid "multi-pack-index stores a 64-bit offset, but off_t is too small"
-msgstr "多包索引存储一个64位偏移,但是 off_t 太小"
+msgstr "多包索引存储一个 64 位偏移,但是 off_t 太小"
 
 #: midx.c
 msgid "multi-pack-index large offset out of bounds"
@@ -23245,7 +23354,7 @@
 
 #: midx.c
 msgid "incorrect checksum"
-msgstr "不正确的校验码"
+msgstr "校验码不正确"
 
 #: midx.c
 msgid "Looking for referenced packfiles"
@@ -23253,7 +23362,7 @@
 
 #: midx.c
 msgid "the midx contains no oid"
-msgstr "midx 不包含 oid"
+msgstr "MIDX 不包含 OID"
 
 #: midx.c
 msgid "Verifying OID order in multi-pack-index"
@@ -23290,17 +23399,17 @@
 #: name-hash.c
 #, c-format
 msgid "unable to create lazy_dir thread: %s"
-msgstr "不能创建 lazy_dir 线程:%s"
+msgstr "无法创建 lazy_dir 线程:%s"
 
 #: name-hash.c
 #, c-format
 msgid "unable to create lazy_name thread: %s"
-msgstr "不能创建 lazy_name 线程:%s"
+msgstr "无法创建 lazy_name 线程:%s"
 
 #: name-hash.c
 #, c-format
 msgid "unable to join lazy_name thread: %s"
-msgstr "不能加入 lazy_name 线程:%s"
+msgstr "无法加入 lazy_name 线程:%s"
 
 #: notes-merge.c
 #, c-format
@@ -23309,23 +23418,23 @@
 "Please, use 'git notes merge --commit' or 'git notes merge --abort' to "
 "commit/abort the previous merge before you start a new notes merge."
 msgstr ""
-"您的前一次注释合并尚未结束(存在 %s)。\n"
-"在开始一个新的注释合并之前,请使用 'git notes merge --commit' 或者 'git "
+"您的前一次注解合并尚未结束(存在 %s)。\n"
+"在开始一个新的注解合并之前,请使用 'git notes merge --commit' 或者 'git "
 "notes merge --abort' 来提交/终止前一次合并。"
 
 #: notes-merge.c
 #, c-format
 msgid "You have not concluded your notes merge (%s exists)."
-msgstr "您尚未结束注释合并(存在 %s)。"
+msgstr "您尚未结束注解合并(存在 %s)。"
 
 #: notes-utils.c
 msgid "Cannot commit uninitialized/unreferenced notes tree"
-msgstr "不能提交未初始化/未引用的注解树"
+msgstr "无法提交未初始化/未引用的注解树"
 
 #: notes-utils.c
 #, c-format
 msgid "Bad notes.rewriteMode value: '%s'"
-msgstr "坏的 notes.rewriteMode 值:'%s'"
+msgstr "无效的 notes.rewriteMode 值:'%s'"
 
 #: notes-utils.c
 #, c-format
@@ -23339,16 +23448,16 @@
 #: notes-utils.c
 #, c-format
 msgid "Bad %s value: '%s'"
-msgstr "坏的 %s 值:'%s'"
+msgstr "无效的 %s 值:'%s'"
 
 #: object-file-convert.c
 msgid "failed to decode tree entry"
-msgstr "无法解码树对象"
+msgstr "无法解码树条目"
 
 #: object-file-convert.c
 #, c-format
 msgid "failed to map tree entry for %s"
-msgstr "无法为 %s 映射树对象"
+msgstr "无法为 %s 映射树条目"
 
 #: object-file-convert.c
 #, c-format
@@ -23412,7 +23521,7 @@
 #: object-file.c
 #, c-format
 msgid "unable to open %s"
-msgstr "不能打开 %s"
+msgstr "无法打开 %s"
 
 #: object-file.c
 #, c-format
@@ -23448,12 +23557,12 @@
 
 #: object-file.c
 msgid "unable to write loose object file"
-msgstr "不能写松散对象文件"
+msgstr "无法写入松散对象文件"
 
 #: object-file.c
 #, c-format
 msgid "unable to deflate new object %s (%d)"
-msgstr "不能压缩新对象 %s(%d)"
+msgstr "无法压缩新对象 %s(%d)"
 
 #: object-file.c
 #, c-format
@@ -23463,7 +23572,7 @@
 #: object-file.c
 #, c-format
 msgid "confused by unstable object source data for %s"
-msgstr "被 %s 的不稳定对象源数据搞糊涂了"
+msgstr "由于 %s 的对象源数据不稳定而无法处理"
 
 #: object-file.c
 #, c-format
@@ -23473,7 +23582,7 @@
 #: object-file.c
 #, c-format
 msgid "unable to stream deflate new object (%d)"
-msgstr "不能流式压缩新对象(%d)"
+msgstr "无法流式压缩新对象(%d)"
 
 #: object-file.c
 #, c-format
@@ -23488,7 +23597,7 @@
 #: object-file.c
 #, c-format
 msgid "cannot read object for %s"
-msgstr "不能读取对象 %s"
+msgstr "无法读取对象 %s"
 
 #: object-file.c
 #, c-format
@@ -23520,6 +23629,10 @@
 msgstr "%s:无法插入数据库"
 
 #: object-file.c
+msgid "cannot add a submodule of a different hash algorithm"
+msgstr "无法添加使用不同哈希算法的子模组"
+
+#: object-file.c
 #, c-format
 msgid "%s: unsupported file type"
 msgstr "%s:不支持的文件类型"
@@ -23532,7 +23645,7 @@
 #: object-file.c
 #, c-format
 msgid "unable to mmap %s"
-msgstr "不能 mmap %s"
+msgstr "无法 mmap %s"
 
 #: object-file.c
 #, c-format
@@ -23561,7 +23674,7 @@
 #: object-name.c
 #, c-format
 msgid "%s [bad object]"
-msgstr "%s [坏的对象]"
+msgstr "%s [无效的对象]"
 
 #. TRANSLATORS: This is a line of ambiguous commit
 #. object output. E.g.:
@@ -23598,7 +23711,7 @@
 #: object-name.c
 #, c-format
 msgid "%s [bad tag, could not parse it]"
-msgstr "%s [坏的标签,无法解析]"
+msgstr "%s [无效的标签,无法解析]"
 
 #. TRANSLATORS: This is a line of ambiguous <type>
 #. object output. E.g. "deadbeef tree".
@@ -23619,7 +23732,7 @@
 #: object-name.c
 #, c-format
 msgid "short object ID %s is ambiguous"
-msgstr "短对象ID %s 存在歧义"
+msgstr "短对象 ID %s 存在歧义"
 
 #. TRANSLATORS: The argument is the list of ambiguous
 #. objects composed in show_ambiguous_object(). See
@@ -23631,7 +23744,7 @@
 "The candidates are:\n"
 "%s"
 msgstr ""
-"候选者有:\n"
+"候选项有:\n"
 "%s"
 
 #: object-name.c
@@ -23646,14 +23759,14 @@
 "examine these refs and maybe delete them. Turn this message off by\n"
 "running \"git config set advice.objectNameWarning false\""
 msgstr ""
-"Git 通常不会创建一个以40个十六进制字符结尾的引用,因为当您只提供40\n"
+"Git 通常不会创建一个以 40 个十六进制字符结尾的引用,因为当您只提供 40\n"
 "个十六进制字符时将被忽略。这些引用可能被错误地创建。例如:\n"
 "\n"
 "  git switch -c $br $(git rev-parse ...)\n"
 "\n"
-"当 \"$br\" 某种原因空白时,一个40位十六进制的引用将被创建。请检查这些\n"
+"当 \"$br\" 由于某种原因为空时,一个 40 位十六进制的引用将被创建。请检查这些\n"
 "引用,可能需要删除它们。运行 \"git config set advice.objectNameWarning\n"
-"false\" 命令关闭本消息通知。"
+"false\" 来关闭本消息通知。"
 
 #: object-name.c
 #, c-format
@@ -23714,7 +23827,7 @@
 
 #: object-name.c
 msgid "relative path syntax can't be used outside working tree"
-msgstr "不能在工作区之外使用相对路径语法"
+msgstr "无法在工作区之外使用相对路径语法"
 
 #: object-name.c
 #, c-format
@@ -23744,7 +23857,7 @@
 #: object.c
 #, c-format
 msgid "unable to parse object: %s"
-msgstr "不能解析对象:%s"
+msgstr "无法解析对象:%s"
 
 #: object.c
 #, c-format
@@ -23801,7 +23914,7 @@
 #: odb.c
 #, c-format
 msgid "reference repository '%s' is grafted"
-msgstr "参考仓库 '%s' 已被移植"
+msgstr "参考仓库 '%s' 已被嫁接"
 
 #: odb.c
 #, c-format
@@ -23893,7 +24006,7 @@
 #: pack-bitmap.c
 #, c-format
 msgid "corrupt ewah bitmap: truncated header for entry %d"
-msgstr "损坏的 EWAH 位图:条目 %d 截断的文件头"
+msgstr "损坏的 EWAH 位图:条目 %d 的文件头被截断"
 
 #: pack-bitmap.c
 #, c-format
@@ -23910,7 +24023,7 @@
 
 #: pack-bitmap.c
 msgid "cannot fstat bitmap file"
-msgstr "不能对位图文件调用 fstat"
+msgstr "无法对位图文件调用 fstat"
 
 #: pack-bitmap.c
 msgid "checksum doesn't match in MIDX and bitmap"
@@ -23923,7 +24036,7 @@
 #: pack-bitmap.c
 #, c-format
 msgid "could not open pack %s"
-msgstr "不能打开包 %s"
+msgstr "无法打开包 %s"
 
 #: pack-bitmap.c
 msgid "corrupt bitmap lookup table: triplet position out of index"
@@ -23946,7 +24059,7 @@
 #: pack-bitmap.c
 #, c-format
 msgid "unable to load pack: '%s', disabling pack-reuse"
-msgstr "无法打开包:'%s',禁用包重用"
+msgstr "无法加载包:'%s',禁用包重用"
 
 #: pack-bitmap.c
 msgid "unable to compute preferred pack, disabling pack-reuse"
@@ -23955,7 +24068,7 @@
 #: pack-bitmap.c
 #, c-format
 msgid "object '%s' not found in type bitmaps"
-msgstr "对象 %s 为在类型位图中找到"
+msgstr "对象 '%s' 未在类型位图中找到"
 
 #: pack-bitmap.c
 #, c-format
@@ -23970,7 +24083,7 @@
 #: pack-bitmap.c
 #, c-format
 msgid "object not in bitmap: '%s'"
-msgstr "对象不在位图中:%s"
+msgstr "对象不在位图中:'%s'"
 
 #: pack-bitmap.c
 msgid "failed to load bitmap indexes"
@@ -24022,12 +24135,12 @@
 #: pack-mtimes.c
 #, c-format
 msgid "mtimes file %s has unsupported version %<PRIu32>"
-msgstr "mtimes 文件 %s 不支持的版本 %<PRIu32>"
+msgstr "mtimes 文件 %s 版本 %<PRIu32> 不受支持"
 
 #: pack-mtimes.c
 #, c-format
 msgid "mtimes file %s has unsupported hash id %<PRIu32>"
-msgstr "mtimes 文件 %s 有不支持的哈希 ID %<PRIu32>"
+msgstr "mtimes 文件 %s 的哈希 ID %<PRIu32> 不受支持"
 
 #: pack-mtimes.c
 #, c-format
@@ -24036,7 +24149,7 @@
 
 #: pack-refs.c
 msgid "pack everything"
-msgstr "打包一切"
+msgstr "打包全部"
 
 #: pack-refs.c
 msgid "prune loose refs (default)"
@@ -24067,26 +24180,26 @@
 #: pack-revindex.c
 #, c-format
 msgid "reverse-index file %s has unknown signature"
-msgstr "反向索引文件 %s 有错误的签名"
+msgstr "反向索引文件 %s 有未知的签名"
 
 #: pack-revindex.c
 #, c-format
 msgid "reverse-index file %s has unsupported version %<PRIu32>"
-msgstr "反向索引文件 %s 不支持的版本 %<PRIu32>"
+msgstr "反向索引文件 %s 的版本 %<PRIu32> 不受支持"
 
 #: pack-revindex.c
 #, c-format
 msgid "reverse-index file %s has unsupported hash id %<PRIu32>"
-msgstr "反向索引文件 %s 有不支持的哈希 ID %<PRIu32>"
+msgstr "反向索引文件 %s 的哈希 ID %<PRIu32> 不受支持"
 
 #: pack-revindex.c
 msgid "invalid checksum"
-msgstr "无效的校验码 %s"
+msgstr "无效的校验码"
 
 #: pack-revindex.c
 #, c-format
 msgid "invalid rev-index position at %<PRIu64>: %<PRIu32> != %<PRIu32>"
-msgstr "位于 %<PRIu64> 的无效的反向索引:%<PRIu32> != %<PRIu32>"
+msgstr "在 %<PRIu64> 的反向索引位置无效:%<PRIu32> != %<PRIu32>"
 
 #: pack-revindex.c
 msgid "multi-pack-index reverse-index chunk is the wrong size"
@@ -24098,12 +24211,12 @@
 
 #: pack-write.c
 msgid "cannot both write and verify reverse index"
-msgstr "无法同时写入和校验反向索引"
+msgstr "不能同时写入和校验反向索引"
 
 #: pack-write.c
 #, c-format
 msgid "could not stat: %s"
-msgstr "不能调用 stat:%s"
+msgstr "无法调用 stat:%s"
 
 #: pack-write.c
 #, c-format
@@ -24122,7 +24235,7 @@
 #: packfile.c
 #, c-format
 msgid "packfile %s cannot be mapped%s"
-msgstr "包文件 %s 不能被映射%s"
+msgstr "包文件 %s 无法被映射%s"
 
 #: packfile.c
 #, c-format
@@ -24187,7 +24300,7 @@
 #: parse-options.c
 #, c-format
 msgid "%s expects a non-negative integer value with an optional k/m/g suffix"
-msgstr "%s 期望一个非负整数和一个可选的 k/m/g 后缀"
+msgstr "%s 期望一个非负整数以及可选的 k/m/g 后缀"
 
 #: parse-options.c
 #, c-format
@@ -24221,7 +24334,7 @@
 #: parse-options.c
 #, c-format
 msgid "unknown non-ascii option in string: `%s'"
-msgstr "字符串中未知的非 ascii 字符选项:`%s'"
+msgstr "字符串中未知的非 ASCII 字符选项:`%s'"
 
 #. TRANSLATORS: The "<%s>" part of this string
 #. stands for an optional value given to a command
@@ -24354,21 +24467,21 @@
 
 #: parse-options.h
 msgid "how to strip spaces and #comments from message"
-msgstr "设置如何删除提交说明里的空格和#注释"
+msgstr "设置如何删除提交说明里的空格和 #注释"
 
 #: parse-options.h
 msgid "read pathspec from file"
-msgstr "从文件读取路径表达式"
+msgstr "从文件读取路径规格"
 
 #: parse-options.h
 msgid ""
 "with --pathspec-from-file, pathspec elements are separated with NUL character"
-msgstr "使用 --pathspec-from-file,路径表达式用空字符分隔"
+msgstr "使用 --pathspec-from-file,路径规格元素用 NUL 字符分隔"
 
 #: parse.c
 #, c-format
 msgid "bad boolean environment value '%s' for '%s'"
-msgstr "对于 '%2$s' 的错误的布尔环境取值 '%1$s'"
+msgstr "环境变量 '%2$s' 的布尔值 '%1$s' 无效"
 
 #: path-walk.c
 #, c-format
@@ -24392,7 +24505,7 @@
 #: path.c
 #, c-format
 msgid "Could not make %s writable by group"
-msgstr "不能设置 %s 为组可写"
+msgstr "无法设置 %s 为组可写"
 
 #: pathspec.c
 msgid "Escape character '\\' not allowed as last character in attr value"
@@ -24419,26 +24532,26 @@
 msgid ""
 "global 'literal' pathspec setting is incompatible with all other global "
 "pathspec settings"
-msgstr "全局的 'literal' 路径规格设置和其它的全局路径规格设置不兼容"
+msgstr "全局的 'literal' 路径规格设置和其他的全局路径规格设置不兼容"
 
 #: pathspec.c
 msgid "invalid parameter for pathspec magic 'prefix'"
-msgstr "路径规格包含无效的神奇前缀"
+msgstr "路径规格魔法 'prefix' 的参数无效"
 
 #: pathspec.c
 #, c-format
 msgid "Invalid pathspec magic '%.*s' in '%s'"
-msgstr "在路径规格 '%3$s' 中无效的神奇前缀 '%2$.*1$s'"
+msgstr "路径规格魔法前缀 '%.*s' 在 '%s' 中无效"
 
 #: pathspec.c
 #, c-format
 msgid "Missing ')' at the end of pathspec magic in '%s'"
-msgstr "路径规格 '%s' 的神奇前缀结尾少了一个 ')'"
+msgstr "路径规格 '%s' 的魔法前缀结尾少了一个 ')'"
 
 #: pathspec.c
 #, c-format
 msgid "Unimplemented pathspec magic '%c' in '%s'"
-msgstr "路径规格 '%2$s' 中包含未实现的神奇前缀 '%1$c'"
+msgstr "路径规格 '%2$s' 中包含未实现的魔法前缀 '%1$c'"
 
 #: pathspec.c
 #, c-format
@@ -24463,25 +24576,25 @@
 #: pathspec.c
 #, c-format
 msgid "%s: pathspec magic not supported by this command: %s"
-msgstr "%s:路径规格神奇前缀不被此命令支持:%s"
+msgstr "%s:路径规格魔法前缀不被此命令支持:%s"
 
 #: pathspec.c
 #, c-format
 msgid "pathspec '%s' is beyond a symbolic link"
-msgstr "路径规格 '%s' 位于符号链接中"
+msgstr "路径规格 '%s' 位于符号链接之后"
 
 #: pathspec.c
 #, c-format
 msgid "line is badly quoted: %s"
-msgstr "行被错误地引用:%s"
+msgstr "行引用不正确:%s"
 
 #: pkt-line.c
 msgid "unable to write flush packet"
-msgstr "无法写 flush 包"
+msgstr "无法写入 flush 包"
 
 #: pkt-line.c
 msgid "unable to write delim packet"
-msgstr "无法写 delim 包"
+msgstr "无法写入 delim 包"
 
 #: pkt-line.c
 msgid "unable to write response end packet"
@@ -24489,11 +24602,11 @@
 
 #: pkt-line.c
 msgid "flush packet write failed"
-msgstr "flush 包写错误"
+msgstr "flush 包写入失败"
 
 #: pkt-line.c
 msgid "protocol error: impossibly long line"
-msgstr "协议错误:不可能的长行"
+msgstr "协议错误:行过长"
 
 #: pkt-line.c
 msgid "packet write with format failed"
@@ -24519,7 +24632,7 @@
 #: pkt-line.c
 #, c-format
 msgid "protocol error: bad line length character: %.4s"
-msgstr "协议错误:错误的行长度字符串:%.4s"
+msgstr "协议错误:错误的行长度字符:%.4s"
 
 #: pkt-line.c
 #, c-format
@@ -24542,11 +24655,11 @@
 
 #: pretty.c
 msgid "unable to parse --pretty format"
-msgstr "不能解析 --pretty 格式"
+msgstr "无法解析 --pretty 格式"
 
 #: promisor-remote.c
 msgid "lazy fetching disabled; some objects may not be available"
-msgstr "禁用延迟获取,某些对象可能不可用"
+msgstr "已禁用延迟获取,某些对象可能不可用"
 
 #: promisor-remote.c
 msgid "promisor-remote: unable to fork off fetch subprocess"
@@ -24607,7 +24720,7 @@
 
 #: protocol-caps.c
 msgid "object-info: expected flush after arguments"
-msgstr "object-info:在参数之后应有一个 flush"
+msgstr "object-info:在参数之后应有一个 flush 包"
 
 #: prune-packed.c
 msgid "Removing duplicate objects"
@@ -24621,7 +24734,7 @@
 #: pseudo-merge.c
 #, c-format
 msgid "%s must be non-negative, using default"
-msgstr "%s 必须为非负整数,使用默认值"
+msgstr "%s 必须为非负值,使用默认值"
 
 #: pseudo-merge.c
 #, c-format
@@ -24647,7 +24760,7 @@
 #, c-format
 msgid ""
 "pseudo-merge regex from config has too many capture groups (max=%<PRIuMAX>)"
-msgstr "来自 config 的伪合并正则表达式有太多的捕获组(最多 %<PRIuMAX> 个)"
+msgstr "来自配置的伪合并正则表达式有太多的捕获组(最多 %<PRIuMAX> 个)"
 
 #: pseudo-merge.c
 #, c-format
@@ -24681,16 +24794,16 @@
 
 #: range-diff.c
 msgid "could not start `log`"
-msgstr "不能启动 `log`"
+msgstr "无法启动 `log`"
 
 #: range-diff.c
 msgid "could not read `log` output"
-msgstr "不能读取 `log` 的输出"
+msgstr "无法读取 `log` 的输出"
 
 #: range-diff.c sequencer.c
 #, c-format
 msgid "could not parse commit '%s'"
-msgstr "不能解析提交 '%s'"
+msgstr "无法解析提交 '%s'"
 
 #: range-diff.c
 #, c-format
@@ -24721,12 +24834,12 @@
 #: range-diff.c
 #, c-format
 msgid "could not parse log for '%s'"
-msgstr "不能解析 '%s' 的日志"
+msgstr "无法解析 '%s' 的日志"
 
 #: reachable.c
 #, c-format
 msgid "invalid extra cruft tip: '%s'"
-msgstr "无效的额外废弃提交版本:'%s'"
+msgstr "无效的额外废弃对象 tip:'%s'"
 
 #: reachable.c
 msgid "unable to enumerate additional recent objects"
@@ -24739,12 +24852,12 @@
 
 #: read-cache.c
 msgid "cannot create an empty blob in the object database"
-msgstr "不能在对象数据库中创建空的数据对象"
+msgstr "无法在对象数据库中创建空的数据对象"
 
 #: read-cache.c
 #, c-format
 msgid "%s: can only add regular files, symbolic links or git-directories"
-msgstr "%s:只能添加常规文件、符号链接或 git 目录"
+msgstr "%s:只能添加常规文件、符号链接或 Git 目录"
 
 #: read-cache.c
 #, c-format
@@ -24786,16 +24899,16 @@
 #: read-cache.c
 #, c-format
 msgid "bad signature 0x%08x"
-msgstr "坏的签名 0x%08x"
+msgstr "无效的签名 0x%08x"
 
 #: read-cache.c
 #, c-format
 msgid "bad index version %d"
-msgstr "坏的索引版本 %d"
+msgstr "无效的索引版本 %d"
 
 #: read-cache.c
 msgid "bad index file sha1 signature"
-msgstr "坏的索引文件 sha1 签名"
+msgstr "无效的索引文件 SHA-1 签名"
 
 #: read-cache.c
 #, c-format
@@ -24849,7 +24962,7 @@
 #: read-cache.c
 #, c-format
 msgid "%s: cannot stat the open index"
-msgstr "%s:不能对打开的索引执行 stat 操作"
+msgstr "%s:无法对打开的索引执行 stat 操作"
 
 #: read-cache.c
 #, c-format
@@ -24892,7 +25005,7 @@
 #: read-cache.c
 #, c-format
 msgid "unable to open git dir: %s"
-msgstr "不能打开 git 目录:%s"
+msgstr "无法打开 Git 目录:%s"
 
 #: read-cache.c
 #, c-format
@@ -24902,12 +25015,12 @@
 #: read-cache.c
 #, c-format
 msgid "cannot fix permission bits on '%s'"
-msgstr "不能修复 '%s' 的权限位"
+msgstr "无法修复 '%s' 的权限位"
 
 #: read-cache.c
 #, c-format
 msgid "%s: cannot drop to stage #0"
-msgstr "%s:不能落到暂存区 #0"
+msgstr "%s:无法落到暂存区 #0"
 
 #: read-cache.c
 #, c-format
@@ -24965,7 +25078,7 @@
 "命令:\n"
 "p, pick <提交> = 使用提交\n"
 "r, reword <提交> = 使用提交,但编辑提交说明\n"
-"e, edit <提交> = 使用提交,但停止以便修补提交\n"
+"e, edit <提交> = 使用提交,但停止以便修订提交\n"
 "s, squash <提交> = 使用提交,但挤压到前一个提交\n"
 "f, fixup [-C | -c] <提交> = 类似于 \"squash\",但只保留前一个提交\n"
 "                   的提交说明,除非使用了 -C 参数,此情况下则只\n"
@@ -24977,9 +25090,9 @@
 "l, label <label> = 为当前 HEAD 打上标记\n"
 "t, reset <label> = 重置 HEAD 到该标记\n"
 "m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n"
-".       创建一个合并提交,并使用原始的合并提交说明(如果没有指定\n"
-".       原始提交,使用注释部分的 oneline 作为提交说明)。使用\n"
-".       -c <提交> 可以编辑提交说明。\n"
+"        创建一个合并提交,并使用原始的合并提交说明(如果没有指定\n"
+"        原始提交,使用注释部分的 oneline 作为提交说明)。使用\n"
+"        -c <提交> 可以编辑提交说明。\n"
 "u, update-ref <引用> = 为引用 <ref> 设置一个占位符,以将该引用更新为此处的新"
 "提交。\n"
 "                      此 <引用> 在变基结束后更新。\n"
@@ -24990,8 +25103,8 @@
 #, c-format
 msgid "Rebase %s onto %s (%d command)"
 msgid_plural "Rebase %s onto %s (%d commands)"
-msgstr[0] "变基 %s 到 %s(%d 个提交)"
-msgstr[1] "变基 %s 到 %s(%d 个提交)"
+msgstr[0] "变基 %s 到 %s(%d 个命令)"
+msgstr[1] "变基 %s 到 %s(%d 个命令)"
 
 #: rebase-interactive.c
 msgid ""
@@ -25036,7 +25149,7 @@
 #: rebase-interactive.c
 #, c-format
 msgid "could not write '%s'."
-msgstr "不能写入 '%s'。"
+msgstr "无法写入 '%s'。"
 
 #: rebase-interactive.c
 #, c-format
@@ -25135,7 +25248,7 @@
 #: ref-filter.c
 #, c-format
 msgid "argument expected for %s"
-msgstr "预期参数 %s"
+msgstr "缺少 %s 的参数"
 
 #: ref-filter.c
 #, c-format
@@ -25145,7 +25258,7 @@
 #: ref-filter.c
 #, c-format
 msgid "cannot fully parse %s=%s"
-msgstr "不能完整解析 %s=%s"
+msgstr "无法完整解析 %s=%s"
 
 #: ref-filter.c
 #, c-format
@@ -25206,7 +25319,7 @@
 #, c-format
 msgid ""
 "not a git repository, but the field '%.*s' requires access to object data"
-msgstr "不是 git 仓库,但是字段 '%.*s' 需要访问对象数据"
+msgstr "不是 Git 仓库,但是字段 '%.*s' 需要访问对象数据"
 
 #: ref-filter.c
 #, c-format
@@ -25249,6 +25362,11 @@
 msgstr "--format=%.*s 不能和 --python、--shell、--tcl 同时使用"
 
 #: ref-filter.c
+#, c-format
+msgid "parse_object_buffer failed on %s for %s"
+msgstr "parse_object_buffer 失败于 %2$s 的 %1$s"
+
+#: ref-filter.c
 msgid "failed to run 'describe'"
 msgstr "无法运行 'describe'"
 
@@ -25288,11 +25406,6 @@
 
 #: ref-filter.c
 #, c-format
-msgid "parse_object_buffer failed on %s for %s"
-msgstr "parse_object_buffer 失败于 %2$s 的 %1$s"
-
-#: ref-filter.c
-#, c-format
 msgid "malformed object at '%s'"
 msgstr "格式错误的对象 '%s'"
 
@@ -25323,7 +25436,7 @@
 
 #: ref-filter.h
 msgid "key"
-msgstr "key"
+msgstr "键"
 
 #: ref-filter.h
 msgid "field name to sort on"
@@ -25426,7 +25539,7 @@
 #: refs.c
 #, c-format
 msgid "log for ref %s unexpectedly ended on %s"
-msgstr "引用 %s 的日志意外终止于 %s "
+msgstr "引用 %s 的日志意外终止于 %s"
 
 #: refs.c
 #, c-format
@@ -25483,7 +25596,7 @@
 #: refs.c
 #, c-format
 msgid "cannot process '%s' and '%s' at the same time"
-msgstr "无法同时处理 '%s' 和 '%s'"
+msgstr "不能同时处理 '%s' 和 '%s'"
 
 #: refs.c
 #, c-format
@@ -25618,17 +25731,17 @@
 #: refs/reftable-backend.c
 #, c-format
 msgid "reftable: transaction prepare: %s"
-msgstr "reftable: 事务准备:%s"
+msgstr "reftable:事务准备:%s"
 
 #: refs/reftable-backend.c
 #, c-format
 msgid "reftable: transaction failure: %s"
-msgstr "reftable: 事务失败:%s"
+msgstr "reftable:事务失败:%s"
 
 #: refs/reftable-backend.c
 #, c-format
 msgid "unable to compact stack: %s"
-msgstr "无法压缩栈:%s"
+msgstr "无法压缩堆栈:%s"
 
 #: refs/reftable-backend.c
 #, c-format
@@ -25640,6 +25753,21 @@
 msgid "refname %s is a symbolic ref, copying it is not supported"
 msgstr "引用名 %s 是一个符号引用,不支持复制"
 
+#: refs/reftable-backend.c
+#, c-format
+msgid "reftable stack for worktree '%s' is broken"
+msgstr "工作区 '%s' 的 reftable 堆栈已损坏"
+
+#: refs/reftable-backend.c
+#, c-format
+msgid "could not create iterator for worktree '%s'"
+msgstr "无法为工作区 '%s' 创建迭代器"
+
+#: refs/reftable-backend.c
+#, c-format
+msgid "could not read record for worktree '%s'"
+msgstr "无法读取工作区 '%s' 的记录"
+
 #: refspec.c
 #, c-format
 msgid "pattern '%s' has no '*'"
@@ -25663,7 +25791,7 @@
 #: remote-curl.c
 #, c-format
 msgid "%sinfo/refs not valid: is this a git repository?"
-msgstr "%sinfo/refs 无效:这是 git 仓库么?"
+msgstr "%sinfo/refs 无效:这是 Git 仓库吗?"
 
 #: remote-curl.c
 msgid "invalid server response; expected service, got flush packet"
@@ -25714,7 +25842,7 @@
 #: remote-curl.c
 #, c-format
 msgid "remote-curl: bad line length character: %.4s"
-msgstr "remote-curl:错误的行宽字符:%.4s"
+msgstr "remote-curl:错误的行长度字符:%.4s"
 
 #: remote-curl.c
 msgid "remote-curl: unexpected response end packet"
@@ -25728,17 +25856,17 @@
 #: remote-curl.c
 #, c-format
 msgid "cannot deflate request; zlib deflate error %d"
-msgstr "不能压缩请求,zlib 压缩错误 %d"
+msgstr "无法压缩请求,zlib 压缩错误 %d"
 
 #: remote-curl.c
 #, c-format
 msgid "cannot deflate request; zlib end error %d"
-msgstr "不能压缩请求,zlib 结束错误 %d"
+msgstr "无法压缩请求,zlib 结束错误 %d"
 
 #: remote-curl.c
 #, c-format
 msgid "%d bytes of length header were received"
-msgstr "收到了 %d 字节长度的头信息"
+msgstr "收到了 %d 字节的长度头部"
 
 #: remote-curl.c
 #, c-format
@@ -25747,7 +25875,7 @@
 
 #: remote-curl.c
 msgid "dumb http transport does not support shallow capabilities"
-msgstr "哑 http 传输不支持浅克隆能力"
+msgstr "哑 HTTP 传输不支持浅克隆能力"
 
 #: remote-curl.c
 msgid "fetch failed."
@@ -25755,7 +25883,7 @@
 
 #: remote-curl.c
 msgid "cannot fetch by sha1 over smart http"
-msgstr "无法通过智能 HTTP 获取 sha1"
+msgstr "无法通过智能 HTTP 获取 SHA-1"
 
 #: remote-curl.c
 #, c-format
@@ -25765,7 +25893,7 @@
 #: remote-curl.c
 #, c-format
 msgid "http transport does not support %s"
-msgstr "http 传输协议不支持 %s"
+msgstr "HTTP 传输协议不支持 %s"
 
 #: remote-curl.c
 msgid "protocol error: expected '<url> <path>', missing space"
@@ -25782,7 +25910,7 @@
 
 #: remote-curl.c
 msgid "remote-curl: usage: git remote-curl <remote> [<url>]"
-msgstr "remote-curl:用法:git remote-curl <远程> [<url>]"
+msgstr "remote-curl:用法:git remote-curl <远程> [<URL>]"
 
 #: remote-curl.c
 msgid "remote-curl: error reading command stream from git"
@@ -25790,7 +25918,7 @@
 
 #: remote-curl.c
 msgid "remote-curl: fetch attempted without a local repo"
-msgstr "remote-curl:尝试没有本地仓库下获取"
+msgstr "remote-curl:在没有本地仓库的情况下尝试获取"
 
 #: remote-curl.c
 #, c-format
@@ -25810,13 +25938,13 @@
 "If you cannot, please let us know why you still need to use it by\n"
 "sending an e-mail to <git@vger.kernel.org>."
 msgstr ""
-"正在从 \"%s/%s\" 读取远程内容,该内容被提名删除。\n"
+"正在从 \"%s/%s\" 读取远程内容,该内容被列入移除计划。\n"
 "\n"
-"如果你仍然在使用 \"remotes/\" 目录,建议迁移为基于配置远程的方式:\n"
+"如果您仍然在使用 \"remotes/\" 目录,建议迁移为基于配置远程的方式:\n"
 "\n"
 "\tgit remote rename %s %s\n"
 "\n"
-"如果你无法迁移,请发送电子邮件至 <git@vger.kernel.org> 告知我们你\n"
+"如果您无法迁移,请发送电子邮件至 <git@vger.kernel.org> 告知我们您\n"
 "仍然需要使用它的原因。"
 
 #: remote.c
@@ -25850,7 +25978,7 @@
 #: remote.c
 #, c-format
 msgid "Cannot fetch both %s and %s to %s"
-msgstr "不能同时获取 %s 和 %s 至 %s"
+msgstr "不能同时获取 %s 和 %s 到 %s"
 
 #: remote.c
 #, c-format
@@ -25906,7 +26034,7 @@
 "'%s:refs/heads/%s'?"
 msgstr ""
 "引用规格的 <src> 是一个提交对象。您是想创建一个新的分支而向\n"
-"'%s:refs/heads/%s' 推送么?"
+"'%s:refs/heads/%s' 推送吗?"
 
 #: remote.c
 #, c-format
@@ -25916,7 +26044,7 @@
 "'%s:refs/tags/%s'?"
 msgstr ""
 "引用规格的 <src> 是一个标签对象。您是想创建一个新的标签而向\n"
-"'%s:refs/tags/%s' 推送么?"
+"'%s:refs/tags/%s' 推送吗?"
 
 #: remote.c
 #, c-format
@@ -25926,7 +26054,7 @@
 "'%s:refs/tags/%s'?"
 msgstr ""
 "引用规格的 <src> 是一个树对象。您是想为这个树对象创建标签而向\n"
-"'%s:refs/tags/%s' 推送么?"
+"'%s:refs/tags/%s' 推送吗?"
 
 #: remote.c
 #, c-format
@@ -25936,7 +26064,7 @@
 "'%s:refs/tags/%s'?"
 msgstr ""
 "引用规格的 <src> 是一个数据对象。您是想为这个数据对象创建标签而向\n"
-"'%s:refs/tags/%s' 推送么?"
+"'%s:refs/tags/%s' 推送吗?"
 
 #: remote.c
 #, c-format
@@ -26014,7 +26142,7 @@
 #: remote.c
 #, c-format
 msgid "* Ignoring funny ref '%s' locally"
-msgstr "* 在本地忽略可笑的引用 '%s'"
+msgstr "* 在本地忽略异常的引用 '%s'"
 
 #: remote.c
 #, c-format
@@ -26093,12 +26221,12 @@
 #: remote.c
 #, c-format
 msgid "cannot strip one component off url '%s'"
-msgstr "无法从 url '%s' 剥离一个组件"
+msgstr "无法从 URL '%s' 剥离一个组件"
 
 #: repack-geometry.c
 #, c-format
 msgid "cannot open index for %s"
-msgstr "不能打开 %s 的索引"
+msgstr "无法打开 %s 的索引"
 
 #: repack-geometry.c
 #, c-format
@@ -26108,7 +26236,7 @@
 #: repack-geometry.c
 #, c-format
 msgid "pack %s too large to roll up"
-msgstr "包 %s 太大导致数字溢出"
+msgstr "包 %s 太大,无法汇总"
 
 #: repack-midx.c
 #, c-format
@@ -26117,12 +26245,12 @@
 
 #: repack-midx.c
 msgid "could not close refs snapshot tempfile"
-msgstr "不能关闭引用快照临时文件"
+msgstr "无法关闭引用快照临时文件"
 
 #: repack-midx.c
 #, c-format
 msgid "could not remove stale bitmap: %s"
-msgstr "无法删除过期的位图: %s"
+msgstr "无法删除过期的位图:%s"
 
 #: repack-promisor.c
 msgid "could not start pack-objects to repack promisor objects"
@@ -26138,7 +26266,11 @@
 
 #: repack-promisor.c
 msgid "could not finish pack-objects to repack promisor objects"
-msgstr "无法完成 pack-objects 来重新打包 promisor 对象"
+msgstr "无法完成 pack-objects 来重新打包承诺者对象"
+
+#: repack-promisor.c
+msgid "could not start pack-objects to repack promisor packs"
+msgstr "无法启动 pack-objects 来重新打包承诺者包"
 
 #: repack.c
 #, c-format
@@ -26148,7 +26280,7 @@
 #: repack.c
 #, c-format
 msgid "renaming pack to '%s' failed"
-msgstr "重命名包至 '%s' 失败"
+msgstr "重命名包为 '%s' 失败"
 
 #: repack.c
 #, c-format
@@ -26158,12 +26290,12 @@
 #: repack.c sequencer.c
 #, c-format
 msgid "could not unlink: %s"
-msgstr "不能删除:%s"
+msgstr "无法删除:%s"
 
 #: replace-object.c
 #, c-format
 msgid "bad replace ref name: %s"
-msgstr "错误的替换引用名称:%s"
+msgstr "无效的替换引用名称:%s"
 
 #: replace-object.c
 #, c-format
@@ -26191,7 +26323,7 @@
 #: rerere.c
 #, c-format
 msgid "could not parse conflict hunks in '%s'"
-msgstr "不能解析 '%s' 中的冲突块"
+msgstr "无法解析 '%s' 中的冲突块"
 
 #: rerere.c
 #, c-format
@@ -26221,7 +26353,7 @@
 #: rerere.c
 #, c-format
 msgid "cannot unlink stray '%s'"
-msgstr "不能删除 stray '%s'"
+msgstr "无法删除多余的 '%s'"
 
 #: rerere.c
 #, c-format
@@ -26250,15 +26382,15 @@
 
 #: rerere.c
 msgid "unable to open rr-cache directory"
-msgstr "不能打开 rr-cache 目录"
+msgstr "无法打开 rr-cache 目录"
 
 #: rerere.h
 msgid "update the index with reused conflict resolution if possible"
-msgstr "如果可能,重用冲突解决更新索引"
+msgstr "如果可能,使用复用的冲突解决结果更新索引"
 
 #: reset.c
 msgid "could not determine HEAD revision"
-msgstr "不能确定 HEAD 版本"
+msgstr "无法确定 HEAD 版本"
 
 #: reset.c sequencer.c
 #, c-format
@@ -26268,7 +26400,7 @@
 #: revision.c
 #, c-format
 msgid "unsupported section for hidden refs: %s"
-msgstr "不支持的隐藏引用片段: %s"
+msgstr "不支持的隐藏引用小节:%s"
 
 #: revision.c
 msgid "--exclude-hidden= passed more than once"
@@ -26282,7 +26414,7 @@
 #: revision.c
 #, c-format
 msgid "%s exists but is a symbolic ref"
-msgstr "%s 存在但是一个符号引用"
+msgstr "%s 存在但为符号引用"
 
 #: revision.c
 msgid ""
@@ -26308,7 +26440,7 @@
 
 #: revision.c
 msgid "your current branch appears to be broken"
-msgstr "您的当前分支好像被损坏"
+msgstr "您的当前分支似乎已损坏"
 
 #: revision.c
 #, c-format
@@ -26326,7 +26458,7 @@
 #: run-command.c
 #, c-format
 msgid "cannot create async thread: %s"
-msgstr "不能创建 async 线程:%s"
+msgstr "无法创建 async 线程:%s"
 
 #: scalar.c worktree.c
 #, c-format
@@ -26414,7 +26546,7 @@
 
 #: scalar.c
 msgid "only download metadata for the branch that will be checked out"
-msgstr "只下载要检出的分支的元信息"
+msgstr "只下载要检出的分支的元数据"
 
 #: scalar.c
 msgid "create repository within 'src' directory"
@@ -26422,7 +26554,7 @@
 
 #: scalar.c
 msgid "specify if tags should be fetched during clone"
-msgstr "如若应在克隆期间获取标签则指定"
+msgstr "指定是否在克隆期间获取标签"
 
 #: scalar.c
 msgid "specify if background maintenance should be enabled"
@@ -26507,7 +26639,7 @@
 
 #: scalar.c
 msgid "--all or <enlistment>, but not both"
-msgstr "--all 或者 <登记>,而不是两个一起"
+msgstr "--all 或 <登记>,但不能同时使用"
 
 #: scalar.c
 #, c-format
@@ -26592,7 +26724,7 @@
 #: scalar.c
 #, c-format
 msgid "could not change to '%s'"
-msgstr "无法变更到 '%s'"
+msgstr "无法切换到 '%s'"
 
 #: scalar.c
 msgid "-c requires a <key>=<value> argument"
@@ -26615,7 +26747,7 @@
 #: send-pack.c
 #, c-format
 msgid "unable to parse remote unpack status: %s"
-msgstr "不能解析远程解包状态:%s"
+msgstr "无法解析远程解包状态:%s"
 
 #: send-pack.c
 #, c-format
@@ -26735,12 +26867,12 @@
 #: sequencer.c
 #, c-format
 msgid "could not lock '%s'"
-msgstr "不能锁定 '%s'"
+msgstr "无法锁定 '%s'"
 
 #: sequencer.c
 #, c-format
 msgid "could not write eol to '%s'"
-msgstr "不能将换行符写入 '%s'"
+msgstr "无法将换行符写入 '%s'"
 
 #: sequencer.c
 #, c-format
@@ -26750,11 +26882,11 @@
 #: sequencer.c
 #, c-format
 msgid "your local changes would be overwritten by %s."
-msgstr "您的本地修改将被%s覆盖。"
+msgstr "您的本地修改将被 %s 覆盖。"
 
 #: sequencer.c
 msgid "commit your changes or stash them to proceed."
-msgstr "提交您的修改或贮藏后再继续。"
+msgstr "提交您的修改或储藏后再继续。"
 
 #. TRANSLATORS: %s will be "revert", "cherry-pick" or
 #. "rebase".
@@ -26766,16 +26898,16 @@
 
 #: sequencer.c
 msgid "unable to update cache tree"
-msgstr "不能更新缓存树"
+msgstr "无法更新缓存树"
 
 #: sequencer.c
 msgid "could not resolve HEAD commit"
-msgstr "不能解析 HEAD 提交"
+msgstr "无法解析 HEAD 提交"
 
 #: sequencer.c
 #, c-format
 msgid "no key present in '%.*s'"
-msgstr "在 '%.*s' 中没有 key"
+msgstr "在 '%.*s' 中没有键"
 
 #: sequencer.c
 #, c-format
@@ -26897,11 +27029,11 @@
 
 #: sequencer.c
 msgid "could not parse newly created commit"
-msgstr "不能解析新创建的提交"
+msgstr "无法解析新创建的提交"
 
 #: sequencer.c
 msgid "unable to resolve HEAD after creating commit"
-msgstr "创建提交后,不能解析 HEAD"
+msgstr "创建提交后,无法解析 HEAD"
 
 #: sequencer.c
 msgid "detached HEAD"
@@ -26914,7 +27046,7 @@
 
 #: sequencer.c
 msgid "could not parse HEAD"
-msgstr "不能解析 HEAD"
+msgstr "无法解析 HEAD"
 
 #: sequencer.c
 #, c-format
@@ -26923,12 +27055,12 @@
 
 #: sequencer.c
 msgid "unable to parse commit author"
-msgstr "不能解析提交作者"
+msgstr "无法解析提交作者"
 
 #: sequencer.c
 #, c-format
 msgid "unable to read commit message from '%s'"
-msgstr "不能从 '%s' 读取提交说明"
+msgstr "无法从 '%s' 读取提交说明"
 
 #: sequencer.c
 #, c-format
@@ -26942,12 +27074,12 @@
 #: sequencer.c
 #, c-format
 msgid "could not update %s"
-msgstr "不能更新 %s"
+msgstr "无法更新 %s"
 
 #: sequencer.c
 #, c-format
 msgid "could not parse parent commit %s"
-msgstr "不能解析父提交 %s"
+msgstr "无法解析父提交 %s"
 
 #: sequencer.c
 #, c-format
@@ -26980,7 +27112,7 @@
 #: sequencer.c
 #, c-format
 msgid "cannot write '%s'"
-msgstr "不能写 '%s'"
+msgstr "无法写入 '%s'"
 
 #: sequencer.c
 msgid "need a HEAD to fixup"
@@ -26988,16 +27120,16 @@
 
 #: sequencer.c
 msgid "could not read HEAD"
-msgstr "不能读取 HEAD"
+msgstr "无法读取 HEAD"
 
 #: sequencer.c
 msgid "could not read HEAD's commit message"
-msgstr "不能读取 HEAD 的提交说明"
+msgstr "无法读取 HEAD 的提交说明"
 
 #: sequencer.c
 #, c-format
 msgid "could not read commit message of %s"
-msgstr "不能读取 %s 的提交说明"
+msgstr "无法读取 %s 的提交说明"
 
 #: sequencer.c
 msgid "your index file is unmerged."
@@ -27005,7 +27137,7 @@
 
 #: sequencer.c
 msgid "cannot fixup root commit"
-msgstr "不能修复根提交"
+msgstr "无法修复根提交"
 
 #: sequencer.c
 #, c-format
@@ -27020,24 +27152,24 @@
 #: sequencer.c
 #, c-format
 msgid "cannot get commit message for %s"
-msgstr "不能得到 %s 的提交说明"
+msgstr "无法获取 %s 的提交说明"
 
 #. TRANSLATORS: The first %s will be a "todo" command like
 #. "revert" or "pick", the second %s a SHA1.
 #: sequencer.c
 #, c-format
 msgid "%s: cannot parse parent commit %s"
-msgstr "%s:不能解析父提交 %s"
+msgstr "%s:无法解析父提交 %s"
 
 #: sequencer.c
 #, c-format
 msgid "could not revert %s... %s"
-msgstr "不能还原 %s... %s"
+msgstr "无法还原 %s... %s"
 
 #: sequencer.c
 #, c-format
 msgid "could not apply %s... %s"
-msgstr "不能应用 %s... %s"
+msgstr "无法应用 %s... %s"
 
 #: sequencer.c
 #, c-format
@@ -27107,7 +27239,7 @@
 "'break' to give the control back to you so that you can\n"
 "do 'git commit --amend && git rebase --continue'."
 msgstr ""
-"'edit”' 不接受合并提交。如果您想要重放合并,\n"
+"'edit' 不接受合并提交。如果您想要重放合并,\n"
 "请在提交上使用 'merge -C',然后使用 'break'\n"
 "将控制权交还给您,以便您可以执行\n"
 "'git commit --amend && git rebase --continue'。"
@@ -27164,11 +27296,11 @@
 
 #: sequencer.c
 msgid "cannot cherry-pick during a revert."
-msgstr "不能在回退中执行拣选。"
+msgstr "无法在回退中执行拣选。"
 
 #: sequencer.c
 msgid "cannot revert during a cherry-pick."
-msgstr "不能在拣选中执行回退。"
+msgstr "无法在拣选中执行回退。"
 
 #: sequencer.c
 msgid "unusable squash-onto"
@@ -27204,7 +27336,7 @@
 #: sequencer.c
 #, c-format
 msgid "could not create sequencer directory '%s'"
-msgstr "不能创建序列目录 '%s'"
+msgstr "无法创建序列目录 '%s'"
 
 #: sequencer.c
 msgid "no cherry-pick or revert in progress"
@@ -27212,16 +27344,16 @@
 
 #: sequencer.c
 msgid "cannot resolve HEAD"
-msgstr "不能解析 HEAD"
+msgstr "无法解析 HEAD"
 
 #: sequencer.c
 msgid "cannot abort from a branch yet to be born"
-msgstr "不能从尚未建立的分支终止"
+msgstr "无法从尚未建立的分支终止"
 
 #: sequencer.c
 #, c-format
 msgid "cannot read '%s': %s"
-msgstr "不能读取 '%s':%s"
+msgstr "无法读取 '%s':%s"
 
 #: sequencer.c
 msgid "unexpected end of file"
@@ -27258,12 +27390,12 @@
 "have you committed already?\n"
 "try \"git %s --continue\""
 msgstr ""
-"您已经提交了么?\n"
+"您已经提交了吗?\n"
 "试试 \"git %s --continue\""
 
 #: sequencer.c
 msgid "cannot read HEAD"
-msgstr "不能读取 HEAD"
+msgstr "无法读取 HEAD"
 
 #: sequencer.c
 msgid "could not write commit message file"
@@ -27280,7 +27412,7 @@
 "\n"
 "  git rebase --continue\n"
 msgstr ""
-"您现在可以修补这个提交,使用\n"
+"您现在可以修订这个提交,使用\n"
 "\n"
 "  git commit --amend %s\n"
 "\n"
@@ -27291,12 +27423,12 @@
 #: sequencer.c
 #, c-format
 msgid "Could not apply %s... %.*s"
-msgstr "不能应用 %s... %.*s"
+msgstr "无法应用 %s... %.*s"
 
 #: sequencer.c
 #, c-format
 msgid "Could not merge %.*s"
-msgstr "不能合并 %.*s"
+msgstr "无法合并 %.*s"
 
 #: sequencer.c
 #, c-format
@@ -27320,7 +27452,7 @@
 
 #: sequencer.c
 msgid "and made changes to the index and/or the working tree.\n"
-msgstr "并且修改索引和/或工作区。\n"
+msgstr "并已修改索引和/或工作区。\n"
 
 #: sequencer.c
 #, c-format
@@ -27334,7 +27466,7 @@
 msgstr ""
 "执行成功:%s\n"
 "但是在索引和/或工作区中存在变更。\n"
-"提交或贮藏修改,然后运行\n"
+"提交或储藏修改,然后运行\n"
 "\n"
 "  git rebase --continue\n"
 "\n"
@@ -27345,13 +27477,8 @@
 msgstr "非法的标签名称:'%.*s'"
 
 #: sequencer.c
-#, c-format
-msgid "could not resolve '%s'"
-msgstr "无法解析 '%s'"
-
-#: sequencer.c
 msgid "writing fake root commit"
-msgstr "写伪根提交"
+msgstr "正在写入伪根提交"
 
 #: sequencer.c
 msgid "writing squash-onto"
@@ -27359,7 +27486,7 @@
 
 #: sequencer.c
 msgid "cannot merge without a current revision"
-msgstr "没有当前版本不能合并"
+msgstr "没有当前版本,无法合并"
 
 #: sequencer.c
 #, c-format
@@ -27369,21 +27496,21 @@
 #: sequencer.c
 #, c-format
 msgid "nothing to merge: '%.*s'"
-msgstr "无可用合并:'%.*s'"
+msgstr "无可合并内容:'%.*s'"
 
 #: sequencer.c
 msgid "octopus merge cannot be executed on top of a [new root]"
-msgstr "章鱼合并不能在一个新的根提交上执行"
+msgstr "章鱼合并无法在一个新的根提交上执行"
 
 #: sequencer.c
 #, c-format
 msgid "could not get commit message of '%s'"
-msgstr "不能获取 '%s' 的提交说明"
+msgstr "无法获取 '%s' 的提交说明"
 
 #: sequencer.c
 #, c-format
 msgid "could not even attempt to merge '%.*s'"
-msgstr "甚至不能尝试合并 '%.*s'"
+msgstr "甚至无法尝试合并 '%.*s'"
 
 #: sequencer.c
 msgid "merge: Unable to write new index file"
@@ -27415,22 +27542,22 @@
 
 #: sequencer.c
 msgid "Cannot autostash"
-msgstr "无法自动贮藏"
+msgstr "无法自动储藏"
 
 #: sequencer.c
 #, c-format
 msgid "Unexpected stash response: '%s'"
-msgstr "意外的贮藏响应:'%s'"
+msgstr "意外的储藏响应:'%s'"
 
 #: sequencer.c
 #, c-format
 msgid "Could not create directory for '%s'"
-msgstr "不能为 '%s' 创建目录"
+msgstr "无法为 '%s' 创建目录"
 
 #: sequencer.c
 #, c-format
 msgid "Created autostash: %s\n"
-msgstr "创建了自动贮藏:%s\n"
+msgstr "创建了自动储藏:%s\n"
 
 #: sequencer.c
 msgid "could not reset --hard"
@@ -27439,12 +27566,12 @@
 #: sequencer.c
 #, c-format
 msgid "Applied autostash.\n"
-msgstr "已应用自动贮藏。\n"
+msgstr "已应用自动储藏。\n"
 
 #: sequencer.c
 #, c-format
 msgid "cannot store %s"
-msgstr "不能存储 %s"
+msgstr "无法存储 %s"
 
 #: sequencer.c
 #, c-format
@@ -27454,24 +27581,24 @@
 "You can run \"git stash pop\" or \"git stash drop\" at any time.\n"
 msgstr ""
 "%s\n"
-"您的修改在贮藏区中很安全。\n"
+"您的修改在储藏区中很安全。\n"
 "您可以在任何时候运行 \"git stash pop\" 或 \"git stash drop\"。\n"
 
 #: sequencer.c
 msgid "Applying autostash resulted in conflicts."
-msgstr "应用自动贮藏导致冲突。"
+msgstr "应用自动储藏导致冲突。"
 
 #: sequencer.c
 msgid "Autostash exists; creating a new stash entry."
-msgstr "自动贮藏已经存在;正在创建一个新的贮藏条目。"
+msgstr "自动储藏已经存在;正在创建一个新的储藏条目。"
 
 #: sequencer.c
 msgid "autostash reference is a symref"
-msgstr "自动贮藏的引用是一个符号引用"
+msgstr "自动储藏的引用是一个符号引用"
 
 #: sequencer.c
 msgid "could not detach HEAD"
-msgstr "不能分离头指针"
+msgstr "无法分离头指针"
 
 #: sequencer.c
 #, c-format
@@ -27520,16 +27647,16 @@
 
 #: sequencer.c
 msgid "could not read orig-head"
-msgstr "不能读取 orig-head"
+msgstr "无法读取 orig-head"
 
 #: sequencer.c
 msgid "could not read 'onto'"
-msgstr "不能读取 'onto'"
+msgstr "无法读取 'onto'"
 
 #: sequencer.c
 #, c-format
 msgid "could not update HEAD to %s"
-msgstr "不能更新 HEAD 为 %s"
+msgstr "无法更新 HEAD 为 %s"
 
 #: sequencer.c
 #, c-format
@@ -27542,7 +27669,7 @@
 
 #: sequencer.c
 msgid "cannot amend non-existing commit"
-msgstr "不能修补不存在的提交"
+msgstr "无法修订不存在的提交"
 
 #: sequencer.c
 #, c-format
@@ -27566,25 +27693,25 @@
 #: sequencer.c
 #, c-format
 msgid "could not write file: '%s'"
-msgstr "不能写入文件:'%s'"
+msgstr "无法写入文件:'%s'"
 
 #: sequencer.c
 msgid "could not remove CHERRY_PICK_HEAD"
-msgstr "不能删除 CHERRY_PICK_HEAD"
+msgstr "无法删除 CHERRY_PICK_HEAD"
 
 #: sequencer.c
 msgid "could not commit staged changes."
-msgstr "不能提交暂存的修改。"
+msgstr "无法提交暂存的修改。"
 
 #: sequencer.c
 #, c-format
 msgid "%s: can't cherry-pick a %s"
-msgstr "%s:不能拣选一个%s"
+msgstr "%s:不能拣选一个 %s"
 
 #: sequencer.c
 #, c-format
 msgid "%s: bad revision"
-msgstr "%s:错误的版本"
+msgstr "%s:无效的版本"
 
 #: sequencer.c
 msgid "can't revert as initial commit"
@@ -27719,7 +27846,7 @@
 #: setup.c
 #, c-format
 msgid "not a git repository: %s"
-msgstr "不是 git 仓库:%s"
+msgstr "不是 Git 仓库:%s"
 
 #: setup.c
 #, c-format
@@ -27729,12 +27856,12 @@
 #: setup.c
 #, c-format
 msgid "not a git repository: '%s'"
-msgstr "不是 git 仓库:'%s'"
+msgstr "不是 Git 仓库:'%s'"
 
 #: setup.c
 #, c-format
 msgid "cannot chdir to '%s'"
-msgstr "不能切换目录到 '%s'"
+msgstr "无法切换目录到 '%s'"
 
 #: setup.c
 #, c-format
@@ -27761,17 +27888,17 @@
 
 #: setup.c
 msgid "Unable to read current working directory"
-msgstr "不能读取当前工作目录"
+msgstr "无法读取当前工作目录"
 
 #: setup.c
 #, c-format
 msgid "cannot change to '%s'"
-msgstr "不能切换到 '%s'"
+msgstr "无法切换到 '%s'"
 
 #: setup.c
 #, c-format
 msgid "not a git repository (or any of the parent directories): %s"
-msgstr "不是 git 仓库(或者任何父目录):%s"
+msgstr "不是 Git 仓库(或者任何父目录):%s"
 
 #: setup.c
 #, c-format
@@ -27779,7 +27906,7 @@
 "not a git repository (or any parent up to mount point %s)\n"
 "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)."
 msgstr ""
-"不是 git 仓库(或者直至挂载点 %s 的任何父目录)\n"
+"不是 Git 仓库(或者直至挂载点 %s 的任何父目录)\n"
 "停止在文件系统边界(未设置 GIT_DISCOVERY_ACROSS_FILESYSTEM)。"
 
 #: setup.c
@@ -27807,42 +27934,42 @@
 #: setup.c
 #, c-format
 msgid "cannot stat template '%s'"
-msgstr "不能对模版 '%s' 调用 stat"
+msgstr "无法对模板 '%s' 调用 stat"
 
 #: setup.c
 #, c-format
 msgid "cannot opendir '%s'"
-msgstr "不能打开目录 '%s'"
+msgstr "无法打开目录 '%s'"
 
 #: setup.c
 #, c-format
 msgid "cannot readlink '%s'"
-msgstr "不能读取链接 '%s'"
+msgstr "无法读取链接 '%s'"
 
 #: setup.c
 #, c-format
 msgid "cannot symlink '%s' '%s'"
-msgstr "不能自 '%s' 到 '%s' 创建符号链接"
+msgstr "无法从 '%s' 到 '%s' 创建符号链接"
 
 #: setup.c
 #, c-format
 msgid "cannot copy '%s' to '%s'"
-msgstr "不能拷贝 '%s' 至 '%s'"
+msgstr "无法拷贝 '%s' 至 '%s'"
 
 #: setup.c
 #, c-format
 msgid "ignoring template %s"
-msgstr "忽略模版 %s"
+msgstr "忽略模板 %s"
 
 #: setup.c
 #, c-format
 msgid "templates not found in %s"
-msgstr "没有在 %s 中找到模版"
+msgstr "没有在 %s 中找到模板"
 
 #: setup.c
 #, c-format
 msgid "not copying templates from '%s': %s"
-msgstr "没有从 '%s' 复制模版:%s"
+msgstr "没有从 '%s' 复制模板:%s"
 
 #: setup.c
 #, c-format
@@ -27857,12 +27984,12 @@
 #: setup.c
 #, c-format
 msgid "unable to handle file type %d"
-msgstr "不能处理 %d 类型的文件"
+msgstr "无法处理 %d 类型的文件"
 
 #: setup.c
 #, c-format
 msgid "unable to move %s to %s"
-msgstr "不能移动 %s 至 %s"
+msgstr "无法移动 %s 至 %s"
 
 #: setup.c
 msgid "attempt to reinitialize repository with different hash"
@@ -27881,22 +28008,22 @@
 #: setup.c
 #, c-format
 msgid "Reinitialized existing shared Git repository in %s%s\n"
-msgstr "重新初始化已存在的共享 Git 仓库于 %s%s\n"
+msgstr "在 %s%s 重新初始化已存在的共享 Git 仓库\n"
 
 #: setup.c
 #, c-format
 msgid "Reinitialized existing Git repository in %s%s\n"
-msgstr "已重新初始化已存在的 Git 仓库于 %s%s\n"
+msgstr "在 %s%s 重新初始化已存在的 Git 仓库\n"
 
 #: setup.c
 #, c-format
 msgid "Initialized empty shared Git repository in %s%s\n"
-msgstr "已初始化空的共享 Git 仓库于 %s%s\n"
+msgstr "在 %s%s 初始化空的共享 Git 仓库\n"
 
 #: setup.c
 #, c-format
 msgid "Initialized empty Git repository in %s%s\n"
-msgstr "已初始化空的 Git 仓库于 %s%s\n"
+msgstr "在 %s%s 初始化空的 Git 仓库\n"
 
 #: sparse-index.c
 #, c-format
@@ -27911,71 +28038,97 @@
 #: strbuf.c
 #, c-format
 msgid "bad %s format: element '%s' does not start with '('"
-msgstr "坏的 %s 格式:元素 '%s' 没有以 '(' 开头"
+msgstr "无效的 %s 格式:元素 '%s' 没有以 '(' 开头"
 
 #. TRANSLATORS: The first %s is a command like "ls-tree".
 #: strbuf.c
 #, c-format
 msgid "bad %s format: element '%s' does not end in ')'"
-msgstr "坏的 %s 格式:元素 '%s' 没有以 ')' 结尾"
+msgstr "无效的 %s 格式:元素 '%s' 没有以 ')' 结尾"
 
 #. TRANSLATORS: %s is a command like "ls-tree".
 #: strbuf.c
 #, c-format
 msgid "bad %s format: %%%.*s"
-msgstr "坏的 %s 格式: %%%.*s"
+msgstr "无效的 %s 格式:%%%.*s"
 
-#. TRANSLATORS: IEC 80000-13:2008 gibibyte
 #: strbuf.c
 #, c-format
-msgid "%u.%2.2u GiB"
-msgstr "%u.%2.2u GiB"
+msgid "%u.%2.2u"
+msgstr "%u.%2.2u"
 
-#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second
+#. TRANSLATORS: SI decimal prefix symbol for 10^9
 #: strbuf.c
-#, c-format
-msgid "%u.%2.2u GiB/s"
-msgstr "%u.%2.2u GiB/s"
+msgid "G"
+msgstr "G"
 
-#. TRANSLATORS: IEC 80000-13:2008 mebibyte
+#. TRANSLATORS: SI decimal prefix symbol for 10^6
 #: strbuf.c
-#, c-format
-msgid "%u.%2.2u MiB"
-msgstr "%u.%2.2u MiB"
+msgid "M"
+msgstr "M"
 
-#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second
+#. TRANSLATORS: SI decimal prefix symbol for 10^3
 #: strbuf.c
-#, c-format
-msgid "%u.%2.2u MiB/s"
-msgstr "%u.%2.2u MiB/s"
+msgid "k"
+msgstr "k"
 
-#. TRANSLATORS: IEC 80000-13:2008 kibibyte
+#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte
 #: strbuf.c
-#, c-format
-msgid "%u.%2.2u KiB"
-msgstr "%u.%2.2u KiB"
+msgid "GiB/s"
+msgstr "GiB/s"
 
-#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second
 #: strbuf.c
-#, c-format
-msgid "%u.%2.2u KiB/s"
-msgstr "%u.%2.2u KiB/s"
+msgid "GiB"
+msgstr "GiB"
 
-#. TRANSLATORS: IEC 80000-13:2008 byte
+#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte
 #: strbuf.c
-#, c-format
-msgid "%u byte"
-msgid_plural "%u bytes"
-msgstr[0] "%u 字节"
-msgstr[1] "%u 字节"
+msgid "MiB/s"
+msgstr "MiB/s"
+
+#: strbuf.c
+msgid "MiB"
+msgstr "MiB"
+
+#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte
+#: strbuf.c
+msgid "KiB/s"
+msgstr "KiB/s"
+
+#: strbuf.c
+msgid "KiB"
+msgstr "KiB"
+
+#. TRANSLATORS: IEC 80000-13:2008 byte/second and byte
+#: strbuf.c
+msgid "B/s"
+msgstr "B/s"
+
+#: strbuf.c
+msgid "B"
+msgstr "B"
 
 #. TRANSLATORS: IEC 80000-13:2008 byte/second
 #: strbuf.c
+msgid "byte/s"
+msgid_plural "bytes/s"
+msgstr[0] "字节/秒"
+msgstr[1] "字节/秒"
+
+#. TRANSLATORS: IEC 80000-13:2008 byte
+#: strbuf.c
+msgid "byte"
+msgid_plural "bytes"
+msgstr[0] "字节"
+msgstr[1] "字节"
+
+#. TRANSLATORS: The first argument is the number string. The second
+#. argument is the unit string (i.e. "12.34 MiB/s").
+#.
+#: strbuf.c
 #, c-format
-msgid "%u byte/s"
-msgid_plural "%u bytes/s"
-msgstr[0] "%u 字节/秒"
-msgstr[1] "%u 字节/秒"
+msgid "%s %s"
+msgstr "%s %s"
 
 #: submodule-config.c
 #, c-format
@@ -27994,7 +28147,7 @@
 #: submodule-config.c
 #, c-format
 msgid "Could not update .gitmodules entry %s"
-msgstr "不能更新 .gitmodules 条目 %s"
+msgstr "无法更新 .gitmodules 条目 %s"
 
 #: submodule.c
 msgid "Cannot change unmerged .gitmodules, resolve merge conflicts first"
@@ -28027,14 +28180,14 @@
 #: submodule.c
 #, c-format
 msgid "bad --ignore-submodules argument: %s"
-msgstr "坏的 --ignore-submodules 参数:%s"
+msgstr "无效的 --ignore-submodules 参数:%s"
 
 #: submodule.c
 #, c-format
 msgid ""
 "Submodule in commit %s at path: '%s' collides with a submodule named the "
 "same. Skipping it."
-msgstr "提交 %s 中位于路径 '%s' 的子模组和同名的子模组冲突。 跳过它。"
+msgstr "提交 %s 中位于路径 '%s' 的子模组和同名的子模组冲突。跳过它。"
 
 #: submodule.c
 #, c-format
@@ -28076,12 +28229,12 @@
 #: submodule.c
 #, c-format
 msgid "Could not access submodule '%s' at commit %s\n"
-msgstr "无法访问子模组 '%s' 提交 %s\n"
+msgstr "无法访问子模组 '%s' 的提交 %s\n"
 
 #: submodule.c
 #, c-format
 msgid "Fetching submodule %s%s at commit %s\n"
-msgstr "正在获取子模组 %s%s 提交 %s\n"
+msgstr "正在获取子模组 %s%s 的提交 %s\n"
 
 #: submodule.c
 #, c-format
@@ -28095,7 +28248,7 @@
 #: submodule.c
 #, c-format
 msgid "'%s' not recognized as a git repository"
-msgstr "无法将 '%s' 识别为 git 仓库"
+msgstr "无法将 '%s' 识别为 Git 仓库"
 
 #: submodule.c
 #, c-format
@@ -28125,7 +28278,7 @@
 #: submodule.c
 #, c-format
 msgid "could not recurse into submodule '%s'"
-msgstr "无法递归进子模组路径 '%s'"
+msgstr "无法递归进入子模组 '%s'"
 
 #: submodule.c
 msgid "could not reset submodule index"
@@ -28144,7 +28297,7 @@
 #: submodule.c
 #, c-format
 msgid "submodule git dir '%s' is inside git dir '%.*s'"
-msgstr "子模组 git 目录 '%s' 位于 git 目录 '%.*s' 中"
+msgstr "子模组 Git 目录 '%s' 位于 Git 目录 '%.*s' 中"
 
 #: submodule.c
 #, c-format
@@ -28165,12 +28318,12 @@
 #: submodule.c
 #, c-format
 msgid "could not lookup name for submodule '%s'"
-msgstr "不能查询子模组 '%s' 的名称"
+msgstr "无法查询子模组 '%s' 的名称"
 
 #: submodule.c
 #, c-format
 msgid "refusing to move '%s' into an existing git dir"
-msgstr "禁止移动 '%s' 到现存 git 目录中"
+msgstr "拒绝移动 '%s' 到现存 Git 目录中"
 
 #: submodule.c
 #, c-format
@@ -28179,7 +28332,7 @@
 "'%s' to\n"
 "'%s'\n"
 msgstr ""
-"将 '%s%s' 的 git 目录从\n"
+"将 '%s%s' 的 Git 目录从\n"
 "'%s' 迁移至\n"
 "'%s'\n"
 
@@ -28190,7 +28343,7 @@
 #: submodule.c
 #, c-format
 msgid "ls-tree returned unexpected return code %d"
-msgstr "ls-tree 返回未知返回值 %d"
+msgstr "ls-tree 返回意外返回码 %d"
 
 #: symlinks.c
 #, c-format
@@ -28199,7 +28352,7 @@
 
 #: t/helper/test-bundle-uri.c
 msgid "no remote configured to get bundle URIs from"
-msgstr "没有远程被设置为可以获取归档包 URI"
+msgstr "没有配置可用于获取 bundle URI 的远程"
 
 #: t/helper/test-bundle-uri.c
 msgid "could not get the bundle-uri list"
@@ -28264,7 +28417,7 @@
 
 #: t/helper/test-read-midx.c
 msgid "could not determine MIDX preferred pack"
-msgstr "不能确定多包索引的首选包"
+msgstr "无法确定多包索引的首选包"
 
 #: t/helper/test-serve-v2.c
 msgid "test-tool serve-v2 [<options>]"
@@ -28276,43 +28429,43 @@
 
 #: t/helper/test-simple-ipc.c
 msgid "test-helper simple-ipc is-active    [<name>] [<options>]"
-msgstr "test-helper simple-ipc is-active    [<名字>] [<选项>]"
+msgstr "test-helper simple-ipc is-active    [<名称>] [<选项>]"
 
 #: t/helper/test-simple-ipc.c
 msgid "test-helper simple-ipc run-daemon   [<name>] [<threads>]"
-msgstr "test-helper simple-ipc run-daemon   [<名字>] [<线程>]"
+msgstr "test-helper simple-ipc run-daemon   [<名称>] [<线程>]"
 
 #: t/helper/test-simple-ipc.c
 msgid "test-helper simple-ipc start-daemon [<name>] [<threads>] [<max-wait>]"
-msgstr "test-helper simple-ipc start-daemon [<名字>] [<线程>] [<最大等待>]"
+msgstr "test-helper simple-ipc start-daemon [<名称>] [<线程>] [<最大等待>]"
 
 #: t/helper/test-simple-ipc.c
 msgid "test-helper simple-ipc stop-daemon  [<name>] [<max-wait>]"
-msgstr "test-helper simple-ipc stop-daemon  [<名字>] [<最大等待>]"
+msgstr "test-helper simple-ipc stop-daemon  [<名称>] [<最大等待>]"
 
 #: t/helper/test-simple-ipc.c
 msgid "test-helper simple-ipc send         [<name>] [<token>]"
-msgstr "test-helper simple-ipc send         [<名字>] [<令牌>]"
+msgstr "test-helper simple-ipc send         [<名称>] [<令牌>]"
 
 #: t/helper/test-simple-ipc.c
 msgid "test-helper simple-ipc sendbytes    [<name>] [<bytecount>] [<byte>]"
-msgstr "test-helper simple-ipc sendbytes    [<名字>] [<字节数>] [<字节>]"
+msgstr "test-helper simple-ipc sendbytes    [<名称>] [<字节数>] [<字节>]"
 
 #: t/helper/test-simple-ipc.c
 msgid ""
 "test-helper simple-ipc multiple     [<name>] [<threads>] [<bytecount>] "
 "[<batchsize>]"
 msgstr ""
-"test-helper simple-ipc multiple     [<名字>] [<线程>] [<字节计数>] [<批处理大"
+"test-helper simple-ipc multiple     [<名称>] [<线程>] [<字节计数>] [<批处理大"
 "小>]"
 
 #: t/helper/test-simple-ipc.c
 msgid "name or pathname of unix domain socket"
-msgstr "unix 域套接字的名称或路径名"
+msgstr "Unix 域套接字的名称或路径名"
 
 #: t/helper/test-simple-ipc.c
 msgid "named-pipe name"
-msgstr "命名管道的名字"
+msgstr "命名管道的名称"
 
 #: t/helper/test-simple-ipc.c
 msgid "number of threads in server thread pool"
@@ -28331,12 +28484,8 @@
 msgstr "每个线程的请求数"
 
 #: t/helper/test-simple-ipc.c
-msgid "byte"
-msgstr "字节"
-
-#: t/helper/test-simple-ipc.c
 msgid "ballast character"
-msgstr "ballast character"
+msgstr "压仓字符"
 
 #: t/helper/test-simple-ipc.c
 msgid "token"
@@ -28403,7 +28552,7 @@
 msgid ""
 "unknown mandatory capability %s; this remote helper probably needs newer "
 "version of Git"
-msgstr "未知的强制能力 %s,该远程助手可能需要新版本的Git"
+msgstr "未知的强制能力 %s,该远程助手可能需要新版本的 Git"
 
 #: transport-helper.c
 msgid "this remote helper should implement refspec capability"
@@ -28421,7 +28570,7 @@
 
 #: transport-helper.c
 msgid "couldn't run fast-import"
-msgstr "不能执行 fast-import"
+msgstr "无法执行 fast-import"
 
 #: transport-helper.c
 msgid "error while running fast-import"
@@ -28448,7 +28597,7 @@
 #: transport-helper.c
 #, c-format
 msgid "can't connect to subservice %s"
-msgstr "不能连接到子服务 %s"
+msgstr "无法连接到子服务 %s"
 
 #: transport-helper.c transport.c
 msgid "--negotiate-only requires protocol v2"
@@ -28500,7 +28649,7 @@
 
 #: transport-helper.c
 msgid "remote-helper doesn't support push; refspec needed"
-msgstr "remote-heper 不支持推送,需要引用规格"
+msgstr "remote-helper 不支持推送,需要引用规格"
 
 #: transport-helper.c
 #, c-format
@@ -28557,7 +28706,7 @@
 #: transport-helper.c
 #, c-format
 msgid "can't start thread for copying data: %s"
-msgstr "不能启动线程来拷贝数据:%s"
+msgstr "无法启动线程来拷贝数据:%s"
 
 #: transport-helper.c
 #, c-format
@@ -28571,7 +28720,7 @@
 
 #: transport-helper.c
 msgid "can't start thread for copying data"
-msgstr "不能启动线程来拷贝数据"
+msgstr "无法启动线程来拷贝数据"
 
 #: transport.c
 #, c-format
@@ -28581,7 +28730,7 @@
 #: transport.c
 #, c-format
 msgid "could not read bundle '%s'"
-msgstr "无法读取归档包 '%s'"
+msgstr "无法读取捆绑包 '%s'"
 
 #: transport.c
 #, c-format
@@ -28602,7 +28751,7 @@
 
 #: transport.c
 msgid "could not parse transport.color.* config"
-msgstr "不能解析 transport.color.* 配置"
+msgstr "无法解析 transport.color.* 配置"
 
 #: transport.c
 msgid "support for protocol v2 not implemented yet"
@@ -28657,7 +28806,7 @@
 
 #: transport.c
 msgid "failed to push all needed submodules"
-msgstr "不能推送全部需要的子模组"
+msgstr "无法推送全部需要的子模组"
 
 #: transport.c
 msgid "bundle-uri operation not supported by protocol"
@@ -28673,7 +28822,7 @@
 
 #: tree-walk.c
 msgid "too-short tree object"
-msgstr "太短的树对象"
+msgstr "树对象太短"
 
 #: tree-walk.c
 msgid "malformed mode in tree entry"
@@ -28685,7 +28834,7 @@
 
 #: tree-walk.c
 msgid "too-short tree file"
-msgstr "太短的树文件"
+msgstr "树文件太短"
 
 #: unpack-trees.c
 #, c-format
@@ -28694,7 +28843,7 @@
 "%%sPlease commit your changes or stash them before you switch branches."
 msgstr ""
 "您对下列文件的本地修改将被检出操作覆盖:\n"
-"%%s请在切换分支前提交或贮藏您的修改。"
+"%%s请在切换分支前提交或储藏您的修改。"
 
 #: unpack-trees.c
 #, c-format
@@ -28712,7 +28861,7 @@
 "%%sPlease commit your changes or stash them before you merge."
 msgstr ""
 "您对下列文件的本地修改将被合并操作覆盖:\n"
-"%%s请在合并前提交或贮藏您的修改。"
+"%%s请在合并前提交或储藏您的修改。"
 
 #: unpack-trees.c
 #, c-format
@@ -28730,7 +28879,7 @@
 "%%sPlease commit your changes or stash them before you %s."
 msgstr ""
 "您对下列文件的本地修改将被 %s 覆盖:\n"
-"%%s请在 %s 之前提交或贮藏您的修改。"
+"%%s请在 %s 之前提交或储藏您的修改。"
 
 #: unpack-trees.c
 #, c-format
@@ -28872,7 +29021,7 @@
 #: unpack-trees.c
 #, c-format
 msgid "Entry '%s' overlaps with '%s'.  Cannot bind."
-msgstr "条目 '%s' 和 '%s' 重叠。无法合并。"
+msgstr "条目 '%s' 和 '%s' 重叠。无法绑定。"
 
 #: unpack-trees.c
 #, c-format
@@ -28890,7 +29039,7 @@
 "patterns:\n"
 "%s"
 msgstr ""
-"尽管存在稀疏检出模板,以下路径不是最新,因而保留:\n"
+"尽管存在稀疏检出模式,以下路径不是最新,因而保留:\n"
 "%s"
 
 #: unpack-trees.c
@@ -28899,7 +29048,7 @@
 "The following paths are unmerged and were left despite sparse patterns:\n"
 "%s"
 msgstr ""
-"尽管存在稀疏检出模板,以下路径处于未合并状态,因而保留:\n"
+"尽管存在稀疏检出模式,以下路径处于未合并状态,因而保留:\n"
 "%s"
 
 #: unpack-trees.c
@@ -28909,7 +29058,7 @@
 "patterns:\n"
 "%s"
 msgstr ""
-"尽管存在稀疏检出模板,以下路径已经存在,因而未更新:\n"
+"尽管存在稀疏检出模式,以下路径已经存在,因而未更新:\n"
 "%s"
 
 #: unpack-trees.c
@@ -29003,7 +29152,7 @@
 #: usage.c
 #, c-format
 msgid "'%s' is nominated for removal.\n"
-msgstr "'%s' 被提名以移除。\n"
+msgstr "'%s' 已被列入移除计划。\n"
 
 #: usage.c
 #, c-format
@@ -29156,7 +29305,7 @@
 
 #: worktree.c
 msgid "unable to set extensions.relativeWorktrees setting"
-msgstr "无法设定 extensions.relativeWorktrees 的设置"
+msgstr "无法设定 extensions.relativeWorktrees 的配置"
 
 #: wrapper.c
 #, c-format
@@ -29176,11 +29325,11 @@
 #: wrapper.c
 #, c-format
 msgid "unable to access '%s'"
-msgstr "不能访问 '%s'"
+msgstr "无法访问 '%s'"
 
 #: wrapper.c
 msgid "unable to get current working directory"
-msgstr "不能获取当前工作目录"
+msgstr "无法获取当前工作目录"
 
 #: wrapper.c
 msgid "unable to get random bytes"
@@ -29345,8 +29494,8 @@
 #, c-format
 msgid "Your stash currently has %d entry"
 msgid_plural "Your stash currently has %d entries"
-msgstr[0] "您的贮藏区当前有 %d 条记录"
-msgstr[1] "您的贮藏区当前有 %d 条记录"
+msgstr[0] "您的储藏区当前有 %d 条记录"
+msgstr[1] "您的储藏区当前有 %d 条记录"
 
 #: wt-status.c
 msgid "Submodules changed but not updated:"
@@ -29372,8 +29521,8 @@
 "You can use '--no-ahead-behind' to avoid this.\n"
 msgstr ""
 "\n"
-"花了 %.2f 秒才计算出分支的领先/落后范围。\n"
-"为避免,您可以使用 '--no-ahead-behind'。\n"
+"花了 %.2f 秒才计算出分支的领先/落后值。\n"
+"为避免这种情况,您可以使用 '--no-ahead-behind'。\n"
 
 #: wt-status.c
 msgid "You have unmerged paths."
@@ -29445,11 +29594,11 @@
 #: wt-status.c
 #, c-format
 msgid "  (see more in file %s)"
-msgstr "  (更多参见文件 %s)"
+msgstr "  (更多内容参见文件 %s)"
 
 #: wt-status.c
 msgid "No commands remaining."
-msgstr "未剩下任何命令。"
+msgstr "没有剩余命令。"
 
 #: wt-status.c
 #, c-format
@@ -29505,7 +29654,7 @@
 #  译者:注意保持前导空格
 #: wt-status.c
 msgid "  (Once your working directory is clean, run \"git rebase --continue\")"
-msgstr "  (一旦您工作目录提交干净后,运行 \"git rebase --continue\")"
+msgstr "  (一旦您的工作目录干净后,运行 \"git rebase --continue\")"
 
 #: wt-status.c
 #, c-format
@@ -29519,7 +29668,7 @@
 #  译者:注意保持前导空格
 #: wt-status.c
 msgid "  (use \"git commit --amend\" to amend the current commit)"
-msgstr "  (使用 \"git commit --amend\" 修补当前提交)"
+msgstr "  (使用 \"git commit --amend\" 修订当前提交)"
 
 #  译者:注意保持前导空格
 #: wt-status.c
@@ -29563,12 +29712,12 @@
 
 #: wt-status.c
 msgid "Revert currently in progress."
-msgstr "还原操作正在行中。"
+msgstr "还原操作正在进行中。"
 
 #: wt-status.c
 #, c-format
 msgid "You are currently reverting commit %s."
-msgstr "您在执行反转提交 %s 的操作。"
+msgstr "您在执行还原提交 %s 的操作。"
 
 #  译者:注意保持前导空格
 #: wt-status.c
@@ -29593,7 +29742,7 @@
 #  译者:注意保持前导空格
 #: wt-status.c
 msgid "  (use \"git revert --abort\" to cancel the revert operation)"
-msgstr "  (使用 \"git revert --abort\" 以取消反转提交操作)"
+msgstr "  (使用 \"git revert --abort\" 以取消还原操作)"
 
 #: wt-status.c
 #, c-format
@@ -29611,12 +29760,12 @@
 
 #: wt-status.c
 msgid "You are in a sparse checkout."
-msgstr "您处于一个稀疏检出中。"
+msgstr "您处于稀疏检出状态。"
 
 #: wt-status.c
 #, c-format
 msgid "You are in a sparse checkout with %d%% of tracked files present."
-msgstr "您处于稀疏检出状态,包含 %d%% 的跟踪文件"
+msgstr "您处于稀疏检出状态,已包含 %d%% 的跟踪文件。"
 
 #: wt-status.c
 msgid "On branch "
@@ -29664,22 +29813,22 @@
 "It took %.2f seconds to enumerate untracked files,\n"
 "but the results were cached, and subsequent runs may be faster."
 msgstr ""
-"枚举未追踪的文件花了 %.2f 秒,\n"
+"枚举未跟踪的文件花了 %.2f 秒,\n"
 "结果已被缓存,后续的执行会更快。"
 
 #: wt-status.c
 #, c-format
 msgid "It took %.2f seconds to enumerate untracked files."
-msgstr "枚举未追踪的文件花了 %.2f 秒。"
+msgstr "枚举未跟踪的文件花了 %.2f 秒。"
 
 #: wt-status.c
 msgid "See 'git help status' for information on how to improve this."
-msgstr "参见 'git help status' 来获取如何改善的信息。"
+msgstr "参见 'git help status' 了解如何改善。"
 
 #: wt-status.c
 #, c-format
 msgid "Untracked files not listed%s"
-msgstr "未跟踪的文件没有列出%s"
+msgstr "未跟踪的文件未列出%s"
 
 #  译者:中文字符串拼接,可删除前导空格
 #: wt-status.c
@@ -29730,11 +29879,11 @@
 #: wt-status.c
 #, c-format
 msgid "nothing to commit, working tree clean\n"
-msgstr "无文件要提交,干净的工作区\n"
+msgstr "无文件要提交,工作区干净\n"
 
 #: wt-status.c
 msgid "No commits yet on "
-msgstr "尚无提交在 "
+msgstr "尚无提交于 "
 
 #: wt-status.c
 msgid "HEAD (no branch)"
@@ -29757,7 +29906,7 @@
 #: wt-status.c
 #, c-format
 msgid "cannot %s: You have unstaged changes."
-msgstr "不能%s:您有未暂存的变更。"
+msgstr "无法 %s:您有未暂存的变更。"
 
 #: wt-status.c
 msgid "additionally, your index contains uncommitted changes."
@@ -29766,7 +29915,7 @@
 #: wt-status.c
 #, c-format
 msgid "cannot %s: Your index contains uncommitted changes."
-msgstr "不能%s:您的索引中包含未提交的变更。"
+msgstr "不能 %s:您的索引中包含未提交的变更。"
 
 #: xdiff-interface.c
 #, c-format
@@ -29781,7 +29930,7 @@
 
 #: git-merge-octopus.sh
 msgid "Automated merge did not work."
-msgstr "自动合并未生效。"
+msgstr "自动合并未成功。"
 
 #: git-merge-octopus.sh
 msgid "Should not be doing an octopus."
@@ -29819,26 +29968,26 @@
 #: git-sh-setup.sh
 #, sh-format
 msgid "Cannot chdir to $cdup, the toplevel of the working tree"
-msgstr "不能切换目录到 $cdup,工作区的顶级目录"
+msgstr "无法切换到工作区的顶级目录 $cdup"
 
 #: git-sh-setup.sh
 #, sh-format
 msgid "fatal: $program_name cannot be used without a working tree."
-msgstr "致命错误:$program_name 不能在没有工作区的情况下使用"
+msgstr "致命错误:$program_name 无法在没有工作区的情况下使用"
 
 #: git-sh-setup.sh
 msgid "Cannot rewrite branches: You have unstaged changes."
-msgstr "不能重写分支:您有未暂存的变更。"
+msgstr "无法重写分支:您有未暂存的变更。"
 
 #: git-sh-setup.sh
 #, sh-format
 msgid "Cannot $action: You have unstaged changes."
-msgstr "不能 $action:您有未暂存的变更。"
+msgstr "无法 $action:您有未暂存的变更。"
 
 #: git-sh-setup.sh
 #, sh-format
 msgid "Cannot $action: Your index contains uncommitted changes."
-msgstr "不能 $action:您的索引中包含未提交的变更。"
+msgstr "无法 $action:您的索引中包含未提交的变更。"
 
 #: git-sh-setup.sh
 msgid "Additionally, your index contains uncommitted changes."
@@ -29850,11 +29999,11 @@
 
 #: git-sh-setup.sh
 msgid "Unable to determine absolute path of git directory"
-msgstr "不能确定 git 目录的绝对路径"
+msgstr "无法确定 Git 目录的绝对路径"
 
 #: git-send-email.perl
 msgid "local zone differs from GMT by a non-minute interval\n"
-msgstr "本地时间和 GMT 有不到一分钟间隔\n"
+msgstr "本地时间与 GMT 的偏移不是整数分钟\n"
 
 #: git-send-email.perl
 msgid "local time offset greater than or equal to 24 hours\n"
@@ -29878,11 +30027,11 @@
 #: git-send-email.perl
 #, perl-format
 msgid "'%s.final' contains the composed email.\n"
-msgstr "'%s.final' 包含编辑的邮件。\n"
+msgstr "'%s.final' 包含已撰写的邮件。\n"
 
 #: git-send-email.perl
 msgid "--dump-aliases incompatible with other options\n"
-msgstr "--dump-aliases 和其它选项不兼容\n"
+msgstr "--dump-aliases 和其他选项不兼容\n"
 
 #: git-send-email.perl
 msgid "--dump-aliases and --translate-aliases are mutually exclusive\n"
@@ -29900,7 +30049,7 @@
 
 #: git-send-email.perl
 msgid "Cannot run git format-patch from outside a repository\n"
-msgstr "不能在仓库之外运行 git format-patch\n"
+msgstr "无法在仓库之外运行 git format-patch\n"
 
 #: git-send-email.perl
 msgid ""
@@ -29936,7 +30085,7 @@
 #: git-send-email.perl
 #, perl-format
 msgid "warning: sendmail line is not recognized: %s\n"
-msgstr "警告:不能识别的 sendmail 行:%s\n"
+msgstr "警告:无法识别的 sendmail 行:%s\n"
 
 #: git-send-email.perl
 #, perl-format
@@ -29956,7 +30105,7 @@
 #: git-send-email.perl
 #, perl-format
 msgid "Failed to opendir %s: %s"
-msgstr "无法打开目录 %s: %s"
+msgstr "无法打开目录 %s:%s"
 
 #: git-send-email.perl
 msgid ""
@@ -29976,7 +30125,7 @@
 #: git-send-email.perl
 #, perl-format
 msgid "Failed to open for writing %s: %s"
-msgstr "为写入打开 %s 失败: %s"
+msgstr "为写入打开 %s 失败:%s"
 
 #: git-send-email.perl
 msgid ""
@@ -29994,12 +30143,12 @@
 #: git-send-email.perl
 #, perl-format
 msgid "Failed to open %s.final: %s"
-msgstr "无法打开 %s.final: %s"
+msgstr "无法打开 %s.final:%s"
 
 #: git-send-email.perl
 #, perl-format
 msgid "Failed to open %s: %s"
-msgstr "无法打开 %s: %s"
+msgstr "无法打开 %s:%s"
 
 #: git-send-email.perl
 msgid "Summary email is empty, skipping it\n"
@@ -30019,7 +30168,7 @@
 
 #: git-send-email.perl
 msgid "Which 8bit encoding should I declare [UTF-8]? "
-msgstr "要声明 8bit 为什么样的编码格式 [UTF-8]?"
+msgstr "要声明哪种 8bit 编码格式 [UTF-8]?"
 
 #: git-send-email.perl
 #, perl-format
@@ -30031,11 +30180,11 @@
 msgstr ""
 "拒绝发送,因为补丁\n"
 "\t%s\n"
-"包含模版标题 '*** SUBJECT HERE ***'。如果确实想要发送,使用参数 --force。\n"
+"包含模板标题 '*** SUBJECT HERE ***'。如果确实想要发送,使用参数 --force。\n"
 
 #: git-send-email.perl
 msgid "To whom should the emails be sent (if anyone)?"
-msgstr "邮件将要发送给谁?"
+msgstr "邮件将要发送给谁(如果有的话)?"
 
 #: git-send-email.perl
 #, perl-format
@@ -30044,12 +30193,12 @@
 
 #: git-send-email.perl
 msgid "Message-ID to be used as In-Reply-To for the first email (if any)? "
-msgstr "Message-ID 被用作第一封邮件的 In-Reply-To ?"
+msgstr "如有的话,Message-ID 要用作第一封邮件的 In-Reply-To 吗?"
 
 #: git-send-email.perl
 #, perl-format
 msgid "error: unable to extract a valid address from: %s\n"
-msgstr "错误:不能从 %s 中提取一个有效的邮件地址\n"
+msgstr "错误:无法从 %s 中提取一个有效的邮件地址\n"
 
 #. TRANSLATORS: Make sure to include [q] [d] [e] in your
 #. translation. The program will only accept English input
@@ -30077,8 +30226,8 @@
 "\n"
 msgstr ""
 "    以上的抄送列表(Cc)已经用补丁提交信息中发现的地址进行\n"
-"    了扩展。缺省 send-email 会给出提示。这个行为可以通过\n"
-"    sendemail.confirm 配置设置。\n"
+"    了扩展。默认情况下 send-email 会给出提示。这个行为由\n"
+"    sendemail.confirm 配置控制。\n"
 "\n"
 "    更多信息,执行 'git send-email --help'。\n"
 "    要保持当前行为,但不显示此信息,运行 'git config --global\n"
@@ -30090,7 +30239,7 @@
 #. at this point.
 #: git-send-email.perl
 msgid "Send this email? ([y]es|[n]o|[e]dit|[q]uit|[a]ll): "
-msgstr "发送这封邮件?([y]es|[n]o|[e]dit|[q]uit|[a]ll): "
+msgstr "发送这封邮件?([y]es|[n]o|[e]dit|[q]uit|[a]ll):"
 
 #: git-send-email.perl
 msgid "Send this email reply required"
@@ -30135,7 +30284,7 @@
 #: git-send-email.perl
 #, perl-format
 msgid "Dry-Sent %s"
-msgstr "演习发送 %s"
+msgstr "模拟发送 %s"
 
 #: git-send-email.perl
 #, perl-format
@@ -30144,7 +30293,7 @@
 
 #: git-send-email.perl
 msgid "Dry-OK. Log says:"
-msgstr "演习成功。日志说:"
+msgstr "模拟成功。日志说:"
 
 #: git-send-email.perl
 msgid "OK. Log says:"
@@ -30166,22 +30315,22 @@
 #: git-send-email.perl
 #, perl-format
 msgid "(mbox) Adding cc: %s from line '%s'\n"
-msgstr "(mbox) 添加 cc:%s 自行 '%s'\n"
+msgstr "(mbox) 添加 cc:%s 来自行 '%s'\n"
 
 #: git-send-email.perl
 #, perl-format
 msgid "(mbox) Adding to: %s from line '%s'\n"
-msgstr "(mbox) 添加 to:%s 自行 '%s'\n"
+msgstr "(mbox) 添加 to:%s 来自行 '%s'\n"
 
 #: git-send-email.perl
 #, perl-format
 msgid "(non-mbox) Adding cc: %s from line '%s'\n"
-msgstr "(non-mbox) 添加 cc:%s 自行 '%s'\n"
+msgstr "(non-mbox) 添加 cc:%s 来自行 '%s'\n"
 
 #: git-send-email.perl
 #, perl-format
 msgid "(body) Adding cc: %s from line '%s'\n"
-msgstr "(body) 添加 cc: %s 自行 '%s'\n"
+msgstr "(body) 添加 cc:%s 来自行 '%s'\n"
 
 #: git-send-email.perl
 #, perl-format
@@ -30191,12 +30340,12 @@
 #: git-send-email.perl
 #, perl-format
 msgid "(%s) Could not execute '%s'"
-msgstr "(%s) 不能执行 '%s'"
+msgstr "(%s) 无法执行 '%s'"
 
 #: git-send-email.perl
 #, perl-format
 msgid "(%s) Malformed output from '%s'"
-msgstr "(%s) 非法的输出信息,来自于: '%s'"
+msgstr "(%s) 来自 '%s' 的输出信息格式错误"
 
 #: git-send-email.perl
 #, perl-format
@@ -30206,11 +30355,11 @@
 #: git-send-email.perl
 #, perl-format
 msgid "(%s) Adding %s: %s from: '%s'\n"
-msgstr "(%s) 添加 %s: %s 自:'%s'\n"
+msgstr "(%s) 添加 %s:%s 自:'%s'\n"
 
 #: git-send-email.perl
 msgid "cannot send message as 7bit"
-msgstr "不能以 7bit 形式发送信息"
+msgstr "无法以 7bit 形式发送信息"
 
 #: git-send-email.perl
 msgid "invalid transfer encoding"
@@ -30230,7 +30379,7 @@
 #: git-send-email.perl
 #, perl-format
 msgid "unable to open %s: %s\n"
-msgstr "不能打开 %s:%s\n"
+msgstr "无法打开 %s:%s\n"
 
 #: git-send-email.perl
 #, perl-format
diff --git a/po/zh_TW.po b/po/zh_TW.po
index 5301aff..7172d6d 100644
--- a/po/zh_TW.po
+++ b/po/zh_TW.po
@@ -19,9 +19,9 @@
 # - Yichao Yu <yyc1992 AT gmail.com>
 # - Zhuang Ya <zhuangya AT me.com>
 #
-# Yi-Jyun Pan <pan93412@gmail.com>, 2021, 2022, 2023, 2024, 2025.
+# Yi-Jyun Pan <pan93412@gmail.com>, 2021, 2022, 2023, 2024, 2025, 2026.
 # Kaiyang Wu <self@origincode.me>, 2022.
-# Lumynous <lumynou5.tw@gmail.com>, 2023, 2024, 2025.
+# Lumynous <lumynou5.tw@gmail.com>, 2023, 2024, 2025, 2026.
 # Kisaragi Hiu <mail@kisaragi-hiu.com>, 2024.
 # Ngoo Ka-iu <willy04wu69@gmail.com>, 2024.
 # Nightfeather Chen <slat@nightfeather.me>, 2024.
@@ -30,9 +30,9 @@
 msgstr ""
 "Project-Id-Version: Git\n"
 "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2025-11-12 20:14+0800\n"
-"PO-Revision-Date: 2025-11-15 10:02+0000\n"
-"Last-Translator: Lumynous <lumynou5.tw@gmail.com>\n"
+"POT-Creation-Date: 2026-01-28 22:23+0800\n"
+"PO-Revision-Date: 2026-01-28 22:42+0800\n"
+"Last-Translator: Yi-Jyun Pan <pan93412@gmail.com>\n"
 "Language-Team: Chinese (Traditional Han script) <https://weblate.slat.org/"
 "projects/git-po/git-cli/zh_Hant/>\n"
 "Language: zh_TW\n"
@@ -40,9 +40,9 @@
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=1; plural=0;\n"
-"X-Generator: Weblate 5.13.2\n"
-"X-ZhConverter: 繁化姬 dict-f4bc617e-r910 @ 2019/11/16 20:23:12 | "
-"https://zhconvert.org\n"
+"X-Generator: Poedit 3.8\n"
+"X-ZhConverter: 繁化姬 dict-f4bc617e-r910 @ 2019/11/16 20:23:12 | https://"
+"zhconvert.org\n"
 
 #: add-interactive.c
 #, c-format
@@ -962,9 +962,9 @@
 #: builtin/fast-export.c builtin/fetch.c builtin/help.c builtin/index-pack.c
 #: builtin/init-db.c builtin/log.c builtin/ls-files.c builtin/merge-base.c
 #: builtin/merge-tree.c builtin/merge.c builtin/rebase.c builtin/repack.c
-#: builtin/replay.c builtin/reset.c builtin/rev-parse.c builtin/show-branch.c
-#: builtin/stash.c builtin/submodule--helper.c builtin/tag.c builtin/worktree.c
-#: parse-options.c range-diff.c revision.c
+#: builtin/reset.c builtin/rev-parse.c builtin/show-branch.c builtin/stash.c
+#: builtin/submodule--helper.c builtin/tag.c builtin/worktree.c parse-options.c
+#: range-diff.c revision.c
 #, c-format
 msgid "options '%s' and '%s' cannot be used together"
 msgstr "無法同時使用「%s」和「%s」選項"
@@ -2106,8 +2106,8 @@
 msgstr "「%s」不是有效的分支名稱"
 
 #: branch.c builtin/branch.c
-msgid "See `man git check-ref-format`"
-msgstr "請參閱「man git check-ref-format」"
+msgid "See 'git help check-ref-format'"
+msgstr "請參閱「git help check-ref-format」"
 
 #: branch.c
 #, c-format
@@ -3228,6 +3228,18 @@
 msgid "must end with a color"
 msgstr "結尾必須是一個顏色"
 
+#: builtin/blame.c diff.c merge-ort.c transport.c
+#, c-format
+msgid "unknown value for config '%s': %s"
+msgstr "設定 '%s' 的取值未知:%s"
+
+#: builtin/blame.c builtin/merge-file.c diff.c
+msgid ""
+"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and "
+"\"histogram\""
+msgstr ""
+"diff-algorithm 選項有 \"myers\"、\"minimal\"、\"patience\" 和 \"histogram\""
+
 #: builtin/blame.c
 #, c-format
 msgid "cannot find revision %s to ignore"
@@ -3299,6 +3311,14 @@
 msgid "ignore whitespace differences"
 msgstr "忽略空白差異"
 
+#: builtin/blame.c builtin/merge-file.c diff.c
+msgid "<algorithm>"
+msgstr "<演算法>"
+
+#: builtin/blame.c builtin/merge-file.c diff.c
+msgid "choose a diff algorithm"
+msgstr "選擇一個差異演算法"
+
 #: builtin/blame.c builtin/clone.c builtin/log.c
 msgid "rev"
 msgstr "rev"
@@ -3320,8 +3340,8 @@
 msgstr "根據時間著色"
 
 #: builtin/blame.c
-msgid "spend extra cycles to find better match"
-msgstr "循環更多次來找到更佳符合項目"
+msgid "spend extra cycles to find a better match"
+msgstr "多跑幾輪來找到更符合的項目"
 
 #: builtin/blame.c
 msgid "use revisions from <file> instead of calling git-rev-list"
@@ -3868,7 +3888,7 @@
 "您可刪除任何您不想分享的地方。\n"
 
 #: builtin/bugreport.c builtin/commit.c builtin/fast-export.c
-#: builtin/pack-objects.c builtin/rebase.c parse-options.h
+#: builtin/pack-objects.c builtin/rebase.c builtin/replay.c parse-options.h
 msgid "mode"
 msgstr "mode"
 
@@ -4779,10 +4799,10 @@
 msgid "missing branch name; try -%c"
 msgstr "缺少分支名稱;請嘗試 -%c"
 
-#: builtin/checkout.c
+#: builtin/checkout.c sequencer.c
 #, c-format
-msgid "could not resolve %s"
-msgstr "無法解析 %s"
+msgid "could not resolve '%s'"
+msgstr "無法解析「%s」"
 
 #: builtin/checkout.c
 msgid "invalid path specification"
@@ -6617,10 +6637,18 @@
 #, c-format
 msgid ""
 "cannot overwrite multiple values with a single value\n"
-"       Use a regexp, --add or --replace-all to change %s."
+"       Use --value=<pattern>, --append or --all to change %s."
 msgstr ""
 "無法用一個值覆蓋多個值\n"
-"       使用一個常規表示式、--add 或 --replace-all 來修改 %s。"
+"       使用 --value=<pattern>、--append 或 --all 來變更 %s。"
+
+#: builtin/config.c
+msgid "unset all multi-valued config options"
+msgstr "取消設定所有多值組態選項"
+
+#: builtin/config.c
+msgid "unset multi-valued config options with matching values"
+msgstr "取消設定其值相符的多值組態選項"
 
 #: builtin/config.c
 #, c-format
@@ -6730,6 +6758,15 @@
 msgid "--comment is only applicable to add/set/replace operations"
 msgstr "--comment 只能用於加入、設定和取代動作"
 
+#: builtin/config.c
+#, c-format
+msgid ""
+"cannot overwrite multiple values with a single value\n"
+"       Use a regexp, --add or --replace-all to change %s."
+msgstr ""
+"無法用一個值覆蓋多個值\n"
+"       使用一個常規表示式、--add 或 --replace-all 來修改 %s。"
+
 #: builtin/count-objects.c
 msgid "print sizes in human readable format"
 msgstr "以使用者可讀的格式顯示大小"
@@ -7236,11 +7273,6 @@
 
 #: builtin/fast-export.c
 #, c-format
-msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it"
-msgstr "發現已簽署的提交 %s;使用 --signed-commits=<mode> 來處理"
-
-#: builtin/fast-export.c
-#, c-format
 msgid "exporting %<PRIuMAX> signature(s) for commit %s"
 msgstr "匯出提交 %2$s 的 %1$<PRIuMAX> 個簽章"
 
@@ -7251,6 +7283,19 @@
 
 #: builtin/fast-export.c
 #, c-format
+msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it"
+msgstr "發現已簽署的提交 %s;使用 --signed-commits=<mode> 來處理"
+
+#: builtin/fast-export.c
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-export with --signed-"
+"commits=<mode>"
+msgstr ""
+"「strip-if-invalid」不是搭配 --signed-commits=<mode> 使用 git fast-export 的"
+"有效模式"
+
+#: builtin/fast-export.c
+#, c-format
 msgid ""
 "omitting tag %s,\n"
 "since tags of trees (or tags of tags of trees, etc.) are not supported."
@@ -7265,11 +7310,6 @@
 
 #: builtin/fast-export.c
 #, c-format
-msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it"
-msgstr "發現已簽署的標籤 %s;使用 --signed-tags=<mode> 來處理"
-
-#: builtin/fast-export.c
-#, c-format
 msgid "exporting signed tag %s"
 msgstr "匯出已簽署的標籤 %s"
 
@@ -7280,6 +7320,19 @@
 
 #: builtin/fast-export.c
 #, c-format
+msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it"
+msgstr "發現已簽署的標籤 %s;使用 --signed-tags=<mode> 來處理"
+
+#: builtin/fast-export.c
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-export with --signed-"
+"tags=<mode>"
+msgstr ""
+"「strip-if-invalid」不是搭配 --signed-tags=<mode> 使用 git fast-export 的有效"
+"模式"
+
+#: builtin/fast-export.c
+#, c-format
 msgid ""
 "tag %s tags unexported object; use --tag-of-filtered-object=<mode> to handle "
 "it"
@@ -7769,6 +7822,37 @@
 msgid "parse_one_signature() returned unknown hash algo"
 msgstr "parse_one_signature() 回傳了未知的雜湊演算法"
 
+#: builtin/fast-import.c builtin/fsck.c
+msgid "unknown"
+msgstr "未知"
+
+#: builtin/fast-import.c
+#, c-format
+msgid ""
+"stripping invalid signature for commit '%.100s...'\n"
+"  allegedly by %s"
+msgstr ""
+"正在去除提交「%.100s...」的無效簽章\n"
+"  據稱由 %s 所簽"
+
+#: builtin/fast-import.c
+#, c-format
+msgid ""
+"stripping invalid signature for commit '%.*s'\n"
+"  allegedly by %s"
+msgstr ""
+"正在去除去除「%.*s..」的無效簽章\n"
+"  據稱由 %s 所簽"
+
+#: builtin/fast-import.c
+#, c-format
+msgid ""
+"stripping invalid signature for commit\n"
+"  allegedly by %s"
+msgstr ""
+"正在去除提交的無效簽章\n"
+"  據稱由 %s 所簽"
+
 #: builtin/fast-import.c
 msgid "expected committer but didn't get one"
 msgstr "預期有提交者,卻沒收到"
@@ -7786,10 +7870,6 @@
 msgstr "按原樣匯入提交簽章"
 
 #: builtin/fast-import.c
-msgid "encountered signed tag; use --signed-tags=<mode> to handle it"
-msgstr "發現已簽署的標籤;使用 --signed-tags=<mode> 來處理"
-
-#: builtin/fast-import.c
 #, c-format
 msgid "importing a tag signature verbatim for tag '%s'"
 msgstr "按原樣匯入標籤「%s」的簽章"
@@ -7800,6 +7880,18 @@
 msgstr "去除標籤「%s」的簽章"
 
 #: builtin/fast-import.c
+msgid "encountered signed tag; use --signed-tags=<mode> to handle it"
+msgstr "發現已簽署的標籤;使用 --signed-tags=<mode> 來處理"
+
+#: builtin/fast-import.c
+msgid ""
+"'strip-if-invalid' is not a valid mode for git fast-import with --signed-"
+"tags=<mode>"
+msgstr ""
+"「strip-if-invalid」不是搭配 --signed-tags=<mode> 使用 git fast-import 的有效"
+"模式"
+
+#: builtin/fast-import.c
 #, c-format
 msgid "expected 'from' command, got '%s'"
 msgstr "預期「from」命令,卻收到「%s」"
@@ -7997,8 +8089,8 @@
 msgstr "git fetch [<選項>] <組>"
 
 #: builtin/fetch.c
-msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]"
-msgstr "git fetch --multiple [<選項>] [(<版本庫> | <組>)...]"
+msgid "git fetch --multiple [<options>] [(<repository>|<group>)...]"
+msgstr "git fetch --multiple [<options>] [(<repository>|<group>)...]"
 
 #: builtin/fetch.c
 msgid "git fetch --all [<options>]"
@@ -8574,10 +8666,6 @@
 msgid "got bad config --config=%s"
 msgstr "收到無效的組態 --config=%s"
 
-#: builtin/fsck.c
-msgid "unknown"
-msgstr "未知"
-
 #. TRANSLATORS: e.g. error in tree 01bfda: <more explanation>
 #: builtin/fsck.c
 #, c-format
@@ -8697,6 +8785,11 @@
 msgstr "%s:不是一個提交"
 
 #: builtin/fsck.c
+#, c-format
+msgid "invalid parameter: expected sha1, got '%s'"
+msgstr "無效的參數:期望 sha1,得到 '%s'"
+
+#: builtin/fsck.c
 msgid "notice: No default references"
 msgstr "注意:無預設引用"
 
@@ -8730,31 +8823,6 @@
 
 #: builtin/fsck.c
 #, c-format
-msgid "Checking %s link"
-msgstr "正在檢查 %s 連結"
-
-#: builtin/fsck.c builtin/index-pack.c
-#, c-format
-msgid "invalid %s"
-msgstr "無效的 %s"
-
-#: builtin/fsck.c
-#, c-format
-msgid "%s points to something strange (%s)"
-msgstr "%s 指向奇怪的東西(%s)"
-
-#: builtin/fsck.c
-#, c-format
-msgid "%s: detached HEAD points at nothing"
-msgstr "%s:分離開頭指標的指向不存在"
-
-#: builtin/fsck.c
-#, c-format
-msgid "notice: %s points to an unborn branch (%s)"
-msgstr "注意:%s 指向一個尚未誕生的分支(%s)"
-
-#: builtin/fsck.c
-#, c-format
 msgid "Checking cache tree of %s"
 msgstr "正在檢查 %s 的快取樹"
 
@@ -8854,16 +8922,6 @@
 msgid "Checking objects"
 msgstr "正在檢查物件"
 
-#: builtin/fsck.c
-#, c-format
-msgid "%s: object missing"
-msgstr "%s:物件缺少"
-
-#: builtin/fsck.c
-#, c-format
-msgid "invalid parameter: expected sha1, got '%s'"
-msgstr "無效的參數:期望 sha1,得到 '%s'"
-
 #: builtin/fsmonitor--daemon.c
 msgid "git fsmonitor--daemon start [<options>]"
 msgstr "git fsmonitor--daemon start [<options>]"
@@ -9336,6 +9394,10 @@
 msgstr "無法將版本庫加至全域設定"
 
 #: builtin/gc.c
+msgid "check a specific task"
+msgstr "檢查指定作業"
+
+#: builtin/gc.c
 msgid "git maintenance <subcommand> [<options>]"
 msgstr "git maintenance <子命令> [<選項>]"
 
@@ -9910,6 +9972,11 @@
 
 #: builtin/index-pack.c
 #, c-format
+msgid "invalid %s"
+msgstr "無效的 %s"
+
+#: builtin/index-pack.c
+#, c-format
 msgid "Not all child objects of %s are reachable"
 msgstr "%s 的所有子物件並非都可以取得"
 
@@ -10079,6 +10146,10 @@
 msgid "--verify with no packfile name given"
 msgstr "--verify 沒有提供 packfile 名稱參數"
 
+#: builtin/index-pack.c
+msgid "cannot perform queued object checks outside of a repository"
+msgstr "無法在版本庫外執行排入佇列的物件檢查"
+
 #: builtin/index-pack.c builtin/unpack-objects.c
 msgid "fsck error in pack objects"
 msgstr "在打包物件中 fsck 檢查發生錯誤"
@@ -11033,13 +11104,6 @@
 "git merge-file [<選項>] [-L <檔案1> [-L <初始> [-L <名字2>]]] <檔案1> <初始文"
 "件> <檔案2>"
 
-#: builtin/merge-file.c diff.c
-msgid ""
-"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and "
-"\"histogram\""
-msgstr ""
-"diff-algorithm 選項有 \"myers\"、\"minimal\"、\"patience\" 和 \"histogram\""
-
 #: builtin/merge-file.c
 msgid "send results to standard output"
 msgstr "將結果傳送到標準輸出"
@@ -11056,14 +11120,6 @@
 msgid "use a zealous diff3 based merge"
 msgstr "使用基於 zealous diff3 的合併"
 
-#: builtin/merge-file.c diff.c
-msgid "<algorithm>"
-msgstr "<演算法>"
-
-#: builtin/merge-file.c diff.c
-msgid "choose a diff algorithm"
-msgstr "選擇一個差異演算法"
-
 #: builtin/merge-file.c
 msgid "for conflicts, use this marker size"
 msgstr "如果衝突,使用指定長度的標記"
@@ -12392,6 +12448,11 @@
 
 #: builtin/pack-objects.c
 #, c-format
+msgid "packfile %s is a promisor but --exclude-promisor-objects was given"
+msgstr "封包檔案 %s 是承諾者,但是指定了 --exclude-promisor-objects"
+
+#: builtin/pack-objects.c
+#, c-format
 msgid "could not find pack '%s'"
 msgstr "找不到「%s」包"
 
@@ -12721,12 +12782,12 @@
 msgstr "git patch-id [--stable | --unstable | --verbatim]"
 
 #: builtin/patch-id.c
-msgid "use the unstable patch-id algorithm"
-msgstr "使用不穩定的 patch-id 演算法"
+msgid "use the unstable patch ID algorithm"
+msgstr "使用不穩定的修補檔 ID 演算法"
 
 #: builtin/patch-id.c
-msgid "use the stable patch-id algorithm"
-msgstr "使用穩定的 patch-id 演算法"
+msgid "use the stable patch ID algorithm"
+msgstr "使用穩定的修補檔 ID 演算法"
 
 #: builtin/patch-id.c
 msgid "don't strip whitespace from the patch"
@@ -12757,50 +12818,6 @@
 msgstr "git pull [<選項>] [<版本庫> [<引用規格>...]]"
 
 #: builtin/pull.c
-msgid "control for recursive fetching of submodules"
-msgstr "控制子模組的遞迴取得"
-
-#: builtin/pull.c
-msgid "Options related to merging"
-msgstr "和合併相關的選項"
-
-#: builtin/pull.c
-msgid "incorporate changes by rebasing rather than merging"
-msgstr "使用重定基底動作取代合併動作以套用修改"
-
-#: builtin/pull.c builtin/revert.c
-msgid "allow fast-forward"
-msgstr "允許快轉式"
-
-#: builtin/pull.c
-msgid "control use of pre-merge-commit and commit-msg hooks"
-msgstr "控制 pre-merge-commit 和 commit-msg 掛鉤的使用"
-
-#: builtin/pull.c parse-options.h
-msgid "automatically stash/stash pop before and after"
-msgstr "在動作前後執行自動貯存和彈出貯存"
-
-#: builtin/pull.c
-msgid "Options related to fetching"
-msgstr "和取得相關的參數"
-
-#: builtin/pull.c
-msgid "force overwrite of local branch"
-msgstr "強制覆蓋本機分支"
-
-#: builtin/pull.c
-msgid "number of submodules pulled in parallel"
-msgstr "並行拉取的子模組數量"
-
-#: builtin/pull.c parse-options.h
-msgid "use IPv4 addresses only"
-msgstr "只使用 IPv4 位址"
-
-#: builtin/pull.c parse-options.h
-msgid "use IPv6 addresses only"
-msgstr "只使用 IPv6 位址"
-
-#: builtin/pull.c
 msgid ""
 "There is no candidate for rebasing against among the refs that you just "
 "fetched."
@@ -12913,6 +12930,50 @@
 "設定的預設值。\n"
 
 #: builtin/pull.c
+msgid "control for recursive fetching of submodules"
+msgstr "控制子模組的遞迴取得"
+
+#: builtin/pull.c
+msgid "Options related to merging"
+msgstr "和合併相關的選項"
+
+#: builtin/pull.c
+msgid "incorporate changes by rebasing rather than merging"
+msgstr "使用重定基底動作取代合併動作以套用修改"
+
+#: builtin/pull.c builtin/revert.c
+msgid "allow fast-forward"
+msgstr "允許快轉式"
+
+#: builtin/pull.c
+msgid "control use of pre-merge-commit and commit-msg hooks"
+msgstr "控制 pre-merge-commit 和 commit-msg 掛鉤的使用"
+
+#: builtin/pull.c parse-options.h
+msgid "automatically stash/stash pop before and after"
+msgstr "在動作前後執行自動貯存和彈出貯存"
+
+#: builtin/pull.c
+msgid "Options related to fetching"
+msgstr "和取得相關的參數"
+
+#: builtin/pull.c
+msgid "force overwrite of local branch"
+msgstr "強制覆蓋本機分支"
+
+#: builtin/pull.c
+msgid "number of submodules pulled in parallel"
+msgstr "並行拉取的子模組數量"
+
+#: builtin/pull.c parse-options.h
+msgid "use IPv4 addresses only"
+msgstr "只使用 IPv4 位址"
+
+#: builtin/pull.c parse-options.h
+msgid "use IPv6 addresses only"
+msgstr "只使用 IPv6 位址"
+
+#: builtin/pull.c
 msgid "Updating an unborn branch with changes added to the index."
 msgstr "更新尚未誕生的分支,變更新增至索引。"
 
@@ -15139,6 +15200,11 @@
 msgstr "只能為 -l 提供一個模式"
 
 #: builtin/replay.c
+#, c-format
+msgid "'%s' is not a valid commit-ish for %s"
+msgstr "「%s」不是 %s 有效的提交指示元"
+
+#: builtin/replay.c
 msgid "need some commits to replay"
 msgstr "需要一些提交才能重放"
 
@@ -15157,27 +15223,17 @@
 msgstr "無法用多個來源演進目的地,以免無法確定排序"
 
 #: builtin/replay.c
-msgid ""
-"cannot implicitly determine whether this is an --advance or --onto operation"
-msgstr "無法假設這是 --advance 還是 --onto 動作"
-
-#: builtin/replay.c
-msgid ""
-"cannot advance target with multiple source branches because ordering would "
-"be ill-defined"
-msgstr "無法由多個來源分支演進目的地,以免無法確定排序"
-
-#: builtin/replay.c
-msgid "cannot implicitly determine correct base for --onto"
-msgstr "無法假設 --onto 的正確基底"
+#, c-format
+msgid "invalid %s value: '%s'"
+msgstr "無效的 %s 值:「%s」"
 
 #: builtin/replay.c
 msgid ""
 "(EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance "
-"<branch>) <revision-range>..."
+"<branch>) [--ref-action[=<mode>]] <revision-range>"
 msgstr ""
-"(實驗性功能!)git replay ([--contained] --onto <newbase> | --advance "
-"<branch>) <revision-range>..."
+"(實驗性功能!) git replay ([--contained] --onto <newbase> | --advance "
+"<branch>) [--ref-action[=<mode>]] <revision-range>"
 
 #: builtin/replay.c
 msgid "make replay advance given branch"
@@ -15188,8 +15244,12 @@
 msgstr "重放到指定提交"
 
 #: builtin/replay.c
-msgid "advance all branches contained in revision-range"
-msgstr "演進所有包含在 revision-range 中的分支"
+msgid "update all branches that point at commits in <revision-range>"
+msgstr "更新所有指向 <revision-range> 範圍中提交的分支"
+
+#: builtin/replay.c
+msgid "control ref update behavior (update|print)"
+msgstr "控制引用的更新行為(update|print)"
 
 #: builtin/replay.c
 msgid "option --onto or --advance is mandatory"
@@ -15203,17 +15263,32 @@
 msgstr "將覆寫部分修訂版遍歷選項,強制使用「struct rev_info」的「%s」位元"
 
 #: builtin/replay.c
+#, c-format
+msgid "failed to begin ref transaction: %s"
+msgstr "無法開始引用事務:%s"
+
+#: builtin/replay.c
 msgid "error preparing revisions"
 msgstr "無法準備修訂集"
 
 #: builtin/replay.c
-msgid "replaying down to root commit is not supported yet!"
-msgstr "尚不支援重放到根提交!"
+msgid "replaying down from root commit is not supported yet!"
+msgstr "尚不支援從根提交重放!"
 
 #: builtin/replay.c
 msgid "replaying merge commits is not supported yet!"
 msgstr "尚不支援重放合併提交!"
 
+#: builtin/replay.c
+#, c-format
+msgid "failed to update ref '%s': %s"
+msgstr "無法更新「%s」引用:%s"
+
+#: builtin/replay.c
+#, c-format
+msgid "failed to commit ref transaction: %s"
+msgstr "無法提交引用事務:%s"
+
 #: builtin/repo.c
 #, c-format
 msgid "key '%s' not found"
@@ -15233,10 +15308,18 @@
 msgstr "和 --format=nul 同義"
 
 #: builtin/repo.c
+msgid "print all keys/values"
+msgstr "輸出所有鍵值對"
+
+#: builtin/repo.c
 msgid "unsupported output format"
 msgstr "不支援的輸出格式"
 
 #: builtin/repo.c
+msgid "--all and <key> cannot be used together"
+msgstr "--all 和 <key> 不能同時使用"
+
+#: builtin/repo.c
 msgid "References"
 msgstr "引用"
 
@@ -15277,6 +15360,14 @@
 msgstr "資料物件"
 
 #: builtin/repo.c
+msgid "Inflated size"
+msgstr "未壓縮大小"
+
+#: builtin/repo.c
+msgid "Disk size"
+msgstr "磁碟大小"
+
+#: builtin/repo.c
 msgid "Repository structure"
 msgstr "版本庫結構"
 
@@ -17261,7 +17352,7 @@
 msgid "'%s' already exists in the index and is not a submodule"
 msgstr "「%s」已在索引中,且不是子模組"
 
-#: builtin/submodule--helper.c read-cache.c
+#: builtin/submodule--helper.c object-file.c
 #, c-format
 msgid "'%s' does not have a commit checked out"
 msgstr "'%s' 沒有簽出一個提交"
@@ -18314,6 +18405,11 @@
 msgstr "位於「%s」的套件包清單沒有模式"
 
 #: bundle-uri.c
+#, c-format
+msgid "bundle list at '%s': bundle '%s' has no uri"
+msgstr "套件包清單,位於「%s」:「%s」套件包沒有 URI"
+
+#: bundle-uri.c
 msgid "failed to create temporary file"
 msgstr "無法建立暫存檔"
 
@@ -18342,6 +18438,11 @@
 
 #: bundle-uri.c
 #, c-format
+msgid "bundle '%s' has no uri"
+msgstr "「%s」套件包沒有 URI"
+
+#: bundle-uri.c
+#, c-format
 msgid "failed to download bundle from URI '%s'"
 msgstr "無法從「%s」URI 下載套件包"
 
@@ -18982,8 +19083,8 @@
 msgstr "重用衝突合併的解決方案記錄"
 
 #: command-list.h
-msgid "Reset current HEAD to the specified state"
-msgstr "重設目前 HEAD 到指定狀態"
+msgid "Set `HEAD` or the index to a known state"
+msgstr "將 `HEAD` 或索引設為已知狀態"
 
 #: command-list.h
 msgid "Restore working tree files"
@@ -20821,11 +20922,6 @@
 msgid "Unknown value for 'diff.submodule' config variable: '%s'"
 msgstr "設定變數 'diff.submodule' 未知的取值:'%s'"
 
-#: diff.c merge-ort.c transport.c
-#, c-format
-msgid "unknown value for config '%s': %s"
-msgstr "設定 '%s' 的取值未知:%s"
-
 #: diff.c
 #, c-format
 msgid ""
@@ -23388,6 +23484,10 @@
 msgstr "%s:插入資料庫失敗"
 
 #: object-file.c
+msgid "cannot add a submodule of a different hash algorithm"
+msgstr "無法加入使用不同雜湊演算法的子模組"
+
+#: object-file.c
 #, c-format
 msgid "%s: unsupported file type"
 msgstr "%s:不支援的檔案類型"
@@ -25117,6 +25217,11 @@
 msgstr "--format=%.*s 無法和 --python、--shell、--tcl 一起使用"
 
 #: ref-filter.c
+#, c-format
+msgid "parse_object_buffer failed on %s for %s"
+msgstr "parse_object_buffer 失敗於 %2$s 的 %1$s"
+
+#: ref-filter.c
 msgid "failed to run 'describe'"
 msgstr "無法執行「describe」"
 
@@ -25156,11 +25261,6 @@
 
 #: ref-filter.c
 #, c-format
-msgid "parse_object_buffer failed on %s for %s"
-msgstr "parse_object_buffer 失敗於 %2$s 的 %1$s"
-
-#: ref-filter.c
-#, c-format
 msgid "malformed object at '%s'"
 msgstr "格式錯誤的物件 '%s'"
 
@@ -25510,6 +25610,21 @@
 msgid "refname %s is a symbolic ref, copying it is not supported"
 msgstr "引用名稱 %s 是符號引用,不支援複製"
 
+#: refs/reftable-backend.c
+#, c-format
+msgid "reftable stack for worktree '%s' is broken"
+msgstr "「%s」工作區的 reftable 堆疊已經損壞"
+
+#: refs/reftable-backend.c
+#, c-format
+msgid "could not create iterator for worktree '%s'"
+msgstr "無法建立「%s」工作區的迭代器"
+
+#: refs/reftable-backend.c
+#, c-format
+msgid "could not read record for worktree '%s'"
+msgstr "無法讀取「%s」工作區的記錄"
+
 #: refspec.c
 #, c-format
 msgid "pattern '%s' has no '*'"
@@ -26009,6 +26124,10 @@
 msgid "could not finish pack-objects to repack promisor objects"
 msgstr "無法結束 pack-objects 來重新打包 promisor 物件"
 
+#: repack-promisor.c
+msgid "could not start pack-objects to repack promisor packs"
+msgstr "無法開始 pack-objects 來重新打包 promisor 封包"
+
 #: repack.c
 #, c-format
 msgid "pack prefix %s does not begin with objdir %s"
@@ -27221,11 +27340,6 @@
 msgstr "非法的標籤名稱:'%.*s'"
 
 #: sequencer.c
-#, c-format
-msgid "could not resolve '%s'"
-msgstr "無法解析「%s」"
-
-#: sequencer.c
 msgid "writing fake root commit"
 msgstr "寫偽根提交"
 
@@ -27799,55 +27913,81 @@
 msgid "bad %s format: %%%.*s"
 msgstr "無效的 %s 格式:%%%.*s"
 
-#. TRANSLATORS: IEC 80000-13:2008 gibibyte
 #: strbuf.c
 #, c-format
-msgid "%u.%2.2u GiB"
-msgstr "%u.%2.2u GiB"
+msgid "%u.%2.2u"
+msgstr "%u.%2.2u"
 
-#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second
+#. TRANSLATORS: SI decimal prefix symbol for 10^9
 #: strbuf.c
-#, c-format
-msgid "%u.%2.2u GiB/s"
-msgstr "%u.%2.2u GiB/s"
+msgid "G"
+msgstr "G"
 
-#. TRANSLATORS: IEC 80000-13:2008 mebibyte
+#. TRANSLATORS: SI decimal prefix symbol for 10^6
 #: strbuf.c
-#, c-format
-msgid "%u.%2.2u MiB"
-msgstr "%u.%2.2u MiB"
+msgid "M"
+msgstr "M"
 
-#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second
+#. TRANSLATORS: SI decimal prefix symbol for 10^3
 #: strbuf.c
-#, c-format
-msgid "%u.%2.2u MiB/s"
-msgstr "%u.%2.2u MiB/s"
+msgid "k"
+msgstr "k"
 
-#. TRANSLATORS: IEC 80000-13:2008 kibibyte
+#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte
 #: strbuf.c
-#, c-format
-msgid "%u.%2.2u KiB"
-msgstr "%u.%2.2u KiB"
+msgid "GiB/s"
+msgstr "GiB/秒"
 
-#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second
 #: strbuf.c
-#, c-format
-msgid "%u.%2.2u KiB/s"
-msgstr "%u.%2.2u KiB/s"
+msgid "GiB"
+msgstr "GiB"
 
-#. TRANSLATORS: IEC 80000-13:2008 byte
+#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte
 #: strbuf.c
-#, c-format
-msgid "%u byte"
-msgid_plural "%u bytes"
-msgstr[0] "%u 位元組"
+msgid "MiB/s"
+msgstr "MiB/秒"
+
+#: strbuf.c
+msgid "MiB"
+msgstr "MiB"
+
+#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte
+#: strbuf.c
+msgid "KiB/s"
+msgstr "KiB/秒"
+
+#: strbuf.c
+msgid "KiB"
+msgstr "KiB"
+
+#. TRANSLATORS: IEC 80000-13:2008 byte/second and byte
+#: strbuf.c
+msgid "B/s"
+msgstr "B/秒"
+
+#: strbuf.c
+msgid "B"
+msgstr "B"
 
 #. TRANSLATORS: IEC 80000-13:2008 byte/second
 #: strbuf.c
+msgid "byte/s"
+msgid_plural "bytes/s"
+msgstr[0] "位元組/秒"
+
+#. TRANSLATORS: IEC 80000-13:2008 byte
+#: strbuf.c
+msgid "byte"
+msgid_plural "bytes"
+msgstr[0] "位元組"
+
+#. TRANSLATORS: The first argument is the number string. The second
+#. argument is the unit string (i.e. "12.34 MiB/s").
+#.
+#: strbuf.c
 #, c-format
-msgid "%u byte/s"
-msgid_plural "%u bytes/s"
-msgstr[0] "%u 位元組/秒"
+msgid "%s %s"
+msgstr "%s %s"
 
 #: submodule-config.c
 #, c-format
@@ -28203,10 +28343,6 @@
 msgstr "每個執行緒的請求數"
 
 #: t/helper/test-simple-ipc.c
-msgid "byte"
-msgstr "位元組"
-
-#: t/helper/test-simple-ipc.c
 msgid "ballast character"
 msgstr "穩定 (ballast) 字元"
 
@@ -30123,511 +30259,3 @@
 #, perl-format
 msgid "Do you really want to send %s? [y|N]: "
 msgstr "您真的要傳送 %s?[y|N]: "
-
-#~ msgid "No previous hunk"
-#~ msgstr "沒有上一個區塊"
-
-#~ msgid "No next hunk"
-#~ msgstr "沒有下一個區塊"
-
-#~ msgid "git for-each-ref [<options>] [<pattern>]"
-#~ msgstr "git for-each-ref [<選項>] [<模式>]"
-
-#~ msgid "git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]"
-#~ msgstr "git for-each-ref [--merged [<提交>]] [--no-merged [<提交>]]"
-
-#~ msgid "git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"
-#~ msgstr "git for-each-ref [--contains [<提交>]] [--no-contains [<提交>]]"
-
-#~ msgid "git for-each-ref [--start-after <marker>]"
-#~ msgstr "git for-each-ref [--start-after <marker>]"
-
-#~ msgid ""
-#~ "git pack-refs [--all] [--no-prune] [--auto] [--include <pattern>] [--"
-#~ "exclude <pattern>]"
-#~ msgstr ""
-#~ "git pack-refs [--all] [--no-prune] [--auto] [--include <pattern>] [--"
-#~ "exclude <pattern>]"
-
-#, c-format
-#~ msgid "deleting '%s' failed"
-#~ msgstr "刪除 '%s' 失敗"
-
-#, c-format
-#~ msgid "creating '%s' failed"
-#~ msgstr "建立 '%s' 失敗"
-
-#, c-format
-#~ msgid "could not open index for %s"
-#~ msgstr "無法開啟 %s 的索引"
-
-#~ msgid "cannot handle pushes this big"
-#~ msgstr "不能處理這麼大的推送"
-
-#, c-format
-#~ msgid ""
-#~ "'%s' is nominated for removal.\n"
-#~ "If you still use this command, please add an extra\n"
-#~ "option, '--i-still-use-this', on the command line\n"
-#~ "and let us know you still use it by sending an e-mail\n"
-#~ "to <git@vger.kernel.org>.  Thanks.\n"
-#~ msgstr ""
-#~ "「%s」命令已被提名移除。\n"
-#~ "如果您仍在使用該命令,請多加上「--i-still-use-this」\n"
-#~ "選項,然後寄封電子郵件到 <git@vger.kernel.org>,\n"
-#~ "讓我們知道您還在使用,謝謝。\n"
-
-#~ msgid "start-after"
-#~ msgstr "start-after"
-
-#~ msgid "compact-summary"
-#~ msgstr "精要摘要 (compact-summary)"
-
-#  譯者:請維持前導空格
-#, c-format
-#~ msgid "   (%s will become dangling)"
-#~ msgstr "   (%s 將成為懸空狀態)"
-
-#  譯者:請維持前導空格
-#, c-format
-#~ msgid "   (%s has become dangling)"
-#~ msgstr "   (%s 已成為懸空狀態)"
-
-#~ msgid "use at most one of --auto and --schedule=<frequency>"
-#~ msgstr "--auto 和 --schedule=<頻率> 請任選一"
-
-#, c-format
-#~ msgid "Final output: %d %s\n"
-#~ msgstr "最終輸出:%d %s\n"
-
-#, c-format
-#~ msgid "%d (FSCK_IGNORE?) should never trigger this callback"
-#~ msgstr "%d (FSCK_IGNORE?) 不應觸發這個回呼函式"
-
-#~ msgid ""
-#~ "git pack-objects --stdout [<options>] [< <ref-list> | < <object-list>]"
-#~ msgstr "git pack-objects --stdout [<選項>] [< <引用列表> | < <物件列表>]"
-
-#~ msgid ""
-#~ "git pack-objects [<options>] <base-name> [< <ref-list> | < <object-list>]"
-#~ msgstr "git pack-objects [<選項>] <前綴名稱> [< <引用列表> | < <物件列表>]"
-
-#~ msgid "cannot use --stdin-packs with --cruft"
-#~ msgstr "無法將 --stdin-packs 與 --cruft 組合使用"
-
-#, c-format
-#~ msgid "unreachable: invalid reference: %s"
-#~ msgstr "不可達:無效引用:%s"
-
-#~ msgid "trying to write commit not in index"
-#~ msgstr "嘗試寫入不在索引的提交"
-
-#~ msgid "git cat-file (-t | -s) [--allow-unknown-type] <object>"
-#~ msgstr "git cat-file (-t | -s) [--allow-unknown-type] <object>"
-
-#~ msgid "allow -s and -t to work with broken/corrupt objects"
-#~ msgstr "允許 -s 和 -t 對損壞的物件生效"
-
-#, c-format
-#~ msgid "%s: object is of unknown type '%s': %s"
-#~ msgstr "%s:物件屬於「%s」未知類型:%s"
-
-#~ msgid "(bad commit)\n"
-#~ msgstr "(壞提交)\n"
-
-#, c-format
-#~ msgid "add_cacheinfo failed for path '%s'; merge aborting."
-#~ msgstr "add_cacheinfo 對路徑 '%s' 執行失敗,合併終止。"
-
-#, c-format
-#~ msgid "add_cacheinfo failed to refresh for path '%s'; merge aborting."
-#~ msgstr "add_cacheinfo 無法重新整理路徑 '%s',合併終止。"
-
-#, c-format
-#~ msgid "failed to create path '%s'%s"
-#~ msgstr "建立路徑 '%s'%s 失敗"
-
-#, c-format
-#~ msgid "Removing %s to make room for subdirectory\n"
-#~ msgstr "刪除 %s 以便為子目錄留出空間\n"
-
-#~ msgid ": perhaps a D/F conflict?"
-#~ msgstr ":可能是一個目錄/檔案衝突?"
-
-#, c-format
-#~ msgid "refusing to lose untracked file at '%s'"
-#~ msgstr "拒絕捨棄 '%s' 中的未追蹤檔案"
-
-#, c-format
-#~ msgid "blob expected for %s '%s'"
-#~ msgstr "%s '%s' 應為資料物件"
-
-#, c-format
-#~ msgid "failed to open '%s': %s"
-#~ msgstr "開啟 '%s' 失敗:%s"
-
-#, c-format
-#~ msgid "failed to symlink '%s': %s"
-#~ msgstr "建立符號連結 '%s' 失敗:%s"
-
-#, c-format
-#~ msgid "do not know what to do with %06o %s '%s'"
-#~ msgstr "不知道如何處理 %06o %s '%s'"
-
-#, c-format
-#~ msgid "Failed to merge submodule %s (repository corrupt)"
-#~ msgstr "無法合併子模組 %s (版本庫損壞)"
-
-#, c-format
-#~ msgid "Fast-forwarding submodule %s to the following commit:"
-#~ msgstr "子模組 %s 快轉到如下提交:"
-
-#, c-format
-#~ msgid "Fast-forwarding submodule %s"
-#~ msgstr "快轉子模組 %s"
-
-#, c-format
-#~ msgid "Failed to merge submodule %s (merge following commits not found)"
-#~ msgstr "無法合併子模組 %s (找不到合併跟隨的提交)"
-
-#, c-format
-#~ msgid "Failed to merge submodule %s (not fast-forward)"
-#~ msgstr "無法合併子模組 %s(非快轉)"
-
-#~ msgid "Found a possible merge resolution for the submodule:\n"
-#~ msgstr "找到子模組的一個可能的合併方案:\n"
-
-#, c-format
-#~ msgid ""
-#~ "If this is correct simply add it to the index for example\n"
-#~ "by using:\n"
-#~ "\n"
-#~ "  git update-index --cacheinfo 160000 %s \"%s\"\n"
-#~ "\n"
-#~ "which will accept this suggestion.\n"
-#~ msgstr ""
-#~ "正確的話,就能直接加進索引,例如執行下述命令:\n"
-#~ "\n"
-#~ "  git update-index --cacheinfo 160000 %s \"%s\"\n"
-#~ "\n"
-#~ "接受本建議。\n"
-
-#, c-format
-#~ msgid "Failed to merge submodule %s (multiple merges found)"
-#~ msgstr "無法合併子模組 %s (發現多個合併)"
-
-#~ msgid "failed to execute internal merge"
-#~ msgstr "無法執行內部合併"
-
-#, c-format
-#~ msgid "unable to add %s to database"
-#~ msgstr "無法將 %s 加進資料庫"
-
-#, c-format
-#~ msgid "Error: Refusing to lose untracked file at %s; writing to %s instead."
-#~ msgstr "錯誤:拒絕遺失未追蹤檔案 '%s',而是寫入 %s。"
-
-#, c-format
-#~ msgid ""
-#~ "CONFLICT (%s/delete): %s deleted in %s and %s in %s. Version %s of %s "
-#~ "left in tree."
-#~ msgstr ""
-#~ "衝突(%1$s/刪除):%2$s 在 %3$s 中被刪除,在 %5$s 中被 %4$s。%7$s 的 %6$s "
-#~ "版本被保留。"
-
-#, c-format
-#~ msgid ""
-#~ "CONFLICT (%s/delete): %s deleted in %s and %s to %s in %s. Version %s of "
-#~ "%s left in tree."
-#~ msgstr ""
-#~ "衝突(%1$s/刪除):%2$s 在 %3$s 中被刪除,在 %6$s 中的 %5$s 被 %4$s。%8$s "
-#~ "的 %7$s 版本被保留。"
-
-#, c-format
-#~ msgid ""
-#~ "CONFLICT (%s/delete): %s deleted in %s and %s in %s. Version %s of %s "
-#~ "left in tree at %s."
-#~ msgstr ""
-#~ "衝突(%1$s/刪除):%2$s 在 %3$s 中被刪除,在 %5$s 中被 %4$s。%7$s 的 %6$s "
-#~ "版本保留在 %8$s 中。"
-
-#, c-format
-#~ msgid ""
-#~ "CONFLICT (%s/delete): %s deleted in %s and %s to %s in %s. Version %s of "
-#~ "%s left in tree at %s."
-#~ msgstr ""
-#~ "衝突(%1$s/刪除):%2$s 在 %3$s 中被刪除,在 %6$s 中的 %5$s 被 %4$s。%8$s "
-#~ "的 %7$s 版本保留在 %9$s 中。"
-
-#~ msgid "rename"
-#~ msgstr "重新命名"
-
-#~ msgid "renamed"
-#~ msgstr "已重新命名"
-
-#, c-format
-#~ msgid "Refusing to lose dirty file at %s"
-#~ msgstr "拒絕遺失髒檔案 '%s'"
-
-#, c-format
-#~ msgid "Refusing to lose untracked file at %s, even though it's in the way."
-#~ msgstr "拒絕在 '%s' 處失去未追蹤檔案,即使它存在於重新命名中。"
-
-#, c-format
-#~ msgid "CONFLICT (rename/add): Rename %s->%s in %s.  Added %s in %s"
-#~ msgstr ""
-#~ "衝突(重新命名/新增):在 %3$s 中重新命名 %1$s->%2$s。在 %5$s 中新增 %4$s"
-
-#, c-format
-#~ msgid "%s is a directory in %s adding as %s instead"
-#~ msgstr "%s 是 %s 中的一個目錄而已 %s 為名被新增"
-
-#, c-format
-#~ msgid "Refusing to lose untracked file at %s; adding as %s instead"
-#~ msgstr "拒絕遺失未追蹤檔案 '%s',而是新增為 %s"
-
-#, c-format
-#~ msgid ""
-#~ "CONFLICT (rename/rename): Rename \"%s\"->\"%s\" in branch \"%s\" rename "
-#~ "\"%s\"->\"%s\" in \"%s\"%s"
-#~ msgstr ""
-#~ "衝突(重新命名/重新命名):在分支 \"%3$s\" 中重新命名 \"%1$s\"->\"%2$s\","
-#~ "在分支 \"%6$s\" 中重新命名 \"%4$s\"->\"%5$s\"%7$s"
-
-#~ msgid " (left unresolved)"
-#~ msgstr " (留下未解決)"
-
-#, c-format
-#~ msgid "CONFLICT (rename/rename): Rename %s->%s in %s. Rename %s->%s in %s"
-#~ msgstr ""
-#~ "衝突(重新命名/重新命名):在 %3$s 中重新命名 %1$s->%2$s,在 %6$s 中重新命"
-#~ "名 %4$s->%5$s"
-
-#, c-format
-#~ msgid ""
-#~ "CONFLICT (directory rename split): Unclear where to place %s because "
-#~ "directory %s was renamed to multiple other directories, with no "
-#~ "destination getting a majority of the files."
-#~ msgstr ""
-#~ "衝突(分割的目錄重新命名):不清楚 %s 應該放在哪裡,因為目錄 %s 被重新命名"
-#~ "到多個其它目錄,沒有目錄包含大部分檔案。"
-
-#, c-format
-#~ msgid ""
-#~ "CONFLICT (rename/rename): Rename directory %s->%s in %s. Rename directory "
-#~ "%s->%s in %s"
-#~ msgstr ""
-#~ "衝突(重新命名/重新命名):在 %3$s 中重新命名目錄 %1$s->%2$s,在 %6$s 中重"
-#~ "新命名目錄 %4$s->%5$s"
-
-#, c-format
-#~ msgid "object %s is not a blob"
-#~ msgstr "物件 %s 不是一個資料物件"
-
-#~ msgid "modify"
-#~ msgstr "修改"
-
-#~ msgid "modified"
-#~ msgstr "修改"
-
-#, c-format
-#~ msgid "Skipped %s (merged same as existing)"
-#~ msgstr "略過 %s(已經做過相同合併)"
-
-#, c-format
-#~ msgid "Adding as %s instead"
-#~ msgstr "而是以 %s 為名新增"
-
-#, c-format
-#~ msgid "Removing %s"
-#~ msgstr "刪除 %s"
-
-#~ msgid "file/directory"
-#~ msgstr "檔案/目錄"
-
-#~ msgid "directory/file"
-#~ msgstr "目錄/檔案"
-
-#, c-format
-#~ msgid ""
-#~ "CONFLICT (%s): There is a directory with name %s in %s. Adding %s as %s"
-#~ msgstr ""
-#~ "衝突(%1$s):在 %3$s 中有一個名為 %2$s 的目錄。以 %5$s 為名新增 %4$s"
-
-#, c-format
-#~ msgid "Adding %s"
-#~ msgstr "新增 %s"
-
-#, c-format
-#~ msgid "CONFLICT (add/add): Merge conflict in %s"
-#~ msgstr "衝突(add/add):合併衝突於 %s"
-
-#, c-format
-#~ msgid "merging of trees %s and %s failed"
-#~ msgstr "無法合併樹 %s 和 %s"
-
-#~ msgid "Merging:"
-#~ msgstr "合併:"
-
-#, c-format
-#~ msgid "found %u common ancestor:"
-#~ msgid_plural "found %u common ancestors:"
-#~ msgstr[0] "發現 %u 個共同祖先:"
-
-#~ msgid "merge returned no commit"
-#~ msgstr "合併未返回提交"
-
-#~ msgid "cannot write incremental MIDX with bitmap"
-#~ msgstr "無法寫入有位圖的增量 MIDX"
-
-#, c-format
-#~ msgid "Could not find remote branch %s to clone."
-#~ msgstr "找不到要複製的遠端分支 %s。"
-
-#, c-format
-#~ msgid "merging cannot continue; got unclean result of %d"
-#~ msgstr "無法繼續合併:從 %d 收到的結果不乾淨"
-
-#~ msgid "--onto and --advance are incompatible"
-#~ msgstr "--onto 和 --advance 不相容"
-
-#, c-format
-#~ msgid "key '%s' of pattern had no '*'"
-#~ msgstr "模式的鍵 '%s' 沒有 '*'"
-
-#, c-format
-#~ msgid "preferred pack (%s) is invalid"
-#~ msgstr "偏好的封包 (%s) 無效"
-
-#, c-format
-#~ msgid ""
-#~ "more than %i tags found; listed %i most recent\n"
-#~ "gave up search at %s\n"
-#~ msgstr ""
-#~ "發現多於 %i 個標籤,列出最近的 %i 個\n"
-#~ "在 %s 放棄搜尋\n"
-
-#~ msgid "Public key pinning not supported with cURL < 7.39.0"
-#~ msgstr "不支援公鑰檔案鎖定,因為 cURL < 7.39.0"
-
-#~ msgid "CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0"
-#~ msgstr "不支援 CURLSSLOPT_NO_REVOKE,因為 cURL < 7.44.0"
-
-#~ msgid "revision walk setup failed\n"
-#~ msgstr "修訂版遍歷設定失敗\n"
-
-#, c-format
-#~ msgid "unable to parse contact: %s"
-#~ msgstr "無法解析聯絡地址:%s"
-
-#~ msgid ""
-#~ "the add.interactive.useBuiltin setting has been removed!\n"
-#~ "See its entry in 'git help config' for details."
-#~ msgstr ""
-#~ "add.interactive.useBuiltin 設定已被移除!\n"
-#~ "深入了解請參閱 “git help config” 中的對應條目。"
-
-#~ msgid "git archive: Remote with no URL"
-#~ msgstr "git archive: 未提供遠端 URL"
-
-#~ msgid "only one action at a time"
-#~ msgstr "一次只能有一個動作"
-
-#~ msgid "use [RFC PATCH] instead of [PATCH]"
-#~ msgstr "使用 [RFC PATCH] 代替 [PATCH]"
-
-#, c-format
-#~ msgid "remote '%s' has no configured URL"
-#~ msgstr "“%s” 遠端未設定 URL"
-
-#, c-format
-#~ msgid "truncating .rej filename to %.*s.rej"
-#~ msgstr "正在將 .rej 檔案名稱截短為 %.*s.rej"
-
-#~ msgid ""
-#~ "Use -f if you really want to add them.\n"
-#~ "Turn this message off by running\n"
-#~ "\"git config advice.addIgnoredFile false\""
-#~ msgstr ""
-#~ "若您真的想加入,請傳入 -f。\n"
-#~ "如要關閉此訊息,請執行\n"
-#~ "“git config advice.addIgnoredFile false”"
-
-#~ msgid ""
-#~ "Maybe you wanted to say 'git add .'?\n"
-#~ "Turn this message off by running\n"
-#~ "\"git config advice.addEmptyPathspec false\""
-#~ msgstr ""
-#~ "可能您想做 “git add .”?\n"
-#~ "如要關閉此訊息,請執行\n"
-#~ "“git config advice.addEmptyPathspec false”"
-
-#~ msgid ""
-#~ "clean.requireForce defaults to true and neither -i, -n, nor -f given; "
-#~ "refusing to clean"
-#~ msgstr ""
-#~ "clean.requireForce 預設為 true 且未提供 -i、-n 或 -f 選項,拒絕執行清理動"
-#~ "作"
-
-#, c-format
-#~ msgid "bad ls-files format: element '%s' does not start with '('"
-#~ msgstr "ls-files 格式錯誤:“%s” 元素不以 “(” 開頭"
-
-#, c-format
-#~ msgid "bad ls-files format: element '%s' does not end in ')'"
-#~ msgstr "ls-files 格式錯誤:“%s” 元素不以 “)” 結尾"
-
-#, c-format
-#~ msgid "bad ls-files format: %%%.*s"
-#~ msgstr "ls-files 格式錯誤:%%%.*s"
-
-#~ msgid "keep redundant, empty commits"
-#~ msgstr "保持多餘的、空的提交"
-
-#~ msgid "core.commentChar should only be one ASCII character"
-#~ msgstr "core.commentChar 應該是一個 ASCII 字元"
-
-#~ msgid ""
-#~ "--bundle-uri is incompatible with --depth, --shallow-since, and --shallow-"
-#~ "exclude"
-#~ msgstr ""
-#~ "--bundle-uri 與 --depth、--shallow-since 和 --shallow-exclude 不相容"
-
-#~ msgid "--merge-base is incompatible with --stdin"
-#~ msgstr "--merge-base 與 --stdin 不相容"
-
-#~ msgid ""
-#~ "apply options are incompatible with rebase.autoSquash.  Consider adding --"
-#~ "no-autosquash"
-#~ msgstr "apply 選項與 rebase.autoSquash 不相容。請考慮加上 --no-autosquash"
-
-#~ msgid "--exclude-hidden cannot be used together with --branches"
-#~ msgstr "--exclude-hidden 無法與 --branches 同時使用"
-
-#~ msgid "--exclude-hidden cannot be used together with --tags"
-#~ msgstr "--exclude-hidden 無法與 --tags 同時使用"
-
-#~ msgid "--exclude-hidden cannot be used together with --remotes"
-#~ msgstr "--exclude-hidden 無法與 --remotes 同時使用"
-
-#, c-format
-#~ msgid "only one of '%s', '%s' or '%s' can be given"
-#~ msgstr "只能傳入 “%s”、“%s” 或 “%s”"
-
-#, c-format
-#~ msgid "'%s' and '%s' cannot be used together"
-#~ msgstr "無法同時使用 “%s” 和 “%s”"
-
-#, c-format
-#~ msgid "options '%s', and '%s' cannot be used together"
-#~ msgstr "無法同時使用 “%s” 和 “%s” 選項"
-
-#~ msgid "<commit-ish>"
-#~ msgstr "<提交指示元>"
-
-#, c-format
-#~ msgid "%s is incompatible with %s"
-#~ msgstr "%s 與 %s 不相容"
-
-#~ msgid "unhandled options"
-#~ msgstr "未處理選項"
diff --git a/pretty.c b/pretty.c
index e0646bb..ebf4da4 100644
--- a/pretty.c
+++ b/pretty.c
@@ -781,7 +781,7 @@ static int mailmap_name(const char **email, size_t *email_len,
 	static struct string_list *mail_map;
 	if (!mail_map) {
 		CALLOC_ARRAY(mail_map, 1);
-		read_mailmap(mail_map);
+		read_mailmap(the_repository, mail_map);
 	}
 	return mail_map->nr && map_user(mail_map, email, email_len, name, name_len);
 }
diff --git a/promisor-remote.c b/promisor-remote.c
index 77ebf53..96fa215 100644
--- a/promisor-remote.c
+++ b/promisor-remote.c
@@ -193,6 +193,7 @@ void promisor_remote_clear(struct promisor_remote_config *config)
 	while (config->promisors) {
 		struct promisor_remote *r = config->promisors;
 		free(r->partial_clone_filter);
+		free(r->advertised_filter);
 		config->promisors = config->promisors->next;
 		free(r);
 	}
@@ -375,18 +376,24 @@ static char *fields_from_config(struct string_list *fields_list, const char *con
 	return fields;
 }
 
+static struct string_list *initialize_fields_list(struct string_list *fields_list, int *initialized,
+						  const char *config_key)
+{
+	if (!*initialized) {
+		fields_list->cmp = strcasecmp;
+		fields_from_config(fields_list, config_key);
+		*initialized = 1;
+	}
+
+	return fields_list;
+}
+
 static struct string_list *fields_sent(void)
 {
 	static struct string_list fields_list = STRING_LIST_INIT_NODUP;
 	static int initialized;
 
-	if (!initialized) {
-		fields_list.cmp = strcasecmp;
-		fields_from_config(&fields_list, "promisor.sendFields");
-		initialized = 1;
-	}
-
-	return &fields_list;
+	return initialize_fields_list(&fields_list, &initialized, "promisor.sendFields");
 }
 
 static struct string_list *fields_checked(void)
@@ -394,13 +401,15 @@ static struct string_list *fields_checked(void)
 	static struct string_list fields_list = STRING_LIST_INIT_NODUP;
 	static int initialized;
 
-	if (!initialized) {
-		fields_list.cmp = strcasecmp;
-		fields_from_config(&fields_list, "promisor.checkFields");
-		initialized = 1;
-	}
+	return initialize_fields_list(&fields_list, &initialized, "promisor.checkFields");
+}
 
-	return &fields_list;
+static struct string_list *fields_stored(void)
+{
+	static struct string_list fields_list = STRING_LIST_INIT_NODUP;
+	static int initialized;
+
+	return initialize_fields_list(&fields_list, &initialized, "promisor.storeFields");
 }
 
 /*
@@ -692,6 +701,132 @@ static struct promisor_info *parse_one_advertised_remote(const char *remote_info
 	return info;
 }
 
+static bool store_one_field(struct repository *repo, const char *remote_name,
+			    const char *field_name, const char *field_key,
+			    const char *advertised, const char *current)
+{
+	if (advertised && (!current || strcmp(current, advertised))) {
+		char *key = xstrfmt("remote.%s.%s", remote_name, field_key);
+
+		fprintf(stderr, _("Storing new %s from server for remote '%s'.\n"
+				  "    '%s' -> '%s'\n"),
+			field_name, remote_name,
+			current ? current : "",
+			advertised);
+
+		repo_config_set_gently(repo, key, advertised);
+		free(key);
+
+		return true;
+	}
+
+	return false;
+}
+
+/* Check that a filter is valid by parsing it */
+static bool valid_filter(const char *filter, const char *remote_name)
+{
+	struct list_objects_filter_options filter_opts = LIST_OBJECTS_FILTER_INIT;
+	struct strbuf err = STRBUF_INIT;
+	int res = gently_parse_list_objects_filter(&filter_opts, filter, &err);
+
+	if (res)
+		warning(_("invalid filter '%s' for remote '%s' "
+			  "will not be stored: %s"),
+			filter, remote_name, err.buf);
+
+	list_objects_filter_release(&filter_opts);
+	strbuf_release(&err);
+
+	return !res;
+}
+
+/* Check that a token doesn't contain any control character */
+static bool valid_token(const char *token, const char *remote_name)
+{
+	const char *c = token;
+
+	for (; *c; c++)
+		if (iscntrl(*c)) {
+			warning(_("invalid token '%s' for remote '%s' "
+				  "will not be stored"),
+				token, remote_name);
+			return false;
+		}
+
+	return true;
+}
+
+struct store_info {
+	struct repository *repo;
+	struct string_list config_info;
+	bool store_filter;
+	bool store_token;
+};
+
+static struct store_info *store_info_new(struct repository *repo)
+{
+	struct string_list *fields_to_store = fields_stored();
+	struct store_info *s = xmalloc(sizeof(*s));
+
+	s->repo = repo;
+
+	string_list_init_nodup(&s->config_info);
+	promisor_config_info_list(repo, &s->config_info, fields_to_store);
+	string_list_sort(&s->config_info);
+
+	s->store_filter = !!string_list_lookup(fields_to_store, promisor_field_filter);
+	s->store_token = !!string_list_lookup(fields_to_store, promisor_field_token);
+
+	return s;
+}
+
+static void store_info_free(struct store_info *s)
+{
+	if (s) {
+		promisor_info_list_clear(&s->config_info);
+		free(s);
+	}
+}
+
+static bool promisor_store_advertised_fields(struct promisor_info *advertised,
+					     struct store_info *store_info)
+{
+	struct promisor_info *p;
+	struct string_list_item *item;
+	const char *remote_name = advertised->name;
+	bool reload_config = false;
+
+	if (!(store_info->store_filter || store_info->store_token))
+		return false;
+
+	/*
+	 * Get existing config info for the advertised promisor
+	 * remote. This ensures the remote is already configured on
+	 * the client side.
+	 */
+	item = string_list_lookup(&store_info->config_info, remote_name);
+
+	if (!item)
+		return false;
+
+	p = item->util;
+
+	if (store_info->store_filter && advertised->filter &&
+	    valid_filter(advertised->filter, remote_name))
+		reload_config |= store_one_field(store_info->repo, remote_name,
+						 "filter", promisor_field_filter,
+						 advertised->filter, p->filter);
+
+	if (store_info->store_token && advertised->token &&
+	    valid_token(advertised->token, remote_name))
+		reload_config |= store_one_field(store_info->repo, remote_name,
+						 "token", promisor_field_token,
+						 advertised->token, p->token);
+
+	return reload_config;
+}
+
 static void filter_promisor_remote(struct repository *repo,
 				   struct strvec *accepted,
 				   const char *info)
@@ -700,7 +835,10 @@ static void filter_promisor_remote(struct repository *repo,
 	enum accept_promisor accept = ACCEPT_NONE;
 	struct string_list config_info = STRING_LIST_INIT_NODUP;
 	struct string_list remote_info = STRING_LIST_INIT_DUP;
+	struct store_info *store_info = NULL;
 	struct string_list_item *item;
+	bool reload_config = false;
+	struct string_list accepted_filters = STRING_LIST_INIT_DUP;
 
 	if (!repo_config_get_string_tmp(the_repository, "promisor.acceptfromserver", &accept_str)) {
 		if (!*accept_str || !strcasecmp("None", accept_str))
@@ -736,35 +874,73 @@ static void filter_promisor_remote(struct repository *repo,
 			string_list_sort(&config_info);
 		}
 
-		if (should_accept_remote(accept, advertised, &config_info))
+		if (should_accept_remote(accept, advertised, &config_info)) {
+			if (!store_info)
+				store_info = store_info_new(repo);
+			if (promisor_store_advertised_fields(advertised, store_info))
+				reload_config = true;
+
 			strvec_push(accepted, advertised->name);
 
+			/* Capture advertised filters for accepted remotes */
+			if (advertised->filter) {
+				struct string_list_item *i;
+				i = string_list_append(&accepted_filters, advertised->name);
+				i->util = xstrdup(advertised->filter);
+			}
+		}
+
 		promisor_info_free(advertised);
 	}
 
 	promisor_info_list_clear(&config_info);
 	string_list_clear(&remote_info, 0);
+	store_info_free(store_info);
+
+	if (reload_config)
+		repo_promisor_remote_reinit(repo);
+
+	/* Apply accepted remote filters to the stable repo state */
+	for_each_string_list_item(item, &accepted_filters) {
+		struct promisor_remote *r = repo_promisor_remote_find(repo, item->string);
+		if (r) {
+			free(r->advertised_filter);
+			r->advertised_filter = item->util;
+			item->util = NULL;
+		}
+	}
+
+	string_list_clear(&accepted_filters, 1);
+
+	/* Mark the remotes as accepted in the repository state */
+	for (size_t i = 0; i < accepted->nr; i++) {
+		struct promisor_remote *r = repo_promisor_remote_find(repo, accepted->v[i]);
+		if (r)
+			r->accepted = 1;
+	}
 }
 
-char *promisor_remote_reply(const char *info)
+void promisor_remote_reply(const char *info, char **accepted_out)
 {
 	struct strvec accepted = STRVEC_INIT;
-	struct strbuf reply = STRBUF_INIT;
 
 	filter_promisor_remote(the_repository, &accepted, info);
 
-	if (!accepted.nr)
-		return NULL;
-
-	for (size_t i = 0; i < accepted.nr; i++) {
-		if (i)
-			strbuf_addch(&reply, ';');
-		strbuf_addstr_urlencode(&reply, accepted.v[i], allow_unsanitized);
+	if (accepted_out) {
+		if (accepted.nr) {
+			struct strbuf reply = STRBUF_INIT;
+			for (size_t i = 0; i < accepted.nr; i++) {
+				if (i)
+					strbuf_addch(&reply, ';');
+				strbuf_addstr_urlencode(&reply, accepted.v[i], allow_unsanitized);
+			}
+			*accepted_out = strbuf_detach(&reply, NULL);
+		} else {
+			*accepted_out = NULL;
+		}
 	}
 
 	strvec_clear(&accepted);
-
-	return strbuf_detach(&reply, NULL);
 }
 
 void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes)
@@ -789,3 +965,33 @@ void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes
 
 	string_list_clear(&accepted_remotes, 0);
 }
+
+char *promisor_remote_construct_filter(struct repository *repo)
+{
+	struct promisor_remote *r;
+	struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
+	struct strbuf err = STRBUF_INIT;
+	char *result = NULL;
+
+	promisor_remote_init(repo);
+
+	for (r = repo->promisor_remote_config->promisors; r; r = r->next) {
+		if (r->accepted && r->advertised_filter)
+			if (gently_parse_list_objects_filter(&filter_options,
+							     r->advertised_filter,
+							     &err)) {
+				warning(_("promisor remote '%s' advertised invalid filter '%s': %s"),
+					r->name, r->advertised_filter, err.buf);
+				strbuf_reset(&err);
+				continue;
+			}
+	}
+
+	if (filter_options.choice)
+		result = xstrdup(expand_list_objects_filter_spec(&filter_options));
+
+	list_objects_filter_release(&filter_options);
+	strbuf_release(&err);
+
+	return result;
+}
diff --git a/promisor-remote.h b/promisor-remote.h
index 263d331..3d4d2de 100644
--- a/promisor-remote.h
+++ b/promisor-remote.h
@@ -15,6 +15,7 @@ struct object_id;
 struct promisor_remote {
 	struct promisor_remote *next;
 	char *partial_clone_filter;
+	char *advertised_filter;
 	unsigned int accepted : 1;
 	const char name[FLEX_ARRAY];
 };
@@ -48,12 +49,12 @@ char *promisor_remote_info(struct repository *repo);
 /*
  * Prepare a reply to a "promisor-remote" advertisement from a server.
  * Check the value of "promisor.acceptfromserver" and maybe the
- * configured promisor remotes, if any, to prepare the reply.
- * Return value is NULL if no promisor remote from the server
- * is accepted. Otherwise it contains the names of the accepted promisor
- * remotes separated by ';'. See gitprotocol-v2(5).
+ * configured promisor remotes, if any, to prepare the reply. If the
+ * `accepted_out` argument is not NULL, it is set to either NULL or to
+ * the names of the accepted promisor remotes separated by ';' if
+ * any. See gitprotocol-v2(5).
  */
-char *promisor_remote_reply(const char *info);
+void promisor_remote_reply(const char *info, char **accepted_out);
 
 /*
  * Set the 'accepted' flag for some promisor remotes. Useful on the
@@ -67,4 +68,10 @@ void mark_promisor_remotes_as_accepted(struct repository *repo, const char *remo
  */
 int repo_has_accepted_promisor_remote(struct repository *r);
 
+/*
+ * Use the filters from the accepted remotes to create a combined
+ * filter (useful in `--filter=auto` mode).
+ */
+char *promisor_remote_construct_filter(struct repository *repo);
+
 #endif /* PROMISOR_REMOTE_H */
diff --git a/protocol-caps.c b/protocol-caps.c
index ecdd0dc..35072ed 100644
--- a/protocol-caps.c
+++ b/protocol-caps.c
@@ -4,7 +4,6 @@
 #include "hex.h"
 #include "pkt-line.h"
 #include "hash.h"
-#include "hex.h"
 #include "object.h"
 #include "odb.h"
 #include "repository.h"
diff --git a/pseudo-merge.c b/pseudo-merge.c
index 893b763..a2d5bd8 100644
--- a/pseudo-merge.c
+++ b/pseudo-merge.c
@@ -221,28 +221,25 @@ void load_pseudo_merges_from_config(struct repository *r,
 	}
 }
 
-static int find_pseudo_merge_group_for_ref(const char *refname,
-					   const char *referent UNUSED,
-					   const struct object_id *oid,
-					   int flags UNUSED,
-					   void *_data)
+static int find_pseudo_merge_group_for_ref(const struct reference *ref, void *_data)
 {
 	struct bitmap_writer *writer = _data;
+	const struct object_id *maybe_peeled = ref->oid;
 	struct object_id peeled;
 	struct commit *c;
 	uint32_t i;
 	int has_bitmap;
 
-	if (!peel_iterated_oid(the_repository, oid, &peeled))
-		oid = &peeled;
+	if (!reference_get_peeled_oid(the_repository, ref, &peeled))
+		maybe_peeled = &peeled;
 
-	c = lookup_commit(the_repository, oid);
+	c = lookup_commit(the_repository, maybe_peeled);
 	if (!c)
 		return 0;
-	if (!packlist_find(writer->to_pack, oid))
+	if (!packlist_find(writer->to_pack, maybe_peeled))
 		return 0;
 
-	has_bitmap = bitmap_writer_has_bitmapped_object_id(writer, oid);
+	has_bitmap = bitmap_writer_has_bitmapped_object_id(writer, maybe_peeled);
 
 	for (i = 0; i < writer->pseudo_merge_groups.nr; i++) {
 		struct pseudo_merge_group *group;
@@ -252,7 +249,7 @@ static int find_pseudo_merge_group_for_ref(const char *refname,
 		size_t j;
 
 		group = writer->pseudo_merge_groups.items[i].util;
-		if (regexec(group->pattern, refname, ARRAY_SIZE(captures),
+		if (regexec(group->pattern, ref->name, ARRAY_SIZE(captures),
 			    captures, 0))
 			continue;
 
@@ -269,7 +266,7 @@ static int find_pseudo_merge_group_for_ref(const char *refname,
 			if (group_name.len)
 				strbuf_addch(&group_name, '-');
 
-			strbuf_add(&group_name, refname + match->rm_so,
+			strbuf_add(&group_name, ref->name + match->rm_so,
 				   match->rm_eo - match->rm_so);
 		}
 
diff --git a/reachable.c b/reachable.c
index 22266db..101cfc2 100644
--- a/reachable.c
+++ b/reachable.c
@@ -83,18 +83,17 @@ static void add_rebase_files(struct rev_info *revs)
 	free_worktrees(worktrees);
 }
 
-static int add_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,
-		       int flag, void *cb_data)
+static int add_one_ref(const struct reference *ref, void *cb_data)
 {
 	struct rev_info *revs = (struct rev_info *)cb_data;
 	struct object *object;
 
-	if ((flag & REF_ISSYMREF) && (flag & REF_ISBROKEN)) {
-		warning("symbolic ref is dangling: %s", path);
+	if ((ref->flags & REF_ISSYMREF) && (ref->flags & REF_ISBROKEN)) {
+		warning("symbolic ref is dangling: %s", ref->name);
 		return 0;
 	}
 
-	object = parse_object_or_die(the_repository, oid, path);
+	object = parse_object_or_die(the_repository, ref->oid, ref->name);
 	add_pending_object(revs, object, "");
 
 	return 0;
@@ -192,30 +191,27 @@ static int obj_is_recent(const struct object_id *oid, timestamp_t mtime,
 	return oidset_contains(&data->extra_recent_oids, oid);
 }
 
-static void add_recent_object(const struct object_id *oid,
-			      struct packed_git *pack,
-			      off_t offset,
-			      timestamp_t mtime,
-			      struct recent_data *data)
+static int want_recent_object(struct recent_data *data,
+			      const struct object_id *oid)
 {
+	if (data->ignore_in_core_kept_packs &&
+	    has_object_kept_pack(data->revs->repo, oid, KEPT_PACK_IN_CORE))
+		return 0;
+	return 1;
+}
+
+static int add_recent_object(const struct object_id *oid,
+			     struct object_info *oi,
+			     void *cb_data)
+{
+	struct recent_data *data = cb_data;
 	struct object *obj;
-	enum object_type type;
 
-	if (!obj_is_recent(oid, mtime, data))
-		return;
+	if (!want_recent_object(data, oid) ||
+	    !obj_is_recent(oid, *oi->mtimep, data))
+		return 0;
 
-	/*
-	 * We do not want to call parse_object here, because
-	 * inflating blobs and trees could be very expensive.
-	 * However, we do need to know the correct type for
-	 * later processing, and the revision machinery expects
-	 * commits and tags to have been parsed.
-	 */
-	type = odb_read_object_info(the_repository->objects, oid, NULL);
-	if (type < 0)
-		die("unable to get object info for %s", oid_to_hex(oid));
-
-	switch (type) {
+	switch (*oi->typep) {
 	case OBJ_TAG:
 	case OBJ_COMMIT:
 		obj = parse_object_or_die(the_repository, oid, NULL);
@@ -228,77 +224,22 @@ static void add_recent_object(const struct object_id *oid,
 		break;
 	default:
 		die("unknown object type for %s: %s",
-		    oid_to_hex(oid), type_name(type));
+		    oid_to_hex(oid), type_name(*oi->typep));
 	}
 
 	if (!obj)
 		die("unable to lookup %s", oid_to_hex(oid));
+	if (obj->flags & SEEN)
+		return 0;
 
 	add_pending_object(data->revs, obj, "");
-	if (data->cb)
-		data->cb(obj, pack, offset, mtime);
-}
-
-static int want_recent_object(struct recent_data *data,
-			      const struct object_id *oid)
-{
-	if (data->ignore_in_core_kept_packs &&
-	    has_object_kept_pack(data->revs->repo, oid, IN_CORE_KEEP_PACKS))
-		return 0;
-	return 1;
-}
-
-static int add_recent_loose(const struct object_id *oid,
-			    const char *path, void *data)
-{
-	struct stat st;
-	struct object *obj;
-
-	if (!want_recent_object(data, oid))
-		return 0;
-
-	obj = lookup_object(the_repository, oid);
-
-	if (obj && obj->flags & SEEN)
-		return 0;
-
-	if (stat(path, &st) < 0) {
-		/*
-		 * It's OK if an object went away during our iteration; this
-		 * could be due to a simultaneous repack. But anything else
-		 * we should abort, since we might then fail to mark objects
-		 * which should not be pruned.
-		 */
-		if (errno == ENOENT)
-			return 0;
-		return error_errno("unable to stat %s", oid_to_hex(oid));
+	if (data->cb) {
+		if (oi->whence == OI_PACKED)
+			data->cb(obj, oi->u.packed.pack, oi->u.packed.offset, *oi->mtimep);
+		else
+			data->cb(obj, NULL, 0, *oi->mtimep);
 	}
 
-	add_recent_object(oid, NULL, 0, st.st_mtime, data);
-	return 0;
-}
-
-static int add_recent_packed(const struct object_id *oid,
-			     struct packed_git *p,
-			     uint32_t pos,
-			     void *data)
-{
-	struct object *obj;
-	timestamp_t mtime = p->mtime;
-
-	if (!want_recent_object(data, oid))
-		return 0;
-
-	obj = lookup_object(the_repository, oid);
-
-	if (obj && obj->flags & SEEN)
-		return 0;
-	if (p->is_cruft) {
-		if (load_pack_mtimes(p) < 0)
-			die(_("could not load cruft pack .mtimes"));
-		mtime = nth_packed_mtime(p, pos);
-	}
-	add_recent_object(oid, p, nth_packed_object_offset(p, pos), mtime, data);
 	return 0;
 }
 
@@ -308,7 +249,13 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,
 					   int ignore_in_core_kept_packs)
 {
 	struct recent_data data;
-	enum for_each_object_flags flags;
+	unsigned flags;
+	enum object_type type;
+	time_t mtime;
+	struct object_info oi = {
+		.mtimep = &mtime,
+		.typep = &type,
+	};
 	int r;
 
 	data.revs = revs;
@@ -319,17 +266,14 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,
 	oidset_init(&data.extra_recent_oids, 0);
 	data.extra_recent_oids_loaded = 0;
 
-	r = for_each_loose_object(the_repository->objects, add_recent_loose, &data,
-				  FOR_EACH_OBJECT_LOCAL_ONLY);
+	flags = ODB_FOR_EACH_OBJECT_LOCAL_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER;
+	if (ignore_in_core_kept_packs)
+		flags |= ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS;
+
+	r = odb_for_each_object(revs->repo->objects, &oi, add_recent_object, &data, flags);
 	if (r)
 		goto done;
 
-	flags = FOR_EACH_OBJECT_LOCAL_ONLY | FOR_EACH_OBJECT_PACK_ORDER;
-	if (ignore_in_core_kept_packs)
-		flags |= FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS;
-
-	r = for_each_packed_object(revs->repo, add_recent_packed, &data, flags);
-
 done:
 	oidset_clear(&data.extra_recent_oids);
 
diff --git a/read-cache-ll.h b/read-cache-ll.h
index 71b49d9..2c8b4b2 100644
--- a/read-cache-ll.h
+++ b/read-cache-ll.h
@@ -481,7 +481,7 @@ int cmp_cache_name_compare(const void *a_, const void *b_);
 
 int add_files_to_cache(struct repository *repo, const char *prefix,
 		       const struct pathspec *pathspec, char *ps_matched,
-		       int include_sparse, int flags);
+		       int include_sparse, int flags, int ignored_too );
 
 void overlay_tree_on_index(struct index_state *istate,
 			   const char *tree_name, const char *prefix);
diff --git a/read-cache.c b/read-cache.c
index 032480d..5049f9b 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -47,6 +47,9 @@
 #include "csum-file.h"
 #include "promisor-remote.h"
 #include "hook.h"
+#include "submodule.h"
+#include "submodule-config.h"
+#include "advice.h"
 
 /* Mask for the name length in ce_flags in the on-disk index */
 
@@ -470,6 +473,17 @@ int ie_modified(struct index_state *istate,
 	 * then we know it is.
 	 */
 	if ((changed & DATA_CHANGED) &&
+#ifdef GIT_WINDOWS_NATIVE
+	    /*
+	     * Work around Git for Windows v2.27.0 fixing a bug where symlinks'
+	     * target path lengths were not read at all, and instead recorded
+	     * as 4096: now, all symlinks would appear as modified.
+	     *
+	     * So let's just special-case symlinks with a target path length
+	     * (i.e. `sd_size`) of 4096 and force them to be re-checked.
+	     */
+	    (!S_ISLNK(st->st_mode) || ce->ce_stat_data.sd_size != MAX_PATH) &&
+#endif
 	    (S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0))
 		return changed;
 
@@ -706,7 +720,6 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
 	int add_option = (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE|
 			  (intent_only ? ADD_CACHE_NEW_ONLY : 0));
 	unsigned hash_flags = pretend ? 0 : INDEX_WRITE_OBJECT;
-	struct object_id oid;
 
 	if (flags & ADD_CACHE_RENORMALIZE)
 		hash_flags |= INDEX_RENORMALIZE;
@@ -716,8 +729,6 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
 
 	namelen = strlen(path);
 	if (S_ISDIR(st_mode)) {
-		if (repo_resolve_gitlink_ref(the_repository, path, "HEAD", &oid) < 0)
-			return error(_("'%s' does not have a commit checked out"), path);
 		while (namelen && path[namelen-1] == '/')
 			namelen--;
 	}
@@ -3810,7 +3821,7 @@ void overlay_tree_on_index(struct index_state *istate,
 
 	if (repo_get_oid(the_repository, tree_name, &oid))
 		die("tree-ish %s not found.", tree_name);
-	tree = parse_tree_indirect(&oid);
+	tree = repo_parse_tree_indirect(the_repository, &oid);
 	if (!tree)
 		die("bad tree-ish %s", tree_name);
 
@@ -3881,9 +3892,12 @@ void overlay_tree_on_index(struct index_state *istate,
 
 struct update_callback_data {
 	struct index_state *index;
+	struct repository *repo;
+	struct pathspec *pathspec;
 	int include_sparse;
 	int flags;
 	int add_errors;
+	int ignored_too;
 };
 
 static int fix_unmerged_status(struct diff_filepair *p,
@@ -3907,8 +3921,68 @@ static int fix_unmerged_status(struct diff_filepair *p,
 		return DIFF_STATUS_MODIFIED;
 }
 
+static int skip_submodule(const char *path,
+						struct repository *repo,
+						struct pathspec *pathspec,
+						int ignored_too)
+{
+    struct stat st;
+    const struct submodule *sub;
+    int pathspec_matches = 0;
+    int ps_i;
+    char *norm_pathspec = NULL;
+
+    /* Only consider if path is a directory */
+    if (lstat(path, &st) || !S_ISDIR(st.st_mode))
+		return 0;
+
+    /* Check if it's a submodule with ignore=all */
+    sub = submodule_from_path(repo, null_oid(the_hash_algo), path);
+    if (!sub || !sub->name || !sub->ignore || strcmp(sub->ignore, "all"))
+		return 0;
+
+    trace_printf("ignore=all: %s\n", path);
+    trace_printf("pathspec %s\n", (pathspec && pathspec->nr)
+									? "has pathspec"
+									: "no pathspec");
+
+    /* Check if submodule path is explicitly mentioned in pathspec */
+    if (pathspec) {
+		for (ps_i = 0; ps_i < pathspec->nr; ps_i++) {
+			const char *m = pathspec->items[ps_i].match;
+			if (!m)
+				continue;
+			norm_pathspec = xstrdup(m);
+			strip_dir_trailing_slashes(norm_pathspec);
+			if (!strcmp(path, norm_pathspec)) {
+				pathspec_matches = 1;
+				FREE_AND_NULL(norm_pathspec);
+				break;
+			}
+			FREE_AND_NULL(norm_pathspec);
+		}
+    }
+
+    /* If explicitly matched and forced, allow adding */
+    if (pathspec_matches) {
+		if (ignored_too && ignored_too > 0) {
+			trace_printf("Add submodule due to --force: %s\n", path);
+			return 0;
+		} else {
+			advise_if_enabled(ADVICE_ADD_IGNORED_FILE,
+				_("Skipping submodule due to ignore=all: %s\n"
+					"Use --force if you really want to add the submodule."), path);
+			return 1;
+		}
+    }
+
+    /* No explicit pathspec match -> skip silently */
+    trace_printf("Pathspec to submodule does not match explicitly: %s\n", path);
+    return 1;
+}
+
 static void update_callback(struct diff_queue_struct *q,
-			    struct diff_options *opt UNUSED, void *cbdata)
+							struct diff_options *opt UNUSED, void *cbdata)
 {
 	int i;
 	struct update_callback_data *data = cbdata;
@@ -3918,7 +3992,7 @@ static void update_callback(struct diff_queue_struct *q,
 		const char *path = p->one->path;
 
 		if (!data->include_sparse &&
-		    !path_in_sparse_checkout(path, data->index))
+			!path_in_sparse_checkout(path, data->index))
 			continue;
 
 		switch (fix_unmerged_status(p, data)) {
@@ -3926,6 +4000,11 @@ static void update_callback(struct diff_queue_struct *q,
 			die(_("unexpected diff status %c"), p->status);
 		case DIFF_STATUS_MODIFIED:
 		case DIFF_STATUS_TYPE_CHANGED:
+			if (skip_submodule(path, data->repo,
+								data->pathspec,
+								data->ignored_too))
+				continue;
+
 			if (add_file_to_index(data->index, path, data->flags)) {
 				if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
 					die(_("updating files failed"));
@@ -3946,7 +4025,7 @@ static void update_callback(struct diff_queue_struct *q,
 
 int add_files_to_cache(struct repository *repo, const char *prefix,
 		       const struct pathspec *pathspec, char *ps_matched,
-		       int include_sparse, int flags)
+		       int include_sparse, int flags, int ignored_too )
 {
 	struct odb_transaction *transaction;
 	struct update_callback_data data;
@@ -3956,6 +4035,9 @@ int add_files_to_cache(struct repository *repo, const char *prefix,
 	data.index = repo->index;
 	data.include_sparse = include_sparse;
 	data.flags = flags;
+	data.repo = repo;
+	data.ignored_too = ignored_too;
+	data.pathspec = (struct pathspec *)pathspec;
 
 	repo_init_revisions(repo, &rev, prefix);
 	setup_revisions(0, NULL, &rev, NULL);
diff --git a/ref-filter.c b/ref-filter.c
index 30cc488..1da4c0e 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -91,6 +91,7 @@ static struct expand_data {
 	struct object_id delta_base_oid;
 	void *content;
 
+	struct object *maybe_object;
 	struct object_info info;
 } oi, oi_deref;
 
@@ -1475,11 +1476,29 @@ static void grab_common_values(struct atom_value *val, int deref, struct expand_
 	}
 }
 
-/* See grab_values */
-static void grab_tag_values(struct atom_value *val, int deref, struct object *obj)
+static struct object *get_or_parse_object(struct expand_data *data, const char *refname,
+					  struct strbuf *err, int *eaten)
 {
+	if (!data->maybe_object) {
+		data->maybe_object = parse_object_buffer(the_repository, &data->oid, data->type,
+							 data->size, data->content, eaten);
+		if (!data->maybe_object) {
+			strbuf_addf(err, _("parse_object_buffer failed on %s for %s"),
+				    oid_to_hex(&data->oid), refname);
+			return NULL;
+		}
+	}
+
+	return data->maybe_object;
+}
+
+/* See grab_values */
+static int grab_tag_values(struct atom_value *val, int deref,
+			   struct expand_data *data, const char *refname,
+			   struct strbuf *err, int *eaten)
+{
+	struct tag *tag = NULL;
 	int i;
-	struct tag *tag = (struct tag *) obj;
 
 	for (i = 0; i < used_atom_cnt; i++) {
 		const char *name = used_atom[i].name;
@@ -1487,6 +1506,14 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob
 		struct atom_value *v = &val[i];
 		if (!!deref != (*name == '*'))
 			continue;
+
+		if (!tag) {
+			tag = (struct tag *) get_or_parse_object(data, refname,
+								 err, eaten);
+			if (!tag)
+				return -1;
+		}
+
 		if (deref)
 			name++;
 		if (atom_type == ATOM_TAG)
@@ -1496,22 +1523,35 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob
 		else if (atom_type == ATOM_OBJECT && tag->tagged)
 			v->s = xstrdup(oid_to_hex(&tag->tagged->oid));
 	}
+
+	return 0;
 }
 
 /* See grab_values */
-static void grab_commit_values(struct atom_value *val, int deref, struct object *obj)
+static int grab_commit_values(struct atom_value *val, int deref,
+			      struct expand_data *data, const char *refname,
+			      struct strbuf *err, int *eaten)
 {
 	int i;
-	struct commit *commit = (struct commit *) obj;
+	struct commit *commit = NULL;
 
 	for (i = 0; i < used_atom_cnt; i++) {
 		const char *name = used_atom[i].name;
 		enum atom_type atom_type = used_atom[i].atom_type;
 		struct atom_value *v = &val[i];
+
 		if (!!deref != (*name == '*'))
 			continue;
 		if (deref)
 			name++;
+
+		if (!commit) {
+			commit = (struct commit *) get_or_parse_object(data, refname,
+								       err, eaten);
+			if (!commit)
+				return -1;
+		}
+
 		if (atom_type == ATOM_TREE &&
 		    grab_oid(name, "tree", get_commit_tree_oid(commit), v, &used_atom[i]))
 			continue;
@@ -1531,6 +1571,8 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object
 			v->s = strbuf_detach(&s, NULL);
 		}
 	}
+
+	return 0;
 }
 
 static const char *find_wholine(const char *who, int wholen, const char *buf)
@@ -1711,7 +1753,7 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void
 		    (starts_with(name + wholen, "email") &&
 		    (atom->u.email_option.option & EO_MAILMAP))) {
 			if (!mailmap.items)
-				read_mailmap(&mailmap);
+				read_mailmap(the_repository, &mailmap);
 			strbuf_addstr(&mailmap_buf, buf);
 			apply_mailmap_to_header(&mailmap_buf, headers, &mailmap);
 			wholine = find_wholine(who, wholen, mailmap_buf.buf);
@@ -1759,10 +1801,12 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void
 	}
 }
 
-static void grab_signature(struct atom_value *val, int deref, struct object *obj)
+static int grab_signature(struct atom_value *val, int deref,
+			  struct expand_data *data, const char *refname,
+			  struct strbuf *err, int *eaten)
 {
 	int i;
-	struct commit *commit = (struct commit *) obj;
+	struct commit *commit = NULL;
 	struct signature_check sigc = { 0 };
 	int signature_checked = 0;
 
@@ -1790,6 +1834,13 @@ static void grab_signature(struct atom_value *val, int deref, struct object *obj
 			continue;
 
 		if (!signature_checked) {
+			if (!commit) {
+				commit = (struct commit *) get_or_parse_object(data, refname,
+									       err, eaten);
+				if (!commit)
+					return -1;
+			}
+
 			check_commit_signature(commit, &sigc);
 			signature_checked = 1;
 		}
@@ -1843,6 +1894,8 @@ static void grab_signature(struct atom_value *val, int deref, struct object *obj
 
 	if (signature_checked)
 		signature_check_clear(&sigc);
+
+	return 0;
 }
 
 static void find_subpos(const char *buf,
@@ -1920,9 +1973,8 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size
 }
 
 static void grab_describe_values(struct atom_value *val, int deref,
-				 struct object *obj)
+				 struct expand_data *data)
 {
-	struct commit *commit = (struct commit *)obj;
 	int i;
 
 	for (i = 0; i < used_atom_cnt; i++) {
@@ -1944,7 +1996,7 @@ static void grab_describe_values(struct atom_value *val, int deref,
 		cmd.git_cmd = 1;
 		strvec_push(&cmd.args, "describe");
 		strvec_pushv(&cmd.args, atom->u.describe_args.v);
-		strvec_push(&cmd.args, oid_to_hex(&commit->object.oid));
+		strvec_push(&cmd.args, oid_to_hex(&data->oid));
 		if (pipe_command(&cmd, NULL, 0, &out, 0, &err, 0) < 0) {
 			error(_("failed to run 'describe'"));
 			v->s = xstrdup("");
@@ -2066,24 +2118,36 @@ static void fill_missing_values(struct atom_value *val)
  * pointed at by the ref itself; otherwise it is the object the
  * ref (which is a tag) refers to.
  */
-static void grab_values(struct atom_value *val, int deref, struct object *obj, struct expand_data *data)
+static int grab_values(struct atom_value *val, int deref, struct expand_data *data,
+		       const char *refname, struct strbuf *err, int *eaten)
 {
 	void *buf = data->content;
+	int ret;
 
-	switch (obj->type) {
+	switch (data->type) {
 	case OBJ_TAG:
-		grab_tag_values(val, deref, obj);
+		ret = grab_tag_values(val, deref, data, refname, err, eaten);
+		if (ret < 0)
+			goto out;
+
 		grab_sub_body_contents(val, deref, data);
 		grab_person("tagger", val, deref, buf);
-		grab_describe_values(val, deref, obj);
+		grab_describe_values(val, deref, data);
 		break;
 	case OBJ_COMMIT:
-		grab_commit_values(val, deref, obj);
+		ret = grab_commit_values(val, deref, data, refname, err, eaten);
+		if (ret < 0)
+			goto out;
+
 		grab_sub_body_contents(val, deref, data);
 		grab_person("author", val, deref, buf);
 		grab_person("committer", val, deref, buf);
-		grab_signature(val, deref, obj);
-		grab_describe_values(val, deref, obj);
+
+		ret = grab_signature(val, deref, data, refname, err, eaten);
+		if (ret < 0)
+			goto out;
+
+		grab_describe_values(val, deref, data);
 		break;
 	case OBJ_TREE:
 		/* grab_tree_values(val, deref, obj, buf, sz); */
@@ -2094,8 +2158,12 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, s
 		grab_sub_body_contents(val, deref, data);
 		break;
 	default:
-		die("Eh?  Object of type %d?", obj->type);
+		die("Eh?  Object of type %d?", data->type);
 	}
+
+	ret = 0;
+out:
+	return ret;
 }
 
 static inline char *copy_advance(char *dst, const char *src)
@@ -2105,32 +2173,34 @@ static inline char *copy_advance(char *dst, const char *src)
 	return dst;
 }
 
-static const char *lstrip_ref_components(const char *refname, int len)
+static int normalize_component_count(const char *refname, int len)
 {
-	long remaining = len;
-	const char *start = xstrdup(refname);
-	const char *to_free = start;
-
 	if (len < 0) {
-		int i;
-		const char *p = refname;
+		int slashes = 0;
 
-		/* Find total no of '/' separated path-components */
-		for (i = 0; p[i]; p[i] == '/' ? i++ : *p++)
-			;
+		for (const char *p = refname; *p; p++) {
+			if (*p == '/')
+				slashes++;
+		}
+
 		/*
 		 * The number of components we need to strip is now
 		 * the total minus the components to be left (Plus one
 		 * because we count the number of '/', but the number
 		 * of components is one more than the no of '/').
 		 */
-		remaining = i + len + 1;
+		len = slashes + len + 1;
 	}
+	return len;
+}
+
+static const char *lstrip_ref_components(const char *refname, int len)
+{
+	int remaining = normalize_component_count(refname, len);
 
 	while (remaining > 0) {
-		switch (*start++) {
+		switch (*refname++) {
 		case '\0':
-			free((char *)to_free);
 			return xstrdup("");
 		case '/':
 			remaining--;
@@ -2138,42 +2208,21 @@ static const char *lstrip_ref_components(const char *refname, int len)
 		}
 	}
 
-	start = xstrdup(start);
-	free((char *)to_free);
-	return start;
+	return xstrdup(refname);
 }
 
 static const char *rstrip_ref_components(const char *refname, int len)
 {
-	long remaining = len;
-	const char *start = xstrdup(refname);
-	const char *to_free = start;
+	int remaining = normalize_component_count(refname, len);
+	const char *end = refname + strlen(refname);
 
-	if (len < 0) {
-		int i;
-		const char *p = refname;
-
-		/* Find total no of '/' separated path-components */
-		for (i = 0; p[i]; p[i] == '/' ? i++ : *p++)
-			;
-		/*
-		 * The number of components we need to strip is now
-		 * the total minus the components to be left (Plus one
-		 * because we count the number of '/', but the number
-		 * of components is one more than the no of '/').
-		 */
-		remaining = i + len + 1;
-	}
-
-	while (remaining-- > 0) {
-		char *p = strrchr(start, '/');
-		if (!p) {
-			free((char *)to_free);
+	while (remaining > 0) {
+		if (end == refname)
 			return xstrdup("");
-		} else
-			p[0] = '\0';
+		if (*--end == '/')
+			remaining--;
 	}
-	return start;
+	return xmemdupz(refname, end - refname);
 }
 
 static const char *show_ref(struct refname_atom *atom, const char *refname)
@@ -2292,38 +2341,43 @@ static const char *get_refname(struct used_atom *atom, struct ref_array_item *re
 	return show_ref(&atom->u.refname, ref->refname);
 }
 
-static int get_object(struct ref_array_item *ref, int deref, struct object **obj,
+static int get_object(struct ref_array_item *ref, int deref,
 		      struct expand_data *oi, struct strbuf *err)
 {
-	/* parse_object_buffer() will set eaten to 0 if free() will be needed */
-	int eaten = 1;
+	/* parse_object_buffer() will set eaten to 1 if free() will be needed */
+	int eaten = 0;
+	int ret;
+
+	oi->maybe_object = NULL;
+
 	if (oi->info.contentp) {
 		/* We need to know that to use parse_object_buffer properly */
 		oi->info.sizep = &oi->size;
 		oi->info.typep = &oi->type;
 	}
+
 	if (odb_read_object_info_extended(the_repository->objects, &oi->oid, &oi->info,
-					  OBJECT_INFO_LOOKUP_REPLACE))
-		return strbuf_addf_ret(err, -1, _("missing object %s for %s"),
-				       oid_to_hex(&oi->oid), ref->refname);
+					  OBJECT_INFO_LOOKUP_REPLACE)) {
+		ret = strbuf_addf_ret(err, -1, _("missing object %s for %s"),
+				      oid_to_hex(&oi->oid), ref->refname);
+		goto out;
+	}
 	if (oi->info.disk_sizep && oi->disk_size < 0)
 		BUG("Object size is less than zero.");
 
 	if (oi->info.contentp) {
-		*obj = parse_object_buffer(the_repository, &oi->oid, oi->type, oi->size, oi->content, &eaten);
-		if (!*obj) {
-			if (!eaten)
-				free(oi->content);
-			return strbuf_addf_ret(err, -1, _("parse_object_buffer failed on %s for %s"),
-					       oid_to_hex(&oi->oid), ref->refname);
-		}
-		grab_values(ref->value, deref, *obj, oi);
+		ret = grab_values(ref->value, deref, oi, ref->refname, err, &eaten);
+		if (ret < 0)
+			goto out;
 	}
 
 	grab_common_values(ref->value, deref, oi);
+	ret = 0;
+
+out:
 	if (!eaten)
 		free(oi->content);
-	return 0;
+	return ret;
 }
 
 static void populate_worktree_map(struct hashmap *map, struct worktree **worktrees)
@@ -2376,7 +2430,6 @@ static char *get_worktree_path(const struct ref_array_item *ref)
  */
 static int populate_value(struct ref_array_item *ref, struct strbuf *err)
 {
-	struct object *obj;
 	int i;
 	struct object_info empty = OBJECT_INFO_INIT;
 	int ahead_behind_atoms = 0;
@@ -2564,24 +2617,32 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
 
 
 	oi.oid = ref->objectname;
-	if (get_object(ref, 0, &obj, &oi, err))
+	if (get_object(ref, 0, &oi, err))
 		return -1;
 
 	/*
 	 * If there is no atom that wants to know about tagged
 	 * object, we are done.
 	 */
-	if (!need_tagged || (obj->type != OBJ_TAG))
+	if (!need_tagged || (oi.type != OBJ_TAG))
 		return 0;
 
 	/*
 	 * If it is a tag object, see if we use the peeled value. If we do,
 	 * grab the peeled OID.
 	 */
-	if (need_tagged && peel_iterated_oid(the_repository, &obj->oid, &oi_deref.oid))
-		die("bad tag");
+	if (need_tagged) {
+		if (!is_null_oid(&ref->peeled_oid)) {
+			oidcpy(&oi_deref.oid, &ref->peeled_oid);
+		} else if (!peel_object(the_repository, &oi.oid, &oi_deref.oid,
+					PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE)) {
+			/* We managed to peel the object ourselves. */
+		} else {
+			die("bad tag");
+		}
+	}
 
-	return get_object(ref, 1, &obj, &oi_deref, err);
+	return get_object(ref, 1, &oi_deref, err);
 }
 
 /*
@@ -2701,7 +2762,7 @@ static int start_ref_iterator_after(struct ref_iterator *iter, const char *marke
 	return ret;
 }
 
-static int for_each_fullref_with_seek(struct ref_filter *filter, each_ref_fn cb,
+static int for_each_fullref_with_seek(struct ref_filter *filter, refs_for_each_cb cb,
 				       void *cb_data, unsigned int flags)
 {
 	struct ref_iterator *iter;
@@ -2724,13 +2785,17 @@ static int for_each_fullref_with_seek(struct ref_filter *filter, each_ref_fn cb,
  * pattern match, so the callback still has to match each ref individually.
  */
 static int for_each_fullref_in_pattern(struct ref_filter *filter,
-				       each_ref_fn cb,
+				       refs_for_each_cb cb,
 				       void *cb_data)
 {
+	struct refs_for_each_ref_options opts = {
+		.exclude_patterns = filter->exclude.v,
+	};
+
 	if (filter->kind & FILTER_REFS_ROOT_REFS) {
 		/* In this case, we want to print all refs including root refs. */
 		return for_each_fullref_with_seek(filter, cb, cb_data,
-						  DO_FOR_EACH_INCLUDE_ROOT_REFS);
+						  REFS_FOR_EACH_INCLUDE_ROOT_REFS);
 	}
 
 	if (!filter->match_as_path) {
@@ -2756,10 +2821,9 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
 		return for_each_fullref_with_seek(filter, cb, cb_data, 0);
 	}
 
-	return refs_for_each_fullref_in_prefixes(get_main_ref_store(the_repository),
-						 NULL, filter->name_patterns,
-						 filter->exclude.v,
-						 cb, cb_data);
+	return refs_for_each_ref_in_prefixes(get_main_ref_store(the_repository),
+					     filter->name_patterns, &opts,
+					     cb, cb_data);
 }
 
 /*
@@ -2786,7 +2850,7 @@ static int match_points_at(struct oid_array *points_at,
 	while (obj && obj->type == OBJ_TAG) {
 		struct tag *tag = (struct tag *)obj;
 
-		if (parse_tag(tag) < 0) {
+		if (parse_tag(the_repository, tag) < 0) {
 			obj = NULL;
 			break;
 		}
@@ -2807,12 +2871,15 @@ static int match_points_at(struct oid_array *points_at,
  * Callers can then fill in other struct members at their leisure.
  */
 static struct ref_array_item *new_ref_array_item(const char *refname,
-						 const struct object_id *oid)
+						 const struct object_id *oid,
+						 const struct object_id *peeled_oid)
 {
 	struct ref_array_item *ref;
 
 	FLEX_ALLOC_STR(ref, refname, refname);
 	oidcpy(&ref->objectname, oid);
+	if (peeled_oid)
+		oidcpy(&ref->peeled_oid, peeled_oid);
 	ref->rest = NULL;
 
 	return ref;
@@ -2826,9 +2893,10 @@ static void ref_array_append(struct ref_array *array, struct ref_array_item *ref
 
 struct ref_array_item *ref_array_push(struct ref_array *array,
 				      const char *refname,
-				      const struct object_id *oid)
+				      const struct object_id *oid,
+				      const struct object_id *peeled_oid)
 {
-	struct ref_array_item *ref = new_ref_array_item(refname, oid);
+	struct ref_array_item *ref = new_ref_array_item(refname, oid, peeled_oid);
 	ref_array_append(array, ref);
 	return ref;
 }
@@ -2871,25 +2939,25 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname)
 	return ref_kind_from_refname(refname);
 }
 
-static struct ref_array_item *apply_ref_filter(const char *refname, const char *referent, const struct object_id *oid,
-			    int flag, struct ref_filter *filter)
+static struct ref_array_item *apply_ref_filter(const struct reference *ref,
+					       struct ref_filter *filter)
 {
-	struct ref_array_item *ref;
+	struct ref_array_item *item;
 	struct commit *commit = NULL;
 	unsigned int kind;
 
-	if (flag & REF_BAD_NAME) {
-		warning(_("ignoring ref with broken name %s"), refname);
+	if (ref->flags & REF_BAD_NAME) {
+		warning(_("ignoring ref with broken name %s"), ref->name);
 		return NULL;
 	}
 
-	if (flag & REF_ISBROKEN) {
-		warning(_("ignoring broken ref %s"), refname);
+	if (ref->flags & REF_ISBROKEN) {
+		warning(_("ignoring broken ref %s"), ref->name);
 		return NULL;
 	}
 
 	/* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */
-	kind = filter_ref_kind(filter, refname);
+	kind = filter_ref_kind(filter, ref->name);
 
 	/*
 	 * Generally HEAD refs are printed with special description denoting a rebase,
@@ -2902,13 +2970,13 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char *
 	else if (!(kind & filter->kind))
 		return NULL;
 
-	if (!filter_pattern_match(filter, refname))
+	if (!filter_pattern_match(filter, ref->name))
 		return NULL;
 
-	if (filter_exclude_match(filter, refname))
+	if (filter_exclude_match(filter, ref->name))
 		return NULL;
 
-	if (filter->points_at.nr && !match_points_at(&filter->points_at, oid, refname))
+	if (filter->points_at.nr && !match_points_at(&filter->points_at, ref->oid, ref->name))
 		return NULL;
 
 	/*
@@ -2918,7 +2986,7 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char *
 	 */
 	if (filter->reachable_from || filter->unreachable_from ||
 	    filter->with_commit || filter->no_commit || filter->verbose) {
-		commit = lookup_commit_reference_gently(the_repository, oid, 1);
+		commit = lookup_commit_reference_gently(the_repository, ref->oid, 1);
 		if (!commit)
 			return NULL;
 		/* We perform the filtering for the '--contains' option... */
@@ -2936,13 +3004,13 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char *
 	 * to do its job and the resulting list may yet to be pruned
 	 * by maxcount logic.
 	 */
-	ref = new_ref_array_item(refname, oid);
-	ref->commit = commit;
-	ref->flag = flag;
-	ref->kind = kind;
-	ref->symref = xstrdup_or_null(referent);
+	item = new_ref_array_item(ref->name, ref->oid, ref->peeled_oid);
+	item->commit = commit;
+	item->flag = ref->flags;
+	item->kind = kind;
+	item->symref = xstrdup_or_null(ref->target);
 
-	return ref;
+	return item;
 }
 
 struct ref_filter_cbdata {
@@ -2954,14 +3022,14 @@ struct ref_filter_cbdata {
  * A call-back given to for_each_ref().  Filter refs and keep them for
  * later object processing.
  */
-static int filter_one(const char *refname, const char *referent, const struct object_id *oid, int flag, void *cb_data)
+static int filter_one(const struct reference *ref, void *cb_data)
 {
 	struct ref_filter_cbdata *ref_cbdata = cb_data;
-	struct ref_array_item *ref;
+	struct ref_array_item *item;
 
-	ref = apply_ref_filter(refname, referent, oid, flag, ref_cbdata->filter);
-	if (ref)
-		ref_array_append(ref_cbdata->array, ref);
+	item = apply_ref_filter(ref, ref_cbdata->filter);
+	if (item)
+		ref_array_append(ref_cbdata->array, item);
 
 	return 0;
 }
@@ -2990,17 +3058,17 @@ struct ref_filter_and_format_cbdata {
 	} internal;
 };
 
-static int filter_and_format_one(const char *refname, const char *referent, const struct object_id *oid, int flag, void *cb_data)
+static int filter_and_format_one(const struct reference *ref, void *cb_data)
 {
 	struct ref_filter_and_format_cbdata *ref_cbdata = cb_data;
-	struct ref_array_item *ref;
+	struct ref_array_item *item;
 	struct strbuf output = STRBUF_INIT, err = STRBUF_INIT;
 
-	ref = apply_ref_filter(refname, referent, oid, flag, ref_cbdata->filter);
-	if (!ref)
+	item = apply_ref_filter(ref, ref_cbdata->filter);
+	if (!item)
 		return 0;
 
-	if (format_ref_array_item(ref, ref_cbdata->format, &output, &err))
+	if (format_ref_array_item(item, ref_cbdata->format, &output, &err))
 		die("%s", err.buf);
 
 	if (output.len || !ref_cbdata->format->array_opts.omit_empty) {
@@ -3010,7 +3078,7 @@ static int filter_and_format_one(const char *refname, const char *referent, cons
 
 	strbuf_release(&output);
 	strbuf_release(&err);
-	free_array_item(ref);
+	free_array_item(item);
 
 	/*
 	 * Increment the running count of refs that match the filter. If
@@ -3219,7 +3287,7 @@ void filter_is_base(struct repository *r,
 	free(bases);
 }
 
-static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref_fn fn, void *cb_data)
+static int do_filter_refs(struct ref_filter *filter, unsigned int type, refs_for_each_cb fn, void *cb_data)
 {
 	const char *prefix = NULL;
 	int ret = 0;
@@ -3583,13 +3651,14 @@ void print_formatted_ref_array(struct ref_array *array, struct ref_format *forma
 }
 
 void pretty_print_ref(const char *name, const struct object_id *oid,
+		      const struct object_id *peeled_oid,
 		      struct ref_format *format)
 {
 	struct ref_array_item *ref_item;
 	struct strbuf output = STRBUF_INIT;
 	struct strbuf err = STRBUF_INIT;
 
-	ref_item = new_ref_array_item(name, oid);
+	ref_item = new_ref_array_item(name, oid, peeled_oid);
 	ref_item->kind = ref_kind_from_refname(name);
 	if (format_ref_array_item(ref_item, format, &output, &err))
 		die("%s", err.buf);
@@ -3697,9 +3766,9 @@ void ref_filter_clear(struct ref_filter *filter)
 {
 	strvec_clear(&filter->exclude);
 	oid_array_clear(&filter->points_at);
-	free_commit_list(filter->with_commit);
-	free_commit_list(filter->no_commit);
-	free_commit_list(filter->reachable_from);
-	free_commit_list(filter->unreachable_from);
+	commit_list_free(filter->with_commit);
+	commit_list_free(filter->no_commit);
+	commit_list_free(filter->reachable_from);
+	commit_list_free(filter->unreachable_from);
 	ref_filter_init(filter);
 }
diff --git a/ref-filter.h b/ref-filter.h
index 235c60f..120221b 100644
--- a/ref-filter.h
+++ b/ref-filter.h
@@ -41,6 +41,7 @@ enum ref_sorting_order {
 
 struct ref_array_item {
 	struct object_id objectname;
+	struct object_id peeled_oid;
 	const char *rest;
 	int flag;
 	unsigned int kind;
@@ -187,6 +188,7 @@ void print_formatted_ref_array(struct ref_array *array, struct ref_format *forma
  * name must be a fully qualified refname.
  */
 void pretty_print_ref(const char *name, const struct object_id *oid,
+		      const struct object_id *peeled_oid,
 		      struct ref_format *format);
 
 /*
@@ -195,7 +197,8 @@ void pretty_print_ref(const char *name, const struct object_id *oid,
  */
 struct ref_array_item *ref_array_push(struct ref_array *array,
 				      const char *refname,
-				      const struct object_id *oid);
+				      const struct object_id *oid,
+				      const struct object_id *peeled_oid);
 
 /*
  * If the provided format includes ahead-behind atoms, then compute the
diff --git a/reflog-walk.c b/reflog-walk.c
index 4f1ce04..4dbeaa9 100644
--- a/reflog-walk.c
+++ b/reflog-walk.c
@@ -157,7 +157,8 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
 	int recno = -1;
 	struct string_list_item *item;
 	struct complete_reflogs *reflogs;
-	char *branch, *at = strchr(name, '@');
+	char *branch;
+	const char *at = strchr(name, '@');
 	struct commit_reflog *commit_reflog;
 	enum selector_type selector = SELECTOR_NONE;
 
diff --git a/reflog.c b/reflog.c
index 65ef259..1460ae9 100644
--- a/reflog.c
+++ b/reflog.c
@@ -423,16 +423,13 @@ int should_expire_reflog_ent_verbose(struct object_id *ooid,
 	return expire;
 }
 
-static int push_tip_to_list(const char *refname UNUSED,
-			    const char *referent UNUSED,
-			    const struct object_id *oid,
-			    int flags, void *cb_data)
+static int push_tip_to_list(const struct reference *ref, void *cb_data)
 {
 	struct commit_list **list = cb_data;
 	struct commit *tip_commit;
-	if (flags & REF_ISSYMREF)
+	if (ref->flags & REF_ISSYMREF)
 		return 0;
-	tip_commit = lookup_commit_reference_gently(the_repository, oid, 1);
+	tip_commit = lookup_commit_reference_gently(the_repository, ref->oid, 1);
 	if (!tip_commit)
 		return 0;
 	commit_list_insert(tip_commit, list);
@@ -496,7 +493,7 @@ void reflog_expiry_cleanup(void *cb_data)
 	case UE_HEAD:
 		for (elem = cb->tips; elem; elem = elem->next)
 			clear_commit_marks(elem->item, REACHABLE);
-		free_commit_list(cb->tips);
+		commit_list_free(cb->tips);
 		break;
 	case UE_NORMAL:
 		clear_commit_marks(cb->tip_commit, REACHABLE);
@@ -504,7 +501,7 @@ void reflog_expiry_cleanup(void *cb_data)
 	}
 	for (elem = cb->mark_list; elem; elem = elem->next)
 		clear_commit_marks(elem->item, REACHABLE);
-	free_commit_list(cb->mark_list);
+	commit_list_free(cb->mark_list);
 }
 
 int count_reflog_ent(const char *refname UNUSED,
diff --git a/refs.c b/refs.c
index 9653813..6fb8f9d 100644
--- a/refs.c
+++ b/refs.c
@@ -5,6 +5,7 @@
 #define USE_THE_REPOSITORY_VARIABLE
 
 #include "git-compat-util.h"
+#include "abspath.h"
 #include "advice.h"
 #include "config.h"
 #include "environment.h"
@@ -15,7 +16,6 @@
 #include "iterator.h"
 #include "refs.h"
 #include "refs/refs-internal.h"
-#include "run-command.h"
 #include "hook.h"
 #include "object-name.h"
 #include "odb.h"
@@ -26,7 +26,6 @@
 #include "strvec.h"
 #include "repo-settings.h"
 #include "setup.h"
-#include "sigchain.h"
 #include "date.h"
 #include "commit.h"
 #include "wildmatch.h"
@@ -320,6 +319,49 @@ int check_refname_format(const char *refname, int flags)
 	return check_or_sanitize_refname(refname, flags, NULL);
 }
 
+int refs_fsck_ref(struct ref_store *refs UNUSED, struct fsck_options *o,
+		  struct fsck_ref_report *report,
+		  const char *refname UNUSED, const struct object_id *oid)
+{
+	if (is_null_oid(oid))
+		return fsck_report_ref(o, report, FSCK_MSG_BAD_REF_OID,
+				       "points to invalid object ID '%s'",
+				       oid_to_hex(oid));
+
+	return 0;
+}
+
+int refs_fsck_symref(struct ref_store *refs UNUSED, struct fsck_options *o,
+		     struct fsck_ref_report *report,
+		     const char *refname, const char *target)
+{
+	const char *stripped_refname;
+
+	parse_worktree_ref(refname, NULL, NULL, &stripped_refname);
+
+	if (!strcmp(stripped_refname, "HEAD") &&
+	    !starts_with(target, "refs/heads/") &&
+	    fsck_report_ref(o, report, FSCK_MSG_BAD_HEAD_TARGET,
+			    "HEAD points to non-branch '%s'", target))
+		return -1;
+
+	if (is_root_ref(target))
+		return 0;
+
+	if (check_refname_format(target, 0) &&
+	    fsck_report_ref(o, report, FSCK_MSG_BAD_REFERENT_NAME,
+			    "points to invalid refname '%s'", target))
+		return -1;
+
+	if (!starts_with(target, "refs/") &&
+	    !starts_with(target, "worktrees/") &&
+	    fsck_report_ref(o, report, FSCK_MSG_SYMREF_TARGET_IS_NOT_A_REF,
+			    "points to non-ref target '%s'", target))
+		return -1;
+
+	return 0;
+}
+
 int refs_fsck(struct ref_store *refs, struct fsck_options *o,
 	      struct worktree *wt)
 {
@@ -401,8 +443,8 @@ char *refs_resolve_refdup(struct ref_store *refs,
 /* The argument to for_each_filter_refs */
 struct for_each_ref_filter {
 	const char *pattern;
-	const char *prefix;
-	each_ref_fn *fn;
+	size_t trim_prefix;
+	refs_for_each_cb *fn;
 	void *cb_data;
 };
 
@@ -426,17 +468,21 @@ int refs_ref_exists(struct ref_store *refs, const char *refname)
 					 NULL, NULL);
 }
 
-static int for_each_filter_refs(const char *refname, const char *referent,
-				const struct object_id *oid,
-				int flags, void *data)
+static int for_each_filter_refs(const struct reference *ref, void *data)
 {
 	struct for_each_ref_filter *filter = data;
 
-	if (wildmatch(filter->pattern, refname, 0))
+	if (wildmatch(filter->pattern, ref->name, 0))
 		return 0;
-	if (filter->prefix)
-		skip_prefix(refname, filter->prefix, &refname);
-	return filter->fn(refname, referent, oid, flags, filter->cb_data);
+	if (filter->trim_prefix) {
+		struct reference skipped = *ref;
+		if (strlen(skipped.name) <= filter->trim_prefix)
+			BUG("attempt to trim too many characters");
+		skipped.name += filter->trim_prefix;
+		return filter->fn(&skipped, filter->cb_data);
+	} else {
+		return filter->fn(ref, filter->cb_data);
+	}
 }
 
 struct warn_if_dangling_data {
@@ -447,17 +493,15 @@ struct warn_if_dangling_data {
 	int dry_run;
 };
 
-static int warn_if_dangling_symref(const char *refname, const char *referent UNUSED,
-				   const struct object_id *oid UNUSED,
-				   int flags, void *cb_data)
+static int warn_if_dangling_symref(const struct reference *ref, void *cb_data)
 {
 	struct warn_if_dangling_data *d = cb_data;
 	const char *resolves_to, *msg;
 
-	if (!(flags & REF_ISSYMREF))
+	if (!(ref->flags & REF_ISSYMREF))
 		return 0;
 
-	resolves_to = refs_resolve_ref_unsafe(d->refs, refname, 0, NULL, NULL);
+	resolves_to = refs_resolve_ref_unsafe(d->refs, ref->name, 0, NULL, NULL);
 	if (!resolves_to
 	    || !string_list_has_string(d->refnames, resolves_to)) {
 		return 0;
@@ -466,7 +510,7 @@ static int warn_if_dangling_symref(const char *refname, const char *referent UNU
 	msg = d->dry_run
 		? _("%s%s will become dangling after %s is deleted\n")
 		: _("%s%s has become dangling after %s was deleted\n");
-	fprintf(d->fp, msg, d->indent, refname, resolves_to);
+	fprintf(d->fp, msg, d->indent, ref->name, resolves_to);
 	return 0;
 }
 
@@ -481,25 +525,40 @@ void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,
 		.indent = indent,
 		.dry_run = dry_run,
 	};
-	refs_for_each_rawref(refs, warn_if_dangling_symref, &data);
+	struct refs_for_each_ref_options opts = {
+		.flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+	};
+	refs_for_each_ref_ext(refs, warn_if_dangling_symref, &data, &opts);
 }
 
-int refs_for_each_tag_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_tag_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
 {
-	return refs_for_each_ref_in(refs, "refs/tags/", fn, cb_data);
+	struct refs_for_each_ref_options opts = {
+		.prefix = "refs/tags/",
+		.trim_prefix = strlen("refs/tags/"),
+	};
+	return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
 }
 
-int refs_for_each_branch_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_branch_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
 {
-	return refs_for_each_ref_in(refs, "refs/heads/", fn, cb_data);
+	struct refs_for_each_ref_options opts = {
+		.prefix = "refs/heads/",
+		.trim_prefix = strlen("refs/heads/"),
+	};
+	return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
 }
 
-int refs_for_each_remote_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_remote_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
 {
-	return refs_for_each_ref_in(refs, "refs/remotes/", fn, cb_data);
+	struct refs_for_each_ref_options opts = {
+		.prefix = "refs/remotes/",
+		.trim_prefix = strlen("refs/remotes/"),
+	};
+	return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
 }
 
-int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_head_ref_namespaced(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
 {
 	struct strbuf buf = STRBUF_INIT;
 	int ret = 0;
@@ -507,8 +566,15 @@ int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_da
 	int flag;
 
 	strbuf_addf(&buf, "%sHEAD", get_git_namespace());
-	if (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag))
-		ret = fn(buf.buf, NULL, &oid, flag, cb_data);
+	if (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag)) {
+		struct reference ref = {
+			.name = buf.buf,
+			.oid = &oid,
+			.flags = flag,
+		};
+
+		ret = fn(&ref, cb_data);
+	}
 	strbuf_release(&buf);
 
 	return ret;
@@ -540,42 +606,6 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,
 	strbuf_release(&normalized_pattern);
 }
 
-int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
-			      const char *pattern, const char *prefix, void *cb_data)
-{
-	struct strbuf real_pattern = STRBUF_INIT;
-	struct for_each_ref_filter filter;
-	int ret;
-
-	if (!prefix && !starts_with(pattern, "refs/"))
-		strbuf_addstr(&real_pattern, "refs/");
-	else if (prefix)
-		strbuf_addstr(&real_pattern, prefix);
-	strbuf_addstr(&real_pattern, pattern);
-
-	if (!has_glob_specials(pattern)) {
-		/* Append implied '/' '*' if not present. */
-		strbuf_complete(&real_pattern, '/');
-		/* No need to check for '*', there is none. */
-		strbuf_addch(&real_pattern, '*');
-	}
-
-	filter.pattern = real_pattern.buf;
-	filter.prefix = prefix;
-	filter.fn = fn;
-	filter.cb_data = cb_data;
-	ret = refs_for_each_ref(refs, for_each_filter_refs, &filter);
-
-	strbuf_release(&real_pattern);
-	return ret;
-}
-
-int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,
-			   const char *pattern, void *cb_data)
-{
-	return refs_for_each_glob_ref_in(refs, fn, pattern, NULL, cb_data);
-}
-
 const char *prettify_refname(const char *name)
 {
 	if (skip_prefix(name, "refs/heads/", &name) ||
@@ -1217,6 +1247,7 @@ void ref_transaction_free(struct ref_transaction *transaction)
 		free(transaction->updates[i]->committer_info);
 		free((char *)transaction->updates[i]->new_target);
 		free((char *)transaction->updates[i]->old_target);
+		free((char *)transaction->updates[i]->rejection_details);
 		free(transaction->updates[i]);
 	}
 
@@ -1231,7 +1262,8 @@ void ref_transaction_free(struct ref_transaction *transaction)
 
 int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction,
 				       size_t update_idx,
-				       enum ref_transaction_error err)
+				       enum ref_transaction_error err,
+				       struct strbuf *details)
 {
 	if (update_idx >= transaction->nr)
 		BUG("trying to set rejection on invalid update index");
@@ -1257,6 +1289,7 @@ int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction,
 			   transaction->updates[update_idx]->refname, 0);
 
 	transaction->updates[update_idx]->rejection_err = err;
+	transaction->updates[update_idx]->rejection_details = strbuf_detach(details, NULL);
 	ALLOC_GROW(transaction->rejections->update_indices,
 		   transaction->rejections->nr + 1,
 		   transaction->rejections->alloc);
@@ -1735,14 +1768,21 @@ const char *find_descendant_ref(const char *dirname,
 	return NULL;
 }
 
-int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_head_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
 {
 	struct object_id oid;
 	int flag;
 
 	if (refs_resolve_ref_unsafe(refs, "HEAD", RESOLVE_REF_READING,
-				    &oid, &flag))
-		return fn("HEAD", NULL, &oid, flag, cb_data);
+				    &oid, &flag)) {
+		struct reference ref = {
+			.name = "HEAD",
+			.oid = &oid,
+			.flags = flag,
+		};
+
+		return fn(&ref, cb_data);
+	}
 
 	return 0;
 }
@@ -1752,7 +1792,7 @@ struct ref_iterator *refs_ref_iterator_begin(
 		const char *prefix,
 		const char **exclude_patterns,
 		int trim,
-		enum do_for_each_ref_flags flags)
+		enum refs_for_each_flag flags)
 {
 	struct ref_iterator *iter;
 	struct strvec normalized_exclude_patterns = STRVEC_INIT;
@@ -1774,14 +1814,14 @@ struct ref_iterator *refs_ref_iterator_begin(
 		exclude_patterns = normalized_exclude_patterns.v;
 	}
 
-	if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
+	if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN)) {
 		static int ref_paranoia = -1;
 
 		if (ref_paranoia < 0)
 			ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 1);
 		if (ref_paranoia) {
-			flags |= DO_FOR_EACH_INCLUDE_BROKEN;
-			flags |= DO_FOR_EACH_OMIT_DANGLING_SYMREFS;
+			flags |= REFS_FOR_EACH_INCLUDE_BROKEN;
+			flags |= REFS_FOR_EACH_OMIT_DANGLING_SYMREFS;
 		}
 	}
 
@@ -1798,85 +1838,105 @@ struct ref_iterator *refs_ref_iterator_begin(
 	return iter;
 }
 
-static int do_for_each_ref(struct ref_store *refs, const char *prefix,
-			   const char **exclude_patterns,
-			   each_ref_fn fn, int trim,
-			   enum do_for_each_ref_flags flags, void *cb_data)
-{
-	struct ref_iterator *iter;
-
-	if (!refs)
-		return 0;
-
-	iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns, trim,
-				       flags);
-
-	return do_for_each_ref_iterator(iter, fn, cb_data);
-}
-
-int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
-{
-	return do_for_each_ref(refs, "", NULL, fn, 0, 0, cb_data);
-}
-
-int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
-			 each_ref_fn fn, void *cb_data)
-{
-	return do_for_each_ref(refs, prefix, NULL, fn, strlen(prefix), 0, cb_data);
-}
-
-int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
-			     const char **exclude_patterns,
-			     each_ref_fn fn, void *cb_data)
-{
-	return do_for_each_ref(refs, prefix, exclude_patterns, fn, 0, 0, cb_data);
-}
-
-int refs_for_each_replace_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
-{
-	const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
-	return do_for_each_ref(refs, git_replace_ref_base, NULL, fn,
-			       strlen(git_replace_ref_base),
-			       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
-}
-
-int refs_for_each_namespaced_ref(struct ref_store *refs,
-				 const char **exclude_patterns,
-				 each_ref_fn fn, void *cb_data)
+int refs_for_each_ref_ext(struct ref_store *refs,
+			  refs_for_each_cb cb, void *cb_data,
+			  const struct refs_for_each_ref_options *opts)
 {
 	struct strvec namespaced_exclude_patterns = STRVEC_INIT;
-	struct strbuf prefix = STRBUF_INIT;
+	struct strbuf namespaced_prefix = STRBUF_INIT;
+	struct strbuf real_pattern = STRBUF_INIT;
+	struct for_each_ref_filter filter;
+	struct ref_iterator *iter;
+	size_t trim_prefix = opts->trim_prefix;
+	const char **exclude_patterns;
+	const char *prefix;
 	int ret;
 
-	exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
-							   get_git_namespace(),
-							   &namespaced_exclude_patterns);
+	if (!refs)
+		BUG("no ref store passed");
 
-	strbuf_addf(&prefix, "%srefs/", get_git_namespace());
-	ret = do_for_each_ref(refs, prefix.buf, exclude_patterns, fn, 0, 0, cb_data);
+	if (opts->trim_prefix) {
+		size_t prefix_len;
+
+		if (!opts->prefix)
+			BUG("trimming only allowed with a prefix");
+
+		prefix_len = strlen(opts->prefix);
+		if (prefix_len == opts->trim_prefix && opts->prefix[prefix_len - 1] != '/')
+			BUG("ref pattern must end in a trailing slash when trimming");
+	}
+
+	if (opts->pattern) {
+		if (!opts->prefix && !starts_with(opts->pattern, "refs/"))
+			strbuf_addstr(&real_pattern, "refs/");
+		else if (opts->prefix)
+			strbuf_addstr(&real_pattern, opts->prefix);
+		strbuf_addstr(&real_pattern, opts->pattern);
+
+		if (!has_glob_specials(opts->pattern)) {
+			/* Append implied '/' '*' if not present. */
+			strbuf_complete(&real_pattern, '/');
+			/* No need to check for '*', there is none. */
+			strbuf_addch(&real_pattern, '*');
+		}
+
+		filter.pattern = real_pattern.buf;
+		filter.trim_prefix = opts->trim_prefix;
+		filter.fn = cb;
+		filter.cb_data = cb_data;
+
+		/*
+		 * We need to trim the prefix in the callback function as the
+		 * pattern is expected to match on the full refname.
+		 */
+		trim_prefix = 0;
+
+		cb = for_each_filter_refs;
+		cb_data = &filter;
+	}
+
+	if (opts->namespace) {
+		strbuf_addstr(&namespaced_prefix, opts->namespace);
+		if (opts->prefix)
+			strbuf_addstr(&namespaced_prefix, opts->prefix);
+		else
+			strbuf_addstr(&namespaced_prefix, "refs/");
+
+		prefix = namespaced_prefix.buf;
+		exclude_patterns = get_namespaced_exclude_patterns(opts->exclude_patterns,
+								   opts->namespace,
+								   &namespaced_exclude_patterns);
+	} else {
+		prefix = opts->prefix ? opts->prefix : "";
+		exclude_patterns = opts->exclude_patterns;
+	}
+
+	iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns,
+				       trim_prefix, opts->flags);
+
+	ret = do_for_each_ref_iterator(iter, cb, cb_data);
 
 	strvec_clear(&namespaced_exclude_patterns);
-	strbuf_release(&prefix);
+	strbuf_release(&namespaced_prefix);
+	strbuf_release(&real_pattern);
 	return ret;
 }
 
-int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
 {
-	return refs_for_each_rawref_in(refs, "", fn, cb_data);
+	struct refs_for_each_ref_options opts = { 0 };
+	return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
 }
 
-int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
-			    each_ref_fn fn, void *cb_data)
+int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
 {
-	return do_for_each_ref(refs, prefix, NULL, fn, 0,
-			       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
-}
-
-int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,
-				    void *cb_data)
-{
-	return do_for_each_ref(refs, "", NULL, fn, 0,
-			       DO_FOR_EACH_INCLUDE_ROOT_REFS, cb_data);
+	const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
+	struct refs_for_each_ref_options opts = {
+		.prefix = git_replace_ref_base,
+		.trim_prefix = strlen(git_replace_ref_base),
+		.flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+	};
+	return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
 }
 
 static int qsort_strcmp(const void *va, const void *vb)
@@ -1937,40 +1997,31 @@ static void find_longest_prefixes(struct string_list *out,
 	strbuf_release(&prefix);
 }
 
-int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
-				      const char *namespace,
-				      const char **patterns,
-				      const char **exclude_patterns,
-				      each_ref_fn fn, void *cb_data)
+int refs_for_each_ref_in_prefixes(struct ref_store *ref_store,
+				  const char **prefixes,
+				  const struct refs_for_each_ref_options *opts,
+				  refs_for_each_cb cb, void *cb_data)
 {
-	struct strvec namespaced_exclude_patterns = STRVEC_INIT;
-	struct string_list prefixes = STRING_LIST_INIT_DUP;
+	struct string_list longest_prefixes = STRING_LIST_INIT_DUP;
 	struct string_list_item *prefix;
-	struct strbuf buf = STRBUF_INIT;
-	int ret = 0, namespace_len;
+	int ret = 0;
 
-	find_longest_prefixes(&prefixes, patterns);
+	if (opts->prefix)
+		BUG("refs_for_each_ref_in_prefixes called with specific prefix");
 
-	if (namespace)
-		strbuf_addstr(&buf, namespace);
-	namespace_len = buf.len;
+	find_longest_prefixes(&longest_prefixes, prefixes);
 
-	exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
-							   namespace,
-							   &namespaced_exclude_patterns);
+	for_each_string_list_item(prefix, &longest_prefixes) {
+		struct refs_for_each_ref_options prefix_opts = *opts;
+		prefix_opts.prefix = prefix->string;
 
-	for_each_string_list_item(prefix, &prefixes) {
-		strbuf_addstr(&buf, prefix->string);
-		ret = refs_for_each_fullref_in(ref_store, buf.buf,
-					       exclude_patterns, fn, cb_data);
+		ret = refs_for_each_ref_ext(ref_store, cb, cb_data,
+					    &prefix_opts);
 		if (ret)
 			break;
-		strbuf_setlen(&buf, namespace_len);
 	}
 
-	strvec_clear(&namespaced_exclude_patterns);
-	string_list_clear(&prefixes, 0);
-	strbuf_release(&buf);
+	string_list_clear(&longest_prefixes, 0);
 	return ret;
 }
 
@@ -2106,15 +2157,93 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
 	return NULL;
 }
 
+void refs_create_refdir_stubs(struct repository *repo, const char *refdir,
+			      const char *refs_heads_content)
+{
+	struct strbuf path = STRBUF_INIT;
+
+	strbuf_addf(&path, "%s/HEAD", refdir);
+	write_file(path.buf, "ref: refs/heads/.invalid");
+	adjust_shared_perm(repo, path.buf);
+
+	strbuf_reset(&path);
+	strbuf_addf(&path, "%s/refs", refdir);
+	safe_create_dir(repo, path.buf, 1);
+
+	if (refs_heads_content) {
+		strbuf_reset(&path);
+		strbuf_addf(&path, "%s/refs/heads", refdir);
+		write_file(path.buf, "%s", refs_heads_content);
+		adjust_shared_perm(repo, path.buf);
+	}
+
+	strbuf_release(&path);
+}
+
 /* backend functions */
 int ref_store_create_on_disk(struct ref_store *refs, int flags, struct strbuf *err)
 {
-	return refs->be->create_on_disk(refs, flags, err);
+	int ret = refs->be->create_on_disk(refs, flags, err);
+
+	if (!ret) {
+		/* Creation of stubs for linked worktrees are handled in the worktree code. */
+		if (!(flags & REF_STORE_CREATE_ON_DISK_IS_WORKTREE) && refs->repo->ref_storage_payload) {
+			refs_create_refdir_stubs(refs->repo, refs->repo->gitdir,
+						 "repository uses alternate refs storage");
+		} else if (ref_storage_format_by_name(refs->be->name) != REF_STORAGE_FORMAT_FILES) {
+			struct strbuf msg = STRBUF_INIT;
+			strbuf_addf(&msg, "this repository uses the %s format", refs->be->name);
+			refs_create_refdir_stubs(refs->repo, refs->gitdir, msg.buf);
+			strbuf_release(&msg);
+		}
+	}
+
+	return ret;
 }
 
 int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err)
 {
-	return refs->be->remove_on_disk(refs, err);
+	int ret = refs->be->remove_on_disk(refs, err);
+
+	if (!ret) {
+		enum ref_storage_format format = ref_storage_format_by_name(refs->be->name);
+		struct strbuf sb = STRBUF_INIT;
+
+		/* Backends apart from the files backend create stubs. */
+		if (format == REF_STORAGE_FORMAT_FILES)
+			return ret;
+
+		/* Alternate refs backend require stubs in the gitdir. */
+		if (refs->repo->ref_storage_payload)
+			return ret;
+
+		strbuf_addf(&sb, "%s/HEAD", refs->gitdir);
+		if (unlink(sb.buf) < 0) {
+			strbuf_addf(err, "could not delete stub HEAD: %s",
+				    strerror(errno));
+			ret = -1;
+		}
+		strbuf_reset(&sb);
+
+		strbuf_addf(&sb, "%s/refs/heads", refs->gitdir);
+		if (unlink(sb.buf) < 0) {
+			strbuf_addf(err, "could not delete stub heads: %s",
+				    strerror(errno));
+			ret = -1;
+		}
+		strbuf_reset(&sb);
+
+		strbuf_addf(&sb, "%s/refs", refs->gitdir);
+		if (rmdir(sb.buf) < 0) {
+			strbuf_addf(err, "could not delete refs directory: %s",
+				    strerror(errno));
+			ret = -1;
+		}
+
+		strbuf_release(&sb);
+	}
+
+	return ret;
 }
 
 int repo_resolve_gitlink_ref(struct repository *r,
@@ -2167,7 +2296,11 @@ static struct ref_store *ref_store_init(struct repository *repo,
 	if (!be)
 		BUG("reference backend is unknown");
 
-	refs = be->init(repo, gitdir, flags);
+	/*
+	 * TODO Send in a 'struct worktree' instead of a 'gitdir', and
+	 * allow the backend to handle how it wants to deal with worktrees.
+	 */
+	refs = be->init(repo, repo->ref_storage_payload, gitdir, flags);
 	return refs;
 }
 
@@ -2299,25 +2432,28 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,
 	refs->gitdir = xstrdup(path);
 }
 
-/* backend functions */
-int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts)
-{
-	return refs->be->pack_refs(refs, opts);
-}
-
-int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts)
+int refs_optimize(struct ref_store *refs, struct refs_optimize_opts *opts)
 {
 	return refs->be->optimize(refs, opts);
 }
 
-int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled)
+int refs_optimize_required(struct ref_store *refs,
+			   struct refs_optimize_opts *opts,
+			   bool *required)
 {
-	if (current_ref_iter &&
-	    (current_ref_iter->oid == base ||
-	     oideq(current_ref_iter->oid, base)))
-		return ref_iterator_peel(current_ref_iter, peeled);
+	return refs->be->optimize_required(refs, opts, required);
+}
 
-	return peel_object(r, base, peeled) ? -1 : 0;
+int reference_get_peeled_oid(struct repository *repo,
+			     const struct reference *ref,
+			     struct object_id *peeled_oid)
+{
+	if (ref->peeled_oid) {
+		oidcpy(peeled_oid, ref->peeled_oid);
+		return 0;
+	}
+
+	return peel_object(repo, ref->oid, peeled_oid, 0) ? -1 : 0;
 }
 
 int refs_update_symref(struct ref_store *refs, const char *ref,
@@ -2405,68 +2541,86 @@ static int ref_update_reject_duplicates(struct string_list *refnames,
 	return 0;
 }
 
+struct transaction_feed_cb_data {
+	size_t index;
+	struct strbuf buf;
+};
+
+static int transaction_hook_feed_stdin(int hook_stdin_fd, void *pp_cb, void *pp_task_cb)
+{
+	struct hook_cb_data *hook_cb = pp_cb;
+	struct ref_transaction *transaction = hook_cb->options->feed_pipe_ctx;
+	struct transaction_feed_cb_data *feed_cb_data = pp_task_cb;
+	struct strbuf *buf = &feed_cb_data->buf;
+	struct ref_update *update;
+	size_t i = feed_cb_data->index++;
+	int ret;
+
+	if (i >= transaction->nr)
+		return 1; /* No more refs to process */
+
+	update = transaction->updates[i];
+
+	if (update->flags & REF_LOG_ONLY)
+		return 0;
+
+	strbuf_reset(buf);
+
+	if (!(update->flags & REF_HAVE_OLD))
+		strbuf_addf(buf, "%s ", oid_to_hex(null_oid(the_hash_algo)));
+	else if (update->old_target)
+		strbuf_addf(buf, "ref:%s ", update->old_target);
+	else
+		strbuf_addf(buf, "%s ", oid_to_hex(&update->old_oid));
+
+	if (!(update->flags & REF_HAVE_NEW))
+		strbuf_addf(buf, "%s ", oid_to_hex(null_oid(the_hash_algo)));
+	else if (update->new_target)
+		strbuf_addf(buf, "ref:%s ", update->new_target);
+	else
+		strbuf_addf(buf, "%s ", oid_to_hex(&update->new_oid));
+
+	strbuf_addf(buf, "%s\n", update->refname);
+
+	ret = write_in_full(hook_stdin_fd, buf->buf, buf->len);
+	if (ret < 0 && errno != EPIPE)
+		return ret;
+
+	return 0; /* no more input to feed */
+}
+
+static void *transaction_feed_cb_data_alloc(void *feed_pipe_ctx UNUSED)
+{
+	struct transaction_feed_cb_data *data = xmalloc(sizeof(*data));
+	strbuf_init(&data->buf, 0);
+	data->index = 0;
+	return data;
+}
+
+static void transaction_feed_cb_data_free(void *data)
+{
+	struct transaction_feed_cb_data *d = data;
+	if (!d)
+		return;
+	strbuf_release(&d->buf);
+	free(d);
+}
+
 static int run_transaction_hook(struct ref_transaction *transaction,
 				const char *state)
 {
-	struct child_process proc = CHILD_PROCESS_INIT;
-	struct strbuf buf = STRBUF_INIT;
-	const char *hook;
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
 	int ret = 0;
 
-	hook = find_hook(transaction->ref_store->repo, "reference-transaction");
-	if (!hook)
-		return ret;
+	strvec_push(&opt.args, state);
 
-	strvec_pushl(&proc.args, hook, state, NULL);
-	proc.in = -1;
-	proc.stdout_to_stderr = 1;
-	proc.trace2_hook_name = "reference-transaction";
+	opt.feed_pipe = transaction_hook_feed_stdin;
+	opt.feed_pipe_ctx = transaction;
+	opt.feed_pipe_cb_data_alloc = transaction_feed_cb_data_alloc;
+	opt.feed_pipe_cb_data_free = transaction_feed_cb_data_free;
 
-	ret = start_command(&proc);
-	if (ret)
-		return ret;
+	ret = run_hooks_opt(transaction->ref_store->repo, "reference-transaction", &opt);
 
-	sigchain_push(SIGPIPE, SIG_IGN);
-
-	for (size_t i = 0; i < transaction->nr; i++) {
-		struct ref_update *update = transaction->updates[i];
-
-		if (update->flags & REF_LOG_ONLY)
-			continue;
-
-		strbuf_reset(&buf);
-
-		if (!(update->flags & REF_HAVE_OLD))
-			strbuf_addf(&buf, "%s ", oid_to_hex(null_oid(the_hash_algo)));
-		else if (update->old_target)
-			strbuf_addf(&buf, "ref:%s ", update->old_target);
-		else
-			strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid));
-
-		if (!(update->flags & REF_HAVE_NEW))
-			strbuf_addf(&buf, "%s ", oid_to_hex(null_oid(the_hash_algo)));
-		else if (update->new_target)
-			strbuf_addf(&buf, "ref:%s ", update->new_target);
-		else
-			strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid));
-
-		strbuf_addf(&buf, "%s\n", update->refname);
-
-		if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
-			if (errno != EPIPE) {
-				/* Don't leak errno outside this API */
-				errno = 0;
-				ret = -1;
-			}
-			break;
-		}
-	}
-
-	close(proc.in);
-	sigchain_pop(SIGPIPE);
-	strbuf_release(&buf);
-
-	ret |= finish_command(&proc);
 	return ret;
 }
 
@@ -2491,7 +2645,7 @@ int ref_transaction_prepare(struct ref_transaction *transaction,
 		break;
 	}
 
-	if (refs->repo->objects->sources->disable_ref_updates) {
+	if (refs->repo->disable_ref_updates) {
 		strbuf_addstr(err,
 			      _("ref updates forbidden inside quarantine environment"));
 		return -1;
@@ -2638,30 +2792,33 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs
 			if (!initial_transaction &&
 			    (strset_contains(&conflicting_dirnames, dirname.buf) ||
 			     !refs_read_raw_ref(refs, dirname.buf, &oid, &referent,
-						       &type, &ignore_errno))) {
-				if (transaction && ref_transaction_maybe_set_rejected(
-					    transaction, *update_idx,
-					    REF_TRANSACTION_ERROR_NAME_CONFLICT)) {
-					strset_remove(&dirnames, dirname.buf);
-					strset_add(&conflicting_dirnames, dirname.buf);
-					continue;
-				}
+						&type, &ignore_errno))) {
 
 				strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
 					    dirname.buf, refname);
+
+				if (transaction && ref_transaction_maybe_set_rejected(
+					    transaction, *update_idx,
+					    REF_TRANSACTION_ERROR_NAME_CONFLICT, err)) {
+					strset_remove(&dirnames, dirname.buf);
+					strset_add(&conflicting_dirnames, dirname.buf);
+					goto next_ref;
+				}
+
 				goto cleanup;
 			}
 
 			if (extras && string_list_has_string(extras, dirname.buf)) {
-				if (transaction && ref_transaction_maybe_set_rejected(
-					    transaction, *update_idx,
-					    REF_TRANSACTION_ERROR_NAME_CONFLICT)) {
-					strset_remove(&dirnames, dirname.buf);
-					continue;
-				}
-
 				strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
 					    refname, dirname.buf);
+
+				if (transaction && ref_transaction_maybe_set_rejected(
+					    transaction, *update_idx,
+					    REF_TRANSACTION_ERROR_NAME_CONFLICT, err)) {
+					strset_remove(&dirnames, dirname.buf);
+					goto next_ref;
+				}
+
 				goto cleanup;
 			}
 		}
@@ -2682,23 +2839,23 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs
 
 			if (!iter)
 				iter = refs_ref_iterator_begin(refs, dirname.buf, NULL, 0,
-							       DO_FOR_EACH_INCLUDE_BROKEN);
+							       REFS_FOR_EACH_INCLUDE_BROKEN);
 			else if (ref_iterator_seek(iter, dirname.buf,
 						   REF_ITERATOR_SEEK_SET_PREFIX) < 0)
 				goto cleanup;
 
 			while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
 				if (skip &&
-				    string_list_has_string(skip, iter->refname))
+				    string_list_has_string(skip, iter->ref.name))
 					continue;
+				strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
+					    iter->ref.name, refname);
 
 				if (transaction && ref_transaction_maybe_set_rejected(
 					    transaction, *update_idx,
-					    REF_TRANSACTION_ERROR_NAME_CONFLICT))
-					continue;
+					    REF_TRANSACTION_ERROR_NAME_CONFLICT, err))
+					goto next_ref;
 
-				strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
-					    iter->refname, refname);
 				goto cleanup;
 			}
 
@@ -2708,15 +2865,17 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs
 
 		extra_refname = find_descendant_ref(dirname.buf, extras, skip);
 		if (extra_refname) {
-			if (transaction && ref_transaction_maybe_set_rejected(
-				    transaction, *update_idx,
-				    REF_TRANSACTION_ERROR_NAME_CONFLICT))
-				continue;
-
 			strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
 				    refname, extra_refname);
+
+			if (transaction && ref_transaction_maybe_set_rejected(
+				    transaction, *update_idx,
+				    REF_TRANSACTION_ERROR_NAME_CONFLICT, err))
+				goto next_ref;
+
 			goto cleanup;
 		}
+next_ref:;
 	}
 
 	ret = 0;
@@ -2753,14 +2912,10 @@ struct do_for_each_reflog_help {
 	void *cb_data;
 };
 
-static int do_for_each_reflog_helper(const char *refname,
-				     const char *referent UNUSED,
-				     const struct object_id *oid UNUSED,
-				     int flags UNUSED,
-				     void *cb_data)
+static int do_for_each_reflog_helper(const struct reference *ref, void *cb_data)
 {
 	struct do_for_each_reflog_help *hp = cb_data;
-	return hp->fn(refname, hp->cb_data);
+	return hp->fn(ref->name, hp->cb_data);
 }
 
 int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_data)
@@ -2849,7 +3004,7 @@ void ref_transaction_for_each_rejected_update(struct ref_transaction *transactio
 		   (update->flags & REF_HAVE_OLD) ? &update->old_oid : NULL,
 		   (update->flags & REF_HAVE_NEW) ? &update->new_oid : NULL,
 		   update->old_target, update->new_target,
-		   update->rejection_err, cb_data);
+		   update->rejection_err, update->rejection_details, cb_data);
 	}
 }
 
@@ -2976,25 +3131,24 @@ struct migration_data {
 	uint64_t index;
 };
 
-static int migrate_one_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-			   int flags, void *cb_data)
+static int migrate_one_ref(const struct reference *ref, void *cb_data)
 {
 	struct migration_data *data = cb_data;
 	struct strbuf symref_target = STRBUF_INIT;
 	int ret;
 
-	if (flags & REF_ISSYMREF) {
-		ret = refs_read_symbolic_ref(data->old_refs, refname, &symref_target);
+	if (ref->flags & REF_ISSYMREF) {
+		ret = refs_read_symbolic_ref(data->old_refs, ref->name, &symref_target);
 		if (ret < 0)
 			goto done;
 
-		ret = ref_transaction_update(data->transaction, refname, NULL, null_oid(the_hash_algo),
+		ret = ref_transaction_update(data->transaction, ref->name, NULL, null_oid(the_hash_algo),
 					     symref_target.buf, NULL,
 					     REF_SKIP_CREATE_REFLOG | REF_NO_DEREF, NULL, data->errbuf);
 		if (ret < 0)
 			goto done;
 	} else {
-		ret = ref_transaction_create(data->transaction, refname, oid, NULL,
+		ret = ref_transaction_create(data->transaction, ref->name, ref->oid, NULL,
 					     REF_SKIP_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION,
 					     NULL, data->errbuf);
 		if (ret < 0)
@@ -3131,6 +3285,9 @@ int repo_migrate_ref_storage_format(struct repository *repo,
 				    struct strbuf *errbuf)
 {
 	struct ref_store *old_refs = NULL, *new_refs = NULL;
+	struct refs_for_each_ref_options for_each_ref_opts = {
+		.flags = REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,
+	};
 	struct ref_transaction *transaction = NULL;
 	struct strbuf new_gitdir = STRBUF_INIT;
 	struct migration_data data = {
@@ -3214,7 +3371,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
 	data.errbuf = errbuf;
 
 	/*
-	 * We need to use the internal `do_for_each_ref()` here so that we can
+	 * We need to use `refs_for_each_ref_ext()` here so that we can
 	 * also include broken refs and symrefs. These would otherwise be
 	 * skipped silently.
 	 *
@@ -3224,9 +3381,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
 	 * allow for a central lock due to its design. It's thus on the user to
 	 * ensure that there are no concurrent writes.
 	 */
-	ret = do_for_each_ref(old_refs, "", NULL, migrate_one_ref, 0,
-			      DO_FOR_EACH_INCLUDE_ROOT_REFS | DO_FOR_EACH_INCLUDE_BROKEN,
-			      &data);
+	ret = refs_for_each_ref_ext(old_refs, migrate_one_ref, &data, &for_each_ref_opts);
 	if (ret < 0)
 		goto done;
 
@@ -3347,3 +3502,40 @@ const char *ref_transaction_error_msg(enum ref_transaction_error err)
 		return "unknown failure";
 	}
 }
+
+void refs_compute_filesystem_location(const char *gitdir, const char *payload,
+				      bool *is_worktree, struct strbuf *refdir,
+				      struct strbuf *ref_common_dir)
+{
+	struct strbuf sb = STRBUF_INIT;
+
+	*is_worktree = get_common_dir_noenv(ref_common_dir, gitdir);
+
+	if (!payload) {
+		/*
+		 * We can use the 'gitdir' as the 'refdir' without appending the
+		 * worktree path, as the 'gitdir' here is already the worktree
+		 * path and is different from 'commondir' denoted by 'ref_common_dir'.
+		 */
+		strbuf_addstr(refdir, gitdir);
+		return;
+	}
+
+	if (!is_absolute_path(payload)) {
+		strbuf_addf(&sb, "%s/%s", ref_common_dir->buf, payload);
+		strbuf_realpath(ref_common_dir, sb.buf, 1);
+	} else {
+		strbuf_realpath(ref_common_dir, payload, 1);
+	}
+
+	strbuf_addbuf(refdir, ref_common_dir);
+
+	if (*is_worktree) {
+		const char *wt_id = strrchr(gitdir, '/');
+		if (!wt_id)
+			BUG("worktree path does not contain slash");
+		strbuf_addf(refdir, "/worktrees/%s", wt_id + 1);
+	}
+
+	strbuf_release(&sb);
+}
diff --git a/refs.h b/refs.h
index 4e6bd63..d98c1fc 100644
--- a/refs.h
+++ b/refs.h
@@ -170,7 +170,7 @@ int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err);
  *
  *   peel_object(r, oid, &peeled);
  *
- * with the "oid" value given to the each_ref_fn callback, except
+ * with the "oid" value given to the refs_for_each_cb callback, except
  * that some ref storage may be able to answer the query without
  * actually loading the object in memory.
  */
@@ -329,40 +329,115 @@ int check_tag_ref(struct strbuf *sb, const char *name);
 struct ref_transaction;
 
 /*
- * Bit values set in the flags argument passed to each_ref_fn() and
+ * Bit values set in the flags argument passed to refs_for_each_cb() and
  * stored in ref_iterator::flags. Other bits are for internal use
  * only:
  */
+enum reference_status {
+	/* Reference is a symbolic reference. */
+	REF_ISSYMREF = (1 << 0),
 
-/* Reference is a symbolic reference. */
-#define REF_ISSYMREF 0x01
+	/* Reference is a packed reference. */
+	REF_ISPACKED = (1 << 1),
 
-/* Reference is a packed reference. */
-#define REF_ISPACKED 0x02
+	/*
+	 * Reference cannot be resolved to an object name: dangling symbolic
+	 * reference (directly or indirectly), corrupt reference file,
+	 * reference exists but name is bad, or symbolic reference refers to
+	 * ill-formatted reference name.
+	 */
+	REF_ISBROKEN = (1 << 2),
+
+	/*
+	 * Reference name is not well formed.
+	 *
+	 * See git-check-ref-format(1) for the definition of well formed ref names.
+	 */
+	REF_BAD_NAME = (1 << 3),
+};
+
+/* A reference passed to `for_each_ref()`-style callbacks. */
+struct reference {
+	/* The fully-qualified name of the reference. */
+	const char *name;
+
+	/* The target of a symbolic ref. `NULL` for direct references. */
+	const char *target;
+
+	/*
+	 * The object ID of a reference. Either the direct object ID or the
+	 * resolved object ID in the case of a symbolic ref. May be the zero
+	 * object ID in case the symbolic ref cannot be resolved.
+	 */
+	const struct object_id *oid;
+
+	/*
+	 * An optional peeled object ID. This field _may_ be set for tags in
+	 * case the peeled value is present in the backend. Please refer to
+	 * `reference_get_peeled_oid()`.
+	 */
+	const struct object_id *peeled_oid;
+
+	/* A bitfield of `enum reference_status` flags. */
+	unsigned flags;
+};
 
 /*
- * Reference cannot be resolved to an object name: dangling symbolic
- * reference (directly or indirectly), corrupt reference file,
- * reference exists but name is bad, or symbolic reference refers to
- * ill-formatted reference name.
- */
-#define REF_ISBROKEN 0x04
-
-/*
- * Reference name is not well formed.
+ * Peel the tag to a non-tag commit. If present, this uses the peeled object ID
+ * exposed by the reference backend. Otherwise, the object is peeled via the
+ * object database, which is less efficient.
  *
- * See git-check-ref-format(1) for the definition of well formed ref names.
+ * Return `0` if the reference could be peeled, a negative error code
+ * otherwise.
  */
-#define REF_BAD_NAME 0x08
+int reference_get_peeled_oid(struct repository *repo,
+			     const struct reference *ref,
+			     struct object_id *peeled_oid);
 
 /*
  * The signature for the callback function for the for_each_*()
- * functions below.  The memory pointed to by the refname and oid
- * arguments is only guaranteed to be valid for the duration of a
+ * functions below.  The memory pointed to by the `struct reference`
+ * argument is only guaranteed to be valid for the duration of a
  * single callback invocation.
  */
-typedef int each_ref_fn(const char *refname, const char *referent,
-			const struct object_id *oid, int flags, void *cb_data);
+typedef int refs_for_each_cb(const struct reference *ref, void *cb_data);
+
+/*
+ * These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),
+ * which feeds it).
+ */
+enum refs_for_each_flag {
+	/*
+	 * Include broken references in a do_for_each_ref*() iteration, which
+	 * would normally be omitted. This includes both refs that point to
+	 * missing objects (a true repository corruption), ones with illegal
+	 * names (which we prefer not to expose to callers), as well as
+	 * dangling symbolic refs (i.e., those that point to a non-existent
+	 * ref; this is not a corruption, but as they have no valid oid, we
+	 * omit them from normal iteration results).
+	 */
+	REFS_FOR_EACH_INCLUDE_BROKEN = (1 << 0),
+
+	/*
+	 * Only include per-worktree refs in a do_for_each_ref*() iteration.
+	 * Normally this will be used with a files ref_store, since that's
+	 * where all reference backends will presumably store their
+	 * per-worktree refs.
+	 */
+	REFS_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),
+
+	/*
+	 * Omit dangling symrefs from output; this only has an effect with
+	 * INCLUDE_BROKEN, since they are otherwise not included at all.
+	 */
+	REFS_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),
+
+	/*
+	 * Include root refs i.e. HEAD and pseudorefs along with the regular
+	 * refs.
+	 */
+	REFS_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3),
+};
 
 /*
  * The following functions invoke the specified callback function for
@@ -374,70 +449,75 @@ typedef int each_ref_fn(const char *refname, const char *referent,
  * stop the iteration. Returned references are sorted.
  */
 int refs_head_ref(struct ref_store *refs,
-		  each_ref_fn fn, void *cb_data);
-int refs_for_each_ref(struct ref_store *refs,
-		      each_ref_fn fn, void *cb_data);
-int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
-			 each_ref_fn fn, void *cb_data);
-int refs_for_each_tag_ref(struct ref_store *refs,
-			  each_ref_fn fn, void *cb_data);
-int refs_for_each_branch_ref(struct ref_store *refs,
-			     each_ref_fn fn, void *cb_data);
-int refs_for_each_remote_ref(struct ref_store *refs,
-			     each_ref_fn fn, void *cb_data);
-int refs_for_each_replace_ref(struct ref_store *refs,
-			      each_ref_fn fn, void *cb_data);
+		  refs_for_each_cb fn, void *cb_data);
+int refs_head_ref_namespaced(struct ref_store *refs,
+			     refs_for_each_cb fn, void *cb_data);
 
-/*
- * references matching any pattern in "exclude_patterns" are omitted from the
- * result set on a best-effort basis.
- */
-int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
-			     const char **exclude_patterns,
-			     each_ref_fn fn, void *cb_data);
+
+struct refs_for_each_ref_options {
+	/* Only iterate over references that have this given prefix. */
+	const char *prefix;
+
+	/*
+	 * A globbing pattern that can be used to only yield refs that match.
+	 * If given, refs will be matched against the pattern with
+	 * `wildmatch()`.
+	 *
+	 * If the pattern doesn't contain any globbing characters then it is
+	 * treated as if it was ending with "/" and "*".
+	 */
+	const char *pattern;
+
+	/*
+	 * If set, only yield refs part of the configured namespace. Exclude
+	 * patterns will be rewritten to apply to the namespace, and the prefix
+	 * will be considered relative to the namespace.
+	 */
+	const char *namespace;
+
+	/*
+	 * Exclude any references that match any of these patterns on a
+	 * best-effort basis. The caller needs to be prepared for the exclude
+	 * patterns to be ignored.
+	 *
+	 * The array must be terminated with a NULL sentinel value.
+	 */
+	const char **exclude_patterns;
+
+	/*
+	 * The number of bytes to trim from the refname. Note that the trimmed
+	 * bytes must not cause the reference to become empty. As such, this
+	 * field should typically only be set when one uses a `prefix` ending
+	 * in a slash.
+	 */
+	size_t trim_prefix;
+
+	/* Flags that change which refs will be included. */
+	enum refs_for_each_flag flags;
+};
+
+int refs_for_each_ref(struct ref_store *refs,
+		      refs_for_each_cb fn, void *cb_data);
+int refs_for_each_ref_ext(struct ref_store *refs,
+			  refs_for_each_cb cb, void *cb_data,
+			  const struct refs_for_each_ref_options *opts);
+int refs_for_each_tag_ref(struct ref_store *refs,
+			  refs_for_each_cb fn, void *cb_data);
+int refs_for_each_branch_ref(struct ref_store *refs,
+			     refs_for_each_cb fn, void *cb_data);
+int refs_for_each_remote_ref(struct ref_store *refs,
+			     refs_for_each_cb fn, void *cb_data);
+int refs_for_each_replace_ref(struct ref_store *refs,
+			      refs_for_each_cb fn, void *cb_data);
 
 /**
- * iterate all refs in "patterns" by partitioning patterns into disjoint sets
+ * Iterate all refs in "prefixes" by partitioning prefixes into disjoint sets
  * and iterating the longest-common prefix of each set.
- *
- * references matching any pattern in "exclude_patterns" are omitted from the
- * result set on a best-effort basis.
- *
- * callers should be prepared to ignore references that they did not ask for.
  */
-int refs_for_each_fullref_in_prefixes(struct ref_store *refs,
-				      const char *namespace,
-				      const char **patterns,
-				      const char **exclude_patterns,
-				      each_ref_fn fn, void *cb_data);
-
-/* iterates all refs that match the specified glob pattern. */
-int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,
-			   const char *pattern, void *cb_data);
-
-int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
-			      const char *pattern, const char *prefix, void *cb_data);
-
-int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data);
-
-/*
- * references matching any pattern in "exclude_patterns" are omitted from the
- * result set on a best-effort basis.
- */
-int refs_for_each_namespaced_ref(struct ref_store *refs,
-				 const char **exclude_patterns,
-				 each_ref_fn fn, void *cb_data);
-
-/* can be used to learn about broken ref and symref */
-int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data);
-int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
-			    each_ref_fn fn, void *cb_data);
-
-/*
- * Iterates over all refs including root refs, i.e. pseudorefs and HEAD.
- */
-int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,
-				    void *cb_data);
+int refs_for_each_ref_in_prefixes(struct ref_store *refs,
+				  const char **prefixes,
+				  const struct refs_for_each_ref_options *opts,
+				  refs_for_each_cb cb, void *cb_data);
 
 /*
  * Normalizes partial refs to their fully qualified form.
@@ -461,32 +541,33 @@ void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,
 				const struct string_list *refnames);
 
 /*
- * Flags for controlling behaviour of pack_refs()
- * PACK_REFS_PRUNE: Prune loose refs after packing
- * PACK_REFS_AUTO: Pack refs on a best effort basis. The heuristics and end
- *                 result are decided by the ref backend. Backends may ignore
- *                 this flag and fall back to a normal repack.
+ * Flags for controlling behaviour of refs_optimize()
+ * REFS_OPTIMIZE_PRUNE: Prune loose refs after packing
+ * REFS_OPTIMIZE_AUTO: Pack refs on a best effort basis. The heuristics and end
+ *                     result are decided by the ref backend. Backends may ignore
+ *                     this flag and fall back to a normal repack.
  */
-#define PACK_REFS_PRUNE (1 << 0)
-#define PACK_REFS_AUTO  (1 << 1)
+#define REFS_OPTIMIZE_PRUNE (1 << 0)
+#define REFS_OPTIMIZE_AUTO  (1 << 1)
 
-struct pack_refs_opts {
+struct refs_optimize_opts {
 	unsigned int flags;
 	struct ref_exclusions *exclusions;
 	struct string_list *includes;
 };
 
 /*
- * Write a packed-refs file for the current repository.
- * flags: Combination of the above PACK_REFS_* flags.
- */
-int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts);
-
-/*
  * Optimize the ref store. The exact behavior is up to the backend.
  * For the files backend, this is equivalent to packing refs.
  */
-int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts);
+int refs_optimize(struct ref_store *refs, struct refs_optimize_opts *opts);
+
+/*
+ * Check if refs backend can be optimized by calling 'refs_optimize'.
+ */
+int refs_optimize_required(struct ref_store *ref_store,
+			   struct refs_optimize_opts *opts,
+			   bool *required);
 
 /*
  * Setup reflog before using. Fill in err and return -1 on failure.
@@ -614,6 +695,24 @@ int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_dat
  */
 int check_refname_format(const char *refname, int flags);
 
+struct fsck_ref_report;
+
+/*
+ * Perform generic checks for a specific direct ref. This function is
+ * expected to be called by the ref backends for every symbolic ref.
+ */
+int refs_fsck_ref(struct ref_store *refs, struct fsck_options *o,
+		  struct fsck_ref_report *report,
+		  const char *refname, const struct object_id *oid);
+
+/*
+ * Perform generic checks for a specific symref target. This function is
+ * expected to be called by the ref backends for every symbolic ref.
+ */
+int refs_fsck_symref(struct ref_store *refs, struct fsck_options *o,
+		     struct fsck_ref_report *report,
+		     const char *refname, const char *target);
+
 /*
  * Check the reference database for consistency. Return 0 if refs and
  * reflogs are consistent, and non-zero otherwise. The errors will be
@@ -936,6 +1035,7 @@ typedef void ref_transaction_for_each_rejected_update_fn(const char *refname,
 							 const char *old_target,
 							 const char *new_target,
 							 enum ref_transaction_error err,
+							 const char *details,
 							 void *cb_data);
 void ref_transaction_for_each_rejected_update(struct ref_transaction *transaction,
 					      ref_transaction_for_each_rejected_update_fn cb,
@@ -1251,10 +1351,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,
  * to the next entry, ref_iterator_advance() aborts the iteration,
  * frees the ref_iterator, and returns ITER_ERROR.
  *
- * The reference currently being looked at can be peeled by calling
- * ref_iterator_peel(). This function is often faster than peel_ref(),
- * so it should be preferred when iterating over references.
- *
  * Putting it all together, a typical iteration looks like this:
  *
  *     int ok;
@@ -1269,9 +1365,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,
  *             // Access information about the current reference:
  *             if (!(iter->flags & REF_ISSYMREF))
  *                     printf("%s is %s\n", iter->refname, oid_to_hex(iter->oid));
- *
- *             // If you need to peel the reference:
- *             ref_iterator_peel(iter, &oid);
  *     }
  *
  *     if (ok != ITER_DONE)
@@ -1281,43 +1374,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,
 struct ref_iterator;
 
 /*
- * These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),
- * which feeds it).
- */
-enum do_for_each_ref_flags {
-	/*
-	 * Include broken references in a do_for_each_ref*() iteration, which
-	 * would normally be omitted. This includes both refs that point to
-	 * missing objects (a true repository corruption), ones with illegal
-	 * names (which we prefer not to expose to callers), as well as
-	 * dangling symbolic refs (i.e., those that point to a non-existent
-	 * ref; this is not a corruption, but as they have no valid oid, we
-	 * omit them from normal iteration results).
-	 */
-	DO_FOR_EACH_INCLUDE_BROKEN = (1 << 0),
-
-	/*
-	 * Only include per-worktree refs in a do_for_each_ref*() iteration.
-	 * Normally this will be used with a files ref_store, since that's
-	 * where all reference backends will presumably store their
-	 * per-worktree refs.
-	 */
-	DO_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),
-
-	/*
-	 * Omit dangling symrefs from output; this only has an effect with
-	 * INCLUDE_BROKEN, since they are otherwise not included at all.
-	 */
-	DO_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),
-
-	/*
-	 * Include root refs i.e. HEAD and pseudorefs along with the regular
-	 * refs.
-	 */
-	DO_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3),
-};
-
-/*
  * Return an iterator that goes over each reference in `refs` for
  * which the refname begins with prefix. If trim is non-zero, then
  * trim that many characters off the beginning of each refname.
@@ -1326,7 +1382,7 @@ enum do_for_each_ref_flags {
 struct ref_iterator *refs_ref_iterator_begin(
 	struct ref_store *refs,
 	const char *prefix, const char **exclude_patterns,
-	int trim, enum do_for_each_ref_flags flags);
+	int trim, enum refs_for_each_flag flags);
 
 /*
  * Advance the iterator to the first or next item and return ITER_OK.
@@ -1362,13 +1418,6 @@ enum ref_iterator_seek_flag {
 int ref_iterator_seek(struct ref_iterator *ref_iterator, const char *refname,
 		      unsigned int flags);
 
-/*
- * If possible, peel the reference currently being viewed by the
- * iterator. Return 0 on success.
- */
-int ref_iterator_peel(struct ref_iterator *ref_iterator,
-		      struct object_id *peeled);
-
 /* Free the reference iterator and any associated resources. */
 void ref_iterator_free(struct ref_iterator *ref_iterator);
 
@@ -1382,6 +1431,19 @@ void ref_iterator_free(struct ref_iterator *ref_iterator);
  * iterator style.
  */
 int do_for_each_ref_iterator(struct ref_iterator *iter,
-			     each_ref_fn fn, void *cb_data);
+			     refs_for_each_cb fn, void *cb_data);
+
+/*
+ * Git only recognizes a directory as a repository if it contains:
+ * - HEAD file
+ * - refs/ folder
+ * While it is necessary within the files backend, newer backends may not
+ * follow the same structure. To go around this, we create stubs as necessary.
+ *
+ * If provided with a 'refs_heads_content', we create the 'refs/heads/head' file
+ * with the provided message.
+ */
+void refs_create_refdir_stubs(struct repository *repo, const char *refdir,
+			      const char *refs_heads_content);
 
 #endif /* REFS_H */
diff --git a/refs/debug.c b/refs/debug.c
index c59c172..639db0f 100644
--- a/refs/debug.c
+++ b/refs/debug.c
@@ -124,11 +124,22 @@ static int debug_transaction_abort(struct ref_store *refs,
 	return res;
 }
 
-static int debug_pack_refs(struct ref_store *ref_store, struct pack_refs_opts *opts)
+static int debug_optimize(struct ref_store *ref_store, struct refs_optimize_opts *opts)
 {
 	struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
-	int res = drefs->refs->be->pack_refs(drefs->refs, opts);
-	trace_printf_key(&trace_refs, "pack_refs: %d\n", res);
+	int res = drefs->refs->be->optimize(drefs->refs, opts);
+	trace_printf_key(&trace_refs, "optimize: %d\n", res);
+	return res;
+}
+
+static int debug_optimize_required(struct ref_store *ref_store,
+				   struct refs_optimize_opts *opts,
+				   bool *required)
+{
+	struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
+	int res = drefs->refs->be->optimize_required(drefs->refs, opts, required);
+	trace_printf_key(&trace_refs, "optimize_required: %s, res: %d\n",
+			 *required ? "yes" : "no", res);
 	return res;
 }
 
@@ -168,11 +179,9 @@ static int debug_ref_iterator_advance(struct ref_iterator *ref_iterator)
 		trace_printf_key(&trace_refs, "iterator_advance: (%d)\n", res);
 	else
 		trace_printf_key(&trace_refs, "iterator_advance: %s (0)\n",
-			diter->iter->refname);
+			diter->iter->ref.name);
 
-	diter->base.refname = diter->iter->refname;
-	diter->base.oid = diter->iter->oid;
-	diter->base.flags = diter->iter->flags;
+	diter->base.ref = diter->iter->ref;
 	return res;
 }
 
@@ -187,16 +196,6 @@ static int debug_ref_iterator_seek(struct ref_iterator *ref_iterator,
 	return res;
 }
 
-static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator,
-				   struct object_id *peeled)
-{
-	struct debug_ref_iterator *diter =
-		(struct debug_ref_iterator *)ref_iterator;
-	int res = diter->iter->vtable->peel(diter->iter, peeled);
-	trace_printf_key(&trace_refs, "iterator_peel: %s: %d\n", diter->iter->refname, res);
-	return res;
-}
-
 static void debug_ref_iterator_release(struct ref_iterator *ref_iterator)
 {
 	struct debug_ref_iterator *diter =
@@ -208,7 +207,6 @@ static void debug_ref_iterator_release(struct ref_iterator *ref_iterator)
 static struct ref_iterator_vtable debug_ref_iterator_vtable = {
 	.advance = debug_ref_iterator_advance,
 	.seek = debug_ref_iterator_seek,
-	.peel = debug_ref_iterator_peel,
 	.release = debug_ref_iterator_release,
 };
 
@@ -452,7 +450,9 @@ struct ref_storage_be refs_be_debug = {
 	.transaction_finish = debug_transaction_finish,
 	.transaction_abort = debug_transaction_abort,
 
-	.pack_refs = debug_pack_refs,
+	.optimize = debug_optimize,
+	.optimize_required = debug_optimize_required,
+
 	.rename_ref = debug_rename_ref,
 	.copy_ref = debug_copy_ref,
 
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 1adc4b5..7ce0d57 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -106,19 +106,24 @@ static void clear_loose_ref_cache(struct files_ref_store *refs)
  * set of caches.
  */
 static struct ref_store *files_ref_store_init(struct repository *repo,
+					      const char *payload,
 					      const char *gitdir,
 					      unsigned int flags)
 {
 	struct files_ref_store *refs = xcalloc(1, sizeof(*refs));
 	struct ref_store *ref_store = (struct ref_store *)refs;
-	struct strbuf sb = STRBUF_INIT;
+	struct strbuf ref_common_dir = STRBUF_INIT;
+	struct strbuf refdir = STRBUF_INIT;
+	bool is_worktree;
 
-	base_ref_store_init(ref_store, repo, gitdir, &refs_be_files);
+	refs_compute_filesystem_location(gitdir, payload, &is_worktree, &refdir,
+					 &ref_common_dir);
+
+	base_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files);
 	refs->store_flags = flags;
-	get_common_dir_noenv(&sb, gitdir);
-	refs->gitcommondir = strbuf_detach(&sb, NULL);
+	refs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);
 	refs->packed_ref_store =
-		packed_ref_store_init(repo, refs->gitcommondir, flags);
+		packed_ref_store_init(repo, NULL, refs->gitcommondir, flags);
 	refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
 	repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs);
 
@@ -126,6 +131,8 @@ static struct ref_store *files_ref_store_init(struct repository *repo,
 	chdir_notify_reparent("files-backend $GIT_COMMONDIR",
 			      &refs->gitcommondir);
 
+	strbuf_release(&refdir);
+
 	return ref_store;
 }
 
@@ -354,13 +361,11 @@ static int for_each_root_ref(struct files_ref_store *refs,
 			     void *cb_data)
 {
 	struct strbuf path = STRBUF_INIT, refname = STRBUF_INIT;
-	const char *dirname = refs->loose->root->name;
 	struct dirent *de;
-	size_t dirnamelen;
 	int ret;
 	DIR *d;
 
-	files_ref_path(refs, &path, dirname);
+	files_ref_path(refs, &path, "");
 
 	d = opendir(path.buf);
 	if (!d) {
@@ -368,9 +373,6 @@ static int for_each_root_ref(struct files_ref_store *refs,
 		return -1;
 	}
 
-	strbuf_addstr(&refname, dirname);
-	dirnamelen = refname.len;
-
 	while ((de = readdir(d)) != NULL) {
 		unsigned char dtype;
 
@@ -378,6 +380,8 @@ static int for_each_root_ref(struct files_ref_store *refs,
 			continue;
 		if (ends_with(de->d_name, ".lock"))
 			continue;
+
+		strbuf_reset(&refname);
 		strbuf_addstr(&refname, de->d_name);
 
 		dtype = get_dtype(de, &path, 1);
@@ -386,8 +390,6 @@ static int for_each_root_ref(struct files_ref_store *refs,
 			if (ret)
 				goto done;
 		}
-
-		strbuf_setlen(&refname, dirnamelen);
 	}
 
 	ret = 0;
@@ -444,7 +446,7 @@ static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs,
 
 		dir = get_ref_dir(refs->loose->root);
 
-		if (flags & DO_FOR_EACH_INCLUDE_ROOT_REFS)
+		if (flags & REFS_FOR_EACH_INCLUDE_ROOT_REFS)
 			add_root_refs(refs, dir);
 
 		/*
@@ -960,27 +962,24 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator)
 	int ok;
 
 	while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {
-		if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
-		    parse_worktree_ref(iter->iter0->refname, NULL, NULL,
+		if (iter->flags & REFS_FOR_EACH_PER_WORKTREE_ONLY &&
+		    parse_worktree_ref(iter->iter0->ref.name, NULL, NULL,
 				       NULL) != REF_WORKTREE_CURRENT)
 			continue;
 
-		if ((iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS) &&
-		    (iter->iter0->flags & REF_ISSYMREF) &&
-		    (iter->iter0->flags & REF_ISBROKEN))
+		if ((iter->flags & REFS_FOR_EACH_OMIT_DANGLING_SYMREFS) &&
+		    (iter->iter0->ref.flags & REF_ISSYMREF) &&
+		    (iter->iter0->ref.flags & REF_ISBROKEN))
 			continue;
 
-		if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
-		    !ref_resolves_to_object(iter->iter0->refname,
+		if (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) &&
+		    !ref_resolves_to_object(iter->iter0->ref.name,
 					    iter->repo,
-					    iter->iter0->oid,
-					    iter->iter0->flags))
+					    iter->iter0->ref.oid,
+					    iter->iter0->ref.flags))
 			continue;
 
-		iter->base.refname = iter->iter0->refname;
-		iter->base.oid = iter->iter0->oid;
-		iter->base.flags = iter->iter0->flags;
-		iter->base.referent = iter->iter0->referent;
+		iter->base.ref = iter->iter0->ref;
 
 		return ITER_OK;
 	}
@@ -996,15 +995,6 @@ static int files_ref_iterator_seek(struct ref_iterator *ref_iterator,
 	return ref_iterator_seek(iter->iter0, refname, flags);
 }
 
-static int files_ref_iterator_peel(struct ref_iterator *ref_iterator,
-				   struct object_id *peeled)
-{
-	struct files_ref_iterator *iter =
-		(struct files_ref_iterator *)ref_iterator;
-
-	return ref_iterator_peel(iter->iter0, peeled);
-}
-
 static void files_ref_iterator_release(struct ref_iterator *ref_iterator)
 {
 	struct files_ref_iterator *iter =
@@ -1015,7 +1005,6 @@ static void files_ref_iterator_release(struct ref_iterator *ref_iterator)
 static struct ref_iterator_vtable files_ref_iterator_vtable = {
 	.advance = files_ref_iterator_advance,
 	.seek = files_ref_iterator_seek,
-	.peel = files_ref_iterator_peel,
 	.release = files_ref_iterator_release,
 };
 
@@ -1030,7 +1019,7 @@ static struct ref_iterator *files_ref_iterator_begin(
 	struct ref_iterator *ref_iterator;
 	unsigned int required_flags = REF_STORE_READ;
 
-	if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN))
+	if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN))
 		required_flags |= REF_STORE_ODB;
 
 	refs = files_downcast(ref_store, required_flags, "ref_iterator_begin");
@@ -1068,7 +1057,7 @@ static struct ref_iterator *files_ref_iterator_begin(
 	 */
 	packed_iter = refs_ref_iterator_begin(
 			refs->packed_ref_store, prefix, exclude_patterns, 0,
-			DO_FOR_EACH_INCLUDE_BROKEN);
+			REFS_FOR_EACH_INCLUDE_BROKEN);
 
 	overlay_iter = overlay_ref_iterator_begin(loose_iter, packed_iter);
 
@@ -1367,37 +1356,36 @@ static void prune_refs(struct files_ref_store *refs, struct ref_to_prune **refs_
  * Return true if the specified reference should be packed.
  */
 static int should_pack_ref(struct files_ref_store *refs,
-			   const char *refname,
-			   const struct object_id *oid, unsigned int ref_flags,
-			   struct pack_refs_opts *opts)
+			   const struct reference *ref,
+			   struct refs_optimize_opts *opts)
 {
 	struct string_list_item *item;
 
 	/* Do not pack per-worktree refs: */
-	if (parse_worktree_ref(refname, NULL, NULL, NULL) !=
+	if (parse_worktree_ref(ref->name, NULL, NULL, NULL) !=
 	    REF_WORKTREE_SHARED)
 		return 0;
 
 	/* Do not pack symbolic refs: */
-	if (ref_flags & REF_ISSYMREF)
+	if (ref->flags & REF_ISSYMREF)
 		return 0;
 
 	/* Do not pack broken refs: */
-	if (!ref_resolves_to_object(refname, refs->base.repo, oid, ref_flags))
+	if (!ref_resolves_to_object(ref->name, refs->base.repo, ref->oid, ref->flags))
 		return 0;
 
-	if (ref_excluded(opts->exclusions, refname))
+	if (ref_excluded(opts->exclusions, ref->name))
 		return 0;
 
 	for_each_string_list_item(item, opts->includes)
-		if (!wildmatch(item->string, refname, 0))
+		if (!wildmatch(item->string, ref->name, 0))
 			return 1;
 
 	return 0;
 }
 
 static int should_pack_refs(struct files_ref_store *refs,
-			    struct pack_refs_opts *opts)
+			    struct refs_optimize_opts *opts)
 {
 	struct ref_iterator *iter;
 	size_t packed_size;
@@ -1405,7 +1393,7 @@ static int should_pack_refs(struct files_ref_store *refs,
 	size_t limit;
 	int ret;
 
-	if (!(opts->flags & PACK_REFS_AUTO))
+	if (!(opts->flags & REFS_OPTIMIZE_AUTO))
 		return 1;
 
 	ret = packed_refs_size(refs->packed_ref_store, &packed_size);
@@ -1443,8 +1431,7 @@ static int should_pack_refs(struct files_ref_store *refs,
 	iter = cache_ref_iterator_begin(get_loose_ref_cache(refs, 0), NULL,
 					refs->base.repo, 0);
 	while ((ret = ref_iterator_advance(iter)) == ITER_OK) {
-		if (should_pack_ref(refs, iter->refname, iter->oid,
-				    iter->flags, opts))
+		if (should_pack_ref(refs, &iter->ref, opts))
 			refcount++;
 		if (refcount >= limit) {
 			ref_iterator_free(iter);
@@ -1459,8 +1446,8 @@ static int should_pack_refs(struct files_ref_store *refs,
 	return 0;
 }
 
-static int files_pack_refs(struct ref_store *ref_store,
-			   struct pack_refs_opts *opts)
+static int files_optimize(struct ref_store *ref_store,
+			  struct refs_optimize_opts *opts)
 {
 	struct files_ref_store *refs =
 		files_downcast(ref_store, REF_STORE_WRITE | REF_STORE_ODB,
@@ -1489,24 +1476,24 @@ static int files_pack_refs(struct ref_store *ref_store,
 		 * in the packed ref cache. If the reference should be
 		 * pruned, also add it to refs_to_prune.
 		 */
-		if (!should_pack_ref(refs, iter->refname, iter->oid, iter->flags, opts))
+		if (!should_pack_ref(refs, &iter->ref, opts))
 			continue;
 
 		/*
 		 * Add a reference creation for this reference to the
 		 * packed-refs transaction:
 		 */
-		if (ref_transaction_update(transaction, iter->refname,
-					   iter->oid, NULL, NULL, NULL,
+		if (ref_transaction_update(transaction, iter->ref.name,
+					   iter->ref.oid, NULL, NULL, NULL,
 					   REF_NO_DEREF, NULL, &err))
 			die("failure preparing to create packed reference %s: %s",
-			    iter->refname, err.buf);
+			    iter->ref.name, err.buf);
 
 		/* Schedule the loose reference for pruning if requested. */
-		if ((opts->flags & PACK_REFS_PRUNE)) {
+		if ((opts->flags & REFS_OPTIMIZE_PRUNE)) {
 			struct ref_to_prune *n;
-			FLEX_ALLOC_STR(n, name, iter->refname);
-			oidcpy(&n->oid, iter->oid);
+			FLEX_ALLOC_STR(n, name, iter->ref.name);
+			oidcpy(&n->oid, iter->ref.oid);
 			n->next = refs_to_prune;
 			refs_to_prune = n;
 		}
@@ -1527,13 +1514,14 @@ static int files_pack_refs(struct ref_store *ref_store,
 	return 0;
 }
 
-static int files_optimize(struct ref_store *ref_store, struct pack_refs_opts *opts)
+static int files_optimize_required(struct ref_store *ref_store,
+				   struct refs_optimize_opts *opts,
+				   bool *required)
 {
-	/*
-	 * For the "files" backend, "optimizing" is the same as "packing".
-	 * So, we just call the existing worker function for packing.
-	 */
-	return files_pack_refs(ref_store, opts);
+	struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_READ,
+						      "optimize_required");
+	*required = should_pack_refs(refs, opts);
+	return 0;
 }
 
 /*
@@ -2394,7 +2382,7 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator)
 					 REFNAME_ALLOW_ONELEVEL))
 			continue;
 
-		iter->base.refname = diter->relative_path;
+		iter->base.ref.name = diter->relative_path;
 		return ITER_OK;
 	}
 
@@ -2408,12 +2396,6 @@ static int files_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSED,
 	BUG("ref_iterator_seek() called for reflog_iterator");
 }
 
-static int files_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED,
-				      struct object_id *peeled UNUSED)
-{
-	BUG("ref_iterator_peel() called for reflog_iterator");
-}
-
 static void files_reflog_iterator_release(struct ref_iterator *ref_iterator)
 {
 	struct files_reflog_iterator *iter =
@@ -2424,7 +2406,6 @@ static void files_reflog_iterator_release(struct ref_iterator *ref_iterator)
 static struct ref_iterator_vtable files_reflog_iterator_vtable = {
 	.advance = files_reflog_iterator_advance,
 	.seek = files_reflog_iterator_seek,
-	.peel = files_reflog_iterator_peel,
 	.release = files_reflog_iterator_release,
 };
 
@@ -3004,10 +2985,9 @@ static int files_transaction_prepare(struct ref_store *ref_store,
 					  head_ref, &refnames_to_check,
 					  err);
 		if (ret) {
-			if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
-				strbuf_reset(err);
+			if (ref_transaction_maybe_set_rejected(transaction, i,
+							       ret, err)) {
 				ret = 0;
-
 				continue;
 			}
 			goto cleanup;
@@ -3165,20 +3145,20 @@ static int parse_and_write_reflog(struct files_ref_store *refs,
 	return 0;
 }
 
-static int ref_present(const char *refname, const char *referent UNUSED,
-		       const struct object_id *oid UNUSED,
-		       int flags UNUSED,
-		       void *cb_data)
+static int ref_present(const struct reference *ref, void *cb_data)
 {
 	struct string_list *affected_refnames = cb_data;
 
-	return string_list_has_string(affected_refnames, refname);
+	return string_list_has_string(affected_refnames, ref->name);
 }
 
 static int files_transaction_finish_initial(struct files_ref_store *refs,
 					    struct ref_transaction *transaction,
 					    struct strbuf *err)
 {
+	struct refs_for_each_ref_options opts = {
+		.flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+	};
 	size_t i;
 	int ret = 0;
 	struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
@@ -3203,8 +3183,8 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
 	 * so here we really only check that none of the references
 	 * that we are creating already exists.
 	 */
-	if (refs_for_each_rawref(&refs->base, ref_present,
-				 &transaction->refnames))
+	if (refs_for_each_ref_ext(&refs->base, ref_present,
+				  &transaction->refnames, &opts))
 		BUG("initial ref transaction called with existing refs");
 
 	packed_transaction = ref_store_transaction_begin(refs->packed_ref_store,
@@ -3729,7 +3709,11 @@ static int files_ref_store_remove_on_disk(struct ref_store *ref_store,
 	if (for_each_root_ref(refs, remove_one_root_ref, &data) < 0)
 		ret = -1;
 
-	if (ref_store_remove_on_disk(refs->packed_ref_store, err) < 0)
+	/*
+	 * Directly access the cleanup functions for packed-refs as the generic function
+	 * would try to clear stubs which isn't required for the files backend.
+	 */
+	if (refs->packed_ref_store->be->remove_on_disk(refs->packed_ref_store, err) < 0)
 		ret = -1;
 
 	strbuf_release(&sb);
@@ -3744,64 +3728,50 @@ static int files_ref_store_remove_on_disk(struct ref_store *ref_store,
 typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store,
 				  struct fsck_options *o,
 				  const char *refname,
-				  struct dir_iterator *iter);
+				  const char *path,
+				  int mode);
 
-static int files_fsck_symref_target(struct fsck_options *o,
+static int files_fsck_symref_target(struct ref_store *ref_store,
+				    struct fsck_options *o,
 				    struct fsck_ref_report *report,
+				    const char *refname,
 				    struct strbuf *referent,
 				    unsigned int symbolic_link)
 {
-	int is_referent_root;
 	char orig_last_byte;
 	size_t orig_len;
 	int ret = 0;
 
 	orig_len = referent->len;
 	orig_last_byte = referent->buf[orig_len - 1];
-	if (!symbolic_link)
+
+	if (!symbolic_link) {
 		strbuf_rtrim(referent);
 
-	is_referent_root = is_root_ref(referent->buf);
-	if (!is_referent_root &&
-	    !starts_with(referent->buf, "refs/") &&
-	    !starts_with(referent->buf, "worktrees/")) {
-		ret = fsck_report_ref(o, report,
-				      FSCK_MSG_SYMREF_TARGET_IS_NOT_A_REF,
-				      "points to non-ref target '%s'", referent->buf);
+		if (referent->len == orig_len ||
+		    (referent->len < orig_len && orig_last_byte != '\n')) {
+			ret |= fsck_report_ref(o, report,
+					       FSCK_MSG_REF_MISSING_NEWLINE,
+					       "misses LF at the end");
+		}
 
+		if (referent->len != orig_len && referent->len != orig_len - 1) {
+			ret |= fsck_report_ref(o, report,
+					       FSCK_MSG_TRAILING_REF_CONTENT,
+					       "has trailing whitespaces or newlines");
+		}
 	}
 
-	if (!is_referent_root && check_refname_format(referent->buf, 0)) {
-		ret = fsck_report_ref(o, report,
-				      FSCK_MSG_BAD_REFERENT_NAME,
-				      "points to invalid refname '%s'", referent->buf);
-		goto out;
-	}
+	ret |= refs_fsck_symref(ref_store, o, report, refname, referent->buf);
 
-	if (symbolic_link)
-		goto out;
-
-	if (referent->len == orig_len ||
-	    (referent->len < orig_len && orig_last_byte != '\n')) {
-		ret = fsck_report_ref(o, report,
-				      FSCK_MSG_REF_MISSING_NEWLINE,
-				      "misses LF at the end");
-	}
-
-	if (referent->len != orig_len && referent->len != orig_len - 1) {
-		ret = fsck_report_ref(o, report,
-				      FSCK_MSG_TRAILING_REF_CONTENT,
-				      "has trailing whitespaces or newlines");
-	}
-
-out:
-	return ret;
+	return ret ? -1 : 0;
 }
 
 static int files_fsck_refs_content(struct ref_store *ref_store,
 				   struct fsck_options *o,
 				   const char *target_name,
-				   struct dir_iterator *iter)
+				   const char *path,
+				   int mode)
 {
 	struct strbuf ref_content = STRBUF_INIT;
 	struct strbuf abs_gitdir = STRBUF_INIT;
@@ -3815,7 +3785,7 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 
 	report.path = target_name;
 
-	if (S_ISLNK(iter->st.st_mode)) {
+	if (S_ISLNK(mode)) {
 		const char *relative_referent_path = NULL;
 
 		ret = fsck_report_ref(o, &report,
@@ -3827,7 +3797,7 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 		if (!is_dir_sep(abs_gitdir.buf[abs_gitdir.len - 1]))
 			strbuf_addch(&abs_gitdir, '/');
 
-		strbuf_add_real_path(&ref_content, iter->path.buf);
+		strbuf_add_real_path(&ref_content, path);
 		skip_prefix(ref_content.buf, abs_gitdir.buf,
 			    &relative_referent_path);
 
@@ -3836,11 +3806,12 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 		else
 			strbuf_addbuf(&referent, &ref_content);
 
-		ret |= files_fsck_symref_target(o, &report, &referent, 1);
+		ret |= files_fsck_symref_target(ref_store, o, &report,
+						target_name, &referent, 1);
 		goto cleanup;
 	}
 
-	if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) {
+	if (strbuf_read_file(&ref_content, path, 0) < 0) {
 		/*
 		 * Ref file could be removed by another concurrent process. We should
 		 * ignore this error and continue to the next ref.
@@ -3848,7 +3819,7 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 		if (errno == ENOENT)
 			goto cleanup;
 
-		ret = error_errno(_("cannot read ref file '%s'"), iter->path.buf);
+		ret = error_errno(_("cannot read ref file '%s'"), path);
 		goto cleanup;
 	}
 
@@ -3875,8 +3846,11 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 					      "has trailing garbage: '%s'", trailing);
 			goto cleanup;
 		}
+
+		ret = refs_fsck_ref(ref_store, o, &report, target_name, &oid);
 	} else {
-		ret = files_fsck_symref_target(o, &report, &referent, 0);
+		ret = files_fsck_symref_target(ref_store, o, &report,
+					       target_name, &referent, 0);
 		goto cleanup;
 	}
 
@@ -3890,21 +3864,25 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 				struct fsck_options *o,
 				const char *refname,
-				struct dir_iterator *iter)
+				const char *path,
+				int mode UNUSED)
 {
 	struct strbuf sb = STRBUF_INIT;
+	const char *filename;
 	int ret = 0;
 
+	filename = basename((char *) path);
+
 	/*
 	 * Ignore the files ending with ".lock" as they may be lock files
 	 * However, do not allow bare ".lock" files.
 	 */
-	if (iter->basename[0] != '.' && ends_with(iter->basename, ".lock"))
+	if (filename[0] != '.' && ends_with(filename, ".lock"))
 		goto cleanup;
 
-	/*
-	 * This works right now because we never check the root refs.
-	 */
+	if (is_root_ref(refname))
+		goto cleanup;
+
 	if (check_refname_format(refname, 0)) {
 		struct fsck_ref_report report = { 0 };
 
@@ -3919,11 +3897,44 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 	return ret;
 }
 
+static const files_fsck_refs_fn fsck_refs_fn[]= {
+	files_fsck_refs_name,
+	files_fsck_refs_content,
+	NULL,
+};
+
+static int files_fsck_ref(struct ref_store *ref_store,
+			  struct fsck_options *o,
+			  const char *refname,
+			  const char *path,
+			  int mode)
+{
+	int ret = 0;
+
+	if (o->verbose)
+		fprintf_ln(stderr, "Checking %s", refname);
+
+	if (!S_ISREG(mode) && !S_ISLNK(mode)) {
+		struct fsck_ref_report report = { .path = refname };
+
+		if (fsck_report_ref(o, &report,
+				    FSCK_MSG_BAD_REF_FILETYPE,
+				    "unexpected file type"))
+			ret = -1;
+		goto out;
+	}
+
+	for (size_t i = 0; fsck_refs_fn[i]; i++)
+		if (fsck_refs_fn[i](ref_store, o, refname, path, mode))
+			ret = -1;
+
+out:
+	return ret;
+}
+
 static int files_fsck_refs_dir(struct ref_store *ref_store,
 			       struct fsck_options *o,
-			       const char *refs_check_dir,
-			       struct worktree *wt,
-			       files_fsck_refs_fn *fsck_refs_fn)
+			       struct worktree *wt)
 {
 	struct strbuf refname = STRBUF_INIT;
 	struct strbuf sb = STRBUF_INIT;
@@ -3931,7 +3942,7 @@ static int files_fsck_refs_dir(struct ref_store *ref_store,
 	int iter_status;
 	int ret = 0;
 
-	strbuf_addf(&sb, "%s/%s", ref_store->gitdir, refs_check_dir);
+	strbuf_addf(&sb, "%s/refs", ref_store->gitdir);
 
 	iter = dir_iterator_begin(sb.buf, 0);
 	if (!iter) {
@@ -3943,31 +3954,17 @@ static int files_fsck_refs_dir(struct ref_store *ref_store,
 	}
 
 	while ((iter_status = dir_iterator_advance(iter)) == ITER_OK) {
-		if (S_ISDIR(iter->st.st_mode)) {
+		if (S_ISDIR(iter->st.st_mode))
 			continue;
-		} else if (S_ISREG(iter->st.st_mode) ||
-			   S_ISLNK(iter->st.st_mode)) {
-			strbuf_reset(&refname);
 
-			if (!is_main_worktree(wt))
-				strbuf_addf(&refname, "worktrees/%s/", wt->id);
-			strbuf_addf(&refname, "%s/%s", refs_check_dir,
-				    iter->relative_path);
+		strbuf_reset(&refname);
+		if (!is_main_worktree(wt))
+			strbuf_addf(&refname, "worktrees/%s/", wt->id);
+		strbuf_addf(&refname, "refs/%s", iter->relative_path);
 
-			if (o->verbose)
-				fprintf_ln(stderr, "Checking %s", refname.buf);
-
-			for (size_t i = 0; fsck_refs_fn[i]; i++) {
-				if (fsck_refs_fn[i](ref_store, o, refname.buf, iter))
-					ret = -1;
-			}
-		} else {
-			struct fsck_ref_report report = { .path = iter->basename };
-			if (fsck_report_ref(o, &report,
-					    FSCK_MSG_BAD_REF_FILETYPE,
-					    "unexpected file type"))
-				ret = -1;
-		}
+		if (files_fsck_ref(ref_store, o, refname.buf,
+				   iter->path.buf, iter->st.st_mode) < 0)
+			ret = -1;
 	}
 
 	if (iter_status != ITER_DONE)
@@ -3980,17 +3977,35 @@ static int files_fsck_refs_dir(struct ref_store *ref_store,
 	return ret;
 }
 
-static int files_fsck_refs(struct ref_store *ref_store,
-			   struct fsck_options *o,
-			   struct worktree *wt)
-{
-	files_fsck_refs_fn fsck_refs_fn[]= {
-		files_fsck_refs_name,
-		files_fsck_refs_content,
-		NULL,
-	};
+struct files_fsck_root_ref_data {
+	struct files_ref_store *refs;
+	struct fsck_options *o;
+	struct worktree *wt;
+	struct strbuf refname;
+	struct strbuf path;
+};
 
-	return files_fsck_refs_dir(ref_store, o, "refs", wt, fsck_refs_fn);
+static int files_fsck_root_ref(const char *refname, void *cb_data)
+{
+	struct files_fsck_root_ref_data *data = cb_data;
+	struct stat st;
+
+	strbuf_reset(&data->refname);
+	if (!is_main_worktree(data->wt))
+		strbuf_addf(&data->refname, "worktrees/%s/", data->wt->id);
+	strbuf_addstr(&data->refname, refname);
+
+	strbuf_reset(&data->path);
+	strbuf_addf(&data->path, "%s/%s", data->refs->gitcommondir, data->refname.buf);
+
+	if (stat(data->path.buf, &st)) {
+		if (errno == ENOENT)
+			return 0;
+		return error_errno("failed to read ref: '%s'", data->path.buf);
+	}
+
+	return files_fsck_ref(&data->refs->base, data->o, data->refname.buf,
+			      data->path.buf, st.st_mode);
 }
 
 static int files_fsck(struct ref_store *ref_store,
@@ -3999,9 +4014,27 @@ static int files_fsck(struct ref_store *ref_store,
 {
 	struct files_ref_store *refs =
 		files_downcast(ref_store, REF_STORE_READ, "fsck");
+	struct files_fsck_root_ref_data data = {
+		.refs = refs,
+		.o = o,
+		.wt = wt,
+		.refname = STRBUF_INIT,
+		.path = STRBUF_INIT,
+	};
+	int ret = 0;
 
-	return files_fsck_refs(ref_store, o, wt) |
-	       refs->packed_ref_store->be->fsck(refs->packed_ref_store, o, wt);
+	if (files_fsck_refs_dir(ref_store, o, wt) < 0)
+		ret = -1;
+
+	if (for_each_root_ref(refs, files_fsck_root_ref, &data) < 0)
+		ret = -1;
+
+	if (refs->packed_ref_store->be->fsck(refs->packed_ref_store, o, wt) < 0)
+		ret = -1;
+
+	strbuf_release(&data.refname);
+	strbuf_release(&data.path);
+	return ret;
 }
 
 struct ref_storage_be refs_be_files = {
@@ -4015,8 +4048,8 @@ struct ref_storage_be refs_be_files = {
 	.transaction_finish = files_transaction_finish,
 	.transaction_abort = files_transaction_abort,
 
-	.pack_refs = files_pack_refs,
 	.optimize = files_optimize,
+	.optimize_required = files_optimize_required,
 	.rename_ref = files_rename_ref,
 	.copy_ref = files_copy_ref,
 
diff --git a/refs/iterator.c b/refs/iterator.c
index 17ef841..d5cacde 100644
--- a/refs/iterator.c
+++ b/refs/iterator.c
@@ -21,12 +21,6 @@ int ref_iterator_seek(struct ref_iterator *ref_iterator, const char *refname,
 	return ref_iterator->vtable->seek(ref_iterator, refname, flags);
 }
 
-int ref_iterator_peel(struct ref_iterator *ref_iterator,
-		      struct object_id *peeled)
-{
-	return ref_iterator->vtable->peel(ref_iterator, peeled);
-}
-
 void ref_iterator_free(struct ref_iterator *ref_iterator)
 {
 	if (ref_iterator) {
@@ -41,10 +35,7 @@ void base_ref_iterator_init(struct ref_iterator *iter,
 			    struct ref_iterator_vtable *vtable)
 {
 	iter->vtable = vtable;
-	iter->refname = NULL;
-	iter->referent = NULL;
-	iter->oid = NULL;
-	iter->flags = 0;
+	memset(&iter->ref, 0, sizeof(iter->ref));
 }
 
 struct empty_ref_iterator {
@@ -63,12 +54,6 @@ static int empty_ref_iterator_seek(struct ref_iterator *ref_iterator UNUSED,
 	return 0;
 }
 
-static int empty_ref_iterator_peel(struct ref_iterator *ref_iterator UNUSED,
-				   struct object_id *peeled UNUSED)
-{
-	BUG("peel called for empty iterator");
-}
-
 static void empty_ref_iterator_release(struct ref_iterator *ref_iterator UNUSED)
 {
 }
@@ -76,7 +61,6 @@ static void empty_ref_iterator_release(struct ref_iterator *ref_iterator UNUSED)
 static struct ref_iterator_vtable empty_ref_iterator_vtable = {
 	.advance = empty_ref_iterator_advance,
 	.seek = empty_ref_iterator_seek,
-	.peel = empty_ref_iterator_peel,
 	.release = empty_ref_iterator_release,
 };
 
@@ -127,8 +111,8 @@ enum iterator_selection ref_iterator_select(struct ref_iterator *iter_worktree,
 		 * latter.
 		 */
 		if (iter_worktree) {
-			int cmp = strcmp(iter_worktree->refname,
-					 iter_common->refname);
+			int cmp = strcmp(iter_worktree->ref.name,
+					 iter_common->ref.name);
 			if (cmp < 0)
 				return ITER_SELECT_0;
 			else if (!cmp)
@@ -139,7 +123,7 @@ enum iterator_selection ref_iterator_select(struct ref_iterator *iter_worktree,
 		  * We now know that the lexicographically-next ref is a common
 		  * ref. When the common ref is a shared one we return it.
 		  */
-		if (parse_worktree_ref(iter_common->refname, NULL, NULL,
+		if (parse_worktree_ref(iter_common->ref.name, NULL, NULL,
 				       NULL) == REF_WORKTREE_SHARED)
 			return ITER_SELECT_1;
 
@@ -212,10 +196,7 @@ static int merge_ref_iterator_advance(struct ref_iterator *ref_iterator)
 		}
 
 		if (selection & ITER_YIELD_CURRENT) {
-			iter->base.referent = (*iter->current)->referent;
-			iter->base.refname = (*iter->current)->refname;
-			iter->base.oid = (*iter->current)->oid;
-			iter->base.flags = (*iter->current)->flags;
+			iter->base.ref = (*iter->current)->ref;
 			return ITER_OK;
 		}
 	}
@@ -246,18 +227,6 @@ static int merge_ref_iterator_seek(struct ref_iterator *ref_iterator,
 	return 0;
 }
 
-static int merge_ref_iterator_peel(struct ref_iterator *ref_iterator,
-				   struct object_id *peeled)
-{
-	struct merge_ref_iterator *iter =
-		(struct merge_ref_iterator *)ref_iterator;
-
-	if (!iter->current) {
-		BUG("peel called before advance for merge iterator");
-	}
-	return ref_iterator_peel(*iter->current, peeled);
-}
-
 static void merge_ref_iterator_release(struct ref_iterator *ref_iterator)
 {
 	struct merge_ref_iterator *iter =
@@ -269,7 +238,6 @@ static void merge_ref_iterator_release(struct ref_iterator *ref_iterator)
 static struct ref_iterator_vtable merge_ref_iterator_vtable = {
 	.advance = merge_ref_iterator_advance,
 	.seek = merge_ref_iterator_seek,
-	.peel = merge_ref_iterator_peel,
 	.release = merge_ref_iterator_release,
 };
 
@@ -313,7 +281,7 @@ static enum iterator_selection overlay_iterator_select(
 	else if (!front)
 		return ITER_SELECT_1;
 
-	cmp = strcmp(front->refname, back->refname);
+	cmp = strcmp(front->ref.name, back->ref.name);
 
 	if (cmp < 0)
 		return ITER_SELECT_0;
@@ -371,7 +339,7 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)
 	int ok;
 
 	while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {
-		int cmp = compare_prefix(iter->iter0->refname, iter->prefix);
+		int cmp = compare_prefix(iter->iter0->ref.name, iter->prefix);
 		if (cmp < 0)
 			continue;
 		/*
@@ -382,6 +350,8 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)
 		if (cmp > 0)
 			return ITER_DONE;
 
+		iter->base.ref = iter->iter0->ref;
+
 		if (iter->trim) {
 			/*
 			 * It is nonsense to trim off characters that
@@ -392,15 +362,11 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)
 			 * one character left in the refname after
 			 * trimming, report it as a bug:
 			 */
-			if (strlen(iter->iter0->refname) <= iter->trim)
+			if (strlen(iter->base.ref.name) <= iter->trim)
 				BUG("attempt to trim too many characters");
-			iter->base.refname = iter->iter0->refname + iter->trim;
-		} else {
-			iter->base.refname = iter->iter0->refname;
+			iter->base.ref.name += iter->trim;
 		}
 
-		iter->base.oid = iter->iter0->oid;
-		iter->base.flags = iter->iter0->flags;
 		return ITER_OK;
 	}
 
@@ -420,15 +386,6 @@ static int prefix_ref_iterator_seek(struct ref_iterator *ref_iterator,
 	return ref_iterator_seek(iter->iter0, refname, flags);
 }
 
-static int prefix_ref_iterator_peel(struct ref_iterator *ref_iterator,
-				    struct object_id *peeled)
-{
-	struct prefix_ref_iterator *iter =
-		(struct prefix_ref_iterator *)ref_iterator;
-
-	return ref_iterator_peel(iter->iter0, peeled);
-}
-
 static void prefix_ref_iterator_release(struct ref_iterator *ref_iterator)
 {
 	struct prefix_ref_iterator *iter =
@@ -440,7 +397,6 @@ static void prefix_ref_iterator_release(struct ref_iterator *ref_iterator)
 static struct ref_iterator_vtable prefix_ref_iterator_vtable = {
 	.advance = prefix_ref_iterator_advance,
 	.seek = prefix_ref_iterator_seek,
-	.peel = prefix_ref_iterator_peel,
 	.release = prefix_ref_iterator_release,
 };
 
@@ -466,23 +422,18 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,
 	return ref_iterator;
 }
 
-struct ref_iterator *current_ref_iter = NULL;
-
 int do_for_each_ref_iterator(struct ref_iterator *iter,
-			     each_ref_fn fn, void *cb_data)
+			     refs_for_each_cb fn, void *cb_data)
 {
 	int retval = 0, ok;
-	struct ref_iterator *old_ref_iter = current_ref_iter;
 
-	current_ref_iter = iter;
 	while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
-		retval = fn(iter->refname, iter->referent, iter->oid, iter->flags, cb_data);
+		retval = fn(&iter->ref, cb_data);
 		if (retval)
 			goto out;
 	}
 
 out:
-	current_ref_iter = old_ref_iter;
 	if (ok == ITER_ERROR)
 		retval = -1;
 	ref_iterator_free(iter);
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index a8c22a0..23ed629 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -211,7 +211,12 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot)
 	return snapshot->refs->base.repo->hash_algo->hexsz;
 }
 
+/*
+ * Since packed-refs is only stored in the common dir, don't parse the
+ * payload and rely on the files-backend to set 'gitdir' correctly.
+ */
 struct ref_store *packed_ref_store_init(struct repository *repo,
+					const char *payload UNUSED,
 					const char *gitdir,
 					unsigned int store_flags)
 {
@@ -882,6 +887,7 @@ static int next_record(struct packed_ref_iterator *iter)
 {
 	const char *p, *eol;
 
+	memset(&iter->base.ref, 0, sizeof(iter->base.ref));
 	strbuf_reset(&iter->refname_buf);
 
 	/*
@@ -908,7 +914,7 @@ static int next_record(struct packed_ref_iterator *iter)
 	if (iter->pos == iter->eof)
 		return ITER_DONE;
 
-	iter->base.flags = REF_ISPACKED;
+	iter->base.ref.flags = REF_ISPACKED;
 	p = iter->pos;
 
 	if (iter->eof - p < snapshot_hexsz(iter->snapshot) + 2 ||
@@ -916,6 +922,7 @@ static int next_record(struct packed_ref_iterator *iter)
 	    !isspace(*p++))
 		die_invalid_line(iter->snapshot->refs->path,
 				 iter->pos, iter->eof - iter->pos);
+	iter->base.ref.oid = &iter->oid;
 
 	eol = memchr(p, '\n', iter->eof - p);
 	if (!eol)
@@ -923,22 +930,22 @@ static int next_record(struct packed_ref_iterator *iter)
 				      iter->pos, iter->eof - iter->pos);
 
 	strbuf_add(&iter->refname_buf, p, eol - p);
-	iter->base.refname = iter->refname_buf.buf;
+	iter->base.ref.name = iter->refname_buf.buf;
 
 	if (refname_contains_nul(&iter->refname_buf))
-		die("packed refname contains embedded NULL: %s", iter->base.refname);
+		die("packed refname contains embedded NULL: %s", iter->base.ref.name);
 
-	if (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) {
-		if (!refname_is_safe(iter->base.refname))
+	if (check_refname_format(iter->base.ref.name, REFNAME_ALLOW_ONELEVEL)) {
+		if (!refname_is_safe(iter->base.ref.name))
 			die("packed refname is dangerous: %s",
-			    iter->base.refname);
+			    iter->base.ref.name);
 		oidclr(&iter->oid, iter->repo->hash_algo);
-		iter->base.flags |= REF_BAD_NAME | REF_ISBROKEN;
+		iter->base.ref.flags |= REF_BAD_NAME | REF_ISBROKEN;
 	}
 	if (iter->snapshot->peeled == PEELED_FULLY ||
 	    (iter->snapshot->peeled == PEELED_TAGS &&
-	     starts_with(iter->base.refname, "refs/tags/")))
-		iter->base.flags |= REF_KNOWS_PEELED;
+	     starts_with(iter->base.ref.name, "refs/tags/")))
+		iter->base.ref.flags |= REF_KNOWS_PEELED;
 
 	iter->pos = eol + 1;
 
@@ -956,11 +963,12 @@ static int next_record(struct packed_ref_iterator *iter)
 		 * definitely know the value of *this* reference. But
 		 * we suppress it if the reference is broken:
 		 */
-		if ((iter->base.flags & REF_ISBROKEN)) {
+		if ((iter->base.ref.flags & REF_ISBROKEN)) {
 			oidclr(&iter->peeled, iter->repo->hash_algo);
-			iter->base.flags &= ~REF_KNOWS_PEELED;
+			iter->base.ref.flags &= ~REF_KNOWS_PEELED;
 		} else {
-			iter->base.flags |= REF_KNOWS_PEELED;
+			iter->base.ref.flags |= REF_KNOWS_PEELED;
+			iter->base.ref.peeled_oid = &iter->peeled;
 		}
 	} else {
 		oidclr(&iter->peeled, iter->repo->hash_algo);
@@ -976,15 +984,15 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)
 	int ok;
 
 	while ((ok = next_record(iter)) == ITER_OK) {
-		const char *refname = iter->base.refname;
+		const char *refname = iter->base.ref.name;
 		const char *prefix = iter->prefix;
 
-		if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
-		    !is_per_worktree_ref(iter->base.refname))
+		if (iter->flags & REFS_FOR_EACH_PER_WORKTREE_ONLY &&
+		    !is_per_worktree_ref(iter->base.ref.name))
 			continue;
 
-		if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
-		    !ref_resolves_to_object(iter->base.refname, iter->repo,
+		if (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) &&
+		    !ref_resolves_to_object(iter->base.ref.name, iter->repo,
 					    &iter->oid, iter->flags))
 			continue;
 
@@ -1027,22 +1035,6 @@ static int packed_ref_iterator_seek(struct ref_iterator *ref_iterator,
 	return 0;
 }
 
-static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,
-				   struct object_id *peeled)
-{
-	struct packed_ref_iterator *iter =
-		(struct packed_ref_iterator *)ref_iterator;
-
-	if ((iter->base.flags & REF_KNOWS_PEELED)) {
-		oidcpy(peeled, &iter->peeled);
-		return is_null_oid(&iter->peeled) ? -1 : 0;
-	} else if ((iter->base.flags & (REF_ISBROKEN | REF_ISSYMREF))) {
-		return -1;
-	} else {
-		return peel_object(iter->repo, &iter->oid, peeled) ? -1 : 0;
-	}
-}
-
 static void packed_ref_iterator_release(struct ref_iterator *ref_iterator)
 {
 	struct packed_ref_iterator *iter =
@@ -1056,7 +1048,6 @@ static void packed_ref_iterator_release(struct ref_iterator *ref_iterator)
 static struct ref_iterator_vtable packed_ref_iterator_vtable = {
 	.advance = packed_ref_iterator_advance,
 	.seek = packed_ref_iterator_seek,
-	.peel = packed_ref_iterator_peel,
 	.release = packed_ref_iterator_release,
 };
 
@@ -1173,7 +1164,7 @@ static struct ref_iterator *packed_ref_iterator_begin(
 	struct ref_iterator *ref_iterator;
 	unsigned int required_flags = REF_STORE_READ;
 
-	if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN))
+	if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN))
 		required_flags |= REF_STORE_ODB;
 	refs = packed_downcast(ref_store, required_flags, "ref_iterator_begin");
 
@@ -1194,7 +1185,6 @@ static struct ref_iterator *packed_ref_iterator_begin(
 	iter->snapshot = snapshot;
 	acquire_snapshot(snapshot);
 	strbuf_init(&iter->refname_buf, 0);
-	iter->base.oid = &iter->oid;
 	iter->repo = ref_store->repo;
 	iter->flags = flags;
 
@@ -1416,7 +1406,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
 	 * of updates is exhausted, leave i set to updates->nr.
 	 */
 	iter = packed_ref_iterator_begin(&refs->base, "", NULL,
-					 DO_FOR_EACH_INCLUDE_BROKEN);
+					 REFS_FOR_EACH_INCLUDE_BROKEN);
 	if ((ok = ref_iterator_advance(iter)) != ITER_OK) {
 		ref_iterator_free(iter);
 		iter = NULL;
@@ -1436,7 +1426,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
 			if (!iter)
 				cmp = +1;
 			else
-				cmp = strcmp(iter->refname, update->refname);
+				cmp = strcmp(iter->ref.name, update->refname);
 		}
 
 		if (!cmp) {
@@ -1452,23 +1442,23 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
 						    update->refname);
 					ret = REF_TRANSACTION_ERROR_CREATE_EXISTS;
 
-					if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
-						strbuf_reset(err);
+					if (ref_transaction_maybe_set_rejected(transaction, i,
+									       ret, err)) {
 						ret = 0;
 						continue;
 					}
 
 					goto error;
-				} else if (!oideq(&update->old_oid, iter->oid)) {
+				} else if (!oideq(&update->old_oid, iter->ref.oid)) {
 					strbuf_addf(err, "cannot update ref '%s': "
 						    "is at %s but expected %s",
 						    update->refname,
-						    oid_to_hex(iter->oid),
+						    oid_to_hex(iter->ref.oid),
 						    oid_to_hex(&update->old_oid));
 					ret = REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE;
 
-					if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
-						strbuf_reset(err);
+					if (ref_transaction_maybe_set_rejected(transaction, i,
+									       ret, err)) {
 						ret = 0;
 						continue;
 					}
@@ -1511,8 +1501,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
 					    oid_to_hex(&update->old_oid));
 				ret = REF_TRANSACTION_ERROR_NONEXISTENT_REF;
 
-				if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
-					strbuf_reset(err);
+				if (ref_transaction_maybe_set_rejected(transaction, i,
+								       ret, err)) {
 					ret = 0;
 					continue;
 				}
@@ -1523,13 +1513,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
 
 		if (cmp < 0) {
 			/* Pass the old reference through. */
-
-			struct object_id peeled;
-			int peel_error = ref_iterator_peel(iter, &peeled);
-
-			if (write_packed_entry(out, iter->refname,
-					       iter->oid,
-					       peel_error ? NULL : &peeled))
+			if (write_packed_entry(out, iter->ref.name,
+					       iter->ref.oid, iter->ref.peeled_oid))
 				goto write_error;
 
 			if ((ok = ref_iterator_advance(iter)) != ITER_OK) {
@@ -1547,9 +1532,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
 			i++;
 		} else {
 			struct object_id peeled;
-			int peel_error = peel_object(refs->base.repo,
-						     &update->new_oid,
-						     &peeled);
+			int peel_error = peel_object(refs->base.repo, &update->new_oid,
+						     &peeled, PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);
 
 			if (write_packed_entry(out, update->refname,
 					       &update->new_oid,
@@ -1794,8 +1778,8 @@ static int packed_transaction_finish(struct ref_store *ref_store,
 	return ret;
 }
 
-static int packed_pack_refs(struct ref_store *ref_store UNUSED,
-			    struct pack_refs_opts *pack_opts UNUSED)
+static int packed_optimize(struct ref_store *ref_store UNUSED,
+			   struct refs_optimize_opts *opts UNUSED)
 {
 	/*
 	 * Packed refs are already packed. It might be that loose refs
@@ -1805,6 +1789,17 @@ static int packed_pack_refs(struct ref_store *ref_store UNUSED,
 	return 0;
 }
 
+static int packed_optimize_required(struct ref_store *ref_store UNUSED,
+				    struct refs_optimize_opts *opts UNUSED,
+				    bool *required)
+{
+	/*
+	 * Packed refs are already optimized.
+	 */
+	*required = false;
+	return 0;
+}
+
 static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_store UNUSED)
 {
 	return empty_ref_iterator_begin();
@@ -2150,7 +2145,9 @@ struct ref_storage_be refs_be_packed = {
 	.transaction_finish = packed_transaction_finish,
 	.transaction_abort = packed_transaction_abort,
 
-	.pack_refs = packed_pack_refs,
+	.optimize = packed_optimize,
+	.optimize_required = packed_optimize_required,
+
 	.rename_ref = NULL,
 	.copy_ref = NULL,
 
diff --git a/refs/packed-backend.h b/refs/packed-backend.h
index 9481d5e..2c2377a 100644
--- a/refs/packed-backend.h
+++ b/refs/packed-backend.h
@@ -14,6 +14,7 @@ struct ref_transaction;
  */
 
 struct ref_store *packed_ref_store_init(struct repository *repo,
+					const char *payload,
 					const char *gitdir,
 					unsigned int store_flags);
 
diff --git a/refs/ref-cache.c b/refs/ref-cache.c
index e5e5df1..ffef01a 100644
--- a/refs/ref-cache.c
+++ b/refs/ref-cache.c
@@ -425,10 +425,11 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
 			level->prefix_state = entry_prefix_state;
 			level->index = -1;
 		} else {
-			iter->base.refname = entry->name;
-			iter->base.referent = entry->u.value.referent;
-			iter->base.oid = &entry->u.value.oid;
-			iter->base.flags = entry->flag;
+			memset(&iter->base.ref, 0, sizeof(iter->base.ref));
+			iter->base.ref.name = entry->name;
+			iter->base.ref.target = entry->u.value.referent;
+			iter->base.ref.oid = &entry->u.value.oid;
+			iter->base.ref.flags = entry->flag;
 			return ITER_OK;
 		}
 	}
@@ -545,14 +546,6 @@ static int cache_ref_iterator_seek(struct ref_iterator *ref_iterator,
 	return 0;
 }
 
-static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,
-				   struct object_id *peeled)
-{
-	struct cache_ref_iterator *iter =
-		(struct cache_ref_iterator *)ref_iterator;
-	return peel_object(iter->repo, ref_iterator->oid, peeled) ? -1 : 0;
-}
-
 static void cache_ref_iterator_release(struct ref_iterator *ref_iterator)
 {
 	struct cache_ref_iterator *iter =
@@ -564,7 +557,6 @@ static void cache_ref_iterator_release(struct ref_iterator *ref_iterator)
 static struct ref_iterator_vtable cache_ref_iterator_vtable = {
 	.advance = cache_ref_iterator_advance,
 	.seek = cache_ref_iterator_seek,
-	.peel = cache_ref_iterator_peel,
 	.release = cache_ref_iterator_release,
 };
 
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 4ef3bd7..d79e35f 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -128,6 +128,7 @@ struct ref_update {
 	 * was rejected.
 	 */
 	enum ref_transaction_error rejection_err;
+	const char *rejection_details;
 
 	/*
 	 * If this ref_update was split off of a symref update via
@@ -153,7 +154,8 @@ int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,
  */
 int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction,
 				       size_t update_idx,
-				       enum ref_transaction_error err);
+				       enum ref_transaction_error err,
+				       struct strbuf *details);
 
 /*
  * Add a ref_update with the specified properties to transaction, and
@@ -249,10 +251,7 @@ const char *find_descendant_ref(const char *dirname,
  */
 struct ref_iterator {
 	struct ref_iterator_vtable *vtable;
-	const char *refname;
-	const char *referent;
-	const struct object_id *oid;
-	unsigned int flags;
+	struct reference ref;
 };
 
 /*
@@ -361,12 +360,6 @@ typedef int ref_iterator_seek_fn(struct ref_iterator *ref_iterator,
 				 const char *refname, unsigned int flags);
 
 /*
- * Peels the current ref, returning 0 for success or -1 for failure.
- */
-typedef int ref_iterator_peel_fn(struct ref_iterator *ref_iterator,
-				 struct object_id *peeled);
-
-/*
  * Implementations of this function should free any resources specific
  * to the derived class.
  */
@@ -375,23 +368,9 @@ typedef void ref_iterator_release_fn(struct ref_iterator *ref_iterator);
 struct ref_iterator_vtable {
 	ref_iterator_advance_fn *advance;
 	ref_iterator_seek_fn *seek;
-	ref_iterator_peel_fn *peel;
 	ref_iterator_release_fn *release;
 };
 
-/*
- * current_ref_iter is a performance hack: when iterating over
- * references using the for_each_ref*() functions, current_ref_iter is
- * set to the reference iterator before calling the callback function.
- * If the callback function calls peel_ref(), then peel_ref() first
- * checks whether the reference to be peeled is the one referred to by
- * the iterator (it usually is) and if so, asks the iterator for the
- * peeled version of the reference if it is available. This avoids a
- * refname lookup in a common case. current_ref_iter is set to NULL
- * when the iteration is over.
- */
-extern struct ref_iterator *current_ref_iter;
-
 struct ref_store;
 
 /* refs backends */
@@ -412,6 +391,7 @@ struct ref_store;
  * the ref_store and to record the ref_store for later lookup.
  */
 typedef struct ref_store *ref_store_init_fn(struct repository *repo,
+					    const char *payload,
 					    const char *gitdir,
 					    unsigned int flags);
 /*
@@ -445,10 +425,13 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs,
 				      struct ref_transaction *transaction,
 				      struct strbuf *err);
 
-typedef int pack_refs_fn(struct ref_store *ref_store,
-			 struct pack_refs_opts *opts);
 typedef int optimize_fn(struct ref_store *ref_store,
-			struct pack_refs_opts *opts);
+			struct refs_optimize_opts *opts);
+
+typedef int optimize_required_fn(struct ref_store *ref_store,
+				 struct refs_optimize_opts *opts,
+				 bool *required);
+
 typedef int rename_ref_fn(struct ref_store *ref_store,
 			  const char *oldref, const char *newref,
 			  const char *logmsg);
@@ -573,8 +556,8 @@ struct ref_storage_be {
 	ref_transaction_finish_fn *transaction_finish;
 	ref_transaction_abort_fn *transaction_abort;
 
-	pack_refs_fn *pack_refs;
 	optimize_fn *optimize;
+	optimize_required_fn *optimize_required;
 	rename_ref_fn *rename_ref;
 	copy_ref_fn *copy_ref;
 
@@ -686,4 +669,17 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs
 					  unsigned int initial_transaction,
 					  struct strbuf *err);
 
+/*
+ * Given a gitdir and the reference storage payload provided, retrieve the
+ * 'refdir' and 'ref_common_dir'. The former is where references should be
+ * stored for the current worktree, the latter is the common reference
+ * directory if working with a linked worktree. If working with the main
+ * worktree, both values will be the same.
+ *
+ * This is used by backends that store references in the repository directly.
+ */
+void refs_compute_filesystem_location(const char *gitdir, const char *payload,
+				      bool *is_worktree, struct strbuf *refdir,
+				      struct strbuf *ref_common_dir);
+
 #endif /* REFS_REFS_INTERNAL_H */
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index eeec647..b124404 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -10,9 +10,10 @@
 #include "../gettext.h"
 #include "../hash.h"
 #include "../hex.h"
-#include "../iterator.h"
 #include "../ident.h"
+#include "../iterator.h"
 #include "../object.h"
+#include "../parse.h"
 #include "../path.h"
 #include "../refs.h"
 #include "../reftable/reftable-basics.h"
@@ -25,8 +26,8 @@
 #include "../setup.h"
 #include "../strmap.h"
 #include "../trace2.h"
+#include "../worktree.h"
 #include "../write-or-die.h"
-#include "parse.h"
 #include "refs-internal.h"
 
 /*
@@ -172,6 +173,37 @@ static struct reftable_ref_store *reftable_be_downcast(struct ref_store *ref_sto
 	return refs;
 }
 
+static int backend_for_worktree(struct reftable_backend **out,
+				struct reftable_ref_store *store,
+				const char *worktree_name)
+{
+	struct strbuf worktree_dir = STRBUF_INIT;
+	int ret;
+
+	*out = strmap_get(&store->worktree_backends, worktree_name);
+	if (*out) {
+		ret = 0;
+		goto out;
+	}
+
+	strbuf_addf(&worktree_dir, "%s/worktrees/%s/reftable",
+		    store->base.repo->commondir, worktree_name);
+
+	CALLOC_ARRAY(*out, 1);
+	store->err = ret = reftable_backend_init(*out, worktree_dir.buf,
+						 &store->write_options);
+	if (ret < 0) {
+		free(*out);
+		goto out;
+	}
+
+	strmap_put(&store->worktree_backends, worktree_name, *out);
+
+out:
+	strbuf_release(&worktree_dir);
+	return ret;
+}
+
 /*
  * Some refs are global to the repository (refs/heads/{*}), while others are
  * local to the worktree (eg. HEAD, refs/bisect/{*}). We solve this by having
@@ -191,19 +223,19 @@ static int backend_for(struct reftable_backend **out,
 		       const char **rewritten_ref,
 		       int reload)
 {
-	struct reftable_backend *be;
 	const char *wtname;
 	int wtname_len;
+	int ret;
 
 	if (!refname) {
-		be = &store->main_backend;
+		*out = &store->main_backend;
+		ret = 0;
 		goto out;
 	}
 
 	switch (parse_worktree_ref(refname, &wtname, &wtname_len, rewritten_ref)) {
 	case REF_WORKTREE_OTHER: {
 		static struct strbuf wtname_buf = STRBUF_INIT;
-		struct strbuf wt_dir = STRBUF_INIT;
 
 		/*
 		 * We're using a static buffer here so that we don't need to
@@ -223,20 +255,8 @@ static int backend_for(struct reftable_backend **out,
 		 * already and error out when trying to write a reference via
 		 * both stacks.
 		 */
-		be = strmap_get(&store->worktree_backends, wtname_buf.buf);
-		if (!be) {
-			strbuf_addf(&wt_dir, "%s/worktrees/%s/reftable",
-				    store->base.repo->commondir, wtname_buf.buf);
+		ret = backend_for_worktree(out, store, wtname_buf.buf);
 
-			CALLOC_ARRAY(be, 1);
-			store->err = reftable_backend_init(be, wt_dir.buf,
-							   &store->write_options);
-			assert(store->err != REFTABLE_API_ERROR);
-
-			strmap_put(&store->worktree_backends, wtname_buf.buf, be);
-		}
-
-		strbuf_release(&wt_dir);
 		goto out;
 	}
 	case REF_WORKTREE_CURRENT:
@@ -245,27 +265,24 @@ static int backend_for(struct reftable_backend **out,
 		 * main worktree. We thus return the main stack in that case.
 		 */
 		if (!store->worktree_backend.stack)
-			be = &store->main_backend;
+			*out = &store->main_backend;
 		else
-			be = &store->worktree_backend;
+			*out = &store->worktree_backend;
+		ret = 0;
 		goto out;
 	case REF_WORKTREE_MAIN:
 	case REF_WORKTREE_SHARED:
-		be = &store->main_backend;
+		*out = &store->main_backend;
+		ret = 0;
 		goto out;
 	default:
 		BUG("unhandled worktree reference type");
 	}
 
 out:
-	if (reload) {
-		int ret = reftable_stack_reload(be->stack);
-		if (ret)
-			return ret;
-	}
-	*out = be;
-
-	return 0;
+	if (reload && !ret)
+		ret = reftable_stack_reload((*out)->stack);
+	return ret;
 }
 
 static int should_write_log(struct reftable_ref_store *refs, const char *refname)
@@ -355,18 +372,24 @@ static int reftable_be_fsync(int fd)
 }
 
 static struct ref_store *reftable_be_init(struct repository *repo,
+					  const char *payload,
 					  const char *gitdir,
 					  unsigned int store_flags)
 {
 	struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));
+	struct strbuf ref_common_dir = STRBUF_INIT;
+	struct strbuf refdir = STRBUF_INIT;
 	struct strbuf path = STRBUF_INIT;
-	int is_worktree;
+	bool is_worktree;
 	mode_t mask;
 
 	mask = umask(0);
 	umask(mask);
 
-	base_ref_store_init(&refs->base, repo, gitdir, &refs_be_reftable);
+	refs_compute_filesystem_location(gitdir, payload, &is_worktree, &refdir,
+					 &ref_common_dir);
+
+	base_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);
 	strmap_init(&refs->worktree_backends);
 	refs->store_flags = store_flags;
 	refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
@@ -402,14 +425,11 @@ static struct ref_store *reftable_be_init(struct repository *repo,
 	/*
 	 * Set up the main reftable stack that is hosted in GIT_COMMON_DIR.
 	 * This stack contains both the shared and the main worktree refs.
-	 *
-	 * Note that we don't try to resolve the path in case we have a
-	 * worktree because `get_common_dir_noenv()` already does it for us.
 	 */
-	is_worktree = get_common_dir_noenv(&path, gitdir);
+	strbuf_addbuf(&path, &ref_common_dir);
 	if (!is_worktree) {
 		strbuf_reset(&path);
-		strbuf_realpath(&path, gitdir, 0);
+		strbuf_realpath(&path, ref_common_dir.buf, 0);
 	}
 	strbuf_addstr(&path, "/reftable");
 	refs->err = reftable_backend_init(&refs->main_backend, path.buf,
@@ -426,10 +446,9 @@ static struct ref_store *reftable_be_init(struct repository *repo,
 	 * do it efficiently.
 	 */
 	if (is_worktree) {
-		strbuf_reset(&path);
-		strbuf_addf(&path, "%s/reftable", gitdir);
+		strbuf_addstr(&refdir, "/reftable");
 
-		refs->err = reftable_backend_init(&refs->worktree_backend, path.buf,
+		refs->err = reftable_backend_init(&refs->worktree_backend, refdir.buf,
 						  &refs->write_options);
 		if (refs->err)
 			goto done;
@@ -439,6 +458,8 @@ static struct ref_store *reftable_be_init(struct repository *repo,
 
 done:
 	assert(refs->err != REFTABLE_API_ERROR);
+	strbuf_release(&ref_common_dir);
+	strbuf_release(&refdir);
 	strbuf_release(&path);
 	return &refs->base;
 }
@@ -474,19 +495,6 @@ static int reftable_be_create_on_disk(struct ref_store *ref_store,
 	safe_create_dir(the_repository, sb.buf, 1);
 	strbuf_reset(&sb);
 
-	strbuf_addf(&sb, "%s/HEAD", refs->base.gitdir);
-	write_file(sb.buf, "ref: refs/heads/.invalid");
-	adjust_shared_perm(the_repository, sb.buf);
-	strbuf_reset(&sb);
-
-	strbuf_addf(&sb, "%s/refs", refs->base.gitdir);
-	safe_create_dir(the_repository, sb.buf, 1);
-	strbuf_reset(&sb);
-
-	strbuf_addf(&sb, "%s/refs/heads", refs->base.gitdir);
-	write_file(sb.buf, "this repository uses the reftable format");
-	adjust_shared_perm(the_repository, sb.buf);
-
 	strbuf_release(&sb);
 	return 0;
 }
@@ -512,30 +520,6 @@ static int reftable_be_remove_on_disk(struct ref_store *ref_store,
 			    strerror(errno));
 		ret = -1;
 	}
-	strbuf_reset(&sb);
-
-	strbuf_addf(&sb, "%s/HEAD", refs->base.gitdir);
-	if (unlink(sb.buf) < 0) {
-		strbuf_addf(err, "could not delete stub HEAD: %s",
-			    strerror(errno));
-		ret = -1;
-	}
-	strbuf_reset(&sb);
-
-	strbuf_addf(&sb, "%s/refs/heads", refs->base.gitdir);
-	if (unlink(sb.buf) < 0) {
-		strbuf_addf(err, "could not delete stub heads: %s",
-			    strerror(errno));
-		ret = -1;
-	}
-	strbuf_reset(&sb);
-
-	strbuf_addf(&sb, "%s/refs", refs->base.gitdir);
-	if (rmdir(sb.buf) < 0) {
-		strbuf_addf(err, "could not delete refs directory: %s",
-			    strerror(errno));
-		ret = -1;
-	}
 
 	strbuf_release(&sb);
 	return ret;
@@ -547,6 +531,7 @@ struct reftable_ref_iterator {
 	struct reftable_iterator iter;
 	struct reftable_ref_record ref;
 	struct object_id oid;
+	struct object_id peeled_oid;
 
 	char *prefix;
 	size_t prefix_len;
@@ -644,7 +629,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
 		 * the root refs are to be included. We emulate the same behaviour here.
 		 */
 		if (!starts_with(iter->ref.refname, "refs/") &&
-		    !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS &&
+		    !(iter->flags & REFS_FOR_EACH_INCLUDE_ROOT_REFS &&
 		      is_root_ref(iter->ref.refname))) {
 			continue;
 		}
@@ -658,7 +643,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
 		if (iter->exclude_patterns && should_exclude_current_ref(iter))
 			continue;
 
-		if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
+		if (iter->flags & REFS_FOR_EACH_PER_WORKTREE_ONLY &&
 		    parse_worktree_ref(iter->ref.refname, NULL, NULL, NULL) !=
 			    REF_WORKTREE_CURRENT)
 			continue;
@@ -671,6 +656,8 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
 		case REFTABLE_REF_VAL2:
 			oidread(&iter->oid, iter->ref.value.val2.value,
 				refs->base.repo->hash_algo);
+			oidread(&iter->peeled_oid, iter->ref.value.val2.target_value,
+				refs->base.repo->hash_algo);
 			break;
 		case REFTABLE_REF_SYMREF:
 			referent = refs_resolve_ref_unsafe(&iter->refs->base,
@@ -694,20 +681,23 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
 			flags |= REF_BAD_NAME | REF_ISBROKEN;
 		}
 
-		if (iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS &&
+		if (iter->flags & REFS_FOR_EACH_OMIT_DANGLING_SYMREFS &&
 		    flags & REF_ISSYMREF &&
 		    flags & REF_ISBROKEN)
 			continue;
 
-		if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
+		if (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) &&
 		    !ref_resolves_to_object(iter->ref.refname, refs->base.repo,
 					    &iter->oid, flags))
 				continue;
 
-		iter->base.refname = iter->ref.refname;
-		iter->base.referent = referent;
-		iter->base.oid = &iter->oid;
-		iter->base.flags = flags;
+		memset(&iter->base.ref, 0, sizeof(iter->base.ref));
+		iter->base.ref.name = iter->ref.refname;
+		iter->base.ref.target = referent;
+		iter->base.ref.oid = &iter->oid;
+		if (iter->ref.value_type == REFTABLE_REF_VAL2)
+			iter->base.ref.peeled_oid = &iter->peeled_oid;
+		iter->base.ref.flags = flags;
 
 		break;
 	}
@@ -738,21 +728,6 @@ static int reftable_ref_iterator_seek(struct ref_iterator *ref_iterator,
 	return iter->err;
 }
 
-static int reftable_ref_iterator_peel(struct ref_iterator *ref_iterator,
-				      struct object_id *peeled)
-{
-	struct reftable_ref_iterator *iter =
-		(struct reftable_ref_iterator *)ref_iterator;
-
-	if (iter->ref.value_type == REFTABLE_REF_VAL2) {
-		oidread(peeled, iter->ref.value.val2.target_value,
-			iter->refs->base.repo->hash_algo);
-		return 0;
-	}
-
-	return -1;
-}
-
 static void reftable_ref_iterator_release(struct ref_iterator *ref_iterator)
 {
 	struct reftable_ref_iterator *iter =
@@ -770,7 +745,6 @@ static void reftable_ref_iterator_release(struct ref_iterator *ref_iterator)
 static struct ref_iterator_vtable reftable_ref_iterator_vtable = {
 	.advance = reftable_ref_iterator_advance,
 	.seek = reftable_ref_iterator_seek,
-	.peel = reftable_ref_iterator_peel,
 	.release = reftable_ref_iterator_release,
 };
 
@@ -828,7 +802,7 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_
 
 	iter = xcalloc(1, sizeof(*iter));
 	base_ref_iterator_init(&iter->base, &reftable_ref_iterator_vtable);
-	iter->base.oid = &iter->oid;
+	iter->base.ref.oid = &iter->oid;
 	iter->flags = flags;
 	iter->refs = refs;
 	iter->exclude_patterns = filter_exclude_patterns(exclude_patterns);
@@ -864,7 +838,7 @@ static struct ref_iterator *reftable_be_iterator_begin(struct ref_store *ref_sto
 	struct reftable_ref_store *refs;
 	unsigned int required_flags = REF_STORE_READ;
 
-	if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN))
+	if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN))
 		required_flags |= REF_STORE_ODB;
 	refs = reftable_be_downcast(ref_store, required_flags, "ref_iterator_begin");
 
@@ -1411,10 +1385,9 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
 					    &refnames_to_check, head_type,
 					    &head_referent, &referent, err);
 		if (ret) {
-			if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
-				strbuf_reset(err);
+			if (ref_transaction_maybe_set_rejected(transaction, i,
+							       ret, err)) {
 				ret = 0;
-
 				continue;
 			}
 			goto done;
@@ -1642,7 +1615,8 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
 			ref.refname = (char *)u->refname;
 			ref.update_index = ts;
 
-			peel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled);
+			peel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled,
+						 PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);
 			if (!peel_error) {
 				ref.value_type = REFTABLE_REF_VAL2;
 				memcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);
@@ -1709,11 +1683,11 @@ static int reftable_be_transaction_finish(struct ref_store *ref_store UNUSED,
 	return ret;
 }
 
-static int reftable_be_pack_refs(struct ref_store *ref_store,
-				 struct pack_refs_opts *opts)
+static int reftable_be_optimize(struct ref_store *ref_store,
+				struct refs_optimize_opts *opts)
 {
 	struct reftable_ref_store *refs =
-		reftable_be_downcast(ref_store, REF_STORE_WRITE | REF_STORE_ODB, "pack_refs");
+		reftable_be_downcast(ref_store, REF_STORE_WRITE | REF_STORE_ODB, "optimize_refs");
 	struct reftable_stack *stack;
 	int ret;
 
@@ -1724,7 +1698,7 @@ static int reftable_be_pack_refs(struct ref_store *ref_store,
 	if (!stack)
 		stack = refs->main_backend.stack;
 
-	if (opts->flags & PACK_REFS_AUTO)
+	if (opts->flags & REFS_OPTIMIZE_AUTO)
 		ret = reftable_stack_auto_compact(stack);
 	else
 		ret = reftable_stack_compact_all(stack, NULL);
@@ -1742,10 +1716,27 @@ static int reftable_be_pack_refs(struct ref_store *ref_store,
 	return ret;
 }
 
-static int reftable_be_optimize(struct ref_store *ref_store,
-				struct pack_refs_opts *opts)
+static int reftable_be_optimize_required(struct ref_store *ref_store,
+					 struct refs_optimize_opts *opts,
+					 bool *required)
 {
-	return reftable_be_pack_refs(ref_store, opts);
+	struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_READ,
+							       "optimize_refs_required");
+	struct reftable_stack *stack;
+	bool use_heuristics = false;
+
+	if (refs->err)
+		return refs->err;
+
+	stack = refs->worktree_backend.stack;
+	if (!stack)
+		stack = refs->main_backend.stack;
+
+	if (opts->flags & REFS_OPTIMIZE_AUTO)
+		use_heuristics = true;
+
+	return reftable_stack_compaction_required(stack, use_heuristics,
+						  required);
 }
 
 struct write_create_symref_arg {
@@ -2072,7 +2063,7 @@ static int reftable_reflog_iterator_advance(struct ref_iterator *ref_iterator)
 
 		strbuf_reset(&iter->last_name);
 		strbuf_addstr(&iter->last_name, iter->log.refname);
-		iter->base.refname = iter->log.refname;
+		iter->base.ref.name = iter->log.refname;
 
 		break;
 	}
@@ -2092,13 +2083,6 @@ static int reftable_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSE
 	return -1;
 }
 
-static int reftable_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED,
-					 struct object_id *peeled UNUSED)
-{
-	BUG("reftable reflog iterator cannot be peeled");
-	return -1;
-}
-
 static void reftable_reflog_iterator_release(struct ref_iterator *ref_iterator)
 {
 	struct reftable_reflog_iterator *iter =
@@ -2111,7 +2095,6 @@ static void reftable_reflog_iterator_release(struct ref_iterator *ref_iterator)
 static struct ref_iterator_vtable reftable_reflog_iterator_vtable = {
 	.advance = reftable_reflog_iterator_advance,
 	.seek = reftable_reflog_iterator_seek,
-	.peel = reftable_reflog_iterator_peel,
 	.release = reftable_reflog_iterator_release,
 };
 
@@ -2515,7 +2498,7 @@ static int write_reflog_expiry_table(struct reftable_writer *writer, void *cb_da
 		ref.refname = (char *)arg->refname;
 		ref.update_index = ts;
 
-		if (!peel_object(arg->refs->base.repo, &arg->update_oid, &peeled)) {
+		if (!peel_object(arg->refs->base.repo, &arg->update_oid, &peeled, 0)) {
 			ref.value_type = REFTABLE_REF_VAL2;
 			memcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);
 			memcpy(ref.value.val2.value, arg->update_oid.hash, GIT_MAX_RAWSZ);
@@ -2746,24 +2729,92 @@ static int reftable_fsck_error_handler(struct reftable_fsck_info *info,
 }
 
 static int reftable_be_fsck(struct ref_store *ref_store, struct fsck_options *o,
-			    struct worktree *wt UNUSED)
+			    struct worktree *wt)
 {
-	struct reftable_ref_store *refs;
-	struct strmap_entry *entry;
-	struct hashmap_iter iter;
-	int ret = 0;
+	struct reftable_ref_store *refs =
+		reftable_be_downcast(ref_store, REF_STORE_READ, "fsck");
+	struct reftable_ref_iterator *iter = NULL;
+	struct reftable_ref_record ref = { 0 };
+	struct fsck_ref_report report = { 0 };
+	struct strbuf refname = STRBUF_INIT;
+	struct reftable_backend *backend;
+	int ret, errors = 0;
 
-	refs = reftable_be_downcast(ref_store, REF_STORE_READ, "fsck");
-
-	ret |= reftable_fsck_check(refs->main_backend.stack, reftable_fsck_error_handler,
-				   reftable_fsck_verbose_handler, o);
-
-	strmap_for_each_entry(&refs->worktree_backends, &iter, entry) {
-		struct reftable_backend *b = (struct reftable_backend *)entry->value;
-		ret |= reftable_fsck_check(b->stack, reftable_fsck_error_handler,
-					   reftable_fsck_verbose_handler, o);
+	if (is_main_worktree(wt)) {
+		backend = &refs->main_backend;
+	} else {
+		ret = backend_for_worktree(&backend, refs, wt->id);
+		if (ret < 0) {
+			ret = error(_("reftable stack for worktree '%s' is broken"),
+				    wt->id);
+			goto out;
+		}
 	}
 
+	errors |= reftable_fsck_check(backend->stack, reftable_fsck_error_handler,
+				      reftable_fsck_verbose_handler, o);
+
+	iter = ref_iterator_for_stack(refs, backend->stack, "", NULL, 0);
+	if (!iter) {
+		ret = error(_("could not create iterator for worktree '%s'"), wt->id);
+		goto out;
+	}
+
+	while (1) {
+		ret = reftable_iterator_next_ref(&iter->iter, &ref);
+		if (ret > 0)
+			break;
+		if (ret < 0) {
+			ret = error(_("could not read record for worktree '%s'"), wt->id);
+			goto out;
+		}
+
+		strbuf_reset(&refname);
+		if (!is_main_worktree(wt))
+			strbuf_addf(&refname, "worktrees/%s/", wt->id);
+		strbuf_addstr(&refname, ref.refname);
+		report.path = refname.buf;
+
+		switch (ref.value_type) {
+		case REFTABLE_REF_VAL1:
+		case REFTABLE_REF_VAL2: {
+			struct object_id oid;
+			unsigned hash_id;
+
+			switch (reftable_stack_hash_id(backend->stack)) {
+			case REFTABLE_HASH_SHA1:
+				hash_id = GIT_HASH_SHA1;
+				break;
+			case REFTABLE_HASH_SHA256:
+				hash_id = GIT_HASH_SHA256;
+				break;
+			default:
+				BUG("unhandled hash ID %d",
+				    reftable_stack_hash_id(backend->stack));
+			}
+
+			oidread(&oid, reftable_ref_record_val1(&ref),
+				&hash_algos[hash_id]);
+
+			errors |= refs_fsck_ref(ref_store, o, &report, ref.refname, &oid);
+			break;
+		}
+		case REFTABLE_REF_SYMREF:
+			errors |= refs_fsck_symref(ref_store, o, &report, ref.refname,
+						   ref.value.symref);
+			break;
+		default:
+			BUG("unhandled reference value type %d", ref.value_type);
+		}
+	}
+
+	ret = errors ? -1 : 0;
+
+out:
+	if (iter)
+		ref_iterator_free(&iter->base);
+	reftable_ref_record_release(&ref);
+	strbuf_release(&refname);
 	return ret;
 }
 
@@ -2778,8 +2829,9 @@ struct ref_storage_be refs_be_reftable = {
 	.transaction_finish = reftable_be_transaction_finish,
 	.transaction_abort = reftable_be_transaction_abort,
 
-	.pack_refs = reftable_be_pack_refs,
 	.optimize = reftable_be_optimize,
+	.optimize_required = reftable_be_optimize_required,
+
 	.rename_ref = reftable_be_rename_ref,
 	.copy_ref = reftable_be_copy_ref,
 
diff --git a/reftable/reftable-stack.h b/reftable/reftable-stack.h
index d70fcb7..c2415cb 100644
--- a/reftable/reftable-stack.h
+++ b/reftable/reftable-stack.h
@@ -123,6 +123,17 @@ struct reftable_log_expiry_config {
 int reftable_stack_compact_all(struct reftable_stack *st,
 			       struct reftable_log_expiry_config *config);
 
+/*
+ * Check if compaction is required.
+ *
+ * When `use_heuristics` is false, check if all tables can be compacted to a
+ * single table. If true, use heuristics to determine if the tables need to be
+ * compacted to maintain geometric progression.
+ */
+int reftable_stack_compaction_required(struct reftable_stack *st,
+				       bool use_heuristics,
+				       bool *required);
+
 /* heuristically compact unbalanced table stack. */
 int reftable_stack_auto_compact(struct reftable_stack *st);
 
diff --git a/reftable/stack.c b/reftable/stack.c
index 65d8982..1c9f21d 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -1626,7 +1626,8 @@ struct segment suggest_compaction_segment(uint64_t *sizes, size_t n,
 	return seg;
 }
 
-static uint64_t *stack_table_sizes_for_compaction(struct reftable_stack *st)
+static int stack_segments_for_compaction(struct reftable_stack *st,
+					 struct segment *seg)
 {
 	int version = (st->opts.hash_id == REFTABLE_HASH_SHA1) ? 1 : 2;
 	int overhead = header_size(version) - 1;
@@ -1634,31 +1635,63 @@ static uint64_t *stack_table_sizes_for_compaction(struct reftable_stack *st)
 
 	REFTABLE_CALLOC_ARRAY(sizes, st->merged->tables_len);
 	if (!sizes)
-		return NULL;
+		return REFTABLE_OUT_OF_MEMORY_ERROR;
 
 	for (size_t i = 0; i < st->merged->tables_len; i++)
 		sizes[i] = st->tables[i]->size - overhead;
 
-	return sizes;
+	*seg = suggest_compaction_segment(sizes, st->merged->tables_len,
+					  st->opts.auto_compaction_factor);
+	reftable_free(sizes);
+
+	return 0;
+}
+
+static int update_segment_if_compaction_required(struct reftable_stack *st,
+						 struct segment *seg,
+						 bool use_geometric,
+						 bool *required)
+{
+	int err;
+
+	if (st->merged->tables_len < 2) {
+		*required = false;
+		return 0;
+	}
+
+	if (!use_geometric) {
+		*required = true;
+		return 0;
+	}
+
+	err = stack_segments_for_compaction(st, seg);
+	if (err)
+		return err;
+
+	*required = segment_size(seg) > 0;
+	return 0;
+}
+
+int reftable_stack_compaction_required(struct reftable_stack *st,
+				       bool use_heuristics,
+				       bool *required)
+{
+	struct segment seg;
+	return update_segment_if_compaction_required(st, &seg, use_heuristics,
+						     required);
 }
 
 int reftable_stack_auto_compact(struct reftable_stack *st)
 {
 	struct segment seg;
-	uint64_t *sizes;
+	bool required;
+	int err;
 
-	if (st->merged->tables_len < 2)
-		return 0;
+	err = update_segment_if_compaction_required(st, &seg, true, &required);
+	if (err)
+		return err;
 
-	sizes = stack_table_sizes_for_compaction(st);
-	if (!sizes)
-		return REFTABLE_OUT_OF_MEMORY_ERROR;
-
-	seg = suggest_compaction_segment(sizes, st->merged->tables_len,
-					 st->opts.auto_compaction_factor);
-	reftable_free(sizes);
-
-	if (segment_size(&seg) > 0)
+	if (required)
 		return stack_compact_range(st, seg.start, seg.end - 1,
 					   NULL, STACK_COMPACT_RANGE_BEST_EFFORT);
 
diff --git a/remote-curl.c b/remote-curl.c
index 69f9194..92e40bb 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -876,6 +876,7 @@ static int probe_rpc(struct rpc_state *rpc, struct slot_results *results)
 
 	headers = curl_slist_append(headers, rpc->hdr_content_type);
 	headers = curl_slist_append(headers, rpc->hdr_accept);
+	headers = http_append_auth_header(&http_auth, headers);
 
 	curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0L);
 	curl_easy_setopt(slot->curl, CURLOPT_POST, 1L);
diff --git a/remote.c b/remote.c
index df9675c..7ca2a65 100644
--- a/remote.c
+++ b/remote.c
@@ -29,6 +29,12 @@
 
 enum map_direction { FROM_SRC, FROM_DST };
 
+enum {
+	ENABLE_ADVICE_PULL       = (1 << 0),
+	ENABLE_ADVICE_PUSH       = (1 << 1),
+	ENABLE_ADVICE_DIVERGENCE = (1 << 2),
+};
+
 struct counted_string {
 	size_t len;
 	const char *s;
@@ -272,6 +278,7 @@ static void branch_release(struct branch *branch)
 	free((char *)branch->refname);
 	free(branch->remote_name);
 	free(branch->pushremote_name);
+	free(branch->push_tracking_ref);
 	merge_clear(branch);
 }
 
@@ -1381,12 +1388,7 @@ static struct ref **tail_ref(struct ref **head)
 	return tail;
 }
 
-struct tips {
-	struct commit **tip;
-	size_t nr, alloc;
-};
-
-static void add_to_tips(struct tips *tips, const struct object_id *oid)
+static void add_to_tips(struct commit_stack *tips, const struct object_id *oid)
 {
 	struct commit *commit;
 
@@ -1396,8 +1398,7 @@ static void add_to_tips(struct tips *tips, const struct object_id *oid)
 	if (!commit || (commit->object.flags & TMP_MARK))
 		return;
 	commit->object.flags |= TMP_MARK;
-	ALLOC_GROW(tips->tip, tips->nr + 1, tips->alloc);
-	tips->tip[tips->nr++] = commit;
+	commit_stack_push(tips, commit);
 }
 
 static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***dst_tail)
@@ -1406,13 +1407,12 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
 	struct string_list src_tag = STRING_LIST_INIT_NODUP;
 	struct string_list_item *item;
 	struct ref *ref;
-	struct tips sent_tips;
+	struct commit_stack sent_tips = COMMIT_STACK_INIT;
 
 	/*
 	 * Collect everything we know they would have at the end of
 	 * this push, and collect all tags they have.
 	 */
-	memset(&sent_tips, 0, sizeof(sent_tips));
 	for (ref = *dst; ref; ref = ref->next) {
 		if (ref->peer_ref &&
 		    !is_null_oid(&ref->peer_ref->new_oid))
@@ -1422,7 +1422,7 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
 		if (starts_with(ref->name, "refs/tags/"))
 			string_list_append(&dst_tag, ref->name);
 	}
-	clear_commit_marks_many(sent_tips.nr, sent_tips.tip, TMP_MARK);
+	clear_commit_marks_many(sent_tips.nr, sent_tips.items, TMP_MARK);
 
 	string_list_sort(&dst_tag);
 
@@ -1450,9 +1450,7 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
 	if (sent_tips.nr) {
 		const int reachable_flag = 1;
 		struct commit_list *found_commits;
-		struct commit **src_commits;
-		size_t nr_src_commits = 0, alloc_src_commits = 16;
-		ALLOC_ARRAY(src_commits, alloc_src_commits);
+		struct commit_stack src_commits = COMMIT_STACK_INIT;
 
 		for_each_string_list_item(item, &src_tag) {
 			struct ref *ref = item->util;
@@ -1467,12 +1465,13 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
 				/* not pushing a commit, which is not an error */
 				continue;
 
-			ALLOC_GROW(src_commits, nr_src_commits + 1, alloc_src_commits);
-			src_commits[nr_src_commits++] = commit;
+			commit_stack_push(&src_commits, commit);
 		}
 
-		found_commits = get_reachable_subset(sent_tips.tip, sent_tips.nr,
-						     src_commits, nr_src_commits,
+		found_commits = get_reachable_subset(sent_tips.items,
+						     sent_tips.nr,
+						     src_commits.items,
+						     src_commits.nr,
 						     reachable_flag);
 
 		for_each_string_list_item(item, &src_tag) {
@@ -1502,13 +1501,14 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
 			dst_ref->peer_ref = copy_ref(ref);
 		}
 
-		clear_commit_marks_many(nr_src_commits, src_commits, reachable_flag);
-		free(src_commits);
-		free_commit_list(found_commits);
+		clear_commit_marks_many(src_commits.nr, src_commits.items,
+					reachable_flag);
+		commit_stack_clear(&src_commits);
+		commit_list_free(found_commits);
 	}
 
 	string_list_clear(&src_tag, 0);
-	free(sent_tips.tip);
+	commit_stack_clear(&sent_tips);
 }
 
 struct ref *find_ref_by_name(const struct ref *list, const char *name)
@@ -1838,7 +1838,7 @@ int branch_merge_matches(struct branch *branch,
 }
 
 __attribute__((format (printf,2,3)))
-static const char *error_buf(struct strbuf *err, const char *fmt, ...)
+static char *error_buf(struct strbuf *err, const char *fmt, ...)
 {
 	if (err) {
 		va_list ap;
@@ -1876,9 +1876,9 @@ const char *branch_get_upstream(struct branch *branch, struct strbuf *err)
 	return branch->merge[0]->dst;
 }
 
-static const char *tracking_for_push_dest(struct remote *remote,
-					  const char *refname,
-					  struct strbuf *err)
+static char *tracking_for_push_dest(struct remote *remote,
+				    const char *refname,
+				    struct strbuf *err)
 {
 	char *ret;
 
@@ -1890,8 +1890,8 @@ static const char *tracking_for_push_dest(struct remote *remote,
 	return ret;
 }
 
-static const char *branch_get_push_1(struct repository *repo,
-				     struct branch *branch, struct strbuf *err)
+static char *branch_get_push_1(struct repository *repo,
+			       struct branch *branch, struct strbuf *err)
 {
 	struct remote_state *remote_state = repo->remote_state;
 	struct remote *remote;
@@ -1906,7 +1906,7 @@ static const char *branch_get_push_1(struct repository *repo,
 
 	if (remote->push.nr) {
 		char *dst;
-		const char *ret;
+		char *ret;
 
 		dst = apply_refspecs(&remote->push, branch->refname);
 		if (!dst)
@@ -1931,12 +1931,13 @@ static const char *branch_get_push_1(struct repository *repo,
 		return tracking_for_push_dest(remote, branch->refname, err);
 
 	case PUSH_DEFAULT_UPSTREAM:
-		return branch_get_upstream(branch, err);
+		return xstrdup_or_null(branch_get_upstream(branch, err));
 
 	case PUSH_DEFAULT_UNSPECIFIED:
 	case PUSH_DEFAULT_SIMPLE:
 		{
-			const char *up, *cur;
+			const char *up;
+			char *cur;
 
 			up = branch_get_upstream(branch, err);
 			if (!up)
@@ -1944,9 +1945,11 @@ static const char *branch_get_push_1(struct repository *repo,
 			cur = tracking_for_push_dest(remote, branch->refname, err);
 			if (!cur)
 				return NULL;
-			if (strcmp(cur, up))
+			if (strcmp(cur, up)) {
+				free(cur);
 				return error_buf(err,
 						 _("cannot resolve 'simple' push to a single destination"));
+			}
 			return cur;
 		}
 	}
@@ -2237,43 +2240,49 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
-			 enum ahead_behind_flags abf,
-			 int show_divergence_advice)
+static char *resolve_compare_branch(struct branch *branch, const char *name)
 {
-	int ours, theirs, sti;
-	const char *full_base;
-	char *base;
-	int upstream_is_gone = 0;
+	const char *resolved = NULL;
 
-	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
-	if (sti < 0) {
-		if (!full_base)
-			return 0;
-		upstream_is_gone = 1;
+	if (!branch || !name)
+		return NULL;
+
+	if (!strcasecmp(name, "@{upstream}")) {
+		resolved = branch_get_upstream(branch, NULL);
+	} else if (!strcasecmp(name, "@{push}")) {
+		resolved = branch_get_push(branch, NULL);
+	} else {
+		warning(_("ignoring value '%s' for status.compareBranches, "
+			  "only @{upstream} and @{push} are supported"),
+			name);
+		return NULL;
 	}
 
-	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
-					    full_base, 0);
-	if (upstream_is_gone) {
-		strbuf_addf(sb,
-			_("Your branch is based on '%s', but the upstream is gone.\n"),
-			base);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
-	} else if (!sti) {
+	if (resolved)
+		return xstrdup(resolved);
+	return NULL;
+}
+
+static void format_branch_comparison(struct strbuf *sb,
+				     bool up_to_date,
+				     int ours, int theirs,
+				     const char *branch_name,
+				     enum ahead_behind_flags abf,
+				     unsigned flags)
+{
+	bool use_push_advice = (flags & ENABLE_ADVICE_PUSH);
+	bool use_pull_advice = (flags & ENABLE_ADVICE_PULL);
+	bool use_divergence_advice = (flags & ENABLE_ADVICE_DIVERGENCE);
+
+	if (up_to_date) {
 		strbuf_addf(sb,
 			_("Your branch is up to date with '%s'.\n"),
-			base);
+			branch_name);
 	} else if (abf == AHEAD_BEHIND_QUICK) {
 		strbuf_addf(sb,
 			    _("Your branch and '%s' refer to different commits.\n"),
-			    base);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+			    branch_name);
+		if (use_push_advice && advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");
 	} else if (!theirs) {
@@ -2281,8 +2290,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			Q_("Your branch is ahead of '%s' by %d commit.\n",
 			   "Your branch is ahead of '%s' by %d commits.\n",
 			   ours),
-			base, ours);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+			branch_name, ours);
+		if (use_push_advice && advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git push\" to publish your local commits)\n"));
 	} else if (!ours) {
@@ -2292,8 +2301,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			   "Your branch is behind '%s' by %d commits, "
 			       "and can be fast-forwarded.\n",
 			   theirs),
-			base, theirs);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+			branch_name, theirs);
+		if (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" to update your local branch)\n"));
 	} else {
@@ -2305,31 +2314,121 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			       "and have %d and %d different commits each, "
 			       "respectively.\n",
 			   ours + theirs),
-			base, ours, theirs);
-		if (show_divergence_advice &&
-		    advice_enabled(ADVICE_STATUS_HINTS))
+			branch_name, ours, theirs);
+		if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
 	}
-	free(base);
-	return 1;
 }
 
-static int one_local_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-			 int flag UNUSED,
-			 void *cb_data)
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+			 enum ahead_behind_flags abf,
+			 int show_divergence_advice)
+{
+	char *compare_branches = NULL;
+	struct string_list branches = STRING_LIST_INIT_DUP;
+	struct strset processed_refs = STRSET_INIT;
+	int reported = 0;
+	size_t i;
+	const char *upstream_ref;
+	const char *push_ref;
+
+	repo_config_get_string(the_repository, "status.comparebranches",
+			       &compare_branches);
+
+	if (compare_branches) {
+		string_list_split(&branches, compare_branches, " ", -1);
+		string_list_remove_empty_items(&branches, 0);
+	} else {
+		string_list_append(&branches, "@{upstream}");
+	}
+
+	upstream_ref = branch_get_upstream(branch, NULL);
+	push_ref = branch_get_push(branch, NULL);
+
+	for (i = 0; i < branches.nr; i++) {
+		char *full_ref;
+		char *short_ref;
+		int ours, theirs, cmp;
+		int is_upstream, is_push;
+		unsigned flags = 0;
+
+		full_ref = resolve_compare_branch(branch,
+						  branches.items[i].string);
+		if (!full_ref)
+			continue;
+
+		if (!strset_add(&processed_refs, full_ref)) {
+			free(full_ref);
+			continue;
+		}
+
+		short_ref = refs_shorten_unambiguous_ref(
+			get_main_ref_store(the_repository), full_ref, 0);
+
+		is_upstream = upstream_ref && !strcmp(full_ref, upstream_ref);
+		is_push = push_ref && !strcmp(full_ref, push_ref);
+
+		if (is_upstream && (!push_ref || !strcmp(upstream_ref, push_ref)))
+			is_push = 1;
+
+		cmp = stat_branch_pair(branch->refname, full_ref,
+				       &ours, &theirs, abf);
+
+		if (cmp < 0) {
+			if (is_upstream) {
+				strbuf_addf(sb,
+					_("Your branch is based on '%s', but the upstream is gone.\n"),
+					short_ref);
+				if (advice_enabled(ADVICE_STATUS_HINTS))
+					strbuf_addstr(sb,
+						_("  (use \"git branch --unset-upstream\" to fixup)\n"));
+				reported = 1;
+			}
+			free(full_ref);
+			free(short_ref);
+			continue;
+		}
+
+		if (reported)
+			strbuf_addstr(sb, "\n");
+
+		if (is_upstream)
+			flags |= ENABLE_ADVICE_PULL;
+		if (is_push)
+			flags |= ENABLE_ADVICE_PUSH;
+		if (show_divergence_advice && is_upstream)
+			flags |= ENABLE_ADVICE_DIVERGENCE;
+		format_branch_comparison(sb, !cmp, ours, theirs, short_ref,
+					 abf, flags);
+		reported = 1;
+
+		free(full_ref);
+		free(short_ref);
+	}
+
+	string_list_clear(&branches, 0);
+	strset_clear(&processed_refs);
+	free(compare_branches);
+	return reported;
+}
+
+static int one_local_ref(const struct reference *ref, void *cb_data)
 {
 	struct ref ***local_tail = cb_data;
-	struct ref *ref;
+	struct ref *local_ref;
 
 	/* we already know it starts with refs/ to get here */
-	if (check_refname_format(refname + 5, 0))
+	if (check_refname_format(ref->name + 5, 0))
 		return 0;
 
-	ref = alloc_ref(refname);
-	oidcpy(&ref->new_oid, oid);
-	**local_tail = ref;
-	*local_tail = &ref->next;
+	local_ref = alloc_ref(ref->name);
+	oidcpy(&local_ref->new_oid, ref->oid);
+	**local_tail = local_ref;
+	*local_tail = &local_ref->next;
 	return 0;
 }
 
@@ -2402,15 +2501,14 @@ struct stale_heads_info {
 	struct refspec *rs;
 };
 
-static int get_stale_heads_cb(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-			      int flags, void *cb_data)
+static int get_stale_heads_cb(const struct reference *ref, void *cb_data)
 {
 	struct stale_heads_info *info = cb_data;
 	struct string_list matches = STRING_LIST_INIT_DUP;
 	struct refspec_item query;
 	int i, stale = 1;
 	memset(&query, 0, sizeof(struct refspec_item));
-	query.dst = (char *)refname;
+	query.dst = (char *)ref->name;
 
 	refspec_find_all_matches(info->rs, &query, &matches);
 	if (matches.nr == 0)
@@ -2423,7 +2521,7 @@ static int get_stale_heads_cb(const char *refname, const char *referent UNUSED,
 	 * overlapping refspecs, we need to go over all of the
 	 * matching refs.
 	 */
-	if (flags & REF_ISSYMREF)
+	if (ref->flags & REF_ISSYMREF)
 		goto clean_exit;
 
 	for (i = 0; stale && i < matches.nr; i++)
@@ -2431,8 +2529,8 @@ static int get_stale_heads_cb(const char *refname, const char *referent UNUSED,
 			stale = 0;
 
 	if (stale) {
-		struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
-		oidcpy(&ref->new_oid, oid);
+		struct ref *linked_ref = make_linked_ref(ref->name, &info->stale_refs_tail);
+		oidcpy(&linked_ref->new_oid, ref->oid);
 	}
 
 clean_exit:
@@ -2547,36 +2645,9 @@ static int remote_tracking(struct remote *remote, const char *refname,
 	return 0;
 }
 
-/*
- * The struct "reflog_commit_array" and related helper functions
- * are used for collecting commits into an array during reflog
- * traversals in "check_and_collect_until()".
- */
-struct reflog_commit_array {
-	struct commit **item;
-	size_t nr, alloc;
-};
-
-#define REFLOG_COMMIT_ARRAY_INIT { 0 }
-
-/* Append a commit to the array. */
-static void append_commit(struct reflog_commit_array *arr,
-			  struct commit *commit)
-{
-	ALLOC_GROW(arr->item, arr->nr + 1, arr->alloc);
-	arr->item[arr->nr++] = commit;
-}
-
-/* Free and reset the array. */
-static void free_commit_array(struct reflog_commit_array *arr)
-{
-	FREE_AND_NULL(arr->item);
-	arr->nr = arr->alloc = 0;
-}
-
 struct check_and_collect_until_cb_data {
 	struct commit *remote_commit;
-	struct reflog_commit_array *local_commits;
+	struct commit_stack *local_commits;
 	timestamp_t remote_reflog_timestamp;
 };
 
@@ -2608,7 +2679,7 @@ static int check_and_collect_until(const char *refname UNUSED,
 		return 1;
 
 	if ((commit = lookup_commit_reference(the_repository, n_oid)))
-		append_commit(cb->local_commits, commit);
+		commit_stack_push(cb->local_commits, commit);
 
 	/*
 	 * If the reflog entry timestamp is older than the remote ref's
@@ -2636,7 +2707,7 @@ static int is_reachable_in_reflog(const char *local, const struct ref *remote)
 	struct commit *commit;
 	struct commit **chunk;
 	struct check_and_collect_until_cb_data cb;
-	struct reflog_commit_array arr = REFLOG_COMMIT_ARRAY_INIT;
+	struct commit_stack arr = COMMIT_STACK_INIT;
 	size_t size = 0;
 	int ret = 0;
 
@@ -2667,8 +2738,8 @@ static int is_reachable_in_reflog(const char *local, const struct ref *remote)
 	 * Check if the remote commit is reachable from any
 	 * of the commits in the collected array, in batches.
 	 */
-	for (chunk = arr.item; chunk < arr.item + arr.nr; chunk += size) {
-		size = arr.item + arr.nr - chunk;
+	for (chunk = arr.items; chunk < arr.items + arr.nr; chunk += size) {
+		size = arr.items + arr.nr - chunk;
 		if (MERGE_BASES_BATCH_SIZE < size)
 			size = MERGE_BASES_BATCH_SIZE;
 
@@ -2677,7 +2748,7 @@ static int is_reachable_in_reflog(const char *local, const struct ref *remote)
 	}
 
 cleanup_return:
-	free_commit_array(&arr);
+	commit_stack_clear(&arr);
 	return ret;
 }
 
diff --git a/remote.h b/remote.h
index 0ca399e..fc05294 100644
--- a/remote.h
+++ b/remote.h
@@ -331,7 +331,7 @@ struct branch {
 
 	int merge_alloc;
 
-	const char *push_tracking_ref;
+	char *push_tracking_ref;
 };
 
 struct branch *branch_get(const char *name);
diff --git a/repack-geometry.c b/repack-geometry.c
index b3e32cd..7cebd0c 100644
--- a/repack-geometry.c
+++ b/repack-geometry.c
@@ -66,45 +66,54 @@ void pack_geometry_init(struct pack_geometry *geometry,
 		if (p->is_cruft)
 			continue;
 
-		ALLOC_GROW(geometry->pack,
-			   geometry->pack_nr + 1,
-			   geometry->pack_alloc);
+		if (p->pack_promisor) {
+			ALLOC_GROW(geometry->promisor_pack,
+				   geometry->promisor_pack_nr + 1,
+				   geometry->promisor_pack_alloc);
 
-		geometry->pack[geometry->pack_nr] = p;
-		geometry->pack_nr++;
+			geometry->promisor_pack[geometry->promisor_pack_nr] = p;
+			geometry->promisor_pack_nr++;
+		} else {
+			ALLOC_GROW(geometry->pack,
+				   geometry->pack_nr + 1,
+				   geometry->pack_alloc);
+
+			geometry->pack[geometry->pack_nr] = p;
+			geometry->pack_nr++;
+		}
 	}
 
 	QSORT(geometry->pack, geometry->pack_nr, pack_geometry_cmp);
+	QSORT(geometry->promisor_pack, geometry->promisor_pack_nr, pack_geometry_cmp);
 	strbuf_release(&buf);
 }
 
-void pack_geometry_split(struct pack_geometry *geometry)
+static uint32_t compute_pack_geometry_split(struct packed_git **pack, size_t pack_nr,
+					    int split_factor)
 {
 	uint32_t i;
 	uint32_t split;
 	off_t total_size = 0;
 
-	if (!geometry->pack_nr) {
-		geometry->split = geometry->pack_nr;
-		return;
-	}
+	if (!pack_nr)
+		return 0;
 
 	/*
 	 * First, count the number of packs (in descending order of size) which
 	 * already form a geometric progression.
 	 */
-	for (i = geometry->pack_nr - 1; i > 0; i--) {
-		struct packed_git *ours = geometry->pack[i];
-		struct packed_git *prev = geometry->pack[i - 1];
+	for (i = pack_nr - 1; i > 0; i--) {
+		struct packed_git *ours = pack[i];
+		struct packed_git *prev = pack[i - 1];
 
-		if (unsigned_mult_overflows(geometry->split_factor,
+		if (unsigned_mult_overflows(split_factor,
 					    pack_geometry_weight(prev)))
 			die(_("pack %s too large to consider in geometric "
 			      "progression"),
 			    prev->pack_name);
 
 		if (pack_geometry_weight(ours) <
-		    geometry->split_factor * pack_geometry_weight(prev))
+		    split_factor * pack_geometry_weight(prev))
 			break;
 	}
 
@@ -130,21 +139,19 @@ void pack_geometry_split(struct pack_geometry *geometry)
 	 * the geometric progression.
 	 */
 	for (i = 0; i < split; i++) {
-		struct packed_git *p = geometry->pack[i];
+		struct packed_git *p = pack[i];
 
 		if (unsigned_add_overflows(total_size, pack_geometry_weight(p)))
 			die(_("pack %s too large to roll up"), p->pack_name);
 		total_size += pack_geometry_weight(p);
 	}
-	for (i = split; i < geometry->pack_nr; i++) {
-		struct packed_git *ours = geometry->pack[i];
+	for (i = split; i < pack_nr; i++) {
+		struct packed_git *ours = pack[i];
 
-		if (unsigned_mult_overflows(geometry->split_factor,
-					    total_size))
+		if (unsigned_mult_overflows(split_factor, total_size))
 			die(_("pack %s too large to roll up"), ours->pack_name);
 
-		if (pack_geometry_weight(ours) <
-		    geometry->split_factor * total_size) {
+		if (pack_geometry_weight(ours) < split_factor * total_size) {
 			if (unsigned_add_overflows(total_size,
 						   pack_geometry_weight(ours)))
 				die(_("pack %s too large to roll up"),
@@ -156,7 +163,16 @@ void pack_geometry_split(struct pack_geometry *geometry)
 			break;
 	}
 
-	geometry->split = split;
+	return split;
+}
+
+void pack_geometry_split(struct pack_geometry *geometry)
+{
+	geometry->split = compute_pack_geometry_split(geometry->pack, geometry->pack_nr,
+						      geometry->split_factor);
+	geometry->promisor_split = compute_pack_geometry_split(geometry->promisor_pack,
+							       geometry->promisor_pack_nr,
+							       geometry->split_factor);
 }
 
 struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry)
@@ -194,17 +210,18 @@ struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry)
 	return NULL;
 }
 
-void pack_geometry_remove_redundant(struct pack_geometry *geometry,
-				    struct string_list *names,
-				    struct existing_packs *existing,
-				    const char *packdir)
+static void remove_redundant_packs(struct packed_git **pack,
+				   uint32_t pack_nr,
+				   struct string_list *names,
+				   struct existing_packs *existing,
+				   const char *packdir)
 {
 	const struct git_hash_algo *algop = existing->repo->hash_algo;
 	struct strbuf buf = STRBUF_INIT;
 	uint32_t i;
 
-	for (i = 0; i < geometry->split; i++) {
-		struct packed_git *p = geometry->pack[i];
+	for (i = 0; i < pack_nr; i++) {
+		struct packed_git *p = pack[i];
 		if (string_list_has_string(names, hash_to_hex_algop(p->hash,
 								    algop)))
 			continue;
@@ -223,10 +240,22 @@ void pack_geometry_remove_redundant(struct pack_geometry *geometry,
 	strbuf_release(&buf);
 }
 
+void pack_geometry_remove_redundant(struct pack_geometry *geometry,
+				    struct string_list *names,
+				    struct existing_packs *existing,
+				    const char *packdir)
+{
+	remove_redundant_packs(geometry->pack, geometry->split,
+			       names, existing, packdir);
+	remove_redundant_packs(geometry->promisor_pack, geometry->promisor_split,
+			       names, existing, packdir);
+}
+
 void pack_geometry_release(struct pack_geometry *geometry)
 {
 	if (!geometry)
 		return;
 
 	free(geometry->pack);
+	free(geometry->promisor_pack);
 }
diff --git a/repack-midx.c b/repack-midx.c
index 6f6202c..0682b80 100644
--- a/repack-midx.c
+++ b/repack-midx.c
@@ -16,25 +16,23 @@ struct midx_snapshot_ref_data {
 	int preferred;
 };
 
-static int midx_snapshot_ref_one(const char *refname UNUSED,
-				 const char *referent UNUSED,
-				 const struct object_id *oid,
-				 int flag UNUSED, void *_data)
+static int midx_snapshot_ref_one(const struct reference *ref, void *_data)
 {
 	struct midx_snapshot_ref_data *data = _data;
+	const struct object_id *maybe_peeled = ref->oid;
 	struct object_id peeled;
 
-	if (!peel_iterated_oid(data->repo, oid, &peeled))
-		oid = &peeled;
+	if (!reference_get_peeled_oid(data->repo, ref, &peeled))
+		maybe_peeled = &peeled;
 
-	if (oidset_insert(&data->seen, oid))
+	if (oidset_insert(&data->seen, maybe_peeled))
 		return 0; /* already seen */
 
-	if (odb_read_object_info(data->repo->objects, oid, NULL) != OBJ_COMMIT)
+	if (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) != OBJ_COMMIT)
 		return 0;
 
 	fprintf(data->f->fp, "%s%s\n", data->preferred ? "+" : "",
-		oid_to_hex(oid));
+		oid_to_hex(maybe_peeled));
 
 	return 0;
 }
@@ -42,7 +40,6 @@ static int midx_snapshot_ref_one(const char *refname UNUSED,
 void midx_snapshot_refs(struct repository *repo, struct tempfile *f)
 {
 	struct midx_snapshot_ref_data data;
-	const struct string_list *preferred = bitmap_preferred_tips(repo);
 
 	data.repo = repo;
 	data.f = f;
@@ -53,16 +50,9 @@ void midx_snapshot_refs(struct repository *repo, struct tempfile *f)
 		 die(_("could not open tempfile %s for writing"),
 		     get_tempfile_path(f));
 
-	if (preferred) {
-		struct string_list_item *item;
-
-		data.preferred = 1;
-		for_each_string_list_item(item, preferred)
-			refs_for_each_ref_in(get_main_ref_store(repo),
-					     item->string,
-					     midx_snapshot_ref_one, &data);
-		data.preferred = 0;
-	}
+	data.preferred = 1;
+	for_each_preferred_bitmap_tip(repo, midx_snapshot_ref_one, &data);
+	data.preferred = 0;
 
 	refs_for_each_ref(get_main_ref_store(repo),
 			  midx_snapshot_ref_one, &data);
diff --git a/repack-promisor.c b/repack-promisor.c
index ee6e066..90318ce 100644
--- a/repack-promisor.c
+++ b/repack-promisor.c
@@ -17,8 +17,8 @@ struct write_oid_context {
  * necessary.
  */
 static int write_oid(const struct object_id *oid,
-		     struct packed_git *pack UNUSED,
-		     uint32_t pos UNUSED, void *data)
+		     struct object_info *oi UNUSED,
+		     void *data)
 {
 	struct write_oid_context *ctx = data;
 	struct child_process *cmd = ctx->cmd;
@@ -34,39 +34,17 @@ static int write_oid(const struct object_id *oid,
 	return 0;
 }
 
-void repack_promisor_objects(struct repository *repo,
-			     const struct pack_objects_args *args,
-			     struct string_list *names, const char *packtmp)
+static void finish_repacking_promisor_objects(struct repository *repo,
+					      struct child_process *cmd,
+					      struct string_list *names,
+					      const char *packtmp)
 {
-	struct write_oid_context ctx;
-	struct child_process cmd = CHILD_PROCESS_INIT;
-	FILE *out;
 	struct strbuf line = STRBUF_INIT;
+	FILE *out;
 
-	prepare_pack_objects(&cmd, args, packtmp);
-	cmd.in = -1;
+	close(cmd->in);
 
-	/*
-	 * NEEDSWORK: Giving pack-objects only the OIDs without any ordering
-	 * hints may result in suboptimal deltas in the resulting pack. See if
-	 * the OIDs can be sent with fake paths such that pack-objects can use a
-	 * {type -> existing pack order} ordering when computing deltas instead
-	 * of a {type -> size} ordering, which may produce better deltas.
-	 */
-	ctx.cmd = &cmd;
-	ctx.algop = repo->hash_algo;
-	for_each_packed_object(repo, write_oid, &ctx,
-			       FOR_EACH_OBJECT_PROMISOR_ONLY);
-
-	if (cmd.in == -1) {
-		/* No packed objects; cmd was never started */
-		child_process_clear(&cmd);
-		return;
-	}
-
-	close(cmd.in);
-
-	out = xfdopen(cmd.out, "r");
+	out = xfdopen(cmd->out, "r");
 	while (strbuf_getline_lf(&line, out) != EOF) {
 		struct string_list_item *item;
 		char *promisor_name;
@@ -96,7 +74,66 @@ void repack_promisor_objects(struct repository *repo,
 	}
 
 	fclose(out);
-	if (finish_command(&cmd))
+	if (finish_command(cmd))
 		die(_("could not finish pack-objects to repack promisor objects"));
 	strbuf_release(&line);
 }
+
+void repack_promisor_objects(struct repository *repo,
+			     const struct pack_objects_args *args,
+			     struct string_list *names, const char *packtmp)
+{
+	struct write_oid_context ctx;
+	struct child_process cmd = CHILD_PROCESS_INIT;
+
+	prepare_pack_objects(&cmd, args, packtmp);
+	cmd.in = -1;
+
+	/*
+	 * NEEDSWORK: Giving pack-objects only the OIDs without any ordering
+	 * hints may result in suboptimal deltas in the resulting pack. See if
+	 * the OIDs can be sent with fake paths such that pack-objects can use a
+	 * {type -> existing pack order} ordering when computing deltas instead
+	 * of a {type -> size} ordering, which may produce better deltas.
+	 */
+	ctx.cmd = &cmd;
+	ctx.algop = repo->hash_algo;
+	odb_for_each_object(repo->objects, NULL, write_oid, &ctx,
+			    ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);
+
+	if (cmd.in == -1) {
+		/* No packed objects; cmd was never started */
+		child_process_clear(&cmd);
+		return;
+	}
+
+	finish_repacking_promisor_objects(repo, &cmd, names, packtmp);
+}
+
+void pack_geometry_repack_promisors(struct repository *repo,
+				    const struct pack_objects_args *args,
+				    const struct pack_geometry *geometry,
+				    struct string_list *names,
+				    const char *packtmp)
+{
+	struct child_process cmd = CHILD_PROCESS_INIT;
+	FILE *in;
+
+	if (!geometry->promisor_split)
+		return;
+
+	prepare_pack_objects(&cmd, args, packtmp);
+	strvec_push(&cmd.args, "--stdin-packs");
+	cmd.in = -1;
+	if (start_command(&cmd))
+		die(_("could not start pack-objects to repack promisor packs"));
+
+	in = xfdopen(cmd.in, "w");
+	for (size_t i = 0; i < geometry->promisor_split; i++)
+		fprintf(in, "%s\n", pack_basename(geometry->promisor_pack[i]));
+	for (size_t i = geometry->promisor_split; i < geometry->promisor_pack_nr; i++)
+		fprintf(in, "^%s\n", pack_basename(geometry->promisor_pack[i]));
+	fclose(in);
+
+	finish_repacking_promisor_objects(repo, &cmd, names, packtmp);
+}
diff --git a/repack.h b/repack.h
index 3a688a1..bc9f2e1 100644
--- a/repack.h
+++ b/repack.h
@@ -103,9 +103,19 @@ struct pack_geometry {
 	uint32_t pack_nr, pack_alloc;
 	uint32_t split;
 
+	struct packed_git **promisor_pack;
+	uint32_t promisor_pack_nr, promisor_pack_alloc;
+	uint32_t promisor_split;
+
 	int split_factor;
 };
 
+void pack_geometry_repack_promisors(struct repository *repo,
+				    const struct pack_objects_args *args,
+				    const struct pack_geometry *geometry,
+				    struct string_list *names,
+				    const char *packtmp);
+
 void pack_geometry_init(struct pack_geometry *geometry,
 			struct existing_packs *existing,
 			const struct pack_objects_args *args);
diff --git a/replace-object.c b/replace-object.c
index 3eae051..03d0f1f 100644
--- a/replace-object.c
+++ b/replace-object.c
@@ -8,31 +8,27 @@
 #include "repository.h"
 #include "commit.h"
 
-static int register_replace_ref(const char *refname,
-				const char *referent UNUSED,
-				const struct object_id *oid,
-				int flag UNUSED,
-				void *cb_data)
+static int register_replace_ref(const struct reference *ref, void *cb_data)
 {
 	struct repository *r = cb_data;
 
 	/* Get sha1 from refname */
-	const char *slash = strrchr(refname, '/');
-	const char *hash = slash ? slash + 1 : refname;
+	const char *slash = strrchr(ref->name, '/');
+	const char *hash = slash ? slash + 1 : ref->name;
 	struct replace_object *repl_obj = xmalloc(sizeof(*repl_obj));
 
 	if (get_oid_hex_algop(hash, &repl_obj->original.oid, r->hash_algo)) {
 		free(repl_obj);
-		warning(_("bad replace ref name: %s"), refname);
+		warning(_("bad replace ref name: %s"), ref->name);
 		return 0;
 	}
 
 	/* Copy sha1 from the read ref */
-	oidcpy(&repl_obj->replacement, oid);
+	oidcpy(&repl_obj->replacement, ref->oid);
 
 	/* Register new object */
 	if (oidmap_put(&r->objects->replace_map, repl_obj))
-		die(_("duplicate replace ref: %s"), refname);
+		die(_("duplicate replace ref: %s"), ref->name);
 
 	return 0;
 }
diff --git a/replay.c b/replay.c
new file mode 100644
index 0000000..a63f671
--- /dev/null
+++ b/replay.c
@@ -0,0 +1,381 @@
+#define USE_THE_REPOSITORY_VARIABLE
+
+#include "git-compat-util.h"
+#include "environment.h"
+#include "hex.h"
+#include "merge-ort.h"
+#include "object-name.h"
+#include "refs.h"
+#include "replay.h"
+#include "revision.h"
+#include "strmap.h"
+#include "tree.h"
+
+/*
+ * We technically need USE_THE_REPOSITORY_VARIABLE for DEFAULT_ABBREV, but
+ * do not want to use the_repository.
+ */
+#define the_repository DO_NOT_USE_THE_REPOSITORY
+
+static const char *short_commit_name(struct repository *repo,
+				     struct commit *commit)
+{
+	return repo_find_unique_abbrev(repo, &commit->object.oid,
+				       DEFAULT_ABBREV);
+}
+
+static struct commit *peel_committish(struct repository *repo,
+				      const char *name,
+				      const char *mode)
+{
+	struct object *obj;
+	struct object_id oid;
+
+	if (repo_get_oid(repo, name, &oid))
+		die(_("'%s' is not a valid commit-ish for %s"), name, mode);
+	obj = parse_object_or_die(repo, &oid, name);
+	return (struct commit *)repo_peel_to_type(repo, name, 0, obj,
+						  OBJ_COMMIT);
+}
+
+static char *get_author(const char *message)
+{
+	size_t len;
+	const char *a;
+
+	a = find_commit_header(message, "author", &len);
+	if (a)
+		return xmemdupz(a, len);
+
+	return NULL;
+}
+
+static struct commit *create_commit(struct repository *repo,
+				    struct tree *tree,
+				    struct commit *based_on,
+				    struct commit *parent)
+{
+	struct object_id ret;
+	struct object *obj = NULL;
+	struct commit_list *parents = NULL;
+	char *author;
+	char *sign_commit = NULL; /* FIXME: cli users might want to sign again */
+	struct commit_extra_header *extra = NULL;
+	struct strbuf msg = STRBUF_INIT;
+	const char *out_enc = get_commit_output_encoding();
+	const char *message = repo_logmsg_reencode(repo, based_on,
+						   NULL, out_enc);
+	const char *orig_message = NULL;
+	const char *exclude_gpgsig[] = { "gpgsig", "gpgsig-sha256", NULL };
+
+	commit_list_insert(parent, &parents);
+	extra = read_commit_extra_headers(based_on, exclude_gpgsig);
+	find_commit_subject(message, &orig_message);
+	strbuf_addstr(&msg, orig_message);
+	author = get_author(message);
+	reset_ident_date();
+	if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,
+				 &ret, author, NULL, sign_commit, extra)) {
+		error(_("failed to write commit object"));
+		goto out;
+	}
+
+	obj = parse_object(repo, &ret);
+
+out:
+	repo_unuse_commit_buffer(repo, based_on, message);
+	free_commit_extra_headers(extra);
+	free_commit_list(parents);
+	strbuf_release(&msg);
+	free(author);
+	return (struct commit *)obj;
+}
+
+struct ref_info {
+	struct commit *onto;
+	struct strset positive_refs;
+	struct strset negative_refs;
+	size_t positive_refexprs;
+	size_t negative_refexprs;
+};
+
+static void get_ref_information(struct repository *repo,
+				struct rev_cmdline_info *cmd_info,
+				struct ref_info *ref_info)
+{
+	ref_info->onto = NULL;
+	strset_init(&ref_info->positive_refs);
+	strset_init(&ref_info->negative_refs);
+	ref_info->positive_refexprs = 0;
+	ref_info->negative_refexprs = 0;
+
+	/*
+	 * When the user specifies e.g.
+	 *   git replay origin/main..mybranch
+	 *   git replay ^origin/next mybranch1 mybranch2
+	 * we want to be able to determine where to replay the commits.  In
+	 * these examples, the branches are probably based on an old version
+	 * of either origin/main or origin/next, so we want to replay on the
+	 * newest version of that branch.  In contrast we would want to error
+	 * out if they ran
+	 *   git replay ^origin/master ^origin/next mybranch
+	 *   git replay mybranch~2..mybranch
+	 * the first of those because there's no unique base to choose, and
+	 * the second because they'd likely just be replaying commits on top
+	 * of the same commit and not making any difference.
+	 */
+	for (size_t i = 0; i < cmd_info->nr; i++) {
+		struct rev_cmdline_entry *e = cmd_info->rev + i;
+		struct object_id oid;
+		const char *refexpr = e->name;
+		char *fullname = NULL;
+		int can_uniquely_dwim = 1;
+
+		if (*refexpr == '^')
+			refexpr++;
+		if (repo_dwim_ref(repo, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1)
+			can_uniquely_dwim = 0;
+
+		if (e->flags & BOTTOM) {
+			if (can_uniquely_dwim)
+				strset_add(&ref_info->negative_refs, fullname);
+			if (!ref_info->negative_refexprs)
+				ref_info->onto = lookup_commit_reference_gently(repo,
+										&e->item->oid, 1);
+			ref_info->negative_refexprs++;
+		} else {
+			if (can_uniquely_dwim)
+				strset_add(&ref_info->positive_refs, fullname);
+			ref_info->positive_refexprs++;
+		}
+
+		free(fullname);
+	}
+}
+
+static void set_up_replay_mode(struct repository *repo,
+			       struct rev_cmdline_info *cmd_info,
+			       const char *onto_name,
+			       bool *detached_head,
+			       char **advance_name,
+			       struct commit **onto,
+			       struct strset **update_refs)
+{
+	struct ref_info rinfo;
+	int head_flags = 0;
+
+	refs_read_ref_full(get_main_ref_store(repo), "HEAD",
+			   RESOLVE_REF_NO_RECURSE, NULL, &head_flags);
+	*detached_head = !(head_flags & REF_ISSYMREF);
+
+	get_ref_information(repo, cmd_info, &rinfo);
+	if (!rinfo.positive_refexprs)
+		die(_("need some commits to replay"));
+
+	if (!onto_name == !*advance_name)
+		BUG("one and only one of onto_name and *advance_name must be given");
+
+	if (onto_name) {
+		*onto = peel_committish(repo, onto_name, "--onto");
+		if (rinfo.positive_refexprs <
+		    strset_get_size(&rinfo.positive_refs))
+			die(_("all positive revisions given must be references"));
+		*update_refs = xcalloc(1, sizeof(**update_refs));
+		**update_refs = rinfo.positive_refs;
+		memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
+	} else {
+		struct object_id oid;
+		char *fullname = NULL;
+
+		if (!*advance_name)
+			BUG("expected either onto_name or *advance_name in this function");
+
+		if (repo_dwim_ref(repo, *advance_name, strlen(*advance_name),
+			     &oid, &fullname, 0) == 1) {
+			free(*advance_name);
+			*advance_name = fullname;
+		} else {
+			die(_("argument to --advance must be a reference"));
+		}
+		*onto = peel_committish(repo, *advance_name, "--advance");
+		if (rinfo.positive_refexprs > 1)
+			die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
+	}
+	strset_clear(&rinfo.negative_refs);
+	strset_clear(&rinfo.positive_refs);
+}
+
+static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,
+				    struct commit *commit,
+				    struct commit *fallback)
+{
+	khint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);
+	if (pos == kh_end(replayed_commits))
+		return fallback;
+	return kh_value(replayed_commits, pos);
+}
+
+static struct commit *pick_regular_commit(struct repository *repo,
+					  struct commit *pickme,
+					  kh_oid_map_t *replayed_commits,
+					  struct commit *onto,
+					  struct merge_options *merge_opt,
+					  struct merge_result *result)
+{
+	struct commit *base, *replayed_base;
+	struct tree *pickme_tree, *base_tree, *replayed_base_tree;
+
+	base = pickme->parents->item;
+	replayed_base = mapped_commit(replayed_commits, base, onto);
+
+	replayed_base_tree = repo_get_commit_tree(repo, replayed_base);
+	pickme_tree = repo_get_commit_tree(repo, pickme);
+	base_tree = repo_get_commit_tree(repo, base);
+
+	merge_opt->branch1 = short_commit_name(repo, replayed_base);
+	merge_opt->branch2 = short_commit_name(repo, pickme);
+	merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
+
+	merge_incore_nonrecursive(merge_opt,
+				  base_tree,
+				  replayed_base_tree,
+				  pickme_tree,
+				  result);
+
+	free((char*)merge_opt->ancestor);
+	merge_opt->ancestor = NULL;
+	if (!result->clean)
+		return NULL;
+	/* Drop commits that become empty */
+	if (oideq(&replayed_base_tree->object.oid, &result->tree->object.oid) &&
+	    !oideq(&pickme_tree->object.oid, &base_tree->object.oid))
+		return replayed_base;
+	return create_commit(repo, result->tree, pickme, replayed_base);
+}
+
+void replay_result_release(struct replay_result *result)
+{
+	for (size_t i = 0; i < result->updates_nr; i++)
+		free(result->updates[i].refname);
+	free(result->updates);
+}
+
+static void replay_result_queue_update(struct replay_result *result,
+				       const char *refname,
+				       const struct object_id *old_oid,
+				       const struct object_id *new_oid)
+{
+	ALLOC_GROW(result->updates, result->updates_nr + 1, result->updates_alloc);
+	result->updates[result->updates_nr].refname = xstrdup(refname);
+	result->updates[result->updates_nr].old_oid = *old_oid;
+	result->updates[result->updates_nr].new_oid = *new_oid;
+	result->updates_nr++;
+}
+
+int replay_revisions(struct rev_info *revs,
+		     struct replay_revisions_options *opts,
+		     struct replay_result *out)
+{
+	kh_oid_map_t *replayed_commits = NULL;
+	struct strset *update_refs = NULL;
+	struct commit *last_commit = NULL;
+	struct commit *commit;
+	struct commit *onto = NULL;
+	struct merge_options merge_opt;
+	struct merge_result result = {
+		.clean = 1,
+	};
+	bool detached_head;
+	char *advance;
+	int ret;
+
+	advance = xstrdup_or_null(opts->advance);
+	set_up_replay_mode(revs->repo, &revs->cmdline, opts->onto,
+			   &detached_head, &advance, &onto, &update_refs);
+
+	/* FIXME: Should allow replaying commits with the first as a root commit */
+
+	if (prepare_revision_walk(revs) < 0) {
+		ret = error(_("error preparing revisions"));
+		goto out;
+	}
+
+	init_basic_merge_options(&merge_opt, revs->repo);
+	merge_opt.show_rename_progress = 0;
+	last_commit = onto;
+	replayed_commits = kh_init_oid_map();
+	while ((commit = get_revision(revs))) {
+		const struct name_decoration *decoration;
+		khint_t pos;
+		int hr;
+
+		if (!commit->parents)
+			die(_("replaying down from root commit is not supported yet!"));
+		if (commit->parents->next)
+			die(_("replaying merge commits is not supported yet!"));
+
+		last_commit = pick_regular_commit(revs->repo, commit, replayed_commits,
+						  onto, &merge_opt, &result);
+		if (!last_commit)
+			break;
+
+		/* Record commit -> last_commit mapping */
+		pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);
+		if (hr == 0)
+			BUG("Duplicate rewritten commit: %s\n",
+			    oid_to_hex(&commit->object.oid));
+		kh_value(replayed_commits, pos) = last_commit;
+
+		/* Update any necessary branches */
+		if (advance)
+			continue;
+
+		for (decoration = get_name_decoration(&commit->object);
+		     decoration;
+		     decoration = decoration->next)
+		{
+			if (decoration->type != DECORATION_REF_LOCAL &&
+			    decoration->type != DECORATION_REF_HEAD)
+				continue;
+
+			/*
+			 * We only need to update HEAD separately in case it's
+			 * detached. If it's not we'd already update the branch
+			 * it is pointing to.
+			 */
+			if (decoration->type == DECORATION_REF_HEAD && !detached_head)
+				continue;
+
+			if (!opts->contained &&
+			    !strset_contains(update_refs, decoration->name))
+				continue;
+
+			replay_result_queue_update(out, decoration->name,
+						   &commit->object.oid,
+						   &last_commit->object.oid);
+		}
+	}
+
+	if (!result.clean) {
+		ret = 1;
+		goto out;
+	}
+
+	/* In --advance mode, advance the target ref */
+	if (advance)
+		replay_result_queue_update(out, advance,
+					   &onto->object.oid,
+					   &last_commit->object.oid);
+
+	ret = 0;
+
+out:
+	if (update_refs) {
+		strset_clear(update_refs);
+		free(update_refs);
+	}
+	kh_destroy_oid_map(replayed_commits);
+	merge_finalize(&merge_opt, &result);
+	free(advance);
+	return ret;
+}
diff --git a/replay.h b/replay.h
new file mode 100644
index 0000000..d8407dc
--- /dev/null
+++ b/replay.h
@@ -0,0 +1,61 @@
+#ifndef REPLAY_H
+#define REPLAY_H
+
+#include "hash.h"
+
+struct repository;
+struct rev_info;
+
+/*
+ * A set of options that can be passed to `replay_revisions()`.
+ */
+struct replay_revisions_options {
+	/*
+	 * Starting point at which to create the new commits; must be a branch
+	 * name. The branch will be updated to point to the rewritten commits.
+	 * This option is mutually exclusive with `onto`.
+	 */
+	const char *advance;
+
+	/*
+	 * Starting point at which to create the new commits; must be a
+	 * committish. References pointing at decendants of `onto` will be
+	 * updated to point to the new commits.
+	 */
+	 const char *onto;
+
+	/*
+	 * Update branches that point at commits in the given revision range.
+	 * Requires `onto` to be set.
+	 */
+	int contained;
+};
+
+/* This struct is used as an out-parameter by `replay_revisions()`. */
+struct replay_result {
+	/*
+	 * The set of reference updates that are caused by replaying the
+	 * commits.
+	 */
+	struct replay_ref_update {
+		char *refname;
+		struct object_id old_oid;
+		struct object_id new_oid;
+	} *updates;
+	size_t updates_nr, updates_alloc;
+};
+
+void replay_result_release(struct replay_result *result);
+
+/*
+ * Replay a set of commits onto a new location. Leaves both the working tree,
+ * index and references untouched. Reference updates caused by the replay will
+ * be recorded in the `updates` out pointer.
+ *
+ * Returns 0 on success, 1 on conflict and a negative error code otherwise.
+ */
+int replay_revisions(struct rev_info *revs,
+		     struct replay_revisions_options *opts,
+		     struct replay_result *out);
+
+#endif
diff --git a/repo-settings.c b/repo-settings.c
index 195c24e..208e09f 100644
--- a/repo-settings.c
+++ b/repo-settings.c
@@ -100,6 +100,9 @@ void prepare_repo_settings(struct repository *r)
 	 */
 	if (!repo_config_get_int(r, "index.version", &value))
 		r->settings.index_version = value;
+	repo_cfg_int(r, "core.maxtreedepth",
+		     &r->settings.max_allowed_tree_depth,
+		     DEFAULT_MAX_ALLOWED_TREE_DEPTH);
 
 	if (!repo_config_get_string_tmp(r, "core.untrackedcache", &strval)) {
 		int v = git_parse_maybe_bool(strval);
diff --git a/repo-settings.h b/repo-settings.h
index d477885..cad9c3f 100644
--- a/repo-settings.h
+++ b/repo-settings.h
@@ -67,6 +67,8 @@ struct repo_settings {
 	size_t packed_git_limit;
 	unsigned long big_file_threshold;
 
+	int max_allowed_tree_depth;
+
 	char *hooks_path;
 };
 #define REPO_SETTINGS_INIT { \
@@ -78,6 +80,7 @@ struct repo_settings {
 	.delta_base_cache_limit = DEFAULT_DELTA_BASE_CACHE_LIMIT, \
 	.packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE, \
 	.packed_git_limit = DEFAULT_PACKED_GIT_LIMIT, \
+	.max_allowed_tree_depth = DEFAULT_MAX_ALLOWED_TREE_DEPTH, \
 }
 
 void prepare_repo_settings(struct repository *r);
diff --git a/repository.c b/repository.c
index 6faf5c7..0b8f7ec 100644
--- a/repository.c
+++ b/repository.c
@@ -1,8 +1,10 @@
 #include "git-compat-util.h"
 #include "abspath.h"
 #include "repository.h"
+#include "hook.h"
 #include "odb.h"
 #include "config.h"
+#include "gettext.h"
 #include "object.h"
 #include "lockfile.h"
 #include "path.h"
@@ -38,7 +40,7 @@ struct repository *the_repository = &the_repo;
 static void set_default_hash_algo(struct repository *repo)
 {
 	const char *hash_name;
-	int algo;
+	uint32_t algo;
 
 	hash_name = getenv("GIT_TEST_DEFAULT_HASH_ALGO");
 	if (!hash_name)
@@ -50,14 +52,27 @@ static void set_default_hash_algo(struct repository *repo)
 	repo_set_hash_algo(repo, algo);
 }
 
+struct repo_config_values *repo_config_values(struct repository *repo)
+{
+	if (repo != the_repository)
+		BUG("trying to read config from wrong repository instance");
+	if (!repo->initialized)
+		BUG("config values from uninitialized repository");
+	return &repo->config_values_private_;
+}
+
 void initialize_repository(struct repository *repo)
 {
-	repo->objects = odb_new(repo);
+	if (repo->initialized)
+		BUG("repository initialized already");
+	repo->initialized = true;
+
 	repo->remote_state = remote_state_new();
 	repo->parsed_objects = parsed_object_pool_new(repo);
 	ALLOC_ARRAY(repo->index, 1);
 	index_state_init(repo->index, repo);
 	repo->check_deprecated_config = true;
+	repo_config_values_init(&repo->config_values_private_);
 
 	/*
 	 * When a command runs inside a repository, it learns what
@@ -166,43 +181,45 @@ void repo_set_gitdir(struct repository *repo,
 
 	repo_set_commondir(repo, o->commondir);
 
-	if (!repo->objects->sources) {
-		CALLOC_ARRAY(repo->objects->sources, 1);
-		repo->objects->sources->odb = repo->objects;
-		repo->objects->sources->local = true;
-		repo->objects->sources_tail = &repo->objects->sources->next;
-	}
-	expand_base_dir(&repo->objects->sources->path, o->object_dir,
-			repo->commondir, "objects");
+	if (!repo->objects)
+		repo->objects = odb_new(repo, o->object_dir, o->alternate_db);
+	else if (!o->skip_initializing_odb)
+		BUG("cannot reinitialize an already-initialized object directory");
 
-	repo->objects->sources->disable_ref_updates = o->disable_ref_updates;
+	repo->disable_ref_updates = o->disable_ref_updates;
 
-	free(repo->objects->alternate_db);
-	repo->objects->alternate_db = xstrdup_or_null(o->alternate_db);
 	expand_base_dir(&repo->graft_file, o->graft_file,
 			repo->commondir, "info/grafts");
 	expand_base_dir(&repo->index_file, o->index_file,
 			repo->gitdir, "index");
 }
 
-void repo_set_hash_algo(struct repository *repo, int hash_algo)
+void repo_set_hash_algo(struct repository *repo, uint32_t hash_algo)
 {
 	repo->hash_algo = &hash_algos[hash_algo];
 }
 
-void repo_set_compat_hash_algo(struct repository *repo, int algo)
+void repo_set_compat_hash_algo(struct repository *repo MAYBE_UNUSED, uint32_t algo)
 {
+#ifdef WITH_RUST
 	if (hash_algo_by_ptr(repo->hash_algo) == algo)
 		BUG("hash_algo and compat_hash_algo match");
 	repo->compat_hash_algo = algo ? &hash_algos[algo] : NULL;
 	if (repo->compat_hash_algo)
 		repo_read_loose_object_map(repo);
+#else
+	if (algo)
+		die(_("compatibility hash algorithm support requires Rust"));
+#endif
 }
 
 void repo_set_ref_storage_format(struct repository *repo,
-				 enum ref_storage_format format)
+				 enum ref_storage_format format,
+				 const char *payload)
 {
 	repo->ref_storage_format = format;
+	free(repo->ref_storage_payload);
+	repo->ref_storage_payload = xstrdup_or_null(payload);
 }
 
 /*
@@ -284,10 +301,12 @@ int repo_init(struct repository *repo,
 
 	repo_set_hash_algo(repo, format.hash_algo);
 	repo_set_compat_hash_algo(repo, format.compat_hash_algo);
-	repo_set_ref_storage_format(repo, format.ref_storage_format);
+	repo_set_ref_storage_format(repo, format.ref_storage_format,
+				    format.ref_storage_payload);
 	repo->repository_format_worktree_config = format.worktree_config;
 	repo->repository_format_relative_worktrees = format.relative_worktrees;
 	repo->repository_format_precious_objects = format.precious_objects;
+	repo->repository_format_submodule_path_cfg = format.submodule_path_cfg;
 
 	/* take ownership of format.partial_clone */
 	repo->repository_format_partial_clone = format.partial_clone;
@@ -357,7 +376,6 @@ int repo_submodule_init(struct repository *subrepo,
 static void repo_clear_path_cache(struct repo_path_cache *cache)
 {
 	FREE_AND_NULL(cache->squash_msg);
-	FREE_AND_NULL(cache->squash_msg);
 	FREE_AND_NULL(cache->merge_msg);
 	FREE_AND_NULL(cache->merge_rr);
 	FREE_AND_NULL(cache->merge_mode);
@@ -377,9 +395,10 @@ void repo_clear(struct repository *repo)
 	FREE_AND_NULL(repo->index_file);
 	FREE_AND_NULL(repo->worktree);
 	FREE_AND_NULL(repo->submodule_prefix);
+	FREE_AND_NULL(repo->ref_storage_payload);
 
-	odb_clear(repo->objects);
-	FREE_AND_NULL(repo->objects);
+	odb_free(repo->objects);
+	repo->objects = NULL;
 
 	parsed_object_pool_clear(repo->parsed_objects);
 	FREE_AND_NULL(repo->parsed_objects);
@@ -401,6 +420,11 @@ void repo_clear(struct repository *repo)
 		FREE_AND_NULL(repo->index);
 	}
 
+	if (repo->hook_config_cache) {
+		hook_cache_clear(repo->hook_config_cache);
+		FREE_AND_NULL(repo->hook_config_cache);
+	}
+
 	if (repo->promisor_remote_config) {
 		promisor_remote_clear(repo->promisor_remote_config);
 		FREE_AND_NULL(repo->promisor_remote_config);
diff --git a/repository.h b/repository.h
index 5808a5d..078059a 100644
--- a/repository.h
+++ b/repository.h
@@ -3,6 +3,7 @@
 
 #include "strmap.h"
 #include "repo-settings.h"
+#include "environment.h"
 
 struct config_set;
 struct git_hash_algo;
@@ -72,6 +73,13 @@ struct repository {
 	struct ref_store *refs_private;
 
 	/*
+	 * Disable ref updates. This is especially used in contexts where
+	 * transactions may still be rolled back so that we don't start to
+	 * reference objects that may vanish.
+	 */
+	bool disable_ref_updates;
+
+	/*
 	 * A strmap of ref_stores, stored by submodule name, accessible via
 	 * `repo_get_submodule_ref_store()`.
 	 */
@@ -141,8 +149,16 @@ struct repository {
 	/* Repository's compatibility hash algorithm. */
 	const struct git_hash_algo *compat_hash_algo;
 
+	/* Repository's config values parsed by git_default_config() */
+	struct repo_config_values config_values_private_;
+
 	/* Repository's reference storage format, as serialized on disk. */
 	enum ref_storage_format ref_storage_format;
+	/*
+	 * Reference storage information as needed for the backend. This contains
+	 * only the payload from the reference URI without the schema.
+	 */
+	char *ref_storage_payload;
 
 	/* A unique-id for tracing purposes. */
 	int trace2_repo_id;
@@ -150,6 +166,12 @@ struct repository {
 	/* True if commit-graph has been disabled within this process. */
 	int commit_graph_disabled;
 
+	/*
+	 * Lazily-populated cache mapping hook event names to configured hooks.
+	 * NULL until first hook use.
+	 */
+	struct strmap *hook_config_cache;
+
 	/* Configurations related to promisor remotes. */
 	char *repository_format_partial_clone;
 	struct promisor_remote_config *promisor_remote_config;
@@ -158,12 +180,16 @@ struct repository {
 	int repository_format_worktree_config;
 	int repository_format_relative_worktrees;
 	int repository_format_precious_objects;
+	int repository_format_submodule_path_cfg;
 
 	/* Indicate if a repository has a different 'commondir' from 'gitdir' */
 	unsigned different_commondir:1;
 
 	/* Should repo_config() check for deprecated settings */
 	bool check_deprecated_config;
+
+	/* Has this repository instance been initialized? */
+	bool initialized;
 };
 
 #ifdef USE_THE_REPOSITORY_VARIABLE
@@ -187,16 +213,18 @@ struct set_gitdir_args {
 	const char *graft_file;
 	const char *index_file;
 	const char *alternate_db;
-	int disable_ref_updates;
+	bool disable_ref_updates;
+	bool skip_initializing_odb;
 };
 
 void repo_set_gitdir(struct repository *repo, const char *root,
 		     const struct set_gitdir_args *extra_args);
 void repo_set_worktree(struct repository *repo, const char *path);
-void repo_set_hash_algo(struct repository *repo, int algo);
-void repo_set_compat_hash_algo(struct repository *repo, int compat_algo);
+void repo_set_hash_algo(struct repository *repo, uint32_t algo);
+void repo_set_compat_hash_algo(struct repository *repo, uint32_t compat_algo);
 void repo_set_ref_storage_format(struct repository *repo,
-				 enum ref_storage_format format);
+				 enum ref_storage_format format,
+				 const char *payload);
 void initialize_repository(struct repository *repo);
 RESULT_MUST_BE_USED
 int repo_init(struct repository *r, const char *gitdir, const char *worktree);
diff --git a/reset.c b/reset.c
index bb59027..46e30e6 100644
--- a/reset.c
+++ b/reset.c
@@ -163,7 +163,7 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)
 		goto leave_reset_head;
 	}
 
-	tree = parse_tree_indirect(oid);
+	tree = repo_parse_tree_indirect(the_repository, oid);
 	if (!tree) {
 		ret = error(_("unable to read tree (%s)"), oid_to_hex(oid));
 		goto leave_reset_head;
diff --git a/revision.c b/revision.c
index cf5e6c1..31808e3 100644
--- a/revision.c
+++ b/revision.c
@@ -72,7 +72,7 @@ static void mark_tree_contents_uninteresting(struct repository *r,
 	struct tree_desc desc;
 	struct name_entry entry;
 
-	if (parse_tree_gently(tree, 1) < 0)
+	if (repo_parse_tree_gently(the_repository, tree, 1) < 0)
 		return;
 
 	init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size);
@@ -179,7 +179,7 @@ static void add_children_by_path(struct repository *r,
 	if (!tree)
 		return;
 
-	if (parse_tree_gently(tree, 1) < 0)
+	if (repo_parse_tree_gently(the_repository, tree, 1) < 0)
 		return;
 
 	init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size);
@@ -250,29 +250,6 @@ void mark_trees_uninteresting_sparse(struct repository *r,
 	paths_and_oids_clear(&map);
 }
 
-struct commit_stack {
-	struct commit **items;
-	size_t nr, alloc;
-};
-#define COMMIT_STACK_INIT { 0 }
-
-static void commit_stack_push(struct commit_stack *stack, struct commit *commit)
-{
-	ALLOC_GROW(stack->items, stack->nr + 1, stack->alloc);
-	stack->items[stack->nr++] = commit;
-}
-
-static struct commit *commit_stack_pop(struct commit_stack *stack)
-{
-	return stack->nr ? stack->items[--stack->nr] : NULL;
-}
-
-static void commit_stack_clear(struct commit_stack *stack)
-{
-	FREE_AND_NULL(stack->items);
-	stack->nr = stack->alloc = 0;
-}
-
 static void mark_one_parent_uninteresting(struct rev_info *revs, struct commit *commit,
 					  struct commit_stack *pending)
 {
@@ -1071,7 +1048,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
 				continue;
 			}
 
-			free_commit_list(parent->next);
+			commit_list_free(parent->next);
 			parent->next = NULL;
 			while (commit->parents != parent)
 				pop_commit(&commit->parents);
@@ -1106,7 +1083,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
 					die("cannot simplify commit %s (invalid %s)",
 					    oid_to_hex(&commit->object.oid),
 					    oid_to_hex(&p->object.oid));
-				free_commit_list(p->parents);
+				commit_list_free(p->parents);
 				p->parents = NULL;
 			}
 		/* fallthrough */
@@ -1173,7 +1150,8 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
 			struct commit *p = parent->item;
 			parent = parent->next;
 			if (p)
-				p->object.flags |= UNINTERESTING;
+				p->object.flags |= UNINTERESTING |
+						   CHILD_VISITED;
 			if (repo_parse_commit_gently(revs->repo, p, 1) < 0)
 				continue;
 			if (p->parents)
@@ -1227,7 +1205,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
 			if (!*slot)
 				*slot = *revision_sources_at(revs->sources, commit);
 		}
-		p->object.flags |= pass_flags;
+		p->object.flags |= pass_flags | CHILD_VISITED;
 		if (!(p->object.flags & SEEN)) {
 			p->object.flags |= (SEEN | NOT_USER_GIVEN);
 			if (list)
@@ -1428,7 +1406,7 @@ static void limit_to_ancestry(struct commit_list *bottoms, struct commit_list *l
 		p->item->object.flags &= ~(TMP_MARK | ANCESTRY_PATH);
 	for (p = bottoms; p; p = p->next)
 		p->item->object.flags &= ~(TMP_MARK | ANCESTRY_PATH);
-	free_commit_list(rlist);
+	commit_list_free(rlist);
 }
 
 /*
@@ -1531,7 +1509,7 @@ static int limit_list(struct rev_info *revs)
 		}
 	}
 
-	free_commit_list(original_list);
+	commit_list_free(original_list);
 	revs->commits = newlist;
 	return 0;
 }
@@ -1644,19 +1622,17 @@ struct all_refs_cb {
 	struct worktree *wt;
 };
 
-static int handle_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,
-			  int flag UNUSED,
-			  void *cb_data)
+static int handle_one_ref(const struct reference *ref, void *cb_data)
 {
 	struct all_refs_cb *cb = cb_data;
 	struct object *object;
 
-	if (ref_excluded(&cb->all_revs->ref_excludes, path))
+	if (ref_excluded(&cb->all_revs->ref_excludes, ref->name))
 	    return 0;
 
-	object = get_reference(cb->all_revs, path, oid, cb->all_flags);
-	add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);
-	add_pending_object(cb->all_revs, object, path);
+	object = get_reference(cb->all_revs, ref->name, ref->oid, cb->all_flags);
+	add_rev_cmdline(cb->all_revs, object, ref->name, REV_CMD_REF, cb->all_flags);
+	add_pending_object(cb->all_revs, object, ref->name);
 	return 0;
 }
 
@@ -1671,7 +1647,7 @@ static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs,
 
 static void handle_refs(struct ref_store *refs,
 			struct rev_info *revs, unsigned flags,
-			int (*for_each)(struct ref_store *, each_ref_fn, void *))
+			int (*for_each)(struct ref_store *, refs_for_each_cb, void *))
 {
 	struct all_refs_cb cb;
 
@@ -1872,7 +1848,7 @@ void add_index_objects_to_pending(struct rev_info *revs, unsigned int flags)
 		wt_gitdir = get_worktree_git_dir(wt);
 
 		if (read_index_from(&istate,
-				    worktree_git_path(the_repository, wt, "index"),
+				    worktree_git_path(wt, "index"),
 				    wt_gitdir) > 0)
 			do_add_index_objects_to_pending(revs, &istate, flags);
 
@@ -2036,7 +2012,7 @@ static void prepare_show_merge(struct rev_info *revs)
 		exit(128);
 	add_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING | BOTTOM);
 	add_pending_commit_list(revs, bases, UNINTERESTING | BOTTOM);
-	free_commit_list(bases);
+	commit_list_free(bases);
 	head->object.flags |= SYMMETRIC_LEFT;
 
 	if (!istate->cache_nr)
@@ -2130,13 +2106,13 @@ static int handle_dotdot_1(const char *arg, char *dotdot,
 			return dotdot_missing(arg, dotdot, revs, symmetric);
 
 		if (repo_get_merge_bases(the_repository, a, b, &exclude) < 0) {
-			free_commit_list(exclude);
+			commit_list_free(exclude);
 			return -1;
 		}
 		add_rev_cmdline_list(revs, exclude, REV_CMD_MERGE_BASE,
 				     flags_exclude);
 		add_pending_commit_list(revs, exclude, flags_exclude);
-		free_commit_list(exclude);
+		commit_list_free(exclude);
 
 		b_flags = flags;
 		a_flags = flags | SYMMETRIC_LEFT;
@@ -2402,6 +2378,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 	} else if ((argcount = parse_long_opt("until", argv, &optarg))) {
 		revs->min_age = approxidate(optarg);
 		return argcount;
+	} else if (!strcmp(arg, "--maximal-only")) {
+		revs->maximal_only = 1;
 	} else if (!strcmp(arg, "--first-parent")) {
 		revs->first_parent_only = 1;
 	} else if (!strcmp(arg, "--exclude-first-parent-only")) {
@@ -2543,14 +2521,14 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 		die(_("--unpacked=<packfile> no longer supported"));
 	} else if (!strcmp(arg, "--no-kept-objects")) {
 		revs->no_kept_objects = 1;
-		revs->keep_pack_cache_flags |= IN_CORE_KEEP_PACKS;
-		revs->keep_pack_cache_flags |= ON_DISK_KEEP_PACKS;
+		revs->keep_pack_cache_flags |= KEPT_PACK_IN_CORE;
+		revs->keep_pack_cache_flags |= KEPT_PACK_ON_DISK;
 	} else if (skip_prefix(arg, "--no-kept-objects=", &optarg)) {
 		revs->no_kept_objects = 1;
 		if (!strcmp(optarg, "in-core"))
-			revs->keep_pack_cache_flags |= IN_CORE_KEEP_PACKS;
+			revs->keep_pack_cache_flags |= KEPT_PACK_IN_CORE;
 		if (!strcmp(optarg, "on-disk"))
-			revs->keep_pack_cache_flags |= ON_DISK_KEEP_PACKS;
+			revs->keep_pack_cache_flags |= KEPT_PACK_ON_DISK;
 	} else if (!strcmp(arg, "-r")) {
 		revs->diff = 1;
 		revs->diffopt.flags.recursive = 1;
@@ -2753,23 +2731,25 @@ void revision_opts_finish(struct rev_info *revs)
 	}
 }
 
-static int for_each_bisect_ref(struct ref_store *refs, each_ref_fn fn,
+static int for_each_bisect_ref(struct ref_store *refs, refs_for_each_cb fn,
 			       void *cb_data, const char *term)
 {
+	struct refs_for_each_ref_options opts = { 0 };
 	struct strbuf bisect_refs = STRBUF_INIT;
 	int status;
 	strbuf_addf(&bisect_refs, "refs/bisect/%s", term);
-	status = refs_for_each_fullref_in(refs, bisect_refs.buf, NULL, fn, cb_data);
+	opts.prefix = bisect_refs.buf;
+	status = refs_for_each_ref_ext(refs, fn, cb_data, &opts);
 	strbuf_release(&bisect_refs);
 	return status;
 }
 
-static int for_each_bad_bisect_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+static int for_each_bad_bisect_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
 {
 	return for_each_bisect_ref(refs, fn, cb_data, term_bad);
 }
 
-static int for_each_good_bisect_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+static int for_each_good_bisect_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
 {
 	return for_each_bisect_ref(refs, fn, cb_data, term_good);
 }
@@ -2839,10 +2819,13 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
 		handle_refs(refs, revs, *flags, refs_for_each_remote_ref);
 		clear_ref_exclusions(&revs->ref_excludes);
 	} else if ((argcount = parse_long_opt("glob", argv, &optarg))) {
+		struct refs_for_each_ref_options opts = {
+			.pattern = optarg,
+		};
 		struct all_refs_cb cb;
 		init_all_refs_cb(&cb, revs, *flags);
-		refs_for_each_glob_ref(get_main_ref_store(the_repository),
-				       handle_one_ref, optarg, &cb);
+		refs_for_each_ref_ext(get_main_ref_store(the_repository),
+				      handle_one_ref, &cb, &opts);
 		clear_ref_exclusions(&revs->ref_excludes);
 		return argcount;
 	} else if ((argcount = parse_long_opt("exclude", argv, &optarg))) {
@@ -2852,34 +2835,46 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
 		exclude_hidden_refs(&revs->ref_excludes, optarg);
 		return argcount;
 	} else if (skip_prefix(arg, "--branches=", &optarg)) {
+		struct refs_for_each_ref_options opts = {
+			.prefix = "refs/heads/",
+			.trim_prefix = strlen("refs/heads/"),
+			.pattern = optarg,
+		};
 		struct all_refs_cb cb;
 		if (revs->ref_excludes.hidden_refs_configured)
 			return error(_("options '%s' and '%s' cannot be used together"),
 				     "--exclude-hidden", "--branches");
 		init_all_refs_cb(&cb, revs, *flags);
-		refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
-					  handle_one_ref, optarg,
-					  "refs/heads/", &cb);
+		refs_for_each_ref_ext(get_main_ref_store(the_repository),
+				      handle_one_ref, &cb, &opts);
 		clear_ref_exclusions(&revs->ref_excludes);
 	} else if (skip_prefix(arg, "--tags=", &optarg)) {
+		struct refs_for_each_ref_options opts = {
+			.prefix = "refs/tags/",
+			.trim_prefix = strlen("refs/tags/"),
+			.pattern = optarg,
+		};
 		struct all_refs_cb cb;
 		if (revs->ref_excludes.hidden_refs_configured)
 			return error(_("options '%s' and '%s' cannot be used together"),
 				     "--exclude-hidden", "--tags");
 		init_all_refs_cb(&cb, revs, *flags);
-		refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
-					  handle_one_ref, optarg,
-					  "refs/tags/", &cb);
+		refs_for_each_ref_ext(get_main_ref_store(the_repository),
+				      handle_one_ref, &cb, &opts);
 		clear_ref_exclusions(&revs->ref_excludes);
 	} else if (skip_prefix(arg, "--remotes=", &optarg)) {
+		struct refs_for_each_ref_options opts = {
+			.prefix = "refs/remotes/",
+			.trim_prefix = strlen("refs/remotes/"),
+			.pattern = optarg,
+		};
 		struct all_refs_cb cb;
 		if (revs->ref_excludes.hidden_refs_configured)
 			return error(_("options '%s' and '%s' cannot be used together"),
 				     "--exclude-hidden", "--remotes");
 		init_all_refs_cb(&cb, revs, *flags);
-		refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
-					  handle_one_ref, optarg,
-					  "refs/remotes/", &cb);
+		refs_for_each_ref_ext(get_main_ref_store(the_repository),
+				      handle_one_ref, &cb, &opts);
 		clear_ref_exclusions(&revs->ref_excludes);
 	} else if (!strcmp(arg, "--reflog")) {
 		add_reflogs_to_pending(revs, *flags);
@@ -3172,6 +3167,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
 				  !!revs->reverse, "--reverse",
 				  !!revs->reflog_info, "--walk-reflogs");
 
+	die_for_incompatible_opt2(!!revs->boundary, "--boundary",
+				  !!revs->maximal_only, "--maximal-only");
+
 	if (revs->no_walk && revs->graph)
 		die(_("options '%s' and '%s' cannot be used together"), "--no-walk", "--graph");
 	if (!revs->reflog_info && revs->grep_filter.use_reflog_filter)
@@ -3246,13 +3244,13 @@ static void release_revisions_bloom_keyvecs(struct rev_info *revs)
 
 static void free_void_commit_list(void *list)
 {
-	free_commit_list(list);
+	commit_list_free(list);
 }
 
 void release_revisions(struct rev_info *revs)
 {
-	free_commit_list(revs->commits);
-	free_commit_list(revs->ancestry_path_bottoms);
+	commit_list_free(revs->commits);
+	commit_list_free(revs->ancestry_path_bottoms);
 	release_display_notes(&revs->notes_opt);
 	object_array_clear(&revs->pending);
 	object_array_clear(&revs->boundary_commits);
@@ -3360,7 +3358,7 @@ static int mark_redundant_parents(struct commit *commit)
 	if (i != cnt || cnt+marked != orig_cnt)
 		die("mark_redundant_parents %d %d %d %d", orig_cnt, cnt, i, marked);
 
-	free_commit_list(h);
+	commit_list_free(h);
 
 	return marked;
 }
@@ -3651,8 +3649,7 @@ void reset_revision_walk(void)
 }
 
 static int mark_uninteresting(const struct object_id *oid,
-			      struct packed_git *pack UNUSED,
-			      uint32_t pos UNUSED,
+			      struct object_info *oi UNUSED,
 			      void *cb)
 {
 	struct rev_info *revs = cb;
@@ -3961,10 +3958,9 @@ int prepare_revision_walk(struct rev_info *revs)
 	    (revs->limited && limiting_can_increase_treesame(revs)))
 		revs->treesame.name = "treesame";
 
-	if (revs->exclude_promisor_objects) {
-		for_each_packed_object(revs->repo, mark_uninteresting, revs,
-				       FOR_EACH_OBJECT_PROMISOR_ONLY);
-	}
+	if (revs->exclude_promisor_objects)
+		odb_for_each_object(revs->repo->objects, NULL, mark_uninteresting,
+				    revs, ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);
 
 	if (!revs->reflog_info)
 		prepare_to_use_bloom_filter(revs);
@@ -4150,6 +4146,8 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
 {
 	if (commit->object.flags & SHOWN)
 		return commit_ignore;
+	if (revs->maximal_only && (commit->object.flags & CHILD_VISITED))
+		return commit_ignore;
 	if (revs->unpacked && has_object_pack(revs->repo, &commit->object.oid))
 		return commit_ignore;
 	if (revs->no_kept_objects) {
@@ -4249,7 +4247,7 @@ static void save_parents(struct rev_info *revs, struct commit *commit)
 	if (*pp)
 		return;
 	if (commit->parents)
-		*pp = copy_commit_list(commit->parents);
+		*pp = commit_list_copy(commit->parents);
 	else
 		*pp = EMPTY_PARENT_LIST;
 }
@@ -4257,7 +4255,7 @@ static void save_parents(struct rev_info *revs, struct commit *commit)
 static void free_saved_parent(struct commit_list **parents)
 {
 	if (*parents != EMPTY_PARENT_LIST)
-		free_commit_list(*parents);
+		commit_list_free(*parents);
 }
 
 static void free_saved_parents(struct rev_info *revs)
@@ -4318,8 +4316,8 @@ static void track_linear(struct rev_info *revs, struct commit *commit)
 		if (revs->linear)
 			commit->object.flags |= TRACK_LINEAR;
 	}
-	free_commit_list(revs->previous_parents);
-	revs->previous_parents = copy_commit_list(commit->parents);
+	commit_list_free(revs->previous_parents);
+	revs->previous_parents = commit_list_copy(commit->parents);
 }
 
 static struct commit *get_revision_1(struct rev_info *revs)
@@ -4407,7 +4405,7 @@ static void create_boundary_commit_list(struct rev_info *revs)
 	 * boundary commits anyway.  (This is what the code has always
 	 * done.)
 	 */
-	free_commit_list(revs->commits);
+	commit_list_free(revs->commits);
 	revs->commits = NULL;
 
 	/*
@@ -4529,7 +4527,7 @@ struct commit *get_revision(struct rev_info *revs)
 		reversed = NULL;
 		while ((c = get_revision_internal(revs)))
 			commit_list_insert(c, &reversed);
-		free_commit_list(revs->commits);
+		commit_list_free(revs->commits);
 		revs->commits = reversed;
 		revs->reverse = 0;
 		revs->reverse_output_stage = 1;
@@ -4547,7 +4545,7 @@ struct commit *get_revision(struct rev_info *revs)
 		graph_update(revs->graph, c);
 	if (!c) {
 		free_saved_parents(revs);
-		free_commit_list(revs->previous_parents);
+		commit_list_free(revs->previous_parents);
 		revs->previous_parents = NULL;
 	}
 	return c;
diff --git a/revision.h b/revision.h
index b36acfc..69242ec 100644
--- a/revision.h
+++ b/revision.h
@@ -52,7 +52,9 @@
 #define NOT_USER_GIVEN	(1u<<25)
 #define TRACK_LINEAR	(1u<<26)
 #define ANCESTRY_PATH	(1u<<27)
-#define ALL_REV_FLAGS	(((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR | PULL_MERGE)
+#define CHILD_VISITED	(1u<<28)
+#define ALL_REV_FLAGS	(((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR \
+				      | PULL_MERGE | CHILD_VISITED)
 
 #define DECORATE_SHORT_REFS	1
 #define DECORATE_FULL_REFS	2
@@ -189,6 +191,7 @@ struct rev_info {
 			left_right:1,
 			left_only:1,
 			right_only:1,
+			maximal_only:1,
 			rewrite_parents:1,
 			print_parents:1,
 			show_decorations:1,
diff --git a/run-command.c b/run-command.c
index ed9575b..a8a8974 100644
--- a/run-command.c
+++ b/run-command.c
@@ -743,7 +743,7 @@ int start_command(struct child_process *cmd)
 	fflush(NULL);
 
 	if (cmd->close_object_store)
-		close_object_store(the_repository->objects);
+		odb_close(the_repository->objects);
 
 #ifndef GIT_WINDOWS_NATIVE
 {
@@ -1478,15 +1478,40 @@ enum child_state {
 	GIT_CP_WAIT_CLEANUP,
 };
 
+struct parallel_child {
+	enum child_state state;
+	struct child_process process;
+	struct strbuf err;
+	void *data;
+};
+
+static int child_is_working(const struct parallel_child *pp_child)
+{
+	return pp_child->state == GIT_CP_WORKING;
+}
+
+static int child_is_ready_for_cleanup(const struct parallel_child *pp_child)
+{
+	return child_is_working(pp_child) && !pp_child->process.in;
+}
+
+static int child_is_receiving_input(const struct parallel_child *pp_child)
+{
+	return child_is_working(pp_child) && pp_child->process.in > 0;
+}
+static int child_is_sending_output(const struct parallel_child *pp_child)
+{
+	/*
+	 * all pp children which buffer output through run_command via ungroup=0
+	 * redirect stdout to stderr, so we just need to check process.err.
+	 */
+	return child_is_working(pp_child) && pp_child->process.err > 0;
+}
+
 struct parallel_processes {
 	size_t nr_processes;
 
-	struct {
-		enum child_state state;
-		struct child_process process;
-		struct strbuf err;
-		void *data;
-	} *children;
+	struct parallel_child *children;
 	/*
 	 * The struct pollfd is logically part of *children,
 	 * but the system call expects it as its own array.
@@ -1509,7 +1534,7 @@ static void kill_children(const struct parallel_processes *pp,
 			  int signo)
 {
 	for (size_t i = 0; i < opts->processes; i++)
-		if (pp->children[i].state == GIT_CP_WORKING)
+		if (child_is_working(&pp->children[i]))
 			kill(pp->children[i].process.pid, signo);
 }
 
@@ -1545,7 +1570,7 @@ static void pp_init(struct parallel_processes *pp,
 
 	CALLOC_ARRAY(pp->children, n);
 	if (!opts->ungroup)
-		CALLOC_ARRAY(pp->pfd, n);
+		CALLOC_ARRAY(pp->pfd, n * 2);
 
 	for (size_t i = 0; i < n; i++) {
 		strbuf_init(&pp->children[i].err, 0);
@@ -1652,21 +1677,101 @@ static int pp_start_one(struct parallel_processes *pp,
 	return 0;
 }
 
-static void pp_buffer_stderr(struct parallel_processes *pp,
-			     const struct run_process_parallel_opts *opts,
-			     int output_timeout)
+static void pp_buffer_stdin(struct parallel_processes *pp,
+			    const struct run_process_parallel_opts *opts)
 {
-	while (poll(pp->pfd, opts->processes, output_timeout) < 0) {
+	/* Buffer stdin for each pipe. */
+	for (size_t i = 0; i < opts->processes; i++) {
+		struct child_process *proc = &pp->children[i].process;
+		int ret;
+
+		if (!child_is_receiving_input(&pp->children[i]))
+			continue;
+
+		/*
+		 * child input is provided via path_to_stdin when the feed_pipe cb is
+		 * missing, so we just signal an EOF.
+		 */
+		if (!opts->feed_pipe) {
+			close(proc->in);
+			proc->in = 0;
+			continue;
+		}
+
+		/**
+		 * Feed the pipe:
+		 *   ret < 0 means error
+		 *   ret == 0 means there is more data to be fed
+		 *   ret > 0 means feeding finished
+		 */
+		ret = opts->feed_pipe(proc->in, opts->data, pp->children[i].data);
+		if (ret < 0)
+			die_errno("feed_pipe");
+
+		if (ret) {
+			close(proc->in);
+			proc->in = 0;
+		}
+	}
+}
+
+static void pp_buffer_io(struct parallel_processes *pp,
+			 const struct run_process_parallel_opts *opts,
+			 int timeout)
+{
+	/* for each potential child slot, prepare two pollfd entries */
+	for (size_t i = 0; i < opts->processes; i++) {
+		if (child_is_sending_output(&pp->children[i])) {
+			pp->pfd[2*i].fd = pp->children[i].process.err;
+			pp->pfd[2*i].events = POLLIN | POLLHUP;
+		} else {
+			pp->pfd[2*i].fd = -1;
+		}
+
+		if (child_is_receiving_input(&pp->children[i])) {
+			pp->pfd[2*i+1].fd = pp->children[i].process.in;
+			pp->pfd[2*i+1].events = POLLOUT;
+		} else {
+			pp->pfd[2*i+1].fd = -1;
+		}
+	}
+
+	while (poll(pp->pfd, opts->processes * 2, timeout) < 0) {
 		if (errno == EINTR)
 			continue;
 		pp_cleanup(pp, opts);
 		die_errno("poll");
 	}
 
-	/* Buffer output from all pipes. */
 	for (size_t i = 0; i < opts->processes; i++) {
-		if (pp->children[i].state == GIT_CP_WORKING &&
-		    pp->pfd[i].revents & (POLLIN | POLLHUP)) {
+		/* Handle input feeding (stdin) */
+		if (pp->pfd[2*i+1].revents & (POLLOUT | POLLHUP | POLLERR)) {
+			if (opts->feed_pipe) {
+				int ret = opts->feed_pipe(pp->children[i].process.in,
+							  opts->data,
+							  pp->children[i].data);
+				if (ret < 0)
+					die_errno("feed_pipe");
+				if (ret) {
+					/* done feeding */
+					close(pp->children[i].process.in);
+					pp->children[i].process.in = 0;
+				}
+			} else {
+				/*
+				 * No feed_pipe means there is nothing to do, so
+				 * close the fd. Child input can be fed by other
+				 * methods, such as opts->path_to_stdin which
+				 * slurps a file via dup2, so clean up here.
+				 */
+				close(pp->children[i].process.in);
+				pp->children[i].process.in = 0;
+			}
+		}
+
+		/* Handle output reading (stderr) */
+		if (child_is_working(&pp->children[i]) &&
+		    pp->pfd[2*i].revents & (POLLIN | POLLHUP)) {
 			int n = strbuf_read_once(&pp->children[i].err,
 						 pp->children[i].process.err, 0);
 			if (n == 0) {
@@ -1683,7 +1788,7 @@ static void pp_output(const struct parallel_processes *pp)
 {
 	size_t i = pp->output_owner;
 
-	if (pp->children[i].state == GIT_CP_WORKING &&
+	if (child_is_working(&pp->children[i]) &&
 	    pp->children[i].err.len) {
 		strbuf_write(&pp->children[i].err, stderr);
 		strbuf_reset(&pp->children[i].err);
@@ -1722,6 +1827,7 @@ static int pp_collect_finished(struct parallel_processes *pp,
 		pp->children[i].state = GIT_CP_FREE;
 		if (pp->pfd)
 			pp->pfd[i].fd = -1;
+		pp->children[i].process.in = 0;
 		child_process_init(&pp->children[i].process);
 
 		if (opts->ungroup) {
@@ -1748,7 +1854,7 @@ static int pp_collect_finished(struct parallel_processes *pp,
 			 * running process time.
 			 */
 			for (i = 0; i < n; i++)
-				if (pp->children[(pp->output_owner + i) % n].state == GIT_CP_WORKING)
+				if (child_is_working(&pp->children[(pp->output_owner + i) % n]))
 					break;
 			pp->output_owner = (pp->output_owner + i) % n;
 		}
@@ -1756,10 +1862,25 @@ static int pp_collect_finished(struct parallel_processes *pp,
 	return result;
 }
 
+static void pp_handle_child_IO(struct parallel_processes *pp,
+				const struct run_process_parallel_opts *opts,
+				int timeout)
+{
+	if (opts->ungroup) {
+		pp_buffer_stdin(pp, opts);
+		for (size_t i = 0; i < opts->processes; i++)
+			if (child_is_ready_for_cleanup(&pp->children[i]))
+				pp->children[i].state = GIT_CP_WAIT_CLEANUP;
+	} else {
+		pp_buffer_io(pp, opts, timeout);
+		pp_output(pp);
+	}
+}
+
 void run_processes_parallel(const struct run_process_parallel_opts *opts)
 {
 	int i, code;
-	int output_timeout = 100;
+	int timeout = 100;
 	int spawn_cap = 4;
 	struct parallel_processes_for_signal pp_sig;
 	struct parallel_processes pp = {
@@ -1775,6 +1896,13 @@ void run_processes_parallel(const struct run_process_parallel_opts *opts)
 					   "max:%"PRIuMAX,
 					   (uintmax_t)opts->processes);
 
+	/*
+	 * Child tasks might receive input via stdin, terminating early (or not), so
+	 * ignore the default SIGPIPE which gets handled by each feed_pipe_fn which
+	 * actually writes the data to children stdin fds.
+	 */
+	sigchain_push(SIGPIPE, SIG_IGN);
+
 	pp_init(&pp, opts, &pp_sig);
 	while (1) {
 		for (i = 0;
@@ -1792,13 +1920,7 @@ void run_processes_parallel(const struct run_process_parallel_opts *opts)
 		}
 		if (!pp.nr_processes)
 			break;
-		if (opts->ungroup) {
-			for (size_t i = 0; i < opts->processes; i++)
-				pp.children[i].state = GIT_CP_WAIT_CLEANUP;
-		} else {
-			pp_buffer_stderr(&pp, opts, output_timeout);
-			pp_output(&pp);
-		}
+		pp_handle_child_IO(&pp, opts, timeout);
 		code = pp_collect_finished(&pp, opts);
 		if (code) {
 			pp.shutdown = 1;
@@ -1809,6 +1931,8 @@ void run_processes_parallel(const struct run_process_parallel_opts *opts)
 
 	pp_cleanup(&pp, opts);
 
+	sigchain_pop(SIGPIPE);
+
 	if (do_trace2)
 		trace2_region_leave(tr2_category, tr2_label, NULL);
 }
@@ -1828,7 +1952,7 @@ int prepare_auto_maintenance(int quiet, struct child_process *maint)
 	 */
 	if (repo_config_get_bool(the_repository, "maintenance.autodetach", &auto_detach) &&
 	    repo_config_get_bool(the_repository, "gc.autodetach", &auto_detach))
-		auto_detach = 1;
+		auto_detach = git_env_bool("GIT_TEST_MAINT_AUTO_DETACH", true);
 
 	maint->git_cmd = 1;
 	maint->close_object_store = 1;
@@ -1847,7 +1971,7 @@ int run_auto_maintenance(int quiet)
 	return run_command(&maint);
 }
 
-void prepare_other_repo_env(struct strvec *env, const char *new_git_dir)
+void sanitize_repo_env(struct strvec *env)
 {
 	const char * const *var;
 
@@ -1856,6 +1980,11 @@ void prepare_other_repo_env(struct strvec *env, const char *new_git_dir)
 		    strcmp(*var, CONFIG_COUNT_ENVIRONMENT))
 			strvec_push(env, *var);
 	}
+}
+
+void prepare_other_repo_env(struct strvec *env, const char *new_git_dir)
+{
+	sanitize_repo_env(env);
 	strvec_pushf(env, "%s=%s", GIT_DIR_ENVIRONMENT, new_git_dir);
 }
 
diff --git a/run-command.h b/run-command.h
index 0df25e4..428d76e 100644
--- a/run-command.h
+++ b/run-command.h
@@ -421,6 +421,21 @@ typedef int (*start_failure_fn)(struct strbuf *out,
 				void *pp_task_cb);
 
 /**
+ * This callback is repeatedly called on every child process who requests
+ * start_command() to create a pipe by setting child_process.in < 0.
+ *
+ * pp_cb is the callback cookie as passed into run_processes_parallel, and
+ * pp_task_cb is the callback cookie as passed into get_next_task_fn.
+ *
+ * Returns < 0 for error
+ * Returns == 0 when there is more data to be fed (will be called again)
+ * Returns > 0 when finished (child closed fd or no more data to be fed)
+ */
+typedef int (*feed_pipe_fn)(int child_in,
+				void *pp_cb,
+				void *pp_task_cb);
+
+/**
  * This callback is called on every child process that finished processing.
  *
  * See run_processes_parallel() below for a discussion of the "struct
@@ -473,6 +488,12 @@ struct run_process_parallel_opts
 	 */
 	start_failure_fn start_failure;
 
+	/*
+	 * feed_pipe: see feed_pipe_fn() above. This can be NULL to omit any
+	 * special handling.
+	 */
+	feed_pipe_fn feed_pipe;
+
 	/**
 	 * task_finished: See task_finished_fn() above. This can be
 	 * NULL to omit any special handling.
@@ -510,12 +531,17 @@ struct run_process_parallel_opts
 void run_processes_parallel(const struct run_process_parallel_opts *opts);
 
 /**
+ * Unset all local-repo GIT_* variables in env; see local_repo_env in
+ * environment.h. GIT_CONFIG_PARAMETERS and GIT_CONFIG_COUNT are preserved
+ * to pass -c and --config-env options from the parent process.
+ */
+void sanitize_repo_env(struct strvec *env);
+
+/**
  * Convenience function which prepares env for a command to be run in a
- * new repo. This adds all GIT_* environment variables to env with the
- * exception of GIT_CONFIG_PARAMETERS and GIT_CONFIG_COUNT (which cause the
- * corresponding environment variables to be unset in the subprocess) and adds
- * an environment variable pointing to new_git_dir. See local_repo_env in
- * environment.h for more information.
+ * new repo. This removes variables pointing to the local repository (using
+ * sanitize_repo_env() above), and adds an environment variable pointing to
+ * new_git_dir.
  */
 void prepare_other_repo_env(struct strvec *env, const char *new_git_dir);
 
diff --git a/scalar.c b/scalar.c
index f754311..4efb6ac 100644
--- a/scalar.c
+++ b/scalar.c
@@ -19,6 +19,7 @@
 #include "help.h"
 #include "setup.h"
 #include "trace2.h"
+#include "path.h"
 
 static void setup_enlistment_directory(int argc, const char **argv,
 				       const char * const *usagestr,
@@ -95,7 +96,17 @@ struct scalar_config {
 	int overwrite_on_reconfigure;
 };
 
-static int set_scalar_config(const struct scalar_config *config, int reconfigure)
+static int set_scalar_config(const char *key, const char *value)
+{
+	char *file = repo_git_path(the_repository, "config");
+	int res = repo_config_set_multivar_in_file_gently(the_repository, file,
+							  key, value, NULL,
+							  " # set by scalar", 0);
+	free(file);
+	return res;
+}
+
+static int set_config_if_missing(const struct scalar_config *config, int reconfigure)
 {
 	char *value = NULL;
 	int res;
@@ -103,7 +114,7 @@ static int set_scalar_config(const struct scalar_config *config, int reconfigure
 	if ((reconfigure && config->overwrite_on_reconfigure) ||
 	    repo_config_get_string(the_repository, config->key, &value)) {
 		trace2_data_string("scalar", the_repository, config->key, "created");
-		res = repo_config_set_gently(the_repository, config->key, config->value);
+		res = set_scalar_config(config->key, config->value);
 	} else {
 		trace2_data_string("scalar", the_repository, config->key, "exists");
 		res = 0;
@@ -121,14 +132,38 @@ static int have_fsmonitor_support(void)
 
 static int set_recommended_config(int reconfigure)
 {
+	/*
+	 * Be sure to update Documentation/scalar.adoc if you add, update,
+	 * or remove any of these recommended settings.
+	 */
 	struct scalar_config config[] = {
-		/* Required */
-		{ "am.keepCR", "true", 1 },
-		{ "core.FSCache", "true", 1 },
-		{ "core.multiPackIndex", "true", 1 },
-		{ "core.preloadIndex", "true", 1 },
+		{ "am.keepCR", "true" },
+		{ "commitGraph.changedPaths", "true" },
+		{ "commitGraph.generationVersion", "1" },
+		{ "core.autoCRLF", "false" },
+		{ "core.logAllRefUpdates", "true" },
+		{ "core.safeCRLF", "false" },
+		{ "credential.https://dev.azure.com.useHttpPath", "true" },
+		{ "feature.experimental", "false" },
+		{ "feature.manyFiles", "false" },
+		{ "fetch.showForcedUpdates", "false" },
+		{ "fetch.unpackLimit", "1" },
+		{ "fetch.writeCommitGraph", "false" },
+		{ "gc.auto", "0" },
+		{ "gui.GCWarning", "false" },
+		{ "index.skipHash", "true", 1 /* Fix previous setting. */ },
+		{ "index.threads", "true"},
+		{ "index.version", "4" },
+		{ "merge.renames", "true" },
+		{ "merge.stat", "false" },
+		{ "pack.useBitmaps", "false" },
+		{ "pack.usePathWalk", "true" },
+		{ "receive.autoGC", "false" },
+		{ "status.aheadBehind", "false" },
+
+		/* platform-specific */
 #ifndef WIN32
-		{ "core.untrackedCache", "true", 1 },
+		{ "core.untrackedCache", "true" },
 #else
 		/*
 		 * Unfortunately, Scalar's Functional Tests demonstrated
@@ -142,50 +177,25 @@ static int set_recommended_config(int reconfigure)
 		 * Therefore, with a sad heart, we disable this very useful
 		 * feature on Windows.
 		 */
-		{ "core.untrackedCache", "false", 1 },
+		{ "core.untrackedCache", "false" },
+
+		/* Other Windows-specific required settings: */
+		{ "http.sslBackend", "schannel" },
 #endif
-		{ "core.logAllRefUpdates", "true", 1 },
-		{ "credential.https://dev.azure.com.useHttpPath", "true", 1 },
-		{ "credential.validate", "false", 1 }, /* GCM4W-only */
-		{ "gc.auto", "0", 1 },
-		{ "gui.GCWarning", "false", 1 },
-		{ "index.skipHash", "false", 1 },
-		{ "index.threads", "true", 1 },
-		{ "index.version", "4", 1 },
-		{ "merge.stat", "false", 1 },
-		{ "merge.renames", "true", 1 },
-		{ "pack.useBitmaps", "false", 1 },
-		{ "pack.useSparse", "true", 1 },
-		{ "receive.autoGC", "false", 1 },
-		{ "feature.manyFiles", "false", 1 },
-		{ "feature.experimental", "false", 1 },
-		{ "fetch.unpackLimit", "1", 1 },
-		{ "fetch.writeCommitGraph", "false", 1 },
-#ifdef WIN32
-		{ "http.sslBackend", "schannel", 1 },
-#endif
-		/* Optional */
-		{ "status.aheadBehind", "false" },
-		{ "commitGraph.changedPaths", "true" },
-		{ "commitGraph.generationVersion", "1" },
-		{ "core.autoCRLF", "false" },
-		{ "core.safeCRLF", "false" },
-		{ "fetch.showForcedUpdates", "false" },
-		{ "pack.usePathWalk", "true" },
 		{ NULL, NULL },
 	};
 	int i;
 	char *value;
 
 	for (i = 0; config[i].key; i++) {
-		if (set_scalar_config(config + i, reconfigure))
+		if (set_config_if_missing(config + i, reconfigure))
 			return error(_("could not configure %s=%s"),
 				     config[i].key, config[i].value);
 	}
 
 	if (have_fsmonitor_support()) {
 		struct scalar_config fsmonitor = { "core.fsmonitor", "true" };
-		if (set_scalar_config(&fsmonitor, reconfigure))
+		if (set_config_if_missing(&fsmonitor, reconfigure))
 			return error(_("could not configure %s=%s"),
 				     fsmonitor.key, fsmonitor.value);
 	}
@@ -197,9 +207,8 @@ static int set_recommended_config(int reconfigure)
 	if (repo_config_get_string(the_repository, "log.excludeDecoration", &value)) {
 		trace2_data_string("scalar", the_repository,
 				   "log.excludeDecoration", "created");
-		if (repo_config_set_multivar_gently(the_repository, "log.excludeDecoration",
-						    "refs/prefetch/*",
-						    CONFIG_REGEX_NONE, 0))
+		if (set_scalar_config("log.excludeDecoration",
+					    "refs/prefetch/*"))
 			return error(_("could not configure "
 				       "log.excludeDecoration"));
 	} else {
@@ -384,7 +393,7 @@ static int delete_enlistment(struct strbuf *enlistment)
 {
 	struct strbuf parent = STRBUF_INIT;
 	size_t offset;
-	char *path_sep;
+	const char *path_sep;
 
 	if (unregister_dir())
 		return error(_("failed to unregister repository"));
@@ -931,7 +940,7 @@ static int cmd_delete(int argc, const char **argv)
 	if (dir_inside_of(cwd, enlistment.buf) >= 0)
 		res = error(_("refusing to delete current working directory"));
 	else {
-		close_object_store(the_repository->objects);
+		odb_close(the_repository->objects);
 		res = delete_enlistment(&enlistment);
 	}
 	strbuf_release(&enlistment);
diff --git a/sequencer.c b/sequencer.c
index 5476d39..aafd0bc 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -767,7 +767,7 @@ static int do_recursive_merge(struct repository *r,
 		o.buffer_output = 2;
 	o.show_rename_progress = 1;
 
-	head_tree = parse_tree_indirect(head);
+	head_tree = repo_parse_tree_indirect(the_repository, head);
 	if (!head_tree)
 		return error(_("unable to read tree (%s)"), oid_to_hex(head));
 	next_tree = next ? repo_get_commit_tree(r, next) : empty_tree(r);
@@ -1292,32 +1292,40 @@ int update_head_with_reflog(const struct commit *old_head,
 	return ret;
 }
 
+static int pipe_from_strbuf(int hook_stdin_fd, void *pp_cb, void *pp_task_cb UNUSED)
+{
+	struct hook_cb_data *hook_cb = pp_cb;
+	struct strbuf *to_pipe = hook_cb->options->feed_pipe_ctx;
+	int ret;
+
+	if (!to_pipe)
+		BUG("pipe_from_strbuf called without feed_pipe_ctx");
+
+	ret = write_in_full(hook_stdin_fd, to_pipe->buf, to_pipe->len);
+	if (ret < 0 && errno != EPIPE)
+		return ret;
+
+	return 1; /* done writing */
+}
+
 static int run_rewrite_hook(const struct object_id *oldoid,
 			    const struct object_id *newoid)
 {
-	struct child_process proc = CHILD_PROCESS_INIT;
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
 	int code;
 	struct strbuf sb = STRBUF_INIT;
-	const char *hook_path = find_hook(the_repository, "post-rewrite");
 
-	if (!hook_path)
-		return 0;
-
-	strvec_pushl(&proc.args, hook_path, "amend", NULL);
-	proc.in = -1;
-	proc.stdout_to_stderr = 1;
-	proc.trace2_hook_name = "post-rewrite";
-
-	code = start_command(&proc);
-	if (code)
-		return code;
 	strbuf_addf(&sb, "%s %s\n", oid_to_hex(oldoid), oid_to_hex(newoid));
-	sigchain_push(SIGPIPE, SIG_IGN);
-	write_in_full(proc.in, sb.buf, sb.len);
-	close(proc.in);
+
+	opt.feed_pipe_ctx = &sb;
+	opt.feed_pipe = pipe_from_strbuf;
+
+	strvec_push(&opt.args, "amend");
+
+	code = run_hooks_opt(the_repository, "post-rewrite", &opt);
+
 	strbuf_release(&sb);
-	sigchain_pop(SIGPIPE);
-	return finish_command(&proc);
+	return code;
 }
 
 void commit_post_rewrite(struct repository *r,
@@ -1558,7 +1566,7 @@ static int try_to_commit(struct repository *r,
 			res = error(_("unable to parse commit author"));
 			goto out;
 		}
-		parents = copy_commit_list(current_head->parents);
+		parents = commit_list_copy(current_head->parents);
 		extra = read_commit_extra_headers(current_head, exclude_gpgsig);
 	} else if (current_head &&
 		   (!(flags & CREATE_ROOT_COMMIT) || (flags & AMEND_MSG))) {
@@ -1690,7 +1698,7 @@ static int try_to_commit(struct repository *r,
 
 out:
 	free_commit_extra_headers(extra);
-	free_commit_list(parents);
+	commit_list_free(parents);
 	strbuf_release(&err);
 	strbuf_release(&commit_msg);
 	free(amend_author);
@@ -2468,8 +2476,8 @@ static int do_pick_commit(struct repository *r,
 		res |= try_merge_command(r, opts->strategy,
 					 opts->xopts.nr, opts->xopts.v,
 					common, oid_to_hex(&head), remotes);
-		free_commit_list(common);
-		free_commit_list(remotes);
+		commit_list_free(common);
+		commit_list_free(remotes);
 	}
 
 	/*
@@ -4044,7 +4052,7 @@ static int do_reset(struct repository *r,
 		goto cleanup;
 	}
 
-	tree = parse_tree_indirect(&oid);
+	tree = repo_parse_tree_indirect(the_repository, &oid);
 	if (!tree)
 		return error(_("unable to read tree (%s)"), oid_to_hex(&oid));
 	prime_cache_tree(r, r->index, tree);
@@ -4309,7 +4317,7 @@ static int do_merge(struct repository *r,
 		      git_path_merge_head(r), 0);
 	write_message("no-ff", 5, git_path_merge_mode(r), 0);
 
-	bases = reverse_commit_list(bases);
+	bases = commit_list_reverse(bases);
 
 	repo_read_index(r);
 	init_ui_merge_options(&o, r);
@@ -4373,8 +4381,8 @@ static int do_merge(struct repository *r,
 leave_merge:
 	strbuf_release(&ref_name);
 	rollback_lock_file(&lock);
-	free_commit_list(to_merge);
-	free_commit_list(bases);
+	commit_list_free(to_merge);
+	commit_list_free(bases);
 	return ret;
 }
 
@@ -6031,11 +6039,11 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 			oidset_insert(&shown, oid);
 		}
 
-		free_commit_list(list);
+		commit_list_free(list);
 	}
 
-	free_commit_list(commits);
-	free_commit_list(tips);
+	commit_list_free(commits);
+	commit_list_free(tips);
 
 	strbuf_release(&label_from_message);
 	strbuf_release(&oneline);
diff --git a/serve.c b/serve.c
index 53ecab3..49a6e39 100644
--- a/serve.c
+++ b/serve.c
@@ -14,7 +14,7 @@
 
 static int advertise_sid = -1;
 static int advertise_object_info = -1;
-static int client_hash_algo = GIT_HASH_SHA1_LEGACY;
+static uint32_t client_hash_algo = GIT_HASH_SHA1_LEGACY;
 
 static int always_advertise(struct repository *r UNUSED,
 			    struct strbuf *value UNUSED)
diff --git a/server-info.c b/server-info.c
index b9a7105..4243e24 100644
--- a/server-info.c
+++ b/server-info.c
@@ -148,23 +148,21 @@ static int update_info_file(struct repository *r, char *path,
 	return ret;
 }
 
-static int add_info_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,
-			int flag UNUSED,
-			void *cb_data)
+static int add_info_ref(const struct reference *ref, void *cb_data)
 {
 	struct update_info_ctx *uic = cb_data;
-	struct object *o = parse_object(uic->repo, oid);
+	struct object *o = parse_object(uic->repo, ref->oid);
 	if (!o)
 		return -1;
 
-	if (uic_printf(uic, "%s	%s\n", oid_to_hex(oid), path) < 0)
+	if (uic_printf(uic, "%s	%s\n", oid_to_hex(ref->oid), ref->name) < 0)
 		return -1;
 
 	if (o->type == OBJ_TAG) {
-		o = deref_tag(uic->repo, o, path, 0);
+		o = deref_tag(uic->repo, o, ref->name, 0);
 		if (o)
 			if (uic_printf(uic, "%s	%s^{}\n",
-				oid_to_hex(&o->oid), path) < 0)
+				oid_to_hex(&o->oid), ref->name) < 0)
 				return -1;
 	}
 	return 0;
diff --git a/setup.c b/setup.c
index 7086741..7ec4427 100644
--- a/setup.c
+++ b/setup.c
@@ -22,11 +22,9 @@
 #include "chdir-notify.h"
 #include "path.h"
 #include "quote.h"
-#include "tmp-objdir.h"
 #include "trace.h"
 #include "trace2.h"
 #include "worktree.h"
-#include "exec-cmd.h"
 
 static int inside_git_dir = -1;
 static int inside_work_tree = -1;
@@ -633,6 +631,21 @@ static enum extension_result handle_extension_v0(const char *var,
 		return EXTENSION_UNKNOWN;
 }
 
+static void parse_reference_uri(const char *value, char **format,
+				char **payload)
+{
+	const char *schema_end;
+
+	schema_end = strstr(value, "://");
+	if (!schema_end) {
+		*format = xstrdup(value);
+		*payload = NULL;
+	} else {
+		*format = xstrndup(value, schema_end - value);
+		*payload = xstrdup_or_null(schema_end + 3);
+	}
+}
+
 /*
  * Record any new extensions in this function.
  */
@@ -675,10 +688,17 @@ static enum extension_result handle_extension(const char *var,
 		return EXTENSION_OK;
 	} else if (!strcmp(ext, "refstorage")) {
 		unsigned int format;
+		char *format_str;
 
 		if (!value)
 			return config_error_nonbool(var);
-		format = ref_storage_format_by_name(value);
+
+		parse_reference_uri(value, &format_str,
+				    &data->ref_storage_payload);
+
+		format = ref_storage_format_by_name(format_str);
+		free(format_str);
+
 		if (format == REF_STORAGE_FORMAT_UNKNOWN)
 			return error(_("invalid value for '%s': '%s'"),
 				     "extensions.refstorage", value);
@@ -687,6 +707,9 @@ static enum extension_result handle_extension(const char *var,
 	} else if (!strcmp(ext, "relativeworktrees")) {
 		data->relative_worktrees = git_config_bool(var, value);
 		return EXTENSION_OK;
+	} else if (!strcmp(ext, "submodulepathconfig")) {
+		data->submodule_path_cfg = git_config_bool(var, value);
+		return EXTENSION_OK;
 	}
 	return EXTENSION_UNKNOWN;
 }
@@ -851,6 +874,7 @@ void clear_repository_format(struct repository_format *format)
 	string_list_clear(&format->v1_only_extensions, 0);
 	free(format->work_tree);
 	free(format->partial_clone);
+	free(format->ref_storage_payload);
 	init_repository_format(format);
 }
 
@@ -896,8 +920,10 @@ int verify_repository_format(const struct repository_format *format,
 void read_gitfile_error_die(int error_code, const char *path, const char *dir)
 {
 	switch (error_code) {
-	case READ_GITFILE_ERR_STAT_FAILED:
 	case READ_GITFILE_ERR_NOT_A_FILE:
+	case READ_GITFILE_ERR_STAT_FAILED:
+	case READ_GITFILE_ERR_MISSING:
+	case READ_GITFILE_ERR_IS_A_DIR:
 		/* non-fatal; follow return path */
 		break;
 	case READ_GITFILE_ERR_OPEN_FAILED:
@@ -940,8 +966,14 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)
 	static struct strbuf realpath = STRBUF_INIT;
 
 	if (stat(path, &st)) {
-		/* NEEDSWORK: discern between ENOENT vs other errors */
-		error_code = READ_GITFILE_ERR_STAT_FAILED;
+		if (errno == ENOENT || errno == ENOTDIR)
+			error_code = READ_GITFILE_ERR_MISSING;
+		else
+			error_code = READ_GITFILE_ERR_STAT_FAILED;
+		goto cleanup_return;
+	}
+	if (S_ISDIR(st.st_mode)) {
+		error_code = READ_GITFILE_ERR_IS_A_DIR;
 		goto cleanup_return;
 	}
 	if (!S_ISREG(st.st_mode)) {
@@ -1002,6 +1034,83 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)
 	return error_code ? NULL : path;
 }
 
+static void setup_git_env_internal(const char *git_dir,
+				   bool skip_initializing_odb)
+{
+	char *git_replace_ref_base;
+	const char *shallow_file;
+	const char *replace_ref_base;
+	struct set_gitdir_args args = { NULL };
+	struct strvec to_free = STRVEC_INIT;
+
+	args.commondir = getenv_safe(&to_free, GIT_COMMON_DIR_ENVIRONMENT);
+	args.object_dir = getenv_safe(&to_free, DB_ENVIRONMENT);
+	args.graft_file = getenv_safe(&to_free, GRAFT_ENVIRONMENT);
+	args.index_file = getenv_safe(&to_free, INDEX_ENVIRONMENT);
+	args.alternate_db = getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT);
+	if (getenv(GIT_QUARANTINE_ENVIRONMENT))
+		args.disable_ref_updates = true;
+	args.skip_initializing_odb = skip_initializing_odb;
+
+	repo_set_gitdir(the_repository, git_dir, &args);
+	strvec_clear(&to_free);
+
+	if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
+		disable_replace_refs();
+	replace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);
+	git_replace_ref_base = xstrdup(replace_ref_base ? replace_ref_base
+							  : "refs/replace/");
+	update_ref_namespace(NAMESPACE_REPLACE, git_replace_ref_base);
+
+	shallow_file = getenv(GIT_SHALLOW_FILE_ENVIRONMENT);
+	if (shallow_file)
+		set_alternate_shallow_file(the_repository, shallow_file, 0);
+
+	if (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0))
+		fetch_if_missing = 0;
+}
+
+void setup_git_env(const char *git_dir)
+{
+	setup_git_env_internal(git_dir, false);
+}
+
+static void set_git_dir_1(const char *path, bool skip_initializing_odb)
+{
+	xsetenv(GIT_DIR_ENVIRONMENT, path, 1);
+	setup_git_env_internal(path, skip_initializing_odb);
+}
+
+static void update_relative_gitdir(const char *name UNUSED,
+				   const char *old_cwd,
+				   const char *new_cwd,
+				   void *data UNUSED)
+{
+	char *path = reparent_relative_path(old_cwd, new_cwd,
+					    repo_get_git_dir(the_repository));
+	trace_printf_key(&trace_setup_key,
+			 "setup: move $GIT_DIR to '%s'",
+			 path);
+	set_git_dir_1(path, true);
+	free(path);
+}
+
+static void set_git_dir(const char *path, int make_realpath)
+{
+	struct strbuf realpath = STRBUF_INIT;
+
+	if (make_realpath) {
+		strbuf_realpath(&realpath, path, 1);
+		path = realpath.buf;
+	}
+
+	set_git_dir_1(path, false);
+	if (!is_absolute_path(path))
+		chdir_notify_register(NULL, update_relative_gitdir, NULL);
+
+	strbuf_release(&realpath);
+}
+
 static const char *setup_explicit_git_dir(const char *gitdirenv,
 					  struct strbuf *cwd,
 					  struct repository_format *repo_fmt,
@@ -1248,7 +1357,7 @@ static int safe_directory_cb(const char *key, const char *value,
 	} else {
 		char *allowed = NULL;
 
-		if (!git_config_pathname(&allowed, key, value)) {
+		if (!git_config_pathname(&allowed, key, value) && allowed) {
 			char *normalized = NULL;
 
 			/*
@@ -1500,20 +1609,37 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
 		if (offset > min_offset)
 			strbuf_addch(dir, '/');
 		strbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);
-		gitdirenv = read_gitfile_gently(dir->buf, die_on_error ?
-						NULL : &error_code);
+		gitdirenv = read_gitfile_gently(dir->buf, &error_code);
 		if (!gitdirenv) {
-			if (die_on_error ||
-			    error_code == READ_GITFILE_ERR_NOT_A_FILE) {
-				/* NEEDSWORK: fail if .git is not file nor dir */
+			switch (error_code) {
+			case READ_GITFILE_ERR_MISSING:
+				/* no .git in this directory, move on */
+				break;
+			case READ_GITFILE_ERR_IS_A_DIR:
 				if (is_git_directory(dir->buf)) {
 					gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;
 					gitdir_path = xstrdup(dir->buf);
 				}
-			} else if (error_code != READ_GITFILE_ERR_STAT_FAILED)
-				return GIT_DIR_INVALID_GITFILE;
-		} else
+				break;
+			case READ_GITFILE_ERR_STAT_FAILED:
+				if (die_on_error)
+					die(_("error reading '%s'"), dir->buf);
+				else
+					return GIT_DIR_INVALID_GITFILE;
+			case READ_GITFILE_ERR_NOT_A_FILE:
+				if (die_on_error)
+					die(_("not a regular file: '%s'"), dir->buf);
+				else
+					return GIT_DIR_INVALID_GITFILE;
+			default:
+				if (die_on_error)
+					read_gitfile_error_die(error_code, dir->buf, NULL);
+				else
+					return GIT_DIR_INVALID_GITFILE;
+			}
+		} else {
 			gitfile = xstrdup(dir->buf);
+		}
 		/*
 		 * Earlier, we tentatively added DEFAULT_GIT_DIR_ENVIRONMENT
 		 * to check that directory for a repository.
@@ -1628,79 +1754,85 @@ enum discovery_result discover_git_directory_reason(struct strbuf *commondir,
 	return result;
 }
 
-void setup_git_env(const char *git_dir)
+const char *enter_repo(const char *path, unsigned flags)
 {
-	char *git_replace_ref_base;
-	const char *shallow_file;
-	const char *replace_ref_base;
-	struct set_gitdir_args args = { NULL };
-	struct strvec to_free = STRVEC_INIT;
+	static struct strbuf validated_path = STRBUF_INIT;
+	static struct strbuf used_path = STRBUF_INIT;
 
-	args.commondir = getenv_safe(&to_free, GIT_COMMON_DIR_ENVIRONMENT);
-	args.object_dir = getenv_safe(&to_free, DB_ENVIRONMENT);
-	args.graft_file = getenv_safe(&to_free, GRAFT_ENVIRONMENT);
-	args.index_file = getenv_safe(&to_free, INDEX_ENVIRONMENT);
-	args.alternate_db = getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT);
-	if (getenv(GIT_QUARANTINE_ENVIRONMENT)) {
-		args.disable_ref_updates = 1;
+	if (!path)
+		return NULL;
+
+	if (!(flags & ENTER_REPO_STRICT)) {
+		static const char *suffix[] = {
+			"/.git", "", ".git/.git", ".git", NULL,
+		};
+		const char *gitfile;
+		int len = strlen(path);
+		int i;
+		while ((1 < len) && (path[len-1] == '/'))
+			len--;
+
+		/*
+		 * We can handle arbitrary-sized buffers, but this remains as a
+		 * sanity check on untrusted input.
+		 */
+		if (PATH_MAX <= len)
+			return NULL;
+
+		strbuf_reset(&used_path);
+		strbuf_reset(&validated_path);
+		strbuf_add(&used_path, path, len);
+		strbuf_add(&validated_path, path, len);
+
+		if (used_path.buf[0] == '~') {
+			char *newpath = interpolate_path(used_path.buf, 0);
+			if (!newpath)
+				return NULL;
+			strbuf_attach(&used_path, newpath, strlen(newpath),
+				      strlen(newpath));
+		}
+		for (i = 0; suffix[i]; i++) {
+			struct stat st;
+			size_t baselen = used_path.len;
+			strbuf_addstr(&used_path, suffix[i]);
+			if (!stat(used_path.buf, &st) &&
+			    (S_ISREG(st.st_mode) ||
+			    (S_ISDIR(st.st_mode) && is_git_directory(used_path.buf)))) {
+				strbuf_addstr(&validated_path, suffix[i]);
+				break;
+			}
+			strbuf_setlen(&used_path, baselen);
+		}
+		if (!suffix[i])
+			return NULL;
+		gitfile = read_gitfile(used_path.buf);
+		if (!(flags & ENTER_REPO_ANY_OWNER_OK))
+			die_upon_dubious_ownership(gitfile, NULL, used_path.buf);
+		if (gitfile) {
+			strbuf_reset(&used_path);
+			strbuf_addstr(&used_path, gitfile);
+		}
+		if (chdir(used_path.buf))
+			return NULL;
+		path = validated_path.buf;
+	}
+	else {
+		const char *gitfile = read_gitfile(path);
+		if (!(flags & ENTER_REPO_ANY_OWNER_OK))
+			die_upon_dubious_ownership(gitfile, NULL, path);
+		if (gitfile)
+			path = gitfile;
+		if (chdir(path))
+			return NULL;
 	}
 
-	repo_set_gitdir(the_repository, git_dir, &args);
-	strvec_clear(&to_free);
-
-	if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
-		disable_replace_refs();
-	replace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);
-	git_replace_ref_base = xstrdup(replace_ref_base ? replace_ref_base
-							  : "refs/replace/");
-	update_ref_namespace(NAMESPACE_REPLACE, git_replace_ref_base);
-
-	shallow_file = getenv(GIT_SHALLOW_FILE_ENVIRONMENT);
-	if (shallow_file)
-		set_alternate_shallow_file(the_repository, shallow_file, 0);
-
-	if (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0))
-		fetch_if_missing = 0;
-}
-
-static void set_git_dir_1(const char *path)
-{
-	xsetenv(GIT_DIR_ENVIRONMENT, path, 1);
-	setup_git_env(path);
-}
-
-static void update_relative_gitdir(const char *name UNUSED,
-				   const char *old_cwd,
-				   const char *new_cwd,
-				   void *data UNUSED)
-{
-	char *path = reparent_relative_path(old_cwd, new_cwd,
-					    repo_get_git_dir(the_repository));
-	struct tmp_objdir *tmp_objdir = tmp_objdir_unapply_primary_odb();
-
-	trace_printf_key(&trace_setup_key,
-			 "setup: move $GIT_DIR to '%s'",
-			 path);
-	set_git_dir_1(path);
-	if (tmp_objdir)
-		tmp_objdir_reapply_primary_odb(tmp_objdir, old_cwd, new_cwd);
-	free(path);
-}
-
-void set_git_dir(const char *path, int make_realpath)
-{
-	struct strbuf realpath = STRBUF_INIT;
-
-	if (make_realpath) {
-		strbuf_realpath(&realpath, path, 1);
-		path = realpath.buf;
+	if (is_git_directory(".")) {
+		set_git_dir(".", 0);
+		check_repository_format(NULL);
+		return path;
 	}
 
-	set_git_dir_1(path);
-	if (!is_absolute_path(path))
-		chdir_notify_register(NULL, update_relative_gitdir, NULL);
-
-	strbuf_release(&realpath);
+	return NULL;
 }
 
 static int git_work_tree_initialized;
@@ -1733,6 +1865,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
 	static struct strbuf cwd = STRBUF_INIT;
 	struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT, report = STRBUF_INIT;
 	const char *prefix = NULL;
+	const char *ref_backend_uri;
 	struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
 
 	/*
@@ -1860,11 +1993,14 @@ const char *setup_git_directory_gently(int *nongit_ok)
 			repo_set_compat_hash_algo(the_repository,
 						  repo_fmt.compat_hash_algo);
 			repo_set_ref_storage_format(the_repository,
-						    repo_fmt.ref_storage_format);
+						    repo_fmt.ref_storage_format,
+						    repo_fmt.ref_storage_payload);
 			the_repository->repository_format_worktree_config =
 				repo_fmt.worktree_config;
 			the_repository->repository_format_relative_worktrees =
 				repo_fmt.relative_worktrees;
+			the_repository->repository_format_submodule_path_cfg =
+				repo_fmt.submodule_path_cfg;
 			/* take ownership of repo_fmt.partial_clone */
 			the_repository->repository_format_partial_clone =
 				repo_fmt.partial_clone;
@@ -1889,6 +2025,25 @@ const char *setup_git_directory_gently(int *nongit_ok)
 		setenv(GIT_PREFIX_ENVIRONMENT, "", 1);
 	}
 
+	/*
+	 * The env variable should override the repository config
+	 * for 'extensions.refStorage'.
+	 */
+	ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT);
+	if (ref_backend_uri) {
+		char *backend, *payload;
+		enum ref_storage_format format;
+
+		parse_reference_uri(ref_backend_uri, &backend, &payload);
+		format = ref_storage_format_by_name(backend);
+		if (format == REF_STORAGE_FORMAT_UNKNOWN)
+			die(_("unknown ref storage format: '%s'"), backend);
+		repo_set_ref_storage_format(the_repository, format, payload);
+
+		free(backend);
+		free(payload);
+	}
+
 	setup_original_cwd();
 
 	strbuf_release(&dir);
@@ -1960,9 +2115,12 @@ void check_repository_format(struct repository_format *fmt)
 	repo_set_hash_algo(the_repository, fmt->hash_algo);
 	repo_set_compat_hash_algo(the_repository, fmt->compat_hash_algo);
 	repo_set_ref_storage_format(the_repository,
-				    fmt->ref_storage_format);
+				    fmt->ref_storage_format,
+				    fmt->ref_storage_payload);
 	the_repository->repository_format_worktree_config =
 		fmt->worktree_config;
+	the_repository->repository_format_submodule_path_cfg =
+		fmt->submodule_path_cfg;
 	the_repository->repository_format_relative_worktrees =
 		fmt->relative_worktrees;
 	the_repository->repository_format_partial_clone =
@@ -2221,6 +2379,7 @@ void initialize_repository_version(int hash_algo,
 {
 	struct strbuf repo_version = STRBUF_INIT;
 	int target_version = GIT_REPO_VERSION;
+	int default_submodule_path_config = 0;
 
 	/*
 	 * Note that we initialize the repository version to 1 when the ref
@@ -2230,7 +2389,8 @@ void initialize_repository_version(int hash_algo,
 	 * the remote repository's format.
 	 */
 	if (hash_algo != GIT_HASH_SHA1_LEGACY ||
-	    ref_storage_format != REF_STORAGE_FORMAT_FILES)
+	    ref_storage_format != REF_STORAGE_FORMAT_FILES ||
+	    the_repository->ref_storage_payload)
 		target_version = GIT_REPO_VERSION_READ;
 
 	if (hash_algo != GIT_HASH_SHA1_LEGACY && hash_algo != GIT_HASH_UNKNOWN)
@@ -2239,11 +2399,20 @@ void initialize_repository_version(int hash_algo,
 	else if (reinit)
 		repo_config_set_gently(the_repository, "extensions.objectformat", NULL);
 
-	if (ref_storage_format != REF_STORAGE_FORMAT_FILES)
+	if (the_repository->ref_storage_payload) {
+		struct strbuf ref_uri = STRBUF_INIT;
+
+		strbuf_addf(&ref_uri, "%s://%s",
+			    ref_storage_format_to_name(ref_storage_format),
+			    the_repository->ref_storage_payload);
+		repo_config_set(the_repository, "extensions.refstorage", ref_uri.buf);
+		strbuf_release(&ref_uri);
+	} else if (ref_storage_format != REF_STORAGE_FORMAT_FILES) {
 		repo_config_set(the_repository, "extensions.refstorage",
 				ref_storage_format_to_name(ref_storage_format));
-	else if (reinit)
+	} else if (reinit) {
 		repo_config_set_gently(the_repository, "extensions.refstorage", NULL);
+	}
 
 	if (reinit) {
 		struct strbuf config = STRBUF_INIT;
@@ -2259,6 +2428,15 @@ void initialize_repository_version(int hash_algo,
 		clear_repository_format(&repo_fmt);
 	}
 
+	repo_config_get_bool(the_repository, "init.defaultSubmodulePathConfig",
+			     &default_submodule_path_config);
+	if (default_submodule_path_config) {
+		/* extensions.submodulepathconfig requires at least version 1 */
+		if (target_version == 0)
+			target_version = 1;
+		repo_config_set(the_repository, "extensions.submodulepathconfig", "true");
+	}
+
 	strbuf_addf(&repo_version, "%d", target_version);
 	repo_config_set(the_repository, "core.repositoryformatversion", repo_version.buf);
 
@@ -2277,14 +2455,12 @@ static int is_reinit(void)
 	return ret;
 }
 
-void create_reference_database(enum ref_storage_format ref_storage_format,
-			       const char *initial_branch, int quiet)
+void create_reference_database(const char *initial_branch, int quiet)
 {
 	struct strbuf err = STRBUF_INIT;
 	char *to_free = NULL;
 	int reinit = is_reinit();
 
-	repo_set_ref_storage_format(the_repository, ref_storage_format);
 	if (ref_store_create_on_disk(get_main_ref_store(the_repository), 0, &err))
 		die("failed to set up refs db: %s", err.buf);
 
@@ -2518,6 +2694,7 @@ static void repository_format_configure(struct repository_format *repo_fmt,
 		.ignore_repo = 1,
 		.ignore_worktree = 1,
 	};
+	const char *ref_backend_uri;
 	const char *env;
 
 	config_with_options(read_default_format_config, &cfg, NULL, NULL, &opts);
@@ -2563,7 +2740,26 @@ static void repository_format_configure(struct repository_format *repo_fmt,
 	} else {
 		repo_fmt->ref_storage_format = REF_STORAGE_FORMAT_DEFAULT;
 	}
-	repo_set_ref_storage_format(the_repository, repo_fmt->ref_storage_format);
+
+
+	ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT);
+	if (ref_backend_uri) {
+		char *backend, *payload;
+		enum ref_storage_format format;
+
+		parse_reference_uri(ref_backend_uri, &backend, &payload);
+		format = ref_storage_format_by_name(backend);
+		if (format == REF_STORAGE_FORMAT_UNKNOWN)
+			die(_("unknown ref storage format: '%s'"), backend);
+
+		repo_fmt->ref_storage_format = format;
+		repo_fmt->ref_storage_payload = payload;
+
+		free(backend);
+	}
+
+	repo_set_ref_storage_format(the_repository, repo_fmt->ref_storage_format,
+				    repo_fmt->ref_storage_payload);
 }
 
 int init_db(const char *git_dir, const char *real_git_dir,
@@ -2611,7 +2807,7 @@ int init_db(const char *git_dir, const char *real_git_dir,
 	 * have set up the repository format such that we can evaluate
 	 * includeIf conditions correctly in the case of re-initialization.
 	 */
-	repo_config(the_repository, platform_core_config, NULL);
+	repo_config(the_repository, git_default_core_config, NULL);
 
 	safe_create_dir(the_repository, git_dir, 0);
 
@@ -2619,8 +2815,7 @@ int init_db(const char *git_dir, const char *real_git_dir,
 				      &repo_fmt, init_shared_repository);
 
 	if (!(flags & INIT_DB_SKIP_REFDB))
-		create_reference_database(repo_fmt.ref_storage_format,
-					  initial_branch, flags & INIT_DB_QUIET);
+		create_reference_database(initial_branch, flags & INIT_DB_QUIET);
 	create_object_directory();
 
 	if (repo_settings_get_shared_repository(the_repository)) {
diff --git a/setup.h b/setup.h
index 8522fa8..80bc6e5 100644
--- a/setup.h
+++ b/setup.h
@@ -36,6 +36,8 @@ int is_nonbare_repository_dir(struct strbuf *path);
 #define READ_GITFILE_ERR_NO_PATH 6
 #define READ_GITFILE_ERR_NOT_A_REPO 7
 #define READ_GITFILE_ERR_TOO_LARGE 8
+#define READ_GITFILE_ERR_MISSING 9
+#define READ_GITFILE_ERR_IS_A_DIR 10
 void read_gitfile_error_die(int error_code, const char *path, const char *dir);
 const char *read_gitfile_gently(const char *path, int *return_error_code);
 #define read_gitfile(path) read_gitfile_gently((path), NULL)
@@ -94,9 +96,46 @@ static inline int discover_git_directory(struct strbuf *commondir,
 	return 0;
 }
 
-void set_git_dir(const char *path, int make_realpath);
 void set_git_work_tree(const char *tree);
 
+/* Flags that can be passed to `enter_repo()`. */
+enum {
+	/*
+	 * Callers that require exact paths (as opposed to allowing known
+	 * suffixes like ".git", ".git/.git" to be omitted) can set this bit.
+	 */
+	ENTER_REPO_STRICT = (1<<0),
+
+	/*
+	 * Callers that are willing to run without ownership check can set this
+	 * bit.
+	 */
+	ENTER_REPO_ANY_OWNER_OK = (1<<1),
+};
+
+/*
+ * Discover and enter a repository.
+ *
+ * First, one directory to try is determined by the following algorithm.
+ *
+ * (0) If "strict" is given, the path is used as given and no DWIM is
+ *     done. Otherwise:
+ * (1) "~/path" to mean path under the running user's home directory;
+ * (2) "~user/path" to mean path under named user's home directory;
+ * (3) "relative/path" to mean cwd relative directory; or
+ * (4) "/absolute/path" to mean absolute directory.
+ *
+ * Unless "strict" is given, we check "%s/.git", "%s", "%s.git/.git", "%s.git"
+ * in this order. We select the first one that is a valid git repository, and
+ * chdir() to it. If none match, or we fail to chdir, we return NULL.
+ *
+ * If all goes well, we return the directory we used to chdir() (but
+ * before ~user is expanded), avoiding getcwd() resolving symbolic
+ * links.  User relative paths are also returned as they are given,
+ * except DWIM suffixing.
+ */
+const char *enter_repo(const char *path, unsigned flags);
+
 const char *setup_git_directory_gently(int *);
 const char *setup_git_directory(void);
 char *prefix_path(const char *prefix, int len, const char *path);
@@ -130,10 +169,12 @@ struct repository_format {
 	char *partial_clone; /* value of extensions.partialclone */
 	int worktree_config;
 	int relative_worktrees;
+	int submodule_path_cfg;
 	int is_bare;
 	int hash_algo;
 	int compat_hash_algo;
 	enum ref_storage_format ref_storage_format;
+	char *ref_storage_payload;
 	int sparse_index;
 	char *work_tree;
 	struct string_list unknown_extensions;
@@ -203,8 +244,7 @@ int init_db(const char *git_dir, const char *real_git_dir,
 void initialize_repository_version(int hash_algo,
 				   enum ref_storage_format ref_storage_format,
 				   int reinit);
-void create_reference_database(enum ref_storage_format ref_storage_format,
-			       const char *initial_branch, int quiet);
+void create_reference_database(const char *initial_branch, int quiet);
 
 /*
  * NOTE NOTE NOTE!!
diff --git a/shallow.c b/shallow.c
index d9cd4e2..7a3dd56 100644
--- a/shallow.c
+++ b/shallow.c
@@ -40,7 +40,7 @@ int register_shallow(struct repository *r, const struct object_id *oid)
 	oidcpy(&graft->oid, oid);
 	graft->nr_parent = -1;
 	if (commit && commit->object.parsed) {
-		free_commit_list(commit->parents);
+		commit_list_free(commit->parents);
 		commit->parents = NULL;
 	}
 	return register_commit_graft(r, graft, 0);
@@ -130,11 +130,24 @@ static void free_depth_in_slab(int **ptr)
 {
 	FREE_AND_NULL(*ptr);
 }
-struct commit_list *get_shallow_commits(struct object_array *heads, int depth,
-		int shallow_flag, int not_shallow_flag)
+/*
+ * This is a common internal function that can either return a list of
+ * shallow commits or calculate the current maximum depth of a shallow
+ * repository, depending on the input parameters.
+ *
+ * Depth calculation is triggered by passing the `shallows` parameter.
+ * In this case, the computed depth is stored in `max_cur_depth` (if it is
+ * provided), and the function returns NULL.
+ *
+ * Otherwise, `max_cur_depth` remains unchanged and the function returns
+ * a list of shallow commits.
+ */
+static struct commit_list *get_shallows_or_depth(struct object_array *heads,
+				struct object_array *shallows, int *max_cur_depth,
+				int depth, int shallow_flag, int not_shallow_flag)
 {
 	size_t i = 0;
-	int cur_depth = 0;
+	int cur_depth = 0, cur_depth_shallow = 0;
 	struct commit_list *result = NULL;
 	struct object_array stack = OBJECT_ARRAY_INIT;
 	struct commit *commit = NULL;
@@ -168,16 +181,30 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth,
 		}
 		parse_commit_or_die(commit);
 		cur_depth++;
-		if ((depth != INFINITE_DEPTH && cur_depth >= depth) ||
-		    (is_repository_shallow(the_repository) && !commit->parents &&
-		     (graft = lookup_commit_graft(the_repository, &commit->object.oid)) != NULL &&
-		     graft->nr_parent < 0)) {
-			commit_list_insert(commit, &result);
-			commit->object.flags |= shallow_flag;
-			commit = NULL;
-			continue;
+		if (shallows) {
+			for (size_t j = 0; j < shallows->nr; j++)
+				if (oideq(&commit->object.oid, &shallows->objects[j].item->oid))
+					if (!cur_depth_shallow || cur_depth < cur_depth_shallow)
+						cur_depth_shallow = cur_depth;
+
+			if ((is_repository_shallow(the_repository) && !commit->parents &&
+			     (graft = lookup_commit_graft(the_repository, &commit->object.oid)) != NULL &&
+			     graft->nr_parent < 0)) {
+				commit = NULL;
+				continue;
+			}
+		} else {
+			if ((depth != INFINITE_DEPTH && cur_depth >= depth) ||
+			    (is_repository_shallow(the_repository) && !commit->parents &&
+			     (graft = lookup_commit_graft(the_repository, &commit->object.oid)) != NULL &&
+			     graft->nr_parent < 0)) {
+				commit_list_insert(commit, &result);
+				commit->object.flags |= shallow_flag;
+				commit = NULL;
+				continue;
+			}
+			commit->object.flags |= not_shallow_flag;
 		}
-		commit->object.flags |= not_shallow_flag;
 		for (p = commit->parents, commit = NULL; p; p = p->next) {
 			int **depth_slot = commit_depth_at(&depths, p->item);
 			if (!*depth_slot) {
@@ -198,10 +225,32 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth,
 		}
 	}
 	deep_clear_commit_depth(&depths, free_depth_in_slab);
+	object_array_clear(&stack);
 
+	if (shallows && max_cur_depth)
+		*max_cur_depth = cur_depth_shallow;
 	return result;
 }
 
+int get_shallows_depth(struct object_array *heads, struct object_array *shallows)
+{
+	int max_cur_depth = 0;
+	get_shallows_or_depth(heads, shallows, &max_cur_depth, 0, 0, 0);
+	return max_cur_depth;
+
+}
+
+struct commit_list *get_shallow_commits(struct object_array *heads,
+					struct object_array *shallows, int deepen_relative,
+					int depth, int shallow_flag, int not_shallow_flag)
+{
+	if (shallows && deepen_relative) {
+		depth += get_shallows_depth(heads, shallows);
+	}
+	return get_shallows_or_depth(heads, NULL, NULL,
+				     depth, shallow_flag, not_shallow_flag);
+}
+
 static void show_commit(struct commit *commit, void *data)
 {
 	commit_list_insert(commit, data);
@@ -267,7 +316,7 @@ struct commit_list *get_shallow_commits_by_rev_list(struct strvec *argv,
 				break;
 			}
 	}
-	free_commit_list(not_shallow_list);
+	commit_list_free(not_shallow_list);
 
 	/*
 	 * Now we can clean up NOT_SHALLOW on border commits. Having
@@ -471,6 +520,7 @@ void prepare_shallow_info(struct shallow_info *info, struct oid_array *sa)
 {
 	trace_printf_key(&trace_shallow, "shallow: prepare_shallow_info\n");
 	memset(info, 0, sizeof(*info));
+	commit_stack_init(&info->commits);
 	info->shallow = sa;
 	if (!sa)
 		return;
@@ -503,6 +553,7 @@ void clear_shallow_info(struct shallow_info *info)
 	free(info->shallow_ref);
 	free(info->ours);
 	free(info->theirs);
+	commit_stack_clear(&info->commits);
 }
 
 /* Step 4, remove non-existent ones in "theirs" after getting the pack */
@@ -626,14 +677,10 @@ static void paint_down(struct paint_info *info, const struct object_id *oid,
 	free(tmp);
 }
 
-static int mark_uninteresting(const char *refname UNUSED,
-			      const char *referent UNUSED,
-			      const struct object_id *oid,
-			      int flags UNUSED,
-			      void *cb_data UNUSED)
+static int mark_uninteresting(const struct reference *ref, void *cb_data UNUSED)
 {
 	struct commit *commit = lookup_commit_reference_gently(the_repository,
-							       oid, 1);
+							       ref->oid, 1);
 	if (!commit)
 		return 0;
 	commit->object.flags |= UNINTERESTING;
@@ -713,7 +760,7 @@ void assign_shallow_commits_to_refs(struct shallow_info *info,
 
 	if (used) {
 		int bitmap_size = DIV_ROUND_UP(pi.nr_bits, 32) * sizeof(uint32_t);
-		memset(used, 0, sizeof(*used) * info->shallow->nr);
+		MEMZERO_ARRAY(used, info->shallow->nr);
 		for (i = 0; i < nr_shallow; i++) {
 			const struct commit *c = lookup_commit(the_repository,
 							       &oid[shallow[i]]);
@@ -737,23 +784,13 @@ void assign_shallow_commits_to_refs(struct shallow_info *info,
 	free(shallow);
 }
 
-struct commit_array {
-	struct commit **commits;
-	size_t nr, alloc;
-};
-
-static int add_ref(const char *refname UNUSED,
-		  const char *referent UNUSED,
-		   const struct object_id *oid,
-		   int flags UNUSED,
-		   void *cb_data)
+static int add_ref(const struct reference *ref, void *cb_data)
 {
-	struct commit_array *ca = cb_data;
-	ALLOC_GROW(ca->commits, ca->nr + 1, ca->alloc);
-	ca->commits[ca->nr] = lookup_commit_reference_gently(the_repository,
-							     oid, 1);
-	if (ca->commits[ca->nr])
-		ca->nr++;
+	struct commit_stack *cs = cb_data;
+	struct commit *commit = lookup_commit_reference_gently(the_repository,
+							       ref->oid, 1);
+	if (commit)
+		commit_stack_push(cs, commit);
 	return 0;
 }
 
@@ -778,11 +815,11 @@ static void post_assign_shallow(struct shallow_info *info,
 	uint32_t **bitmap;
 	size_t dst, i, j;
 	size_t bitmap_nr = DIV_ROUND_UP(info->ref->nr, 32);
-	struct commit_array ca;
+	struct commit_stack cs = COMMIT_STACK_INIT;
 
 	trace_printf_key(&trace_shallow, "shallow: post_assign_shallow\n");
 	if (ref_status)
-		memset(ref_status, 0, sizeof(*ref_status) * info->ref->nr);
+		MEMZERO_ARRAY(ref_status, info->ref->nr);
 
 	/* Remove unreachable shallow commits from "theirs" */
 	for (i = dst = 0; i < info->nr_theirs; i++) {
@@ -801,9 +838,8 @@ static void post_assign_shallow(struct shallow_info *info,
 	}
 	info->nr_theirs = dst;
 
-	memset(&ca, 0, sizeof(ca));
-	refs_head_ref(get_main_ref_store(the_repository), add_ref, &ca);
-	refs_for_each_ref(get_main_ref_store(the_repository), add_ref, &ca);
+	refs_head_ref(get_main_ref_store(the_repository), add_ref, &cs);
+	refs_for_each_ref(get_main_ref_store(the_repository), add_ref, &cs);
 
 	/* Remove unreachable shallow commits from "ours" */
 	for (i = dst = 0; i < info->nr_ours; i++) {
@@ -816,7 +852,7 @@ static void post_assign_shallow(struct shallow_info *info,
 		for (j = 0; j < bitmap_nr; j++)
 			if (bitmap[0][j]) {
 				/* Step 7, reachability test at commit level */
-				int ret = repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1);
+				int ret = repo_in_merge_bases_many(the_repository, c, cs.nr, cs.items, 1);
 				if (ret < 0)
 					exit(128);
 				if (!ret) {
@@ -828,7 +864,7 @@ static void post_assign_shallow(struct shallow_info *info,
 	}
 	info->nr_ours = dst;
 
-	free(ca.commits);
+	commit_stack_clear(&cs);
 }
 
 /* (Delayed) step 7, reachability test at commit level */
@@ -838,22 +874,17 @@ int delayed_reachability_test(struct shallow_info *si, int c)
 		struct commit *commit = lookup_commit(the_repository,
 						      &si->shallow->oid[c]);
 
-		if (!si->commits) {
-			struct commit_array ca;
-
-			memset(&ca, 0, sizeof(ca));
+		if (!si->commits.nr) {
 			refs_head_ref(get_main_ref_store(the_repository),
-				      add_ref, &ca);
+				      add_ref, &si->commits);
 			refs_for_each_ref(get_main_ref_store(the_repository),
-					  add_ref, &ca);
-			si->commits = ca.commits;
-			si->nr_commits = ca.nr;
+					  add_ref, &si->commits);
 		}
 
 		si->reachable[c] = repo_in_merge_bases_many(the_repository,
 							    commit,
-							    si->nr_commits,
-							    si->commits,
+							    si->commits.nr,
+							    si->commits.items,
 							    1);
 		if (si->reachable[c] < 0)
 			exit(128);
diff --git a/shallow.h b/shallow.h
index ad591bd..e20ca4c 100644
--- a/shallow.h
+++ b/shallow.h
@@ -1,6 +1,7 @@
 #ifndef SHALLOW_H
 #define SHALLOW_H
 
+#include "commit.h"
 #include "lockfile.h"
 #include "object.h"
 #include "repository.h"
@@ -35,7 +36,9 @@ int commit_shallow_file(struct repository *r, struct shallow_lock *lk);
 /* rollback $GIT_DIR/shallow and reset stat-validity checks */
 void rollback_shallow_file(struct repository *r, struct shallow_lock *lk);
 
+int get_shallows_depth(struct object_array *heads, struct object_array *shallows);
 struct commit_list *get_shallow_commits(struct object_array *heads,
+					struct object_array *shallows, int deepen_relative,
 					int depth, int shallow_flag, int not_shallow_flag);
 struct commit_list *get_shallow_commits_by_rev_list(struct strvec *argv,
 						    int shallow_flag, int not_shallow_flag);
@@ -69,8 +72,7 @@ struct shallow_info {
 	int *need_reachability_test;
 	int *reachable;
 	int *shallow_ref;
-	struct commit **commits;
-	size_t nr_commits;
+	struct commit_stack commits;
 };
 
 void prepare_shallow_info(struct shallow_info *, struct oid_array *);
diff --git a/sparse-index.c b/sparse-index.c
index 76f90da..13629c0 100644
--- a/sparse-index.c
+++ b/sparse-index.c
@@ -152,7 +152,9 @@ static int index_has_unmerged_entries(struct index_state *istate)
 
 int is_sparse_index_allowed(struct index_state *istate, int flags)
 {
-	if (!core_apply_sparse_checkout || !core_sparse_checkout_cone)
+	struct repo_config_values *cfg = repo_config_values(the_repository);
+
+	if (!cfg->apply_sparse_checkout || !core_sparse_checkout_cone)
 		return 0;
 
 	if (!(flags & SPARSE_INDEX_MEMORY_ONLY)) {
@@ -670,7 +672,9 @@ static void clear_skip_worktree_from_present_files_full(struct index_state *ista
 
 void clear_skip_worktree_from_present_files(struct index_state *istate)
 {
-	if (!core_apply_sparse_checkout ||
+	struct repo_config_values *cfg = repo_config_values(the_repository);
+
+	if (!cfg->apply_sparse_checkout ||
 	    sparse_expect_files_outside_of_patterns)
 		return;
 
diff --git a/src/cargo-meson.sh b/src/cargo-meson.sh
index 3998db0..75f3cd1 100755
--- a/src/cargo-meson.sh
+++ b/src/cargo-meson.sh
@@ -19,6 +19,18 @@
 	esac
 done
 
+case "$(cargo -vV | sed -n 's/^host: \(.*\)$/\1/p')" in
+	*-windows-msvc)
+		LIBNAME=gitcore.lib
+		PATH="$(echo "$PATH" | tr ':' '\n' | grep -Ev "^(/mingw64/bin|/usr/bin)$" | paste -sd: -):/mingw64/bin:/usr/bin"
+		export PATH
+		;;
+	*-windows-*)
+		LIBNAME=gitcore.lib;;
+	*)
+		LIBNAME=libgitcore.a;;
+esac
+
 cargo build --lib --quiet --manifest-path="$SOURCE_DIR/Cargo.toml" --target-dir="$BUILD_DIR" "$@"
 RET=$?
 if test $RET -ne 0
@@ -26,13 +38,6 @@
 	exit $RET
 fi
 
-case "$(cargo -vV | sed -s 's/^host: \(.*\)$/\1/')" in
-	*-windows-*)
-		LIBNAME=gitcore.lib;;
-	*)
-		LIBNAME=libgitcore.a;;
-esac
-
 if ! cmp "$BUILD_DIR/$BUILD_TYPE/$LIBNAME" "$BUILD_DIR/libgitcore.a" >/dev/null 2>&1
 then
 	cp "$BUILD_DIR/$BUILD_TYPE/$LIBNAME" "$BUILD_DIR/libgitcore.a"
diff --git a/src/csum_file.rs b/src/csum_file.rs
new file mode 100644
index 0000000..7f2c6c4
--- /dev/null
+++ b/src/csum_file.rs
@@ -0,0 +1,81 @@
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation: version 2 of the License, dated June 1991.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, see <https://www.gnu.org/licenses/>.
+
+use crate::hash::{HashAlgorithm, GIT_MAX_RAWSZ};
+use std::ffi::CStr;
+use std::io::{self, Write};
+use std::os::raw::c_void;
+
+/// A writer that can write files identified by their hash or containing a trailing hash.
+pub struct HashFile {
+    ptr: *mut c_void,
+    algo: HashAlgorithm,
+}
+
+impl HashFile {
+    /// Create a new HashFile.
+    ///
+    /// The hash used will be `algo`, its name should be in `name`, and an open file descriptor
+    /// pointing to that file should be in `fd`.
+    pub fn new(algo: HashAlgorithm, fd: i32, name: &CStr) -> HashFile {
+        HashFile {
+            ptr: unsafe { c::hashfd(algo.hash_algo_ptr(), fd, name.as_ptr()) },
+            algo,
+        }
+    }
+
+    /// Finalize this HashFile instance.
+    ///
+    /// Returns the hash computed over the data.
+    pub fn finalize(self, component: u32, flags: u32) -> Vec<u8> {
+        let mut result = vec![0u8; GIT_MAX_RAWSZ];
+        unsafe { c::finalize_hashfile(self.ptr, result.as_mut_ptr(), component, flags) };
+        result.truncate(self.algo.raw_len());
+        result
+    }
+}
+
+impl Write for HashFile {
+    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
+        for chunk in data.chunks(u32::MAX as usize) {
+            unsafe {
+                c::hashwrite(
+                    self.ptr,
+                    chunk.as_ptr() as *const c_void,
+                    chunk.len() as u32,
+                )
+            };
+        }
+        Ok(data.len())
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        unsafe { c::hashflush(self.ptr) };
+        Ok(())
+    }
+}
+
+pub mod c {
+    use std::os::raw::{c_char, c_int, c_void};
+
+    extern "C" {
+        pub fn hashfd(algop: *const c_void, fd: i32, name: *const c_char) -> *mut c_void;
+        pub fn hashwrite(f: *mut c_void, data: *const c_void, len: u32);
+        pub fn hashflush(f: *mut c_void);
+        pub fn finalize_hashfile(
+            f: *mut c_void,
+            data: *mut u8,
+            component: u32,
+            flags: u32,
+        ) -> c_int;
+    }
+}
diff --git a/src/hash.rs b/src/hash.rs
new file mode 100644
index 0000000..dea2998
--- /dev/null
+++ b/src/hash.rs
@@ -0,0 +1,466 @@
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation: version 2 of the License, dated June 1991.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, see <https://www.gnu.org/licenses/>.
+
+use std::error::Error;
+use std::fmt::{self, Debug, Display};
+use std::io::{self, Write};
+use std::os::raw::c_void;
+
+pub const GIT_MAX_RAWSZ: usize = 32;
+
+/// An error indicating an invalid hash algorithm.
+///
+/// The contained `u32` is the same as the `algo` field in `ObjectID`.
+#[derive(Debug, Copy, Clone)]
+pub struct InvalidHashAlgorithm(pub u32);
+
+impl Display for InvalidHashAlgorithm {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "invalid hash algorithm {}", self.0)
+    }
+}
+
+impl Error for InvalidHashAlgorithm {}
+
+/// A binary object ID.
+#[repr(C)]
+#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
+pub struct ObjectID {
+    pub hash: [u8; GIT_MAX_RAWSZ],
+    pub algo: u32,
+}
+
+#[allow(dead_code)]
+impl ObjectID {
+    /// Return a new object ID with the given algorithm and hash.
+    ///
+    /// `hash` must be exactly the proper length for `algo` and this function panics if it is not.
+    /// The extra internal storage of `hash`, if any, is zero filled.
+    pub fn new(algo: HashAlgorithm, hash: &[u8]) -> Self {
+        let mut data = [0u8; GIT_MAX_RAWSZ];
+        // This verifies that the length of `hash` is correct.
+        data[0..algo.raw_len()].copy_from_slice(hash);
+        Self {
+            hash: data,
+            algo: algo as u32,
+        }
+    }
+
+    /// Return the algorithm for this object ID.
+    ///
+    /// If the algorithm set internally is not valid, this function panics.
+    pub fn algo(&self) -> Result<HashAlgorithm, InvalidHashAlgorithm> {
+        HashAlgorithm::from_u32(self.algo).ok_or(InvalidHashAlgorithm(self.algo))
+    }
+
+    pub fn as_slice(&self) -> Result<&[u8], InvalidHashAlgorithm> {
+        match HashAlgorithm::from_u32(self.algo) {
+            Some(algo) => Ok(&self.hash[0..algo.raw_len()]),
+            None => Err(InvalidHashAlgorithm(self.algo)),
+        }
+    }
+
+    pub fn as_mut_slice(&mut self) -> Result<&mut [u8], InvalidHashAlgorithm> {
+        match HashAlgorithm::from_u32(self.algo) {
+            Some(algo) => Ok(&mut self.hash[0..algo.raw_len()]),
+            None => Err(InvalidHashAlgorithm(self.algo)),
+        }
+    }
+}
+
+impl Display for ObjectID {
+    /// Format this object ID as a hex object ID.
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let hash = self.as_slice().unwrap();
+        for x in hash {
+            write!(f, "{:02x}", x)?;
+        }
+        Ok(())
+    }
+}
+
+impl Debug for ObjectID {
+    /// Format this object ID as a hex object ID with a colon and name appended to it.
+    ///
+    /// ```
+    /// assert_eq!(
+    ///     format!("{:?}", HashAlgorithm::SHA256.null_oid()),
+    ///     "0000000000000000000000000000000000000000000000000000000000000000:sha256"
+    /// );
+    /// ```
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let hash = match self.as_slice() {
+            Ok(hash) => hash,
+            Err(_) => &self.hash,
+        };
+        for x in hash {
+            write!(f, "{:02x}", x)?;
+        }
+        match self.algo() {
+            Ok(algo) => write!(f, ":{}", algo.name()),
+            Err(e) => write!(f, ":invalid-hash-algo-{}", e.0),
+        }
+    }
+}
+
+/// A trait to implement hashing with a cryptographic algorithm.
+pub trait CryptoDigest {
+    /// Return true if this digest is safe for use with untrusted data, false otherwise.
+    fn is_safe(&self) -> bool;
+
+    /// Update the digest with the specified data.
+    fn update(&mut self, data: &[u8]);
+
+    /// Return an object ID, consuming the hasher.
+    fn into_oid(self) -> ObjectID;
+
+    /// Return a hash as a `Vec`, consuming the hasher.
+    fn into_vec(self) -> Vec<u8>;
+}
+
+/// A structure to hash data with a cryptographic hash algorithm.
+///
+/// Instances of this class are safe for use with untrusted data, provided Git has been compiled
+/// with a collision-detecting implementation of SHA-1.
+pub struct CryptoHasher {
+    algo: HashAlgorithm,
+    ctx: *mut c_void,
+}
+
+impl CryptoHasher {
+    /// Create a new hasher with the algorithm specified with `algo`.
+    ///
+    /// This hasher is safe to use on untrusted data.  If SHA-1 is selected and Git was compiled
+    /// with a collision-detecting implementation of SHA-1, then this function will use that
+    /// implementation and detect any attempts at a collision.
+    pub fn new(algo: HashAlgorithm) -> Self {
+        let ctx = unsafe { c::git_hash_alloc() };
+        unsafe { c::git_hash_init(ctx, algo.hash_algo_ptr()) };
+        Self { algo, ctx }
+    }
+}
+
+impl CryptoDigest for CryptoHasher {
+    /// Return true if this digest is safe for use with untrusted data, false otherwise.
+    fn is_safe(&self) -> bool {
+        true
+    }
+
+    /// Update the hasher with the specified data.
+    fn update(&mut self, data: &[u8]) {
+        unsafe { c::git_hash_update(self.ctx, data.as_ptr() as *const c_void, data.len()) };
+    }
+
+    /// Return an object ID, consuming the hasher.
+    fn into_oid(self) -> ObjectID {
+        let mut oid = ObjectID {
+            hash: [0u8; 32],
+            algo: self.algo as u32,
+        };
+        unsafe { c::git_hash_final_oid(&mut oid as *mut ObjectID as *mut c_void, self.ctx) };
+        oid
+    }
+
+    /// Return a hash as a `Vec`, consuming the hasher.
+    fn into_vec(self) -> Vec<u8> {
+        let mut v = vec![0u8; self.algo.raw_len()];
+        unsafe { c::git_hash_final(v.as_mut_ptr(), self.ctx) };
+        v
+    }
+}
+
+impl Clone for CryptoHasher {
+    fn clone(&self) -> Self {
+        let ctx = unsafe { c::git_hash_alloc() };
+        unsafe { c::git_hash_clone(ctx, self.ctx) };
+        Self {
+            algo: self.algo,
+            ctx,
+        }
+    }
+}
+
+impl Drop for CryptoHasher {
+    fn drop(&mut self) {
+        unsafe { c::git_hash_free(self.ctx) };
+    }
+}
+
+impl Write for CryptoHasher {
+    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
+        self.update(data);
+        Ok(data.len())
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+
+/// A hash algorithm,
+#[repr(C)]
+#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
+pub enum HashAlgorithm {
+    SHA1 = 1,
+    SHA256 = 2,
+}
+
+#[allow(dead_code)]
+impl HashAlgorithm {
+    const SHA1_NULL_OID: ObjectID = ObjectID {
+        hash: [0u8; 32],
+        algo: Self::SHA1 as u32,
+    };
+    const SHA256_NULL_OID: ObjectID = ObjectID {
+        hash: [0u8; 32],
+        algo: Self::SHA256 as u32,
+    };
+
+    const SHA1_EMPTY_TREE: ObjectID = ObjectID {
+        hash: *b"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+        algo: Self::SHA1 as u32,
+    };
+    const SHA256_EMPTY_TREE: ObjectID = ObjectID {
+        hash: *b"\x6e\xf1\x9b\x41\x22\x5c\x53\x69\xf1\xc1\x04\xd4\x5d\x8d\x85\xef\xa9\xb0\x57\xb5\x3b\x14\xb4\xb9\xb9\x39\xdd\x74\xde\xcc\x53\x21",
+        algo: Self::SHA256 as u32,
+    };
+
+    const SHA1_EMPTY_BLOB: ObjectID = ObjectID {
+        hash: *b"\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+        algo: Self::SHA1 as u32,
+    };
+    const SHA256_EMPTY_BLOB: ObjectID = ObjectID {
+        hash: *b"\x47\x3a\x0f\x4c\x3b\xe8\xa9\x36\x81\xa2\x67\xe3\xb1\xe9\xa7\xdc\xda\x11\x85\x43\x6f\xe1\x41\xf7\x74\x91\x20\xa3\x03\x72\x18\x13",
+        algo: Self::SHA256 as u32,
+    };
+
+    /// Return a hash algorithm based on the internal integer ID used by Git.
+    ///
+    /// Returns `None` if the algorithm doesn't indicate a valid algorithm.
+    pub const fn from_u32(algo: u32) -> Option<HashAlgorithm> {
+        match algo {
+            1 => Some(HashAlgorithm::SHA1),
+            2 => Some(HashAlgorithm::SHA256),
+            _ => None,
+        }
+    }
+
+    /// Return a hash algorithm based on the internal integer ID used by Git.
+    ///
+    /// Returns `None` if the algorithm doesn't indicate a valid algorithm.
+    pub const fn from_format_id(algo: u32) -> Option<HashAlgorithm> {
+        match algo {
+            0x73686131 => Some(HashAlgorithm::SHA1),
+            0x73323536 => Some(HashAlgorithm::SHA256),
+            _ => None,
+        }
+    }
+
+    /// The name of this hash algorithm as a string suitable for the configuration file.
+    pub const fn name(self) -> &'static str {
+        match self {
+            HashAlgorithm::SHA1 => "sha1",
+            HashAlgorithm::SHA256 => "sha256",
+        }
+    }
+
+    /// The format ID of this algorithm for binary formats.
+    ///
+    /// Note that when writing this to a data format, it should be written in big-endian format
+    /// explicitly.
+    pub const fn format_id(self) -> u32 {
+        match self {
+            HashAlgorithm::SHA1 => 0x73686131,
+            HashAlgorithm::SHA256 => 0x73323536,
+        }
+    }
+
+    /// The length of binary object IDs in this algorithm in bytes.
+    pub const fn raw_len(self) -> usize {
+        match self {
+            HashAlgorithm::SHA1 => 20,
+            HashAlgorithm::SHA256 => 32,
+        }
+    }
+
+    /// The length of object IDs in this algorithm in hexadecimal characters.
+    pub const fn hex_len(self) -> usize {
+        self.raw_len() * 2
+    }
+
+    /// The number of bytes which is processed by one iteration of this algorithm's compression
+    /// function.
+    pub const fn block_size(self) -> usize {
+        match self {
+            HashAlgorithm::SHA1 => 64,
+            HashAlgorithm::SHA256 => 64,
+        }
+    }
+
+    /// The object ID representing the empty blob.
+    pub const fn empty_blob(self) -> &'static ObjectID {
+        match self {
+            HashAlgorithm::SHA1 => &Self::SHA1_EMPTY_BLOB,
+            HashAlgorithm::SHA256 => &Self::SHA256_EMPTY_BLOB,
+        }
+    }
+
+    /// The object ID representing the empty tree.
+    pub const fn empty_tree(self) -> &'static ObjectID {
+        match self {
+            HashAlgorithm::SHA1 => &Self::SHA1_EMPTY_TREE,
+            HashAlgorithm::SHA256 => &Self::SHA256_EMPTY_TREE,
+        }
+    }
+
+    /// The object ID which is all zeros.
+    pub const fn null_oid(self) -> &'static ObjectID {
+        match self {
+            HashAlgorithm::SHA1 => &Self::SHA1_NULL_OID,
+            HashAlgorithm::SHA256 => &Self::SHA256_NULL_OID,
+        }
+    }
+
+    /// A pointer to the C `struct git_hash_algo` for interoperability with C.
+    pub fn hash_algo_ptr(self) -> *const c_void {
+        unsafe { c::hash_algo_ptr_by_number(self as u32) }
+    }
+
+    /// Create a hasher for this algorithm.
+    pub fn hasher(self) -> CryptoHasher {
+        CryptoHasher::new(self)
+    }
+}
+
+pub mod c {
+    use std::os::raw::c_void;
+
+    extern "C" {
+        pub fn hash_algo_ptr_by_number(n: u32) -> *const c_void;
+        pub fn unsafe_hash_algo(algop: *const c_void) -> *const c_void;
+        pub fn git_hash_alloc() -> *mut c_void;
+        pub fn git_hash_free(ctx: *mut c_void);
+        pub fn git_hash_init(dst: *mut c_void, algop: *const c_void);
+        pub fn git_hash_clone(dst: *mut c_void, src: *const c_void);
+        pub fn git_hash_update(ctx: *mut c_void, inp: *const c_void, len: usize);
+        pub fn git_hash_final(hash: *mut u8, ctx: *mut c_void);
+        pub fn git_hash_final_oid(hash: *mut c_void, ctx: *mut c_void);
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{CryptoDigest, HashAlgorithm, ObjectID};
+    use std::io::Write;
+
+    fn all_algos() -> &'static [HashAlgorithm] {
+        &[HashAlgorithm::SHA1, HashAlgorithm::SHA256]
+    }
+
+    #[test]
+    fn format_id_round_trips() {
+        for algo in all_algos() {
+            assert_eq!(
+                *algo,
+                HashAlgorithm::from_format_id(algo.format_id()).unwrap()
+            );
+        }
+    }
+
+    #[test]
+    fn offset_round_trips() {
+        for algo in all_algos() {
+            assert_eq!(*algo, HashAlgorithm::from_u32(*algo as u32).unwrap());
+        }
+    }
+
+    #[test]
+    fn slices_have_correct_length() {
+        for algo in all_algos() {
+            for oid in [algo.null_oid(), algo.empty_blob(), algo.empty_tree()] {
+                assert_eq!(oid.as_slice().unwrap().len(), algo.raw_len());
+            }
+        }
+    }
+
+    #[test]
+    fn object_ids_format_correctly() {
+        let entries = &[
+            (
+                HashAlgorithm::SHA1.null_oid(),
+                "0000000000000000000000000000000000000000",
+                "0000000000000000000000000000000000000000:sha1",
+            ),
+            (
+                HashAlgorithm::SHA1.empty_blob(),
+                "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
+                "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391:sha1",
+            ),
+            (
+                HashAlgorithm::SHA1.empty_tree(),
+                "4b825dc642cb6eb9a060e54bf8d69288fbee4904",
+                "4b825dc642cb6eb9a060e54bf8d69288fbee4904:sha1",
+            ),
+            (
+                HashAlgorithm::SHA256.null_oid(),
+                "0000000000000000000000000000000000000000000000000000000000000000",
+                "0000000000000000000000000000000000000000000000000000000000000000:sha256",
+            ),
+            (
+                HashAlgorithm::SHA256.empty_blob(),
+                "473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813",
+                "473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813:sha256",
+            ),
+            (
+                HashAlgorithm::SHA256.empty_tree(),
+                "6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321",
+                "6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321:sha256",
+            ),
+        ];
+        for (oid, display, debug) in entries {
+            assert_eq!(format!("{}", oid), *display);
+            assert_eq!(format!("{:?}", oid), *debug);
+        }
+    }
+
+    #[test]
+    fn hasher_works_correctly() {
+        for algo in all_algos() {
+            let tests: &[(&[u8], &ObjectID)] = &[
+                (b"blob 0\0", algo.empty_blob()),
+                (b"tree 0\0", algo.empty_tree()),
+            ];
+            for (data, oid) in tests {
+                let mut h = algo.hasher();
+                assert!(h.is_safe());
+                // Test that this works incrementally.
+                h.update(&data[0..2]);
+                h.update(&data[2..]);
+
+                let h2 = h.clone();
+
+                let actual_oid = h.into_oid();
+                assert_eq!(**oid, actual_oid);
+
+                let v = h2.into_vec();
+                assert_eq!((*oid).as_slice().unwrap(), &v);
+
+                let mut h = algo.hasher();
+                h.write_all(&data[0..2]).unwrap();
+                h.write_all(&data[2..]).unwrap();
+
+                let actual_oid = h.into_oid();
+                assert_eq!(**oid, actual_oid);
+            }
+        }
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 9da70d8..0c59829 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1 +1,4 @@
+pub mod csum_file;
+pub mod hash;
+pub mod loose;
 pub mod varint;
diff --git a/src/loose.rs b/src/loose.rs
new file mode 100644
index 0000000..24accf9
--- /dev/null
+++ b/src/loose.rs
@@ -0,0 +1,913 @@
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation: version 2 of the License, dated June 1991.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, see <https://www.gnu.org/licenses/>.
+
+use crate::hash::{HashAlgorithm, ObjectID, GIT_MAX_RAWSZ};
+use std::collections::BTreeMap;
+use std::convert::TryInto;
+use std::io::{self, Write};
+
+/// The type of object stored in the map.
+///
+/// If this value is `Reserved`, then it is never written to disk and is used primarily to store
+/// certain hard-coded objects, like the empty tree, empty blob, or null object ID.
+///
+/// If this value is `LooseObject`, then this represents a loose object.  `Shallow` represents a
+/// shallow commit, its parent, or its tree.  `Submodule` represents a submodule commit.
+#[repr(C)]
+#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
+pub enum MapType {
+    Reserved = 0,
+    LooseObject = 1,
+    Shallow = 2,
+    Submodule = 3,
+}
+
+impl MapType {
+    pub fn from_u32(n: u32) -> Option<MapType> {
+        match n {
+            0 => Some(Self::Reserved),
+            1 => Some(Self::LooseObject),
+            2 => Some(Self::Shallow),
+            3 => Some(Self::Submodule),
+            _ => None,
+        }
+    }
+}
+
+/// The value of an object stored in a `ObjectMemoryMap`.
+///
+/// This keeps the object ID to which the key is mapped and its kind together.
+struct MappedObject {
+    oid: ObjectID,
+    kind: MapType,
+}
+
+/// Memory storage for a loose object.
+struct ObjectMemoryMap {
+    to_compat: BTreeMap<ObjectID, MappedObject>,
+    to_storage: BTreeMap<ObjectID, MappedObject>,
+    compat: HashAlgorithm,
+    storage: HashAlgorithm,
+}
+
+impl ObjectMemoryMap {
+    /// Create a new `ObjectMemoryMap`.
+    ///
+    /// The storage and compatibility `HashAlgorithm` instances are used to store the object IDs in
+    /// the correct map.
+    fn new(storage: HashAlgorithm, compat: HashAlgorithm) -> Self {
+        Self {
+            to_compat: BTreeMap::new(),
+            to_storage: BTreeMap::new(),
+            compat,
+            storage,
+        }
+    }
+
+    fn len(&self) -> usize {
+        self.to_compat.len()
+    }
+
+    /// Write this map to an interface implementing `std::io::Write`.
+    fn write<W: Write>(&self, wrtr: W) -> io::Result<()> {
+        const VERSION_NUMBER: u32 = 1;
+        const NUM_OBJECT_FORMATS: u32 = 2;
+        const PADDING: [u8; 4] = [0u8; 4];
+
+        let mut wrtr = wrtr;
+        let header_size: u32 = (4 * 5) + (4 + 4 + 8) * NUM_OBJECT_FORMATS + 8;
+
+        wrtr.write_all(b"LMAP")?;
+        wrtr.write_all(&VERSION_NUMBER.to_be_bytes())?;
+        wrtr.write_all(&header_size.to_be_bytes())?;
+        wrtr.write_all(&(self.to_compat.len() as u32).to_be_bytes())?;
+        wrtr.write_all(&NUM_OBJECT_FORMATS.to_be_bytes())?;
+
+        let storage_short_len = self.find_short_name_len(&self.to_compat, self.storage);
+        let compat_short_len = self.find_short_name_len(&self.to_storage, self.compat);
+
+        let storage_npadding = Self::required_nul_padding(self.to_compat.len(), storage_short_len);
+        let compat_npadding = Self::required_nul_padding(self.to_compat.len(), compat_short_len);
+
+        let mut offset: u64 = header_size as u64;
+
+        for (algo, len, npadding) in &[
+            (self.storage, storage_short_len, storage_npadding),
+            (self.compat, compat_short_len, compat_npadding),
+        ] {
+            wrtr.write_all(&algo.format_id().to_be_bytes())?;
+            wrtr.write_all(&(*len as u32).to_be_bytes())?;
+
+            offset += *npadding;
+            wrtr.write_all(&offset.to_be_bytes())?;
+
+            offset += self.to_compat.len() as u64 * (*len as u64 + algo.raw_len() as u64 + 4);
+        }
+
+        wrtr.write_all(&offset.to_be_bytes())?;
+
+        let order_map: BTreeMap<&ObjectID, usize> = self
+            .to_compat
+            .keys()
+            .enumerate()
+            .map(|(i, oid)| (oid, i))
+            .collect();
+
+        wrtr.write_all(&PADDING[0..storage_npadding as usize])?;
+        for oid in self.to_compat.keys() {
+            wrtr.write_all(&oid.as_slice().unwrap()[0..storage_short_len])?;
+        }
+        for oid in self.to_compat.keys() {
+            wrtr.write_all(oid.as_slice().unwrap())?;
+        }
+        for meta in self.to_compat.values() {
+            wrtr.write_all(&(meta.kind as u32).to_be_bytes())?;
+        }
+
+        wrtr.write_all(&PADDING[0..compat_npadding as usize])?;
+        for oid in self.to_storage.keys() {
+            wrtr.write_all(&oid.as_slice().unwrap()[0..compat_short_len])?;
+        }
+        for meta in self.to_compat.values() {
+            wrtr.write_all(meta.oid.as_slice().unwrap())?;
+        }
+        for meta in self.to_storage.values() {
+            wrtr.write_all(&(order_map[&meta.oid] as u32).to_be_bytes())?;
+        }
+
+        Ok(())
+    }
+
+    fn required_nul_padding(nitems: usize, short_len: usize) -> u64 {
+        let shortened_table_len = nitems as u64 * short_len as u64;
+        let misalignment = shortened_table_len & 3;
+        // If the value is 0, return 0; otherwise, return the difference from 4.
+        (4 - misalignment) & 3
+    }
+
+    fn last_matching_offset(a: &ObjectID, b: &ObjectID, algop: HashAlgorithm) -> usize {
+        for i in 0..=algop.raw_len() {
+            if a.hash[i] != b.hash[i] {
+                return i;
+            }
+        }
+        algop.raw_len()
+    }
+
+    fn find_short_name_len(
+        &self,
+        map: &BTreeMap<ObjectID, MappedObject>,
+        algop: HashAlgorithm,
+    ) -> usize {
+        if map.len() <= 1 {
+            return 1;
+        }
+        let mut len = 1;
+        let mut iter = map.keys();
+        let mut cur = match iter.next() {
+            Some(cur) => cur,
+            None => return len,
+        };
+        for item in iter {
+            let offset = Self::last_matching_offset(cur, item, algop);
+            if offset >= len {
+                len = offset + 1;
+            }
+            cur = item;
+        }
+        if len > algop.raw_len() {
+            algop.raw_len()
+        } else {
+            len
+        }
+    }
+}
+
+struct ObjectFormatData {
+    data_off: usize,
+    shortened_len: usize,
+    full_off: usize,
+    mapping_off: Option<usize>,
+}
+
+pub struct MmapedObjectMapIter<'a> {
+    offset: usize,
+    algos: Vec<HashAlgorithm>,
+    source: &'a MmapedObjectMap<'a>,
+}
+
+impl<'a> Iterator for MmapedObjectMapIter<'a> {
+    type Item = Vec<ObjectID>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.offset >= self.source.nitems {
+            return None;
+        }
+        let offset = self.offset;
+        self.offset += 1;
+        let v: Vec<ObjectID> = self
+            .algos
+            .iter()
+            .cloned()
+            .filter_map(|algo| self.source.oid_from_offset(offset, algo))
+            .collect();
+        if v.len() != self.algos.len() {
+            return None;
+        }
+        Some(v)
+    }
+}
+
+#[allow(dead_code)]
+pub struct MmapedObjectMap<'a> {
+    memory: &'a [u8],
+    nitems: usize,
+    meta_off: usize,
+    obj_formats: BTreeMap<HashAlgorithm, ObjectFormatData>,
+    main_algo: HashAlgorithm,
+}
+
+#[derive(Debug)]
+#[allow(dead_code)]
+enum MmapedParseError {
+    HeaderTooSmall,
+    InvalidSignature,
+    InvalidVersion,
+    UnknownAlgorithm,
+    OffsetTooLarge,
+    TooFewObjectFormats,
+    UnalignedData,
+    InvalidTrailerOffset,
+}
+
+#[allow(dead_code)]
+impl<'a> MmapedObjectMap<'a> {
+    fn new(
+        slice: &'a [u8],
+        hash_algo: HashAlgorithm,
+    ) -> Result<MmapedObjectMap<'a>, MmapedParseError> {
+        let object_format_header_size = 4 + 4 + 8;
+        let trailer_offset_size = 8;
+        let header_size: usize =
+            4 + 4 + 4 + 4 + 4 + object_format_header_size * 2 + trailer_offset_size;
+        if slice.len() < header_size {
+            return Err(MmapedParseError::HeaderTooSmall);
+        }
+        if slice[0..4] != *b"LMAP" {
+            return Err(MmapedParseError::InvalidSignature);
+        }
+        if Self::u32_at_offset(slice, 4) != 1 {
+            return Err(MmapedParseError::InvalidVersion);
+        }
+        let _ = Self::u32_at_offset(slice, 8) as usize;
+        let nitems = Self::u32_at_offset(slice, 12) as usize;
+        let nobj_formats = Self::u32_at_offset(slice, 16) as usize;
+        if nobj_formats < 2 {
+            return Err(MmapedParseError::TooFewObjectFormats);
+        }
+        let mut offset = 20;
+        let mut meta_off = None;
+        let mut data = BTreeMap::new();
+        for i in 0..nobj_formats {
+            if offset + object_format_header_size + trailer_offset_size > slice.len() {
+                return Err(MmapedParseError::HeaderTooSmall);
+            }
+            let format_id = Self::u32_at_offset(slice, offset);
+            let shortened_len = Self::u32_at_offset(slice, offset + 4) as usize;
+            let data_off = Self::u64_at_offset(slice, offset + 8);
+
+            let algo = HashAlgorithm::from_format_id(format_id)
+                .ok_or(MmapedParseError::UnknownAlgorithm)?;
+            let data_off: usize = data_off
+                .try_into()
+                .map_err(|_| MmapedParseError::OffsetTooLarge)?;
+
+            // Every object format must have these entries.
+            let shortened_table_len = shortened_len
+                .checked_mul(nitems)
+                .ok_or(MmapedParseError::OffsetTooLarge)?;
+            let full_off = data_off
+                .checked_add(shortened_table_len)
+                .ok_or(MmapedParseError::OffsetTooLarge)?;
+            Self::verify_aligned(full_off)?;
+            Self::verify_valid(slice, full_off as u64)?;
+
+            let full_length = algo
+                .raw_len()
+                .checked_mul(nitems)
+                .ok_or(MmapedParseError::OffsetTooLarge)?;
+            let off = full_length
+                .checked_add(full_off)
+                .ok_or(MmapedParseError::OffsetTooLarge)?;
+            Self::verify_aligned(off)?;
+            Self::verify_valid(slice, off as u64)?;
+
+            // This is for the metadata for the first object format and for the order mapping for
+            // other object formats.
+            let meta_size = nitems
+                .checked_mul(4)
+                .ok_or(MmapedParseError::OffsetTooLarge)?;
+            let meta_end = off
+                .checked_add(meta_size)
+                .ok_or(MmapedParseError::OffsetTooLarge)?;
+            Self::verify_valid(slice, meta_end as u64)?;
+
+            let mut mapping_off = None;
+            if i == 0 {
+                meta_off = Some(off);
+            } else {
+                mapping_off = Some(off);
+            }
+
+            data.insert(
+                algo,
+                ObjectFormatData {
+                    data_off,
+                    shortened_len,
+                    full_off,
+                    mapping_off,
+                },
+            );
+            offset += object_format_header_size;
+        }
+        let trailer = Self::u64_at_offset(slice, offset);
+        Self::verify_aligned(trailer as usize)?;
+        Self::verify_valid(slice, trailer)?;
+        let end = trailer
+            .checked_add(hash_algo.raw_len() as u64)
+            .ok_or(MmapedParseError::OffsetTooLarge)?;
+        if end != slice.len() as u64 {
+            return Err(MmapedParseError::InvalidTrailerOffset);
+        }
+        match meta_off {
+            Some(meta_off) => Ok(MmapedObjectMap {
+                memory: slice,
+                nitems,
+                meta_off,
+                obj_formats: data,
+                main_algo: hash_algo,
+            }),
+            None => Err(MmapedParseError::TooFewObjectFormats),
+        }
+    }
+
+    fn iter(&self) -> MmapedObjectMapIter<'_> {
+        let mut algos = Vec::with_capacity(self.obj_formats.len());
+        algos.push(self.main_algo);
+        for algo in self.obj_formats.keys().cloned() {
+            if algo != self.main_algo {
+                algos.push(algo);
+            }
+        }
+        MmapedObjectMapIter {
+            offset: 0,
+            algos,
+            source: self,
+        }
+    }
+
+    /// Treats `sl` as if it were a set of slices of `wanted.len()` bytes, and searches for
+    /// `wanted` within it.
+    ///
+    /// If found, returns the offset of the subslice in `sl`.
+    ///
+    /// ```
+    /// let sl = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+    ///
+    /// assert_eq!(MmapedObjectMap::binary_search_slice(sl, &[2, 3]), Some(1));
+    /// assert_eq!(MmapedObjectMap::binary_search_slice(sl, &[6, 7]), Some(4));
+    /// assert_eq!(MmapedObjectMap::binary_search_slice(sl, &[1, 2]), None);
+    /// assert_eq!(MmapedObjectMap::binary_search_slice(sl, &[10, 20]), None);
+    /// ```
+    fn binary_search_slice(sl: &[u8], wanted: &[u8]) -> Option<usize> {
+        let len = wanted.len();
+        let res = sl.binary_search_by(|item| {
+            // We would like element_offset, but that is currently nightly only.  Instead, do a
+            // pointer subtraction to find the index.
+            let index = unsafe { (item as *const u8).offset_from(sl.as_ptr()) } as usize;
+            // Now we have the index of this object.  Round it down to the nearest full-sized
+            // chunk to find the actual offset where this starts.
+            let index = index - (index % len);
+            // Compute the comparison of that value instead, which will provide the expected
+            // result.
+            sl[index..index + wanted.len()].cmp(wanted)
+        });
+        res.ok().map(|offset| offset / len)
+    }
+
+    /// Look up `oid` in the map in order to convert it to `algo`.
+    ///
+    /// If this object is in the map, return the offset in the table for the main algorithm.
+    fn look_up_object(&self, oid: &ObjectID) -> Option<usize> {
+        let oid_algo = HashAlgorithm::from_u32(oid.algo)?;
+        let params = self.obj_formats.get(&oid_algo)?;
+        let short_table =
+            &self.memory[params.data_off..params.data_off + (params.shortened_len * self.nitems)];
+        let index = Self::binary_search_slice(
+            short_table,
+            &oid.as_slice().unwrap()[0..params.shortened_len],
+        )?;
+        match params.mapping_off {
+            Some(from_off) => {
+                // oid is in a compatibility algorithm.  Find the mapping index.
+                let mapped = Self::u32_at_offset(self.memory, from_off + index * 4) as usize;
+                if mapped >= self.nitems {
+                    return None;
+                }
+                let oid_offset = params.full_off + mapped * oid_algo.raw_len();
+                if self.memory[oid_offset..oid_offset + oid_algo.raw_len()]
+                    != *oid.as_slice().unwrap()
+                {
+                    return None;
+                }
+                Some(mapped)
+            }
+            None => {
+                // oid is in the main algorithm.  Find the object ID in the main map to confirm
+                // it's correct.
+                let oid_offset = params.full_off + index * oid_algo.raw_len();
+                if self.memory[oid_offset..oid_offset + oid_algo.raw_len()]
+                    != *oid.as_slice().unwrap()
+                {
+                    return None;
+                }
+                Some(index)
+            }
+        }
+    }
+
+    #[allow(dead_code)]
+    fn map_object(&self, oid: &ObjectID, algo: HashAlgorithm) -> Option<MappedObject> {
+        let main = self.look_up_object(oid)?;
+        let meta = MapType::from_u32(Self::u32_at_offset(self.memory, self.meta_off + (main * 4)))?;
+        Some(MappedObject {
+            oid: self.oid_from_offset(main, algo)?,
+            kind: meta,
+        })
+    }
+
+    fn map_oid(&self, oid: &ObjectID, algo: HashAlgorithm) -> Option<ObjectID> {
+        if algo as u32 == oid.algo {
+            return Some(oid.clone());
+        }
+
+        let main = self.look_up_object(oid)?;
+        self.oid_from_offset(main, algo)
+    }
+
+    fn oid_from_offset(&self, offset: usize, algo: HashAlgorithm) -> Option<ObjectID> {
+        let aparams = self.obj_formats.get(&algo)?;
+
+        let mut hash = [0u8; GIT_MAX_RAWSZ];
+        let len = algo.raw_len();
+        let oid_off = aparams.full_off + (offset * len);
+        hash[0..len].copy_from_slice(&self.memory[oid_off..oid_off + len]);
+        Some(ObjectID {
+            hash,
+            algo: algo as u32,
+        })
+    }
+
+    fn u32_at_offset(slice: &[u8], offset: usize) -> u32 {
+        u32::from_be_bytes(slice[offset..offset + 4].try_into().unwrap())
+    }
+
+    fn u64_at_offset(slice: &[u8], offset: usize) -> u64 {
+        u64::from_be_bytes(slice[offset..offset + 8].try_into().unwrap())
+    }
+
+    fn verify_aligned(offset: usize) -> Result<(), MmapedParseError> {
+        if (offset & 3) != 0 {
+            return Err(MmapedParseError::UnalignedData);
+        }
+        Ok(())
+    }
+
+    fn verify_valid(slice: &[u8], offset: u64) -> Result<(), MmapedParseError> {
+        if offset >= slice.len() as u64 {
+            return Err(MmapedParseError::OffsetTooLarge);
+        }
+        Ok(())
+    }
+}
+
+/// A map for loose and other non-packed object IDs that maps between a storage and compatibility
+/// mapping.
+///
+/// In addition to the in-memory option, there is an optional batched storage, which can be used to
+/// write objects to disk in an efficient way.
+pub struct ObjectMap {
+    mem: ObjectMemoryMap,
+    batch: Option<ObjectMemoryMap>,
+}
+
+impl ObjectMap {
+    /// Create a new `ObjectMap` with the given hash algorithms.
+    ///
+    /// This initializes the memory map to automatically map the empty tree, empty blob, and null
+    /// object ID.
+    pub fn new(storage: HashAlgorithm, compat: HashAlgorithm) -> Self {
+        let mut map = ObjectMemoryMap::new(storage, compat);
+        for (main, compat) in &[
+            (storage.empty_tree(), compat.empty_tree()),
+            (storage.empty_blob(), compat.empty_blob()),
+            (storage.null_oid(), compat.null_oid()),
+        ] {
+            map.to_storage.insert(
+                (*compat).clone(),
+                MappedObject {
+                    oid: (*main).clone(),
+                    kind: MapType::Reserved,
+                },
+            );
+            map.to_compat.insert(
+                (*main).clone(),
+                MappedObject {
+                    oid: (*compat).clone(),
+                    kind: MapType::Reserved,
+                },
+            );
+        }
+        Self {
+            mem: map,
+            batch: None,
+        }
+    }
+
+    pub fn hash_algo(&self) -> HashAlgorithm {
+        self.mem.storage
+    }
+
+    /// Start a batch for efficient writing.
+    ///
+    /// If there is already a batch started, this does nothing and the existing batch is retained.
+    pub fn start_batch(&mut self) {
+        if self.batch.is_none() {
+            self.batch = Some(ObjectMemoryMap::new(self.mem.storage, self.mem.compat));
+        }
+    }
+
+    pub fn batch_len(&self) -> Option<usize> {
+        self.batch.as_ref().map(|b| b.len())
+    }
+
+    /// If a batch exists, write it to the writer.
+    pub fn finish_batch<W: Write>(&mut self, w: W) -> io::Result<()> {
+        if let Some(txn) = self.batch.take() {
+            txn.write(w)?;
+        }
+        Ok(())
+    }
+
+    /// If a batch exists, write it to the writer.
+    pub fn abort_batch(&mut self) {
+        self.batch = None;
+    }
+
+    /// Return whether there is a batch already started.
+    ///
+    /// If you just want a batch to exist and don't care whether one has already been started, you
+    /// may simply call `start_batch` unconditionally.
+    pub fn has_batch(&self) -> bool {
+        self.batch.is_some()
+    }
+
+    /// Insert an object into the map.
+    ///
+    /// If `write` is true and there is a batch started, write the object into the batch as well as
+    /// into the memory map.
+    pub fn insert(&mut self, oid1: &ObjectID, oid2: &ObjectID, kind: MapType, write: bool) {
+        let (compat_oid, storage_oid) =
+            if HashAlgorithm::from_u32(oid1.algo) == Some(self.mem.compat) {
+                (oid1, oid2)
+            } else {
+                (oid2, oid1)
+            };
+        Self::insert_into(&mut self.mem, storage_oid, compat_oid, kind);
+        if write {
+            if let Some(ref mut batch) = self.batch {
+                Self::insert_into(batch, storage_oid, compat_oid, kind);
+            }
+        }
+    }
+
+    fn insert_into(
+        map: &mut ObjectMemoryMap,
+        storage: &ObjectID,
+        compat: &ObjectID,
+        kind: MapType,
+    ) {
+        map.to_compat.insert(
+            storage.clone(),
+            MappedObject {
+                oid: compat.clone(),
+                kind,
+            },
+        );
+        map.to_storage.insert(
+            compat.clone(),
+            MappedObject {
+                oid: storage.clone(),
+                kind,
+            },
+        );
+    }
+
+    #[allow(dead_code)]
+    fn map_object(&self, oid: &ObjectID, algo: HashAlgorithm) -> Option<&MappedObject> {
+        let map = if algo == self.mem.storage {
+            &self.mem.to_storage
+        } else {
+            &self.mem.to_compat
+        };
+        map.get(oid)
+    }
+
+    #[allow(dead_code)]
+    fn map_oid<'a, 'b: 'a>(
+        &'b self,
+        oid: &'a ObjectID,
+        algo: HashAlgorithm,
+    ) -> Option<&'a ObjectID> {
+        if algo as u32 == oid.algo {
+            return Some(oid);
+        }
+        let entry = self.map_object(oid, algo);
+        entry.map(|obj| &obj.oid)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{MapType, MmapedObjectMap, ObjectMap, ObjectMemoryMap};
+    use crate::hash::{CryptoDigest, CryptoHasher, HashAlgorithm, ObjectID};
+    use std::convert::TryInto;
+    use std::io::{self, Cursor, Write};
+
+    struct TrailingWriter {
+        curs: Cursor<Vec<u8>>,
+        hasher: CryptoHasher,
+    }
+
+    impl TrailingWriter {
+        fn new() -> Self {
+            Self {
+                curs: Cursor::new(Vec::new()),
+                hasher: CryptoHasher::new(HashAlgorithm::SHA256),
+            }
+        }
+
+        fn finalize(mut self) -> Vec<u8> {
+            let _ = self.hasher.flush();
+            let mut v = self.curs.into_inner();
+            v.extend(self.hasher.into_vec());
+            v
+        }
+    }
+
+    impl Write for TrailingWriter {
+        fn write(&mut self, data: &[u8]) -> io::Result<usize> {
+            self.hasher.write_all(data)?;
+            self.curs.write_all(data)?;
+            Ok(data.len())
+        }
+
+        fn flush(&mut self) -> io::Result<()> {
+            self.hasher.flush()?;
+            self.curs.flush()?;
+            Ok(())
+        }
+    }
+
+    fn sha1_oid(b: &[u8]) -> ObjectID {
+        assert_eq!(b.len(), 20);
+        let mut data = [0u8; 32];
+        data[0..20].copy_from_slice(b);
+        ObjectID {
+            hash: data,
+            algo: HashAlgorithm::SHA1 as u32,
+        }
+    }
+
+    fn sha256_oid(b: &[u8]) -> ObjectID {
+        assert_eq!(b.len(), 32);
+        ObjectID {
+            hash: b.try_into().unwrap(),
+            algo: HashAlgorithm::SHA256 as u32,
+        }
+    }
+
+    #[allow(clippy::type_complexity)]
+    fn test_entries() -> &'static [(&'static str, &'static [u8], &'static [u8], MapType, bool)] {
+        // These are all example blobs containing the content in the first argument.
+        &[
+            ("abc", b"\xf2\xba\x8f\x84\xab\x5c\x1b\xce\x84\xa7\xb4\x41\xcb\x19\x59\xcf\xc7\x09\x3b\x7f", b"\xc1\xcf\x6e\x46\x50\x77\x93\x0e\x88\xdc\x51\x36\x64\x1d\x40\x2f\x72\xa2\x29\xdd\xd9\x96\xf6\x27\xd6\x0e\x96\x39\xea\xba\x35\xa6", MapType::LooseObject, false),
+            ("def", b"\x0c\x00\x38\x32\xe7\xbf\xa9\xca\x8b\x5c\x20\x35\xc9\xbd\x68\x4a\x5f\x26\x23\xbc", b"\x8a\x90\x17\x26\x48\x4d\xb0\xf2\x27\x9f\x30\x8d\x58\x96\xd9\x6b\xf6\x3a\xd6\xde\x95\x7c\xa3\x8a\xdc\x33\x61\x68\x03\x6e\xf6\x63", MapType::Shallow, true),
+            ("ghi", b"\x45\xa8\x2e\x29\x5c\x52\x47\x31\x14\xc5\x7c\x18\xf4\xf5\x23\x68\xdf\x2a\x3c\xfd", b"\x6e\x47\x4c\x74\xf5\xd7\x78\x14\xc7\xf7\xf0\x7c\x37\x80\x07\x90\x53\x42\xaf\x42\x81\xe6\x86\x8d\x33\x46\x45\x4b\xb8\x63\xab\xc3", MapType::Submodule, false),
+            ("jkl", b"\x45\x32\x8c\x36\xff\x2e\x9b\x9b\x4e\x59\x2c\x84\x7d\x3f\x9a\x7f\xd9\xb3\xe7\x16", b"\xc3\xee\xf7\x54\xa2\x1e\xc6\x9d\x43\x75\xbe\x6f\x18\x47\x89\xa8\x11\x6f\xd9\x66\xfc\x67\xdc\x31\xd2\x11\x15\x42\xc8\xd5\xa0\xaf", MapType::LooseObject, true),
+        ]
+    }
+
+    fn test_map(write_all: bool) -> Box<ObjectMap> {
+        let mut map = Box::new(ObjectMap::new(HashAlgorithm::SHA256, HashAlgorithm::SHA1));
+
+        map.start_batch();
+
+        for (_blob_content, sha1, sha256, kind, swap) in test_entries() {
+            let s256 = sha256_oid(sha256);
+            let s1 = sha1_oid(sha1);
+            let write = write_all || (*kind as u32 & 2) == 0;
+            if *swap {
+                // Insert the item into the batch arbitrarily based on the type.  This tests that
+                // we can specify either order and we'll do the right thing.
+                map.insert(&s256, &s1, *kind, write);
+            } else {
+                map.insert(&s1, &s256, *kind, write);
+            }
+        }
+
+        map
+    }
+
+    #[test]
+    fn can_read_and_write_format() {
+        for full in &[true, false] {
+            let mut map = test_map(*full);
+            let mut wrtr = TrailingWriter::new();
+            map.finish_batch(&mut wrtr).unwrap();
+
+            assert!(!map.has_batch());
+
+            let data = wrtr.finalize();
+            MmapedObjectMap::new(&data, HashAlgorithm::SHA256).unwrap();
+        }
+    }
+
+    #[test]
+    fn looks_up_from_mmaped() {
+        let mut map = test_map(true);
+        let mut wrtr = TrailingWriter::new();
+        map.finish_batch(&mut wrtr).unwrap();
+
+        assert!(!map.has_batch());
+
+        let data = wrtr.finalize();
+        let entries = test_entries();
+        let map = MmapedObjectMap::new(&data, HashAlgorithm::SHA256).unwrap();
+
+        for (_, sha1, sha256, kind, _) in entries {
+            let s256 = sha256_oid(sha256);
+            let s1 = sha1_oid(sha1);
+
+            let res = map.map_object(&s256, HashAlgorithm::SHA1).unwrap();
+            assert_eq!(res.oid, s1);
+            assert_eq!(res.kind, *kind);
+            let res = map.map_oid(&s256, HashAlgorithm::SHA1).unwrap();
+            assert_eq!(res, s1);
+
+            let res = map.map_object(&s256, HashAlgorithm::SHA256).unwrap();
+            assert_eq!(res.oid, s256);
+            assert_eq!(res.kind, *kind);
+            let res = map.map_oid(&s256, HashAlgorithm::SHA256).unwrap();
+            assert_eq!(res, s256);
+
+            let res = map.map_object(&s1, HashAlgorithm::SHA256).unwrap();
+            assert_eq!(res.oid, s256);
+            assert_eq!(res.kind, *kind);
+            let res = map.map_oid(&s1, HashAlgorithm::SHA256).unwrap();
+            assert_eq!(res, s256);
+
+            let res = map.map_object(&s1, HashAlgorithm::SHA1).unwrap();
+            assert_eq!(res.oid, s1);
+            assert_eq!(res.kind, *kind);
+            let res = map.map_oid(&s1, HashAlgorithm::SHA1).unwrap();
+            assert_eq!(res, s1);
+        }
+
+        for octet in &[0x00u8, 0x6d, 0x6e, 0x8a, 0xff] {
+            let missing_oid = ObjectID {
+                hash: [*octet; 32],
+                algo: HashAlgorithm::SHA256 as u32,
+            };
+
+            assert!(map.map_object(&missing_oid, HashAlgorithm::SHA1).is_none());
+            assert!(map.map_oid(&missing_oid, HashAlgorithm::SHA1).is_none());
+
+            assert_eq!(
+                map.map_oid(&missing_oid, HashAlgorithm::SHA256).unwrap(),
+                missing_oid
+            );
+        }
+    }
+
+    #[test]
+    fn binary_searches_slices_correctly() {
+        let sl = &[
+            0, 1, 2, 15, 14, 13, 18, 10, 2, 20, 20, 20, 21, 21, 0, 21, 21, 1, 21, 21, 21, 21, 21,
+            22, 22, 23, 24,
+        ];
+
+        let expected: &[(&[u8], Option<usize>)] = &[
+            (&[0, 1, 2], Some(0)),
+            (&[15, 14, 13], Some(1)),
+            (&[18, 10, 2], Some(2)),
+            (&[20, 20, 20], Some(3)),
+            (&[21, 21, 0], Some(4)),
+            (&[21, 21, 1], Some(5)),
+            (&[21, 21, 21], Some(6)),
+            (&[21, 21, 22], Some(7)),
+            (&[22, 23, 24], Some(8)),
+            (&[2, 15, 14], None),
+            (&[0, 21, 21], None),
+            (&[21, 21, 23], None),
+            (&[22, 22, 23], None),
+            (&[0xff, 0xff, 0xff], None),
+            (&[0, 0, 0], None),
+        ];
+
+        for (wanted, value) in expected {
+            assert_eq!(MmapedObjectMap::binary_search_slice(sl, wanted), *value);
+        }
+    }
+
+    #[test]
+    fn looks_up_oid_correctly() {
+        let map = test_map(false);
+        let entries = test_entries();
+
+        let s256 = sha256_oid(entries[0].2);
+        let s1 = sha1_oid(entries[0].1);
+
+        let missing_oid = ObjectID {
+            hash: [0xffu8; 32],
+            algo: HashAlgorithm::SHA256 as u32,
+        };
+
+        let res = map.map_object(&s256, HashAlgorithm::SHA1).unwrap();
+        assert_eq!(res.oid, s1);
+        assert_eq!(res.kind, MapType::LooseObject);
+        let res = map.map_oid(&s256, HashAlgorithm::SHA1).unwrap();
+        assert_eq!(*res, s1);
+
+        let res = map.map_object(&s1, HashAlgorithm::SHA256).unwrap();
+        assert_eq!(res.oid, s256);
+        assert_eq!(res.kind, MapType::LooseObject);
+        let res = map.map_oid(&s1, HashAlgorithm::SHA256).unwrap();
+        assert_eq!(*res, s256);
+
+        assert!(map.map_object(&missing_oid, HashAlgorithm::SHA1).is_none());
+        assert!(map.map_oid(&missing_oid, HashAlgorithm::SHA1).is_none());
+
+        assert_eq!(
+            *map.map_oid(&missing_oid, HashAlgorithm::SHA256).unwrap(),
+            missing_oid
+        );
+    }
+
+    #[test]
+    fn looks_up_known_oids_correctly() {
+        let map = test_map(false);
+
+        let funcs: &[&dyn Fn(HashAlgorithm) -> &'static ObjectID] = &[
+            &|h: HashAlgorithm| h.empty_tree(),
+            &|h: HashAlgorithm| h.empty_blob(),
+            &|h: HashAlgorithm| h.null_oid(),
+        ];
+
+        for f in funcs {
+            let s256 = f(HashAlgorithm::SHA256);
+            let s1 = f(HashAlgorithm::SHA1);
+
+            let res = map.map_object(s256, HashAlgorithm::SHA1).unwrap();
+            assert_eq!(res.oid, *s1);
+            assert_eq!(res.kind, MapType::Reserved);
+            let res = map.map_oid(s256, HashAlgorithm::SHA1).unwrap();
+            assert_eq!(*res, *s1);
+
+            let res = map.map_object(s1, HashAlgorithm::SHA256).unwrap();
+            assert_eq!(res.oid, *s256);
+            assert_eq!(res.kind, MapType::Reserved);
+            let res = map.map_oid(s1, HashAlgorithm::SHA256).unwrap();
+            assert_eq!(*res, *s256);
+        }
+    }
+
+    #[test]
+    fn nul_padding() {
+        assert_eq!(ObjectMemoryMap::required_nul_padding(1, 1), 3);
+        assert_eq!(ObjectMemoryMap::required_nul_padding(2, 1), 2);
+        assert_eq!(ObjectMemoryMap::required_nul_padding(3, 1), 1);
+        assert_eq!(ObjectMemoryMap::required_nul_padding(2, 2), 0);
+
+        assert_eq!(ObjectMemoryMap::required_nul_padding(39, 3), 3);
+    }
+}
diff --git a/src/meson.build b/src/meson.build
index 25b9ad5..4573995 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,5 +1,8 @@
 libgit_rs_sources = [
+  'csum_file.rs',
+  'hash.rs',
   'lib.rs',
+  'loose.rs',
   'varint.rs',
 ]
 
diff --git a/strbuf.c b/strbuf.c
index 6c3851a..3939863 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -566,7 +566,7 @@ ssize_t strbuf_write(struct strbuf *sb, FILE *f)
 	return sb->len ? fwrite(sb->buf, 1, sb->len, f) : 0;
 }
 
-#define STRBUF_MAXLINK (2*PATH_MAX)
+#define STRBUF_MAXLINK (32767)
 
 int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
 {
@@ -578,12 +578,12 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
 	while (hint < STRBUF_MAXLINK) {
 		ssize_t len;
 
-		strbuf_grow(sb, hint);
-		len = readlink(path, sb->buf, hint);
+		strbuf_grow(sb, hint + 1);
+		len = readlink(path, sb->buf, hint + 1);
 		if (len < 0) {
 			if (errno != ERANGE)
 				break;
-		} else if (len < hint) {
+		} else if (len <= hint) {
 			strbuf_setlen(sb, len);
 			return 0;
 		}
@@ -836,47 +836,83 @@ void strbuf_addstr_urlencode(struct strbuf *sb, const char *s,
 	strbuf_add_urlencode(sb, s, strlen(s), allow_unencoded_fn);
 }
 
-static void strbuf_humanise(struct strbuf *buf, off_t bytes,
-				 int humanise_rate)
+void humanise_count(size_t count, char **value, const char **unit)
 {
-	if (bytes > 1 << 30) {
-		strbuf_addf(buf,
-				humanise_rate == 0 ?
-					/* TRANSLATORS: IEC 80000-13:2008 gibibyte */
-					_("%u.%2.2u GiB") :
-					/* TRANSLATORS: IEC 80000-13:2008 gibibyte/second */
-					_("%u.%2.2u GiB/s"),
-			    (unsigned)(bytes >> 30),
-			    (unsigned)(bytes & ((1 << 30) - 1)) / 10737419);
-	} else if (bytes > 1 << 20) {
-		unsigned x = bytes + 5243;  /* for rounding */
-		strbuf_addf(buf,
-				humanise_rate == 0 ?
-					/* TRANSLATORS: IEC 80000-13:2008 mebibyte */
-					_("%u.%2.2u MiB") :
-					/* TRANSLATORS: IEC 80000-13:2008 mebibyte/second */
-					_("%u.%2.2u MiB/s"),
-			    x >> 20, ((x & ((1 << 20) - 1)) * 100) >> 20);
-	} else if (bytes > 1 << 10) {
-		unsigned x = bytes + 5;  /* for rounding */
-		strbuf_addf(buf,
-				humanise_rate == 0 ?
-					/* TRANSLATORS: IEC 80000-13:2008 kibibyte */
-					_("%u.%2.2u KiB") :
-					/* TRANSLATORS: IEC 80000-13:2008 kibibyte/second */
-					_("%u.%2.2u KiB/s"),
-			    x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10);
+	if (count >= 1000000000) {
+		size_t x = count + 5000000; /* for rounding */
+		*value = xstrfmt(_("%u.%2.2u"), (unsigned)(x / 1000000000),
+				 (unsigned)(x % 1000000000 / 10000000));
+		/* TRANSLATORS: SI decimal prefix symbol for 10^9 */
+		*unit = _("G");
+	} else if (count >= 1000000) {
+		size_t x = count + 5000; /* for rounding */
+		*value = xstrfmt(_("%u.%2.2u"), (unsigned)(x / 1000000),
+				 (unsigned)(x % 1000000 / 10000));
+		/* TRANSLATORS: SI decimal prefix symbol for 10^6 */
+		*unit = _("M");
+	} else if (count >= 1000) {
+		size_t x = count + 5; /* for rounding */
+		*value = xstrfmt(_("%u.%2.2u"), (unsigned)(x / 1000),
+				 (unsigned)(x % 1000 / 10));
+		/* TRANSLATORS: SI decimal prefix symbol for 10^3 */
+		*unit = _("k");
 	} else {
-		strbuf_addf(buf,
-				humanise_rate == 0 ?
-					/* TRANSLATORS: IEC 80000-13:2008 byte */
-					Q_("%u byte", "%u bytes", bytes) :
-					/* TRANSLATORS: IEC 80000-13:2008 byte/second */
-					Q_("%u byte/s", "%u bytes/s", bytes),
-				(unsigned)bytes);
+		*value = xstrfmt("%u", (unsigned)count);
+		*unit = NULL;
 	}
 }
 
+void humanise_bytes(off_t bytes, char **value, const char **unit,
+		    unsigned flags)
+{
+	int humanise_rate = flags & HUMANISE_RATE;
+
+	if (bytes > 1 << 30) {
+		*value = xstrfmt(_("%u.%2.2u"), (unsigned)(bytes >> 30),
+				 (unsigned)(bytes & ((1 << 30) - 1)) / 10737419);
+		/* TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte */
+		*unit = humanise_rate ? _("GiB/s") : _("GiB");
+	} else if (bytes > 1 << 20) {
+		unsigned x = bytes + 5243; /* for rounding */
+		*value = xstrfmt(_("%u.%2.2u"), x >> 20,
+				 ((x & ((1 << 20) - 1)) * 100) >> 20);
+		/* TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte */
+		*unit = humanise_rate ? _("MiB/s") : _("MiB");
+	} else if (bytes > 1 << 10) {
+		unsigned x = bytes + 5; /* for rounding */
+		*value = xstrfmt(_("%u.%2.2u"), x >> 10,
+				 ((x & ((1 << 10) - 1)) * 100) >> 10);
+		/* TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte */
+		*unit = humanise_rate ? _("KiB/s") : _("KiB");
+	} else {
+		*value = xstrfmt("%u", (unsigned)bytes);
+		if (flags & HUMANISE_COMPACT)
+			/* TRANSLATORS: IEC 80000-13:2008 byte/second and byte */
+			*unit = humanise_rate ? _("B/s") : _("B");
+		else
+			*unit = humanise_rate ?
+					/* TRANSLATORS: IEC 80000-13:2008 byte/second */
+					Q_("byte/s", "bytes/s", bytes) :
+					/* TRANSLATORS: IEC 80000-13:2008 byte */
+					Q_("byte", "bytes", bytes);
+	}
+}
+
+static void strbuf_humanise(struct strbuf *buf, off_t bytes, unsigned flags)
+{
+	char *value;
+	const char *unit;
+
+	humanise_bytes(bytes, &value, &unit, flags);
+
+	/*
+	 * TRANSLATORS: The first argument is the number string. The second
+	 * argument is the unit string (i.e. "12.34 MiB/s").
+	 */
+	strbuf_addf(buf, _("%s %s"), value, unit);
+	free(value);
+}
+
 void strbuf_humanise_bytes(struct strbuf *buf, off_t bytes)
 {
 	strbuf_humanise(buf, bytes, 0);
@@ -884,7 +920,7 @@ void strbuf_humanise_bytes(struct strbuf *buf, off_t bytes)
 
 void strbuf_humanise_rate(struct strbuf *buf, off_t bytes)
 {
-	strbuf_humanise(buf, bytes, 1);
+	strbuf_humanise(buf, bytes, HUMANISE_RATE);
 }
 
 int printf_ln(const char *fmt, ...)
@@ -1083,6 +1119,6 @@ void strbuf_stripspace(struct strbuf *sb, const char *comment_prefix)
 
 void strbuf_strip_file_from_path(struct strbuf *sb)
 {
-	char *path_sep = find_last_dir_sep(sb->buf);
+	const char *path_sep = find_last_dir_sep(sb->buf);
 	strbuf_setlen(sb, path_sep ? path_sep - sb->buf + 1 : 0);
 }
diff --git a/strbuf.h b/strbuf.h
index a580ac6..06e284f 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -367,6 +367,31 @@ void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src);
  */
 void strbuf_add_percentencode(struct strbuf *dst, const char *src, int flags);
 
+enum humanise_flags {
+	/*
+	 * Use rate based units for humanised values.
+	 */
+	HUMANISE_RATE = (1 << 0),
+	/*
+	 * Use compact "B" unit symbol instead of "byte/bytes" for humanised
+	 * values.
+	 */
+	HUMANISE_COMPACT = (1 << 1),
+};
+
+/**
+ * Converts the given byte size into a downscaled human-readable value and
+ * corresponding unit as two separate strings.
+ */
+void humanise_bytes(off_t bytes, char **value, const char **unit,
+		    unsigned flags);
+
+/**
+ * Converts the given count into a downscaled human-readable value and
+ * corresponding unit as two separate strings.
+ */
+void humanise_count(size_t count, char **value, const char **unit);
+
 /**
  * Append the given byte size as a human-readable string (i.e. 12.23 KiB,
  * 3.50 MiB).
diff --git a/streaming.c b/streaming.c
deleted file mode 100644
index 4b13827..0000000
--- a/streaming.c
+++ /dev/null
@@ -1,552 +0,0 @@
-/*
- * Copyright (c) 2011, Google Inc.
- */
-
-#define USE_THE_REPOSITORY_VARIABLE
-
-#include "git-compat-util.h"
-#include "convert.h"
-#include "environment.h"
-#include "streaming.h"
-#include "repository.h"
-#include "object-file.h"
-#include "odb.h"
-#include "replace-object.h"
-#include "packfile.h"
-
-typedef int (*open_istream_fn)(struct git_istream *,
-			       struct repository *,
-			       const struct object_id *,
-			       enum object_type *);
-typedef int (*close_istream_fn)(struct git_istream *);
-typedef ssize_t (*read_istream_fn)(struct git_istream *, char *, size_t);
-
-#define FILTER_BUFFER (1024*16)
-
-struct filtered_istream {
-	struct git_istream *upstream;
-	struct stream_filter *filter;
-	char ibuf[FILTER_BUFFER];
-	char obuf[FILTER_BUFFER];
-	int i_end, i_ptr;
-	int o_end, o_ptr;
-	int input_finished;
-};
-
-struct git_istream {
-	open_istream_fn open;
-	close_istream_fn close;
-	read_istream_fn read;
-
-	unsigned long size; /* inflated size of full object */
-	git_zstream z;
-	enum { z_unused, z_used, z_done, z_error } z_state;
-
-	union {
-		struct {
-			char *buf; /* from odb_read_object_info_extended() */
-			unsigned long read_ptr;
-		} incore;
-
-		struct {
-			void *mapped;
-			unsigned long mapsize;
-			char hdr[32];
-			int hdr_avail;
-			int hdr_used;
-		} loose;
-
-		struct {
-			struct packed_git *pack;
-			off_t pos;
-		} in_pack;
-
-		struct filtered_istream filtered;
-	} u;
-};
-
-/*****************************************************************
- *
- * Common helpers
- *
- *****************************************************************/
-
-static void close_deflated_stream(struct git_istream *st)
-{
-	if (st->z_state == z_used)
-		git_inflate_end(&st->z);
-}
-
-
-/*****************************************************************
- *
- * Filtered stream
- *
- *****************************************************************/
-
-static int close_istream_filtered(struct git_istream *st)
-{
-	free_stream_filter(st->u.filtered.filter);
-	return close_istream(st->u.filtered.upstream);
-}
-
-static ssize_t read_istream_filtered(struct git_istream *st, char *buf,
-				     size_t sz)
-{
-	struct filtered_istream *fs = &(st->u.filtered);
-	size_t filled = 0;
-
-	while (sz) {
-		/* do we already have filtered output? */
-		if (fs->o_ptr < fs->o_end) {
-			size_t to_move = fs->o_end - fs->o_ptr;
-			if (sz < to_move)
-				to_move = sz;
-			memcpy(buf + filled, fs->obuf + fs->o_ptr, to_move);
-			fs->o_ptr += to_move;
-			sz -= to_move;
-			filled += to_move;
-			continue;
-		}
-		fs->o_end = fs->o_ptr = 0;
-
-		/* do we have anything to feed the filter with? */
-		if (fs->i_ptr < fs->i_end) {
-			size_t to_feed = fs->i_end - fs->i_ptr;
-			size_t to_receive = FILTER_BUFFER;
-			if (stream_filter(fs->filter,
-					  fs->ibuf + fs->i_ptr, &to_feed,
-					  fs->obuf, &to_receive))
-				return -1;
-			fs->i_ptr = fs->i_end - to_feed;
-			fs->o_end = FILTER_BUFFER - to_receive;
-			continue;
-		}
-
-		/* tell the filter to drain upon no more input */
-		if (fs->input_finished) {
-			size_t to_receive = FILTER_BUFFER;
-			if (stream_filter(fs->filter,
-					  NULL, NULL,
-					  fs->obuf, &to_receive))
-				return -1;
-			fs->o_end = FILTER_BUFFER - to_receive;
-			if (!fs->o_end)
-				break;
-			continue;
-		}
-		fs->i_end = fs->i_ptr = 0;
-
-		/* refill the input from the upstream */
-		if (!fs->input_finished) {
-			fs->i_end = read_istream(fs->upstream, fs->ibuf, FILTER_BUFFER);
-			if (fs->i_end < 0)
-				return -1;
-			if (fs->i_end)
-				continue;
-		}
-		fs->input_finished = 1;
-	}
-	return filled;
-}
-
-static struct git_istream *attach_stream_filter(struct git_istream *st,
-						struct stream_filter *filter)
-{
-	struct git_istream *ifs = xmalloc(sizeof(*ifs));
-	struct filtered_istream *fs = &(ifs->u.filtered);
-
-	ifs->close = close_istream_filtered;
-	ifs->read = read_istream_filtered;
-	fs->upstream = st;
-	fs->filter = filter;
-	fs->i_end = fs->i_ptr = 0;
-	fs->o_end = fs->o_ptr = 0;
-	fs->input_finished = 0;
-	ifs->size = -1; /* unknown */
-	return ifs;
-}
-
-/*****************************************************************
- *
- * Loose object stream
- *
- *****************************************************************/
-
-static ssize_t read_istream_loose(struct git_istream *st, char *buf, size_t sz)
-{
-	size_t total_read = 0;
-
-	switch (st->z_state) {
-	case z_done:
-		return 0;
-	case z_error:
-		return -1;
-	default:
-		break;
-	}
-
-	if (st->u.loose.hdr_used < st->u.loose.hdr_avail) {
-		size_t to_copy = st->u.loose.hdr_avail - st->u.loose.hdr_used;
-		if (sz < to_copy)
-			to_copy = sz;
-		memcpy(buf, st->u.loose.hdr + st->u.loose.hdr_used, to_copy);
-		st->u.loose.hdr_used += to_copy;
-		total_read += to_copy;
-	}
-
-	while (total_read < sz) {
-		int status;
-
-		st->z.next_out = (unsigned char *)buf + total_read;
-		st->z.avail_out = sz - total_read;
-		status = git_inflate(&st->z, Z_FINISH);
-
-		total_read = st->z.next_out - (unsigned char *)buf;
-
-		if (status == Z_STREAM_END) {
-			git_inflate_end(&st->z);
-			st->z_state = z_done;
-			break;
-		}
-		if (status != Z_OK && (status != Z_BUF_ERROR || total_read < sz)) {
-			git_inflate_end(&st->z);
-			st->z_state = z_error;
-			return -1;
-		}
-	}
-	return total_read;
-}
-
-static int close_istream_loose(struct git_istream *st)
-{
-	close_deflated_stream(st);
-	munmap(st->u.loose.mapped, st->u.loose.mapsize);
-	return 0;
-}
-
-static int open_istream_loose(struct git_istream *st, struct repository *r,
-			      const struct object_id *oid,
-			      enum object_type *type)
-{
-	struct object_info oi = OBJECT_INFO_INIT;
-	oi.sizep = &st->size;
-	oi.typep = type;
-
-	st->u.loose.mapped = map_loose_object(r, oid, &st->u.loose.mapsize);
-	if (!st->u.loose.mapped)
-		return -1;
-	switch (unpack_loose_header(&st->z, st->u.loose.mapped,
-				    st->u.loose.mapsize, st->u.loose.hdr,
-				    sizeof(st->u.loose.hdr))) {
-	case ULHR_OK:
-		break;
-	case ULHR_BAD:
-	case ULHR_TOO_LONG:
-		goto error;
-	}
-	if (parse_loose_header(st->u.loose.hdr, &oi) < 0 || *type < 0)
-		goto error;
-
-	st->u.loose.hdr_used = strlen(st->u.loose.hdr) + 1;
-	st->u.loose.hdr_avail = st->z.total_out;
-	st->z_state = z_used;
-	st->close = close_istream_loose;
-	st->read = read_istream_loose;
-
-	return 0;
-error:
-	git_inflate_end(&st->z);
-	munmap(st->u.loose.mapped, st->u.loose.mapsize);
-	return -1;
-}
-
-
-/*****************************************************************
- *
- * Non-delta packed object stream
- *
- *****************************************************************/
-
-static ssize_t read_istream_pack_non_delta(struct git_istream *st, char *buf,
-					   size_t sz)
-{
-	size_t total_read = 0;
-
-	switch (st->z_state) {
-	case z_unused:
-		memset(&st->z, 0, sizeof(st->z));
-		git_inflate_init(&st->z);
-		st->z_state = z_used;
-		break;
-	case z_done:
-		return 0;
-	case z_error:
-		return -1;
-	case z_used:
-		break;
-	}
-
-	while (total_read < sz) {
-		int status;
-		struct pack_window *window = NULL;
-		unsigned char *mapped;
-
-		mapped = use_pack(st->u.in_pack.pack, &window,
-				  st->u.in_pack.pos, &st->z.avail_in);
-
-		st->z.next_out = (unsigned char *)buf + total_read;
-		st->z.avail_out = sz - total_read;
-		st->z.next_in = mapped;
-		status = git_inflate(&st->z, Z_FINISH);
-
-		st->u.in_pack.pos += st->z.next_in - mapped;
-		total_read = st->z.next_out - (unsigned char *)buf;
-		unuse_pack(&window);
-
-		if (status == Z_STREAM_END) {
-			git_inflate_end(&st->z);
-			st->z_state = z_done;
-			break;
-		}
-
-		/*
-		 * Unlike the loose object case, we do not have to worry here
-		 * about running out of input bytes and spinning infinitely. If
-		 * we get Z_BUF_ERROR due to too few input bytes, then we'll
-		 * replenish them in the next use_pack() call when we loop. If
-		 * we truly hit the end of the pack (i.e., because it's corrupt
-		 * or truncated), then use_pack() catches that and will die().
-		 */
-		if (status != Z_OK && status != Z_BUF_ERROR) {
-			git_inflate_end(&st->z);
-			st->z_state = z_error;
-			return -1;
-		}
-	}
-	return total_read;
-}
-
-static int close_istream_pack_non_delta(struct git_istream *st)
-{
-	close_deflated_stream(st);
-	return 0;
-}
-
-static int open_istream_pack_non_delta(struct git_istream *st,
-				       struct repository *r UNUSED,
-				       const struct object_id *oid UNUSED,
-				       enum object_type *type UNUSED)
-{
-	struct pack_window *window;
-	enum object_type in_pack_type;
-
-	window = NULL;
-
-	in_pack_type = unpack_object_header(st->u.in_pack.pack,
-					    &window,
-					    &st->u.in_pack.pos,
-					    &st->size);
-	unuse_pack(&window);
-	switch (in_pack_type) {
-	default:
-		return -1; /* we do not do deltas for now */
-	case OBJ_COMMIT:
-	case OBJ_TREE:
-	case OBJ_BLOB:
-	case OBJ_TAG:
-		break;
-	}
-	st->z_state = z_unused;
-	st->close = close_istream_pack_non_delta;
-	st->read = read_istream_pack_non_delta;
-
-	return 0;
-}
-
-
-/*****************************************************************
- *
- * In-core stream
- *
- *****************************************************************/
-
-static int close_istream_incore(struct git_istream *st)
-{
-	free(st->u.incore.buf);
-	return 0;
-}
-
-static ssize_t read_istream_incore(struct git_istream *st, char *buf, size_t sz)
-{
-	size_t read_size = sz;
-	size_t remainder = st->size - st->u.incore.read_ptr;
-
-	if (remainder <= read_size)
-		read_size = remainder;
-	if (read_size) {
-		memcpy(buf, st->u.incore.buf + st->u.incore.read_ptr, read_size);
-		st->u.incore.read_ptr += read_size;
-	}
-	return read_size;
-}
-
-static int open_istream_incore(struct git_istream *st, struct repository *r,
-			       const struct object_id *oid, enum object_type *type)
-{
-	struct object_info oi = OBJECT_INFO_INIT;
-
-	st->u.incore.read_ptr = 0;
-	st->close = close_istream_incore;
-	st->read = read_istream_incore;
-
-	oi.typep = type;
-	oi.sizep = &st->size;
-	oi.contentp = (void **)&st->u.incore.buf;
-	return odb_read_object_info_extended(r->objects, oid, &oi,
-					     OBJECT_INFO_DIE_IF_CORRUPT);
-}
-
-/*****************************************************************************
- * static helpers variables and functions for users of streaming interface
- *****************************************************************************/
-
-static int istream_source(struct git_istream *st,
-			  struct repository *r,
-			  const struct object_id *oid,
-			  enum object_type *type)
-{
-	unsigned long size;
-	int status;
-	struct object_info oi = OBJECT_INFO_INIT;
-
-	oi.typep = type;
-	oi.sizep = &size;
-	status = odb_read_object_info_extended(r->objects, oid, &oi, 0);
-	if (status < 0)
-		return status;
-
-	switch (oi.whence) {
-	case OI_LOOSE:
-		st->open = open_istream_loose;
-		return 0;
-	case OI_PACKED:
-		if (!oi.u.packed.is_delta &&
-		    repo_settings_get_big_file_threshold(the_repository) < size) {
-			st->u.in_pack.pack = oi.u.packed.pack;
-			st->u.in_pack.pos = oi.u.packed.offset;
-			st->open = open_istream_pack_non_delta;
-			return 0;
-		}
-		/* fallthru */
-	default:
-		st->open = open_istream_incore;
-		return 0;
-	}
-}
-
-/****************************************************************
- * Users of streaming interface
- ****************************************************************/
-
-int close_istream(struct git_istream *st)
-{
-	int r = st->close(st);
-	free(st);
-	return r;
-}
-
-ssize_t read_istream(struct git_istream *st, void *buf, size_t sz)
-{
-	return st->read(st, buf, sz);
-}
-
-struct git_istream *open_istream(struct repository *r,
-				 const struct object_id *oid,
-				 enum object_type *type,
-				 unsigned long *size,
-				 struct stream_filter *filter)
-{
-	struct git_istream *st = xmalloc(sizeof(*st));
-	const struct object_id *real = lookup_replace_object(r, oid);
-	int ret = istream_source(st, r, real, type);
-
-	if (ret) {
-		free(st);
-		return NULL;
-	}
-
-	if (st->open(st, r, real, type)) {
-		if (open_istream_incore(st, r, real, type)) {
-			free(st);
-			return NULL;
-		}
-	}
-	if (filter) {
-		/* Add "&& !is_null_stream_filter(filter)" for performance */
-		struct git_istream *nst = attach_stream_filter(st, filter);
-		if (!nst) {
-			close_istream(st);
-			return NULL;
-		}
-		st = nst;
-	}
-
-	*size = st->size;
-	return st;
-}
-
-int stream_blob_to_fd(int fd, const struct object_id *oid, struct stream_filter *filter,
-		      int can_seek)
-{
-	struct git_istream *st;
-	enum object_type type;
-	unsigned long sz;
-	ssize_t kept = 0;
-	int result = -1;
-
-	st = open_istream(the_repository, oid, &type, &sz, filter);
-	if (!st) {
-		if (filter)
-			free_stream_filter(filter);
-		return result;
-	}
-	if (type != OBJ_BLOB)
-		goto close_and_exit;
-	for (;;) {
-		char buf[1024 * 16];
-		ssize_t wrote, holeto;
-		ssize_t readlen = read_istream(st, buf, sizeof(buf));
-
-		if (readlen < 0)
-			goto close_and_exit;
-		if (!readlen)
-			break;
-		if (can_seek && sizeof(buf) == readlen) {
-			for (holeto = 0; holeto < readlen; holeto++)
-				if (buf[holeto])
-					break;
-			if (readlen == holeto) {
-				kept += holeto;
-				continue;
-			}
-		}
-
-		if (kept && lseek(fd, kept, SEEK_CUR) == (off_t) -1)
-			goto close_and_exit;
-		else
-			kept = 0;
-		wrote = write_in_full(fd, buf, readlen);
-
-		if (wrote < 0)
-			goto close_and_exit;
-	}
-	if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 ||
-		     xwrite(fd, "", 1) != 1))
-		goto close_and_exit;
-	result = 0;
-
- close_and_exit:
-	close_istream(st);
-	return result;
-}
diff --git a/streaming.h b/streaming.h
deleted file mode 100644
index bd27f59..0000000
--- a/streaming.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (c) 2011, Google Inc.
- */
-#ifndef STREAMING_H
-#define STREAMING_H 1
-
-#include "object.h"
-
-/* opaque */
-struct git_istream;
-struct stream_filter;
-
-struct git_istream *open_istream(struct repository *, const struct object_id *,
-				 enum object_type *, unsigned long *,
-				 struct stream_filter *);
-int close_istream(struct git_istream *);
-ssize_t read_istream(struct git_istream *, void *, size_t);
-
-int stream_blob_to_fd(int fd, const struct object_id *, struct stream_filter *, int can_seek);
-
-#endif /* STREAMING_H */
diff --git a/string-list.c b/string-list.c
index 08dc009..fffa2ad 100644
--- a/string-list.c
+++ b/string-list.c
@@ -247,6 +247,12 @@ void string_list_sort(struct string_list *list)
 	QSORT_S(list->items, list->nr, cmp_items, &sort_ctx);
 }
 
+void string_list_sort_u(struct string_list *list, int free_util)
+{
+	string_list_sort(list);
+	string_list_remove_duplicates(list, free_util);
+}
+
 struct string_list_item *unsorted_string_list_lookup(struct string_list *list,
 						     const char *string)
 {
@@ -327,7 +333,7 @@ static int split_string(struct string_list *list, const char *string, const char
 		BUG("string_list_split() called without strdup_strings");
 
 	for (;;) {
-		char *end;
+		const char *end;
 
 		if (flags & STRING_LIST_SPLIT_TRIM) {
 			/* ltrim */
diff --git a/string-list.h b/string-list.h
index fa6ba07..3ad862a 100644
--- a/string-list.h
+++ b/string-list.h
@@ -240,6 +240,12 @@ struct string_list_item *string_list_append_nodup(struct string_list *list, char
 void string_list_sort(struct string_list *list);
 
 /**
+ * Sort the list and then remove duplicate entries.  If free_util is true,
+ * call free() on the util members of any items that have to be deleted.
+ */
+void string_list_sort_u(struct string_list *list, int free_util);
+
+/**
  * Like `string_list_has_string()` but for unsorted lists. Linear in
  * size of the list.
  */
diff --git a/submodule.c b/submodule.c
index 35c5515..cd879a5 100644
--- a/submodule.c
+++ b/submodule.c
@@ -31,6 +31,8 @@
 #include "commit-reach.h"
 #include "read-cache-ll.h"
 #include "setup.h"
+#include "advice.h"
+#include "url.h"
 
 static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
 static int initialized_fetch_ref_tips;
@@ -99,7 +101,7 @@ int is_staging_gitmodules_ok(struct index_state *istate)
 }
 
 static int for_each_remote_ref_submodule(const char *submodule,
-					 each_ref_fn fn, void *cb_data)
+					 refs_for_each_cb fn, void *cb_data)
 {
 	return refs_for_each_remote_ref(repo_get_submodule_ref_store(the_repository,
 								     submodule),
@@ -639,7 +641,7 @@ void show_submodule_diff_summary(struct diff_options *o, const char *path,
 	print_submodule_diff_summary(sub, &rev, o);
 
 out:
-	free_commit_list(merge_bases);
+	commit_list_free(merge_bases);
 	release_revisions(&rev);
 	clear_commit_marks(left, ~0);
 	clear_commit_marks(right, ~0);
@@ -729,7 +731,7 @@ void show_submodule_inline_diff(struct diff_options *o, const char *path,
 
 done:
 	strbuf_release(&sb);
-	free_commit_list(merge_bases);
+	commit_list_free(merge_bases);
 	if (left)
 		clear_commit_marks(left, ~0);
 	if (right)
@@ -934,10 +936,7 @@ static void free_submodules_data(struct string_list *submodules)
 	string_list_clear(submodules, 1);
 }
 
-static int has_remote(const char *refname UNUSED,
-		      const char *referent UNUSED,
-		      const struct object_id *oid UNUSED,
-		      int flags UNUSED, void *cb_data UNUSED)
+static int has_remote(const struct reference *ref UNUSED, void *cb_data UNUSED)
 {
 	return 1;
 }
@@ -1255,13 +1254,10 @@ int push_unpushed_submodules(struct repository *r,
 	return ret;
 }
 
-static int append_oid_to_array(const char *ref UNUSED,
-			       const char *referent UNUSED,
-			       const struct object_id *oid,
-			       int flags UNUSED, void *data)
+static int append_oid_to_array(const struct reference *ref, void *data)
 {
 	struct oid_array *array = data;
-	oid_array_append(array, oid);
+	oid_array_append(array, ref->oid);
 	return 0;
 }
 
@@ -2164,19 +2160,15 @@ int submodule_move_head(const char *path, const char *super_prefix,
 				if (validate_submodule_git_dir(git_dir,
 							       sub->name) < 0)
 					die(_("refusing to create/use '%s' in "
-					      "another submodule's git dir"),
-					    git_dir);
+					      "another submodule's git dir. "
+					      "Enabling extensions.submodulePathConfig "
+					      "should fix this."), git_dir);
 				free(git_dir);
 			}
 		} else {
 			struct strbuf gitdir = STRBUF_INIT;
 			submodule_name_to_gitdir(&gitdir, the_repository,
 						 sub->name);
-			if (validate_submodule_git_dir(gitdir.buf,
-						       sub->name) < 0)
-				die(_("refusing to create/use '%s' in another "
-				      "submodule's git dir"),
-				    gitdir.buf);
 			connect_work_tree_and_git_dir(path, gitdir.buf, 0);
 			strbuf_release(&gitdir);
 
@@ -2256,12 +2248,155 @@ int submodule_move_head(const char *path, const char *super_prefix,
 	return ret;
 }
 
-int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
+static int check_casefolding_conflict(const char *git_dir,
+				      const char *submodule_name,
+				      const bool suffixes_match)
+{
+	char *p, *modules_dir = xstrdup(git_dir);
+	struct dirent *de;
+	DIR *dir = NULL;
+	int ret = 0;
+
+	if ((p = find_last_dir_sep(modules_dir)))
+		*p = '\0';
+
+	/* No conflict is possible if modules_dir doesn't exist (first clone) */
+	if (!is_directory(modules_dir))
+		goto cleanup;
+
+	dir = opendir(modules_dir);
+	if (!dir) {
+		ret = -1;
+		goto cleanup;
+	}
+
+	/* Check for another directory under .git/modules that differs only in case. */
+	while ((de = readdir(dir))) {
+		if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+			continue;
+
+		if ((suffixes_match || is_git_directory(git_dir)) &&
+		    !strcasecmp(de->d_name, submodule_name) &&
+		    strcmp(de->d_name, submodule_name)) {
+			ret = -1; /* collision found */
+			break;
+		}
+	}
+
+cleanup:
+	if (dir)
+		closedir(dir);
+	free(modules_dir);
+	return ret;
+}
+
+struct submodule_from_gitdir_cb {
+	const char *gitdir;
+	const char *submodule_name;
+	bool conflict_found;
+};
+
+static int find_conflict_by_gitdir_cb(const char *var, const char *value,
+				      const struct config_context *ctx UNUSED, void *data)
+{
+	struct submodule_from_gitdir_cb *cb = data;
+	const char *submodule_name_start;
+	size_t submodule_name_len;
+	const char *suffix = ".gitdir";
+	size_t suffix_len = strlen(suffix);
+
+	if (!skip_prefix(var, "submodule.", &submodule_name_start))
+		return 0;
+
+	/* Check if submodule_name_start ends with ".gitdir" */
+	submodule_name_len = strlen(submodule_name_start);
+	if (submodule_name_len < suffix_len ||
+	    strcmp(submodule_name_start + submodule_name_len - suffix_len, suffix) != 0)
+		return 0; /* Does not end with ".gitdir" */
+
+	submodule_name_len -= suffix_len;
+
+	/*
+	 * A conflict happens if:
+	 * 1. The submodule names are different and
+	 * 2. The gitdir paths resolve to the same absolute path
+	 */
+	if (value && strncmp(cb->submodule_name, submodule_name_start, submodule_name_len)) {
+		char *abs_path_cb = absolute_pathdup(cb->gitdir);
+		char *abs_path_value = absolute_pathdup(value);
+
+		cb->conflict_found = !strcmp(abs_path_cb, abs_path_value);
+
+		free(abs_path_cb);
+		free(abs_path_value);
+	}
+
+	return cb->conflict_found;
+}
+
+static bool submodule_conflicts_with_existing(const char *gitdir, const char *submodule_name)
+{
+	struct submodule_from_gitdir_cb cb = { 0 };
+	cb.submodule_name = submodule_name;
+	cb.gitdir = gitdir;
+
+	/* Find conflicts with existing repo gitdir configs */
+	repo_config(the_repository, find_conflict_by_gitdir_cb, &cb);
+
+	return cb.conflict_found;
+}
+
+/*
+ * Encoded gitdir validation, only used when extensions.submodulePathConfig is enabled.
+ * This does not print errors like the non-encoded version, because encoding is supposed
+ * to mitigate / fix all these.
+ */
+static int validate_submodule_encoded_git_dir(char *git_dir, const char *submodule_name)
+{
+	const char *modules_marker = "/modules/";
+	char *p = git_dir, *last_submodule_name = NULL;
+	int config_ignorecase = 0;
+
+	if (!the_repository->repository_format_submodule_path_cfg)
+		BUG("validate_submodule_encoded_git_dir() must be called with "
+		    "extensions.submodulePathConfig enabled.");
+
+	/* Find the last submodule name in the gitdir path (modules can be nested). */
+	while ((p = strstr(p, modules_marker))) {
+		last_submodule_name = p + strlen(modules_marker);
+		p++;
+	}
+
+	/* Prevent the use of '/' in encoded names */
+	if (!last_submodule_name || strchr(last_submodule_name, '/'))
+		return -1;
+
+	/* Prevent conflicts with existing submodule gitdirs */
+	if (is_git_directory(git_dir) &&
+	    submodule_conflicts_with_existing(git_dir, submodule_name))
+			return -1;
+
+	/* Prevent conflicts on case-folding filesystems */
+	repo_config_get_bool(the_repository, "core.ignorecase", &config_ignorecase);
+	if (ignore_case || config_ignorecase) {
+		bool suffixes_match = !strcmp(last_submodule_name, submodule_name);
+		return check_casefolding_conflict(git_dir, submodule_name,
+						  suffixes_match);
+	}
+
+	return 0;
+}
+
+static int validate_submodule_legacy_git_dir(char *git_dir, const char *submodule_name)
 {
 	size_t len = strlen(git_dir), suffix_len = strlen(submodule_name);
 	char *p;
 	int ret = 0;
 
+	if (the_repository->repository_format_submodule_path_cfg)
+		BUG("validate_submodule_git_dir() must be called with "
+		    "extensions.submodulePathConfig disabled.");
+
 	if (len <= suffix_len || (p = git_dir + len - suffix_len)[-1] != '/' ||
 	    strcmp(p, submodule_name))
 		BUG("submodule name '%s' not a suffix of git dir '%s'",
@@ -2297,6 +2432,14 @@ int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
 	return 0;
 }
 
+int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
+{
+	if (!the_repository->repository_format_submodule_path_cfg)
+		return validate_submodule_legacy_git_dir(git_dir, submodule_name);
+
+	return validate_submodule_encoded_git_dir(git_dir, submodule_name);
+}
+
 int validate_submodule_path(const char *path)
 {
 	char *p = xstrdup(path);
@@ -2355,9 +2498,6 @@ static void relocate_single_git_dir_into_superproject(const char *path,
 		die(_("could not lookup name for submodule '%s'"), path);
 
 	submodule_name_to_gitdir(&new_gitdir, the_repository, sub->name);
-	if (validate_submodule_git_dir(new_gitdir.buf, sub->name) < 0)
-		die(_("refusing to move '%s' into an existing git dir"),
-		    real_old_git_dir);
 	if (safe_create_leading_directories_const(the_repository, new_gitdir.buf) < 0)
 		die(_("could not create directory '%s'"), new_gitdir.buf);
 	real_new_git_dir = real_pathdup(new_gitdir.buf, 1);
@@ -2419,7 +2559,7 @@ void absorb_git_dir_into_superproject(const char *path,
 		const struct submodule *sub;
 		struct strbuf sub_gitdir = STRBUF_INIT;
 
-		if (err_code == READ_GITFILE_ERR_STAT_FAILED) {
+		if (err_code == READ_GITFILE_ERR_MISSING) {
 			/* unpopulated as expected */
 			strbuf_release(&gitdir);
 			return;
@@ -2584,26 +2724,37 @@ int submodule_to_gitdir(struct repository *repo,
 void submodule_name_to_gitdir(struct strbuf *buf, struct repository *r,
 			      const char *submodule_name)
 {
-	/*
-	 * NEEDSWORK: The current way of mapping a submodule's name to
-	 * its location in .git/modules/ has problems with some naming
-	 * schemes. For example, if a submodule is named "foo" and
-	 * another is named "foo/bar" (whether present in the same
-	 * superproject commit or not - the problem will arise if both
-	 * superproject commits have been checked out at any point in
-	 * time), or if two submodule names only have different cases in
-	 * a case-insensitive filesystem.
-	 *
-	 * There are several solutions, including encoding the path in
-	 * some way, introducing a submodule.<name>.gitdir config in
-	 * .git/config (not .gitmodules) that allows overriding what the
-	 * gitdir of a submodule would be (and teach Git, upon noticing
-	 * a clash, to automatically determine a non-clashing name and
-	 * to write such a config), or introducing a
-	 * submodule.<name>.gitdir config in .gitmodules that repo
-	 * administrators can explicitly set. Nothing has been decided,
-	 * so for now, just append the name at the end of the path.
-	 */
-	repo_git_path_append(r, buf, "modules/");
-	strbuf_addstr(buf, submodule_name);
+	if (!r->repository_format_submodule_path_cfg) {
+		/*
+		 * If extensions.submodulePathConfig is disabled,
+		 * continue to use the plain path.
+		 */
+		repo_git_path_append(r, buf, "modules/%s", submodule_name);
+	} else {
+		const char *gitdir;
+		char *key;
+		int ret;
+
+		/* Otherwise the extension is enabled, so use the gitdir config. */
+		key = xstrfmt("submodule.%s.gitdir", submodule_name);
+		ret = repo_config_get_string_tmp(r, key, &gitdir);
+		FREE_AND_NULL(key);
+
+		if (ret)
+			die(_("the 'submodule.%s.gitdir' config does not exist for module '%s'. "
+			      "Please ensure it is set, for example by running something like: "
+			      "'git config submodule.%s.gitdir .git/modules/%s'. For details "
+			      "see the extensions.submodulePathConfig documentation."),
+			    submodule_name, submodule_name, submodule_name, submodule_name);
+
+		strbuf_addstr(buf, gitdir);
+	}
+
+	/* validate because users might have modified the config */
+	if (validate_submodule_git_dir(buf->buf, submodule_name)) {
+		advise(_("enabling extensions.submodulePathConfig might fix the "
+			 "following error, if it's not already enabled."));
+		die(_("refusing to create/use '%s' in another submodule's "
+		      " git dir."), buf->buf);
+	}
 }
diff --git a/subprojects/.gitignore b/subprojects/.gitignore
index 63ea916..2bb68c8 100644
--- a/subprojects/.gitignore
+++ b/subprojects/.gitignore
@@ -1 +1,2 @@
 /*/
+.wraplock
diff --git a/subprojects/git-gui b/subprojects/git-gui
new file mode 120000
index 0000000..c6d9170
--- /dev/null
+++ b/subprojects/git-gui
@@ -0,0 +1 @@
+../git-gui
\ No newline at end of file
diff --git a/subprojects/gitk b/subprojects/gitk
new file mode 120000
index 0000000..b66ad18
--- /dev/null
+++ b/subprojects/gitk
@@ -0,0 +1 @@
+../gitk-git
\ No newline at end of file
diff --git a/symlinks.c b/symlinks.c
index 9cc090d..9e01ab3 100644
--- a/symlinks.c
+++ b/symlinks.c
@@ -74,11 +74,12 @@ static inline void reset_lstat_cache(struct cache_def *cache)
  */
 static int lstat_cache_matchlen(struct cache_def *cache,
 				const char *name, int len,
-				int *ret_flags, int track_flags,
+				unsigned int *ret_flags, unsigned int track_flags,
 				int prefix_len_stat_func)
 {
 	int match_len, last_slash, last_slash_dir, previous_slash;
-	int save_flags, ret, saved_errno = 0;
+	unsigned int save_flags;
+	int ret, saved_errno = 0;
 	struct stat st;
 
 	if (cache->track_flags != track_flags ||
@@ -192,10 +193,10 @@ static int lstat_cache_matchlen(struct cache_def *cache,
 	return match_len;
 }
 
-static int lstat_cache(struct cache_def *cache, const char *name, int len,
-		       int track_flags, int prefix_len_stat_func)
+static unsigned int lstat_cache(struct cache_def *cache, const char *name, int len,
+		       unsigned int track_flags, int prefix_len_stat_func)
 {
-	int flags;
+	unsigned int flags;
 	(void)lstat_cache_matchlen(cache, name, len, &flags, track_flags,
 			prefix_len_stat_func);
 	return flags;
@@ -234,7 +235,7 @@ int check_leading_path(const char *name, int len, int warn_on_lstat_err)
 static int threaded_check_leading_path(struct cache_def *cache, const char *name,
 				       int len, int warn_on_lstat_err)
 {
-	int flags;
+	unsigned int flags;
 	int match_len = lstat_cache_matchlen(cache, name, len, &flags,
 			   FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT);
 	int saved_errno = errno;
diff --git a/symlinks.h b/symlinks.h
index 7ae3d5b..25bf04f 100644
--- a/symlinks.h
+++ b/symlinks.h
@@ -5,8 +5,8 @@
 
 struct cache_def {
 	struct strbuf path;
-	int flags;
-	int track_flags;
+	unsigned int flags;
+	unsigned int track_flags;
 	int prefix_len_stat_func;
 };
 #define CACHE_DEF_INIT { \
diff --git a/t/for-each-ref-tests.sh b/t/for-each-ref-tests.sh
index e3ad192..bd2d45c 100644
--- a/t/for-each-ref-tests.sh
+++ b/t/for-each-ref-tests.sh
@@ -1744,6 +1744,15 @@
 	)
 '
 
+test_expect_success '%(push) with an invalid push-simple config' '
+	echo "refs/heads/main " >expect &&
+	git -c push.default=simple \
+	    -c remote.pushdefault=myfork \
+	    for-each-ref \
+	    --format="%(refname) %(push)" refs/heads/main >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success "${git_for_each_ref} --ignore-case ignores case" '
 	${git_for_each_ref} --format="%(refname)" refs/heads/MAIN >actual &&
 	test_must_be_empty actual &&
@@ -1809,7 +1818,9 @@
 	bad=$(git hash-object -w -t tag bad) &&
 	git update-ref refs/tags/broken-tag-bad $bad &&
 	test_must_fail ${git_for_each_ref} --format="%(*objectname)" \
-		refs/tags/broken-tag-*
+		refs/tags/broken-tag-* &&
+	test_must_fail ${git_for_each_ref} --format="%(*objectname)" \
+		refs/tags/broken-tag-bad
 '
 
 test_expect_success 'set up tag with signature and no blank lines' '
diff --git a/t/helper/test-cache-tree.c b/t/helper/test-cache-tree.c
index 3ae45ce..ff61d0c 100644
--- a/t/helper/test-cache-tree.c
+++ b/t/helper/test-cache-tree.c
@@ -41,7 +41,7 @@ int cmd__cache_tree(int argc, const char **argv)
 		die(_("unable to read index file"));
 
 	oidcpy(&oid, &the_repository->index->cache_tree->oid);
-	tree = parse_tree_indirect(&oid);
+	tree = repo_parse_tree_indirect(the_repository, &oid);
 	if (!tree)
 		die(_("not a tree object: %s"), oid_to_hex(&oid));
 
diff --git a/t/helper/test-genrandom.c b/t/helper/test-genrandom.c
index 51b67f2..d681961 100644
--- a/t/helper/test-genrandom.c
+++ b/t/helper/test-genrandom.c
@@ -6,6 +6,7 @@
 
 #include "test-tool.h"
 #include "git-compat-util.h"
+#include "parse.h"
 
 int cmd__genrandom(int argc, const char **argv)
 {
@@ -22,7 +23,9 @@ int cmd__genrandom(int argc, const char **argv)
 		next = next * 11 + *c;
 	} while (*c++);
 
-	count = (argc == 3) ? strtoul(argv[2], NULL, 0) : ULONG_MAX;
+	count = ULONG_MAX;
+	if (argc == 3 && !git_parse_ulong(argv[2], &count))
+		return error_errno("cannot parse argument '%s'", argv[2]);
 
 	while (count--) {
 		next = next * 1103515245 + 12345;
diff --git a/t/helper/test-match-trees.c b/t/helper/test-match-trees.c
index e0e2048..2ed064b 100644
--- a/t/helper/test-match-trees.c
+++ b/t/helper/test-match-trees.c
@@ -19,10 +19,10 @@ int cmd__match_trees(int ac UNUSED, const char **av)
 		die("cannot parse %s as an object name", av[1]);
 	if (repo_get_oid(the_repository, av[2], &hash2))
 		die("cannot parse %s as an object name", av[2]);
-	one = parse_tree_indirect(&hash1);
+	one = repo_parse_tree_indirect(the_repository, &hash1);
 	if (!one)
 		die("not a tree-ish %s", av[1]);
-	two = parse_tree_indirect(&hash2);
+	two = repo_parse_tree_indirect(the_repository, &hash2);
 	if (!two)
 		die("not a tree-ish %s", av[2]);
 
diff --git a/t/helper/test-mktemp.c b/t/helper/test-mktemp.c
index 2290688..da19564 100644
--- a/t/helper/test-mktemp.c
+++ b/t/helper/test-mktemp.c
@@ -6,10 +6,16 @@
 
 int cmd__mktemp(int argc, const char **argv)
 {
+	char *template;
+	int fd;
+
 	if (argc != 2)
 		usage("Expected 1 parameter defining the temporary file template");
+	template = xstrdup(argv[1]);
 
-	xmkstemp(xstrdup(argv[1]));
+	fd = xmkstemp(template);
 
+	close(fd);
+	free(template);
 	return 0;
 }
diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c
index f5f3375..874542e 100644
--- a/t/helper/test-path-utils.c
+++ b/t/helper/test-path-utils.c
@@ -477,14 +477,20 @@ int cmd__path_utils(int argc, const char **argv)
 
 	if (argc > 5 && !strcmp(argv[1], "slice-tests")) {
 		int res = 0;
-		long offset, stride, i;
+		long slice, slices_total, i;
 		struct string_list list = STRING_LIST_INIT_NODUP;
 		struct stat st;
 
-		offset = strtol(argv[2], NULL, 10);
-		stride = strtol(argv[3], NULL, 10);
-		if (stride < 1)
-			stride = 1;
+		slices_total = strtol(argv[3], NULL, 10);
+		if (slices_total < 1)
+			die("there must be at least one slice, got '%s'",
+			    argv[3]);
+
+		slice = strtol(argv[2], NULL, 10);
+		if (1 > slice || slice > slices_total)
+			die("slice must be in the range 1 <= slice <= %ld, got '%s'",
+			    slices_total, argv[2]);
+
 		for (i = 4; i < argc; i++)
 			if (stat(argv[i], &st))
 				res = error_errno("Cannot stat '%s'", argv[i]);
@@ -492,7 +498,7 @@ int cmd__path_utils(int argc, const char **argv)
 				string_list_append(&list, argv[i])->util =
 					(void *)(intptr_t)st.st_size;
 		QSORT(list.items, list.nr, cmp_by_st_size);
-		for (i = offset; i < list.nr; i+= stride)
+		for (i = slice - 1; i < list.nr; i+= slices_total)
 			printf("%s\n", list.items[i].string);
 
 		return !!res;
diff --git a/t/helper/test-reach.c b/t/helper/test-reach.c
index 028ec00..3131b54 100644
--- a/t/helper/test-reach.c
+++ b/t/helper/test-reach.c
@@ -34,8 +34,8 @@ int cmd__reach(int ac, const char **av)
 	struct commit *A, *B;
 	struct commit_list *X, *Y;
 	struct object_array X_obj = OBJECT_ARRAY_INIT;
-	struct commit **X_array, **Y_array;
-	size_t X_nr, X_alloc, Y_nr, Y_alloc;
+	struct commit_stack X_stack = COMMIT_STACK_INIT;
+	struct commit_stack Y_stack = COMMIT_STACK_INIT;
 	struct strbuf buf = STRBUF_INIT;
 	struct repository *r = the_repository;
 
@@ -46,10 +46,6 @@ int cmd__reach(int ac, const char **av)
 
 	A = B = NULL;
 	X = Y = NULL;
-	X_nr = Y_nr = 0;
-	X_alloc = Y_alloc = 16;
-	ALLOC_ARRAY(X_array, X_alloc);
-	ALLOC_ARRAY(Y_array, Y_alloc);
 
 	while (strbuf_getline(&buf, stdin) != EOF) {
 		struct object_id oid;
@@ -63,7 +59,7 @@ int cmd__reach(int ac, const char **av)
 			die("failed to resolve %s", buf.buf + 2);
 
 		orig = parse_object(r, &oid);
-		peeled = deref_tag_noverify(the_repository, orig);
+		peeled = deref_tag(the_repository, orig, NULL, 0);
 
 		if (!peeled)
 			die("failed to load commit for input %s resulting in oid %s",
@@ -88,15 +84,13 @@ int cmd__reach(int ac, const char **av)
 
 			case 'X':
 				commit_list_insert(c, &X);
-				ALLOC_GROW(X_array, X_nr + 1, X_alloc);
-				X_array[X_nr++] = c;
+				commit_stack_push(&X_stack, c);
 				add_object_array(orig, NULL, &X_obj);
 				break;
 
 			case 'Y':
 				commit_list_insert(c, &Y);
-				ALLOC_GROW(Y_array, Y_nr + 1, Y_alloc);
-				Y_array[Y_nr++] = c;
+				commit_stack_push(&Y_stack, c);
 				break;
 
 			default:
@@ -112,26 +106,26 @@ int cmd__reach(int ac, const char **av)
 		       repo_in_merge_bases(the_repository, A, B));
 	else if (!strcmp(av[1], "in_merge_bases_many"))
 		printf("%s(A,X):%d\n", av[1],
-		       repo_in_merge_bases_many(the_repository, A, X_nr, X_array, 0));
+		       repo_in_merge_bases_many(the_repository, A, X_stack.nr, X_stack.items, 0));
 	else if (!strcmp(av[1], "is_descendant_of"))
 		printf("%s(A,X):%d\n", av[1], repo_is_descendant_of(r, A, X));
 	else if (!strcmp(av[1], "get_branch_base_for_tip"))
-		printf("%s(A,X):%d\n", av[1], get_branch_base_for_tip(r, A, X_array, X_nr));
+		printf("%s(A,X):%d\n", av[1], get_branch_base_for_tip(r, A, X_stack.items, X_stack.nr));
 	else if (!strcmp(av[1], "get_merge_bases_many")) {
 		struct commit_list *list = NULL;
 		if (repo_get_merge_bases_many(the_repository,
-					      A, X_nr,
-					      X_array,
+					      A, X_stack.nr,
+					      X_stack.items,
 					      &list) < 0)
 			exit(128);
 		printf("%s(A,X):\n", av[1]);
 		print_sorted_commit_ids(list);
-		free_commit_list(list);
+		commit_list_free(list);
 	} else if (!strcmp(av[1], "reduce_heads")) {
 		struct commit_list *list = reduce_heads(X);
 		printf("%s(X):\n", av[1]);
 		print_sorted_commit_ids(list);
-		free_commit_list(list);
+		commit_list_free(list);
 	} else if (!strcmp(av[1], "can_all_from_reach")) {
 		printf("%s(X,Y):%d\n", av[1], can_all_from_reach(X, Y, 1));
 	} else if (!strcmp(av[1], "can_all_from_reach_with_flag")) {
@@ -159,8 +153,8 @@ int cmd__reach(int ac, const char **av)
 		const int reachable_flag = 1;
 		int count = 0;
 		struct commit_list *current;
-		struct commit_list *list = get_reachable_subset(X_array, X_nr,
-								Y_array, Y_nr,
+		struct commit_list *list = get_reachable_subset(X_stack.items, X_stack.nr,
+								Y_stack.items, Y_stack.nr,
 								reachable_flag);
 		printf("get_reachable_subset(X,Y)\n");
 		for (current = list; current; current = current->next) {
@@ -169,8 +163,8 @@ int cmd__reach(int ac, const char **av)
 				    oid_to_hex(&list->item->object.oid));
 			count++;
 		}
-		for (size_t i = 0; i < Y_nr; i++) {
-			if (Y_array[i]->object.flags & reachable_flag)
+		for (size_t i = 0; i < Y_stack.nr; i++) {
+			if (Y_stack.items[i]->object.flags & reachable_flag)
 				count--;
 		}
 
@@ -178,14 +172,14 @@ int cmd__reach(int ac, const char **av)
 			die(_("too many commits marked reachable"));
 
 		print_sorted_commit_ids(list);
-		free_commit_list(list);
+		commit_list_free(list);
 	}
 
 	object_array_clear(&X_obj);
 	strbuf_release(&buf);
-	free_commit_list(X);
-	free_commit_list(Y);
-	free(X_array);
-	free(Y_array);
+	commit_list_free(X);
+	commit_list_free(Y);
+	commit_stack_clear(&X_stack);
+	commit_stack_clear(&Y_stack);
 	return 0;
 }
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index 83b06d3..74edf20 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -154,27 +154,31 @@ static int cmd_rename_ref(struct ref_store *refs, const char **argv)
 	return refs_rename_ref(refs, oldref, newref, logmsg);
 }
 
-static int each_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-		    int flags, void *cb_data UNUSED)
+static int each_ref(const struct reference *ref, void *cb_data UNUSED)
 {
-	printf("%s %s 0x%x\n", oid_to_hex(oid), refname, flags);
+	printf("%s %s 0x%x\n", oid_to_hex(ref->oid), ref->name, ref->flags);
 	return 0;
 }
 
 static int cmd_for_each_ref(struct ref_store *refs, const char **argv)
 {
 	const char *prefix = notnull(*argv++, "prefix");
-
-	return refs_for_each_ref_in(refs, prefix, each_ref, NULL);
+	struct refs_for_each_ref_options opts = {
+		.prefix = prefix,
+		.trim_prefix = strlen(prefix),
+	};
+	return refs_for_each_ref_ext(refs, each_ref, NULL, &opts);
 }
 
 static int cmd_for_each_ref__exclude(struct ref_store *refs, const char **argv)
 {
 	const char *prefix = notnull(*argv++, "prefix");
-	const char **exclude_patterns = argv;
+	struct refs_for_each_ref_options opts = {
+		.prefix = prefix,
+		.exclude_patterns = argv,
+	};
 
-	return refs_for_each_fullref_in(refs, prefix, exclude_patterns, each_ref,
-					NULL);
+	return refs_for_each_ref_ext(refs, each_ref, NULL, &opts);
 }
 
 static int cmd_resolve_ref(struct ref_store *refs, const char **argv)
diff --git a/t/helper/test-repository.c b/t/helper/test-repository.c
index 63c37de..9ba94cd 100644
--- a/t/helper/test-repository.c
+++ b/t/helper/test-repository.c
@@ -17,10 +17,6 @@ static void test_parse_commit_in_graph(const char *gitdir, const char *worktree,
 	struct commit *c;
 	struct commit_list *parent;
 
-	setup_git_env(gitdir);
-
-	repo_clear(the_repository);
-
 	if (repo_init(&r, gitdir, worktree))
 		die("Couldn't init repo");
 
@@ -47,10 +43,6 @@ static void test_get_commit_tree_in_graph(const char *gitdir,
 	struct commit *c;
 	struct tree *tree;
 
-	setup_git_env(gitdir);
-
-	repo_clear(the_repository);
-
 	if (repo_init(&r, gitdir, worktree))
 		die("Couldn't init repo");
 
@@ -75,24 +67,20 @@ static void test_get_commit_tree_in_graph(const char *gitdir,
 
 int cmd__repository(int argc, const char **argv)
 {
-	int nongit_ok = 0;
-
-	setup_git_directory_gently(&nongit_ok);
-
 	if (argc < 2)
 		die("must have at least 2 arguments");
 	if (!strcmp(argv[1], "parse_commit_in_graph")) {
 		struct object_id oid;
 		if (argc < 5)
 			die("not enough arguments");
-		if (parse_oid_hex(argv[4], &oid, &argv[4]))
+		if (parse_oid_hex_any(argv[4], &oid, &argv[4]) == GIT_HASH_UNKNOWN)
 			die("cannot parse oid '%s'", argv[4]);
 		test_parse_commit_in_graph(argv[2], argv[3], &oid);
 	} else if (!strcmp(argv[1], "get_commit_tree_in_graph")) {
 		struct object_id oid;
 		if (argc < 5)
 			die("not enough arguments");
-		if (parse_oid_hex(argv[4], &oid, &argv[4]))
+		if (parse_oid_hex_any(argv[4], &oid, &argv[4]) == GIT_HASH_UNKNOWN)
 			die("cannot parse oid '%s'", argv[4]);
 		test_get_commit_tree_in_graph(argv[2], argv[3], &oid);
 	} else {
diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c
index 3719f23..4a56456 100644
--- a/t/helper/test-run-command.c
+++ b/t/helper/test-run-command.c
@@ -23,19 +23,26 @@ static int number_callbacks;
 static int parallel_next(struct child_process *cp,
 			 struct strbuf *err,
 			 void *cb,
-			 void **task_cb UNUSED)
+			 void **task_cb)
 {
 	struct child_process *d = cb;
 	if (number_callbacks >= 4)
 		return 0;
 
 	strvec_pushv(&cp->args, d->args.v);
+	cp->in = d->in;
+	cp->no_stdin = d->no_stdin;
 	if (err)
 		strbuf_addstr(err, "preloaded output of a child\n");
 	else
 		fprintf(stderr, "preloaded output of a child\n");
 
 	number_callbacks++;
+
+	/* test_stdin callback will use this to count remaining lines */
+	*task_cb = xmalloc(sizeof(int));
+	*(int*)(*task_cb) = 2;
+
 	return 1;
 }
 
@@ -54,15 +61,48 @@ static int no_job(struct child_process *cp UNUSED,
 static int task_finished(int result UNUSED,
 			 struct strbuf *err,
 			 void *pp_cb UNUSED,
-			 void *pp_task_cb UNUSED)
+			 void *pp_task_cb)
 {
 	if (err)
 		strbuf_addstr(err, "asking for a quick stop\n");
 	else
 		fprintf(stderr, "asking for a quick stop\n");
+
+	FREE_AND_NULL(pp_task_cb);
+
 	return 1;
 }
 
+static int task_finished_quiet(int result UNUSED,
+			       struct strbuf *err UNUSED,
+			       void *pp_cb UNUSED,
+			       void *pp_task_cb)
+{
+	FREE_AND_NULL(pp_task_cb);
+	return 0;
+}
+
+static int test_stdin_pipe_feed(int hook_stdin_fd, void *cb UNUSED, void *task_cb)
+{
+	int *lines_remaining = task_cb;
+
+	if (*lines_remaining) {
+		struct strbuf buf = STRBUF_INIT;
+		strbuf_addf(&buf, "sample stdin %d\n", --(*lines_remaining));
+		if (write_in_full(hook_stdin_fd, buf.buf, buf.len) < 0) {
+			if (errno == EPIPE) {
+				/* child closed stdin, nothing more to do */
+				strbuf_release(&buf);
+				return 1;
+			}
+			die_errno("write");
+		}
+		strbuf_release(&buf);
+	}
+
+	return !(*lines_remaining);
+}
+
 struct testsuite {
 	struct string_list tests, failed;
 	int next;
@@ -157,6 +197,7 @@ static int testsuite(int argc, const char **argv)
 	struct run_process_parallel_opts opts = {
 		.get_next_task = next_test,
 		.start_failure = test_failed,
+		.feed_pipe = test_stdin_pipe_feed,
 		.task_finished = test_finished,
 		.data = &suite,
 	};
@@ -460,12 +501,19 @@ int cmd__run_command(int argc, const char **argv)
 
 	if (!strcmp(argv[1], "run-command-parallel")) {
 		opts.get_next_task = parallel_next;
+		opts.task_finished = task_finished_quiet;
 	} else if (!strcmp(argv[1], "run-command-abort")) {
 		opts.get_next_task = parallel_next;
 		opts.task_finished = task_finished;
 	} else if (!strcmp(argv[1], "run-command-no-jobs")) {
 		opts.get_next_task = no_job;
 		opts.task_finished = task_finished;
+	} else if (!strcmp(argv[1], "run-command-stdin")) {
+		proc.in = -1;
+		proc.no_stdin = 0;
+		opts.get_next_task = parallel_next;
+		opts.task_finished = task_finished_quiet;
+		opts.feed_pipe = test_stdin_pipe_feed;
 	} else {
 		ret = 1;
 		fprintf(stderr, "check usage\n");
diff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c
index 03cc5ee..442ad6b 100644
--- a/t/helper/test-simple-ipc.c
+++ b/t/helper/test-simple-ipc.c
@@ -603,7 +603,12 @@ int cmd__simple_ipc(int argc, const char **argv)
 		OPT_INTEGER(0, "bytecount", &cl_args.bytecount, N_("number of bytes")),
 		OPT_INTEGER(0, "batchsize", &cl_args.batchsize, N_("number of requests per thread")),
 
-		OPT_STRING(0, "byte", &bytevalue, N_("byte"), N_("ballast character")),
+		/*
+		 * The "byte" string here is not marked for translation and
+		 * instead relies on translation in strbuf.c:humanise_bytes() to
+		 * avoid conflict with the plural form.
+		 */
+		OPT_STRING(0, "byte", &bytevalue, "byte", N_("ballast character")),
 		OPT_STRING(0, "token", &cl_args.token, N_("token"), N_("command token to send to the server")),
 
 		OPT_END()
diff --git a/t/helper/test-trace2.c b/t/helper/test-trace2.c
index 415df07..3b12f41 100644
--- a/t/helper/test-trace2.c
+++ b/t/helper/test-trace2.c
@@ -467,6 +467,63 @@ static int ut_303redact_def_param(int argc, const char **argv)
 }
 
 /*
+ * Run a child process with specific trace2 environment settings so that
+ * we can capture its trace2 output (including cmd_ancestry) in isolation.
+ *
+ * test-tool trace2 400ancestry <target> <output_file> [<child_command_line>]
+ *
+ * <target> is one of: normal, perf, event
+ *
+ * For example:
+ *     test-tool trace2 400ancestry normal out.normal test-tool trace2 001return 0
+ *
+ * The child process inherits a controlled trace2 environment where only
+ * the specified target is directed to <output_file>. The parent's trace2
+ * environment variables are cleared in the child so that only the child's
+ * events are captured.
+ *
+ * This is used by t0213-trace2-ancestry.sh to test cmd_ancestry events.
+ * The child process will see "test-tool" as its immediate parent in the
+ * process ancestry, giving us a predictable value to verify.
+ */
+static int ut_400ancestry(int argc, const char **argv)
+{
+	struct child_process cmd = CHILD_PROCESS_INIT;
+	const char *target;
+	const char *outfile;
+	int result;
+
+	if (argc < 3)
+		die("expect <target> <output_file> <child_command_line>");
+
+	target = argv[0];
+	outfile = argv[1];
+	argv += 2;
+	argc -= 2;
+
+	/* Clear all trace2 environment variables in the child. */
+	strvec_push(&cmd.env, "GIT_TRACE2=");
+	strvec_push(&cmd.env, "GIT_TRACE2_PERF=");
+	strvec_push(&cmd.env, "GIT_TRACE2_EVENT=");
+	strvec_push(&cmd.env, "GIT_TRACE2_BRIEF=1");
+
+	/* Set only the requested target. */
+	if (!strcmp(target, "normal"))
+		strvec_pushf(&cmd.env, "GIT_TRACE2=%s", outfile);
+	else if (!strcmp(target, "perf"))
+		strvec_pushf(&cmd.env, "GIT_TRACE2_PERF=%s", outfile);
+	else if (!strcmp(target, "event"))
+		strvec_pushf(&cmd.env, "GIT_TRACE2_EVENT=%s", outfile);
+	else
+		die("invalid target '%s', expected: normal, perf, event",
+		    target);
+
+	strvec_pushv(&cmd.args, argv);
+	result = run_command(&cmd);
+	exit(result);
+}
+
+/*
  * Usage:
  *     test-tool trace2 <ut_name_1> <ut_usage_1>
  *     test-tool trace2 <ut_name_2> <ut_usage_2>
@@ -497,6 +554,8 @@ static struct unit_test ut_table[] = {
 	{ ut_301redact_child_start, "301redact_child_start", "<argv...>" },
 	{ ut_302redact_exec,        "302redact_exec",        "<exe> <argv...>" },
 	{ ut_303redact_def_param,   "303redact_def_param",   "<key> <value>" },
+
+	{ ut_400ancestry,           "400ancestry",           "<target> <output_file> [<child_command_line>]" },
 };
 /* clang-format on */
 
diff --git a/t/lib-cvs.sh b/t/lib-cvs.sh
index 57b9b2d..c8b4404 100644
--- a/t/lib-cvs.sh
+++ b/t/lib-cvs.sh
@@ -2,6 +2,12 @@
 
 . ./test-lib.sh
 
+if test -n "$NO_CVS_TESTS"
+then
+	skip_all='skipping git cvs tests, NO_CVS_TESTS defined'
+	test_done
+fi
+
 unset CVS_SERVER
 
 if ! type cvs >/dev/null 2>&1
diff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh
index 2a5b873..d22e9c6 100644
--- a/t/lib-git-p4.sh
+++ b/t/lib-git-p4.sh
@@ -16,6 +16,11 @@
 
 . ./test-lib.sh
 
+if test -n "$NO_P4_TESTS"
+then
+	skip_all='skipping git p4 tests, NO_P4_TESTS defined'
+	test_done
+fi
 if ! test_have_prereq PYTHON
 then
 	skip_all='skipping git p4 tests; python not available'
diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh
index 5091db9..5f42c31 100644
--- a/t/lib-httpd.sh
+++ b/t/lib-httpd.sh
@@ -319,13 +319,22 @@
 	'
 }
 
-set_askpass() {
+set_askpass () {
 	>"$TRASH_DIRECTORY/askpass-query" &&
 	echo "$1" >"$TRASH_DIRECTORY/askpass-user" &&
 	echo "$2" >"$TRASH_DIRECTORY/askpass-pass"
 }
 
-expect_askpass() {
+set_netrc () {
+	# $HOME=$TRASH_DIRECTORY
+	echo "machine $1 login $2 password $3" >"$TRASH_DIRECTORY/.netrc"
+}
+
+clear_netrc () {
+	rm -f "$TRASH_DIRECTORY/.netrc"
+}
+
+expect_askpass () {
 	dest=$HTTPD_DEST${3+/$3}
 
 	{
diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf
index e631ab0..6b8c50a 100644
--- a/t/lib-httpd/apache.conf
+++ b/t/lib-httpd/apache.conf
@@ -238,6 +238,10 @@
 	AuthName "git-auth"
 	AuthUserFile passwd
 	Require valid-user
+
+	# return 403 for authenticated user: forbidden-user@host
+	RewriteCond "%{REMOTE_USER}" "^forbidden-user@host"
+	RewriteRule ^ - [F]
 </Location>
 
 <LocationMatch "^/auth-push/.*/git-receive-pack$">
diff --git a/t/lib-httpd/passwd b/t/lib-httpd/passwd
index d9c122f..3bab7b6 100644
--- a/t/lib-httpd/passwd
+++ b/t/lib-httpd/passwd
@@ -1 +1,2 @@
 user@host:$apr1$LGPmCZWj$9vxEwj5Z5GzQLBMxp3mCx1
+forbidden-user@host:$apr1$LGPmCZWj$9vxEwj5Z5GzQLBMxp3mCx1
diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh
index 36f767c..f591de6 100644
--- a/t/lib-submodule-update.sh
+++ b/t/lib-submodule-update.sh
@@ -95,14 +95,14 @@
 			git commit -m "modified file2 and added file3" &&
 			git push origin modifications
 		) &&
-		git add sub1 &&
+		git add --force sub1 &&
 		git commit -m "Modify sub1" &&
 
 		git checkout -b add_nested_sub modify_sub1 &&
 		git -C sub1 checkout -b "add_nested_sub" &&
 		git -C sub1 submodule add --branch no_submodule ../submodule_update_sub2 sub2 &&
 		git -C sub1 commit -a -m "add a nested submodule" &&
-		git add sub1 &&
+		git add --force sub1 &&
 		git commit -a -m "update submodule, that updates a nested submodule" &&
 		git checkout -b modify_sub1_recursively &&
 		git -C sub1 checkout -b modify_sub1_recursively &&
@@ -112,7 +112,7 @@
 		git -C sub1/sub2 commit -m "make a change in nested sub" &&
 		git -C sub1 add sub2 &&
 		git -C sub1 commit -m "update nested sub" &&
-		git add sub1 &&
+		git add --force sub1 &&
 		git commit -m "update sub1, that updates nested sub" &&
 		git -C sub1 push origin modify_sub1_recursively &&
 		git -C sub1/sub2 push origin modify_sub1_recursively &&
diff --git a/t/lib-unicode-nfc-nfd.sh b/t/lib-unicode-nfc-nfd.sh
index aed0a4d..201ab9b 100755
--- a/t/lib-unicode-nfc-nfd.sh
+++ b/t/lib-unicode-nfc-nfd.sh
@@ -75,7 +75,7 @@
 #
 # Note that I've used the canonical ordering of the
 # combining characters.  It is also possible to
-# swap them.  My testing shows that that non-standard
+# swap them.  My testing shows that non-standard
 # ordering also causes a collision in mkdir.  However,
 # the resulting names don't draw correctly on the
 # terminal (implying that the on-disk format also has
diff --git a/t/lib-verify-submodule-gitdir-path.sh b/t/lib-verify-submodule-gitdir-path.sh
new file mode 100644
index 0000000..4e0cfdc
--- /dev/null
+++ b/t/lib-verify-submodule-gitdir-path.sh
@@ -0,0 +1,24 @@
+# Helper to verify if repo $1 contains a submodule named $2 with gitdir path $3
+
+# This does not check filesystem existence. That is done in submodule.c via the
+# submodule_name_to_gitdir() API which this helper ends up calling. The gitdirs
+# might or might not exist (e.g. when adding a new submodule), so this only
+# checks the expected configuration path, which might be overridden by the user.
+
+verify_submodule_gitdir_path () {
+	repo="$1" &&
+	name="$2" &&
+	path="$3" &&
+	(
+		cd "$repo" &&
+		# Compute expected absolute path
+		expected="$(git rev-parse --git-common-dir)/$path" &&
+		expected="$(test-tool path-utils real_path "$expected")" &&
+		# Compute actual absolute path
+		actual="$(git submodule--helper gitdir "$name")" &&
+		actual="$(test-tool path-utils real_path "$actual")" &&
+		echo "$expected" >expect &&
+		echo "$actual" >actual &&
+		test_cmp expect actual
+	)
+}
diff --git a/t/meson.build b/t/meson.build
index a5531df..9b2fa4d 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -4,6 +4,7 @@
   'unit-tests/u-example-decorate.c',
   'unit-tests/u-hash.c',
   'unit-tests/u-hashmap.c',
+  'unit-tests/u-list-objects-filter-options.c',
   'unit-tests/u-mem-pool.c',
   'unit-tests/u-oid-array.c',
   'unit-tests/u-oidmap.c',
@@ -24,6 +25,7 @@
   'unit-tests/u-strvec.c',
   'unit-tests/u-trailer.c',
   'unit-tests/u-urlmatch-normalization.c',
+  'unit-tests/u-utf8-width.c',
 ]
 
 clar_sources = [
@@ -79,6 +81,7 @@
   't0006-date.sh',
   't0007-git-var.sh',
   't0008-ignores.sh',
+  't0009-git-dir-validation.sh',
   't0010-racy-git.sh',
   't0012-help.sh',
   't0013-sha1dc.sh',
@@ -97,6 +100,7 @@
   't0028-working-tree-encoding.sh',
   't0029-core-unsetenvvars.sh',
   't0030-stripspace.sh',
+  't0031-lockfile-pid.sh',
   't0033-safe-directory.sh',
   't0034-root-safe-directory.sh',
   't0035-safe-bare-repository.sh',
@@ -131,6 +135,7 @@
   't0210-trace2-normal.sh',
   't0211-trace2-perf.sh',
   't0212-trace2-event.sh',
+  't0213-trace2-ancestry.sh',
   't0300-credentials.sh',
   't0301-credential-cache.sh',
   't0302-credential-store.sh',
@@ -184,6 +189,7 @@
   't1308-config-set.sh',
   't1309-early-config.sh',
   't1310-config-default.sh',
+  't1311-config-optional.sh',
   't1350-config-hooks-path.sh',
   't1400-update-ref.sh',
   't1401-symbolic-ref.sh',
@@ -208,6 +214,7 @@
   't1420-lost-found.sh',
   't1421-reflog-write.sh',
   't1422-show-ref-exists.sh',
+  't1423-ref-backend.sh',
   't1430-bad-ref-name.sh',
   't1450-fsck.sh',
   't1451-fsck-buffer.sh',
@@ -237,7 +244,7 @@
   't1700-split-index.sh',
   't1701-racy-split-index.sh',
   't1800-hook.sh',
-  't1900-repo.sh',
+  't1900-repo-info.sh',
   't1901-repo-structure.sh',
   't2000-conflict-when-checking-files-out.sh',
   't2002-checkout-cache-u.sh',
@@ -290,6 +297,7 @@
   't2203-add-intent.sh',
   't2204-add-ignored.sh',
   't2205-add-worktree-config.sh',
+  't2206-add-submodule-ignored.sh',
   't2300-cd-to-toplevel.sh',
   't2400-worktree-add.sh',
   't2401-worktree-prune.sh',
@@ -385,6 +393,8 @@
   't3436-rebase-more-options.sh',
   't3437-rebase-fixup-options.sh',
   't3438-rebase-broken-files.sh',
+  't3450-history.sh',
+  't3451-history-reword.sh',
   't3500-cherry.sh',
   't3501-revert-cherry-pick.sh',
   't3502-cherry-pick-merge.sh',
@@ -496,6 +506,7 @@
   't4070-diff-pairs.sh',
   't4071-diff-minimal.sh',
   't4072-diff-max-depth.sh',
+  't4073-diff-stat-name-width.sh',
   't4100-apply-stat.sh',
   't4101-apply-nonl.sh',
   't4102-apply-rename.sh',
@@ -688,6 +699,7 @@
   't5562-http-backend-content-length.sh',
   't5563-simple-http-auth.sh',
   't5564-http-proxy.sh',
+  't5565-push-multiple.sh',
   't5570-git-daemon.sh',
   't5571-pre-push-hook.sh',
   't5572-pull-submodule.sh',
@@ -884,6 +896,7 @@
   't7422-submodule-output.sh',
   't7423-submodule-symlinks.sh',
   't7424-submodule-mixed-ref-formats.sh',
+  't7425-submodule-gitdir-path-extension.sh',
   't7450-bad-git-dotfiles.sh',
   't7500-commit-template-squash-signoff.sh',
   't7501-commit-basic-functionality.sh',
@@ -956,6 +969,7 @@
   't8012-blame-colors.sh',
   't8013-blame-ignore-revs.sh',
   't8014-blame-ignore-fuzzy.sh',
+  't8015-blame-diff-algorithm.sh',
   't8020-last-modified.sh',
   't9001-send-email.sh',
   't9002-column.sh',
@@ -1202,6 +1216,7 @@
 
 test_environment = script_environment
 test_environment.set('GIT_BUILD_DIR', git_build_dir)
+test_environment.set('MERGE_TOOLS_DIR', meson.project_source_root() / 'mergetools')
 
 foreach integration_test : integration_tests
   test(fs.stem(integration_test), shell,
diff --git a/t/pack-refs-tests.sh b/t/pack-refs-tests.sh
index 3dbcc01..2fdaccb 100644
--- a/t/pack-refs-tests.sh
+++ b/t/pack-refs-tests.sh
@@ -354,8 +354,8 @@
 
 			# Create 14 additional references, which brings us to
 			# 15 together with the default branch.
-			printf "create refs/heads/loose-%d HEAD\n" $(test_seq 14) >stdin &&
-			git update-ref --stdin <stdin &&
+			test_seq -f "create refs/heads/loose-%d HEAD" 14 |
+			git update-ref --stdin &&
 			test_path_is_missing .git/packed-refs &&
 			git ${pack_refs} --auto --all &&
 			test_path_is_missing .git/packed-refs &&
@@ -379,8 +379,8 @@
 			test_line_count = 2 .git/packed-refs &&
 
 			# Create 15 loose references.
-			printf "create refs/heads/loose-%d HEAD\n" $(test_seq 15) >stdin &&
-			git update-ref --stdin <stdin &&
+			test_seq -f "create refs/heads/loose-%d HEAD" 15 |
+			git update-ref --stdin &&
 			git ${pack_refs} --auto --all &&
 			test_line_count = 2 .git/packed-refs &&
 
@@ -401,18 +401,14 @@
 
 			# Create 99 packed refs. This should cause the heuristic
 			# to require more than the minimum amount of loose refs.
-			test_seq 99 |
-			while read i
-			do
-				printf "create refs/heads/packed-%d HEAD\n" $i || return 1
-			done >stdin &&
-			git update-ref --stdin <stdin &&
+			test_seq -f "create refs/heads/packed-%d HEAD" 99 |
+			git update-ref --stdin &&
 			git ${pack_refs} --all &&
 			test_line_count = 101 .git/packed-refs &&
 
 			# Create 24 loose refs, which should not yet cause us to repack.
-			printf "create refs/heads/loose-%d HEAD\n" $(test_seq 24) >stdin &&
-			git update-ref --stdin <stdin &&
+			test_seq -f "create refs/heads/loose-%d HEAD" 24 |
+			git update-ref --stdin &&
 			git ${pack_refs} --auto --all &&
 			test_line_count = 101 .git/packed-refs &&
 
@@ -420,12 +416,42 @@
 			# Note that we explicitly do not check for strict
 			# boundaries here, as this also depends on the size of
 			# the object hash.
-			printf "create refs/heads/addn-%d HEAD\n" $(test_seq 10) >stdin &&
-			git update-ref --stdin <stdin &&
+			test_seq -f "create refs/heads/addn-%d HEAD" 10 |
+			git update-ref --stdin &&
 			git ${pack_refs} --auto --all &&
 			test_line_count = 135 .git/packed-refs
 		)
 	'
 done
 
-test_done
+test_expect_success 'pack-refs does not store invalid peeled tag value' '
+	test_when_finished rm -rf repo &&
+	git init repo &&
+	(
+		cd repo &&
+		git commit --allow-empty --message initial &&
+
+		echo garbage >blob-content &&
+		blob_id=$(git hash-object -w -t blob blob-content) &&
+
+		# Write an invalid tag into the object database. The tag itself
+		# is well-formed, but the tagged object is a blob while we
+		# claim that it is a commit.
+		cat >tag-content <<-EOF &&
+		object $blob_id
+		type commit
+		tag bad-tag
+		tagger C O Mitter <committer@example.com> 1112354055 +0200
+
+		annotated
+		EOF
+		tag_id=$(git hash-object -w -t tag tag-content) &&
+		git update-ref refs/tags/bad-tag "$tag_id" &&
+
+		# The packed-refs file should not contain the peeled object ID.
+		# If it did this would cause commands that use the peeled value
+		# to not notice this corrupted tag.
+		git pack-refs --all &&
+		test_grep ! "^\^" .git/packed-refs
+	)
+'
diff --git a/t/perf/p1006-cat-file.sh b/t/perf/p1006-cat-file.sh
index dcd8015..da34ece 100755
--- a/t/perf/p1006-cat-file.sh
+++ b/t/perf/p1006-cat-file.sh
@@ -9,4 +9,18 @@
 	git cat-file --batch-all-objects --batch-check
 '
 
+test_perf 'list all objects (sorted)' '
+	git cat-file --batch-all-objects --batch-check="%(objectname)"
+'
+
+test_perf 'list all objects (unsorted)' '
+	git cat-file --batch-all-objects --batch-check="%(objectname)" \
+		--unordered
+'
+
+test_perf 'list blobs' '
+	git cat-file --batch-all-objects --batch-check="%(objectname)" \
+		--unordered --filter=object:type=blob
+'
+
 test_done
diff --git a/t/perf/p3400-rebase.sh b/t/perf/p3400-rebase.sh
index e6b0277..56dc3e1 100755
--- a/t/perf/p3400-rebase.sh
+++ b/t/perf/p3400-rebase.sh
@@ -9,21 +9,44 @@
 	git checkout -f -B base &&
 	git checkout -B to-rebase &&
 	git checkout -B upstream &&
-	for i in $(test_seq 100)
-	do
-		# simulate huge diffs
-		echo change$i >unrelated-file$i &&
-		test_seq 1000 >>unrelated-file$i &&
-		git add unrelated-file$i &&
-		test_tick &&
-		git commit -m commit$i unrelated-file$i &&
-		echo change$i >unrelated-file$i &&
-		test_seq 1000 | sort -nr >>unrelated-file$i &&
-		git add unrelated-file$i &&
-		test_tick &&
-		git commit -m commit$i-reverse unrelated-file$i ||
-		return 1
-	done &&
+	test_seq 1000 >content_fwd &&
+	sort -nr content_fwd >content_rev &&
+	(
+		for i in $(test_seq 100)
+		do
+			test_tick &&
+			echo "commit refs/heads/upstream" &&
+			echo "committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE" &&
+			echo "data <<EOF" &&
+			echo "commit$i" &&
+			echo "EOF" &&
+
+			if test "$i" = 1; then
+				echo "from refs/heads/upstream^0"
+			fi &&
+
+			echo "M 100644 inline unrelated-file$i" &&
+			echo "data <<EOF" &&
+			echo "change$i" &&
+			cat content_fwd &&
+			echo "EOF" &&
+
+			echo "commit refs/heads/upstream" &&
+			echo "committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE" &&
+			echo "data <<EOF" &&
+			echo "commit$i-reverse" &&
+			echo "EOF" &&
+			echo "M 100644 inline unrelated-file$i" &&
+			echo "data <<EOF" &&
+			echo "change$i" &&
+			cat content_rev &&
+			echo "EOF" || exit 1
+		done
+	) >fast_import_stream &&
+
+	git fast-import <fast_import_stream &&
+	git repack -a -d &&
+	git checkout -f upstream &&
 	git checkout to-rebase &&
 	test_commit our-patch interesting-file
 '
diff --git a/t/perf/p6010-merge-base.sh b/t/perf/p6010-merge-base.sh
index 54f52fa..08212dd 100755
--- a/t/perf/p6010-merge-base.sh
+++ b/t/perf/p6010-merge-base.sh
@@ -83,9 +83,9 @@
 test_expect_success 'setup' '
 	max_level=15 &&
 	build_history $max_level | git fast-import --export-marks=marks &&
-	git tag one &&
+	git branch one &&
 	build_history2 $max_level | git fast-import --import-marks=marks --force &&
-	git tag two &&
+	git branch two &&
 	git gc &&
 	git log --format=%H --no-merges >expect
 '
@@ -98,4 +98,8 @@
 	test_cmp expect actual
 '
 
+test_perf 'git show-branch' '
+	git show-branch one two
+'
+
 test_done
diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh
index b15c74d..2ac0078 100644
--- a/t/perf/perf-lib.sh
+++ b/t/perf/perf-lib.sh
@@ -20,7 +20,7 @@
 # These variables must be set before the inclusion of test-lib.sh below,
 # because it will change our working directory.
 TEST_DIRECTORY=$(pwd)/..
-TEST_OUTPUT_DIRECTORY=$(pwd)
+perf_dir=$(pwd)
 
 TEST_NO_CREATE_REPO=t
 TEST_NO_MALLOC_CHECK=t
@@ -58,6 +58,7 @@
 fi
 
 . "$GIT_BUILD_DIR"/GIT-BUILD-OPTIONS
+: ${TEST_OUTPUT_DIRECTORY:=$perf_dir}
 . "$GIT_SOURCE_DIR"/t/test-lib.sh
 
 # Then restore GIT_PERF_* settings.
diff --git a/t/perf/run b/t/perf/run
index 073bcb2..13913db 100755
--- a/t/perf/run
+++ b/t/perf/run
@@ -204,8 +204,18 @@
 get_var_from_env_or_config "GIT_PERF_CODESPEED_OUTPUT" "perf" "codespeedOutput" "--bool"
 get_var_from_env_or_config "GIT_PERF_SEND_TO_CODESPEED" "perf" "sendToCodespeed"
 
+# Preserve GIT_PERF settings from the environment when loading
+# GIT-BUILD-OPTIONS; see the similar hack in perf-lib.sh.
+git_perf_settings="$(env |
+        sed -n "/^GIT_PERF_/{
+                # escape all single-quotes in the value
+                s/'/'\\\\''/g
+                # turn this into an eval-able assignment
+                s/^\\([^=]*=\\)\\(.*\\)/\\1'\\2'/p
+        }")"
 cd "$(dirname $0)"
 . ../../GIT-BUILD-OPTIONS
+eval "$git_perf_settings"
 
 if test -n "$TEST_OUTPUT_DIRECTORY"
 then
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index 618da08..e4d32bb 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -425,7 +425,11 @@
 	git init --separate-git-dir ../realgitdir
 	) &&
 	echo "gitdir: $(pwd)/realgitdir" >expected &&
-	test_cmp expected newdir/.git &&
+	case "$GIT_TEST_CMP" in
+	# `git diff --no-index` does not resolve symlinks
+	*--no-index*) cmp expected newdir/.git;;
+	*) test_cmp expected newdir/.git;;
+	esac &&
 	test_cmp expected newdir/here &&
 	test_path_is_dir realgitdir/refs
 '
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index 3c98b62..582e207 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -664,4 +664,24 @@
 	test_cmp expect err
 '
 
+test_expect_success ULIMIT_STACK_SIZE 'deep macro recursion' '
+	n=3000 &&
+	{
+		i=0 &&
+		while test $i -lt $n; do
+			echo "[attr]a$i a$((i+1))" &&
+			i=$((i+1)) ||
+			return 1
+		done &&
+		echo "[attr]a$n -text" &&
+		echo "file a0"
+	} >.gitattributes &&
+	{
+		echo "file: text: unset" &&
+		test_seq -f "file: a%d: set" 0 $n
+	} >expect &&
+	run_with_limited_stack git check-attr -a file >actual &&
+	test_cmp expect actual
+'
+
 test_done
diff --git a/t/t0009-git-dir-validation.sh b/t/t0009-git-dir-validation.sh
new file mode 100755
index 0000000..33d21ed
--- /dev/null
+++ b/t/t0009-git-dir-validation.sh
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+test_description='setup: validation of .git file/directory types
+
+Verify that setup_git_directory() correctly handles:
+1. Valid .git directories (including symlinks to them).
+2. Invalid .git files (FIFOs, sockets) by erroring out.
+3. Invalid .git files (garbage) by erroring out.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'setup: create parent git repository' '
+	git init parent &&
+	test_commit -C parent "root-commit"
+'
+
+test_expect_success SYMLINKS 'setup: .git as a symlink to a directory is valid' '
+	test_when_finished "rm -rf parent/link-to-dir" &&
+	mkdir -p parent/link-to-dir &&
+	(
+		cd parent/link-to-dir &&
+		git init real-repo &&
+		ln -s real-repo/.git .git &&
+		git rev-parse --git-dir >actual &&
+		echo .git >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success PIPE 'setup: .git as a FIFO (named pipe) is rejected' '
+	test_when_finished "rm -rf parent/fifo-trap" &&
+	mkdir -p parent/fifo-trap &&
+	(
+		cd parent/fifo-trap &&
+		mkfifo .git &&
+		test_must_fail git rev-parse --git-dir 2>stderr &&
+		grep "not a regular file" stderr
+	)
+'
+
+test_expect_success SYMLINKS,PIPE 'setup: .git as a symlink to a FIFO is rejected' '
+	test_when_finished "rm -rf parent/symlink-fifo-trap" &&
+	mkdir -p parent/symlink-fifo-trap &&
+	(
+		cd parent/symlink-fifo-trap &&
+		mkfifo target-fifo &&
+		ln -s target-fifo .git &&
+		test_must_fail git rev-parse --git-dir 2>stderr &&
+		grep "not a regular file" stderr
+	)
+'
+
+test_expect_success 'setup: .git with garbage content is rejected' '
+	test_when_finished "rm -rf parent/garbage-trap" &&
+	mkdir -p parent/garbage-trap &&
+	(
+		cd parent/garbage-trap &&
+		echo "garbage" >.git &&
+		test_must_fail git rev-parse --git-dir 2>stderr &&
+		grep "invalid gitfile format" stderr
+	)
+'
+
+test_expect_success 'setup: .git as an empty directory is ignored' '
+	test_when_finished "rm -rf parent/empty-dir" &&
+	mkdir -p parent/empty-dir &&
+	(
+		cd parent/empty-dir &&
+		git rev-parse --git-dir >expect &&
+		mkdir .git &&
+		git rev-parse --git-dir >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_done
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 07a53e7..68b4903 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -112,4 +112,89 @@
 	done
 '
 
+test_expect_success 'alias without value reports error' '
+	test_when_finished "git config --unset alias.noval" &&
+	cat >>.git/config <<-\EOF &&
+	[alias]
+		noval
+	EOF
+	test_must_fail git noval 2>error &&
+	test_grep "alias.noval" error
+'
+
+test_expect_success 'subsection syntax works' '
+	test_config alias.testnew.command "!echo ran-subsection" &&
+	git testnew >output &&
+	test_grep "ran-subsection" output
+'
+
+test_expect_success 'subsection syntax only accepts command key' '
+	test_config alias.invalid.notcommand value &&
+	test_must_fail git invalid 2>error &&
+	test_grep -i "not a git command" error
+'
+
+test_expect_success 'subsection syntax requires value for command' '
+	test_when_finished "git config --remove-section alias.noval" &&
+	cat >>.git/config <<-\EOF &&
+	[alias "noval"]
+		command
+	EOF
+	test_must_fail git noval 2>error &&
+	test_grep "alias.noval.command" error
+'
+
+test_expect_success 'simple syntax is case-insensitive' '
+	test_config alias.LegacyCase "!echo ran-legacy" &&
+	git legacycase >output &&
+	test_grep "ran-legacy" output
+'
+
+test_expect_success 'subsection syntax is case-sensitive' '
+	test_config alias.SubCase.command "!echo ran-upper" &&
+	test_config alias.subcase.command "!echo ran-lower" &&
+	git SubCase >upper.out &&
+	git subcase >lower.out &&
+	test_grep "ran-upper" upper.out &&
+	test_grep "ran-lower" lower.out
+'
+
+test_expect_success 'UTF-8 alias with Swedish characters' '
+	test_config alias."förgrena".command "!echo ran-swedish" &&
+	git förgrena >output &&
+	test_grep "ran-swedish" output
+'
+
+test_expect_success 'UTF-8 alias with CJK characters' '
+	test_config alias."分支".command "!echo ran-cjk" &&
+	git 分支 >output &&
+	test_grep "ran-cjk" output
+'
+
+test_expect_success 'alias with spaces in name' '
+	test_config alias."test name".command "!echo ran-spaces" &&
+	git "test name" >output &&
+	test_grep "ran-spaces" output
+'
+
+test_expect_success 'subsection aliases listed in help -a' '
+	test_config alias."förgrena".command "!echo test" &&
+	git help -a >output &&
+	test_grep "förgrena" output
+'
+
+test_expect_success 'empty subsection treated as no subsection' '
+	test_config "alias..something" "!echo foobar" &&
+	git something >actual &&
+	echo foobar >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'alias with leading dot via subsection syntax' '
+	test_config alias.".something".command "!echo foobar" &&
+	git .something >actual &&
+	echo foobar >expect &&
+	test_cmp expect actual
+'
+
 test_done
diff --git a/t/t0031-lockfile-pid.sh b/t/t0031-lockfile-pid.sh
new file mode 100755
index 0000000..8ef87ad
--- /dev/null
+++ b/t/t0031-lockfile-pid.sh
@@ -0,0 +1,105 @@
+#!/bin/sh
+
+test_description='lock file PID info tests
+
+Tests for PID info file alongside lock files.
+The feature is opt-in via core.lockfilePid config setting (boolean).
+'
+
+. ./test-lib.sh
+
+test_expect_success 'stale lock detected when PID is not running' '
+	git init repo &&
+	(
+		cd repo &&
+		touch .git/index.lock &&
+		printf "pid 99999" >.git/index~pid.lock &&
+		test_must_fail git -c core.lockfilePid=true add . 2>err &&
+		test_grep "process 99999, which is no longer running" err &&
+		test_grep "appears to be stale" err
+	)
+'
+
+test_expect_success 'PID info not shown by default' '
+	git init repo2 &&
+	(
+		cd repo2 &&
+		touch .git/index.lock &&
+		printf "pid 99999" >.git/index~pid.lock &&
+		test_must_fail git add . 2>err &&
+		# Should not crash, just show normal error without PID
+		test_grep "Unable to create" err &&
+		! test_grep "is held by process" err
+	)
+'
+
+test_expect_success 'running process detected when PID is alive' '
+	git init repo3 &&
+	(
+		cd repo3 &&
+		echo content >file &&
+		# Get the correct PID for this platform
+		shell_pid=$$ &&
+		if test_have_prereq MINGW && test -f /proc/$shell_pid/winpid
+		then
+			# In Git for Windows, Bash uses MSYS2 PIDs but git.exe
+			# uses Windows PIDs. Use the Windows PID.
+			shell_pid=$(cat /proc/$shell_pid/winpid)
+		fi &&
+		# Create a lock and PID file with current shell PID (which is running)
+		touch .git/index.lock &&
+		printf "pid %d" "$shell_pid" >.git/index~pid.lock &&
+		# Verify our PID is shown in the error message
+		test_must_fail git -c core.lockfilePid=true add file 2>err &&
+		test_grep "held by process $shell_pid" err
+	)
+'
+
+test_expect_success 'PID info file cleaned up on successful operation when enabled' '
+	git init repo4 &&
+	(
+		cd repo4 &&
+		echo content >file &&
+		git -c core.lockfilePid=true add file &&
+		# After successful add, no lock or PID files should exist
+		test_path_is_missing .git/index.lock &&
+		test_path_is_missing .git/index~pid.lock
+	)
+'
+
+test_expect_success 'no PID file created by default' '
+	git init repo5 &&
+	(
+		cd repo5 &&
+		echo content >file &&
+		git add file &&
+		# PID file should not be created when feature is disabled
+		test_path_is_missing .git/index~pid.lock
+	)
+'
+
+test_expect_success 'core.lockfilePid=false does not create PID file' '
+	git init repo6 &&
+	(
+		cd repo6 &&
+		echo content >file &&
+		git -c core.lockfilePid=false add file &&
+		# PID file should not be created when feature is disabled
+		test_path_is_missing .git/index~pid.lock
+	)
+'
+
+test_expect_success 'existing PID files are read even when feature disabled' '
+	git init repo7 &&
+	(
+		cd repo7 &&
+		touch .git/index.lock &&
+		printf "pid 99999" >.git/index~pid.lock &&
+		# Even with lockfilePid disabled, existing PID files are read
+		# to help diagnose stale locks
+		test_must_fail git add . 2>err &&
+		test_grep "process 99999" err
+	)
+'
+
+test_done
diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh
index 76d4936..2f77fde 100755
--- a/t/t0061-run-command.sh
+++ b/t/t0061-run-command.sh
@@ -164,6 +164,37 @@
 	test_line_count = 4 err
 '
 
+test_expect_success 'run_command listens to stdin' '
+	cat >expect <<-\EOF &&
+	preloaded output of a child
+	listening for stdin:
+	sample stdin 1
+	sample stdin 0
+	preloaded output of a child
+	listening for stdin:
+	sample stdin 1
+	sample stdin 0
+	preloaded output of a child
+	listening for stdin:
+	sample stdin 1
+	sample stdin 0
+	preloaded output of a child
+	listening for stdin:
+	sample stdin 1
+	sample stdin 0
+	EOF
+
+	write_script stdin-script <<-\EOF &&
+	echo "listening for stdin:"
+	while read line
+	do
+		echo "$line"
+	done
+	EOF
+	test-tool run-command run-command-stdin 2 ./stdin-script 2>actual &&
+	test_cmp expect actual
+'
+
 cat >expect <<-EOF
 preloaded output of a child
 asking for a quick stop
diff --git a/t/t0068-for-each-repo.sh b/t/t0068-for-each-repo.sh
index f2f3e50..80b163e 100755
--- a/t/t0068-for-each-repo.sh
+++ b/t/t0068-for-each-repo.sh
@@ -2,17 +2,23 @@
 
 test_description='git for-each-repo builtin'
 
+# We need to test running 'git for-each-repo' outside of a repo context.
+TEST_NO_CREATE_REPO=1
+
 . ./test-lib.sh
 
 test_expect_success 'run based on configured value' '
-	git init one &&
-	git init two &&
-	git init three &&
-	git init ~/four &&
+	git init --initial-branch=one one &&
+	git init --initial-branch=two two &&
+	git -C two worktree add --orphan ../three &&
+	git -C three checkout -b three &&
+	git init --initial-branch=four ~/four &&
+
 	git -C two commit --allow-empty -m "DID NOT RUN" &&
-	git config run.key "$TRASH_DIRECTORY/one" &&
-	git config --add run.key "$TRASH_DIRECTORY/three" &&
-	git config --add run.key "~/four" &&
+	git config --global run.key "$TRASH_DIRECTORY/one" &&
+	git config --global --add run.key "$TRASH_DIRECTORY/three" &&
+	git config --global --add run.key "~/four" &&
+
 	git for-each-repo --config=run.key commit --allow-empty -m "ran" &&
 	git -C one log -1 --pretty=format:%s >message &&
 	grep ran message &&
@@ -22,6 +28,7 @@
 	grep ran message &&
 	git -C ~/four log -1 --pretty=format:%s >message &&
 	grep ran message &&
+
 	git for-each-repo --config=run.key -- commit --allow-empty -m "ran again" &&
 	git -C one log -1 --pretty=format:%s >message &&
 	grep again message &&
@@ -30,7 +37,43 @@
 	git -C three log -1 --pretty=format:%s >message &&
 	grep again message &&
 	git -C ~/four log -1 --pretty=format:%s >message &&
-	grep again message
+	grep again message &&
+
+	git -C three for-each-repo --config=run.key -- \
+		commit --allow-empty -m "ran from worktree" &&
+	git -C one log -1 --pretty=format:%s >message &&
+	test_grep "ran from worktree" message &&
+	git -C two log -1 --pretty=format:%s >message &&
+	test_grep ! "ran from worktree" message &&
+	git -C three log -1 --pretty=format:%s >message &&
+	test_grep "ran from worktree" message &&
+	git -C ~/four log -1 --pretty=format:%s >message &&
+	test_grep "ran from worktree" message &&
+
+	# Test running with config values set by environment
+	cat >expect <<-EOF &&
+	ran from worktree (HEAD -> refs/heads/one)
+	ran from worktree (HEAD -> refs/heads/three)
+	ran from worktree (HEAD -> refs/heads/four)
+	EOF
+
+	GIT_CONFIG_PARAMETERS="${SQ}log.decorate=full${SQ}" \
+		git -C three for-each-repo --config=run.key -- log --format="%s%d" -1 >out &&
+	test_cmp expect out &&
+
+	cat >test-config <<-EOF &&
+	[run]
+		key = $(pwd)/one
+		key = $(pwd)/three
+		key = $(pwd)/four
+
+	[log]
+		decorate = full
+	EOF
+
+	GIT_CONFIG_GLOBAL="$(pwd)/test-config" \
+		git -C three for-each-repo --config=run.key -- log --format="%s%d" -1 >out &&
+	test_cmp expect out
 '
 
 test_expect_success 'do nothing on empty config' '
@@ -46,7 +89,7 @@
 '
 
 test_expect_success 'error on NULL value for config keys' '
-	cat >>.git/config <<-\EOF &&
+	cat >>.gitconfig <<-\EOF &&
 	[empty]
 		key
 	EOF
@@ -59,8 +102,8 @@
 '
 
 test_expect_success '--keep-going' '
-	git config keep.going non-existing &&
-	git config --add keep.going . &&
+	git config --global keep.going non-existing &&
+	git config --global --add keep.going one &&
 
 	test_must_fail git for-each-repo --config=keep.going \
 		-- branch >out 2>err &&
@@ -70,7 +113,7 @@
 	test_must_fail git for-each-repo --config=keep.going --keep-going \
 		-- branch >out 2>err &&
 	test_grep "cannot change to .*non-existing" err &&
-	git branch >expect &&
+	git -C one branch >expect &&
 	test_cmp expect out
 '
 
diff --git a/t/t0081-find-pack.sh b/t/t0081-find-pack.sh
index 5a628bf..26f0174 100755
--- a/t/t0081-find-pack.sh
+++ b/t/t0081-find-pack.sh
@@ -68,6 +68,7 @@
 '
 
 test_expect_success 'add more commits (as loose objects)' '
+	test_config maintenance.auto false &&
 	test_commit six &&
 	test_commit seven &&
 
diff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh
index 96c68f6..7e1e7af 100755
--- a/t/t0210-trace2-normal.sh
+++ b/t/t0210-trace2-normal.sh
@@ -74,8 +74,9 @@
 	#      This line is only emitted when RUNTIME_PREFIX is defined,
 	#      so just omit it for testing purposes.
 	#
-	#   4. 'cmd_ancestry' is not implemented everywhere, so for portability's
-	#      sake, skip it when parsing normal.
+	#   4. 'cmd_ancestry' output depends on how the test is run and
+	#      is not relevant to the features we are testing here.
+	#      Ancestry tests are covered in t0213-trace2-ancestry.sh instead.
 	sed \
 		-e 's/elapsed:[0-9]*\.[0-9][0-9]*\([eE][-+]\{0,1\}[0-9][0-9]*\)\{0,1\}/elapsed:_TIME_/g' \
 		-e "s/^start '[^']*' \(.*\)/start _EXE_ \1/" \
diff --git a/t/t0213-trace2-ancestry.sh b/t/t0213-trace2-ancestry.sh
new file mode 100755
index 0000000..a2b9536
--- /dev/null
+++ b/t/t0213-trace2-ancestry.sh
@@ -0,0 +1,180 @@
+#!/bin/sh
+
+test_description='test trace2 cmd_ancestry event'
+
+. ./test-lib.sh
+
+# Turn off any inherited trace2 settings for this test.
+sane_unset GIT_TRACE2 GIT_TRACE2_PERF GIT_TRACE2_EVENT
+sane_unset GIT_TRACE2_BRIEF
+sane_unset GIT_TRACE2_CONFIG_PARAMS
+
+# Add t/helper directory to PATH so that we can use a relative
+# path to run nested instances of test-tool.exe (see 004child).
+# This helps with HEREDOC comparisons later.
+TTDIR="$GIT_BUILD_DIR/t/helper/" && export TTDIR
+PATH="$TTDIR:$PATH" && export PATH
+
+# The 400ancestry helper spawns a child process so that the child
+# sees "test-tool" in its process ancestry.  We capture only the
+# child's trace2 output to a file.
+#
+# The tests use git commands that spawn child git processes (e.g.,
+# alias resolution) to create a controlled multi-level process tree.
+# Because cmd_ancestry walks the real process tree, processes will
+# also report ancestors above "test-tool" that depend on the test
+# runner environment (e.g., bash, make, tmux).  The filter functions
+# below truncate the ancestry at "test-tool", discarding anything
+# above it, so only the controlled portion is verified.
+#
+# On platforms without a real procinfo implementation (the stub),
+# no cmd_ancestry event is emitted.  We detect this at runtime and
+# skip the format-specific tests accordingly.
+
+# Determine if cmd_ancestry is supported on this platform.
+test_expect_success 'detect cmd_ancestry support' '
+	test_when_finished "rm -f trace.detect" &&
+	GIT_TRACE2_BRIEF=1 GIT_TRACE2="$(pwd)/trace.detect" \
+		test-tool trace2 001return 0 &&
+	if grep -q "^cmd_ancestry" trace.detect
+	then
+		test_set_prereq TRACE2_ANCESTRY
+	fi
+'
+
+# Filter functions for each trace2 target format.
+#
+# Each extracts cmd_ancestry events, strips format-specific syntax,
+# and truncates the ancestor list at the outermost "test-tool"
+# (or "test-tool.exe" on Windows), discarding any higher-level
+# (uncontrolled) ancestors.
+#
+# Output is a space-separated list of ancestor names, one line per
+# cmd_ancestry event, with the immediate parent listed first:
+#
+#   test-tool                          (or: test-tool.exe)
+#   git test-tool                      (or: git.exe test-tool.exe)
+#   git test-tool test-tool            (or: git.exe test-tool.exe test-tool.exe)
+
+if test_have_prereq MINGW
+then
+	TT=test-tool$X
+else
+	TT=test-tool
+fi
+
+filter_ancestry_normal () {
+	sed -n '/^cmd_ancestry/{
+		s/^cmd_ancestry //
+		s/ <- / /g
+		s/\(.*'"$TT"'\) .*/\1/
+		p
+	}'
+}
+
+filter_ancestry_perf () {
+	sed -n '/cmd_ancestry/{
+		s/.*ancestry:\[//
+		s/\]//
+		s/\(.*'"$TT"'\) .*/\1/
+		p
+	}'
+}
+
+filter_ancestry_event () {
+	sed -n '/"cmd_ancestry"/{
+		s/.*"ancestry":\[//
+		s/\].*//
+		s/"//g
+		s/,/ /g
+		s/\(.*'"$TT"'\) .*/\1/
+		p
+	}'
+}
+
+# On Windows (MINGW) when running with the bin-wrappers, we also see "sh.exe" in
+# the ancestry. We must therefore account for this expected ancestry element in
+# the expected output of the tests.
+if test_have_prereq MINGW && test -z "$no_bin_wrappers"; then
+	SH_TT="sh$X $TT"
+else
+	SH_TT="$TT"
+fi
+
+# Git alias resolution spawns the target command as a child process.
+# Using "git -c alias.xyz=version xyz" creates a two-level chain:
+#
+#   test-tool (400ancestry)
+#     -> git (resolves alias xyz -> version)
+#          -> git (version)
+#
+# Both git processes are instrumented and emit cmd_ancestry.  After
+# filtering out ancestors above test-tool, we get:
+#
+#   test-tool                 (from git alias resolver)
+#   git test-tool             (from git version)
+
+test_expect_success TRACE2_ANCESTRY 'normal: git alias chain, 2 levels' '
+	test_when_finished "rm -f trace.normal actual expect" &&
+	test-tool trace2 400ancestry normal "$(pwd)/trace.normal" \
+		git -c alias.xyz=version xyz &&
+	filter_ancestry_normal <trace.normal >actual &&
+	cat >expect <<-EOF &&
+	$SH_TT
+	git$X $SH_TT
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success TRACE2_ANCESTRY 'perf: git alias chain, 2 levels' '
+	test_when_finished "rm -f trace.perf actual expect" &&
+	test-tool trace2 400ancestry perf "$(pwd)/trace.perf" \
+		git -c alias.xyz=version xyz &&
+	filter_ancestry_perf <trace.perf >actual &&
+	cat >expect <<-EOF &&
+	$SH_TT
+	git$X $SH_TT
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success TRACE2_ANCESTRY 'event: git alias chain, 2 levels' '
+	test_when_finished "rm -f trace.event actual expect" &&
+	test-tool trace2 400ancestry event "$(pwd)/trace.event" \
+		git -c alias.xyz=version xyz &&
+	filter_ancestry_event <trace.event >actual &&
+	cat >expect <<-EOF &&
+	$SH_TT
+	git$X $SH_TT
+	EOF
+	test_cmp expect actual
+'
+
+# Use 004child to add a test-tool layer, creating a three-level chain:
+#
+#   test-tool (400ancestry)
+#     -> test-tool (004child)
+#          -> git (resolves alias xyz -> version)
+#               -> git (version)
+#
+# Three instrumented processes emit cmd_ancestry.  After filtering:
+#
+#   test-tool                  (from test-tool 004child)
+#   test-tool test-tool        (from git alias resolver)
+#   git test-tool test-tool    (from git version)
+
+test_expect_success TRACE2_ANCESTRY 'normal: deeper chain, 3 levels' '
+	test_when_finished "rm -f trace.normal actual expect" &&
+	test-tool trace2 400ancestry normal "$(pwd)/trace.normal" \
+		test-tool trace2 004child \
+			git -c alias.xyz=version xyz &&
+	filter_ancestry_normal <trace.normal >actual &&
+	cat >expect <<-EOF &&
+	$TT
+	$SH_TT $TT
+	git$X $SH_TT $TT
+	EOF
+	test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh
index dc30289..6f7cfd9 100755
--- a/t/t0301-credential-cache.sh
+++ b/t/t0301-credential-cache.sh
@@ -123,7 +123,8 @@
 		rmdir \"\$HOME/dir/\" &&
 		rm \"\$HOME/.git-credential-cache\"
 	" &&
-	mkdir -p -m 700 "$HOME/dir/" &&
+	mkdir -p "$HOME/dir/" &&
+	chmod 700 "$HOME/dir/" &&
 	ln -s "$HOME/dir" "$HOME/.git-credential-cache" &&
 	check approve cache <<-\EOF &&
 	protocol=https
diff --git a/t/t0450-txt-doc-vs-help.sh b/t/t0450-txt-doc-vs-help.sh
index e12e18f..822b0d5 100755
--- a/t/t0450-txt-doc-vs-help.sh
+++ b/t/t0450-txt-doc-vs-help.sh
@@ -56,7 +56,7 @@
 	b2t="$(builtin_to_adoc "$builtin")" &&
 	sed -n \
 		-E '/^\[(verse|synopsis)\]$/,/^$/ {
-			/^$/d;
+			/^$/q;
 			/^\[(verse|synopsis)\]$/d;
 			s/\{litdd\}/--/g;
 			s/'\''(git[ a-z-]*)'\''/\1/g;
diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh
index b11126e..74bfa2e 100755
--- a/t/t0600-reffiles-backend.sh
+++ b/t/t0600-reffiles-backend.sh
@@ -467,7 +467,7 @@
 	esac
 '
 
-test_expect_success SYMLINKS 'symref transaction supports symlinks' '
+test_expect_success SYMLINKS,!MINGW 'symref transaction supports symlinks' '
 	test_when_finished "git symbolic-ref -d TEST_SYMREF_HEAD" &&
 	git update-ref refs/heads/new @ &&
 	test_config core.prefersymlinkrefs true &&
diff --git a/t/t0601-reffiles-pack-refs.sh b/t/t0601-reffiles-pack-refs.sh
index 12cf5d1..3c70697 100755
--- a/t/t0601-reffiles-pack-refs.sh
+++ b/t/t0601-reffiles-pack-refs.sh
@@ -18,3 +18,5 @@
 . ./test-lib.sh
 
 . "$TEST_DIRECTORY"/pack-refs-tests.sh
+
+test_done
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 0ef4836..3c1f553 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -905,4 +905,34 @@
 	)
 '
 
+test_expect_success 'complains about broken root ref' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		echo "ref: refs/heads/../HEAD" >.git/HEAD &&
+		test_must_fail git refs verify 2>err &&
+		cat >expect <<-EOF &&
+		error: HEAD: badReferentName: points to invalid refname ${SQ}refs/heads/../HEAD${SQ}
+		EOF
+		test_cmp expect err
+	)
+'
+
+test_expect_success 'complains about broken root ref in worktree' '
+	test_when_finished "rm -rf repo worktree" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit initial &&
+		git worktree add ../worktree &&
+		echo "ref: refs/heads/../HEAD" >.git/worktrees/worktree/HEAD &&
+		test_must_fail git refs verify 2>err &&
+		cat >expect <<-EOF &&
+		error: worktrees/worktree/HEAD: badReferentName: points to invalid refname ${SQ}refs/heads/../HEAD${SQ}
+		EOF
+		test_cmp expect err
+	)
+'
+
 test_done
diff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh
index 3ea5d51..e19e036 100755
--- a/t/t0610-reftable-basics.sh
+++ b/t/t0610-reftable-basics.sh
@@ -207,7 +207,7 @@
 		test_commit file1 &&
 		for f in .git/reftable/*.ref
 		do
-			: >"$f" || return 1
+			test-tool truncate "$f" 0 || return 1
 		done &&
 		test_must_fail git update-ref refs/heads/main HEAD
 	)
@@ -1135,4 +1135,32 @@
 	test_cmp expect actual
 '
 
+test_expect_success 'writes do not persist peeled value for invalid tags' '
+	test_when_finished rm -rf repo &&
+	git init repo &&
+	(
+		cd repo &&
+		git commit --allow-empty --message initial &&
+
+		# We cannot easily verify that the peeled value is not stored
+		# in the tables. Instead, we test this indirectly: we create
+		# two tags that both point to the same object, but they claim
+		# different object types. If we parse both tags we notice that
+		# the parsed tagged object has a mismatch between the two tags
+		# and bail out.
+		#
+		# If we instead use the persisted peeled value we would not
+		# even parse the tags. As such, we would not notice the
+		# discrepancy either and thus listing these tags would succeed.
+		git tag tag-1 -m "tag 1" &&
+		git cat-file tag tag-1 >raw-tag &&
+		sed "s/^type commit$/type blob/" <raw-tag >broken-tag &&
+		broken_tag_id=$(git hash-object -w -t tag broken-tag) &&
+		git update-ref refs/tags/tag-2 $broken_tag_id &&
+
+		test_must_fail git for-each-ref --format="%(*objectname)" refs/tags/ 2>err &&
+		test_grep "bad tag pointer" err
+	)
+'
+
 test_done
diff --git a/t/t0613-reftable-write-options.sh b/t/t0613-reftable-write-options.sh
index e334751..26b716c 100755
--- a/t/t0613-reftable-write-options.sh
+++ b/t/t0613-reftable-write-options.sh
@@ -68,8 +68,8 @@
 	(
 		cd repo &&
 		test_commit initial &&
-		test_seq -f "update refs/heads/branch-%d HEAD" 200 >input &&
-		git update-ref --stdin <input &&
+		test_seq -f "update refs/heads/branch-%d HEAD" 200 |
+		git update-ref --stdin &&
 		git pack-refs &&
 
 		cat >expect <<-EOF &&
@@ -178,8 +178,8 @@
 	(
 		cd repo &&
 		test_commit initial &&
-		test_seq -f "update refs/heads/branch-%d HEAD" 10 >input &&
-		git update-ref --stdin <input &&
+		test_seq -f "update refs/heads/branch-%d HEAD" 10 |
+		git update-ref --stdin &&
 		git -c reftable.restartInterval=1 pack-refs &&
 
 		cat >expect <<-EOF &&
@@ -218,8 +218,8 @@
 	(
 		cd repo &&
 		test_commit initial &&
-		test_seq -f "update refs/heads/branch-%d HEAD" 5 >input &&
-		git update-ref --stdin <input &&
+		test_seq -f "update refs/heads/branch-%d HEAD" 5 |
+		git update-ref --stdin &&
 		git -c reftable.blockSize=100 pack-refs &&
 
 		cat >expect <<-EOF &&
@@ -253,8 +253,8 @@
 	(
 		cd repo &&
 		test_commit initial &&
-		test_seq -f "update refs/heads/branch-%d HEAD" 5 >input &&
-		git update-ref --stdin <input &&
+		test_seq -f "update refs/heads/branch-%d HEAD" 5 |
+		git update-ref --stdin &&
 		git -c reftable.blockSize=100 -c reftable.indexObjects=false pack-refs &&
 
 		cat >expect <<-EOF &&
diff --git a/t/t0614-reftable-fsck.sh b/t/t0614-reftable-fsck.sh
index 85cc47d..d24b87f 100755
--- a/t/t0614-reftable-fsck.sh
+++ b/t/t0614-reftable-fsck.sh
@@ -20,7 +20,7 @@
 		done &&
 
 		# The repository should end up with multiple tables.
-		test_line_count ">" 1 .git/reftable/tables.list &&
+		test_line_count -gt 1 .git/reftable/tables.list &&
 
 		git refs verify 2>err &&
 		test_must_be_empty err
@@ -55,4 +55,48 @@
 	'
 done
 
+test_expect_success 'worktree stacks can be verified' '
+	test_when_finished "rm -rf repo worktree" &&
+	git init repo &&
+	test_commit -C repo initial &&
+	git -C repo worktree add ../worktree &&
+
+	git -C worktree refs verify 2>err &&
+	test_must_be_empty err &&
+
+	REFTABLE_DIR=$(git -C worktree rev-parse --git-dir)/reftable &&
+	EXISTING_TABLE=$(head -n1 "$REFTABLE_DIR/tables.list") &&
+	mv "$REFTABLE_DIR/$EXISTING_TABLE" "$REFTABLE_DIR/broken.ref" &&
+
+	for d in repo worktree
+	do
+		echo "broken.ref" >"$REFTABLE_DIR/tables.list" &&
+		git -C "$d" refs verify 2>err &&
+		cat >expect <<-EOF &&
+		warning: broken.ref: badReftableTableName: invalid reftable table name
+		EOF
+		test_cmp expect err &&
+
+		echo garbage >"$REFTABLE_DIR/tables.list" &&
+		test_must_fail git -C "$d" refs verify 2>err &&
+		cat >expect <<-EOF &&
+		error: reftable stack for worktree ${SQ}worktree${SQ} is broken
+		EOF
+		test_cmp expect err || return 1
+
+	done
+'
+
+test_expect_success 'invalid symref gets reported' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	test_commit -C repo initial &&
+	git -C repo symbolic-ref refs/heads/symref garbage &&
+	test_must_fail git -C repo refs verify 2>err &&
+	cat >expect <<-EOF &&
+	error: refs/heads/symref: badReferentName: points to invalid refname ${SQ}garbage${SQ}
+	EOF
+	test_cmp expect err
+'
+
 test_done
diff --git a/t/t1005-read-tree-reset.sh b/t/t1005-read-tree-reset.sh
index 6b5033d..1bf384d 100755
--- a/t/t1005-read-tree-reset.sh
+++ b/t/t1005-read-tree-reset.sh
@@ -40,7 +40,7 @@
 	git ls-files -s &&
 	read_tree_u_must_succeed --reset -u HEAD &&
 	git ls-files -s >actual &&
-	! test -f old &&
+	test_path_is_missing old &&
 	test_cmp expect actual
 '
 
@@ -56,7 +56,7 @@
 	git ls-files -s &&
 	read_tree_u_must_succeed --reset -u HEAD HEAD &&
 	git ls-files -s >actual &&
-	! test -f old &&
+	test_path_is_missing old &&
 	test_cmp expect actual
 '
 
@@ -72,7 +72,7 @@
 	git ls-files -s &&
 	git reset --hard &&
 	git ls-files -s >actual &&
-	! test -f old &&
+	test_path_is_missing old &&
 	test_cmp expect actual
 '
 
@@ -88,7 +88,7 @@
 	git ls-files -s &&
 	git checkout -f &&
 	git ls-files -s >actual &&
-	! test -f old &&
+	test_path_is_missing old &&
 	test_cmp expect actual
 '
 
@@ -104,7 +104,7 @@
 	git ls-files -s &&
 	git checkout -f HEAD &&
 	git ls-files -s >actual &&
-	! test -f old &&
+	test_path_is_missing old &&
 	test_cmp expect actual
 '
 
diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh
index 1f61b66..8e2c526 100755
--- a/t/t1006-cat-file.sh
+++ b/t/t1006-cat-file.sh
@@ -241,10 +241,16 @@
 hello_size=$(strlen "$hello_content")
 hello_oid=$(echo_without_newline "$hello_content" | git hash-object --stdin)
 
-test_expect_success "setup" '
+test_expect_success "setup part 1" '
 	git config core.repositoryformatversion 1 &&
-	git config extensions.objectformat $test_hash_algo &&
-	git config extensions.compatobjectformat $test_compat_hash_algo &&
+	git config extensions.objectformat $test_hash_algo
+'
+
+test_expect_success RUST 'compat setup' '
+	git config extensions.compatobjectformat $test_compat_hash_algo
+'
+
+test_expect_success 'setup part 2' '
 	echo_without_newline "$hello_content" > hello &&
 	git update-index --add hello &&
 	echo_without_newline "$hello_content" > "path with spaces" &&
@@ -273,9 +279,13 @@
     '
 }
 
-hello_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $hello_oid)
 run_blob_tests $hello_oid
-run_blob_tests $hello_compat_oid
+
+if test_have_prereq RUST
+then
+	hello_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $hello_oid)
+	run_blob_tests $hello_compat_oid
+fi
 
 test_expect_success '--batch-check without %(rest) considers whole line' '
 	echo "$hello_oid blob $hello_size" >expect &&
@@ -286,62 +296,76 @@
 '
 
 tree_oid=$(git write-tree)
-tree_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $tree_oid)
 tree_size=$((2 * $(test_oid rawsz) + 13 + 24))
-tree_compat_size=$((2 * $(test_oid --hash=compat rawsz) + 13 + 24))
 tree_pretty_content="100644 blob $hello_oid	hello${LF}100755 blob $hello_oid	path with spaces${LF}"
-tree_compat_pretty_content="100644 blob $hello_compat_oid	hello${LF}100755 blob $hello_compat_oid	path with spaces${LF}"
 
 run_tests 'tree' $tree_oid "" $tree_size "" "$tree_pretty_content"
-run_tests 'tree' $tree_compat_oid "" $tree_compat_size "" "$tree_compat_pretty_content"
 run_tests 'blob' "$tree_oid:hello" "100644" $hello_size "" "$hello_content" $hello_oid
-run_tests 'blob' "$tree_compat_oid:hello" "100644" $hello_size "" "$hello_content" $hello_compat_oid
 run_tests 'blob' "$tree_oid:path with spaces" "100755" $hello_size "" "$hello_content" $hello_oid
-run_tests 'blob' "$tree_compat_oid:path with spaces" "100755" $hello_size "" "$hello_content" $hello_compat_oid
+
+if test_have_prereq RUST
+then
+	tree_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $tree_oid)
+	tree_compat_size=$((2 * $(test_oid --hash=compat rawsz) + 13 + 24))
+	tree_compat_pretty_content="100644 blob $hello_compat_oid	hello${LF}100755 blob $hello_compat_oid	path with spaces${LF}"
+
+	run_tests 'tree' $tree_compat_oid "" $tree_compat_size "" "$tree_compat_pretty_content"
+	run_tests 'blob' "$tree_compat_oid:hello" "100644" $hello_size "" "$hello_content" $hello_compat_oid
+	run_tests 'blob' "$tree_compat_oid:path with spaces" "100755" $hello_size "" "$hello_content" $hello_compat_oid
+fi
 
 commit_message="Initial commit"
 commit_oid=$(echo_without_newline "$commit_message" | git commit-tree $tree_oid)
-commit_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $commit_oid)
 commit_size=$(($(test_oid hexsz) + 137))
-commit_compat_size=$(($(test_oid --hash=compat hexsz) + 137))
 commit_content="tree $tree_oid
 author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 
 $commit_message"
 
-commit_compat_content="tree $tree_compat_oid
+run_tests 'commit' $commit_oid "" $commit_size "$commit_content" "$commit_content"
+
+if test_have_prereq RUST
+then
+	commit_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $commit_oid)
+	commit_compat_size=$(($(test_oid --hash=compat hexsz) + 137))
+	commit_compat_content="tree $tree_compat_oid
 author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 
 $commit_message"
 
-run_tests 'commit' $commit_oid "" $commit_size "$commit_content" "$commit_content"
-run_tests 'commit' $commit_compat_oid "" $commit_compat_size "$commit_compat_content" "$commit_compat_content"
+	run_tests 'commit' $commit_compat_oid "" $commit_compat_size "$commit_compat_content" "$commit_compat_content"
+fi
 
 tag_header_without_oid="type blob
 tag hellotag
 tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
 tag_header_without_timestamp="object $hello_oid
 $tag_header_without_oid"
-tag_compat_header_without_timestamp="object $hello_compat_oid
-$tag_header_without_oid"
 tag_description="This is a tag"
 tag_content="$tag_header_without_timestamp 0 +0000
 
 $tag_description"
-tag_compat_content="$tag_compat_header_without_timestamp 0 +0000
-
-$tag_description"
 
 tag_oid=$(echo_without_newline "$tag_content" | git hash-object -t tag --stdin -w)
 tag_size=$(strlen "$tag_content")
 
-tag_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $tag_oid)
-tag_compat_size=$(strlen "$tag_compat_content")
-
 run_tests 'tag' $tag_oid "" $tag_size "$tag_content" "$tag_content"
-run_tests 'tag' $tag_compat_oid "" $tag_compat_size "$tag_compat_content" "$tag_compat_content"
+
+if test_have_prereq RUST
+then
+	tag_compat_header_without_timestamp="object $hello_compat_oid
+$tag_header_without_oid"
+	tag_compat_content="$tag_compat_header_without_timestamp 0 +0000
+
+$tag_description"
+
+	tag_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $tag_oid)
+	tag_compat_size=$(strlen "$tag_compat_content")
+
+	run_tests 'tag' $tag_compat_oid "" $tag_compat_size "$tag_compat_content" "$tag_compat_content"
+fi
 
 test_expect_success "Reach a blob from a tag pointing to it" '
 	echo_without_newline "$hello_content" >expect &&
@@ -590,7 +614,8 @@
 }
 
 batch_tests $hello_oid $tree_oid $tree_size $commit_oid $commit_size "$commit_content" $tag_oid $tag_size "$tag_content"
-batch_tests $hello_compat_oid $tree_compat_oid $tree_compat_size $commit_compat_oid $commit_compat_size "$commit_compat_content" $tag_compat_oid $tag_compat_size "$tag_compat_content"
+
+test_have_prereq RUST && batch_tests $hello_compat_oid $tree_compat_oid $tree_compat_size $commit_compat_oid $commit_compat_size "$commit_compat_content" $tag_compat_oid $tag_compat_size "$tag_compat_content"
 
 
 test_expect_success FUNNYNAMES 'setup with newline in input' '
@@ -643,7 +668,7 @@
 '
 
 test_expect_success 'setup blobs which are likely to delta' '
-	test-tool genrandom foo 10240 >foo &&
+	test-tool genrandom foo 10k >foo &&
 	{ cat foo && echo plus; } >foo-plus &&
 	git add foo foo-plus &&
 	git commit -m foo &&
@@ -1048,18 +1073,28 @@
 	echo .. >>expect &&
 	echo HEAD:dir/subdir/out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks >actual &&
 	test_cmp expect actual &&
-	echo symlink 3 >expect &&
-	echo ../ >>expect &&
+	if test_have_prereq MINGW,SYMLINKS
+	then
+		test_write_lines "symlink 2" ..
+	else
+		test_write_lines "symlink 3" ../
+	fi >expect &&
 	echo HEAD:dir/subdir/out-of-repo-link-dir-trailing | git cat-file --batch-check --follow-symlinks >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'git cat-file --batch-check --follow-symlinks works for symlinks with internal ..' '
-	echo HEAD: | git cat-file --batch-check >expect &&
-	echo HEAD:up-down | git cat-file --batch-check --follow-symlinks >actual &&
-	test_cmp expect actual &&
-	echo HEAD:up-down-trailing | git cat-file --batch-check --follow-symlinks >actual &&
-	test_cmp expect actual &&
+	if test_have_prereq !MINGW
+	then
+		# The `up-down` and `up-down-trailing` symlinks are normalized
+		# in MSYS in `winsymlinks` mode and are therefore in a
+		# different shape than Git expects them.
+		echo HEAD: | git cat-file --batch-check >expect &&
+		echo HEAD:up-down | git cat-file --batch-check --follow-symlinks >actual &&
+		test_cmp expect actual &&
+		echo HEAD:up-down-trailing | git cat-file --batch-check --follow-symlinks >actual &&
+		test_cmp expect actual
+	fi &&
 	echo HEAD:up-down-file | git cat-file --batch-check --follow-symlinks >actual &&
 	test_cmp found actual &&
 	echo symlink 7 >expect &&
@@ -1226,7 +1261,10 @@
 	test_unconfig extensions.compatobjectformat &&
 	printf "160000 commit $(test_oid deadbeef)\tsub\n" >tree-with-sub &&
 	tree=$(git mktree <tree-with-sub) &&
-	test_config extensions.compatobjectformat $test_compat_hash_algo &&
+	if test_have_prereq RUST
+	then
+		test_config extensions.compatobjectformat $test_compat_hash_algo
+	fi &&
 
 	git cat-file --batch-check >actual <<-EOF &&
 	$tree:sub
diff --git a/t/t1016-compatObjectFormat.sh b/t/t1016-compatObjectFormat.sh
index 0efce53..92d48b9 100755
--- a/t/t1016-compatObjectFormat.sh
+++ b/t/t1016-compatObjectFormat.sh
@@ -8,6 +8,12 @@
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-gpg.sh
 
+if ! test_have_prereq RUST
+then
+	skip_all='interoperability requires a Git built with Rust'
+	test_done
+fi
+
 # All of the follow variables must be defined in the environment:
 # GIT_AUTHOR_NAME
 # GIT_AUTHOR_EMAIL
diff --git a/t/t1050-large.sh b/t/t1050-large.sh
index 5be2736..7d40d08 100755
--- a/t/t1050-large.sh
+++ b/t/t1050-large.sh
@@ -104,9 +104,9 @@
 		# mid1 and mid2 will fit within 256k limit but
 		# appending mid3 will bust the limit and will
 		# result in a separate packfile.
-		test-tool genrandom "a" $(( 66 * 1024 )) >mid1 &&
-		test-tool genrandom "b" $(( 80 * 1024 )) >mid2 &&
-		test-tool genrandom "c" $(( 128 * 1024 )) >mid3 &&
+		test-tool genrandom "a" 66k >mid1 &&
+		test-tool genrandom "b" 80k >mid2 &&
+		test-tool genrandom "c" 128k >mid3 &&
 		git add mid1 mid2 mid3 &&
 
 		count=0 &&
diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh
index b2da4fe..cd0aed9 100755
--- a/t/t1091-sparse-checkout-builtin.sh
+++ b/t/t1091-sparse-checkout-builtin.sh
@@ -817,6 +817,54 @@
 	test_cmp expect out
 '
 
+test_expect_success 'sparse-checkout deduplicates repeated cone patterns' '
+	rm -f repo/.git/info/sparse-checkout &&
+	git -C repo sparse-checkout init --cone &&
+	git -C repo sparse-checkout add --stdin <<-\EOF &&
+	foo/bar/baz
+	a/b/c
+	foo/bar/baz
+	a/b
+	EOF
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	/a/
+	!/a/*/
+	/foo/
+	!/foo/*/
+	/foo/bar/
+	!/foo/bar/*/
+	/a/b/
+	/foo/bar/baz/
+	EOF
+	test_cmp expect repo/.git/info/sparse-checkout
+'
+
+test_expect_success 'sparse-checkout list deduplicates repeated cone patterns' '
+	rm -f repo/.git/info/sparse-checkout &&
+	git -C repo sparse-checkout init --cone &&
+	cat <<-\EOF >repo/.git/info/sparse-checkout &&
+	/*
+	!/*/
+	/a/
+	!/a/*/
+	/foo/
+	!/foo/*/
+	/foo/bar/
+	!/foo/bar/*/
+	/a/b/
+	/foo/bar/baz/
+	/foo/bar/baz/
+	EOF
+	git -C repo sparse-checkout list >actual &&
+	cat <<-\EOF >expect &&
+	a/b
+	foo/bar/baz
+	EOF
+	test_cmp expect actual
+'
+
 test_expect_success 'malformed cone-mode patterns' '
 	git -C repo sparse-checkout init --cone &&
 	mkdir -p repo/foo/bar &&
diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh
index b0f691c..d98cb4a 100755
--- a/t/t1092-sparse-checkout-compatibility.sh
+++ b/t/t1092-sparse-checkout-compatibility.sh
@@ -2559,4 +2559,18 @@
 	ensure_expanded cat-file --batch <in
 '
 
+test_expect_success 'merge -s ours' '
+	init_repos &&
+
+	test_all_match git rev-parse HEAD^{tree} &&
+	test_all_match git merge -s ours merge-right &&
+	test_all_match git rev-parse HEAD^{tree} &&
+	test_all_match git rev-parse HEAD^2
+'
+
+test_expect_success 'sparse-index is not expanded: merge-ours' '
+	init_repos &&
+	ensure_not_expanded merge -s ours merge-right
+'
+
 test_done
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 358d636..128971e 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1232,12 +1232,12 @@
 	test_when_finished "rm myconfig" &&
 	ln -s notyet myconfig &&
 	git config --file=myconfig test.frotz nitfol &&
-	test -h myconfig &&
-	test -f notyet &&
+	test_path_is_symlink myconfig &&
+	test_path_is_file notyet &&
 	test "z$(git config --file=notyet test.frotz)" = znitfol &&
 	git config --file=myconfig test.xyzzy rezrov &&
-	test -h myconfig &&
-	test -f notyet &&
+	test_path_is_symlink myconfig &&
+	test_path_is_file notyet &&
 	cat >expect <<-\EOF &&
 	nitfol
 	rezrov
@@ -2459,9 +2459,15 @@
 
 cat >.git/config <<-\EOF &&
 [section]
-foo = true
+foo = True
 number = 10
 big = 1M
+path = ~/dir
+red = red
+blue = Blue
+date = Fri Jun 4 15:46:55 2010
+missing=:(optional)no-such-path
+exists=:(optional)expect
 EOF
 
 test_expect_success 'identical modern --type specifiers are allowed' '
@@ -2503,6 +2509,82 @@
 	test_cmp_config 1048576 --type=bool --no-type --type=int section.big
 '
 
+test_expect_success 'list --type=int shows only canonicalizable int values' '
+	cat >expect <<-EOF &&
+	section.number=10
+	section.big=1048576
+	EOF
+
+	git config ${mode_prefix}list --type=int >actual 2>err &&
+	test_cmp expect actual &&
+	test_must_be_empty err
+'
+
+test_expect_success 'list --type=bool shows only canonicalizable bool values' '
+	cat >expect <<-EOF &&
+	section.foo=true
+	section.number=true
+	section.big=true
+	EOF
+
+	git config ${mode_prefix}list --type=bool >actual 2>err &&
+	test_cmp expect actual &&
+	test_must_be_empty err
+'
+
+test_expect_success 'list --type=bool-or-int shows only canonicalizable values' '
+	cat >expect <<-EOF &&
+	section.foo=true
+	section.number=10
+	section.big=1048576
+	EOF
+
+	git config ${mode_prefix}list --type=bool-or-int >actual 2>err &&
+	test_cmp expect actual &&
+	test_must_be_empty err
+'
+
+test_expect_success 'list --type=path shows only canonicalizable path values' '
+	cat >expect <<-EOF &&
+	section.foo=True
+	section.number=10
+	section.big=1M
+	section.path=$HOME/dir
+	section.red=red
+	section.blue=Blue
+	section.date=Fri Jun 4 15:46:55 2010
+	section.exists=expect
+	EOF
+
+	git config ${mode_prefix}list --type=path >actual 2>err &&
+	test_cmp expect actual &&
+	test_must_be_empty err
+'
+
+test_expect_success 'list --type=expiry-date shows only canonicalizable dates' '
+	git config ${mode_prefix}list --type=expiry-date >actual 2>err &&
+
+	# section.number and section.big parse as relative dates that could
+	# have clock skew in their results.
+	test_grep section.big actual &&
+	test_grep section.number actual &&
+	test_grep "section.date=$(git config --type=expiry-date section.$key)" actual &&
+	test_must_be_empty err
+'
+
+test_expect_success 'list --type=color shows only canonicalizable color values' '
+	cat >expect <<-EOF &&
+	section.number=<>
+	section.red=<RED>
+	section.blue=<BLUE>
+	EOF
+
+	git config ${mode_prefix}list --type=color >actual.raw 2>err &&
+	test_decode_color <actual.raw >actual &&
+	test_cmp expect actual &&
+	test_must_be_empty err
+'
+
 test_expect_success '--type rejects unknown specifiers' '
 	test_must_fail git config --type=nonsense section.foo 2>error &&
 	test_grep "unrecognized --type argument" error
diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
index 8ff2b0c..6e51f89 100755
--- a/t/t1305-config-include.sh
+++ b/t/t1305-config-include.sh
@@ -286,7 +286,7 @@
 	)
 '
 
-test_expect_success SYMLINKS 'conditional include, gitdir matching symlink' '
+test_expect_success SYMLINKS,!MINGW 'conditional include, gitdir matching symlink' '
 	ln -s foo bar &&
 	(
 		cd bar &&
@@ -298,7 +298,7 @@
 	)
 '
 
-test_expect_success SYMLINKS 'conditional include, gitdir matching symlink, icase' '
+test_expect_success SYMLINKS,!MINGW 'conditional include, gitdir matching symlink, icase' '
 	(
 		cd bar &&
 		echo "[includeIf \"gitdir/i:BAR/\"]path=bar8" >>.git/config &&
diff --git a/t/t1311-config-optional.sh b/t/t1311-config-optional.sh
new file mode 100755
index 0000000..fbbacfc
--- /dev/null
+++ b/t/t1311-config-optional.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# Copyright (c) 2025 Google LLC
+#
+
+test_description=':(optional) paths'
+
+. ./test-lib.sh
+
+test_expect_success 'var=:(optional)path-exists' '
+	test_config a.path ":(optional)path-exists" &&
+	>path-exists &&
+	echo path-exists >expect &&
+
+	git config get --path a.path >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'missing optional value is ignored' '
+	test_config a.path ":(optional)no-such-path" &&
+	# Using --show-scope ensures we skip writing not only the value
+	# but also any meta-information about the ignored key.
+	test_must_fail git config get --show-scope --path a.path >actual &&
+	test_line_count = 0 actual
+'
+
+test_expect_success 'missing optional value is ignored in multi-value config' '
+	test_when_finished "git config unset --all a.path" &&
+	git config set --append a.path ":(optional)path-exists" &&
+	git config set --append a.path ":(optional)no-such-path" &&
+	>path-exists &&
+	echo path-exists >expect &&
+
+	git config --get --path a.path >actual &&
+	test_cmp expect actual
+'
+
+test_done
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index db7f544..b2858a9 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -1380,16 +1380,16 @@
 
 test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction creating branches does not burst open file limit' '
 (
-	test_seq -f "create refs/heads/%d HEAD" 33 >large_input &&
-	run_with_limited_open_files git update-ref --stdin <large_input &&
+	test_seq -f "create refs/heads/%d HEAD" 33 |
+	run_with_limited_open_files git update-ref --stdin &&
 	git rev-parse --verify -q refs/heads/33
 )
 '
 
 test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction deleting branches does not burst open file limit' '
 (
-	test_seq -f "delete refs/heads/%d HEAD" 33 >large_input &&
-	run_with_limited_open_files git update-ref --stdin <large_input &&
+	test_seq -f "delete refs/heads/%d HEAD" 33 |
+	run_with_limited_open_files git update-ref --stdin &&
 	test_must_fail git rev-parse --verify -q refs/heads/33
 )
 '
@@ -2093,14 +2093,15 @@
 
 			format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
 			format_command $type "update refs/heads/ref2" "$(test_oid 001)" "$head" >>stdin &&
-			git update-ref $type --stdin --batch-updates <stdin >stdout &&
+			git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
 			echo $old_head >expect &&
 			git rev-parse refs/heads/ref1 >actual &&
 			test_cmp expect actual &&
 			echo $head >expect &&
 			git rev-parse refs/heads/ref2 >actual &&
 			test_cmp expect actual &&
-			test_grep -q "invalid new value provided" stdout
+			test_grep "rejected refs/heads/ref2 $(test_oid 001) $head invalid new value provided" stdout &&
+			test_grep "trying to write ref ${SQ}refs/heads/ref2${SQ} with nonexistent object" err
 		)
 	'
 
@@ -2119,14 +2120,15 @@
 
 			format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
 			format_command $type "update refs/heads/ref2" "$head_tree" "$head" >>stdin &&
-			git update-ref $type --stdin --batch-updates <stdin >stdout &&
+			git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
 			echo $old_head >expect &&
 			git rev-parse refs/heads/ref1 >actual &&
 			test_cmp expect actual &&
 			echo $head >expect &&
 			git rev-parse refs/heads/ref2 >actual &&
 			test_cmp expect actual &&
-			test_grep -q "invalid new value provided" stdout
+			test_grep "rejected refs/heads/ref2 $head_tree $head invalid new value provided" stdout &&
+			test_grep "trying to write non-commit object $head_tree to branch ${SQ}refs/heads/ref2${SQ}" err
 		)
 	'
 
@@ -2143,12 +2145,13 @@
 
 			format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
 			format_command $type "update refs/heads/ref2" "$old_head" "$head" >>stdin &&
-			git update-ref $type --stdin --batch-updates <stdin >stdout &&
+			git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
 			echo $old_head >expect &&
 			git rev-parse refs/heads/ref1 >actual &&
 			test_cmp expect actual &&
 			test_must_fail git rev-parse refs/heads/ref2 &&
-			test_grep -q "reference does not exist" stdout
+			test_grep "rejected refs/heads/ref2 $old_head $head reference does not exist" stdout &&
+			test_grep "cannot lock ref ${SQ}refs/heads/ref2${SQ}: unable to resolve reference ${SQ}refs/heads/ref2${SQ}" err
 		)
 	'
 
@@ -2166,13 +2169,14 @@
 
 			format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
 			format_command $type "update refs/heads/ref2" "$old_head" "$head" >>stdin &&
-			git update-ref $type --no-deref --stdin --batch-updates <stdin >stdout &&
+			git update-ref $type --no-deref --stdin --batch-updates <stdin >stdout 2>err &&
 			echo $old_head >expect &&
 			git rev-parse refs/heads/ref1 >actual &&
 			test_cmp expect actual &&
 			echo $head >expect &&
 			test_must_fail git rev-parse refs/heads/ref2 &&
-			test_grep -q "reference does not exist" stdout
+			test_grep "rejected refs/heads/ref2 $old_head $head reference does not exist" stdout &&
+			test_grep "cannot lock ref ${SQ}refs/heads/ref2${SQ}: reference is missing but expected $head" err
 		)
 	'
 
@@ -2190,7 +2194,7 @@
 
 			format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
 			format_command $type "symref-update refs/heads/ref2" "$old_head" "ref" "refs/heads/nonexistent" >>stdin &&
-			git update-ref $type --no-deref --stdin --batch-updates <stdin >stdout &&
+			git update-ref $type --no-deref --stdin --batch-updates <stdin >stdout 2>err &&
 			echo $old_head >expect &&
 			git rev-parse refs/heads/ref1 >actual &&
 			test_cmp expect actual &&
@@ -2198,7 +2202,8 @@
 			echo $head >expect &&
 			git rev-parse refs/heads/ref2 >actual &&
 			test_cmp expect actual &&
-			test_grep -q "expected symref but found regular ref" stdout
+			test_grep "rejected refs/heads/ref2 $ZERO_OID $ZERO_OID expected symref but found regular ref" stdout &&
+			test_grep "cannot lock ref ${SQ}refs/heads/ref2${SQ}: expected symref with target ${SQ}refs/heads/nonexistent${SQ}: but is a regular ref" err
 		)
 	'
 
@@ -2216,14 +2221,15 @@
 
 			format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
 			format_command $type "update refs/heads/ref2" "$old_head" "$Z" >>stdin &&
-			git update-ref $type --stdin --batch-updates <stdin >stdout &&
+			git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
 			echo $old_head >expect &&
 			git rev-parse refs/heads/ref1 >actual &&
 			test_cmp expect actual &&
 			echo $head >expect &&
 			git rev-parse refs/heads/ref2 >actual &&
 			test_cmp expect actual &&
-			test_grep -q "reference already exists" stdout
+			test_grep "rejected refs/heads/ref2 $old_head $ZERO_OID reference already exists" stdout &&
+			test_grep "cannot lock ref ${SQ}refs/heads/ref2${SQ}: reference already exists" err
 		)
 	'
 
@@ -2241,14 +2247,15 @@
 
 			format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
 			format_command $type "update refs/heads/ref2" "$head" "$old_head" >>stdin &&
-			git update-ref $type --stdin --batch-updates <stdin >stdout &&
+			git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
 			echo $old_head >expect &&
 			git rev-parse refs/heads/ref1 >actual &&
 			test_cmp expect actual &&
 			echo $head >expect &&
 			git rev-parse refs/heads/ref2 >actual &&
 			test_cmp expect actual &&
-			test_grep -q "incorrect old value provided" stdout
+			test_grep "rejected refs/heads/ref2 $head $old_head incorrect old value provided" stdout &&
+			test_grep "cannot lock ref ${SQ}refs/heads/ref2${SQ}: is at $head but expected $old_head" err
 		)
 	'
 
@@ -2264,12 +2271,13 @@
 			git update-ref refs/heads/ref/foo $head &&
 
 			format_command $type "update refs/heads/ref/foo" "$old_head" "$head" >stdin &&
-			format_command $type "update refs/heads/ref" "$old_head" "" >>stdin &&
-			git update-ref $type --stdin --batch-updates <stdin >stdout &&
+			format_command $type "update refs/heads/ref" "$old_head" "$ZERO_OID" >>stdin &&
+			git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
 			echo $old_head >expect &&
 			git rev-parse refs/heads/ref/foo >actual &&
 			test_cmp expect actual &&
-			test_grep -q "refname conflict" stdout
+			test_grep "rejected refs/heads/ref $old_head $ZERO_OID refname conflict" stdout &&
+			test_grep "${SQ}refs/heads/ref/foo${SQ} exists; cannot create ${SQ}refs/heads/ref${SQ}" err
 		)
 	'
 
@@ -2284,13 +2292,14 @@
 			head=$(git rev-parse HEAD) &&
 			git update-ref refs/heads/ref/foo $head &&
 
-			format_command $type "update refs/heads/foo" "$old_head" "" >stdin &&
-			format_command $type "update refs/heads/ref" "$old_head" "" >>stdin &&
-			git update-ref $type --stdin --batch-updates <stdin >stdout &&
+			format_command $type "update refs/heads/foo" "$old_head" "$ZERO_OID" >stdin &&
+			format_command $type "update refs/heads/ref" "$old_head" "$ZERO_OID" >>stdin &&
+			git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
 			echo $old_head >expect &&
 			git rev-parse refs/heads/foo >actual &&
 			test_cmp expect actual &&
-			test_grep -q "refname conflict" stdout
+			test_grep "rejected refs/heads/ref $old_head $ZERO_OID refname conflict" stdout &&
+			test_grep "${SQ}refs/heads/ref/foo${SQ} exists; cannot create ${SQ}refs/heads/ref${SQ}" err
 		)
 	'
 
@@ -2309,14 +2318,15 @@
 				format_command $type "create refs/heads/ref" "$old_head" &&
 				format_command $type "create refs/heads/Foo" "$old_head"
 			} >stdin &&
-			git update-ref $type --stdin --batch-updates <stdin >stdout &&
+			git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
 
 			echo $head >expect &&
 			git rev-parse refs/heads/foo >actual &&
 			echo $old_head >expect &&
 			git rev-parse refs/heads/ref >actual &&
 			test_cmp expect actual &&
-			test_grep -q "reference conflict due to case-insensitive filesystem" stdout
+			test_grep "rejected refs/heads/Foo $old_head $ZERO_OID reference conflict due to case-insensitive filesystem" stdout &&
+			test_grep -e "cannot lock ref ${SQ}refs/heads/Foo${SQ}: Unable to create" -e "Foo.lock" err
 		)
 	'
 
@@ -2357,8 +2367,9 @@
 			git symbolic-ref refs/heads/symbolic refs/heads/non-existent &&
 
 			format_command $type "delete refs/heads/symbolic" "$head" >stdin &&
-			git update-ref $type --stdin --batch-updates <stdin >stdout &&
-			test_grep "reference does not exist" stdout
+			git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
+			test_grep "rejected refs/heads/non-existent $ZERO_OID $head reference does not exist" stdout &&
+			test_grep "cannot lock ref ${SQ}refs/heads/symbolic${SQ}: unable to resolve reference ${SQ}refs/heads/non-existent${SQ}" err
 		)
 	'
 
@@ -2373,8 +2384,9 @@
 			head=$(git rev-parse HEAD) &&
 
 			format_command $type "delete refs/heads/new-branch" "$head" >stdin &&
-			git update-ref $type --stdin --batch-updates <stdin >stdout &&
-			test_grep "incorrect old value provided" stdout
+			git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
+			test_grep "rejected refs/heads/new-branch $ZERO_OID $head incorrect old value provided" stdout &&
+			test_grep "cannot lock ref ${SQ}refs/heads/new-branch${SQ}: is at $(git rev-parse new-branch) but expected $head" err
 		)
 	'
 
@@ -2387,8 +2399,9 @@
 			head=$(git rev-parse HEAD) &&
 
 			format_command $type "delete refs/heads/non-existent" "$head" >stdin &&
-			git update-ref $type --stdin --batch-updates <stdin >stdout &&
-			test_grep "reference does not exist" stdout
+			git update-ref $type --stdin --batch-updates <stdin >stdout 2>err &&
+			test_grep "rejected refs/heads/non-existent $ZERO_OID $head reference does not exist" stdout &&
+			test_grep "cannot lock ref ${SQ}refs/heads/non-existent${SQ}: unable to resolve reference ${SQ}refs/heads/non-existent${SQ}" err
 		)
 	'
 done
diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
index e30f87a..ce71f9a 100755
--- a/t/t1410-reflog.sh
+++ b/t/t1410-reflog.sh
@@ -130,10 +130,10 @@
 
 test_expect_success rewind '
 	test_tick && git reset --hard HEAD~2 &&
-	test -f C &&
-	test -f A/B/E &&
-	! test -f F &&
-	! test -f A/G &&
+	test_path_is_file C &&
+	test_path_is_file A/B/E &&
+	test_path_is_missing F &&
+	test_path_is_missing A/G &&
 
 	check_have A B C D E F G H I J K L &&
 
diff --git a/t/t1420-lost-found.sh b/t/t1420-lost-found.sh
index 2fb2f44..926c6d6 100755
--- a/t/t1420-lost-found.sh
+++ b/t/t1420-lost-found.sh
@@ -28,9 +28,12 @@
 	test_tick &&
 	git reset --hard HEAD^ &&
 	git fsck --lost-found &&
-	test 2 = $(ls .git/lost-found/*/* | wc -l) &&
-	test -f .git/lost-found/commit/$(cat lost-commit) &&
-	test -f .git/lost-found/other/$(cat lost-other)
+	ls .git/lost-found/*/* >actual &&
+	cat >expect <<-EOF &&
+	.git/lost-found/commit/$(cat lost-commit)
+	.git/lost-found/other/$(cat lost-other)
+	EOF
+	test_cmp expect actual
 '
 
 test_done
diff --git a/t/t1423-ref-backend.sh b/t/t1423-ref-backend.sh
new file mode 100755
index 0000000..fd47d77
--- /dev/null
+++ b/t/t1423-ref-backend.sh
@@ -0,0 +1,280 @@
+#!/bin/sh
+
+test_description='Test reference backend URIs'
+
+. ./test-lib.sh
+
+# Run a git command with the provided reference storage. Reset the backend
+# post running the command.
+# Usage: run_with_uri <repo> <backend> <uri> <cmd>
+#   <repo> is the relative path to the repo to run the command in.
+#   <backend> is the original ref storage of the repo.
+#   <uri> is the new URI to be set for the ref storage.
+#   <cmd> is the git subcommand to be run in the repository.
+#   <via> if 'config', set the backend via the 'extensions.refStorage' config.
+#         if 'env', set the backend via the 'GIT_REFERENCE_BACKEND' env.
+run_with_uri () {
+	repo=$1 &&
+	backend=$2 &&
+	uri=$3 &&
+	cmd=$4 &&
+	via=$5 &&
+
+	git -C "$repo" config set core.repositoryformatversion 1 &&
+	if test "$via" = "env"
+	then
+		test_env GIT_REFERENCE_BACKEND="$uri" git -C "$repo" $cmd
+	elif test "$via" = "config"
+	then
+		git -C "$repo" config set extensions.refStorage "$uri" &&
+		git -C "$repo" $cmd &&
+		git -C "$repo" config set extensions.refStorage "$backend"
+	fi
+}
+
+# Test a repository with a given reference storage by running and comparing
+# 'git refs list' before and after setting the new reference backend. If
+# err_msg is set, expect the command to fail and grep for the provided err_msg.
+# Usage: run_with_uri <repo> <backend> <uri> <cmd>
+#   <repo> is the relative path to the repo to run the command in.
+#   <backend> is the original ref storage of the repo.
+#   <uri> is the new URI to be set for the ref storage.
+#   <via> if 'config', set the backend via the 'extensions.refStorage' config.
+#         if 'env', set the backend via the 'GIT_REFERENCE_BACKEND' env.
+#   <err_msg> (optional) if set, check if 'git-refs(1)' failed with the provided msg.
+test_refs_backend () {
+	repo=$1 &&
+	backend=$2 &&
+	uri=$3 &&
+	via=$4 &&
+	err_msg=$5 &&
+
+
+	if test -n "$err_msg";
+	then
+		if test "$via" = "env"
+		then
+			test_env GIT_REFERENCE_BACKEND="$uri" test_must_fail git -C "$repo" refs list 2>err
+		elif test "$via" = "config"
+		then
+			git -C "$repo" config set extensions.refStorage "$uri" &&
+			test_must_fail git -C "$repo" refs list 2>err &&
+			test_grep "$err_msg" err
+		fi
+	else
+		git -C "$repo" refs list >expect &&
+		run_with_uri "$repo" "$backend" "$uri" "refs list" "$via">actual &&
+		test_cmp expect actual
+	fi
+}
+
+# Verify that the expected files are present in the gitdir and the refsdir.
+# Usage: verify_files_exist <gitdir> <refdir>
+#   <gitdir> is the path for the gitdir.
+#   <refdir> is the path for the refdir.
+verify_files_exist () {
+	gitdir=$1 &&
+	refdir=$2 &&
+
+	# verify that the stubs were added to the $GITDIR.
+	echo "repository uses alternate refs storage" >expect &&
+	test_cmp expect $gitdir/refs/heads &&
+	echo "ref: refs/heads/.invalid" >expect &&
+	test_cmp expect $gitdir/HEAD
+
+	# verify that backend specific files exist.
+	case "$GIT_DEFAULT_REF_FORMAT" in
+	files)
+		test_path_is_dir $refdir/refs/heads &&
+		test_path_is_file $refdir/HEAD;;
+	reftable)
+		test_path_is_dir $refdir/reftable &&
+		test_path_is_file $refdir/reftable/tables.list;;
+	*)
+		BUG "unhandled ref format $GIT_DEFAULT_REF_FORMAT";;
+	esac
+}
+
+methods="config env"
+for method in $methods
+do
+
+test_expect_success "$method: URI is invalid" '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	test_refs_backend repo files "reftable@/home/reftable" "$method" \
+		"invalid value for ${SQ}extensions.refstorage${SQ}"
+'
+
+test_expect_success "$method: URI ends with colon" '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	test_refs_backend repo files "reftable:" "$method" \
+		"invalid value for ${SQ}extensions.refstorage${SQ}"
+'
+
+test_expect_success "$method: unknown reference backend" '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	test_refs_backend repo files "db://.git" "$method" \
+		"invalid value for ${SQ}extensions.refstorage${SQ}"
+'
+
+ref_formats="files reftable"
+for from_format in $ref_formats
+do
+
+for to_format in $ref_formats
+do
+	if test "$from_format" = "$to_format"
+	then
+		continue
+	fi
+
+
+	for dir in "$(pwd)/repo/.git" "."
+	do
+
+		test_expect_success "$method: read from $to_format backend, $dir dir" '
+			test_when_finished "rm -rf repo" &&
+			git init --ref-format=$from_format repo &&
+			(
+				cd repo &&
+				test_commit 1 &&
+				test_commit 2 &&
+				test_commit 3 &&
+
+				git refs migrate --dry-run --ref-format=$to_format >out &&
+				BACKEND_PATH="$dir/$(sed "s/.* ${SQ}.git\/\(.*\)${SQ}/\1/" out)" &&
+				test_refs_backend . $from_format "$to_format://$BACKEND_PATH" "$method"
+			)
+		'
+
+		test_expect_success "$method: write to $to_format backend, $dir dir" '
+			test_when_finished "rm -rf repo" &&
+			git init --ref-format=$from_format repo &&
+			(
+				cd repo &&
+				test_commit 1 &&
+				test_commit 2 &&
+				test_commit 3 &&
+
+				git refs migrate --dry-run --ref-format=$to_format >out &&
+				BACKEND_PATH="$dir/$(sed "s/.* ${SQ}.git\/\(.*\)${SQ}/\1/" out)" &&
+
+				test_refs_backend . $from_format "$to_format://$BACKEND_PATH" "$method" &&
+
+				git refs list >expect &&
+				run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \
+					"tag -d 1" "$method" &&
+				git refs list >actual &&
+				test_cmp expect actual &&
+
+				git refs list | grep -v "refs/tags/1" >expect &&
+				run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \
+					"refs list" "$method" >actual &&
+				test_cmp expect actual
+			)
+		'
+
+		test_expect_success "$method: with worktree and $to_format backend, $dir dir" '
+			test_when_finished "rm -rf repo wt" &&
+			git init --ref-format=$from_format repo &&
+			(
+				cd repo &&
+				test_commit 1 &&
+				test_commit 2 &&
+				test_commit 3 &&
+
+				git refs migrate --dry-run --ref-format=$to_format >out &&
+				BACKEND_PATH="$dir/$(sed "s/.* ${SQ}.git\/\(.*\)${SQ}/\1/" out)" &&
+
+				run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \
+					"worktree add ../wt 2" "$method" &&
+
+				run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \
+					"for-each-ref --include-root-refs" "$method" >actual &&
+				run_with_uri ../wt "$from_format" "$to_format://$BACKEND_PATH" \
+					"for-each-ref --include-root-refs" "$method" >expect &&
+				! test_cmp expect actual &&
+
+				run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \
+					"rev-parse 2" "$method" >actual &&
+				run_with_uri ../wt "$from_format" "$to_format://$BACKEND_PATH" \
+					"rev-parse HEAD" "$method" >expect &&
+				test_cmp expect actual
+			)
+		'
+	done # closes dir
+
+	test_expect_success "migrating repository to $to_format with alternate refs directory" '
+		test_when_finished "rm -rf repo refdir" &&
+		mkdir refdir &&
+		GIT_REFERENCE_BACKEND="${from_format}://$(pwd)/refdir" git init repo &&
+		(
+			cd repo &&
+
+			test_commit 1 &&
+			test_commit 2 &&
+			test_commit 3 &&
+
+			git refs migrate --ref-format=$to_format &&
+			git refs list >out &&
+			test_grep "refs/tags/1"	out &&
+			test_grep "refs/tags/2"	out &&
+			test_grep "refs/tags/3"	out
+		)
+	'
+
+done # closes to_format
+done # closes from_format
+
+done # closes method
+
+test_expect_success 'initializing repository with alt ref directory' '
+	test_when_finished "rm -rf repo refdir" &&
+	mkdir refdir &&
+	BACKEND="$(test_detect_ref_format)://$(pwd)/refdir" &&
+	GIT_REFERENCE_BACKEND=$BACKEND git init repo &&
+	verify_files_exist repo/.git refdir &&
+	(
+		cd repo &&
+
+		git config get extensions.refstorage >actual &&
+		echo $BACKEND >expect &&
+		test_cmp expect actual &&
+
+		test_commit 1 &&
+		test_commit 2 &&
+		test_commit 3 &&
+		git refs list >out &&
+		test_grep "refs/tags/1"	out &&
+		test_grep "refs/tags/2"	out &&
+		test_grep "refs/tags/3"	out
+	)
+'
+
+test_expect_success 'cloning repository with alt ref directory' '
+	test_when_finished "rm -rf source repo refdir" &&
+	mkdir refdir &&
+
+	git init source &&
+	test_commit -C source 1 &&
+	test_commit -C source 2 &&
+	test_commit -C source 3 &&
+
+	BACKEND="$(test_detect_ref_format)://$(pwd)/refdir" &&
+	GIT_REFERENCE_BACKEND=$BACKEND git clone source repo &&
+
+	git -C repo config get extensions.refstorage >actual &&
+	echo $BACKEND >expect &&
+	test_cmp expect actual &&
+
+	verify_files_exist repo/.git refdir &&
+
+	git -C source for-each-ref refs/tags/ >expect &&
+	git -C repo for-each-ref refs/tags/ >actual &&
+	test_cmp expect actual
+'
+
+test_done
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index c4b651c..54e81c2 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -105,7 +105,7 @@
 	echo $ZERO_OID >.git/HEAD &&
 	# avoid corrupt/broken HEAD from interfering with repo discovery
 	test_must_fail env GIT_DIR=.git git fsck 2>out &&
-	test_grep "detached HEAD points" out
+	test_grep "HEAD: badRefOid: points to invalid object ID ${SQ}$ZERO_OID${SQ}" out
 '
 
 test_expect_success 'HEAD link pointing at a funny place' '
@@ -113,7 +113,7 @@
 	test-tool ref-store main create-symref HEAD refs/funny/place &&
 	# avoid corrupt/broken HEAD from interfering with repo discovery
 	test_must_fail env GIT_DIR=.git git fsck 2>out &&
-	test_grep "HEAD points to something strange" out
+	test_grep "HEAD: badHeadTarget: HEAD points to non-branch ${SQ}refs/funny/place${SQ}" out
 '
 
 test_expect_success REFFILES 'HEAD link pointing at a funny object (from different wt)' '
@@ -123,7 +123,7 @@
 	echo $ZERO_OID >.git/HEAD &&
 	# avoid corrupt/broken HEAD from interfering with repo discovery
 	test_must_fail git -C wt fsck 2>out &&
-	test_grep "main-worktree/HEAD: detached HEAD points" out
+	test_grep "HEAD: badRefOid: points to invalid object ID ${SQ}$ZERO_OID${SQ}" out
 '
 
 test_expect_success REFFILES 'other worktree HEAD link pointing at a funny object' '
@@ -131,7 +131,7 @@
 	git worktree add other &&
 	echo $ZERO_OID >.git/worktrees/other/HEAD &&
 	test_must_fail git fsck 2>out &&
-	test_grep "worktrees/other/HEAD: detached HEAD points" out
+	test_grep "worktrees/other/HEAD: badRefOid: points to invalid object ID ${SQ}$ZERO_OID${SQ}" out
 '
 
 test_expect_success 'other worktree HEAD link pointing at missing object' '
@@ -148,7 +148,7 @@
 	git worktree add other &&
 	git -C other symbolic-ref HEAD refs/funny/place &&
 	test_must_fail git fsck 2>out &&
-	test_grep "worktrees/other/HEAD points to something strange" out
+	test_grep "worktrees/other/HEAD: badHeadTarget: HEAD points to non-branch ${SQ}refs/funny/place${SQ}" out
 '
 
 test_expect_success 'commit with multiple signatures is okay' '
@@ -852,6 +852,44 @@
 	! grep corrupt out
 '
 
+test_expect_success 'fsck handles multiple packfiles with big blobs' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+
+		# We construct two packfiles with two objects in common and one
+		# object not in common. The objects in common can then be
+		# corrupted in one of the packfiles, respectively. The other
+		# objects that are unique to the packs are merely used to not
+		# have both packs contain the same data.
+		blob_one=$(test-tool genrandom one 200k | git hash-object -t blob -w --stdin) &&
+		blob_two=$(test-tool genrandom two 200k | git hash-object -t blob -w --stdin) &&
+		blob_three=$(test-tool genrandom three 200k | git hash-object -t blob -w --stdin) &&
+		blob_four=$(test-tool genrandom four 200k | git hash-object -t blob -w --stdin) &&
+		pack_one=$(printf "%s\n" "$blob_one" "$blob_two" "$blob_three" | git pack-objects .git/objects/pack/pack) &&
+		pack_two=$(printf "%s\n" "$blob_two" "$blob_three" "$blob_four" | git pack-objects .git/objects/pack/pack) &&
+		chmod a+w .git/objects/pack/pack-*.pack &&
+
+		# Corrupt blob two in the first pack.
+		git verify-pack -v .git/objects/pack/pack-$pack_one >objects &&
+		offset_one=$(sed <objects -n "s/^$blob_two .* \(.*\)$/\1/p") &&
+		printf "\0" | dd of=.git/objects/pack/pack-$pack_one.pack bs=1 conv=notrunc seek=$offset_one &&
+
+		# Corrupt blob three in the second pack.
+		git verify-pack -v .git/objects/pack/pack-$pack_two >objects &&
+		offset_two=$(sed <objects -n "s/^$blob_three .* \(.*\)$/\1/p") &&
+		printf "\0" | dd of=.git/objects/pack/pack-$pack_two.pack bs=1 conv=notrunc seek=$offset_two &&
+
+		# We now expect to see two failures for the corrupted objects,
+		# even though they exist in a non-corrupted form in the
+		# respective other pack.
+		test_must_fail git -c core.bigFileThreshold=100k fsck 2>err &&
+		test_grep "unknown object type 0 at offset $offset_one in .git/objects/pack/pack-$pack_one.pack" err &&
+		test_grep "unknown object type 0 at offset $offset_two in .git/objects/pack/pack-$pack_two.pack" err
+	)
+'
+
 test_expect_success 'fsck fails on corrupt packfile' '
 	hsh=$(git commit-tree -m mycommit HEAD^{tree}) &&
 	pack=$(echo $hsh | git pack-objects .git/objects/pack/pack) &&
@@ -918,7 +956,7 @@
 test_expect_success 'fsck detects truncated loose object' '
 	# make it big enough that we know we will truncate in the data
 	# portion, not the header
-	test-tool genrandom truncate 4096 >file &&
+	test-tool genrandom truncate 4k >file &&
 	blob=$(git hash-object -w file) &&
 	file=$(sha1_file $blob) &&
 	test_when_finished "remove_object $blob" &&
diff --git a/t/t1460-refs-migrate.sh b/t/t1460-refs-migrate.sh
index 0e1116a..5246468 100755
--- a/t/t1460-refs-migrate.sh
+++ b/t/t1460-refs-migrate.sh
@@ -276,11 +276,11 @@
 	test_when_finished "rm -rf repo" &&
 	git init --ref-format=files repo &&
 	test_commit -C repo first &&
-	printf "create refs/heads/ref-%d HEAD\n" $(test_seq 5000) >stdin &&
-	git -C repo update-ref --stdin <stdin &&
+	test_seq -f "create refs/heads/ref-%d HEAD" 5000 |
+	git -C repo update-ref --stdin &&
 	test_commit -C repo second &&
-	printf "update refs/heads/ref-%d HEAD\n" $(test_seq 3000) >stdin &&
-	git -C repo update-ref --stdin <stdin &&
+	test_seq -f "update refs/heads/ref-%d HEAD" 3000 |
+	git -C repo update-ref --stdin &&
 	test_migration repo reftable true
 '
 
diff --git a/t/t1463-refs-optimize.sh b/t/t1463-refs-optimize.sh
index c11c905..9afe3c1 100755
--- a/t/t1463-refs-optimize.sh
+++ b/t/t1463-refs-optimize.sh
@@ -15,3 +15,5 @@
 
 pack_refs='refs optimize'
 . "$TEST_DIRECTORY"/pack-refs-tests.sh
+
+test_done
diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh
index 7739ab6..98c5a77 100755
--- a/t/t1500-rev-parse.sh
+++ b/t/t1500-rev-parse.sh
@@ -208,7 +208,7 @@
 '
 
 
-test_expect_success 'rev-parse --show-object-format in repo with compat mode' '
+test_expect_success RUST 'rev-parse --show-object-format in repo with compat mode' '
 	mkdir repo &&
 	(
 		sane_unset GIT_DEFAULT_HASH &&
diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh
index 4feaf0d..b1583e9 100755
--- a/t/t1800-hook.sh
+++ b/t/t1800-hook.sh
@@ -1,18 +1,79 @@
 #!/bin/sh
 
-test_description='git-hook command'
+test_description='git-hook command and config-managed multihooks'
 
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-terminal.sh
 
+setup_hooks () {
+	test_config hook.ghi.command "/path/ghi"
+	test_config hook.ghi.event pre-commit --add
+	test_config hook.ghi.event test-hook --add
+	test_config_global hook.def.command "/path/def"
+	test_config_global hook.def.event pre-commit --add
+}
+
+setup_hookdir () {
+	mkdir .git/hooks
+	write_script .git/hooks/pre-commit <<-EOF
+	echo \"Legacy Hook\"
+	EOF
+	test_when_finished rm -rf .git/hooks
+}
+
 test_expect_success 'git hook usage' '
 	test_expect_code 129 git hook &&
 	test_expect_code 129 git hook run &&
 	test_expect_code 129 git hook run -h &&
+	test_expect_code 129 git hook list -h &&
 	test_expect_code 129 git hook run --unknown 2>err &&
+	test_expect_code 129 git hook list &&
+	test_expect_code 129 git hook list -h &&
 	grep "unknown option" err
 '
 
+test_expect_success 'git hook list: nonexistent hook' '
+	cat >stderr.expect <<-\EOF &&
+	warning: No hooks found for event '\''test-hook'\''
+	EOF
+	test_expect_code 1 git hook list test-hook 2>stderr.actual &&
+	test_cmp stderr.expect stderr.actual
+'
+
+test_expect_success 'git hook list: traditional hook from hookdir' '
+	test_hook test-hook <<-EOF &&
+	echo Test hook
+	EOF
+
+	cat >expect <<-\EOF &&
+	hook from hookdir
+	EOF
+	git hook list test-hook >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git hook list: configured hook' '
+	test_config hook.myhook.command "echo Hello" &&
+	test_config hook.myhook.event test-hook --add &&
+
+	echo "myhook" >expect &&
+	git hook list test-hook >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git hook list: -z shows NUL-terminated output' '
+	test_hook test-hook <<-EOF &&
+	echo Test hook
+	EOF
+	test_config hook.myhook.command "echo Hello" &&
+	test_config hook.myhook.event test-hook --add &&
+
+	printf "myhookQhook from hookdirQ" >expect &&
+	git hook list -z test-hook >actual.raw &&
+	nul_to_q <actual.raw >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'git hook run: nonexistent hook' '
 	cat >stderr.expect <<-\EOF &&
 	error: cannot find a hook named test-hook
@@ -83,12 +144,18 @@
 	test_cmp expect actual
 '
 
-test_expect_success 'git hook run -- out-of-repo runs excluded' '
-	test_hook test-hook <<-EOF &&
-	echo Test hook
-	EOF
+test_expect_success 'git hook run: out-of-repo runs execute global hooks' '
+	test_config_global hook.global-hook.event test-hook --add &&
+	test_config_global hook.global-hook.command "echo no repo no problems" --add &&
 
-	nongit test_must_fail git hook run test-hook
+	echo "global-hook" >expect &&
+	nongit git hook list test-hook >actual &&
+	test_cmp expect actual &&
+
+	echo "no repo no problems" >expect &&
+
+	nongit git hook run test-hook 2>actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'git -c core.hooksPath=<PATH> hook run' '
@@ -150,6 +217,170 @@
 	test_hook_tty commit -m"B.new"
 '
 
+test_expect_success 'git hook list orders by config order' '
+	setup_hooks &&
+
+	cat >expected <<-\EOF &&
+	def
+	ghi
+	EOF
+
+	git hook list pre-commit >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'git hook list reorders on duplicate event declarations' '
+	setup_hooks &&
+
+	# 'def' is usually configured globally; move it to the end by
+	# configuring it locally.
+	test_config hook.def.event "pre-commit" --add &&
+
+	cat >expected <<-\EOF &&
+	ghi
+	def
+	EOF
+
+	git hook list pre-commit >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'git hook list: empty event value resets events' '
+	setup_hooks &&
+
+	# ghi is configured for pre-commit; reset it with an empty value
+	test_config hook.ghi.event "" --add &&
+
+	# only def should remain for pre-commit
+	echo "def" >expected &&
+	git hook list pre-commit >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'hook can be configured for multiple events' '
+	setup_hooks &&
+
+	# 'ghi' should be included in both 'pre-commit' and 'test-hook'
+	git hook list pre-commit >actual &&
+	grep "ghi" actual &&
+	git hook list test-hook >actual &&
+	grep "ghi" actual
+'
+
+test_expect_success 'git hook list shows hooks from the hookdir' '
+	setup_hookdir &&
+
+	cat >expected <<-\EOF &&
+	hook from hookdir
+	EOF
+
+	git hook list pre-commit >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'inline hook definitions execute oneliners' '
+	test_config hook.oneliner.event "pre-commit" &&
+	test_config hook.oneliner.command "echo \"Hello World\"" &&
+
+	echo "Hello World" >expected &&
+
+	# hooks are run with stdout_to_stderr = 1
+	git hook run pre-commit 2>actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'inline hook definitions resolve paths' '
+	write_script sample-hook.sh <<-\EOF &&
+	echo \"Sample Hook\"
+	EOF
+
+	test_when_finished "rm sample-hook.sh" &&
+
+	test_config hook.sample-hook.event pre-commit &&
+	test_config hook.sample-hook.command "\"$(pwd)/sample-hook.sh\"" &&
+
+	echo \"Sample Hook\" >expected &&
+
+	# hooks are run with stdout_to_stderr = 1
+	git hook run pre-commit 2>actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'hookdir hook included in git hook run' '
+	setup_hookdir &&
+
+	echo \"Legacy Hook\" >expected &&
+
+	# hooks are run with stdout_to_stderr = 1
+	git hook run pre-commit 2>actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'stdin to multiple hooks' '
+	test_config hook.stdin-a.event "test-hook" &&
+	test_config hook.stdin-a.command "xargs -P1 -I% echo a%" &&
+	test_config hook.stdin-b.event "test-hook" &&
+	test_config hook.stdin-b.command "xargs -P1 -I% echo b%" &&
+
+	cat >input <<-\EOF &&
+	1
+	2
+	3
+	EOF
+
+	cat >expected <<-\EOF &&
+	a1
+	a2
+	a3
+	b1
+	b2
+	b3
+	EOF
+
+	git hook run --to-stdin=input test-hook 2>actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'rejects hooks with no commands configured' '
+	test_config hook.broken.event "test-hook" &&
+	test_must_fail git hook list test-hook 2>actual &&
+	test_grep "hook.broken.command" actual &&
+	test_must_fail git hook run test-hook 2>actual &&
+	test_grep "hook.broken.command" actual
+'
+
+test_expect_success 'disabled hook is not run' '
+	test_config hook.skipped.event "test-hook" &&
+	test_config hook.skipped.command "echo \"Should not run\"" &&
+	test_config hook.skipped.enabled false &&
+
+	git hook run --ignore-missing test-hook 2>actual &&
+	test_must_be_empty actual
+'
+
+test_expect_success 'disabled hook does not appear in git hook list' '
+	test_config hook.active.event "pre-commit" &&
+	test_config hook.active.command "echo active" &&
+	test_config hook.inactive.event "pre-commit" &&
+	test_config hook.inactive.command "echo inactive" &&
+	test_config hook.inactive.enabled false &&
+
+	git hook list pre-commit >actual &&
+	test_grep "active" actual &&
+	test_grep ! "inactive" actual
+'
+
+test_expect_success 'globally disabled hook can be re-enabled locally' '
+	test_config_global hook.global-hook.event "test-hook" &&
+	test_config_global hook.global-hook.command "echo \"global-hook ran\"" &&
+	test_config_global hook.global-hook.enabled false &&
+	test_config hook.global-hook.enabled true &&
+
+	echo "global-hook ran" >expected &&
+	git hook run test-hook 2>actual &&
+	test_cmp expected actual
+'
+
 test_expect_success 'git hook run a hook with a bad shebang' '
 	test_when_finished "rm -rf bad-hooks" &&
 	mkdir bad-hooks &&
@@ -167,6 +398,7 @@
 '
 
 test_expect_success 'stdin to hooks' '
+	mkdir -p .git/hooks &&
 	write_script .git/hooks/test-hook <<-\EOF &&
 	echo BEGIN stdin
 	cat
@@ -184,4 +416,141 @@
 	test_cmp expect actual
 '
 
+check_stdout_separate_from_stderr () {
+	for hook in "$@"
+	do
+		# Ensure hook's stdout is only in stdout, not stderr
+		test_grep "Hook $hook stdout" stdout.actual || return 1
+		test_grep ! "Hook $hook stdout" stderr.actual || return 1
+
+		# Ensure hook's stderr is only in stderr, not stdout
+		test_grep "Hook $hook stderr" stderr.actual || return 1
+		test_grep ! "Hook $hook stderr" stdout.actual || return 1
+	done
+}
+
+check_stdout_merged_to_stderr () {
+	for hook in "$@"
+	do
+		# Ensure hook's stdout is only in stderr, not stdout
+		test_grep "Hook $hook stdout" stderr.actual || return 1
+		test_grep ! "Hook $hook stdout" stdout.actual || return 1
+
+		# Ensure hook's stderr is only in stderr, not stdout
+		test_grep "Hook $hook stderr" stderr.actual || return 1
+		test_grep ! "Hook $hook stderr" stdout.actual || return 1
+	done
+}
+
+setup_hooks () {
+	for hook in "$@"
+	do
+		test_hook $hook <<-EOF
+		echo >&1 Hook $hook stdout
+		echo >&2 Hook $hook stderr
+		EOF
+	done
+}
+
+test_expect_success 'client hooks: pre-push expects separate stdout and stderr' '
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	git init --bare remote &&
+	git remote add origin remote &&
+	test_commit A &&
+	setup_hooks pre-push &&
+	git push origin HEAD:main >stdout.actual 2>stderr.actual &&
+	check_stdout_separate_from_stderr pre-push
+'
+
+test_expect_success 'client hooks: commit hooks expect stdout redirected to stderr' '
+	hooks="pre-commit prepare-commit-msg \
+		commit-msg post-commit \
+		reference-transaction" &&
+	setup_hooks $hooks &&
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	git checkout -B main &&
+	git checkout -b branch-a &&
+	test_commit commit-on-branch-a &&
+	git commit --allow-empty -m "Test" >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr $hooks
+'
+
+test_expect_success 'client hooks: checkout hooks expect stdout redirected to stderr' '
+	setup_hooks post-checkout reference-transaction &&
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	git checkout -b new-branch main >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr post-checkout reference-transaction
+'
+
+test_expect_success 'client hooks: merge hooks expect stdout redirected to stderr' '
+	setup_hooks pre-merge-commit post-merge reference-transaction &&
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	test_commit new-branch-commit &&
+	git merge --no-ff branch-a >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr pre-merge-commit post-merge reference-transaction
+'
+
+test_expect_success 'client hooks: post-rewrite hooks expect stdout redirected to stderr' '
+	setup_hooks post-rewrite reference-transaction &&
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	git commit --amend --allow-empty --no-edit >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr post-rewrite reference-transaction
+'
+
+test_expect_success 'client hooks: applypatch hooks expect stdout redirected to stderr' '
+	setup_hooks applypatch-msg pre-applypatch post-applypatch &&
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	git checkout -b branch-b main &&
+	test_commit branch-b &&
+	git format-patch -1 --stdout >patch &&
+	git checkout -b branch-c main &&
+	git am patch >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr applypatch-msg pre-applypatch post-applypatch
+'
+
+test_expect_success 'client hooks: rebase hooks expect stdout redirected to stderr' '
+	setup_hooks pre-rebase &&
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	git checkout -b branch-d main &&
+	test_commit branch-d &&
+	git checkout main &&
+	test_commit diverge-main &&
+	git checkout branch-d &&
+	git rebase main >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr pre-rebase
+'
+
+test_expect_success 'client hooks: post-index-change expects stdout redirected to stderr' '
+	setup_hooks post-index-change &&
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	oid=$(git hash-object -w --stdin </dev/null) &&
+	git update-index --add --cacheinfo 100644 $oid new-file \
+	    >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr post-index-change
+'
+
+test_expect_success 'server hooks expect stdout redirected to stderr' '
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	git init --bare remote-server &&
+	git remote add origin-server remote-server &&
+	cd remote-server &&
+	setup_hooks pre-receive update post-receive post-update &&
+	cd .. &&
+	git push origin-server HEAD:new-branch >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr pre-receive update post-receive post-update
+'
+
+test_expect_success 'server push-to-checkout hook expects stdout redirected to stderr' '
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	git init server &&
+	git -C server checkout -b main &&
+	test_config -C server receive.denyCurrentBranch updateInstead &&
+	git remote add origin-server-2 server &&
+	cd server &&
+	setup_hooks push-to-checkout &&
+	cd .. &&
+	git push origin-server-2 HEAD:main >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr push-to-checkout
+'
+
 test_done
diff --git a/t/t1900-repo.sh b/t/t1900-repo-info.sh
similarity index 68%
rename from t/t1900-repo.sh
rename to t/t1900-repo-info.sh
index 2beba67..a9eb07a 100755
--- a/t/t1900-repo.sh
+++ b/t/t1900-repo-info.sh
@@ -25,7 +25,7 @@
 		eval "$init_command $repo_name"
 	'
 
-	test_expect_success "keyvalue: $label" '
+	test_expect_success "lines: $label" '
 		echo "$key=$expected_value" > expect &&
 		git -C "$repo_name" repo info "$key" >actual &&
 		test_cmp expect actual
@@ -106,8 +106,47 @@
 
 test_expect_success 'git repo info uses the last requested format' '
 	echo "layout.bare=false" >expected &&
-	git repo info --format=nul -z --format=keyvalue layout.bare >actual &&
+	git repo info --format=nul -z --format=lines layout.bare >actual &&
 	test_cmp expected actual
 '
 
+test_expect_success 'git repo info --all and git repo info $(git repo info --keys) output the same data' '
+	git repo info $(git repo info --keys) >expect &&
+	git repo info --all >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git repo info --all <key> aborts' '
+	echo "fatal: --all and <key> cannot be used together" >expect &&
+	test_must_fail git repo info --all object.format 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git repo info --keys --format=nul uses nul-terminated output' '
+	git repo info --keys --format=lines >lines &&
+	lf_to_nul <lines >expect &&
+	git repo info --keys --format=nul >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git repo info --keys aborts when using --format other than lines or nul' '
+	echo "fatal: --keys can only be used with --format=lines or --format=nul" >expect &&
+	test_must_fail git repo info --keys --format=table 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git repo info --keys aborts when requesting keys' '
+	echo "fatal: --keys cannot be used with a <key> or --all" >expect &&
+	test_must_fail git repo info --keys --all 2>actual_all &&
+	test_must_fail git repo info --keys some.key 2>actual_key &&
+	test_cmp expect actual_all &&
+	test_cmp expect actual_key
+'
+
+test_expect_success 'git repo info --keys uses lines as its default output format' '
+	git repo info --keys --format=lines >expect &&
+	git repo info --keys >actual &&
+	test_cmp expect actual
+'
+
 test_done
diff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh
index 36a71a1..98921ce 100755
--- a/t/t1901-repo-structure.sh
+++ b/t/t1901-repo-structure.sh
@@ -4,27 +4,66 @@
 
 . ./test-lib.sh
 
+object_type_disk_usage() {
+	disk_usage_opt="--disk-usage"
+
+	if test "$2" = "true"
+	then
+		disk_usage_opt="--disk-usage=human"
+	fi
+
+	if test "$1" = "all"
+	then
+		git rev-list --all --objects $disk_usage_opt
+	else
+		git rev-list --all --objects $disk_usage_opt \
+			--filter=object:type=$1 --filter-provided-objects
+	fi
+}
+
 test_expect_success 'empty repository' '
 	test_when_finished "rm -rf repo" &&
 	git init repo &&
 	(
 		cd repo &&
 		cat >expect <<-\EOF &&
-		| Repository structure | Value |
-		| -------------------- | ----- |
-		| * References         |       |
-		|   * Count            |     0 |
-		|     * Branches       |     0 |
-		|     * Tags           |     0 |
-		|     * Remotes        |     0 |
-		|     * Others         |     0 |
-		|                      |       |
-		| * Reachable objects  |       |
-		|   * Count            |     0 |
-		|     * Commits        |     0 |
-		|     * Trees          |     0 |
-		|     * Blobs          |     0 |
-		|     * Tags           |     0 |
+		| Repository structure      | Value  |
+		| ------------------------- | ------ |
+		| * References              |        |
+		|   * Count                 |    0   |
+		|     * Branches            |    0   |
+		|     * Tags                |    0   |
+		|     * Remotes             |    0   |
+		|     * Others              |    0   |
+		|                           |        |
+		| * Reachable objects       |        |
+		|   * Count                 |    0   |
+		|     * Commits             |    0   |
+		|     * Trees               |    0   |
+		|     * Blobs               |    0   |
+		|     * Tags                |    0   |
+		|   * Inflated size         |    0 B |
+		|     * Commits             |    0 B |
+		|     * Trees               |    0 B |
+		|     * Blobs               |    0 B |
+		|     * Tags                |    0 B |
+		|   * Disk size             |    0 B |
+		|     * Commits             |    0 B |
+		|     * Trees               |    0 B |
+		|     * Blobs               |    0 B |
+		|     * Tags                |    0 B |
+		|                           |        |
+		| * Largest objects         |        |
+		|   * Commits               |        |
+		|     * Maximum size        |    0 B |
+		|     * Maximum parents     |    0   |
+		|   * Trees                 |        |
+		|     * Maximum size        |    0 B |
+		|     * Maximum entries     |    0   |
+		|   * Blobs                 |        |
+		|     * Maximum size        |    0 B |
+		|   * Tags                  |        |
+		|     * Maximum size        |    0 B |
 		EOF
 
 		git repo structure >out 2>err &&
@@ -34,12 +73,12 @@
 	)
 '
 
-test_expect_success 'repository with references and objects' '
+test_expect_success SHA1 'repository with references and objects' '
 	test_when_finished "rm -rf repo" &&
 	git init repo &&
 	(
 		cd repo &&
-		test_commit_bulk 42 &&
+		test_commit_bulk 1005 &&
 		git tag -a foo -m bar &&
 
 		oid="$(git rev-parse HEAD)" &&
@@ -48,22 +87,54 @@
 		# Also creates a commit, tree, and blob.
 		git notes add -m foo &&
 
-		cat >expect <<-\EOF &&
-		| Repository structure | Value |
-		| -------------------- | ----- |
-		| * References         |       |
-		|   * Count            |     4 |
-		|     * Branches       |     1 |
-		|     * Tags           |     1 |
-		|     * Remotes        |     1 |
-		|     * Others         |     1 |
-		|                      |       |
-		| * Reachable objects  |       |
-		|   * Count            |   130 |
-		|     * Commits        |    43 |
-		|     * Trees          |    43 |
-		|     * Blobs          |    43 |
-		|     * Tags           |     1 |
+		# The tags disk size is handled specially due to the
+		# git-rev-list(1) --disk-usage=human option printing the full
+		# "byte/bytes" unit string instead of just "B".
+		cat >expect <<-EOF &&
+		| Repository structure      | Value      |
+		| ------------------------- | ---------- |
+		| * References              |            |
+		|   * Count                 |      4     |
+		|     * Branches            |      1     |
+		|     * Tags                |      1     |
+		|     * Remotes             |      1     |
+		|     * Others              |      1     |
+		|                           |            |
+		| * Reachable objects       |            |
+		|   * Count                 |   3.02 k   |
+		|     * Commits             |   1.01 k   |
+		|     * Trees               |   1.01 k   |
+		|     * Blobs               |   1.01 k   |
+		|     * Tags                |      1     |
+		|   * Inflated size         |  16.03 MiB |
+		|     * Commits             | 217.92 KiB |
+		|     * Trees               |  15.81 MiB |
+		|     * Blobs               |  11.68 KiB |
+		|     * Tags                |    132 B   |
+		|   * Disk size             | $(object_type_disk_usage all true) |
+		|     * Commits             | $(object_type_disk_usage commit true) |
+		|     * Trees               | $(object_type_disk_usage tree true) |
+		|     * Blobs               |  $(object_type_disk_usage blob true) |
+		|     * Tags                |    $(object_type_disk_usage tag) B   |
+		|                           |            |
+		| * Largest objects         |            |
+		|   * Commits               |            |
+		|     * Maximum size    [1] |    223 B   |
+		|     * Maximum parents [2] |      1     |
+		|   * Trees                 |            |
+		|     * Maximum size    [3] |  32.29 KiB |
+		|     * Maximum entries [4] |   1.01 k   |
+		|   * Blobs                 |            |
+		|     * Maximum size    [5] |     13 B   |
+		|   * Tags                  |            |
+		|     * Maximum size    [6] |    132 B   |
+
+		[1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
+		[2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a
+		[3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+		[4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c
+		[5] 97d808e45116bf02103490294d3d46dad7a2ac62
+		[6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2
 		EOF
 
 		git repo structure >out 2>err &&
@@ -73,7 +144,7 @@
 	)
 '
 
-test_expect_success 'keyvalue and nul format' '
+test_expect_success SHA1 'lines and nul format' '
 	test_when_finished "rm -rf repo" &&
 	git init repo &&
 	(
@@ -81,7 +152,7 @@
 		test_commit_bulk 42 &&
 		git tag -a foo -m bar &&
 
-		cat >expect <<-\EOF &&
+		cat >expect <<-EOF &&
 		references.branches.count=1
 		references.tags.count=1
 		references.remotes.count=0
@@ -90,18 +161,45 @@
 		objects.trees.count=42
 		objects.blobs.count=42
 		objects.tags.count=1
+		objects.commits.inflated_size=9225
+		objects.trees.inflated_size=28554
+		objects.blobs.inflated_size=453
+		objects.tags.inflated_size=132
+		objects.commits.disk_size=$(object_type_disk_usage commit)
+		objects.trees.disk_size=$(object_type_disk_usage tree)
+		objects.blobs.disk_size=$(object_type_disk_usage blob)
+		objects.tags.disk_size=$(object_type_disk_usage tag)
+		objects.commits.max_size=221
+		objects.commits.max_size_oid=de3508174b5c2ace6993da67cae9be9069e2df39
+		objects.trees.max_size=1335
+		objects.trees.max_size_oid=09931deea9d81ec21300d3e13c74412f32eacec5
+		objects.blobs.max_size=11
+		objects.blobs.max_size_oid=eaeeedced46482bd4281fda5a5f05ce24854151f
+		objects.tags.max_size=132
+		objects.tags.max_size_oid=1ee0f2b16ea37d895dbe9dbd76cd2ac70446176c
+		objects.commits.max_parents=1
+		objects.commits.max_parents_oid=de3508174b5c2ace6993da67cae9be9069e2df39
+		objects.trees.max_entries=42
+		objects.trees.max_entries_oid=09931deea9d81ec21300d3e13c74412f32eacec5
 		EOF
 
-		git repo structure --format=keyvalue >out 2>err &&
+		git repo structure --format=lines >out 2>err &&
 
 		test_cmp expect out &&
 		test_line_count = 0 err &&
 
-		# Replace key and value delimiters for nul format.
-		tr "\n=" "\0\n" <expect >expect_nul &&
 		git repo structure --format=nul >out 2>err &&
+		tr "\012\000" "=\012" <out >actual &&
 
-		test_cmp expect_nul out &&
+		test_cmp expect actual &&
+		test_line_count = 0 err &&
+
+		# "-z", as a synonym to "--format=nul", participates in the
+		# usual "last one wins" rule.
+		git repo structure --format=table -z >out 2>err &&
+		tr "\012\000" "=\012" <out >actual &&
+
+		test_cmp expect actual &&
 		test_line_count = 0 err
 	)
 '
diff --git a/t/t2003-checkout-cache-mkdir.sh b/t/t2003-checkout-cache-mkdir.sh
index ff163cf..19166ad 100755
--- a/t/t2003-checkout-cache-mkdir.sh
+++ b/t/t2003-checkout-cache-mkdir.sh
@@ -24,27 +24,28 @@
 	mkdir path2 &&
 	ln -s path2 path1 &&
 	git checkout-index -f -a &&
-	test ! -h path1 && test -d path1 &&
-	test -f path1/file1 && test ! -f path2/file1
+	test_path_is_dir_not_symlink path1 &&
+	test_path_is_file path1/file1 &&
+	test_path_is_missing path2/file1
 '
 
 test_expect_success 'use --prefix=path2/' '
 	rm -fr path0 path1 path2 &&
 	mkdir path2 &&
 	git checkout-index --prefix=path2/ -f -a &&
-	test -f path2/path0 &&
-	test -f path2/path1/file1 &&
-	test ! -f path0 &&
-	test ! -f path1/file1
+	test_path_is_file path2/path0 &&
+	test_path_is_file path2/path1/file1 &&
+	test_path_is_missing path0 &&
+	test_path_is_missing path1/file1
 '
 
 test_expect_success 'use --prefix=tmp-' '
 	rm -fr path0 path1 path2 tmp* &&
 	git checkout-index --prefix=tmp- -f -a &&
-	test -f tmp-path0 &&
-	test -f tmp-path1/file1 &&
-	test ! -f path0 &&
-	test ! -f path1/file1
+	test_path_is_file tmp-path0 &&
+	test_path_is_file tmp-path1/file1 &&
+	test_path_is_missing path0 &&
+	test_path_is_missing path1/file1
 '
 
 test_expect_success 'use --prefix=tmp- but with a conflicting file and dir' '
@@ -52,10 +53,10 @@
 	echo nitfol >tmp-path1 &&
 	mkdir tmp-path0 &&
 	git checkout-index --prefix=tmp- -f -a &&
-	test -f tmp-path0 &&
-	test -f tmp-path1/file1 &&
-	test ! -f path0 &&
-	test ! -f path1/file1
+	test_path_is_file tmp-path0 &&
+	test_path_is_file tmp-path1/file1 &&
+	test_path_is_missing path0 &&
+	test_path_is_missing path1/file1
 '
 
 test_expect_success SYMLINKS 'use --prefix=tmp/orary/ where tmp is a symlink' '
@@ -63,10 +64,10 @@
 	mkdir tmp1 tmp1/orary &&
 	ln -s tmp1 tmp &&
 	git checkout-index --prefix=tmp/orary/ -f -a &&
-	test -d tmp1/orary &&
-	test -f tmp1/orary/path0 &&
-	test -f tmp1/orary/path1/file1 &&
-	test -h tmp
+	test_path_is_dir tmp1/orary &&
+	test_path_is_file tmp1/orary/path0 &&
+	test_path_is_file tmp1/orary/path1/file1 &&
+	test_path_is_symlink tmp
 '
 
 test_expect_success SYMLINKS 'use --prefix=tmp/orary- where tmp is a symlink' '
@@ -74,9 +75,9 @@
 	mkdir tmp1 &&
 	ln -s tmp1 tmp &&
 	git checkout-index --prefix=tmp/orary- -f -a &&
-	test -f tmp1/orary-path0 &&
-	test -f tmp1/orary-path1/file1 &&
-	test -h tmp
+	test_path_is_file tmp1/orary-path0 &&
+	test_path_is_file tmp1/orary-path1/file1 &&
+	test_path_is_symlink tmp
 '
 
 test_expect_success SYMLINKS 'use --prefix=tmp- where tmp-path1 is a symlink' '
@@ -84,10 +85,9 @@
 	mkdir tmp1 &&
 	ln -s tmp1 tmp-path1 &&
 	git checkout-index --prefix=tmp- -f -a &&
-	test -f tmp-path0 &&
-	test ! -h tmp-path1 &&
-	test -d tmp-path1 &&
-	test -f tmp-path1/file1
+	test_path_is_file tmp-path0 &&
+	test_path_is_dir_not_symlink tmp-path1 &&
+	test_path_is_file tmp-path1/file1
 '
 
 test_expect_success 'apply filter from working tree .gitattributes with --prefix' '
diff --git a/t/t2004-checkout-cache-temp.sh b/t/t2004-checkout-cache-temp.sh
index b92d96f..0afe0ff 100755
--- a/t/t2004-checkout-cache-temp.sh
+++ b/t/t2004-checkout-cache-temp.sh
@@ -42,7 +42,7 @@
 	test_line_count = 1 actual &&
 	test $(cut "-d	" -f2 actual) = path1 &&
 	p=$(cut "-d	" -f1 actual) &&
-	test -f $p &&
+	test_path_is_file $p &&
 	test $(cat $p) = tree1path1
 '
 
@@ -55,7 +55,7 @@
 	do
 		test $(grep $f actual | cut "-d	" -f2) = $f &&
 		p=$(grep $f actual | cut "-d	" -f1) &&
-		test -f $p &&
+		test_path_is_file $p &&
 		test $(cat $p) = tree1$f || return 1
 	done
 '
@@ -71,7 +71,7 @@
 	test_line_count = 1 actual &&
 	test $(cut "-d	" -f2 actual) = path1 &&
 	p=$(cut "-d	" -f1 actual) &&
-	test -f $p &&
+	test_path_is_file $p &&
 	test $(cat $p) = tree2path1
 '
 
@@ -83,7 +83,7 @@
 	do
 		test $(grep $f actual | cut "-d	" -f2) = $f &&
 		p=$(grep $f actual | cut "-d	" -f1) &&
-		test -f $p &&
+		test_path_is_file $p &&
 		test $(cat $p) = tree2$f || return 1
 	done
 '
@@ -108,9 +108,9 @@
 	test_line_count = 1 actual &&
 	test $(cut "-d	" -f2 actual) = path1 &&
 	cut "-d	" -f1 actual | (read s1 s2 s3 &&
-	test -f $s1 &&
-	test -f $s2 &&
-	test -f $s3 &&
+	test_path_is_file $s1 &&
+	test_path_is_file $s2 &&
+	test_path_is_file $s3 &&
 	test $(cat $s1) = tree1path1 &&
 	test $(cat $s2) = tree2path1 &&
 	test $(cat $s3) = tree3path1)
@@ -143,8 +143,8 @@
 	test $(cut "-d	" -f2 actual) = path2 &&
 	cut "-d	" -f1 actual | (read s1 s2 s3 &&
 	test $s1 = . &&
-	test -f $s2 &&
-	test -f $s3 &&
+	test_path_is_file $s2 &&
+	test_path_is_file $s3 &&
 	test $(cat $s2) = tree2path2 &&
 	test $(cat $s3) = tree3path2)
 '
@@ -162,9 +162,9 @@
 test_expect_success '-- path1: all 3 stages' '
 	test $(grep path1 actual | cut "-d	" -f2) = path1 &&
 	grep path1 actual | cut "-d	" -f1 | (read s1 s2 s3 &&
-	test -f $s1 &&
-	test -f $s2 &&
-	test -f $s3 &&
+	test_path_is_file $s1 &&
+	test_path_is_file $s2 &&
+	test_path_is_file $s3 &&
 	test $(cat $s1) = tree1path1 &&
 	test $(cat $s2) = tree2path1 &&
 	test $(cat $s3) = tree3path1)
@@ -174,8 +174,8 @@
 	test $(grep path2 actual | cut "-d	" -f2) = path2 &&
 	grep path2 actual | cut "-d	" -f1 | (read s1 s2 s3 &&
 	test $s1 = . &&
-	test -f $s2 &&
-	test -f $s3 &&
+	test_path_is_file $s2 &&
+	test_path_is_file $s3 &&
 	test $(cat $s2) = tree2path2 &&
 	test $(cat $s3) = tree3path2)
 '
@@ -183,9 +183,9 @@
 test_expect_success '-- path3: no stage 2, have stage 1 and 3' '
 	test $(grep path3 actual | cut "-d	" -f2) = path3 &&
 	grep path3 actual | cut "-d	" -f1 | (read s1 s2 s3 &&
-	test -f $s1 &&
+	test_path_is_file $s1 &&
 	test $s2 = . &&
-	test -f $s3 &&
+	test_path_is_file $s3 &&
 	test $(cat $s1) = tree1path3 &&
 	test $(cat $s3) = tree3path3)
 '
@@ -193,8 +193,8 @@
 test_expect_success '-- path4: no stage 3, have stage 1 and 3' '
 	test $(grep path4 actual | cut "-d	" -f2) = path4 &&
 	grep path4 actual | cut "-d	" -f1 | (read s1 s2 s3 &&
-	test -f $s1 &&
-	test -f $s2 &&
+	test_path_is_file $s1 &&
+	test_path_is_file $s2 &&
 	test $s3 = . &&
 	test $(cat $s1) = tree1path4 &&
 	test $(cat $s2) = tree2path4)
@@ -203,7 +203,7 @@
 test_expect_success '-- asubdir/path5: no stage 2 and 3 have stage 1' '
 	test $(grep asubdir/path5 actual | cut "-d	" -f2) = asubdir/path5 &&
 	grep asubdir/path5 actual | cut "-d	" -f1 | (read s1 s2 s3 &&
-	test -f $s1 &&
+	test_path_is_file $s1 &&
 	test $s2 = . &&
 	test $s3 = . &&
 	test $(cat $s1) = tree1asubdir/path5)
@@ -216,7 +216,7 @@
 		test_line_count = 1 actual &&
 		test $(grep path5 actual | cut "-d	" -f2) = path5 &&
 		grep path5 actual | cut "-d	" -f1 | (read s1 s2 s3 &&
-		test -f ../$s1 &&
+		test_path_is_file ../$s1 &&
 		test $s2 = . &&
 		test $s3 = . &&
 		test $(cat ../$s1) = tree1asubdir/path5)
@@ -230,7 +230,7 @@
 	test_line_count = 1 actual &&
 	test $(cut "-d	" -f2 actual) = path6 &&
 	p=$(cut "-d	" -f1 actual) &&
-	test -f $p &&
+	test_path_is_file $p &&
 	test $(cat $p) = path7
 '
 
diff --git a/t/t2021-checkout-overwrite.sh b/t/t2021-checkout-overwrite.sh
index a5c03d5..38c41ae 100755
--- a/t/t2021-checkout-overwrite.sh
+++ b/t/t2021-checkout-overwrite.sh
@@ -27,7 +27,7 @@
 	git rm --cached a/b &&
 	git commit -m "un-track the file" &&
 	test_must_fail git checkout start &&
-	test -f a/b
+	test_path_is_file a/b
 '
 
 test_expect_success 'create a commit where dir a/b changed to symlink' '
@@ -49,7 +49,7 @@
 
 test_expect_success SYMLINKS 'the symlink remained' '
 
-	test -h a/b
+	test_path_is_symlink a/b
 '
 
 test_expect_success 'cleanup after previous symlink tests' '
diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh
index a397790..c01f1cd 100755
--- a/t/t2027-checkout-track.sh
+++ b/t/t2027-checkout-track.sh
@@ -47,4 +47,22 @@
 	test_cmp_config refs/heads/main branch.b4.merge
 '
 
+test_expect_success 'ambiguous tracking info' '
+	# Set up a few remote repositories
+	git init --bare --initial-branch=trunk src1 &&
+	git init --bare --initial-branch=trunk src2 &&
+	git push src1 one:refs/heads/trunk &&
+	git push src2 two:refs/heads/trunk &&
+
+	git remote add -f src1 "file://$PWD/src1" &&
+	git remote add -f src2 "file://$PWD/src2" &&
+
+	# DWIM
+	test_must_fail git checkout trunk 2>hint.checkout &&
+	test_grep "hint: *git checkout --track" hint.checkout &&
+
+	test_must_fail git switch trunk 2>hint.switch &&
+	test_grep "hint: *git switch --track" hint.switch
+'
+
 test_done
diff --git a/t/t2206-add-submodule-ignored.sh b/t/t2206-add-submodule-ignored.sh
new file mode 100755
index 0000000..e581e87
--- /dev/null
+++ b/t/t2206-add-submodule-ignored.sh
@@ -0,0 +1,134 @@
+#!/bin/sh
+# shellcheck disable=SC2016
+
+# shellcheck disable=SC2034
+test_description='git add respects submodule ignore=all and explicit pathspec'
+
+# This test covers the behavior of "git add", "git status" and "git log" when
+# dealing with submodules that have the ignore=all setting in
+# .gitmodules. It ensures that changes in such submodules are
+# ignored by default, but can be staged with "git add --force".
+
+# shellcheck disable=SC1091
+. ./test-lib.sh
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+base_path=$(pwd -P)
+
+#1
+test_expect_success 'setup: create origin repos'  '
+	cd "${base_path}" &&
+	git config --global protocol.file.allow always &&
+	git init sub &&
+		pwd &&
+		cd sub &&
+		test_commit sub_file1 &&
+		git tag v1.0 &&
+		test_commit sub_file2 &&
+		git tag v2.0 &&
+		test_commit sub_file3 &&
+		git tag v3.0 &&
+	cd "${base_path}" &&
+	git init main &&
+		cd main &&
+		test_commit first &&
+	cd "${base_path}"
+'
+#2
+# add submodule with default config (ignore=none) and
+# check log that is contains a path entry for the submodule 'sub'
+# change the commit in the submodule and check that 'git status' shows it as modified
+test_expect_success 'main: add submodule with default config'  '
+	cd "${base_path}" &&
+	cd main &&
+	git submodule add ../sub &&
+	git commit -m "add submodule" &&
+	git log --oneline --name-only | grep "^sub$" &&
+	git -C sub reset --hard v2.0 &&
+	git status --porcelain | grep "^ M sub$" &&
+	echo
+'
+#3
+# change the submodule config to ignore=all and check that status and log do not show changes
+test_expect_success 'main: submodule config ignore=all'  '
+	cd "${base_path}" &&
+	cd main &&
+	git config -f .gitmodules submodule.sub.ignore all &&
+	GIT_TRACE=1 git add . &&
+	git commit -m "update submodule config sub.ignore all" &&
+	! git status --porcelain | grep "^.*$" &&
+	! git log --oneline --name-only | grep "^sub$" &&
+	echo
+'
+#4
+# change the commit in the submodule and check that 'git status' does not show it as modified
+# but 'git status --ignore-submodules=none' does show it as modified
+test_expect_success 'sub: change to different sha1 and check status in main'  '
+	cd "${base_path}" &&
+	cd main &&
+	git -C sub reset --hard v1.0 &&
+	! git status --porcelain | grep "^ M sub$" &&
+	git status --ignore-submodules=none --porcelain | grep "^ M sub$" &&
+	echo
+'
+
+#5
+# check that normal 'git add' does not stage the change in the submodule
+test_expect_success 'main: check normal add and status'  '
+	cd "${base_path}" &&
+	cd main &&
+	GIT_TRACE=1 git add . &&
+	! git status --porcelain | grep "^ M sub$" &&
+	echo
+'
+
+#6
+# check that 'git add --force .' does not stage the change in the submodule
+# and that 'git status' does not show it as modified
+test_expect_success 'main: check --force add . and status'  '
+	cd "${base_path}" &&
+	cd main &&
+	GIT_TRACE=1 git add --force . &&
+	! git status --porcelain | grep "^M  sub$" &&
+	echo
+'
+
+#7
+# check that 'git add .' does not stage the change in the submodule
+# and that 'git status' does not show it as modified
+test_expect_success 'main: check _add sub_ and status'  '
+	cd "${base_path}" &&
+	cd main &&
+	GIT_TRACE=1 git add sub 2>&1 | grep "Skipping submodule due to ignore=all: sub" &&
+	! git status --porcelain | grep "^M  sub$" &&
+	echo
+'
+
+#8
+# check that 'git add --force sub' does stage the change in the submodule
+# check that 'git add --force ./sub/' does stage the change in the submodule
+# and that 'git status --porcelain' does show it as modified
+# commit it..
+# check that 'git log --ignore-submodules=none' shows the submodule change
+# in the log
+test_expect_success 'main: check force add sub and ./sub/ and status'  '
+	cd "${base_path}" &&
+	cd main &&
+	echo "Adding with --force should work: git add --force sub" &&
+	GIT_TRACE=1 git add --force sub &&
+	git status --porcelain | grep "^M  sub$" &&
+	git restore --staged sub &&
+	! git status --porcelain | grep "^M  sub$" &&
+	echo "Adding with --force should work: git add --force ./sub/" &&
+	GIT_TRACE=1 git add --force ./sub/ &&
+	git status --porcelain | grep "^M  sub$" &&
+	git commit -m "update submodule pointer" &&
+	! git status --porcelain | grep "^ M sub$" &&
+	git log --ignore-submodules=none --name-only --oneline | grep "^sub$" &&
+	echo
+'
+
+test_done
+exit 0
diff --git a/t/t2402-worktree-list.sh b/t/t2402-worktree-list.sh
index 8ef1cad..e0c6abd 100755
--- a/t/t2402-worktree-list.sh
+++ b/t/t2402-worktree-list.sh
@@ -29,23 +29,34 @@
 	test_cmp expect actual
 '
 
-test_expect_success '"list" all worktrees from main' '
-	echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
-	test_when_finished "rm -rf here out actual expect && git worktree prune" &&
-	git worktree add --detach here main &&
-	echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
-	git worktree list >out &&
-	sed "s/  */ /g" <out >actual &&
+test_expect_success '"list" all worktrees from main core.quotepath=false' '
+	test_config core.quotepath false &&
+	echo "$(git rev-parse --show-toplevel)      $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
+	test_when_finished "rm -rf áááá out actual expect && git worktree prune" &&
+	git worktree add --detach áááá main &&
+	echo "$(git -C áááá rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
+	git worktree list >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success '"list" all worktrees from main core.quotepath=true' '
+	test_config core.quotepath true &&
+	echo "$(git rev-parse --show-toplevel)            $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
+	test_when_finished "rm -rf á out actual expect && git worktree prune" &&
+	git worktree add --detach á main &&
+	echo "\"$(git -C á rev-parse --show-toplevel)\" $(git rev-parse --short HEAD) (detached HEAD)" |
+		sed s/á/\\\\303\\\\241/g >>expect &&
+	git worktree list >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success '"list" all worktrees from linked' '
-	echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
-	test_when_finished "rm -rf here out actual expect && git worktree prune" &&
-	git worktree add --detach here main &&
-	echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
-	git -C here worktree list >out &&
-	sed "s/  */ /g" <out >actual &&
+	test_config core.quotepath false &&
+	echo "$(git rev-parse --show-toplevel)      $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
+	test_when_finished "rm -rf áááá out actual expect && git worktree prune" &&
+	git worktree add --detach áááá main &&
+	echo "$(git -C áááá rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
+	git -C áááá worktree list >actual &&
 	test_cmp expect actual
 '
 
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index f3e720d..c58e505 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1707,9 +1707,9 @@
 '
 
 test_expect_success 'errors if given a bad branch name' '
-	cat <<-\EOF >expect &&
-	fatal: '\''foo..bar'\'' is not a valid branch name
-	hint: See `man git check-ref-format`
+	cat <<-EOF >expect &&
+	fatal: ${SQ}foo..bar${SQ} is not a valid branch name
+	hint: See ${SQ}git help check-ref-format${SQ}
 	hint: Disable this message with "git config set advice.refSyntax false"
 	EOF
 	test_must_fail git branch foo..bar >actual 2>&1 &&
diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh
index 597df5e..0bb366f 100755
--- a/t/t3310-notes-merge-manual-resolve.sh
+++ b/t/t3310-notes-merge-manual-resolve.sh
@@ -227,7 +227,8 @@
 	# Verify that current notes tree (pre-merge) has not changed (m == y)
 	verify_notes y &&
 	verify_notes m &&
-	test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+	git rev-parse refs/notes/m >actual &&
+	test_cmp pre_merge_y actual
 '
 
 cat <<EOF | sort >expect_notes_z
@@ -260,7 +261,7 @@
 '
 
 test_expect_success 'cannot do merge w/conflicts when previous merge is unfinished' '
-	test -d .git/NOTES_MERGE_WORKTREE &&
+	test_path_is_dir .git/NOTES_MERGE_WORKTREE &&
 	test_must_fail git notes merge z >output 2>&1 &&
 	# Output should indicate what is wrong
 	test_grep -q "\\.git/NOTES_MERGE_\\* exists" output
@@ -320,7 +321,7 @@
 EOF
 
 test_expect_success 'can do merge without conflicts even if previous merge is unfinished (x => w)' '
-	test -d .git/NOTES_MERGE_WORKTREE &&
+	test_path_is_dir .git/NOTES_MERGE_WORKTREE &&
 	git notes merge x &&
 	verify_notes w &&
 	# Verify that other notes refs has not changed (x and y)
@@ -375,8 +376,10 @@
 	git notes merge --commit &&
 	notes_merge_files_gone &&
 	# Merge commit has pre-merge y and pre-merge z as parents
-	test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
-	test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" &&
+	git rev-parse refs/notes/m^1 >actual &&
+	test_cmp pre_merge_y actual &&
+	git rev-parse refs/notes/m^2 >actual &&
+	test_cmp pre_merge_z actual &&
 	# Merge commit mentions the notes refs merged
 	git log -1 --format=%B refs/notes/m > merge_commit_msg &&
 	grep -q refs/notes/m merge_commit_msg &&
@@ -428,14 +431,16 @@
 	# Verify that current notes tree (pre-merge) has not changed (m == y)
 	verify_notes y &&
 	verify_notes m &&
-	test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+	git rev-parse refs/notes/m >actual &&
+	test_cmp pre_merge_y actual
 '
 
 test_expect_success 'abort notes merge' '
 	git notes merge --abort &&
 	notes_merge_files_gone &&
 	# m has not moved (still == y)
-	test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" &&
+	git rev-parse refs/notes/m >actual &&
+	test_cmp pre_merge_y actual &&
 	# Verify that other notes refs has not changed (w, x, y and z)
 	verify_notes w &&
 	verify_notes x &&
@@ -460,7 +465,8 @@
 	# Verify that current notes tree (pre-merge) has not changed (m == y)
 	verify_notes y &&
 	verify_notes m &&
-	test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+	git rev-parse refs/notes/m >actual &&
+	test_cmp pre_merge_y actual
 '
 
 cat <<EOF | sort >expect_notes_m
@@ -500,8 +506,10 @@
 	git notes merge --commit &&
 	notes_merge_files_gone &&
 	# Merge commit has pre-merge y and pre-merge z as parents
-	test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
-	test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" &&
+	git rev-parse refs/notes/m^1 >actual &&
+	test_cmp pre_merge_y actual &&
+	git rev-parse refs/notes/m^2 >actual &&
+	test_cmp pre_merge_z actual &&
 	# Merge commit mentions the notes refs merged
 	git log -1 --format=%B refs/notes/m > merge_commit_msg &&
 	grep -q refs/notes/m merge_commit_msg &&
@@ -539,7 +547,8 @@
 	# Verify that current notes tree (pre-merge) has not changed (m == y)
 	verify_notes y &&
 	verify_notes m &&
-	test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+	git rev-parse refs/notes/m >actual &&
+	test_cmp pre_merge_y actual
 '
 
 cp expect_notes_w expect_notes_m
@@ -548,7 +557,7 @@
 test_expect_success 'reset notes ref m to somewhere else (w)' '
 	git update-ref refs/notes/m refs/notes/w &&
 	verify_notes m &&
-	test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+	test_cmp_rev refs/notes/m refs/notes/w
 '
 
 test_expect_success 'fail to finalize conflicting merge if underlying ref has moved in the meantime (m != NOTES_MERGE_PARTIAL^1)' '
@@ -564,18 +573,20 @@
 	# NOTES_MERGE_* refs and .git/NOTES_MERGE_* state files must remain
 	git rev-parse --verify NOTES_MERGE_PARTIAL &&
 	git rev-parse --verify NOTES_MERGE_REF &&
-	test -f .git/NOTES_MERGE_WORKTREE/$commit_sha1 &&
-	test -f .git/NOTES_MERGE_WORKTREE/$commit_sha2 &&
-	test -f .git/NOTES_MERGE_WORKTREE/$commit_sha3 &&
-	test -f .git/NOTES_MERGE_WORKTREE/$commit_sha4 &&
+	test_path_is_file .git/NOTES_MERGE_WORKTREE/$commit_sha1 &&
+	test_path_is_file .git/NOTES_MERGE_WORKTREE/$commit_sha2 &&
+	test_path_is_file .git/NOTES_MERGE_WORKTREE/$commit_sha3 &&
+	test_path_is_file .git/NOTES_MERGE_WORKTREE/$commit_sha4 &&
 	# Refs are unchanged
-	test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
-	test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)" &&
-	test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)" &&
+	test_cmp_rev refs/notes/m refs/notes/w &&
+	test_cmp_rev refs/notes/y NOTES_MERGE_PARTIAL^1 &&
+	test_cmp_rev ! refs/notes/m NOTES_MERGE_PARTIAL^1 &&
 	# Mention refs/notes/m, and its current and expected value in output
 	test_grep -q "refs/notes/m" output &&
-	test_grep -q "$(git rev-parse refs/notes/m)" output &&
-	test_grep -q "$(git rev-parse NOTES_MERGE_PARTIAL^1)" output &&
+	oid=$(git rev-parse refs/notes/m) &&
+	test_grep -q "$oid" output &&
+	oid=$(git rev-parse NOTES_MERGE_PARTIAL^1) &&
+	test_grep -q "$oid" output &&
 	# Verify that other notes refs has not changed (w, x, y and z)
 	verify_notes w &&
 	verify_notes x &&
@@ -587,7 +598,7 @@
 	git notes merge --abort &&
 	notes_merge_files_gone &&
 	# m has not moved (still == w)
-	test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
+	test_cmp_rev refs/notes/m refs/notes/w &&
 	# Verify that other notes refs has not changed (w, x, y and z)
 	verify_notes w &&
 	verify_notes x &&
@@ -606,8 +617,8 @@
 	test_must_fail git notes merge refs/notes/other &&
 	(
 		cd .git/NOTES_MERGE_WORKTREE &&
-		echo "foo" > $(git rev-parse HEAD) &&
-		echo "bar" >> $(git rev-parse HEAD) &&
+		oid=$(git rev-parse HEAD) &&
+		test_write_lines foo bar >"$oid" &&
 		git notes merge --commit
 	) &&
 	git notes show HEAD > actual_notes &&
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index e778dd8..3e44562 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -31,6 +31,12 @@
 . "$TEST_DIRECTORY"/lib-rebase.sh
 
 test_expect_success 'setup' '
+	# Commit dates are hardcoded to 2005, and the reflog entries will have
+	# a matching timestamp. Maintenance may thus immediately expire
+	# reflogs if it was running.
+	git config set gc.reflogExpire never &&
+	git config set gc.reflogExpireUnreachable never &&
+
 	git switch -C primary &&
 	test_commit A file1 &&
 	test_commit B file1 &&
diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index a1d7fa7..bc51a9d 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -8,6 +8,12 @@
 . ./test-lib.sh
 
 test_expect_success 'setup' '
+	# Commit dates are hardcoded to 2005, and the reflog entries will have
+	# a matching timestamp. Maintenance may thus immediately expire
+	# reflogs if it was running.
+	git config set gc.reflogExpire never &&
+	git config set gc.reflogExpireUnreachable never &&
+
 	test_commit O fileO &&
 	test_commit X fileX &&
 	git branch fast-forward &&
diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh
index be09fc7..4336f41 100755
--- a/t/t3431-rebase-fork-point.sh
+++ b/t/t3431-rebase-fork-point.sh
@@ -17,6 +17,12 @@
 # C was formerly part of main but main was rewound to remove C
 #
 test_expect_success setup '
+	# Commit dates are hardcoded to 2005, and the reflog entries will have
+	# a matching timestamp. Maintenance may thus immediately expire
+	# reflogs if it was running.
+	git config set gc.reflogExpire never &&
+	git config set gc.reflogExpireUnreachable never &&
+
 	test_commit A &&
 	test_commit B &&
 	test_commit C &&
diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 5086e14..181d19d 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -11,6 +11,12 @@
 . ./test-lib.sh
 
 test_expect_success setup '
+	# Commit dates are hardcoded to 2005, and the reflog entries will have
+	# a matching timestamp. Maintenance may thus immediately expire
+	# reflogs if it was running.
+	git config set gc.reflogExpire never &&
+	git config set gc.reflogExpireUnreachable never &&
+
 	test_commit A &&
 	test_commit B &&
 	test_commit C &&
diff --git a/t/t3450-history.sh b/t/t3450-history.sh
new file mode 100755
index 0000000..f513463
--- /dev/null
+++ b/t/t3450-history.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test_description='tests for git-history command'
+
+. ./test-lib.sh
+
+test_expect_success 'does nothing without any arguments' '
+	test_must_fail git history 2>err &&
+	test_grep "need a subcommand" err
+'
+
+test_expect_success 'raises an error with unknown argument' '
+	test_must_fail git history garbage 2>err &&
+	test_grep "unknown subcommand: .garbage." err
+'
+
+test_done
diff --git a/t/t3451-history-reword.sh b/t/t3451-history-reword.sh
new file mode 100755
index 0000000..de7b357
--- /dev/null
+++ b/t/t3451-history-reword.sh
@@ -0,0 +1,399 @@
+#!/bin/sh
+
+test_description='tests for git-history reword subcommand'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-log-graph.sh"
+
+reword_with_message () {
+	cat >message &&
+	write_script fake-editor.sh <<-\EOF &&
+	cp message "$1"
+	EOF
+	test_set_editor "$(pwd)"/fake-editor.sh &&
+	git history reword "$@" &&
+	rm fake-editor.sh message
+}
+
+expect_graph () {
+	cat >expect &&
+	lib_test_cmp_graph --graph --format=%s "$@"
+}
+
+expect_log () {
+	git log --format="%s" "$@" >actual &&
+	cat >expect &&
+	test_cmp expect actual
+}
+
+test_expect_success 'can reword tip of a branch' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit first &&
+		test_commit second &&
+		test_commit third &&
+
+		git symbolic-ref HEAD >expect &&
+		reword_with_message HEAD <<-EOF &&
+		third reworded
+		EOF
+		git symbolic-ref HEAD >actual &&
+		test_cmp expect actual &&
+
+		expect_log <<-\EOF &&
+		third reworded
+		second
+		first
+		EOF
+
+		git reflog >reflog &&
+		test_grep "reword: updating HEAD" reflog
+	)
+'
+
+test_expect_success 'can reword commit in the middle' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit first &&
+		test_commit second &&
+		test_commit third &&
+
+		git symbolic-ref HEAD >expect &&
+		reword_with_message HEAD~ <<-EOF &&
+		second reworded
+		EOF
+		git symbolic-ref HEAD >actual &&
+		test_cmp expect actual &&
+
+		expect_log <<-\EOF
+		third
+		second reworded
+		first
+		EOF
+	)
+'
+
+test_expect_success 'can reword commit in the middle even on detached head' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit first &&
+		test_commit second &&
+		test_commit third_on_main &&
+		git checkout --detach HEAD^ &&
+		test_commit third_on_head &&
+
+		reword_with_message HEAD~ <<-EOF &&
+		second reworded
+		EOF
+
+		expect_graph HEAD --branches <<-\EOF
+		* third_on_head
+		| * third_on_main
+		|/
+		* second reworded
+		* first
+		EOF
+       )
+'
+
+test_expect_success 'can reword the detached head' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit first &&
+		test_commit second &&
+		git checkout --detach HEAD &&
+		test_commit third &&
+
+		reword_with_message HEAD <<-EOF &&
+		third reworded
+		EOF
+
+		expect_log <<-\EOF
+		third reworded
+		second
+		first
+		EOF
+	)
+'
+
+test_expect_success 'can reword root commit' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit first &&
+		test_commit second &&
+		test_commit third &&
+		reword_with_message HEAD~2 <<-EOF &&
+		first reworded
+		EOF
+
+		expect_log <<-\EOF
+		third
+		second
+		first reworded
+		EOF
+	)
+'
+
+test_expect_success 'can reword in a bare repo' '
+	test_when_finished "rm -rf repo repo.git" &&
+	git init repo &&
+	test_commit -C repo first &&
+	git clone --bare repo repo.git &&
+	(
+		cd repo.git &&
+		reword_with_message HEAD <<-EOF &&
+		reworded
+		EOF
+
+		expect_log <<-\EOF
+		reworded
+		EOF
+	)
+'
+
+test_expect_success 'can reword a commit on a different branch' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit base &&
+		git branch theirs &&
+		test_commit ours &&
+		git switch theirs &&
+		test_commit theirs &&
+
+		git rev-parse ours >ours-before &&
+		reword_with_message theirs <<-EOF &&
+		Reworded theirs
+		EOF
+		git rev-parse ours >ours-after &&
+		test_cmp ours-before ours-after &&
+
+		expect_graph --branches <<-\EOF
+		* Reworded theirs
+		| * ours
+		|/
+		* base
+		EOF
+	)
+'
+
+test_expect_success 'can reword a merge commit' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit base &&
+		git branch branch &&
+		test_commit ours &&
+		git switch branch &&
+		test_commit theirs &&
+		git switch - &&
+		git merge theirs &&
+
+		# It is not possible to replay merge commits embedded in the
+		# history (yet).
+		test_must_fail git -c core.editor=false history reword HEAD~ 2>err &&
+		test_grep "replaying merge commits is not supported yet" err &&
+
+		# But it is possible to reword a merge commit directly.
+		reword_with_message HEAD <<-EOF &&
+		Reworded merge commit
+		EOF
+		expect_graph <<-\EOF
+		*   Reworded merge commit
+		|\
+		| * theirs
+		* | ours
+		|/
+		* base
+		EOF
+	)
+'
+
+test_expect_success '--dry-run prints ref updates without modifying repo' '
+	test_when_finished "rm -rf repo" &&
+	git init repo --initial-branch=main &&
+	(
+		cd repo &&
+		test_commit base &&
+		git branch branch &&
+		test_commit ours &&
+		git switch branch &&
+		test_commit theirs &&
+
+		git refs list >refs-expect &&
+		reword_with_message --dry-run --update-refs=head base >updates <<-\EOF &&
+		reworded commit
+		EOF
+		git refs list >refs-actual &&
+		test_cmp refs-expect refs-actual &&
+		test_grep "update refs/heads/branch" updates &&
+		test_grep ! "update refs/heads/main" updates &&
+
+		reword_with_message --dry-run base >updates <<-\EOF &&
+		reworded commit
+		EOF
+		git refs list >refs-actual &&
+		test_cmp refs-expect refs-actual &&
+
+		test_grep "update refs/heads/branch" updates &&
+		test_grep "update refs/heads/main" updates &&
+		git update-ref --stdin <updates &&
+		expect_log --branches <<-\EOF
+		theirs
+		ours
+		reworded commit
+		EOF
+	)
+'
+
+test_expect_success '--update-refs=head updates only HEAD' '
+	test_when_finished "rm -rf repo" &&
+	git init repo --initial-branch=main &&
+	(
+		cd repo &&
+		test_commit base &&
+		git branch branch &&
+		test_commit theirs &&
+		git switch branch &&
+		test_commit ours &&
+
+		# When told to update HEAD, only, the command will refuse to
+		# rewrite commits that are not an ancestor of HEAD.
+		test_must_fail git -c core.editor=false history reword --update-refs=head theirs 2>err &&
+		test_grep "rewritten commit must be an ancestor of HEAD" err &&
+
+		reword_with_message --update-refs=head base >updates <<-\EOF &&
+		reworded base
+		EOF
+		expect_log HEAD <<-\EOF &&
+		ours
+		reworded base
+		EOF
+		expect_log main <<-\EOF
+		theirs
+		base
+		EOF
+	)
+'
+
+test_expect_success 'editor shows proper status' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit first &&
+
+		write_script fake-editor.sh <<-\EOF &&
+		cp "$1" . &&
+		printf "\namend a comment\n" >>"$1"
+		EOF
+		test_set_editor "$(pwd)"/fake-editor.sh &&
+		git history reword HEAD &&
+
+		cat >expect <<-EOF &&
+		first
+
+		# Please enter the commit message for the reworded changes. Lines starting
+		# with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
+		# Changes to be committed:
+		#	new file:   first.t
+		#
+		EOF
+		test_cmp expect COMMIT_EDITMSG &&
+
+		test_commit_message HEAD <<-\EOF
+		first
+
+		amend a comment
+		EOF
+	)
+'
+
+# For now, git-history(1) does not yet execute any hooks. This is subject to
+# change in the future, and if it does this test here is expected to start
+# failing. In other words, this test is not an endorsement of the current
+# status quo.
+test_expect_success 'hooks are not executed for rewritten commits' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit first &&
+		test_commit second &&
+		test_commit third &&
+
+		ORIG_PATH="$(pwd)" &&
+		export ORIG_PATH &&
+		for hook in prepare-commit-msg pre-commit post-commit post-rewrite commit-msg
+		do
+			write_script .git/hooks/$hook <<-\EOF || exit 1
+			touch "$ORIG_PATH/hooks.log
+			EOF
+		done &&
+
+		reword_with_message HEAD~ <<-EOF &&
+		second reworded
+		EOF
+
+		cat >expect <<-EOF &&
+		third
+		second reworded
+		first
+		EOF
+		git log --format=%s >actual &&
+		test_cmp expect actual &&
+
+		test_path_is_missing hooks.log
+	)
+'
+
+test_expect_success 'aborts with empty commit message' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit first &&
+
+		! reword_with_message HEAD 2>err </dev/null &&
+		test_grep "Aborting commit due to empty commit message." err
+	)
+'
+
+test_expect_success 'retains changes in the worktree and index' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		touch a b &&
+		git add . &&
+		git commit -m "initial commit" &&
+		echo foo >a &&
+		echo bar >b &&
+		git add b &&
+		reword_with_message HEAD <<-EOF &&
+		message
+		EOF
+		cat >expect <<-\EOF &&
+		 M a
+		M  b
+		?? actual
+		?? expect
+		EOF
+		git status --porcelain >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_done
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index 58b3759..a03f8f9 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -25,6 +25,8 @@
 	git switch -c topic3 &&
 	test_commit G &&
 	test_commit H &&
+	git switch -c empty &&
+	git commit --allow-empty -m empty &&
 	git switch -c topic4 main &&
 	test_commit I &&
 	test_commit J &&
@@ -43,6 +45,13 @@
 	test_commit L &&
 	test_commit M &&
 
+	git switch --detach topic4 &&
+	test_commit N &&
+	test_commit O &&
+	git switch -c topic-with-merge topic4 &&
+	test_merge P O --no-ff &&
+	git switch main &&
+
 	git switch -c conflict B &&
 	test_commit C.conflict C.t conflict
 '
@@ -51,8 +60,55 @@
 	git clone --bare . bare
 '
 
+test_expect_success 'argument to --advance must be a reference' '
+	echo "fatal: argument to --advance must be a reference" >expect &&
+	oid=$(git rev-parse main) &&
+	test_must_fail git replay --advance=$oid topic1..topic2 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success '--onto with invalid commit-ish' '
+	printf "fatal: ${SQ}refs/not-valid${SQ} is not " >expect &&
+	printf "a valid commit-ish for --onto\n" >>expect &&
+	test_must_fail git replay --onto=refs/not-valid topic1..topic2 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'option --onto or --advance is mandatory' '
+	echo "error: option --onto or --advance is mandatory" >expect &&
+	test_might_fail git replay -h >>expect &&
+	test_must_fail git replay topic1..topic2 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'no base or negative ref gives no-replaying down to root error' '
+	echo "fatal: replaying down from root commit is not supported yet!" >expect &&
+	test_must_fail git replay --onto=topic1 topic2 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'options --advance and --contained cannot be used together' '
+	printf "fatal: options ${SQ}--advance${SQ} " >expect &&
+	printf "and ${SQ}--contained${SQ} cannot be used together\n" >>expect &&
+	test_must_fail git replay --advance=main --contained \
+		topic1..topic2 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'cannot advance target ... ordering would be ill-defined' '
+	echo "fatal: cannot advance target with multiple sources because ordering would be ill-defined" >expect &&
+	test_must_fail git replay --advance=main main topic1 topic2 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'replaying merge commits is not supported yet' '
+	echo "fatal: replaying merge commits is not supported yet!" >expect &&
+	test_must_fail git replay --advance=main main..topic-with-merge 2>actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'using replay to rebase two branches, one on top of other' '
-	git replay --onto main topic1..topic2 >result &&
+	git replay --ref-action=print --onto main topic1..topic2 >result &&
 
 	test_line_count = 1 result &&
 
@@ -68,7 +124,7 @@
 '
 
 test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '
-	git -C bare replay --onto main topic1..topic2 >result-bare &&
+	git -C bare replay --ref-action=print --onto main topic1..topic2 >result-bare &&
 	test_cmp expect result-bare
 '
 
@@ -86,7 +142,7 @@
 	# 2nd field of result is refs/heads/main vs. refs/heads/topic2
 	# 4th field of result is hash for main instead of hash for topic2
 
-	git replay --advance main topic1..topic2 >result &&
+	git replay --ref-action=print --advance main topic1..topic2 >result &&
 
 	test_line_count = 1 result &&
 
@@ -102,10 +158,29 @@
 '
 
 test_expect_success 'using replay on bare repo to perform basic cherry-pick' '
-	git -C bare replay --advance main topic1..topic2 >result-bare &&
+	git -C bare replay --ref-action=print --advance main topic1..topic2 >result-bare &&
 	test_cmp expect result-bare
 '
 
+test_expect_success 'commits that become empty are dropped' '
+	# Save original branches
+	git for-each-ref --format="update %(refname) %(objectname)" \
+		refs/heads/ >original-branches &&
+	test_when_finished "git update-ref --stdin <original-branches &&
+		rm original-branches" &&
+	# Cherry-pick tip of topic1 ("F"), from the middle of A..empty, to main
+	git replay --advance main topic1^! &&
+
+	# Replay all of A..empty onto main (which includes topic1 & thus F
+	# in the middle)
+	git replay --onto main --branches --ancestry-path=empty ^A \
+		>result &&
+	git log --format="%s%d" L..empty >actual &&
+	test_write_lines >expect \
+		"empty (empty)" "H (topic3)" G "C (topic1)" "F (main)" "M (tag: M)" &&
+	test_cmp expect actual
+'
+
 test_expect_success 'replay on bare repo fails with both --advance and --onto' '
 	test_must_fail git -C bare replay --advance main --onto main topic1..topic2 >result-bare
 '
@@ -115,7 +190,7 @@
 '
 
 test_expect_success 'using replay to also rebase a contained branch' '
-	git replay --contained --onto main main..topic3 >result &&
+	git replay --ref-action=print --contained --onto main main..topic3 >result &&
 
 	test_line_count = 2 result &&
 	cut -f 3 -d " " result >new-branch-tips &&
@@ -139,12 +214,12 @@
 '
 
 test_expect_success 'using replay on bare repo to also rebase a contained branch' '
-	git -C bare replay --contained --onto main main..topic3 >result-bare &&
+	git -C bare replay --ref-action=print --contained --onto main main..topic3 >result-bare &&
 	test_cmp expect result-bare
 '
 
 test_expect_success 'using replay to rebase multiple divergent branches' '
-	git replay --onto main ^topic1 topic2 topic4 >result &&
+	git replay --ref-action=print --onto main ^topic1 topic2 topic4 >result &&
 
 	test_line_count = 2 result &&
 	cut -f 3 -d " " result >new-branch-tips &&
@@ -168,7 +243,7 @@
 '
 
 test_expect_success 'using replay on bare repo to rebase multiple divergent branches, including contained ones' '
-	git -C bare replay --contained --onto main ^main topic2 topic3 topic4 >result &&
+	git -C bare replay --ref-action=print --contained --onto main ^main topic2 topic3 topic4 >result &&
 
 	test_line_count = 4 result &&
 	cut -f 3 -d " " result >new-branch-tips &&
@@ -195,6 +270,15 @@
 	done
 '
 
+test_expect_success 'using replay to update detached HEAD' '
+	current_head=$(git branch --show-current) &&
+	test_when_finished git switch "$current_head" &&
+	git switch --detach &&
+	test_commit something &&
+	git replay --ref-action=print --onto HEAD~2 --ref-action=print HEAD~..HEAD >updates &&
+	test_grep "update HEAD " updates
+'
+
 test_expect_success 'merge.directoryRenames=false' '
 	# create a test case that stress-tests the rename caching
 	git switch -c rename-onto &&
@@ -217,4 +301,101 @@
 		--onto rename-onto rename-onto..rename-from
 '
 
+test_expect_success 'default atomic behavior updates refs directly' '
+	# Use a separate branch to avoid contaminating topic2 for later tests
+	git branch test-atomic topic2 &&
+	test_when_finished "git branch -D test-atomic" &&
+
+	# Test default atomic behavior (no output, refs updated)
+	git replay --onto main topic1..test-atomic >output &&
+	test_must_be_empty output &&
+
+	# Verify ref was updated
+	git log --format=%s test-atomic >actual &&
+	test_write_lines E D M L B A >expect &&
+	test_cmp expect actual &&
+
+	# Verify reflog message includes SHA of onto commit
+	git reflog test-atomic -1 --format=%gs >reflog-msg &&
+	ONTO_SHA=$(git rev-parse main) &&
+	echo "replay --onto $ONTO_SHA" >expect-reflog &&
+	test_cmp expect-reflog reflog-msg
+'
+
+test_expect_success 'atomic behavior in bare repository' '
+	# Store original state for cleanup
+	START=$(git -C bare rev-parse topic2) &&
+	test_when_finished "git -C bare update-ref refs/heads/topic2 $START" &&
+
+	# Test atomic updates work in bare repo
+	git -C bare replay --onto main topic1..topic2 >output &&
+	test_must_be_empty output &&
+
+	# Verify ref was updated in bare repo
+	git -C bare log --format=%s topic2 >actual &&
+	test_write_lines E D M L B A >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'reflog message for --advance mode' '
+	# Store original state
+	START=$(git rev-parse main) &&
+	test_when_finished "git update-ref refs/heads/main $START" &&
+
+	# Test --advance mode reflog message
+	git replay --advance main topic1..topic2 >output &&
+	test_must_be_empty output &&
+
+	# Verify reflog message includes --advance and branch name
+	git reflog main -1 --format=%gs >reflog-msg &&
+	echo "replay --advance main" >expect-reflog &&
+	test_cmp expect-reflog reflog-msg
+'
+
+test_expect_success 'replay.refAction=print config option' '
+	# Store original state
+	START=$(git rev-parse topic2) &&
+	test_when_finished "git branch -f topic2 $START" &&
+
+	# Test with config set to print
+	test_config replay.refAction print &&
+	git replay --onto main topic1..topic2 >output &&
+	test_line_count = 1 output &&
+	test_grep "^update refs/heads/topic2 " output
+'
+
+test_expect_success 'replay.refAction=update config option' '
+	# Store original state
+	START=$(git rev-parse topic2) &&
+	test_when_finished "git branch -f topic2 $START" &&
+
+	# Test with config set to update
+	test_config replay.refAction update &&
+	git replay --onto main topic1..topic2 >output &&
+	test_must_be_empty output &&
+
+	# Verify ref was updated
+	git log --format=%s topic2 >actual &&
+	test_write_lines E D M L B A >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'command-line --ref-action overrides config' '
+	# Store original state
+	START=$(git rev-parse topic2) &&
+	test_when_finished "git branch -f topic2 $START" &&
+
+	# Set config to update but use --ref-action=print
+	test_config replay.refAction update &&
+	git replay --ref-action=print --onto main topic1..topic2 >output &&
+	test_line_count = 1 output &&
+	test_grep "^update refs/heads/topic2 " output
+'
+
+test_expect_success 'invalid replay.refAction value' '
+	test_config replay.refAction invalid &&
+	test_must_fail git replay --onto main topic1..topic2 2>error &&
+	test_grep "invalid.*replay.refAction.*value" error
+'
+
 test_done
diff --git a/t/t3700-add.sh b/t/t3700-add.sh
index df580a5..2947bf9 100755
--- a/t/t3700-add.sh
+++ b/t/t3700-add.sh
@@ -38,7 +38,8 @@
 '
 
 test_expect_success 'Post-check that foo is in the index' '
-	git ls-files foo | grep foo
+	git ls-files foo >actual &&
+	test_grep foo actual
 '
 
 test_expect_success 'Test that "git add -- -q" works' '
@@ -140,7 +141,7 @@
 	git ls-files >files &&
 	sed -n "/\\.ig/p" <files >actual &&
 	test_must_be_empty actual &&
-	grep a.if files
+	test_grep a.if files
 '
 
 test_expect_success 'add ignored ones with -f' '
@@ -195,8 +196,9 @@
 	echo new > file &&
 	echo new > symlink &&
 	git add file symlink &&
-	git ls-files --stage | grep "^100755 .* 0	file$" &&
-	git ls-files --stage | grep "^120000 .* 0	symlink$"
+	git ls-files --stage >actual &&
+	test_grep "^100755 .* 0	file$" actual &&
+	test_grep "^120000 .* 0	symlink$" actual
 '
 
 test_expect_success 'git add with filemode=0, symlinks=0 prefers stage 2 over stage 1' '
@@ -212,8 +214,9 @@
 	echo new > file &&
 	echo new > symlink &&
 	git add file symlink &&
-	git ls-files --stage | grep "^100755 .* 0	file$" &&
-	git ls-files --stage | grep "^120000 .* 0	symlink$"
+	git ls-files --stage >actual &&
+	test_grep "^100755 .* 0	file$" actual &&
+	test_grep "^120000 .* 0	symlink$" actual
 '
 
 test_expect_success 'git add --refresh' '
@@ -238,8 +241,8 @@
 	test_must_be_empty actual &&
 
 	git diff-files --name-only >actual &&
-	! grep bar actual &&
-	grep baz actual
+	test_grep ! bar actual &&
+	test_grep baz actual
 '
 
 test_expect_success 'git add --refresh correctly reports no match error' "
@@ -254,7 +257,8 @@
 	date >foo2 &&
 	chmod 0 foo2 &&
 	test_must_fail git add --verbose . &&
-	! ( git ls-files foo1 | grep foo1 )
+	git ls-files foo1 >actual &&
+	test_grep ! foo1 actual
 '
 
 rm -f foo2
@@ -265,7 +269,8 @@
 	date >foo2 &&
 	chmod 0 foo2 &&
 	test_must_fail git add --verbose --ignore-errors . &&
-	git ls-files foo1 | grep foo1
+	git ls-files foo1 >actual &&
+	test_grep foo1 actual
 '
 
 rm -f foo2
@@ -277,7 +282,8 @@
 	date >foo2 &&
 	chmod 0 foo2 &&
 	test_must_fail git add --verbose . &&
-	git ls-files foo1 | grep foo1
+	git ls-files foo1 >actual &&
+	test_grep foo1 actual
 '
 rm -f foo2
 
@@ -288,7 +294,8 @@
 	date >foo2 &&
 	chmod 0 foo2 &&
 	test_must_fail git add --verbose . &&
-	! ( git ls-files foo1 | grep foo1 )
+	git ls-files foo1 >actual &&
+	test_grep ! foo1 actual
 '
 rm -f foo2
 
@@ -299,7 +306,8 @@
 	date >foo2 &&
 	chmod 0 foo2 &&
 	test_must_fail git add --verbose --no-ignore-errors . &&
-	! ( git ls-files foo1 | grep foo1 ) &&
+	git ls-files foo1 >actual &&
+	test_grep ! foo1 actual &&
 	git config add.ignore-errors 0
 '
 rm -f foo2
@@ -308,8 +316,10 @@
 	git reset --hard &&
 	touch fo\[ou\]bar foobar &&
 	git add '\''fo\[ou\]bar'\'' &&
-	git ls-files fo\[ou\]bar | grep -F fo\[ou\]bar &&
-	! ( git ls-files foobar | grep foobar )
+	git ls-files fo\[ou\]bar >actual &&
+	test_grep -F fo\[ou\]bar actual &&
+	git ls-files foobar >actual &&
+	test_grep ! foobar actual
 '
 
 test_expect_success 'git add to resolve conflicts on otherwise ignored path' '
@@ -326,7 +336,8 @@
 
 test_expect_success '"add non-existent" should fail' '
 	test_must_fail git add non-existent &&
-	! (git ls-files | grep "non-existent")
+	git ls-files >actual &&
+	test_grep ! "non-existent" actual
 '
 
 test_expect_success 'git add -A on empty repo does not error out' '
@@ -388,6 +399,7 @@
 	test_must_fail git add empty >actual 2>&1 &&
 	cat >expect <<-EOF &&
 	error: '"'empty/'"' does not have a commit checked out
+	error: unable to index file '"'empty/'"'
 	fatal: adding files failed
 	EOF
 	test_cmp expect actual
@@ -535,9 +547,36 @@
 		touch x y z sub/a sub/dir/b &&
 		git add -A &&
 		git add --chmod=+x . &&
-		test $(git ls-files --stage | grep ^100644 | wc -l) -eq 0 &&
+		git ls-files --stage >actual &&
+		test_grep ! ^100644 actual &&
 		git add --chmod=-x . &&
-		test $(git ls-files --stage | grep ^100755 | wc -l) -eq 0
+		git ls-files --stage >actual &&
+		test_grep ! ^100755 actual
+	)
+'
+
+test_expect_success 'cannot add a submodule of a different algorithm' '
+	git init --object-format=sha256 sha256 &&
+	(
+		cd sha256 &&
+		test_commit abc &&
+		git init --object-format=sha1 submodule &&
+		test_commit -C submodule def &&
+		test_must_fail git add submodule 2>err &&
+		test_grep "cannot add a submodule of a different hash algorithm" err &&
+		git ls-files --stage >entries &&
+		test_grep ! ^160000 entries
+	) &&
+	git init --object-format=sha1 sha1 &&
+	(
+		cd sha1 &&
+		test_commit abc &&
+		git init --object-format=sha256 submodule &&
+		test_commit -C submodule def &&
+		test_must_fail git add submodule 2>err &&
+		test_grep "cannot add a submodule of a different hash algorithm" err &&
+		git ls-files --stage >entries &&
+		test_grep ! ^160000 entries
 	)
 '
 
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 4285314..6e120a4 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -527,7 +527,7 @@
 	_10
 	+15
 	_20
-	(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_
+	(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_
 	EOF
 	test_write_lines s y g 1 | git add -p >actual &&
 	tail -n 7 <actual >actual.trimmed &&
@@ -540,7 +540,7 @@
 	_10
 	+15
 	_20
-	(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_
+	(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_
 	EOF
 	test_write_lines s y g1 | git add -p >actual &&
 	tail -n 4 <actual >actual.trimmed &&
@@ -554,7 +554,7 @@
 	_10
 	+15
 	_20
-	(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_
+	(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_
 	EOF
 	test_write_lines s y /1,2 | git add -p >actual &&
 	tail -n 5 <actual >actual.trimmed &&
@@ -567,7 +567,7 @@
 	_10
 	+15
 	_20
-	(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_
+	(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_
 	EOF
 	test_write_lines s y / 1,2 | git add -p >actual &&
 	tail -n 4 <actual >actual.trimmed &&
@@ -579,11 +579,11 @@
 	tr _ " " >expect <<-EOF &&
 	+15
 	 20
-	(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? @@ -1,2 +1,3 @@
+	(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? @@ -1,2 +1,3 @@
 	 10
 	+15
 	 20
-	(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_
+	(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_
 	EOF
 	test_write_lines s y g 1 p | git add -p >actual &&
 	tail -n 7 <actual >actual.trimmed &&
@@ -595,11 +595,11 @@
 	cat >expect <<-EOF &&
 	<GREEN>+<RESET><GREEN>15<RESET>
 	 20<RESET>
-	<BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>PAGER <CYAN>@@ -1,2 +1,3 @@<RESET>
+	<BOLD;BLUE>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>PAGER <CYAN>@@ -1,2 +1,3 @@<RESET>
 	PAGER  10<RESET>
 	PAGER <GREEN>+<RESET><GREEN>15<RESET>
 	PAGER  20<RESET>
-	<BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>
+	<BOLD;BLUE>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>
 	EOF
 	test_write_lines s y g 1 P |
 	(
@@ -810,7 +810,7 @@
 	<BOLD>-old<RESET>
 	<BLUE>+new<RESET>
 	<CYAN> more-context<RESET>
-	<YELLOW>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>
+	<YELLOW>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>
 	EOF
 	test_cmp expect actual
 '
@@ -1441,5 +1441,105 @@
 	test_grep file out &&
 	test_grep ! file2 out
 '
+for cmd in add checkout reset "stash save" "stash push"
+do
+	test_expect_success "$cmd rejects invalid --no-auto-advance options" '
+		test_must_fail git $cmd --no-auto-advance 2>actual &&
+		test_grep -E  "requires .*--(interactive|patch)" actual
+	'
+done
+
+test_expect_success 'manual advance (">") moves to next file with --no-auto-advance' '
+	git reset --hard &&
+	echo line1 >first-file &&
+	echo line2 >second-file &&
+	git add -A &&
+	git commit -m initial >/dev/null 2>&1 &&
+	echo change_first >>first-file &&
+	echo change_second >>second-file &&
+
+	printf ">\nq\n" | git add -p --no-auto-advance >output.test 2>&1 &&
+	test_grep  -E "(a|b)/second-file" output.test
+'
+
+test_expect_success 'select n on a hunk, go to another file, come back and change to y stages' '
+	git reset --hard &&
+	echo one >f1 &&
+	echo one >f2 &&
+	git add -A &&
+	git commit -m initial >/dev/null 2>&1 &&
+	echo change1 >>f1 &&
+	echo change2 >>f2 &&
+
+	printf "n\n>\n<\ny\nq\n" | git add -p --no-auto-advance >output.staged 2>&1 &&
+	git diff --cached --name-only >staged &&
+	test_grep -E "(a/f1)" output.staged
+'
+
+test_expect_success 'select y on a hunk, go to another file, come back and change to n does not stage' '
+	git reset --hard &&
+	echo one >f1 &&
+	echo one >f2 &&
+	git add -A &&
+	git commit -m initial >/dev/null 2>&1 &&
+	echo change1 >>f1 &&
+	echo change2 >>f2 &&
+
+	printf "y\n>\n<\nn\nq\n" | git add -p --no-auto-advance >output.unstaged 2>&1 &&
+	git diff --cached --name-only >staged &&
+	test_must_be_empty staged
+'
+
+test_expect_success 'deciding all hunks in a file does not auto advance' '
+	git reset --hard &&
+	echo line >stay &&
+	echo line >other &&
+	git add -A &&
+	git commit -m initial >/dev/null 2>&1 &&
+	echo change >>stay &&
+	echo change >>other &&
+	test_write_lines y | git add -p --no-auto-advance >raw-output 2>&1 &&
+	test_grep "(1/1) Stage this hunk (was: y)" raw-output &&
+	test_grep ! "diff --git a/stay b/stay" raw-output
+'
+test_expect_success 'HUNKS SUMMARY does not show in help text when there are undecided hunks' '
+	git reset --hard &&
+	test_write_lines 1 2 3 4 5 6 7 8 9 >f &&
+	git add f &&
+	git commit -m initial >/dev/null 2>&1 &&
+	test_write_lines 1 X 3 4 Y 6 7 Z 9 >f &&
+	test_write_lines s y n | git add -p --no-auto-advance >raw-nostat 2>&1 &&
+	test_grep ! "HUNKS SUMMARY - Hunks: " raw-nostat
+'
+
+test_expect_success 'help text shows HUNK SUMMARY when all hunks have been decided' '
+	git reset --hard &&
+	test_write_lines 1 2 3 4 5 6 7 8 9 >f2 &&
+	git add f2 &&
+	git commit -m initial >/dev/null 2>&1 &&
+	test_write_lines 1 X 3 4 Y 6 7 Z 9 >f2 &&
+	printf "s\ny\nn\ny\n?\n" | git add -p --no-auto-advance >raw-stat 2>&1 &&
+	test_grep "HUNKS SUMMARY - Hunks: 3, USE: 2, SKIP: 1" raw-stat
+'
+
+test_expect_success 'selective staging across multiple files with --no-advance' '
+	git reset --hard &&
+	test_write_lines 1 2 3 4 5 6 7 8 9 >a.file &&
+	test_write_lines 1 2 3 4 5 6 7 8 9 >b.file &&
+	test_write_lines 1 2 3 4 5 6 7 8 9 >c.file &&
+	git add -A &&
+	git commit -m initial >/dev/null 2>&1 &&
+	test_write_lines 1 A2 3 4 A5 6 7 8 9 >a.file &&
+	test_write_lines 1 2 B3 4 5 6 7 B8 9 >b.file &&
+	test_write_lines C1 2 3 4 5 C6 7 8 9 >c.file &&
+	printf "s\ny\nn\n>\ns\nn\ny\n>\ns\ny\ny\nq\n" | git add -p --no-auto-advance >output.index 2>&1 &&
+	git diff --cached >staged.diff &&
+	test_grep "+A2" staged.diff &&
+	test_grep ! "+A5" staged.diff &&
+	test_grep "+B8" staged.diff &&
+	test_grep ! "+B3" staged.diff &&
+	test_grep "+C1" staged.diff &&
+	test_grep "+C6" staged.diff
+'
 
 test_done
diff --git a/t/t4007-rename-3.sh b/t/t4007-rename-3.sh
index e8faf0d..1012a37 100755
--- a/t/t4007-rename-3.sh
+++ b/t/t4007-rename-3.sh
@@ -41,6 +41,16 @@
 	compare_diff_raw current expected
 '
 
+test_expect_success 'exit code of quiet copy detection' '
+	test_expect_code 1 \
+	git diff --quiet --cached --find-copies-harder $tree
+'
+
+test_expect_success 'exit code of quiet copy detection with --no-ext-diff' '
+	test_expect_code 1 \
+	git diff --quiet --cached --find-copies-harder --no-ext-diff $tree
+'
+
 # In the tree, there is only path0/COPYING.  In the cache, path0 and
 # path1 both have COPYING and the latter is a copy of path0/COPYING.
 # However when we say we care only about path1, we should just see
@@ -57,7 +67,28 @@
 '
 
 test_expect_success 'tweak work tree' '
-	rm -f path0/COPYING &&
+	rm -f path0/COPYING
+'
+
+cat >expected <<EOF
+:100644 100644 $blob $blob C100	path1/COPYING	path0/COPYING
+EOF
+
+# The cache has path0/COPYING and path1/COPYING, the working tree only
+# path1/COPYING.  This is a deletion -- we don't treat deduplication
+# specially.  In reverse it should be detected as a copy, though.
+test_expect_success 'copy detection, files to index' '
+	git diff-files -C --find-copies-harder -R >current &&
+	compare_diff_raw current expected
+'
+
+test_expect_success 'copy detection, files to preloaded index' '
+	GIT_TEST_PRELOAD_INDEX=1 \
+	git diff-files -C --find-copies-harder -R >current &&
+	compare_diff_raw current expected
+'
+
+test_expect_success 'tweak index' '
 	git update-index --remove path0/COPYING
 '
 # In the tree, there is only path0/COPYING.  In the cache, path0 does
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 2782b1f..bcdb944 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -980,7 +980,7 @@
 
 test_expect_success 'get git version' '
 	git_version=$(git --version) &&
-	git_version=${git_version##* }
+	git_version=${git_version#git version }
 '
 
 signature() {
@@ -1472,6 +1472,14 @@
 	test_cmp expect patch.head
 '
 
+test_expect_success '--from applies to cover letter' '
+	test_when_finished "rm -rf patches" &&
+	git format-patch -1 --cover-letter --from="Foo Bar <author@example.com>" -o patches &&
+	echo "From: Foo Bar <author@example.com>" >expect &&
+	grep "^From:" patches/0000-cover-letter.patch >patch.head &&
+	test_cmp expect patch.head
+'
+
 test_expect_success '--from omits redundant in-body header' '
 	git format-patch -1 --stdout --from="A U Thor <author@example.com>" >patch &&
 	cat >expect <<-\EOF &&
@@ -2541,10 +2549,26 @@
 	grep "^--- blorp" actual
 '
 
+test_expect_success 'format.noprefix=false' '
+	git -c format.noprefix=false format-patch -1 --stdout >actual &&
+	grep "^--- a/blorp" actual
+'
+
 test_expect_success 'format-patch --default-prefix overrides format.noprefix' '
 	git -c format.noprefix \
 		format-patch -1 --default-prefix --stdout >actual &&
 	grep "^--- a/blorp" actual
 '
 
+test_expect_success 'errors on format.noprefix which is not boolean' '
+	cat >expect <<-EOF &&
+	fatal: bad boolean config value ${SQ}not-a-bool${SQ} for ${SQ}format.noprefix${SQ}
+	hint: ${SQ}format.noprefix${SQ} used to accept any value and treat that as ${SQ}true${SQ}.
+	hint: Now it only accepts boolean values, like what ${SQ}diff.noprefix${SQ} does.
+	EOF
+	test_must_fail git -c format.noprefix=not-a-bool \
+		format-patch -1 --stdout 2>actual &&
+	test_cmp expect actual
+'
+
 test_done
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index 9de7f73..b691d29 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -43,6 +43,79 @@
 	'
 done
 
+test_expect_success "incomplete line in both pre- and post-image context" '
+	(echo foo && echo baz | tr -d "\012") >x &&
+	git add x &&
+	(echo bar && echo baz | tr -d "\012") >x &&
+	git diff x &&
+	git -c core.whitespace=incomplete diff --check x &&
+	git diff -R x &&
+	git -c core.whitespace=incomplete diff -R --check x
+'
+
+test_expect_success "incomplete lines on both pre- and post-image" '
+	# The interpretation taken here is "since you are touching
+	# the line anyway, you would better fix the incomplete line
+	# while you are at it."  but this is debatable.
+	echo foo | tr -d "\012" >x &&
+	git add x &&
+	echo bar | tr -d "\012" >x &&
+	git diff x &&
+	test_must_fail git -c core.whitespace=incomplete diff --check x >error &&
+	test_grep "no newline at the end of file" error &&
+	git diff -R x &&
+	test_must_fail git -c core.whitespace=incomplete diff -R --check x >error &&
+	test_grep "no newline at the end of file" error
+'
+
+test_expect_success "fix incomplete line in pre-image" '
+	echo foo | tr -d "\012" >x &&
+	git add x &&
+	echo bar >x &&
+	git diff x &&
+	git -c core.whitespace=incomplete diff --check x &&
+	git diff -R x &&
+	test_must_fail git -c core.whitespace=incomplete diff -R --check x >error &&
+	test_grep "no newline at the end of file" error
+'
+
+test_expect_success "new incomplete line in post-image" '
+	echo foo >x &&
+	git add x &&
+	echo bar | tr -d "\012" >x &&
+	git diff x &&
+	test_must_fail git -c core.whitespace=incomplete diff --check x >error &&
+	test_grep "no newline at the end of file" error &&
+	git diff -R x &&
+	git -c core.whitespace=incomplete diff -R --check x
+'
+
+test_expect_success SYMLINKS "incomplete-line error is disabled for symlinks" '
+	test_when_finished "git reset --hard" &&
+	test_when_finished "rm -f mylink" &&
+
+	# a regular file with an incomplete line
+	printf "%s" one >mylink &&
+	git add mylink &&
+
+	# a symbolic link
+	rm mylink &&
+	ln -s two mylink &&
+
+	git -c diff.color=always -c core.whitespace=incomplete \
+		diff mylink >forward.raw &&
+	test_decode_color >forward <forward.raw &&
+	test_grep ! "<BRED>\\\\ No newline at end of file<RESET>" forward &&
+
+	git -c diff.color=always -c core.whitespace=incomplete \
+		diff -R mylink >reverse.raw &&
+	test_decode_color >reverse <reverse.raw &&
+	test_grep "<BRED>\\\\ No newline at end of file<RESET>" reverse &&
+
+	git -c core.whitespace=incomplete diff --check mylink &&
+	test_must_fail git -c core.whitespace=incomplete diff --check -R mylink
+'
+
 test_expect_success "Ray Lehtiniemi's example" '
 	cat <<-\EOF >x &&
 	do {
@@ -1040,7 +1113,8 @@
 	{
 		echo "0. blank-at-eol " &&
 		echo "1. still-blank-at-eol " &&
-		echo "2. and a new line "
+		echo "2. and a new line " &&
+		printf "3. and more"
 	} >x &&
 	new_hash_x=$(git hash-object x) &&
 	after=$(git rev-parse --short "$new_hash_x") &&
@@ -1050,11 +1124,13 @@
 	<BOLD>index $before..$after 100644<RESET>
 	<BOLD>--- a/x<RESET>
 	<BOLD>+++ b/x<RESET>
-	<CYAN>@@ -1,2 +1,3 @@<RESET>
+	<CYAN>@@ -1,2 +1,4 @@<RESET>
 	 0. blank-at-eol <RESET>
 	<RED>-<RESET><RED>1. blank-at-eol<RESET><BLUE> <RESET>
 	<GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>
 	<GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>
+	<GREEN>+<RESET><GREEN>3. and more<RESET>
+	<BLUE>\ No newline at end of file<RESET>
 	EOF
 
 	cat >expect.all <<-EOF &&
@@ -1062,11 +1138,13 @@
 	<BOLD>index $before..$after 100644<RESET>
 	<BOLD>--- a/x<RESET>
 	<BOLD>+++ b/x<RESET>
-	<CYAN>@@ -1,2 +1,3 @@<RESET>
+	<CYAN>@@ -1,2 +1,4 @@<RESET>
 	 <RESET>0. blank-at-eol<RESET><BLUE> <RESET>
 	<RED>-<RESET><RED>1. blank-at-eol<RESET><BLUE> <RESET>
 	<GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>
 	<GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>
+	<GREEN>+<RESET><GREEN>3. and more<RESET>
+	<BLUE>\ No newline at end of file<RESET>
 	EOF
 
 	cat >expect.none <<-EOF
@@ -1074,16 +1152,19 @@
 	<BOLD>index $before..$after 100644<RESET>
 	<BOLD>--- a/x<RESET>
 	<BOLD>+++ b/x<RESET>
-	<CYAN>@@ -1,2 +1,3 @@<RESET>
+	<CYAN>@@ -1,2 +1,4 @@<RESET>
 	 0. blank-at-eol <RESET>
 	<RED>-1. blank-at-eol <RESET>
 	<GREEN>+1. still-blank-at-eol <RESET>
 	<GREEN>+2. and a new line <RESET>
+	<GREEN>+3. and more<RESET>
+	\ No newline at end of file<RESET>
 	EOF
 
 '
 
 test_expect_success 'test --ws-error-highlight option' '
+	git config core.whitespace blank-at-eol,incomplete-line &&
 
 	git diff --color --ws-error-highlight=default,old >current.raw &&
 	test_decode_color <current.raw >current &&
@@ -1100,6 +1181,7 @@
 '
 
 test_expect_success 'test diff.wsErrorHighlight config' '
+	git config core.whitespace blank-at-eol,incomplete-line &&
 
 	git -c diff.wsErrorHighlight=default,old diff --color >current.raw &&
 	test_decode_color <current.raw >current &&
@@ -1116,6 +1198,7 @@
 '
 
 test_expect_success 'option overrides diff.wsErrorHighlight' '
+	git config core.whitespace blank-at-eol,incomplete-line &&
 
 	git -c diff.wsErrorHighlight=none \
 		diff --color --ws-error-highlight=default,old >current.raw &&
@@ -1135,6 +1218,8 @@
 '
 
 test_expect_success 'detect moved code, complete file' '
+	git config core.whitespace blank-at-eol &&
+
 	git reset --hard &&
 	cat <<-\EOF >test.c &&
 	#include<stdio.h>
diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh
index 4d4aa16..4dd4954 100755
--- a/t/t4041-diff-submodule-option.sh
+++ b/t/t4041-diff-submodule-option.sh
@@ -37,8 +37,12 @@
 			test_tick &&
 			# "git commit -m" would break MinGW, as Windows refuse to pass
 			# $test_encoding encoded parameter to git.
-			echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding |
-			git -c "i18n.commitEncoding=$test_encoding" commit -F -
+			message="Add $name ($added $name)" &&
+			if test_have_prereq ICONV
+			then
+				message=$(echo "$message" | iconv -f utf-8 -t $test_encoding)
+			fi &&
+			echo "$message" | git -c "i18n.commitEncoding=$test_encoding" commit -F -
 		done >/dev/null &&
 		git rev-parse --short --verify HEAD
 	)
diff --git a/t/t4052-stat-output.sh b/t/t4052-stat-output.sh
index 740bb97..7c74906 100755
--- a/t/t4052-stat-output.sh
+++ b/t/t4052-stat-output.sh
@@ -413,4 +413,36 @@
 	test_cmp expect actual
 '
 
+# We want git-log to print only 1 commit containing a single branch graph and a
+# diffstat (the diffstat display width, when not manually set through the
+# option "--stat-width", will be automatically calculated).
+# The diffstat will be only one file, with a placeholder FILENAME, that, with
+# enough terminal display width, will contain the following line:
+#     "<RED>|<RESET>  ${FILENAME} | 0"
+# where "<RED>" and "<RESET>" are ANSI escape codes to color the text.
+# To calculate the minimium terminal display width MIN_TERM_WIDTH so that the
+# FILENAME in the diffstat will not be shortened, we take the FILENAME length
+# and add 9 to it.
+# To check if the diffstat width, when the line_prefix (the "<RED>|<RESET>" of
+# the graph) contains ANSI escape codes (the ANSI escape codes to color the
+# text), is calculated correctly, we:
+#     1. check if it contains the line defined before when using MIN_TERM_WIDTH
+#     2. check if it contains the line defined before, but with the FILENAME
+#        shortened by only one character, when using MIN_TERM_WIDTH - 1
+
+test_expect_success 'diffstat where line_prefix contains ANSI escape codes is correct width' '
+	FILENAME="placeholder-text-placeholder-text" &&
+	FILENAME_TRIMMED="...eholder-text-placeholder-text" &&
+	MIN_TERM_WIDTH=$((${#FILENAME} + 9)) &&
+	test_config color.diff always &&
+	git commit --allow-empty --allow-empty-message &&
+	>${FILENAME} &&
+	git add ${FILENAME} &&
+	git commit --allow-empty-message &&
+	COLUMNS=$((MIN_TERM_WIDTH)) git log --graph --stat -n1 | test_decode_color >out &&
+	test_grep "<RED>|<RESET>  ${FILENAME} | 0" out &&
+	COLUMNS=$((MIN_TERM_WIDTH - 1)) git log --graph --stat -n1 | test_decode_color >out &&
+	test_grep "<RED>|<RESET>  ${FILENAME_TRIMMED} | 0" out
+'
+
 test_done
diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh
index 6959927..15076df 100755
--- a/t/t4053-diff-no-index.sh
+++ b/t/t4053-diff-no-index.sh
@@ -76,6 +76,16 @@
 	)
 '
 
+test_expect_success 'git diff --find-object outside repo fails gracefully' '
+	(
+		GIT_CEILING_DIRECTORIES=$TRASH_DIRECTORY/non &&
+		export GIT_CEILING_DIRECTORIES &&
+		cd non/git &&
+		test_must_fail git diff --find-object=abc123 2>err &&
+		test_grep "find-object requires a git repository" err
+	)
+'
+
 test_expect_success 'diff D F and diff F D' '
 	(
 		cd repo &&
diff --git a/t/t4059-diff-submodule-not-initialized.sh b/t/t4059-diff-submodule-not-initialized.sh
index 0fe8105..bb902ce 100755
--- a/t/t4059-diff-submodule-not-initialized.sh
+++ b/t/t4059-diff-submodule-not-initialized.sh
@@ -35,8 +35,12 @@
 			test_tick &&
 			# "git commit -m" would break MinGW, as Windows refuse to pass
 			# $test_encoding encoded parameter to git.
-			echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding |
-			git -c "i18n.commitEncoding=$test_encoding" commit -F -
+			message="Add $name ($added $name)" &&
+			if test_have_prereq ICONV
+			then
+				message=$(echo "$message" | iconv -f utf-8 -t $test_encoding)
+			fi &&
+			echo "$message" | git -c "i18n.commitEncoding=$test_encoding" commit -F -
 		done >/dev/null &&
 		git rev-parse --short --verify HEAD
 	)
diff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh
index dbfeb74..d8f9213 100755
--- a/t/t4060-diff-submodule-option-diff-format.sh
+++ b/t/t4060-diff-submodule-option-diff-format.sh
@@ -35,8 +35,12 @@
 			test_tick &&
 			# "git commit -m" would break MinGW, as Windows refuse to pass
 			# $test_encoding encoded parameter to git.
-			echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding |
-			git -c "i18n.commitEncoding=$test_encoding" commit -F -
+			message="Add $name ($added $name)" &&
+			if test_have_prereq ICONV
+			then
+				message=$(echo "$message" | iconv -f utf-8 -t $test_encoding)
+			fi &&
+			echo "$message" | git -c "i18n.commitEncoding=$test_encoding" commit -F -
 		done >/dev/null &&
 		git rev-parse --short --verify HEAD
 	)
diff --git a/t/t4067-diff-partial-clone.sh b/t/t4067-diff-partial-clone.sh
index 581250d..3081310 100755
--- a/t/t4067-diff-partial-clone.sh
+++ b/t/t4067-diff-partial-clone.sh
@@ -132,6 +132,72 @@
 	test_line_count = 1 done_lines
 '
 
+test_expect_success 'diff succeeds even if prefetch triggered by break-rewrites' '
+	test_when_finished "rm -rf server client trace" &&
+
+	test_create_repo server &&
+	echo xyz >server/foo &&
+	mkdir server/bar &&
+	test_seq -f "line %d" 1 100 >server/bar/baz &&
+	git -C server add -A &&
+	git -C server commit -m x &&
+
+	echo xyzz >server/foo &&
+	test_seq -f "line %d" 90 190 >server/bar/baz &&
+	git -C server add -A &&
+	git -C server commit -m x &&
+
+	test_config -C server uploadpack.allowfilter 1 &&
+	test_config -C server uploadpack.allowanysha1inwant 1 &&
+	git clone --filter=blob:limit=0 "file://$(pwd)/server" client &&
+
+	# Fetch bar/baz without fetching foo.
+	# Foo will be lazily fetched during break rewrites detection.
+	git -C client checkout HEAD~1 bar &&
+
+	# Ensure baz in the working tree is different from baz in HEAD~1.
+	# We need baz to trigger break-rewrites detection.
+	git -C client reset --hard HEAD &&
+
+	# break-rewrites detction in reset.
+	git -C client reset HEAD~1
+'
+
+test_expect_success 'diff succeeds even if entries are removed from queue' '
+	test_when_finished "rm -rf server client trace" &&
+
+	test_create_repo server &&
+	for l in a c e g i p
+	do
+		echo $l >server/$l &&
+		git -C server add $l || return 1
+	done &&
+	git -C server commit -m x &&
+
+	for l in a e i
+	do
+		git -C server rm $l || return 1
+	done &&
+
+	for l in b d f i
+		do
+		echo $l$l >server/$l &&
+		git -C server add $l || return 1
+	done &&
+	git -C server commit -a -m x &&
+
+	test_config -C server uploadpack.allowfilter 1 &&
+	test_config -C server uploadpack.allowanysha1inwant 1 &&
+	git clone --filter=blob:limit=0 "file://$(pwd)/server" client &&
+
+	for file in $(ls client)
+	do
+		cat client/$file >$file &&
+		mv $file client/$file || return 1
+	done &&
+	git -C client diff --name-only --relative HEAD^
+'
+
 test_expect_success 'diff does not fetch anything if inexact rename detection is not needed' '
 	test_when_finished "rm -rf server client trace" &&
 
diff --git a/t/t4073-diff-stat-name-width.sh b/t/t4073-diff-stat-name-width.sh
new file mode 100755
index 0000000..ec5d3c3
--- /dev/null
+++ b/t/t4073-diff-stat-name-width.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+test_description='git-diff check diffstat filepaths length when containing UTF-8 chars'
+
+. ./test-lib.sh
+
+
+create_files () {
+	mkdir -p "d你好" &&
+	touch "d你好/f再见"
+}
+
+test_expect_success 'setup' '
+	git init &&
+	git config core.quotepath off &&
+	git commit -m "Initial commit" --allow-empty &&
+	create_files &&
+	git add . &&
+	git commit -m "Added files"
+'
+
+test_expect_success 'test name-width long enough for filepath' '
+	git diff HEAD~1 HEAD --stat --stat-name-width=12 >out &&
+	grep "d你好/f再见 |" out &&
+	git diff HEAD~1 HEAD --stat --stat-name-width=11 >out &&
+	grep "d你好/f再见 |" out
+'
+
+test_expect_success 'test name-width not long enough for dir name' '
+	git diff HEAD~1 HEAD --stat --stat-name-width=10 >out &&
+	grep ".../f再见  |" out &&
+	git diff HEAD~1 HEAD --stat --stat-name-width=9 >out &&
+	grep ".../f再见 |" out
+'
+
+test_expect_success 'test name-width not long enough for slash' '
+	git diff HEAD~1 HEAD --stat --stat-name-width=8 >out &&
+	grep "...f再见 |" out
+'
+
+test_expect_success 'test name-width not long enough for file name' '
+	git diff HEAD~1 HEAD --stat --stat-name-width=7 >out &&
+	grep "...再见 |" out &&
+	git diff HEAD~1 HEAD --stat --stat-name-width=6 >out &&
+	grep "...见  |" out &&
+	git diff HEAD~1 HEAD --stat --stat-name-width=5 >out &&
+	grep "...见 |" out &&
+	git diff HEAD~1 HEAD --stat --stat-name-width=4 >out &&
+	grep "...  |" out
+'
+
+test_expect_success 'test name-width minimum length' '
+	git diff HEAD~1 HEAD --stat --stat-name-width=3 >out &&
+	grep "... |" out &&
+	git diff HEAD~1 HEAD --stat --stat-name-width=2 >out &&
+	grep "... |" out &&
+	git diff HEAD~1 HEAD --stat --stat-name-width=1 >out &&
+	grep "... |" out
+'
+
+test_done
diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh
index 485c7d2..29ea7d4 100755
--- a/t/t4124-apply-ws-rule.sh
+++ b/t/t4124-apply-ws-rule.sh
@@ -556,4 +556,277 @@
 	git apply --include=used --stat --whitespace=error <patch
 '
 
+test_expect_success 'check incomplete lines (setup)' '
+	rm -f .gitattributes &&
+	git config core.whitespace incomplete-line
+'
+
+test_expect_success 'incomplete context line (not an error)' '
+	(test_write_lines 1 2 3 4 5 && printf 6) >sample-i &&
+	(test_write_lines 1 2 3 0 5 && printf 6) >sample2-i &&
+	cat sample-i >target &&
+	git add target &&
+	cat sample2-i >target &&
+	git diff-files -p target >patch &&
+
+	cat sample-i >target &&
+	git apply --whitespace=error <patch &&
+	test_cmp sample2-i target &&
+
+	cat sample-i >target &&
+	git apply --whitespace=error --check <patch 2>error &&
+	test_cmp sample-i target &&
+	test_must_be_empty error &&
+
+	cat sample2-i >target &&
+	git apply --whitespace=error -R <patch &&
+	test_cmp sample-i target &&
+
+	cat sample2-i >target &&
+	git apply -R --whitespace=error --check <patch 2>error &&
+	test_cmp sample2-i target &&
+	test_must_be_empty error
+'
+
+test_expect_success 'last line made incomplete (error)' '
+	test_write_lines 1 2 3 4 5 6 >sample &&
+	(test_write_lines 1 2 3 4 5 && printf 6) >sample-i &&
+	cat sample >target &&
+	git add target &&
+	cat sample-i >target &&
+	git diff-files -p target >patch &&
+
+	cat sample >target &&
+	test_must_fail git apply --whitespace=error <patch 2>error &&
+	test_grep "no newline" error &&
+
+	cat sample >target &&
+	test_must_fail git apply --whitespace=error --check <patch 2>actual &&
+	test_cmp sample target &&
+	cat >expect <<-\EOF &&
+	<stdin>:10: no newline at the end of file.
+	6
+	error: 1 line adds whitespace errors.
+	EOF
+	test_cmp expect actual &&
+
+	cat sample-i >target &&
+	git apply --whitespace=error -R <patch &&
+	test_cmp sample target &&
+
+	cat sample-i >target &&
+	git apply --whitespace=error --check -R <patch 2>error &&
+	test_cmp sample-i target &&
+	test_must_be_empty error &&
+
+	cat sample >target &&
+	git apply --whitespace=fix <patch &&
+	test_cmp sample target
+'
+
+test_expect_success 'incomplete line removed at the end (not an error)' '
+	(test_write_lines 1 2 3 4 5 && printf 6) >sample-i &&
+	test_write_lines 1 2 3 4 5 6 >sample &&
+	cat sample-i >target &&
+	git add target &&
+	cat sample >target &&
+	git diff-files -p target >patch &&
+
+	cat sample-i >target &&
+	git apply --whitespace=error <patch &&
+	test_cmp sample target &&
+
+	cat sample-i >target &&
+	git apply --whitespace=error --check <patch 2>error &&
+	test_cmp sample-i target &&
+	test_must_be_empty error &&
+
+	cat sample >target &&
+	test_must_fail git apply --whitespace=error -R <patch 2>error &&
+	test_grep "no newline" error &&
+
+	cat sample >target &&
+	test_must_fail git apply --whitespace=error --check -R <patch 2>actual &&
+	test_cmp sample target &&
+	cat >expect <<-\EOF &&
+	<stdin>:9: no newline at the end of file.
+	6
+	error: 1 line adds whitespace errors.
+	EOF
+	test_cmp expect actual &&
+
+	cat sample >target &&
+	git apply --whitespace=fix -R <patch &&
+	test_cmp sample target
+'
+
+test_expect_success 'incomplete line corrected at the end (not an error)' '
+	(test_write_lines 1 2 3 4 5 && printf 6) >sample-i &&
+	test_write_lines 1 2 3 4 5 7 >sample3 &&
+	cat sample-i >target &&
+	git add target &&
+	cat sample3 >target &&
+	git diff-files -p target >patch &&
+
+	cat sample-i >target &&
+	git apply --whitespace=error <patch &&
+	test_cmp sample3 target &&
+
+	cat sample-i >target &&
+	git apply --whitespace=error --check <patch 2>error &&
+	test_cmp sample-i target &&
+	test_must_be_empty error &&
+
+	cat sample3 >target &&
+	test_must_fail git apply --whitespace=error -R <patch 2>error &&
+	test_grep "no newline" error &&
+
+	cat sample3 >target &&
+	test_must_fail git apply --whitespace=error -R --check <patch 2>actual &&
+	test_cmp sample3 target &&
+	cat >expect <<-\EOF &&
+	<stdin>:9: no newline at the end of file.
+	6
+	error: 1 line adds whitespace errors.
+	EOF
+	test_cmp expect actual &&
+
+	cat sample3 >target &&
+	git apply --whitespace=fix -R <patch &&
+	test_cmp sample target
+'
+
+test_expect_success 'incomplete line modified at the end (error)' '
+	(test_write_lines 1 2 3 4 5 && printf 6) >sample-i &&
+	(test_write_lines 1 2 3 4 5 && printf 7) >sample3-i &&
+	test_write_lines 1 2 3 4 5 6 >sample &&
+	test_write_lines 1 2 3 4 5 7 >sample3 &&
+	cat sample-i >target &&
+	git add target &&
+	cat sample3-i >target &&
+	git diff-files -p target >patch &&
+
+	cat sample-i >target &&
+	test_must_fail git apply --whitespace=error <patch 2>error &&
+	test_grep "no newline" error &&
+
+	cat sample-i >target &&
+	test_must_fail git apply --whitespace=error --check <patch 2>actual &&
+	test_cmp sample-i target &&
+	cat >expect <<-\EOF &&
+	<stdin>:11: no newline at the end of file.
+	7
+	error: 1 line adds whitespace errors.
+	EOF
+	test_cmp expect actual &&
+
+	cat sample3-i >target &&
+	test_must_fail git apply --whitespace=error -R <patch 2>error &&
+	test_grep "no newline" error &&
+
+	cat sample3-i >target &&
+	test_must_fail git apply --whitespace=error --check -R <patch 2>actual &&
+	test_cmp sample3-i target &&
+	cat >expect <<-\EOF &&
+	<stdin>:9: no newline at the end of file.
+	6
+	error: 1 line adds whitespace errors.
+	EOF
+	test_cmp expect actual &&
+
+	cat sample-i >target &&
+	git apply --whitespace=fix <patch &&
+	test_cmp sample3 target &&
+
+	cat sample3-i >target &&
+	git apply --whitespace=fix -R <patch &&
+	test_cmp sample target
+'
+
+test_expect_success "incomplete-line error is disabled for symlinks" '
+	test_when_finished "git reset" &&
+	test_when_finished "rm -f patch.txt" &&
+	oneblob=$(printf "one" | git hash-object --stdin -w -t blob) &&
+	twoblob=$(printf "two" | git hash-object --stdin -w -t blob) &&
+
+	oneshort=$(git rev-parse --short $oneblob) &&
+	twoshort=$(git rev-parse --short $twoblob) &&
+
+	cat >patch0.txt <<-EOF &&
+	diff --git a/mylink b/mylink
+	index $oneshort..$twoshort 120000
+	--- a/mylink
+	+++ b/mylink
+	@@ -1 +1 @@
+	-one
+	\ No newline at end of file
+	+two
+	\ No newline at end of file
+	EOF
+
+	# the index has the preimage symlink
+	git update-index --add --cacheinfo "120000,$oneblob,mylink" &&
+
+	# check the patch going forward and reverse
+	git -c core.whitespace=incomplete apply --cached --check \
+		--whitespace=error patch0.txt &&
+
+	git update-index --add --cacheinfo "120000,$twoblob,mylink" &&
+	git -c core.whitespace=incomplete apply --cached --check \
+		--whitespace=error -R patch0.txt &&
+
+	# the patch turns it into the postimage symlink
+	git update-index --add --cacheinfo "120000,$oneblob,mylink" &&
+	git -c core.whitespace=incomplete apply --cached --whitespace=error \
+		patch0.txt &&
+
+	# and then back.
+	git -c core.whitespace=incomplete apply --cached -R --whitespace=error \
+		patch0.txt &&
+
+	# a text file turns into a symlink
+	cat >patch1.txt <<-EOF &&
+	diff --git a/mylink b/mylink
+	deleted file mode 100644
+	index $oneshort..0000000
+	--- a/mylink
+	+++ /dev/null
+	@@ -1 +0,0 @@
+	-one
+	\ No newline at end of file
+	diff --git a/mylink b/mylink
+	new file mode 120000
+	index 0000000..$twoshort
+	--- /dev/null
+	+++ b/mylink
+	@@ -0,0 +1 @@
+	+two
+	\ No newline at end of file
+	EOF
+
+	# the index has the preimage text
+	git update-index --cacheinfo "100644,$oneblob,mylink" &&
+
+	# check
+	git -c core.whitespace=incomplete apply --cached \
+		--check --whitespace=error patch1.txt &&
+
+	# reverse, leaving an incomplete text file, should error
+	git update-index --cacheinfo "120000,$twoblob,mylink" &&
+	test_must_fail git -c core.whitespace=incomplete \
+		apply --cached --check --whitespace=error -R patch1.txt &&
+
+	# apply to create a symbolic link
+	git update-index --cacheinfo "100644,$oneblob,mylink" &&
+	git -c core.whitespace=incomplete apply --cached --whitespace=error \
+		patch1.txt &&
+
+	# turning it back into an incomplete text file is an error
+	test_must_fail git -c core.whitespace=incomplete \
+		apply --cached --whitespace=error -R patch1.txt
+
+
+
+'
+
 test_done
diff --git a/t/t4128-apply-root.sh b/t/t4128-apply-root.sh
index f6db5a7..5eba15f 100755
--- a/t/t4128-apply-root.sh
+++ b/t/t4128-apply-root.sh
@@ -43,6 +43,47 @@
 
 '
 
+test_expect_success 'apply --directory (./ prefix)' '
+	git reset --hard initial &&
+	git apply --directory=./some/sub -p3 --index patch &&
+	echo Bello >expect &&
+	git show :some/sub/dir/file >actual &&
+	test_cmp expect actual &&
+	test_cmp expect some/sub/dir/file
+'
+
+test_expect_success 'apply --directory (double slash)' '
+	git reset --hard initial &&
+	git apply --directory=some//sub -p3 --index patch &&
+	echo Bello >expect &&
+	git show :some/sub/dir/file >actual &&
+	test_cmp expect actual &&
+	test_cmp expect some/sub/dir/file
+'
+
+test_expect_success 'apply --directory (./ in the middle)' '
+	git reset --hard initial &&
+	git apply --directory=some/./sub -p3 --index patch &&
+	echo Bello >expect &&
+	git show :some/sub/dir/file >actual &&
+	test_cmp expect actual &&
+	test_cmp expect some/sub/dir/file
+'
+
+test_expect_success 'apply --directory (../ in the middle)' '
+	git reset --hard initial &&
+	git apply --directory=some/../some/sub -p3 --index patch &&
+	echo Bello >expect &&
+	git show :some/sub/dir/file >actual &&
+	test_cmp expect actual &&
+	test_cmp expect some/sub/dir/file
+'
+
+test_expect_success 'apply --directory rejects leading ../' '
+	test_must_fail git apply --directory=../foo -p3 patch 2>err &&
+	test_grep "unable to normalize directory" err
+'
+
 cat > patch << EOF
 diff --git a/newfile b/newfile
 new file mode 100644
diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh
index 8f2ba98..3865f6a 100755
--- a/t/t4205-log-pretty-formats.sh
+++ b/t/t4205-log-pretty-formats.sh
@@ -9,7 +9,12 @@
 . ./test-lib.sh
 
 # Tested non-UTF-8 encoding
-test_encoding="ISO8859-1"
+if test_have_prereq ICONV
+then
+	test_encoding="ISO8859-1"
+else
+	test_encoding="UTF-8"
+fi
 
 sample_utf8_part=$(printf "f\303\244ng")
 
@@ -18,7 +23,7 @@
 	# (translated with Google Translate),
 	# encoded in UTF-8, used as a commit log message below.
 	msg="initial. an${sample_utf8_part}lich\n"
-	if test -n "$1"
+	if test -n "$1" && test "$1" != "UTF-8"
 	then
 		printf "$msg" | iconv -f utf-8 -t "$1"
 	else
@@ -113,19 +118,19 @@
 	test_must_fail git log --pretty=test-foo
 '
 
-test_expect_success ICONV 'NUL separation' '
+test_expect_success 'NUL separation' '
 	printf "add bar\0$(commit_msg)" >expected &&
 	git log -z --pretty="format:%s" >actual &&
 	test_cmp expected actual
 '
 
-test_expect_success ICONV 'NUL termination' '
+test_expect_success 'NUL termination' '
 	printf "add bar\0$(commit_msg)\0" >expected &&
 	git log -z --pretty="tformat:%s" >actual &&
 	test_cmp expected actual
 '
 
-test_expect_success ICONV 'NUL separation with --stat' '
+test_expect_success 'NUL separation with --stat' '
 	stat0_part=$(git diff --stat HEAD^ HEAD) &&
 	stat1_part=$(git diff-tree --no-commit-id --stat --root HEAD^) &&
 	printf "add bar\n$stat0_part\n\0$(commit_msg)\n$stat1_part\n" >expected &&
@@ -180,7 +185,7 @@
 	head4=$(git rev-parse --verify --short HEAD~3)
 '
 
-test_expect_success ICONV 'left alignment formatting' '
+test_expect_success 'left alignment formatting' '
 	git log --pretty="tformat:%<(40)%s" >actual &&
 	qz_to_tab_space <<-EOF >expected &&
 	message two                            Z
@@ -202,7 +207,7 @@
 	test_cmp expected actual
 '
 
-test_expect_success ICONV 'left alignment formatting at the nth column' '
+test_expect_success 'left alignment formatting at the nth column' '
 	git log --pretty="tformat:%h %<|(40)%s" >actual &&
 	qz_to_tab_space <<-EOF >expected &&
 	$head1 message two                    Z
@@ -213,7 +218,7 @@
 	test_cmp expected actual
 '
 
-test_expect_success ICONV 'left alignment formatting at the nth column' '
+test_expect_success 'left alignment formatting at the nth column' '
 	COLUMNS=50 git log --pretty="tformat:%h %<|(-10)%s" >actual &&
 	qz_to_tab_space <<-EOF >expected &&
 	$head1 message two                    Z
@@ -235,7 +240,7 @@
 	test_cmp expected actual
 '
 
-test_expect_success ICONV 'left alignment formatting with no padding' '
+test_expect_success 'left alignment formatting with no padding' '
 	git log --pretty="tformat:%<(1)%s" >actual &&
 	cat <<-EOF >expected &&
 	message two
@@ -246,7 +251,7 @@
 	test_cmp expected actual
 '
 
-test_expect_success 'left alignment formatting with no padding. i18n.logOutputEncoding' '
+test_expect_success ICONV 'left alignment formatting with no padding. i18n.logOutputEncoding' '
 	git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%<(1)%s" >actual &&
 	cat <<-EOF | iconv -f utf-8 -t $test_encoding >expected &&
 	message two
@@ -257,7 +262,7 @@
 	test_cmp expected actual
 '
 
-test_expect_success ICONV 'left alignment formatting with trunc' '
+test_expect_success 'left alignment formatting with trunc' '
 	git log --pretty="tformat:%<(10,trunc)%s" >actual &&
 	qz_to_tab_space <<-\EOF >expected &&
 	message ..
@@ -279,7 +284,7 @@
 	test_cmp expected actual
 '
 
-test_expect_success ICONV 'left alignment formatting with ltrunc' '
+test_expect_success 'left alignment formatting with ltrunc' '
 	git log --pretty="tformat:%<(10,ltrunc)%s" >actual &&
 	qz_to_tab_space <<-EOF >expected &&
 	..sage two
@@ -301,7 +306,7 @@
 	test_cmp expected actual
 '
 
-test_expect_success ICONV 'left alignment formatting with mtrunc' '
+test_expect_success 'left alignment formatting with mtrunc' '
 	git log --pretty="tformat:%<(10,mtrunc)%s" >actual &&
 	qz_to_tab_space <<-\EOF >expected &&
 	mess.. two
@@ -323,7 +328,7 @@
 	test_cmp expected actual
 '
 
-test_expect_success ICONV 'right alignment formatting' '
+test_expect_success 'right alignment formatting' '
 	git log --pretty="tformat:%>(40)%s" >actual &&
 	qz_to_tab_space <<-EOF >expected &&
 	Z                            message two
@@ -345,7 +350,7 @@
 	test_cmp expected actual
 '
 
-test_expect_success ICONV 'right alignment formatting at the nth column' '
+test_expect_success 'right alignment formatting at the nth column' '
 	git log --pretty="tformat:%h %>|(40)%s" >actual &&
 	qz_to_tab_space <<-EOF >expected &&
 	$head1                      message two
@@ -356,7 +361,7 @@
 	test_cmp expected actual
 '
 
-test_expect_success ICONV 'right alignment formatting at the nth column' '
+test_expect_success 'right alignment formatting at the nth column' '
 	COLUMNS=50 git log --pretty="tformat:%h %>|(-10)%s" >actual &&
 	qz_to_tab_space <<-EOF >expected &&
 	$head1                      message two
@@ -391,7 +396,7 @@
 	test_cmp expected actual
 '
 
-test_expect_success ICONV 'right alignment formatting with no padding' '
+test_expect_success 'right alignment formatting with no padding' '
 	git log --pretty="tformat:%>(1)%s" >actual &&
 	cat <<-EOF >expected &&
 	message two
@@ -402,7 +407,7 @@
 	test_cmp expected actual
 '
 
-test_expect_success ICONV 'right alignment formatting with no padding and with --graph' '
+test_expect_success 'right alignment formatting with no padding and with --graph' '
 	git log --graph --pretty="tformat:%>(1)%s" >actual &&
 	cat <<-EOF >expected &&
 	* message two
@@ -424,7 +429,7 @@
 	test_cmp expected actual
 '
 
-test_expect_success ICONV 'center alignment formatting' '
+test_expect_success 'center alignment formatting' '
 	git log --pretty="tformat:%><(40)%s" >actual &&
 	qz_to_tab_space <<-EOF >expected &&
 	Z             message two              Z
@@ -445,7 +450,8 @@
 	EOF
 	test_cmp expected actual
 '
-test_expect_success ICONV 'center alignment formatting at the nth column' '
+
+test_expect_success 'center alignment formatting at the nth column' '
 	git log --pretty="tformat:%h %><|(40)%s" >actual &&
 	qz_to_tab_space <<-EOF >expected &&
 	$head1           message two          Z
@@ -456,7 +462,7 @@
 	test_cmp expected actual
 '
 
-test_expect_success ICONV 'center alignment formatting at the nth column' '
+test_expect_success 'center alignment formatting at the nth column' '
 	COLUMNS=70 git log --pretty="tformat:%h %><|(-30)%s" >actual &&
 	qz_to_tab_space <<-EOF >expected &&
 	$head1           message two          Z
@@ -478,7 +484,7 @@
 	test_cmp expected actual
 '
 
-test_expect_success ICONV 'center alignment formatting with no padding' '
+test_expect_success 'center alignment formatting with no padding' '
 	git log --pretty="tformat:%><(1)%s" >actual &&
 	cat <<-EOF >expected &&
 	message two
diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh
index 961c6aa..c8c1c5c 100755
--- a/t/t5003-archive-zip.sh
+++ b/t/t5003-archive-zip.sh
@@ -239,6 +239,40 @@
 check_added with_untracked2 untracked one/untracked
 check_added with_untracked2 untracked two/untracked
 
+test_expect_success 'git-archive --format=zip with bigFile delta chains' '
+	test_when_finished rm -rf repo &&
+	git init repo &&
+	(
+		cd repo &&
+		test-tool genrandom foo 100000 >base &&
+		{
+			cat base &&
+			echo "trailing data"
+		} >delta-1 &&
+		{
+			cat delta-1 &&
+			echo "trailing data"
+		} >delta-2 &&
+		git add . &&
+		git commit -m "blobs" &&
+		git repack -Ad &&
+		git verify-pack -v .git/objects/pack/pack-*.idx >stats &&
+		test_grep "chain length = 1: 1 object" stats &&
+		test_grep "chain length = 2: 1 object" stats &&
+
+		git -c core.bigFileThreshold=1k archive --format=zip HEAD >archive.zip &&
+		if test_have_prereq UNZIP
+		then
+			mkdir unpack &&
+			cd unpack &&
+			"$GIT_UNZIP" ../archive.zip &&
+			test_cmp base ../base &&
+			test_cmp delta-1 ../delta-1 &&
+			test_cmp delta-2 ../delta-2
+		fi
+	)
+'
+
 # Test remote archive over HTTP protocol.
 #
 # Note: this should be the last part of this test suite, because
diff --git a/t/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh
index 027dedd..df513a4 100755
--- a/t/t5004-archive-corner-cases.sh
+++ b/t/t5004-archive-corner-cases.sh
@@ -176,8 +176,7 @@
 	blob=$(echo $s | git hash-object -w --stdin) &&
 
 	# create tree containing 65500 entries of that blob
-	test_seq -f "100644 blob $blob\t%d" 1 65500 >tree &&
-	tree=$(git mktree <tree) &&
+	tree=$(test_seq -f "100644 blob $blob\t%d" 1 65500 | git mktree) &&
 
 	# zip it, creating an archive a bit bigger than 4GB
 	git archive -0 -o many-big.zip $tree &&
diff --git a/t/t5301-sliding-window.sh b/t/t5301-sliding-window.sh
index ff6b515..3c3666b 100755
--- a/t/t5301-sliding-window.sh
+++ b/t/t5301-sliding-window.sh
@@ -12,7 +12,7 @@
 	for i in a b c
 	do
 	echo $i >$i &&
-	test-tool genrandom "$i" 32768 >>$i &&
+	test-tool genrandom "$i" 32k >>$i &&
 	git update-index --add $i || return 1
 	done &&
 	echo d >d && cat c >>d && git update-index --add d &&
diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh
index 413c992..9697448 100755
--- a/t/t5302-pack-index.sh
+++ b/t/t5302-pack-index.sh
@@ -293,4 +293,20 @@
 	grep "maximum allowed size (20 bytes)" err
 '
 
+# git-index-pack(1) uses the default hash algorithm outside of the repository,
+# and it has no way to tell it otherwise. So we can only run this test with the
+# default hash algorithm, as it would otherwise fail to parse the tree.
+test_expect_success DEFAULT_HASH_ALGORITHM 'index-pack --fsck-objects outside of a repo' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		printf "100644 blob $(test_oid 001)\t.gitattributes\n" >tree &&
+		git mktree --missing <tree >tree-oid &&
+		git pack-objects <tree-oid pack &&
+		test_must_fail nongit git index-pack --fsck-objects "$(pwd)"/pack-*.pack 2>err &&
+		test_grep "cannot perform queued object checks outside of a repository" err
+	)
+'
+
 test_done
diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh
index 6718fb9..f693cb5 100755
--- a/t/t5310-pack-bitmaps.sh
+++ b/t/t5310-pack-bitmaps.sh
@@ -242,7 +242,7 @@
 	'
 
 	test_expect_success 'splitting packs does not generate bogus bitmaps' '
-		test-tool genrandom foo $((1024 * 1024)) >rand &&
+		test-tool genrandom foo 1m >rand &&
 		git add rand &&
 		git commit -m "commit with big file" &&
 		git -c pack.packSizeLimit=500k repack -adb &&
@@ -466,6 +466,47 @@
 		)
 	'
 
+	test_expect_success 'pack.preferBitmapTips interprets patterns as hierarchy' '
+		git init repo &&
+		test_when_finished "rm -fr repo" &&
+		(
+			cd repo &&
+
+			# Create enough commits that not all will receive bitmap
+			# coverage even if they are all at the tip of some reference.
+			test_commit_bulk --message="%s" 103 &&
+			git log --format="create refs/tags/%s/tag %H" HEAD >refs &&
+			git update-ref --stdin <refs &&
+
+			# Create the bitmap.
+			git repack -adb &&
+			test-tool bitmap list-commits | sort >commits-with-bitmap &&
+
+			# Verify that we have at least one commit that did not
+			# receive a bitmap.
+			git rev-list HEAD >commits.raw &&
+			sort <commits.raw >commits &&
+			comm -13 commits-with-bitmap commits >commits-wo-bitmap &&
+			test_file_not_empty commits-wo-bitmap &&
+			commit_id=$(head commits-wo-bitmap) &&
+			ref_without_bitmap=$(git for-each-ref --points-at="$commit_id" --format="%(refname)") &&
+
+			# When passing the full refname we do not expect a
+			# bitmap to be generated, as it should be interpreted
+			# as if a slash was appended to the pattern.
+			git -c pack.preferBitmapTips="$ref_without_bitmap" repack -adb &&
+			test-tool bitmap list-commits >after &&
+			test_grep ! "$commit_id" after &&
+
+			# But if we pass the parent directory of the ref we
+			# should see a bitmap.
+			ref_namespace=$(dirname "$ref_without_bitmap") &&
+			git -c pack.preferBitmapTips="$ref_namespace" repack -adb &&
+			test-tool bitmap list-commits >after &&
+			test_grep "$commit_id" after
+		)
+	'
+
 	test_expect_success 'complains about multiple pack bitmaps' '
 		rm -fr repo &&
 		git init repo &&
diff --git a/t/t5316-pack-delta-depth.sh b/t/t5316-pack-delta-depth.sh
index 03dfb7a..8a067a4 100755
--- a/t/t5316-pack-delta-depth.sh
+++ b/t/t5316-pack-delta-depth.sh
@@ -48,6 +48,7 @@
 # repeatedly-modified file to generate the delta chain).
 
 test_expect_success 'create series of packs' '
+	test_config maintenance.auto false &&
 	test-tool genrandom foo 4096 >content &&
 	prev= &&
 	for i in $(test_seq 1 10)
diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh
index 93f319a..a7c7922 100755
--- a/t/t5319-multi-pack-index.sh
+++ b/t/t5319-multi-pack-index.sh
@@ -350,7 +350,69 @@
 		# the new MIDX
 		git multi-pack-index write --preferred-pack=pack-$pack.pack
 	)
+'
 
+test_expect_success 'preferred pack cannot be determined without bitmap' '
+	test_when_finished "rm -fr preferred-can-be-queried" &&
+	git init preferred-can-be-queried &&
+	(
+		cd preferred-can-be-queried &&
+		test_commit initial &&
+		git repack -Adl --write-midx --no-write-bitmap-index &&
+		test_must_fail test-tool read-midx --preferred-pack .git/objects 2>err &&
+		test_grep "could not determine MIDX preferred pack" err &&
+		git repack -Adl --write-midx --write-bitmap-index &&
+		test-tool read-midx --preferred-pack .git/objects
+	)
+'
+
+test_midx_is_retained () {
+	test-tool chmtime =0 .git/objects/pack/multi-pack-index &&
+	ls -l .git/objects/pack/multi-pack-index >expect &&
+	git multi-pack-index write "$@" &&
+	ls -l .git/objects/pack/multi-pack-index >actual &&
+	test_cmp expect actual
+}
+
+test_midx_is_rewritten () {
+	test-tool chmtime =0 .git/objects/pack/multi-pack-index &&
+	ls -l .git/objects/pack/multi-pack-index >expect &&
+	git multi-pack-index write "$@" &&
+	ls -l .git/objects/pack/multi-pack-index >actual &&
+	! test_cmp expect actual
+}
+
+test_expect_success 'up-to-date multi-pack-index is retained' '
+	test_when_finished "rm -fr midx-up-to-date" &&
+	git init midx-up-to-date &&
+	(
+		cd midx-up-to-date &&
+
+		# Write the initial pack that contains the most objects.
+		test_commit first &&
+		test_commit second &&
+		git repack -Ad --write-midx &&
+		test_midx_is_retained &&
+
+		# Writing a new bitmap index should cause us to regenerate the MIDX.
+		test_midx_is_rewritten --bitmap &&
+		test_midx_is_retained --bitmap &&
+
+		# Ensure that writing a new packfile causes us to rewrite the index.
+		test_commit incremental &&
+		git repack -d &&
+		test_midx_is_rewritten &&
+		test_midx_is_retained &&
+
+		for pack in .git/objects/pack/*.idx
+		do
+			basename "$pack" || exit 1
+		done >stdin &&
+		test_line_count = 2 stdin &&
+		test_midx_is_retained --stdin-packs <stdin &&
+		head -n1 stdin >stdin.trimmed &&
+		test_midx_is_rewritten --stdin-packs <stdin.trimmed
+	)
 '
 
 test_expect_success 'verify multi-pack-index success' '
@@ -1253,6 +1315,7 @@
 	git init repo &&
 	(
 		cd repo &&
+		git config set maintenance.auto false &&
 
 		for i in 1 2 3 4 5
 		do
@@ -1283,4 +1346,47 @@
 	)
 '
 
+test_expect_success 'pack.preferBitmapTips interprets patterns as hierarchy' '
+	git init repo &&
+	test_when_finished "rm -fr repo" &&
+	(
+		cd repo &&
+
+		# Create enough commits that not all will receive bitmap
+		# coverage even if they are all at the tip of some reference.
+		test_commit_bulk --message="%s" 103 &&
+		git log --format="create refs/tags/%s %H" HEAD >refs &&
+		git update-ref --stdin <refs &&
+
+		# Create the bitmap via the MIDX.
+		git repack -adb --write-midx &&
+		test-tool bitmap list-commits | sort >commits-with-bitmap &&
+
+		# Verify that we have at least one commit that did not
+		# receive a bitmap.
+		git rev-list HEAD >commits.raw &&
+		sort <commits.raw >commits &&
+		comm -13 commits-with-bitmap commits >commits-wo-bitmap &&
+		test_file_not_empty commits-wo-bitmap &&
+		commit_id=$(head commits-wo-bitmap) &&
+		ref_without_bitmap=$(git for-each-ref --points-at="$commit_id" --format="%(refname)") &&
+
+		# When passing the full refname we do not expect a bitmap to be
+		# generated, as it should be interpreted as if a slash was
+		# appended to the pattern.
+		rm .git/objects/pack/multi-pack-index* &&
+		git -c pack.preferBitmapTips="$ref_without_bitmap" repack -adb --write-midx &&
+		test-tool bitmap list-commits >after &&
+		test_grep ! "$commit_id" after &&
+
+		# But if we pass the parent directory of the ref we should see
+		# a bitmap.
+		ref_namespace=$(dirname "$ref_without_bitmap") &&
+		rm .git/objects/pack/multi-pack-index* &&
+		git -c pack.preferBitmapTips="$ref_namespace" repack -adb --write-midx &&
+		test-tool bitmap list-commits >after &&
+		test_grep "$commit_id" after
+	)
+'
+
 test_done
diff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh
index 892aeb0..62bd973 100755
--- a/t/t5326-multi-pack-bitmaps.sh
+++ b/t/t5326-multi-pack-bitmaps.sh
@@ -93,7 +93,8 @@
 	test_expect_success 'setup test_repository' '
 		rm -rf * .git &&
 		git init &&
-		git config pack.writeBitmapLookupTable '"$writeLookupTable"'
+		git config pack.writeBitmapLookupTable '"$writeLookupTable"' &&
+		git config maintenance.auto false
 	'
 
 	midx_bitmap_core
diff --git a/t/t5327-multi-pack-bitmaps-rev.sh b/t/t5327-multi-pack-bitmaps-rev.sh
index 9cac03a..cfa12de 100755
--- a/t/t5327-multi-pack-bitmaps-rev.sh
+++ b/t/t5327-multi-pack-bitmaps-rev.sh
@@ -30,7 +30,8 @@
 	test_expect_success 'setup bitmap config' '
 		rm -rf * .git &&
 		git init &&
-		git config pack.writeBitmapLookupTable '"$writeLookupTable"'
+		git config pack.writeBitmapLookupTable '"$writeLookupTable"' &&
+		git config maintenance.auto false
 	'
 
 	midx_bitmap_core rev
diff --git a/t/t5331-pack-objects-stdin.sh b/t/t5331-pack-objects-stdin.sh
index 4a8df5a..7eb79bc 100755
--- a/t/t5331-pack-objects-stdin.sh
+++ b/t/t5331-pack-objects-stdin.sh
@@ -14,6 +14,7 @@
 
 test_expect_success 'setup for --stdin-packs tests' '
 	git init stdin-packs &&
+	git -C stdin-packs config set maintenance.auto false &&
 	(
 		cd stdin-packs &&
 
@@ -255,6 +256,7 @@
 	git init repo &&
 	(
 		cd repo &&
+		git config set maintenance.auto false &&
 
 		for c in A B C D
 		do
@@ -319,6 +321,63 @@
 	)
 '
 
+test_expect_success '--stdin-packs with promisors' '
+	test_when_finished "rm -fr repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		git config set maintenance.auto false &&
+		git remote add promisor garbage &&
+		git config set remote.promisor.promisor true &&
+
+		for c in A B C D
+		do
+			echo "$c" >file &&
+			git add file &&
+			git commit --message "$c" &&
+			git tag "$c" || return 1
+		done &&
+
+		A="$(echo A | git pack-objects --revs $packdir/pack)" &&
+		B="$(echo A..B | git pack-objects --revs $packdir/pack --filter=blob:none)" &&
+		C="$(echo B..C | git pack-objects --revs $packdir/pack)" &&
+		D="$(echo C..D | git pack-objects --revs $packdir/pack)" &&
+		touch $packdir/pack-$B.promisor &&
+
+		test_must_fail git pack-objects --stdin-packs --exclude-promisor-objects pack- 2>err <<-EOF &&
+			pack-$B.pack
+		EOF
+		test_grep "is a promisor but --exclude-promisor-objects was given" err &&
+
+		PACK=$(git pack-objects --stdin-packs=follow --exclude-promisor-objects $packdir/pack <<-EOF
+			pack-$D.pack
+			EOF
+		) &&
+		objects_in_packs $C $D >expect &&
+		objects_in_packs $PACK >actual &&
+		test_cmp expect actual &&
+		rm -f $packdir/pack-$PACK.*
+	)
+'
+
+test_expect_success '--stdin-packs does not perform backfill fetch' '
+	test_when_finished "rm -rf remote client" &&
+
+	git init remote &&
+	test_commit_bulk -C remote 10 &&
+	git -C remote config set --local uploadpack.allowfilter 1 &&
+	git -C remote config set --local uploadpack.allowanysha1inwant 1 &&
+
+	git clone --filter=tree:0 "file://$(pwd)/remote" client &&
+	(
+		cd client &&
+		ls .git/objects/pack/*.promisor | sed "s|.*/||; s/\.promisor$/.pack/" >packs &&
+		test_line_count -gt 1 packs &&
+		GIT_TRACE2_EVENT="$(pwd)/event.log" git pack-objects --stdin-packs pack <packs &&
+		test_grep ! "\"event\":\"child_start\"" event.log
+	)
+'
+
 stdin_packs__follow_with_only () {
 	rm -fr stdin_packs__follow_with_only &&
 	git init stdin_packs__follow_with_only &&
diff --git a/t/t5332-multi-pack-reuse.sh b/t/t5332-multi-pack-reuse.sh
index 395d094..881ce66 100755
--- a/t/t5332-multi-pack-reuse.sh
+++ b/t/t5332-multi-pack-reuse.sh
@@ -59,6 +59,7 @@
 
 test_expect_success 'preferred pack is reused for single-pack reuse' '
 	test_config pack.allowPackReuse single &&
+	git config set maintenance.auto false &&
 
 	for i in A B
 	do
diff --git a/t/t5334-incremental-multi-pack-index.sh b/t/t5334-incremental-multi-pack-index.sh
index d30d725..99c7d44 100755
--- a/t/t5334-incremental-multi-pack-index.sh
+++ b/t/t5334-incremental-multi-pack-index.sh
@@ -15,6 +15,7 @@
 
 test_expect_success 'convert non-incremental MIDX to incremental' '
 	test_commit base &&
+	git config set maintenance.auto false &&
 	git repack -ad &&
 	git multi-pack-index write &&
 
diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh
index 83b42ff..b32a0a6 100755
--- a/t/t5400-send-pack.sh
+++ b/t/t5400-send-pack.sh
@@ -187,6 +187,7 @@
 		cd child &&
 		git config gc.autopacklimit 1 &&
 		git config gc.autodetach false &&
+		git config maintenance.strategy gc &&
 		git branch test_auto_gc &&
 		# And create a file that follows the temporary object naming
 		# convention for the auto-gc to remove
diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh
index 17a46fd..44ec875 100755
--- a/t/t5401-update-hooks.sh
+++ b/t/t5401-update-hooks.sh
@@ -134,8 +134,8 @@
 	EOF
 	rm -f victim.git/hooks/update victim.git/hooks/post-update &&
 
-	printf "create refs/heads/branch_%d main\n" $(test_seq 100 999) >input &&
-	git update-ref --stdin <input &&
+	test_seq -f "create refs/heads/branch_%d main" 100 999 |
+	git update-ref --stdin &&
 	git push ./victim.git "+refs/heads/*:refs/heads/*"
 '
 
diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh
index 978f240..cb0300b 100755
--- a/t/t5403-post-checkout-hook.sh
+++ b/t/t5403-post-checkout-hook.sh
@@ -9,9 +9,20 @@
 
 . ./test-lib.sh
 
+# Usage: check_post_checkout <file> <old-ref> <new-ref> <flag>
+#
+# Verify that the post-checkout hook arguments in <file> match the expected
+# values: <old-ref> for the previous HEAD, <new-ref> for the new HEAD, and
+# <flag> indicating whether this was a branch checkout (1) or file checkout (0).
+check_post_checkout () {
+	test "$#" = 4 || BUG "check_post_checkout takes 4 args"
+	echo "old=$2 new=$3 flag=$4" >expect &&
+	test_cmp expect "$1"
+}
+
 test_expect_success setup '
 	test_hook --setup post-checkout <<-\EOF &&
-	echo "$@" >.git/post-checkout.args
+	echo "old=$1 new=$2 flag=$3" >.git/post-checkout.args
 	EOF
 	test_commit one &&
 	test_commit two &&
@@ -23,29 +34,30 @@
 test_expect_success 'post-checkout receives the right arguments with HEAD unchanged ' '
 	test_when_finished "rm -f .git/post-checkout.args" &&
 	git checkout main &&
-	read old new flag <.git/post-checkout.args &&
-	test $old = $new && test $flag = 1
+	check_post_checkout .git/post-checkout.args \
+		"$(git rev-parse HEAD)" "$(git rev-parse HEAD)" 1
 '
 
 test_expect_success 'post-checkout args are correct with git checkout -b ' '
 	test_when_finished "rm -f .git/post-checkout.args" &&
 	git checkout -b new1 &&
-	read old new flag <.git/post-checkout.args &&
-	test $old = $new && test $flag = 1
+	check_post_checkout .git/post-checkout.args \
+		"$(git rev-parse HEAD)" "$(git rev-parse HEAD)" 1
 '
 
 test_expect_success 'post-checkout receives the right args with HEAD changed ' '
 	test_when_finished "rm -f .git/post-checkout.args" &&
+	old=$(git rev-parse HEAD) &&
 	git checkout two &&
-	read old new flag <.git/post-checkout.args &&
-	test $old != $new && test $flag = 1
+	check_post_checkout .git/post-checkout.args \
+		"$old" "$(git rev-parse HEAD)" 1
 '
 
 test_expect_success 'post-checkout receives the right args when not switching branches ' '
 	test_when_finished "rm -f .git/post-checkout.args" &&
 	git checkout main -- three.t &&
-	read old new flag <.git/post-checkout.args &&
-	test $old = $new && test $flag = 0
+	check_post_checkout .git/post-checkout.args \
+		"$(git rev-parse HEAD)" "$(git rev-parse HEAD)" 0
 '
 
 test_rebase () {
@@ -55,10 +67,8 @@
 		git checkout -B rebase-test main &&
 		rm -f .git/post-checkout.args &&
 		git rebase $args rebase-on-me &&
-		read old new flag <.git/post-checkout.args &&
-		test_cmp_rev main $old &&
-		test_cmp_rev rebase-on-me $new &&
-		test $flag = 1
+		check_post_checkout .git/post-checkout.args \
+			"$(git rev-parse main)" "$(git rev-parse rebase-on-me)" 1
 	'
 
 	test_expect_success "post-checkout is triggered on rebase $args with fast-forward" '
@@ -66,10 +76,8 @@
 		git checkout -B ff-rebase-test rebase-on-me^ &&
 		rm -f .git/post-checkout.args &&
 		git rebase $args rebase-on-me &&
-		read old new flag <.git/post-checkout.args &&
-		test_cmp_rev rebase-on-me^ $old &&
-		test_cmp_rev rebase-on-me $new &&
-		test $flag = 1
+		check_post_checkout .git/post-checkout.args \
+			"$(git rev-parse rebase-on-me^)" "$(git rev-parse rebase-on-me)" 1
 	'
 
 	test_expect_success "rebase $args fast-forward branch checkout runs post-checkout hook" '
@@ -79,10 +87,8 @@
 		git checkout two  &&
 		rm -f .git/post-checkout.args &&
 		git rebase $args HEAD rebase-fast-forward  &&
-		read old new flag <.git/post-checkout.args &&
-		test_cmp_rev two $old &&
-		test_cmp_rev three $new &&
-		test $flag = 1
+		check_post_checkout .git/post-checkout.args \
+			"$(git rev-parse two)" "$(git rev-parse three)" 1
 	'
 
 	test_expect_success "rebase $args checkout does not remove untracked files" '
@@ -106,10 +112,11 @@
 test_expect_success 'post-checkout hook is triggered by clone' '
 	mkdir -p templates/hooks &&
 	write_script templates/hooks/post-checkout <<-\EOF &&
-	echo "$@" >"$GIT_DIR/post-checkout.args"
+	echo "old=$1 new=$2 flag=$3" >"$GIT_DIR/post-checkout.args"
 	EOF
 	git clone --template=templates . clone3 &&
-	test -f clone3/.git/post-checkout.args
+	check_post_checkout clone3/.git/post-checkout.args \
+		"$(test_oid zero)" "$(git -C clone3 rev-parse HEAD)" 1
 '
 
 test_done
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
index 2677cd5..649a615 100755
--- a/t/t5500-fetch-pack.sh
+++ b/t/t5500-fetch-pack.sh
@@ -154,7 +154,8 @@
 '
 
 test_expect_success 'clone shallow' '
-	git clone --no-single-branch --depth 2 "file://$(pwd)/." shallow
+	git clone --no-single-branch --depth 2 "file://$(pwd)/." shallow &&
+	git -C shallow config set maintenance.auto false
 '
 
 test_expect_success 'clone shallow depth count' '
@@ -892,15 +893,20 @@
 	test_commit other &&
 	git commit-graph write --reachable &&
 	git config core.commitGraph true &&
+	oid_algo=$(test_oid algo) &&
+	oid_other=$(git rev-parse other) &&
+	oid_main=$(git rev-parse main) &&
 
-	GIT_PROTOCOL=version=2 git upload-pack . <<-EOF >/dev/null
-	0012command=fetch
-	$(echo "object-format=$(test_oid algo)" | packetize)
-	00010013deepen-since 1
-	$(echo "want $(git rev-parse other)" | packetize)
-	$(echo "have $(git rev-parse main)" | packetize)
+	test-tool pkt-line pack >input <<-EOF &&
+	command=fetch
+	object-format=$oid_algo
+	0001
+	deepen-since 1
+	want $oid_other
+	have $oid_main
 	0000
 	EOF
+	GIT_PROTOCOL=version=2 git upload-pack . <input >/dev/null
 	)
 '
 
@@ -955,6 +961,29 @@
 	)
 '
 
+test_expect_success 'fetching deepen beyond merged branch' '
+	test_create_repo shallow-deepen-merged &&
+	(
+		cd shallow-deepen-merged &&
+		git commit --allow-empty -m one &&
+		git commit --allow-empty -m two &&
+		git commit --allow-empty -m three &&
+		git switch -c branch &&
+		git commit --allow-empty -m four &&
+		git commit --allow-empty -m five &&
+		git switch main &&
+		git merge --no-ff branch &&
+		cd - &&
+		git clone --bare --depth 3 "file://$(pwd)/shallow-deepen-merged" deepen.git &&
+		git -C deepen.git fetch origin --deepen=1 &&
+		git -C deepen.git rev-list --all >actual &&
+		for commit in $(sed "/^$/d" deepen.git/shallow)
+		do
+			test_grep "$commit" actual || exit 1
+		done
+	)
+'
+
 test_negotiation_algorithm_default () {
 	test_when_finished rm -rf clientv0 clientv2 &&
 	rm -rf server client &&
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index b7059cc..5dcb4b5 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -1321,6 +1321,7 @@
 		git config fetch.unpackLimit 1 &&
 		git config gc.autoPackLimit 1 &&
 		git config gc.autoDetach false &&
+		git config maintenance.strategy gc &&
 		GIT_ASK_YESNO="$TRASH_DIRECTORY/askyesno" git fetch --verbose >fetch.out 2>&1 &&
 		test_grep "Auto packing the repository" fetch.out &&
 		! grep "Should I try again" fetch.out
@@ -1516,7 +1517,7 @@
 		git remote add origin ../base &&
 		touch refs/heads/foo.lock &&
 		test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err &&
-		test_grep "error: fetching ref refs/heads/foo failed: reference already exists" err &&
+		test_grep -e "error: cannot lock ref ${SQ}refs/heads/foo${SQ}: Unable to create" -e "refs/heads/foo.lock${SQ}: File exists." err &&
 		git rev-parse refs/heads/main >expect &&
 		git rev-parse refs/heads/branch >actual &&
 		test_cmp expect actual
@@ -1530,7 +1531,7 @@
 		cd case_insensitive &&
 		git remote add origin -- ../case_sensitive_fd &&
 		test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err &&
-		test_grep "failed: refname conflict" err &&
+		test_grep "cannot process ${SQ}refs/remotes/origin/foo${SQ} and ${SQ}refs/remotes/origin/foo/bar${SQ} at the same time" err &&
 		git rev-parse refs/heads/main >expect &&
 		git rev-parse refs/heads/foo/bar >actual &&
 		test_cmp expect actual
@@ -1544,7 +1545,7 @@
 		cd case_insensitive &&
 		git remote add origin -- ../case_sensitive_df &&
 		test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err &&
-		test_grep "failed: refname conflict" err &&
+		test_grep "cannot lock ref ${SQ}refs/remotes/origin/foo${SQ}: there is a non-empty directory ${SQ}./refs/remotes/origin/foo${SQ} blocking reference ${SQ}refs/remotes/origin/foo${SQ}" err &&
 		git rev-parse refs/heads/main >expect &&
 		git rev-parse refs/heads/Foo/bar >actual &&
 		test_cmp expect actual
@@ -1552,6 +1553,7 @@
 '
 
 test_expect_success REFFILES 'D/F conflict on case sensitive filesystem with lock' '
+	test_when_finished rm -rf base repo &&
 	(
 		git init --ref-format=reftable base &&
 		cd base &&
@@ -1577,6 +1579,155 @@
 	)
 '
 
+test_expect_success 'fetch --tags fetches existing tags' '
+	test_when_finished rm -rf base repo &&
+
+	git init base &&
+	git -C base commit --allow-empty -m "empty-commit" &&
+
+	git clone --bare base repo &&
+
+	git -C base tag tag-1 &&
+	git -C repo for-each-ref >out &&
+	test_grep ! "tag-1" out &&
+	git -C repo fetch --tags &&
+	git -C repo for-each-ref >out &&
+	test_grep "tag-1" out
+'
+
+test_expect_success 'fetch --tags fetches non-conflicting tags' '
+	test_when_finished rm -rf base repo &&
+
+	git init base &&
+	git -C base commit --allow-empty -m "empty-commit" &&
+	git -C base tag tag-1 &&
+
+	git clone --bare base repo &&
+
+	git -C base tag tag-2 &&
+	git -C repo for-each-ref >out &&
+	test_grep ! "tag-2" out &&
+
+	git -C base commit --allow-empty -m "second empty-commit" &&
+	git -C base tag -f tag-1 &&
+
+	test_must_fail git -C repo fetch --tags 2>out &&
+	test_grep "tag-1  (would clobber existing tag)" out &&
+	git -C repo for-each-ref >out &&
+	test_grep "tag-2" out
+'
+
+test_expect_success "backfill tags when providing a refspec" '
+	test_when_finished rm -rf source target &&
+
+	git init source &&
+	git -C source commit --allow-empty --message common &&
+	git clone file://"$(pwd)"/source target &&
+	(
+	    cd source &&
+	    test_commit history &&
+	    test_commit fetch-me
+	) &&
+
+	# The "history" tag is backfilled even though we requested
+	# to only fetch HEAD
+	git -C target fetch origin HEAD:branch &&
+	git -C target tag -l >actual &&
+	cat >expect <<-\EOF &&
+	fetch-me
+	history
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success REFFILES "FETCH_HEAD is updated even if ref updates fail" '
+	test_when_finished rm -rf base repo &&
+
+	git init base &&
+	(
+		cd base &&
+		test_commit "updated" &&
+
+		git update-ref refs/heads/foo @ &&
+		git update-ref refs/heads/branch @
+	) &&
+
+	git init --bare repo &&
+	(
+		cd repo &&
+		rm -f FETCH_HEAD &&
+		git remote add origin ../base &&
+		>refs/heads/foo.lock &&
+		test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err &&
+		test_grep -e "error: cannot lock ref ${SQ}refs/heads/foo${SQ}: Unable to create" -e "refs/heads/foo.lock${SQ}: File exists." err &&
+		test_grep "branch ${SQ}branch${SQ} of ../base" FETCH_HEAD &&
+		test_grep "branch ${SQ}foo${SQ} of ../base" FETCH_HEAD
+	)
+'
+
+test_expect_success "upstream tracking info is added with --set-upstream" '
+	test_when_finished rm -rf base repo &&
+
+	git init --initial-branch=main base &&
+	test_commit -C base "updated" &&
+
+	git init --bare --initial-branch=main repo &&
+	(
+		cd repo &&
+		git remote add origin ../base &&
+		git fetch origin --set-upstream main &&
+		git config get branch.main.remote >actual &&
+		echo "origin" >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success REFFILES "upstream tracking info is added even with conflicts" '
+	test_when_finished rm -rf base repo &&
+
+	git init --initial-branch=main base &&
+	test_commit -C base "updated" &&
+
+	git init --bare --initial-branch=main repo &&
+	(
+		cd repo &&
+		git remote add origin ../base &&
+		test_must_fail git config get branch.main.remote &&
+
+		mkdir -p refs/remotes/origin &&
+		>refs/remotes/origin/main.lock &&
+		test_must_fail git fetch origin --set-upstream main &&
+		git config get branch.main.remote >actual &&
+		echo "origin" >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success REFFILES "HEAD is updated even with conflicts" '
+	test_when_finished rm -rf base repo &&
+
+	git init base &&
+	(
+		cd base &&
+		test_commit "updated" &&
+
+		git update-ref refs/heads/foo @ &&
+		git update-ref refs/heads/branch @
+	) &&
+
+	git init --bare repo &&
+	(
+		cd repo &&
+		git remote add origin ../base &&
+
+		test_path_is_missing refs/remotes/origin/HEAD &&
+		mkdir -p refs/remotes/origin &&
+		>refs/remotes/origin/branch.lock &&
+		test_must_fail git fetch origin &&
+		test -f refs/remotes/origin/HEAD
+	)
+'
+
 . "$TEST_DIRECTORY"/lib-httpd.sh
 start_httpd
 
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 46926e7..29e2f17 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1882,4 +1882,20 @@
 	git push testrepo :refs/heads/branch/conflict refs/heads/branch
 '
 
+test_expect_success 'pushing non-commit objects should report error' '
+	test_when_finished "rm -rf dest repo" &&
+	git init dest &&
+	git init repo &&
+
+	(
+		cd repo &&
+		test_commit --annotate test &&
+
+		tagsha=$(git rev-parse test^{tag}) &&
+		test_must_fail git push ../dest "$tagsha:refs/heads/branch" 2>err &&
+		test_grep "! \[remote rejected\] $tagsha -> branch (invalid new value provided)" err &&
+		test_grep "trying to write non-commit object $tagsha to branch ${SQ}refs/heads/branch${SQ}" err
+	)
+'
+
 test_done
diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh
index ed0ad66..9d0a7f5 100755
--- a/t/t5550-http-fetch-dumb.sh
+++ b/t/t5550-http-fetch-dumb.sh
@@ -102,6 +102,31 @@
 	expect_askpass both wrong
 '
 
+test_expect_success 'using credentials from netrc to clone successfully' '
+	test_when_finished clear_netrc &&
+	set_askpass wrong &&
+	set_netrc 127.0.0.1 user@host pass@host &&
+	git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-netrc &&
+	expect_askpass none
+'
+
+test_expect_success 'netrc unauthorized credentials (prompt after 401)' '
+	test_when_finished clear_netrc &&
+	set_askpass wrong &&
+	set_netrc 127.0.0.1 user@host pass@wrong &&
+	test_must_fail git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-netrc-401 &&
+	expect_askpass both wrong
+'
+
+test_expect_success 'netrc authorized but forbidden credentials (fail on 403)' '
+	test_when_finished clear_netrc &&
+	set_askpass wrong &&
+	set_netrc 127.0.0.1 forbidden-user@host pass@host &&
+	test_must_fail git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-netrc-403 2>err &&
+	expect_askpass none &&
+	grep "The requested URL returned error: 403" err
+'
+
 test_expect_success 'http auth can use user/pass in URL' '
 	set_askpass wrong &&
 	git clone "$HTTPD_URL_USER_PASS/auth/dumb/repo.git" clone-auth-none &&
@@ -339,32 +364,32 @@
 '
 
 test_expect_success 'did not use upload-pack service' '
-	! grep "/git-upload-pack" "$HTTPD_ROOT_PATH/access.log"
+	test_grep ! "/git-upload-pack" "$HTTPD_ROOT_PATH/access.log"
 '
 
-test_expect_success 'git client shows text/plain errors' '
+test_expect_success ICONV 'git client shows text/plain errors' '
 	test_must_fail git clone "$HTTPD_URL/error/text" 2>stderr &&
-	grep "this is the error message" stderr
+	test_grep "this is the error message" stderr
 '
 
-test_expect_success 'git client does not show html errors' '
+test_expect_success ICONV 'git client does not show html errors' '
 	test_must_fail git clone "$HTTPD_URL/error/html" 2>stderr &&
-	! grep "this is the error message" stderr
+	test_grep ! "this is the error message" stderr
 '
 
-test_expect_success 'git client shows text/plain with a charset' '
+test_expect_success ICONV 'git client shows text/plain with a charset' '
 	test_must_fail git clone "$HTTPD_URL/error/charset" 2>stderr &&
-	grep "this is the error message" stderr
+	test_grep "this is the error message" stderr
 '
 
 test_expect_success ICONV 'http error messages are reencoded' '
 	test_must_fail git clone "$HTTPD_URL/error/utf16" 2>stderr &&
-	grep "this is the error message" stderr
+	test_grep "this is the error message" stderr
 '
 
 test_expect_success ICONV 'reencoding is robust to whitespace oddities' '
 	test_must_fail git clone "$HTTPD_URL/error/odd-spacing" 2>stderr &&
-	grep "this is the error message" stderr
+	test_grep "this is the error message" stderr
 '
 
 check_language () {
@@ -406,7 +431,7 @@
 
 test_expect_success 'git client send an empty Accept-Language' '
 	GIT_TRACE_CURL=true LANGUAGE= git ls-remote "$HTTPD_URL/dumb/repo.git" 2>stderr &&
-	! grep "^=> Send header: Accept-Language:" stderr
+	test_grep ! "^=> Send header: Accept-Language:" stderr
 '
 
 test_expect_success 'remote-http complains cleanly about malformed urls' '
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
index b0d4ea7..73cf531 100755
--- a/t/t5551-http-fetch-smart.sh
+++ b/t/t5551-http-fetch-smart.sh
@@ -333,12 +333,12 @@
 
 test_expect_success 'cookies stored in http.cookiefile when http.savecookies set' '
 	cat >cookies.txt <<-\EOF &&
-	127.0.0.1	FALSE	/smart_cookies/	FALSE	0	othername	othervalue
+	127.0.0.1	FALSE	/smart_cookies	FALSE	0	othername	othervalue
 	EOF
 	sort >expect_cookies.txt <<-\EOF &&
-	127.0.0.1	FALSE	/smart_cookies/	FALSE	0	othername	othervalue
-	127.0.0.1	FALSE	/smart_cookies/repo.git/	FALSE	0	name	value
-	127.0.0.1	FALSE	/smart_cookies/repo.git/info/	FALSE	0	name	value
+	127.0.0.1	FALSE	/smart_cookies	FALSE	0	othername	othervalue
+	127.0.0.1	FALSE	/smart_cookies/repo.git	FALSE	0	name	value
+	127.0.0.1	FALSE	/smart_cookies/repo.git/info	FALSE	0	name	value
 	EOF
 	git config http.cookiefile cookies.txt &&
 	git config http.savecookies true &&
@@ -351,8 +351,11 @@
 		tag -m "foo" cookie-tag &&
 	git fetch $HTTPD_URL/smart_cookies/repo.git cookie-tag &&
 
-	grep "^[^#]" cookies.txt | sort >cookies_stripped.txt &&
-	test_cmp expect_cookies.txt cookies_stripped.txt
+	# Strip trailing slashes from cookie paths to handle output from both
+	# old curl ("/smart_cookies/") and new ("/smart_cookies").
+	HT="	" &&
+	grep "^[^#]" cookies.txt | sed "s,/$HT,$HT," | sort >cookies_clean.txt &&
+	test_cmp expect_cookies.txt cookies_clean.txt
 '
 
 test_expect_success 'transfer.hiderefs works over smart-http' '
diff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh
index 317f33a..0063581 100755
--- a/t/t5563-simple-http-auth.sh
+++ b/t/t5563-simple-http-auth.sh
@@ -469,7 +469,7 @@
 	EOF
 '
 
-test_expect_success 'access using basic auth with wwwauth header mixed line-endings' '
+test_expect_success 'access using basic auth with wwwauth header mixed continuations' '
 	test_when_finished "per_test_cleanup" &&
 
 	set_credential_reply get <<-EOF &&
@@ -490,7 +490,7 @@
 	printf "id=default response=WWW-Authenticate: FooBar param1=\"value1\"\r\n" >>"$CHALLENGE" &&
 	printf "id=default response= \r\n" >>"$CHALLENGE" &&
 	printf "id=default response=\tparam2=\"value2\"\r\n" >>"$CHALLENGE" &&
-	printf "id=default response=WWW-Authenticate: Basic realm=\"example.com\"" >>"$CHALLENGE" &&
+	printf "id=default response=WWW-Authenticate: Basic realm=\"example.com\"\r\n" >>"$CHALLENGE" &&
 
 	test_config_global credential.helper test-helper &&
 	git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
@@ -605,6 +605,51 @@
 	EOF
 '
 
+test_expect_success 'clone with bearer auth and probe_rpc' '
+	test_when_finished "per_test_cleanup" &&
+	test_when_finished "rm -rf large.git" &&
+
+	# Set up a repository large enough to trigger probe_rpc
+	git init large.git &&
+	(
+		cd large.git &&
+		git config set maintenance.auto false &&
+		git commit --allow-empty --message "initial" &&
+		# Create many refs to trigger probe_rpc, which is called when
+		# the request body is larger than http.postBuffer.
+		#
+		# In the test later, http.postBuffer is set to 70000. Each
+		# "want" line is ~45 bytes, so we need at least 70000/45 = ~1600
+		# refs
+		test_seq -f "create refs/heads/branch-%d @" 2000 |
+		git update-ref --stdin
+	) &&
+	git clone --bare large.git "$HTTPD_DOCUMENT_ROOT_PATH/large.git" &&
+
+	# Clone it through HTTP with a Bearer token
+	set_credential_reply get <<-EOF &&
+	capability[]=authtype
+	authtype=Bearer
+	credential=YS1naXQtdG9rZW4=
+	EOF
+
+	# Bearer token
+	cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
+	id=1 creds=Bearer YS1naXQtdG9rZW4=
+	EOF
+
+	cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
+	id=1 status=200
+	id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com"
+	EOF
+
+	# Set a small buffer to force probe_rpc to be called
+	# Must be > LARGE_PACKET_MAX (65520)
+	test_config_global http.postBuffer 70000 &&
+	test_config_global credential.helper test-helper &&
+	git clone "$HTTPD_URL/custom_auth/large.git" partial-auth-clone 2>clone-error
+'
+
 test_expect_success 'access using three-legged auth' '
 	test_when_finished "per_test_cleanup" &&
 
diff --git a/t/t5564-http-proxy.sh b/t/t5564-http-proxy.sh
index c3903fa..3bcbdef 100755
--- a/t/t5564-http-proxy.sh
+++ b/t/t5564-http-proxy.sh
@@ -40,10 +40,10 @@
 
 start_socks() {
 	mkfifo socks_output &&
-	{
+	(
 		"$PERL_PATH" "$TEST_DIRECTORY/socks4-proxy.pl" "$1" >socks_output &
 		echo $! > "$TRASH_DIRECTORY/socks.pid"
-	} &&
+	) &&
 	read line <socks_output &&
 	test "$line" = ready
 }
diff --git a/t/t5565-push-multiple.sh b/t/t5565-push-multiple.sh
new file mode 100755
index 0000000..7e93668
--- /dev/null
+++ b/t/t5565-push-multiple.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+test_description='push to group'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+	for i in 1 2 3
+	do
+		git init dest-$i &&
+		git -C dest-$i symbolic-ref HEAD refs/heads/not-a-branch ||
+		return 1
+	done &&
+	test_tick &&
+	git commit --allow-empty -m "initial" &&
+	git config set --append remote.them.pushurl "file://$(pwd)/dest-1" &&
+	git config set --append remote.them.pushurl "file://$(pwd)/dest-2" &&
+	git config set --append remote.them.pushurl "file://$(pwd)/dest-3" &&
+	git config set --append remote.them.push "+refs/heads/*:refs/heads/*"
+'
+
+test_expect_success 'push to group' '
+	git push them &&
+	j= &&
+	for i in 1 2 3
+	do
+		git -C dest-$i for-each-ref >actual-$i &&
+		if test -n "$j"
+		then
+			test_cmp actual-$j actual-$i
+		else
+			cat actual-$i
+		fi &&
+		j=$i ||
+		return 1
+	done
+'
+
+test_done
diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh
index 1e354e0..1c2805a 100755
--- a/t/t5616-partial-clone.sh
+++ b/t/t5616-partial-clone.sh
@@ -229,7 +229,7 @@
 
 	GIT_TRACE2_EVENT="$PWD/trace1.event" \
 	git -C pc1 fetch --refetch origin &&
-	test_subcommand git maintenance run --auto --no-quiet --detach <trace1.event &&
+	test_subcommand git maintenance run --auto --no-quiet --no-detach <trace1.event &&
 	grep \"param\":\"gc.autopacklimit\",\"value\":\"1\" trace1.event &&
 	grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"-1\" trace1.event &&
 
@@ -238,7 +238,7 @@
 		-c gc.autoPackLimit=0 \
 		-c maintenance.incremental-repack.auto=1234 \
 		-C pc1 fetch --refetch origin &&
-	test_subcommand git maintenance run --auto --no-quiet --detach <trace2.event &&
+	test_subcommand git maintenance run --auto --no-quiet --no-detach <trace2.event &&
 	grep \"param\":\"gc.autopacklimit\",\"value\":\"0\" trace2.event &&
 	grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"-1\" trace2.event &&
 
@@ -247,7 +247,7 @@
 		-c gc.autoPackLimit=1234 \
 		-c maintenance.incremental-repack.auto=0 \
 		-C pc1 fetch --refetch origin &&
-	test_subcommand git maintenance run --auto --no-quiet --detach <trace3.event &&
+	test_subcommand git maintenance run --auto --no-quiet --no-detach <trace3.event &&
 	grep \"param\":\"gc.autopacklimit\",\"value\":\"1\" trace3.event &&
 	grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"0\" trace3.event
 '
@@ -585,6 +585,7 @@
 	git clone --filter=blob:none "file://$(pwd)/srv.bare" pack-test &&
 	ls pack-test/.git/objects/pack/*pack >pack-list &&
 	test_line_count = 2 pack-list &&
+	test_config -C pack-test maintenance.auto false &&
 	for i in A B C
 	do
 		test_commit -C src $i &&
diff --git a/t/t5710-promisor-remote-capability.sh b/t/t5710-promisor-remote-capability.sh
index 023735d..357822c 100755
--- a/t/t5710-promisor-remote-capability.sh
+++ b/t/t5710-promisor-remote-capability.sh
@@ -20,7 +20,7 @@
 	test_commit -C template 1 &&
 	test_commit -C template 2 &&
 	test_commit -C template 3 &&
-	test-tool genrandom foo 10240 >template/foo &&
+	test-tool genrandom foo 10k >template/foo &&
 	git -C template add foo &&
 	git -C template commit -m foo
 '
@@ -360,6 +360,129 @@
 	check_missing_objects server 1 "$oid"
 '
 
+test_expect_success "clone with promisor.storeFields=partialCloneFilter" '
+	git -C server config promisor.advertise true &&
+	test_when_finished "rm -rf client" &&
+
+	git -C server remote add otherLop "https://invalid.invalid"  &&
+	git -C server config remote.otherLop.token "fooBar" &&
+	git -C server config remote.otherLop.stuff "baz" &&
+	git -C server config remote.otherLop.partialCloneFilter "blob:limit=10k" &&
+	test_when_finished "git -C server remote remove otherLop" &&
+
+	git -C server config remote.lop.token "fooXXX" &&
+	git -C server config remote.lop.partialCloneFilter "blob:limit=8k" &&
+
+	test_config -C server promisor.sendFields "partialCloneFilter, token" &&
+	test_when_finished "rm trace" &&
+
+	# Clone from server to create a client
+	GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \
+		-c remote.lop.promisor=true \
+		-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
+		-c remote.lop.url="file://$(pwd)/lop" \
+		-c remote.lop.token="fooYYY" \
+		-c remote.lop.partialCloneFilter="blob:none" \
+		-c promisor.acceptfromserver=All \
+		-c promisor.storeFields=partialcloneFilter \
+		--no-local --filter="blob:limit=5k" server client 2>err &&
+
+	# Check that the filter from the server is stored
+	echo "blob:limit=8k" >expected &&
+	git -C client config remote.lop.partialCloneFilter >actual &&
+	test_cmp expected actual &&
+
+	# Check that user is notified when the filter is stored
+	test_grep "Storing new filter from server for remote '\''lop'\''" err &&
+	test_grep "'\''blob:none'\'' -> '\''blob:limit=8k'\''" err &&
+
+	# Check that the token from the server is NOT stored
+	echo "fooYYY" >expected &&
+	git -C client config remote.lop.token >actual &&
+	test_cmp expected actual &&
+	test_grep ! "Storing new token from server" err &&
+
+	# Check that the filter for an unknown remote is NOT stored
+	test_must_fail git -C client config remote.otherLop.partialCloneFilter >actual &&
+
+	# Check that the largest object is still missing on the server
+	check_missing_objects server 1 "$oid" &&
+
+	# Change the configuration on the server and fetch from the client
+	git -C server config remote.lop.partialCloneFilter "blob:limit=7k" &&
+	GIT_NO_LAZY_FETCH=0 git -C client fetch \
+		--filter="blob:limit=5k" ../server 2>err &&
+
+	# Check that the fetch updated the configuration on the client
+	echo "blob:limit=7k" >expected &&
+	git -C client config remote.lop.partialCloneFilter >actual &&
+	test_cmp expected actual &&
+
+	# Check that user is notified when the new filter is stored
+	test_grep "Storing new filter from server for remote '\''lop'\''" err &&
+	test_grep "'\''blob:limit=8k'\'' -> '\''blob:limit=7k'\''" err
+'
+
+test_expect_success "clone and fetch with --filter=auto" '
+	git -C server config promisor.advertise true &&
+	test_when_finished "rm -rf client trace" &&
+
+	git -C server config remote.lop.partialCloneFilter "blob:limit=9500" &&
+	test_config -C server promisor.sendFields "partialCloneFilter" &&
+
+	GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \
+		-c remote.lop.promisor=true \
+		-c remote.lop.url="file://$(pwd)/lop" \
+		-c promisor.acceptfromserver=All \
+		--no-local --filter=auto server client 2>err &&
+
+	test_grep "filter blob:limit=9500" trace &&
+	test_grep ! "filter auto" trace &&
+
+	# Verify "auto" is persisted in config
+	echo auto >expected &&
+	git -C client config remote.origin.partialCloneFilter >actual &&
+	test_cmp expected actual &&
+
+	# Check that the largest object is still missing on the server
+	check_missing_objects server 1 "$oid" &&
+
+	# Now change the filter on the server
+	git -C server config remote.lop.partialCloneFilter "blob:limit=5678" &&
+
+	# Get a new commit on the server to ensure "git fetch" actually runs fetch-pack
+	test_commit -C template new-commit &&
+	git -C template push --all "$(pwd)/server" &&
+
+	# Perform a fetch WITH --filter=auto
+	rm -rf trace &&
+	GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch --filter=auto &&
+
+	# Verify that the new filter was used
+	test_grep "filter blob:limit=5678" trace &&
+
+	# Check that the largest object is still missing on the server
+	check_missing_objects server 1 "$oid" &&
+
+	# Change the filter on the server again
+	git -C server config remote.lop.partialCloneFilter "blob:limit=5432" &&
+
+	# Get yet a new commit on the server to ensure fetch-pack runs
+	test_commit -C template yet-a-new-commit &&
+	git -C template push --all "$(pwd)/server" &&
+
+	# Perform a fetch WITHOUT --filter=auto
+	# Relies on "auto" being persisted in the client config
+	rm -rf trace &&
+	GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch &&
+
+	# Verify that the new filter was used
+	test_grep "filter blob:limit=5432" trace &&
+
+	# Check that the largest object is still missing on the server
+	check_missing_objects server 1 "$oid"
+'
+
 test_expect_success "clone with promisor.advertise set to 'true' but don't delete the client" '
 	git -C server config promisor.advertise true &&
 
@@ -376,7 +499,7 @@
 
 test_expect_success "setup for subsequent fetches" '
 	# Generate new commit with large blob
-	test-tool genrandom bar 10240 >template/bar &&
+	test-tool genrandom bar 10k >template/bar &&
 	git -C template add bar &&
 	git -C template commit -m bar &&
 
diff --git a/t/t5750-bundle-uri-parse.sh b/t/t5750-bundle-uri-parse.sh
index 80a3f83..294f9d9 100755
--- a/t/t5750-bundle-uri-parse.sh
+++ b/t/t5750-bundle-uri-parse.sh
@@ -286,4 +286,30 @@
 	grep "could not parse bundle list key creationToken with value '\''bogus'\''" err
 '
 
+test_expect_success 'parse config format: bundle with missing uri' '
+	cat >input <<-\EOF &&
+	[bundle]
+		version = 1
+		mode = all
+	[bundle "missing-uri"]
+		creationToken = 1
+	EOF
+
+	test_must_fail test-tool bundle-uri parse-config input 2>err &&
+	grep "bundle '\''missing-uri'\'' has no uri" err
+'
+
+test_expect_success 'parse config format: bundle with url instead of uri' '
+	cat >input <<-\EOF &&
+	[bundle]
+		version = 1
+		mode = all
+	[bundle "typo"]
+		url = https://example.com/bundle.bdl
+	EOF
+
+	test_must_fail test-tool bundle-uri parse-config input 2>err &&
+	grep "bundle '\''typo'\'' has no uri" err
+'
+
 test_done
diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh
index fec1644..d0a2a86 100755
--- a/t/t6000-rev-list-misc.sh
+++ b/t/t6000-rev-list-misc.sh
@@ -248,4 +248,19 @@
 	test_cmp expect actual
 '
 
+test_expect_success 'rev-list --boundary incompatible with --maximal-only' '
+	test_when_finished rm -rf repo &&
+
+	git init repo &&
+	test_commit -C repo 1 &&
+	test_commit -C repo 2 &&
+
+	oid1=$(git -C repo rev-parse HEAD~) &&
+	oid2=$(git -C repo rev-parse HEAD) &&
+
+	test_must_fail git -C repo rev-list --boundary --maximal-only \
+		HEAD~1..HEAD 2>err &&
+	test_grep "cannot be used together" err
+'
+
 test_done
diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh
index eb93d68..5819844 100755
--- a/t/t6006-rev-list-format.sh
+++ b/t/t6006-rev-list-format.sh
@@ -378,15 +378,23 @@
 	test_cmp expect actual
 '
 
-iconv -f utf-8 -t $test_encoding > commit-msg <<EOF
-Test printing of complex bodies
-
-This commit message is much longer than the others,
-and it will be encoded in $test_encoding. We should therefore
-include an ISO8859 character: ¡bueno!
-EOF
-
 test_expect_success 'setup complex body' '
+	message=$(cat <<-EOF
+	Test printing of complex bodies
+
+	This commit message is much longer than the others,
+	and it will be encoded in $test_encoding. We should therefore
+	include an ISO8859 character: ¡bueno!
+	EOF
+	) &&
+
+	if test_have_prereq ICONV
+	then
+		echo "$message" | iconv -f utf-8 -t $test_encoding >commit-msg
+	else
+		echo "$message" >commit-msg
+	fi &&
+
 	git config i18n.commitencoding $test_encoding &&
 	echo change2 >foo && git commit -a -F commit-msg &&
 	head3=$(git rev-parse --verify HEAD) &&
@@ -448,7 +456,12 @@
 	commit $head2
 	commit $head1
 	EOF
-	iconv -f utf-8 -t $test_encoding expected.utf-8 >expected.ISO8859-1
+	if test_have_prereq ICONV
+	then
+		iconv -f utf-8 -t $test_encoding expected.utf-8 >expected.ISO8859-1
+	else
+		cp expected.utf-8 expected.ISO8859-1
+	fi
 '
 
 test_format complex-body %b <expected.ISO8859-1
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 0b719bb..0242b5b 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -292,4 +292,358 @@
 	test_cmp expect actual
 '
 
+test_expect_success 'status tracking origin/main shows only main' '
+	git -C test checkout b4 &&
+	git -C test status >actual &&
+	cat >expect <<-EOF &&
+	On branch b4
+	Your branch is ahead of ${SQ}origin/main${SQ} by 2 commits.
+	  (use "git push" to publish your local commits)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status --no-ahead-behind tracking origin/main shows only main' '
+	git -C test checkout b4 &&
+	git -C test status --no-ahead-behind >actual &&
+	cat >expect <<-EOF &&
+	On branch b4
+	Your branch and ${SQ}origin/main${SQ} refer to different commits.
+	  (use "git status --ahead-behind" for details)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches deduplicates when upstream and push are the same' '
+	test_config -C test status.compareBranches "@{upstream} @{push}" &&
+	git -C test checkout main &&
+	git -C test status >actual &&
+	cat >expect <<-EOF &&
+	On branch main
+	Your branch is up to date with ${SQ}origin/main${SQ}.
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches with only upstream shows only upstream' '
+	test_config -C test status.compareBranches "@{upstream}" &&
+	git -C test checkout main &&
+	git -C test status >actual &&
+	cat >expect <<-EOF &&
+	On branch main
+	Your branch is up to date with ${SQ}origin/main${SQ}.
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches with only push shows only push' '
+	test_config -C test push.default current &&
+	test_config -C test status.compareBranches "@{push}" &&
+	git -C test checkout -b feature2 origin/main &&
+	git -C test push origin HEAD &&
+	(cd test && advance work) &&
+	git -C test status >actual &&
+	cat >expect <<-EOF &&
+	On branch feature2
+	Your branch is ahead of ${SQ}origin/feature2${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches shows ahead of both upstream and push branch' '
+	test_config -C test push.default current &&
+	test_config -C test status.compareBranches "@{upstream} @{push}" &&
+	git -C test checkout -b feature3 origin/main &&
+	git -C test push origin HEAD &&
+	(cd test && advance work) &&
+	git -C test status >actual &&
+	cat >expect <<-EOF &&
+	On branch feature3
+	Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
+
+	Your branch is ahead of ${SQ}origin/feature3${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'checkout with status.compareBranches shows both branches' '
+	test_config -C test push.default current &&
+	test_config -C test status.compareBranches "@{upstream} @{push}" &&
+	git -C test checkout feature3 >actual &&
+	cat >expect <<-EOF &&
+	Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
+
+	Your branch is ahead of ${SQ}origin/feature3${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'setup for ahead of tracked but diverged from main' '
+	(
+		cd test &&
+		git checkout -b feature4 origin/main &&
+		advance work1 &&
+		git checkout origin/main &&
+		advance work2 &&
+		git push origin HEAD:main &&
+		git checkout feature4 &&
+		advance work3
+	)
+'
+
+test_expect_success 'status.compareBranches shows diverged and ahead' '
+	test_config -C test push.default current &&
+	test_config -C test status.compareBranches "@{upstream} @{push}" &&
+	git -C test checkout feature4 &&
+	git -C test branch --set-upstream-to origin/main &&
+	git -C test push origin HEAD &&
+	(cd test && advance work) &&
+	git -C test status >actual &&
+	cat >expect <<-EOF &&
+	On branch feature4
+	Your branch and ${SQ}origin/main${SQ} have diverged,
+	and have 3 and 1 different commits each, respectively.
+	  (use "git pull" if you want to integrate the remote branch with yours)
+
+	Your branch is ahead of ${SQ}origin/feature4${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status --no-ahead-behind with status.compareBranches' '
+	test_config -C test push.default current &&
+	test_config -C test status.compareBranches "@{upstream} @{push}" &&
+	git -C test checkout feature4 &&
+	git -C test status --no-ahead-behind >actual &&
+	cat >expect <<-EOF &&
+	On branch feature4
+	Your branch and ${SQ}origin/main${SQ} refer to different commits.
+
+	Your branch and ${SQ}origin/feature4${SQ} refer to different commits.
+	  (use "git status --ahead-behind" for details)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'setup upstream remote' '
+	(
+		cd test &&
+		git remote add upstream ../. &&
+		git fetch upstream
+	)
+'
+
+test_expect_success 'status.compareBranches with upstream and origin remotes' '
+	test_config -C test push.default current &&
+	test_config -C test remote.pushDefault origin &&
+	test_config -C test status.compareBranches "@{upstream} @{push}" &&
+	git -C test checkout -b feature5 upstream/main &&
+	git -C test push origin &&
+	(cd test && advance work) &&
+	git -C test status >actual &&
+	cat >expect <<-EOF &&
+	On branch feature5
+	Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
+
+	Your branch is ahead of ${SQ}origin/feature5${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches supports ordered upstream/push entries' '
+	test_config -C test push.default current &&
+	test_config -C test remote.pushDefault origin &&
+	test_config -C test status.compareBranches "@{push} @{upstream}" &&
+	git -C test checkout -b feature6 upstream/main &&
+	git -C test push origin &&
+	(cd test && advance work) &&
+	git -C test status >actual &&
+	cat >expect <<-EOF &&
+	On branch feature6
+	Your branch is ahead of ${SQ}origin/feature6${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+
+	Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches deduplicates repeated specifiers' '
+	test_config -C test push.default current &&
+	test_config -C test remote.pushDefault origin &&
+	test_config -C test status.compareBranches "@{push} @{upstream} @{push}" &&
+	git -C test checkout -b feature7 upstream/main &&
+	git -C test push origin &&
+	(cd test && advance work) &&
+	git -C test status >actual &&
+	cat >expect <<-EOF &&
+	On branch feature7
+	Your branch is ahead of ${SQ}origin/feature7${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+
+	Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches with diverged push branch' '
+	test_config -C test push.default current &&
+	test_config -C test remote.pushDefault origin &&
+	test_config -C test status.compareBranches "@{upstream} @{push}" &&
+	git -C test checkout -b feature8 upstream/main &&
+	(cd test && advance work81) &&
+	git -C test push origin &&
+	git -C test reset --hard upstream/main &&
+	(cd test && advance work82) &&
+	git -C test status >actual &&
+	cat >expect <<-EOF &&
+	On branch feature8
+	Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
+
+	Your branch and ${SQ}origin/feature8${SQ} have diverged,
+	and have 1 and 1 different commits each, respectively.
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches shows up to date branches' '
+	test_config -C test push.default current &&
+	test_config -C test remote.pushDefault origin &&
+	test_config -C test status.compareBranches "@{upstream} @{push}" &&
+	git -C test checkout -b feature9 upstream/main &&
+	git -C test push origin &&
+	git -C test status >actual &&
+	cat >expect <<-EOF &&
+	On branch feature9
+	Your branch is up to date with ${SQ}upstream/main${SQ}.
+
+	Your branch is up to date with ${SQ}origin/feature9${SQ}.
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status --no-ahead-behind with status.compareBranches up to date' '
+	test_config -C test push.default current &&
+	test_config -C test remote.pushDefault origin &&
+	test_config -C test status.compareBranches "@{upstream} @{push}" &&
+	git -C test checkout feature9 >actual &&
+	git -C test push origin &&
+	git -C test status --no-ahead-behind >actual &&
+	cat >expect <<-EOF &&
+	On branch feature9
+	Your branch is up to date with ${SQ}upstream/main${SQ}.
+
+	Your branch is up to date with ${SQ}origin/feature9${SQ}.
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'checkout with status.compareBranches shows up to date' '
+	test_config -C test push.default current &&
+	test_config -C test remote.pushDefault origin &&
+	test_config -C test status.compareBranches "@{upstream} @{push}" &&
+	git -C test checkout feature9 >actual &&
+	cat >expect <<-EOF &&
+	Your branch is up to date with ${SQ}upstream/main${SQ}.
+
+	Your branch is up to date with ${SQ}origin/feature9${SQ}.
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches with upstream behind and push up to date' '
+	test_config -C test push.default current &&
+	test_config -C test remote.pushDefault origin &&
+	test_config -C test status.compareBranches "@{upstream} @{push}" &&
+	git -C test checkout -b ahead upstream/main &&
+	(cd test && advance work) &&
+	git -C test push upstream HEAD &&
+	git -C test checkout -b feature10 upstream/main &&
+	git -C test push origin &&
+	git -C test branch --set-upstream-to upstream/ahead &&
+	git -C test status >actual &&
+	cat >expect <<-EOF &&
+	On branch feature10
+	Your branch is behind ${SQ}upstream/ahead${SQ} by 1 commit, and can be fast-forwarded.
+	  (use "git pull" to update your local branch)
+
+	Your branch is up to date with ${SQ}origin/feature10${SQ}.
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches with remapped push refspec' '
+	test_config -C test remote.origin.push refs/heads/feature11:refs/heads/remapped &&
+	test_config -C test status.compareBranches "@{upstream} @{push}" &&
+	git -C test checkout -b feature11 origin/main &&
+	git -C test push &&
+	(cd test && advance work) &&
+	git -C test status >actual &&
+	cat >expect <<-EOF &&
+	On branch feature11
+	Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
+
+	Your branch is ahead of ${SQ}origin/remapped${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches with remapped push and upstream remote' '
+	test_config -C test remote.pushDefault origin &&
+	test_config -C test remote.origin.push refs/heads/feature12:refs/heads/remapped &&
+	test_config -C test status.compareBranches "@{upstream} @{push}" &&
+	git -C test checkout -b feature12 upstream/main &&
+	git -C test push origin &&
+	(cd test && advance work) &&
+	git -C test status >actual &&
+	cat >expect <<-EOF &&
+	On branch feature12
+	Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
+
+	Your branch is ahead of ${SQ}origin/remapped${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
 test_done
diff --git a/t/t6403-merge-file.sh b/t/t6403-merge-file.sh
index 06ab4d7..4d6e748 100755
--- a/t/t6403-merge-file.sh
+++ b/t/t6403-merge-file.sh
@@ -428,6 +428,42 @@
 	test_cmp expect actual
 '
 
+test_expect_success 'merge.conflictStyle honored outside repo' '
+	test_config_global merge.conflictStyle diff3 &&
+	cat >nongit-base <<-\EOF &&
+	line1
+	original
+	line3
+	EOF
+	cat >nongit-ours <<-\EOF &&
+	line1
+	ours
+	line3
+	EOF
+	cat >nongit-theirs <<-\EOF &&
+	line1
+	theirs
+	line3
+	EOF
+	cat >expect <<-\EOF &&
+	line1
+	<<<<<<< ours
+	ours
+	||||||| base
+	original
+	=======
+	theirs
+	>>>>>>> theirs
+	line3
+	EOF
+	test_must_fail nongit git merge-file -p \
+		-L ours -L base -L theirs \
+		"$PWD/nongit-ours" \
+		"$PWD/nongit-base" \
+		"$PWD/nongit-theirs" >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'marker size' '
 	cat >expect <<-\EOF &&
 	Dominus regit me,
diff --git a/t/t6422-merge-rename-corner-cases.sh b/t/t6422-merge-rename-corner-cases.sh
index f14c0fb..e18d5a2 100755
--- a/t/t6422-merge-rename-corner-cases.sh
+++ b/t/t6422-merge-rename-corner-cases.sh
@@ -1439,4 +1439,90 @@
 	)
 '
 
+# Testcase preliminary submodule/directory conflict and submodule rename
+#   Commit O: <empty, or additional irrelevant stuff>
+#   Commit A1: introduce "folder" (as a tree)
+#   Commit B1: introduce "folder" (as a submodule)
+#   Commit A2: merge B1 into A1, but keep folder as a tree
+#   Commit B2: merge A1 into B1, but keep folder as a submodule
+#   Merge A2 & B2
+test_setup_submodule_directory_preliminary_conflict () {
+	git init submodule_directory_preliminary_conflict &&
+	(
+		cd submodule_directory_preliminary_conflict &&
+
+		# Trying to do the A2 and B2 merges above is slightly more
+		# challenging with a local submodule (because checking out
+		# another commit has the submodule in the way).  Instead,
+		# first create the commits with the wrong parents but right
+		# trees, in the order A1, A2, B1, B2...
+		#
+		# Then go back and create new A2 & B2 with the correct
+		# parents and the same trees.
+
+		git commit --allow-empty -m orig &&
+
+		git branch A &&
+		git branch B &&
+
+		git checkout B &&
+		mkdir folder &&
+		echo A>folder/A &&
+		echo B>folder/B &&
+		echo C>folder/C &&
+		echo D>folder/D &&
+		echo E>folder/E &&
+		git add folder &&
+		git commit -m B1 &&
+
+		git commit --allow-empty -m B2 &&
+
+		git checkout A &&
+		git init folder &&
+		(
+			cd folder &&
+			>Z &&
+			>Y &&
+			git add Z Y &&
+			git commit -m "original submodule commit"
+		) &&
+		git add folder &&
+		git commit -m A1 &&
+
+		git commit --allow-empty -m A2 &&
+
+		NewA2=$(git commit-tree -p A^ -p B^ -m "Merge B into A" A^{tree}) &&
+		NewB2=$(git commit-tree -p B^ -p A^ -m "Merge A into B" B^{tree}) &&
+		git update-ref refs/heads/A $NewA2 &&
+		git update-ref refs/heads/B $NewB2
+	)
+}
+
+test_expect_success 'submodule/directory preliminary conflict' '
+	test_setup_submodule_directory_preliminary_conflict &&
+	(
+		cd submodule_directory_preliminary_conflict &&
+
+		git checkout A^0 &&
+
+		test_expect_code 1 git merge B^0 &&
+
+		# Make sure the index has the right number of entries
+		git ls-files -s >actual &&
+		test_line_count = 2 actual &&
+
+		# The "folder" as directory should have been resolved away
+		# as part of the merge.  The "folder" as submodule got
+		# renamed to "folder~Temporary merge branch 2" in the
+		# virtual merge base, resulting in a
+		#    "folder~Temporary merge branch 2" -> "folder"
+		# rename in the outermerge for the submodule, which then
+		# becomes part of a rename/delete conflict (because "folder"
+		# as a submodule was deleted in A2).
+		submod=$(git rev-parse A:folder) &&
+		printf "160000 $submod 1\tfolder\n160000 $submod 2\tfolder\n" >expect &&
+		test_cmp expect actual
+	)
+'
+
 test_done
diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh
index 533ac85..53535a8 100755
--- a/t/t6423-merge-rename-directories.sh
+++ b/t/t6423-merge-rename-directories.sh
@@ -5158,13 +5158,18 @@
 		git switch B &&
 		git rm dir/subdir/file &&
 		mkdir dir &&
-		ln -s /dev/null dir/subdir &&
+		if test_have_prereq MINGW
+		then
+			cmd //c 'mklink dir\subdir NUL'
+		else
+			ln -s /dev/null dir/subdir
+		fi &&
 		git add . &&
 		git commit -m "B"
 	)
 }
 
-test_expect_success '12m: Change parent of renamed-dir to symlink on other side' '
+test_expect_success SYMLINKS '12m: Change parent of renamed-dir to symlink on other side' '
 	test_setup_12m &&
 	(
 		cd 12m &&
diff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh
index 0f39ed0..15dd2d9 100755
--- a/t/t6429-merge-sequence-rename-caching.sh
+++ b/t/t6429-merge-sequence-rename-caching.sh
@@ -11,14 +11,13 @@
 #         sure that we are triggering rename caching rather than rename
 #         bypassing.
 #
-# NOTE 2: this testfile uses 'test-tool fast-rebase' instead of either
-#         cherry-pick or rebase.  sequencer.c is only superficially
-#         integrated with merge-ort; it calls merge_switch_to_result()
-#         after EACH merge, which updates the index and working copy AND
-#         throws away the cached results (because merge_switch_to_result()
-#         is only supposed to be called at the end of the sequence).
-#         Integrating them more deeply is a big task, so for now the tests
-#         use 'test-tool fast-rebase'.
+# NOTE 2: this testfile uses replay instead of either cherry-pick or rebase.
+#         sequencer.c is only superficially integrated with merge-ort; it
+#         calls merge_switch_to_result() after EACH merge, which updates the
+#         index and working copy AND throws away the cached results (because
+#         merge_switch_to_result() is only supposed to be called at the end
+#         of the sequence).  Integrating them more deeply is a big task, so
+#         for now the tests use 'git replay'.
 #
 
 
@@ -769,4 +768,82 @@
 	)
 '
 
+#
+# In the following testcase:
+#   Base:     olddir/{valuesX_1, valuesY_1, valuesZ_1}
+#             other/content
+#   Upstream: rename olddir/valuesX_1 -> newdir/valuesX_2
+#   Topic_1:  modify olddir/valuesX_1 -> olddir/valuesX_3
+#   Topic_2:  modify olddir/valuesY,
+#             modify other/content
+#   Expected Pick1: olddir/{valuesY, valuesZ}, newdir/valuesX, other/content
+#   Expected Pick2: olddir/{valuesY, valuesZ}, newdir/valuesX, other/content
+#
+# This testcase presents no problems for git traditionally, but the fact that
+#    olddir/valuesX -> newdir/valuesX
+# gets cached after the first pick presents a problem for the second commit to
+# be replayed, because it appears to be an irrelevant rename, so the trivial
+# directory resolution will resolve newdir/ without recursing into it, giving
+# us no way to apply the cached rename to anything.
+#
+test_expect_success 'rename a file, use it on first pick, but irrelevant on second' '
+	git init rename_a_file_use_it_once_irrelevant_on_second &&
+	(
+		cd rename_a_file_use_it_once_irrelevant_on_second &&
+
+		mkdir olddir/ other/ &&
+		test_seq 3 8 >olddir/valuesX &&
+		test_seq 3 8 >olddir/valuesY &&
+		test_seq 3 8 >olddir/valuesZ &&
+		printf "%s\n" A B C D E F G >other/content &&
+		git add olddir other &&
+		git commit -m orig &&
+
+		git branch upstream &&
+		git branch topic &&
+
+		git switch upstream &&
+		test_seq 1 8 >olddir/valuesX &&
+		git add olddir &&
+		mkdir newdir &&
+		git mv olddir/valuesX newdir &&
+		git commit -m "Renamed (and modified) olddir/valuesX into newdir/" &&
+
+		git switch topic &&
+
+		test_seq 3 10 >olddir/valuesX &&
+		git add olddir &&
+		git commit -m A &&
+
+		test_seq 1 8 >olddir/valuesY &&
+		printf "%s\n" A B C D E F G H I >other/content &&
+		git add olddir/valuesY other &&
+		git commit -m B &&
+
+		#
+		# Actual testing; mostly we want to verify that we do not hit
+		#     git: merge-ort.c:3032: process_renames: Assertion `newinfo && !newinfo->merged.clean` failed.
+		#
+
+		git switch upstream &&
+		git config merge.directoryRenames true &&
+
+		git replay --onto HEAD upstream~1..topic >out &&
+
+		#
+		# ...but we may as well check that the replay gave us a reasonable result
+		#
+
+		git update-ref --stdin <out &&
+		git checkout topic &&
+
+		git ls-files >tracked &&
+		test_line_count = 4 tracked &&
+		test_path_is_file newdir/valuesX &&
+		test_path_is_file olddir/valuesY &&
+		test_path_is_file olddir/valuesZ &&
+		test_path_is_file other/content
+	)
+'
+
 test_done
diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh
index bef472c..ea9aaad 100755
--- a/t/t6500-gc.sh
+++ b/t/t6500-gc.sh
@@ -11,6 +11,7 @@
 	# behavior, make sure we always pack everything to one pack by
 	# default
 	git config gc.bigPackThreshold 2g &&
+	git config set --global maintenance.strategy gc &&
 	test_oid_init
 '
 
diff --git a/t/t6600-test-reach.sh b/t/t6600-test-reach.sh
index 6638d1a..2613075 100755
--- a/t/t6600-test-reach.sh
+++ b/t/t6600-test-reach.sh
@@ -762,4 +762,79 @@
 		--sort=refname --sort=-is-base:commit-2-3
 '
 
+test_expect_success 'rev-list --maximal-only (all positive)' '
+	# Only one maximal.
+	cat >input <<-\EOF &&
+	refs/heads/commit-1-1
+	refs/heads/commit-4-2
+	refs/heads/commit-4-4
+	refs/heads/commit-8-4
+	EOF
+
+	cat >expect <<-EOF &&
+	$(git rev-parse refs/heads/commit-8-4)
+	EOF
+	run_all_modes git rev-list --maximal-only --stdin &&
+
+	# All maximal.
+	cat >input <<-\EOF &&
+	refs/heads/commit-5-2
+	refs/heads/commit-4-3
+	refs/heads/commit-3-4
+	refs/heads/commit-2-5
+	EOF
+
+	cat >expect <<-EOF &&
+	$(git rev-parse refs/heads/commit-5-2)
+	$(git rev-parse refs/heads/commit-4-3)
+	$(git rev-parse refs/heads/commit-3-4)
+	$(git rev-parse refs/heads/commit-2-5)
+	EOF
+	run_all_modes git rev-list --maximal-only --stdin &&
+
+	# Mix of both.
+	cat >input <<-\EOF &&
+	refs/heads/commit-5-2
+	refs/heads/commit-3-2
+	refs/heads/commit-2-5
+	EOF
+
+	cat >expect <<-EOF &&
+	$(git rev-parse refs/heads/commit-5-2)
+	$(git rev-parse refs/heads/commit-2-5)
+	EOF
+	run_all_modes git rev-list --maximal-only --stdin
+'
+
+test_expect_success 'rev-list --maximal-only (range)' '
+	cat >input <<-\EOF &&
+	refs/heads/commit-1-1
+	refs/heads/commit-2-5
+	refs/heads/commit-6-4
+	^refs/heads/commit-4-5
+	EOF
+
+	cat >expect <<-EOF &&
+	$(git rev-parse refs/heads/commit-6-4)
+	EOF
+	run_all_modes git rev-list --maximal-only --stdin &&
+
+	# first-parent changes reachability: the first parent
+	# reduces the second coordinate to 1 before reducing the
+	# first coordinate.
+	cat >input <<-\EOF &&
+	refs/heads/commit-1-1
+	refs/heads/commit-2-5
+	refs/heads/commit-6-4
+	^refs/heads/commit-4-5
+	EOF
+
+	cat >expect <<-EOF &&
+	$(git rev-parse refs/heads/commit-6-4)
+	$(git rev-parse refs/heads/commit-2-5)
+	EOF
+	run_all_modes git rev-list --maximal-only --stdin \
+		--first-parent --exclude-first-parent-only
+'
+
 test_done
diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh
index 5ab4d41..c475769 100755
--- a/t/t7003-filter-branch.sh
+++ b/t/t7003-filter-branch.sh
@@ -92,8 +92,8 @@
 
 test_expect_success 'test that the file was renamed' '
 	test D = "$(git show HEAD:doh --)" &&
-	! test -f D.t &&
-	test -f doh &&
+	test_path_is_missing D.t &&
+	test_path_is_file doh &&
 	test D = "$(cat doh)"
 '
 
@@ -103,10 +103,10 @@
 
 test_expect_success 'test that the directory was renamed' '
 	test dir/D = "$(git show HEAD:diroh/D.t --)" &&
-	! test -d dir &&
-	test -d diroh &&
-	! test -d diroh/dir &&
-	test -f diroh/D.t &&
+	test_path_is_missing dir &&
+	test_path_is_dir diroh &&
+	test_path_is_missing diroh/dir &&
+	test_path_is_file diroh/D.t &&
 	test dir/D = "$(cat diroh/D.t)"
 '
 
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 1083563..ce2ff2a 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -2293,24 +2293,26 @@
 # don't recurse down to tags for trees or blobs pointed to by *those*
 # commits.
 test_expect_success 'Does --[no-]contains stop at commits? Yes!' '
-	cd no-contains &&
-	blob=$(git rev-parse v0.3:v0.3.t) &&
-	tree=$(git rev-parse v0.3^{tree}) &&
-	git tag tag-blob $blob &&
-	git tag tag-tree $tree &&
-	git tag --contains v0.3 >actual &&
-	cat >expected <<-\EOF &&
-	v0.3
-	v0.4
-	v0.5
-	EOF
-	test_cmp expected actual &&
-	git tag --no-contains v0.3 >actual &&
-	cat >expected <<-\EOF &&
-	v0.1
-	v0.2
-	EOF
-	test_cmp expected actual
+	(
+		cd no-contains &&
+		blob=$(git rev-parse v0.3:v0.3.t) &&
+		tree=$(git rev-parse v0.3^{tree}) &&
+		git tag tag-blob $blob &&
+		git tag tag-tree $tree &&
+		git tag --contains v0.3 >actual &&
+		cat >expected <<-\EOF &&
+		v0.3
+		v0.4
+		v0.5
+		EOF
+		test_cmp expected actual &&
+		git tag --no-contains v0.3 >actual &&
+		cat >expected <<-\EOF &&
+		v0.1
+		v0.2
+		EOF
+		test_cmp expected actual
+	)
 '
 
 test_expect_success 'If tag is created then tag message file is unlinked' '
@@ -2332,4 +2334,24 @@
 	test_path_exists .git/TAG_EDITMSG
 '
 
+test_expect_success 'annotated tag version sort' '
+	git tag -a -m "sample 1.0" vsample-1.0 &&
+	git tag -a -m "sample 2.0" vsample-2.0 &&
+	git tag -a -m "sample 10.0" vsample-10.0 &&
+	cat >expect <<-EOF &&
+	vsample-1.0
+	vsample-2.0
+	vsample-10.0
+	EOF
+
+	git tag --list --sort=version:tag vsample-\* >actual &&
+	test_cmp expect actual &&
+
+	# Ensure that we also handle this case alright in the case we have the
+	# peeled values cached e.g. via the packed-refs file.
+	git pack-refs --all &&
+	git tag --list --sort=version:tag vsample-\* &&
+	test_cmp expect actual
+'
+
 test_done
diff --git a/t/t7101-reset-empty-subdirs.sh b/t/t7101-reset-empty-subdirs.sh
index 33d5d5b..d1d3e23 100755
--- a/t/t7101-reset-empty-subdirs.sh
+++ b/t/t7101-reset-empty-subdirs.sh
@@ -34,32 +34,32 @@
 '
 
 test_expect_success 'checking initial files exist after rewind' '
-	test -d path0 &&
-	test -f path0/COPYING
+	test_path_is_dir path0 &&
+	test_path_is_file path0/COPYING
 '
 
 test_expect_success 'checking lack of path1/path2/COPYING' '
-	! test -f path1/path2/COPYING
+	test_path_is_missing path1/path2/COPYING
 '
 
 test_expect_success 'checking lack of path1/COPYING' '
-	! test -f path1/COPYING
+	test_path_is_missing path1/COPYING
 '
 
 test_expect_success 'checking lack of COPYING' '
-	! test -f COPYING
+	test_path_is_missing COPYING
 '
 
-test_expect_success 'checking checking lack of path1/COPYING-TOO' '
-	! test -f path0/COPYING-TOO
+test_expect_success 'checking lack of path0/COPYING-TOO' '
+	test_path_is_missing path0/COPYING-TOO
 '
 
 test_expect_success 'checking lack of path1/path2' '
-	! test -d path1/path2
+	test_path_is_missing path1/path2
 '
 
 test_expect_success 'checking lack of path1' '
-	! test -d path1
+	test_path_is_missing path1
 '
 
 test_done
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
index fd3e7e3..65fcfae 100755
--- a/t/t7400-submodule-basic.sh
+++ b/t/t7400-submodule-basic.sh
@@ -48,6 +48,25 @@
 	git submodule deinit --all
 '
 
+test_expect_success 'submodule add with incomplete .gitmodules' '
+	test_when_finished "rm -f expect actual" &&
+	test_when_finished "git config remove-section submodule.one" &&
+	test_when_finished "git rm -f one .gitmodules" &&
+	git init one &&
+	git -C one commit --allow-empty -m one-initial &&
+	git config -f .gitmodules submodule.one.ignore all &&
+
+	git submodule add ./one &&
+
+	for var in ignore path url
+	do
+		git config -f .gitmodules --get "submodule.one.$var" ||
+		return 1
+	done >actual &&
+	test_write_lines all one ./one >expect &&
+	test_cmp expect actual
+'
+
 test_expect_success 'setup - initial commit' '
 	>t &&
 	git add t &&
@@ -407,6 +426,31 @@
 	test_grep toplevel output.err
 '
 
+test_expect_success 'submodule add of a different algorithm fails' '
+	git init --object-format=sha256 sha256 &&
+	(
+		cd sha256 &&
+		test_commit abc &&
+		git init --object-format=sha1 submodule &&
+		test_commit -C submodule def &&
+		test_must_fail git submodule add "$submodurl" submodule 2>err &&
+		test_grep "cannot add a submodule of a different hash algorithm" err &&
+		git ls-files --stage >entries &&
+		test_grep ! ^160000 entries
+	) &&
+	git init --object-format=sha1 sha1 &&
+	(
+		cd sha1 &&
+		test_commit abc &&
+		git init --object-format=sha256 submodule &&
+		test_commit -C submodule def &&
+		test_must_fail git submodule add "$submodurl" submodule 2>err &&
+		test_grep "cannot add a submodule of a different hash algorithm" err &&
+		git ls-files --stage >entries &&
+		test_grep ! ^160000 entries
+	)
+'
+
 test_expect_success 'setup - add an example entry to .gitmodules' '
 	git config --file=.gitmodules submodule.example.url git://example.com/init.git
 '
diff --git a/t/t7425-submodule-gitdir-path-extension.sh b/t/t7425-submodule-gitdir-path-extension.sh
new file mode 100755
index 0000000..ea86ecf
--- /dev/null
+++ b/t/t7425-submodule-gitdir-path-extension.sh
@@ -0,0 +1,528 @@
+#!/bin/sh
+
+test_description='submodulePathConfig extension works as expected'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-verify-submodule-gitdir-path.sh
+
+test_expect_success 'setup: allow file protocol' '
+       git config --global protocol.file.allow always
+'
+
+test_expect_success 'create repo with mixed extension submodules' '
+	git init -b main legacy-sub &&
+	test_commit -C legacy-sub legacy-initial &&
+	legacy_rev=$(git -C legacy-sub rev-parse HEAD) &&
+
+	git init -b main new-sub &&
+	test_commit -C new-sub new-initial &&
+	new_rev=$(git -C new-sub rev-parse HEAD) &&
+
+	git init -b main main &&
+	(
+		cd main &&
+		git submodule add ../legacy-sub legacy &&
+		test_commit legacy-sub &&
+
+		# trigger the "die_path_inside_submodule" check
+		test_must_fail git submodule add ../new-sub "legacy/nested" &&
+
+		git config core.repositoryformatversion 1 &&
+		git config extensions.submodulePathConfig true &&
+
+		git submodule add ../new-sub "New Sub" &&
+		test_commit new &&
+
+		# retrigger the "die_path_inside_submodule" check with encoding
+		test_must_fail git submodule add ../new-sub "New Sub/nested2"
+       )
+'
+
+test_expect_success 'verify new submodule gitdir config' '
+	git -C main config submodule."New Sub".gitdir >actual &&
+	echo ".git/modules/New Sub" >expect &&
+	test_cmp expect actual &&
+	verify_submodule_gitdir_path main "New Sub" "modules/New Sub"
+'
+
+test_expect_success 'manual add and verify legacy submodule gitdir config' '
+	# the legacy module should not contain a gitdir config, because it
+	# was added before the extension was enabled. Add and test it.
+	test_must_fail git -C main config submodule.legacy.gitdir &&
+	git -C main config submodule.legacy.gitdir .git/modules/legacy &&
+	git -C main config submodule.legacy.gitdir >actual &&
+	echo ".git/modules/legacy" >expect &&
+	test_cmp expect actual &&
+	verify_submodule_gitdir_path main "legacy" "modules/legacy"
+'
+
+test_expect_success 'gitdir config path is relative for both absolute and relative urls' '
+	test_when_finished "rm -rf relative-cfg-path-test" &&
+	git init -b main relative-cfg-path-test &&
+	(
+		cd relative-cfg-path-test &&
+		git config core.repositoryformatversion 1 &&
+		git config extensions.submodulePathConfig true &&
+
+		# Test with absolute URL
+		git submodule add "$TRASH_DIRECTORY/new-sub" sub-abs &&
+		git config submodule.sub-abs.gitdir >actual &&
+		echo ".git/modules/sub-abs" >expect &&
+		test_cmp expect actual &&
+
+		# Test with relative URL
+		git submodule add ../new-sub sub-rel &&
+		git config submodule.sub-rel.gitdir >actual &&
+		echo ".git/modules/sub-rel" >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'clone from repo with both legacy and new-style submodules' '
+	git clone --recurse-submodules main cloned-non-extension &&
+	(
+		cd cloned-non-extension &&
+
+		test_path_is_dir .git/modules/legacy &&
+		test_path_is_dir .git/modules/"New Sub" &&
+
+		test_must_fail git config submodule.legacy.gitdir &&
+		test_must_fail git config submodule."New Sub".gitdir &&
+
+		git submodule status >list &&
+		test_grep "$legacy_rev legacy" list &&
+		test_grep "$new_rev New Sub" list
+	) &&
+
+	git clone -c extensions.submodulePathConfig=true --recurse-submodules main cloned-extension &&
+	(
+		cd cloned-extension &&
+
+		test_path_is_dir .git/modules/legacy &&
+		test_path_is_dir ".git/modules/New Sub" &&
+
+		git config submodule.legacy.gitdir &&
+		git config submodule."New Sub".gitdir &&
+
+		git submodule status >list &&
+		test_grep "$legacy_rev legacy" list &&
+		test_grep "$new_rev New Sub" list
+	)
+'
+
+test_expect_success 'commit and push changes to encoded submodules' '
+	git -C legacy-sub config receive.denyCurrentBranch updateInstead &&
+	git -C new-sub config receive.denyCurrentBranch updateInstead &&
+	git -C main config receive.denyCurrentBranch updateInstead &&
+	(
+		cd cloned-extension &&
+
+		git -C legacy switch --track -C main origin/main  &&
+		test_commit -C legacy second-commit &&
+		git -C legacy push &&
+
+		git -C "New Sub" switch --track -C main origin/main &&
+		test_commit -C "New Sub" second-commit &&
+		git -C "New Sub" push &&
+
+		# Stage and commit submodule changes in superproject
+		git switch --track -C main origin/main  &&
+		git add legacy "New Sub" &&
+		git commit -m "update submodules" &&
+
+		# push superproject commit to main repo
+		git push
+	) &&
+
+	# update expected legacy & new submodule checksums
+	legacy_rev=$(git -C legacy-sub rev-parse HEAD) &&
+	new_rev=$(git -C new-sub rev-parse HEAD)
+'
+
+test_expect_success 'fetch mixed submodule changes and verify updates' '
+	(
+		cd main &&
+
+		# only update submodules because superproject was
+		# pushed into at the end of last test
+		git submodule update --init --recursive &&
+
+		test_path_is_dir .git/modules/legacy &&
+		test_path_is_dir ".git/modules/New Sub" &&
+
+		# Verify both submodules are at the expected commits
+		git submodule status >list &&
+		test_grep "$legacy_rev legacy" list &&
+		test_grep "$new_rev New Sub" list
+	)
+'
+
+test_expect_success '`git init` respects init.defaultSubmodulePathConfig' '
+	test_config_global init.defaultSubmodulePathConfig true &&
+	git init repo-init &&
+	git -C repo-init config extensions.submodulePathConfig >actual &&
+	echo true >expect &&
+	test_cmp expect actual &&
+	# create a submodule and check gitdir
+	(
+		cd repo-init &&
+		git init -b main sub &&
+		test_commit -C sub sub-initial &&
+		git submodule add ./sub sub &&
+		git config submodule.sub.gitdir >actual &&
+		echo ".git/modules/sub" >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '`git init` does not set extension by default' '
+	git init upstream &&
+	test_commit -C upstream initial &&
+	test_must_fail git -C upstream config extensions.submodulePathConfig &&
+	# create a pair of submodules and check gitdir is not created
+	git init -b main sub &&
+	test_commit -C sub sub-initial &&
+	(
+		cd upstream &&
+		git submodule add ../sub sub1 &&
+		test_path_is_dir .git/modules/sub1 &&
+		test_must_fail git config submodule.sub1.gitdir &&
+		git submodule add ../sub sub2 &&
+		test_path_is_dir .git/modules/sub2 &&
+		test_must_fail git config submodule.sub2.gitdir &&
+		git commit -m "Add submodules"
+	)
+'
+
+test_expect_success '`git clone` does not set extension by default' '
+	test_when_finished "rm -rf repo-clone-no-ext" &&
+	git clone upstream repo-clone-no-ext &&
+	(
+		cd repo-clone-no-ext &&
+
+		test_must_fail git config extensions.submodulePathConfig &&
+		test_path_is_missing .git/modules/sub1 &&
+		test_path_is_missing .git/modules/sub2 &&
+
+		# create a submodule and check gitdir is not created
+		git submodule add ../sub sub3 &&
+		test_must_fail git config submodule.sub3.gitdir
+	)
+'
+
+test_expect_success '`git clone --recurse-submodules` does not set extension by default' '
+	test_when_finished "rm -rf repo-clone-no-ext" &&
+	git clone --recurse-submodules upstream repo-clone-no-ext &&
+	(
+		cd repo-clone-no-ext &&
+
+		# verify that that submodules do not have gitdir set
+		test_must_fail git config extensions.submodulePathConfig &&
+		test_path_is_dir .git/modules/sub1 &&
+		test_must_fail git config submodule.sub1.gitdir &&
+		test_path_is_dir .git/modules/sub2 &&
+		test_must_fail git config submodule.sub2.gitdir &&
+
+		# create another submodule and check that gitdir is not created
+		git submodule add ../sub sub3 &&
+		test_path_is_dir .git/modules/sub3 &&
+		test_must_fail git config submodule.sub3.gitdir
+	)
+
+'
+
+test_expect_success '`git clone` respects init.defaultSubmodulePathConfig' '
+	test_when_finished "rm -rf repo-clone" &&
+	test_config_global init.defaultSubmodulePathConfig true &&
+	git clone upstream repo-clone &&
+	(
+		cd repo-clone &&
+
+		# verify new repo extension is inherited from global config
+		git config extensions.submodulePathConfig >actual &&
+		echo true >expect &&
+		test_cmp expect actual &&
+
+		# new submodule has a gitdir config
+		git submodule add ../sub sub &&
+		test_path_is_dir .git/modules/sub &&
+		git config submodule.sub.gitdir >actual &&
+		echo ".git/modules/sub" >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '`git clone --recurse-submodules` respects init.defaultSubmodulePathConfig' '
+	test_when_finished "rm -rf repo-clone-recursive" &&
+	test_config_global init.defaultSubmodulePathConfig true &&
+	git clone  --recurse-submodules upstream repo-clone-recursive &&
+	(
+		cd repo-clone-recursive &&
+
+		# verify new repo extension is inherited from global config
+		git config extensions.submodulePathConfig >actual &&
+		echo true >expect &&
+		test_cmp expect actual &&
+
+		# previous submodules should exist
+		git config submodule.sub1.gitdir &&
+		git config submodule.sub2.gitdir &&
+		test_path_is_dir .git/modules/sub1 &&
+		test_path_is_dir .git/modules/sub2 &&
+
+		# create another submodule and check that gitdir is created
+		git submodule add ../sub new-sub &&
+		test_path_is_dir .git/modules/new-sub &&
+		git config submodule.new-sub.gitdir >actual &&
+		echo ".git/modules/new-sub" >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'submodule--helper migrates legacy modules' '
+	(
+		cd upstream &&
+
+		# previous submodules exist and were not migrated yet
+		test_must_fail git config submodule.sub1.gitdir &&
+		test_must_fail git config submodule.sub2.gitdir &&
+		test_path_is_dir .git/modules/sub1 &&
+		test_path_is_dir .git/modules/sub2 &&
+
+		# run migration
+		git submodule--helper migrate-gitdir-configs &&
+
+		# test that migration worked
+		git config submodule.sub1.gitdir >actual &&
+		echo ".git/modules/sub1" >expect &&
+		test_cmp expect actual &&
+		git config submodule.sub2.gitdir >actual &&
+		echo ".git/modules/sub2" >expect &&
+		test_cmp expect actual &&
+
+		# repository extension is enabled after migration
+		git config extensions.submodulePathConfig >actual &&
+		echo "true" >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '`git clone --recurse-submodules` works after migration' '
+	test_when_finished "rm -rf repo-clone-recursive" &&
+
+	# test with extension disabled after the upstream repo was migrated
+	git clone --recurse-submodules upstream repo-clone-recursive &&
+	(
+		cd repo-clone-recursive &&
+
+		# init.defaultSubmodulePathConfig was disabled before clone, so
+		# the repo extension config should also be off, the migration ignored
+		test_must_fail git config extensions.submodulePathConfig &&
+
+		# modules should look like there was no migration done
+		test_must_fail git config submodule.sub1.gitdir &&
+		test_must_fail git config submodule.sub2.gitdir &&
+		test_path_is_dir .git/modules/sub1 &&
+		test_path_is_dir .git/modules/sub2
+	) &&
+	rm -rf repo-clone-recursive &&
+
+	# enable the extension, then retry the clone
+	test_config_global init.defaultSubmodulePathConfig true &&
+	git clone --recurse-submodules upstream repo-clone-recursive &&
+	(
+		cd repo-clone-recursive &&
+
+		# repository extension is enabled
+		git config extensions.submodulePathConfig >actual &&
+		echo "true" >expect &&
+		test_cmp expect actual &&
+
+		# gitdir configs exist for submodules
+		git config submodule.sub1.gitdir &&
+		git config submodule.sub2.gitdir &&
+		test_path_is_dir .git/modules/sub1 &&
+		test_path_is_dir .git/modules/sub2
+	)
+'
+
+test_expect_success 'setup submodules with nested git dirs' '
+	git init nested &&
+	test_commit -C nested nested &&
+	(
+		cd nested &&
+		cat >.gitmodules <<-EOF &&
+		[submodule "hippo"]
+			url = .
+			path = thing1
+		[submodule "hippo/hooks"]
+			url = .
+			path = thing2
+		EOF
+		git clone . thing1 &&
+		git clone . thing2 &&
+		git add .gitmodules thing1 thing2 &&
+		test_tick &&
+		git commit -m nested
+	)
+'
+
+test_expect_success 'git dirs of encoded sibling submodules must not be nested' '
+	git clone -c extensions.submodulePathConfig=true --recurse-submodules nested clone_nested &&
+
+	verify_submodule_gitdir_path clone_nested hippo modules/hippo &&
+	git -C clone_nested config submodule.hippo.gitdir >actual &&
+	test_grep "\.git/modules/hippo$" actual &&
+
+	verify_submodule_gitdir_path clone_nested hippo/hooks modules/hippo%2fhooks &&
+	git -C clone_nested config submodule.hippo/hooks.gitdir >actual &&
+	test_grep "\.git/modules/hippo%2fhooks$" actual
+'
+
+test_expect_success 'submodule git dir nesting detection must work with parallel cloning' '
+	git clone -c extensions.submodulePathConfig=true --recurse-submodules --jobs=2 nested clone_parallel &&
+
+	verify_submodule_gitdir_path clone_parallel hippo modules/hippo &&
+	git -C clone_nested config submodule.hippo.gitdir >actual &&
+	test_grep "\.git/modules/hippo$" actual &&
+
+	verify_submodule_gitdir_path clone_parallel hippo/hooks modules/hippo%2fhooks &&
+	git -C clone_nested config submodule.hippo/hooks.gitdir >actual &&
+	test_grep "\.git/modules/hippo%2fhooks$" actual
+'
+
+test_expect_success 'disabling extensions.submodulePathConfig prevents nested submodules' '
+	(
+		cd clone_nested &&
+		# disable extension and verify failure
+		git config --replace-all extensions.submodulePathConfig false &&
+		test_must_fail git submodule add ./thing2 hippo/foobar &&
+		# re-enable extension and verify it works
+		git config --replace-all extensions.submodulePathConfig true &&
+		git submodule add ./thing2 hippo/foobar
+	)
+'
+
+test_expect_success CASE_INSENSITIVE_FS 'verify case-folding conflicts are correctly encoded' '
+	git clone -c extensions.submodulePathConfig=true main cloned-folding &&
+	(
+		cd cloned-folding &&
+
+		# conflict: the "folding" gitdir will already be taken
+		git submodule add ../new-sub "folding" &&
+		test_commit lowercase &&
+		git submodule add ../new-sub "FoldinG" &&
+		test_commit uppercase &&
+
+		# conflict: the "foo" gitdir will already be taken
+		git submodule add ../new-sub "FOO" &&
+		test_commit uppercase-foo &&
+		git submodule add ../new-sub "foo" &&
+		test_commit lowercase-foo &&
+
+		# create a multi conflict between foobar, fooBar and foo%42ar
+		# the "foo" gitdir will already be taken
+		git submodule add ../new-sub "foobar" &&
+		test_commit lowercase-foobar &&
+		git submodule add ../new-sub "foo%42ar" &&
+		test_commit encoded-foo%42ar &&
+		git submodule add ../new-sub "fooBar" &&
+		test_commit mixed-fooBar
+	) &&
+	verify_submodule_gitdir_path cloned-folding "folding" "modules/folding" &&
+	verify_submodule_gitdir_path cloned-folding "FoldinG" "modules/%46oldin%47" &&
+	verify_submodule_gitdir_path cloned-folding "FOO" "modules/FOO" &&
+	verify_submodule_gitdir_path cloned-folding "foo" "modules/foo0" &&
+	verify_submodule_gitdir_path cloned-folding "foobar" "modules/foobar" &&
+	verify_submodule_gitdir_path cloned-folding "foo%42ar" "modules/foo%42ar" &&
+	verify_submodule_gitdir_path cloned-folding "fooBar" "modules/fooBar0"
+'
+
+test_expect_success CASE_INSENSITIVE_FS 'verify hashing conflict resolution as a last resort' '
+	git clone -c extensions.submodulePathConfig=true main cloned-hash &&
+	(
+		cd cloned-hash &&
+
+		# conflict: add all submodule conflicting variants until we reach the
+		# final hashing conflict resolution for submodule "foo"
+		git submodule add ../new-sub "foo" &&
+		git submodule add ../new-sub "foo0" &&
+		git submodule add ../new-sub "foo1" &&
+		git submodule add ../new-sub "foo2" &&
+		git submodule add ../new-sub "foo3" &&
+		git submodule add ../new-sub "foo4" &&
+		git submodule add ../new-sub "foo5" &&
+		git submodule add ../new-sub "foo6" &&
+		git submodule add ../new-sub "foo7" &&
+		git submodule add ../new-sub "foo8" &&
+		git submodule add ../new-sub "foo9" &&
+		git submodule add ../new-sub "%46oo" &&
+		git submodule add ../new-sub "%46oo0" &&
+		git submodule add ../new-sub "%46oo1" &&
+		git submodule add ../new-sub "%46oo2" &&
+		git submodule add ../new-sub "%46oo3" &&
+		git submodule add ../new-sub "%46oo4" &&
+		git submodule add ../new-sub "%46oo5" &&
+		git submodule add ../new-sub "%46oo6" &&
+		git submodule add ../new-sub "%46oo7" &&
+		git submodule add ../new-sub "%46oo8" &&
+		git submodule add ../new-sub "%46oo9" &&
+		test_commit add-foo-variants &&
+		git submodule add ../new-sub "Foo" &&
+		test_commit add-uppercase-foo
+	) &&
+	verify_submodule_gitdir_path cloned-hash "foo" "modules/foo" &&
+	verify_submodule_gitdir_path cloned-hash "foo0" "modules/foo0" &&
+	verify_submodule_gitdir_path cloned-hash "foo1" "modules/foo1" &&
+	verify_submodule_gitdir_path cloned-hash "foo2" "modules/foo2" &&
+	verify_submodule_gitdir_path cloned-hash "foo3" "modules/foo3" &&
+	verify_submodule_gitdir_path cloned-hash "foo4" "modules/foo4" &&
+	verify_submodule_gitdir_path cloned-hash "foo5" "modules/foo5" &&
+	verify_submodule_gitdir_path cloned-hash "foo6" "modules/foo6" &&
+	verify_submodule_gitdir_path cloned-hash "foo7" "modules/foo7" &&
+	verify_submodule_gitdir_path cloned-hash "foo8" "modules/foo8" &&
+	verify_submodule_gitdir_path cloned-hash "foo9" "modules/foo9" &&
+	verify_submodule_gitdir_path cloned-hash "%46oo" "modules/%46oo" &&
+	verify_submodule_gitdir_path cloned-hash "%46oo0" "modules/%46oo0" &&
+	verify_submodule_gitdir_path cloned-hash "%46oo1" "modules/%46oo1" &&
+	verify_submodule_gitdir_path cloned-hash "%46oo2" "modules/%46oo2" &&
+	verify_submodule_gitdir_path cloned-hash "%46oo3" "modules/%46oo3" &&
+	verify_submodule_gitdir_path cloned-hash "%46oo4" "modules/%46oo4" &&
+	verify_submodule_gitdir_path cloned-hash "%46oo5" "modules/%46oo5" &&
+	verify_submodule_gitdir_path cloned-hash "%46oo6" "modules/%46oo6" &&
+	verify_submodule_gitdir_path cloned-hash "%46oo7" "modules/%46oo7" &&
+	verify_submodule_gitdir_path cloned-hash "%46oo8" "modules/%46oo8" &&
+	verify_submodule_gitdir_path cloned-hash "%46oo9" "modules/%46oo9" &&
+	hash=$(printf "Foo" | git hash-object --stdin) &&
+	verify_submodule_gitdir_path cloned-hash "Foo" "modules/${hash}"
+'
+
+test_expect_success 'submodule gitdir conflicts with previously encoded name (local config)' '
+	git init -b main super_with_encoded &&
+	(
+		cd super_with_encoded &&
+
+		git config core.repositoryformatversion 1 &&
+		git config extensions.submodulePathConfig true &&
+
+		# Add a submodule with a nested path
+		git submodule add --name "nested/sub" ../sub nested/sub &&
+		test_commit add-encoded-gitdir &&
+
+		verify_submodule_gitdir_path . "nested/sub" "modules/nested%2fsub" &&
+		test_path_is_dir ".git/modules/nested%2fsub"
+	) &&
+
+	# create a submodule that will conflict with the encoded gitdir name:
+	# the existing gitdir is ".git/modules/nested%2fsub", which is used
+	# by "nested/sub", so the new submod will get another (non-conflicting)
+	# name: "nested%252fsub".
+	(
+		cd super_with_encoded &&
+		git submodule add ../sub "nested%2fsub" &&
+		verify_submodule_gitdir_path . "nested%2fsub" "modules/nested%252fsub" &&
+		test_path_is_dir ".git/modules/nested%252fsub"
+	)
+'
+
+test_done
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index abad229..a5e21bf 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -1576,7 +1576,7 @@
 
 test_expect_success 'git commit --dry-run will show a staged but ignored submodule' '
 	git reset HEAD^ &&
-	git add sm &&
+	git add --force sm &&
 	cat >expect << EOF &&
 On branch main
 Your branch and '\''upstream'\'' have diverged,
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 25e8e97..08e82f7 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -594,6 +594,15 @@
 	test_cmp expected actual
 '
 
+test_expect_success 'rebase in a linked worktree' '
+	test_might_fail git rebase --abort &&
+	git worktree add wt &&
+	test_when_finished "test_might_fail git -C wt rebase --abort;
+				git worktree remove wt" &&
+	GIT_SEQUENCE_EDITOR="echo break >" git -C wt rebase -i HEAD &&
+	git -C wt status >actual &&
+	test_grep "interactive rebase in progress" actual
+'
 
 test_expect_success 'prepare am_session' '
 	git reset --hard main &&
diff --git a/t/t7519/fsmonitor-watchman b/t/t7519/fsmonitor-watchman
index 264b9da..bcc055c 100755
--- a/t/t7519/fsmonitor-watchman
+++ b/t/t7519/fsmonitor-watchman
@@ -38,8 +38,6 @@
 	$git_work_tree = Cwd::cwd();
 }
 
-my $retry = 1;
-
 launch_watchman();
 
 sub launch_watchman {
@@ -92,9 +90,8 @@
 
 	my $o = $json_pkg->new->utf8->decode($response);
 
-	if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) {
+	if ($o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) {
 		print STDERR "Adding '$git_work_tree' to watchman's watch list.\n";
-		$retry--;
 		qx/watchman watch "$git_work_tree"/;
 		die "Failed to make watchman watch '$git_work_tree'.\n" .
 		    "Falling back to scanning...\n" if $? != 0;
@@ -109,7 +106,6 @@
 		close $fh;
 
 		print "/\0";
-		eval { launch_watchman() };
 		exit 0;
 	}
 
diff --git a/t/t7519/fsmonitor-watchman-v2 b/t/t7519/fsmonitor-watchman-v2
index 14ed0aa..368604c 100755
--- a/t/t7519/fsmonitor-watchman-v2
+++ b/t/t7519/fsmonitor-watchman-v2
@@ -29,8 +29,6 @@
 
 my $git_work_tree = get_working_dir();
 
-my $retry = 1;
-
 my $json_pkg;
 eval {
 	require JSON::XS;
@@ -122,8 +120,7 @@
 sub is_work_tree_watched {
 	my ($output) = @_;
 	my $error = $output->{error};
-	if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
-		$retry--;
+	if ($error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
 		my $response = qx/watchman watch "$git_work_tree"/;
 		die "Failed to make watchman watch '$git_work_tree'.\n" .
 		    "Falling back to scanning...\n" if $? != 0;
@@ -141,15 +138,12 @@
 		# Watchman query just to get it over with now so we won't pay
 		# the cost in git to look up each individual file.
 		my $o = watchman_clock();
-		$error = $output->{error};
+		$error = $o->{error};
 
 		die "Watchman: $error.\n" .
 		"Falling back to scanning...\n" if $error;
 
 		output_result($o->{clock}, ("/"));
-		$last_update_token = $o->{clock};
-
-		eval { launch_watchman() };
 		return 0;
 	}
 
diff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh
index 409cd0c..e7b4065 100755
--- a/t/t7527-builtin-fsmonitor.sh
+++ b/t/t7527-builtin-fsmonitor.sh
@@ -408,9 +408,8 @@
 # ensure we are getting the OS notifications and do not try to confirm what
 # is reported by `git status`.
 #
-# We run a simple query after modifying the filesystem just to introduce
-# a bit of a delay so that the trace logging from the daemon has time to
-# get flushed to disk.
+# We use retry_grep to handle races between the daemon writing events
+# to the trace file and our check.
 #
 # We `reset` and `clean` at the bottom of each test (and before stopping the
 # daemon) because these commands might implicitly restart the daemon.
@@ -422,6 +421,24 @@
 	rm -f .git/trace
 }
 
+# Retry a grep up to RETRY_TIMEOUT times until it succeeds.
+#
+RETRY_TIMEOUT=5
+
+retry_grep () {
+	nr_tries_left=$RETRY_TIMEOUT
+	until grep "$1" "$2" 2>/dev/null
+	do
+		if test $nr_tries_left -eq 0
+		then
+			grep "$1" "$2"
+			return
+		fi
+		nr_tries_left=$(($nr_tries_left - 1))
+		sleep 1
+	done
+}
+
 test_expect_success 'edit some files' '
 	test_when_finished clean_up_repo_and_stop_daemon &&
 
@@ -429,12 +446,10 @@
 
 	edit_files &&
 
-	test-tool fsmonitor-client query --token 0 &&
-
-	grep "^event: dir1/modified$"  .git/trace &&
-	grep "^event: dir2/modified$"  .git/trace &&
-	grep "^event: modified$"       .git/trace &&
-	grep "^event: dir1/untracked$" .git/trace
+	retry_grep "^event: dir1/modified$" .git/trace &&
+	retry_grep "^event: dir2/modified$"  .git/trace &&
+	retry_grep "^event: modified$"       .git/trace &&
+	retry_grep "^event: dir1/untracked$" .git/trace
 '
 
 test_expect_success 'create some files' '
@@ -444,11 +459,9 @@
 
 	create_files &&
 
-	test-tool fsmonitor-client query --token 0 &&
-
-	grep "^event: dir1/new$" .git/trace &&
-	grep "^event: dir2/new$" .git/trace &&
-	grep "^event: new$"      .git/trace
+	retry_grep "^event: dir1/new$" .git/trace &&
+	retry_grep "^event: dir2/new$" .git/trace &&
+	retry_grep "^event: new$"      .git/trace
 '
 
 test_expect_success 'delete some files' '
@@ -458,11 +471,9 @@
 
 	delete_files &&
 
-	test-tool fsmonitor-client query --token 0 &&
-
-	grep "^event: dir1/delete$" .git/trace &&
-	grep "^event: dir2/delete$" .git/trace &&
-	grep "^event: delete$"      .git/trace
+	retry_grep "^event: dir1/delete$" .git/trace &&
+	retry_grep "^event: dir2/delete$" .git/trace &&
+	retry_grep "^event: delete$"      .git/trace
 '
 
 test_expect_success 'rename some files' '
@@ -472,14 +483,12 @@
 
 	rename_files &&
 
-	test-tool fsmonitor-client query --token 0 &&
-
-	grep "^event: dir1/rename$"  .git/trace &&
-	grep "^event: dir2/rename$"  .git/trace &&
-	grep "^event: rename$"       .git/trace &&
-	grep "^event: dir1/renamed$" .git/trace &&
-	grep "^event: dir2/renamed$" .git/trace &&
-	grep "^event: renamed$"      .git/trace
+	retry_grep "^event: dir1/rename$" .git/trace &&
+	retry_grep "^event: dir2/rename$"  .git/trace &&
+	retry_grep "^event: rename$"       .git/trace &&
+	retry_grep "^event: dir1/renamed$" .git/trace &&
+	retry_grep "^event: dir2/renamed$" .git/trace &&
+	retry_grep "^event: renamed$"      .git/trace
 '
 
 test_expect_success 'rename directory' '
@@ -489,10 +498,8 @@
 
 	mv dirtorename dirrenamed &&
 
-	test-tool fsmonitor-client query --token 0 &&
-
-	grep "^event: dirtorename/*$" .git/trace &&
-	grep "^event: dirrenamed/*$"  .git/trace
+	retry_grep "^event: dirtorename/*$" .git/trace &&
+	retry_grep "^event: dirrenamed/*$"  .git/trace
 '
 
 test_expect_success 'file changes to directory' '
@@ -502,10 +509,8 @@
 
 	file_to_directory &&
 
-	test-tool fsmonitor-client query --token 0 &&
-
-	grep "^event: delete$"     .git/trace &&
-	grep "^event: delete/new$" .git/trace
+	retry_grep "^event: delete$" .git/trace &&
+	retry_grep "^event: delete/new$" .git/trace
 '
 
 test_expect_success 'directory changes to a file' '
@@ -515,9 +520,7 @@
 
 	directory_to_file &&
 
-	test-tool fsmonitor-client query --token 0 &&
-
-	grep "^event: dir1$" .git/trace
+	retry_grep "^event: dir1$" .git/trace
 '
 
 # The next few test cases exercise the token-resync code.  When filesystem
diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh
index 73b78bd..63ef63f 100755
--- a/t/t7700-repack.sh
+++ b/t/t7700-repack.sh
@@ -217,6 +217,7 @@
 		cd keep-pack &&
 		# avoid producing different packs due to delta/base choices
 		git config pack.window 0 &&
+		git config maintenance.auto false &&
 		P1=$(commit_and_pack 1) &&
 		P2=$(commit_and_pack 2) &&
 		P3=$(commit_and_pack 3) &&
@@ -260,6 +261,7 @@
 
 		# Avoid producing different packs due to delta/base choices
 		git config pack.window 0 &&
+		git config maintenance.auto false &&
 		P1=$(commit_and_pack 1) &&
 		P2=$(commit_and_pack 2) &&
 		P3=$(commit_and_pack 3) &&
@@ -319,7 +321,7 @@
 
 test_expect_success 'auto-bitmaps do not complain if unavailable' '
 	test_config -C bare.git pack.packSizeLimit 1M &&
-	blob=$(test-tool genrandom big $((1024*1024)) |
+	blob=$(test-tool genrandom big 1m |
 	       git -C bare.git hash-object -w --stdin) &&
 	git -C bare.git update-ref refs/tags/big $blob &&
 
@@ -495,9 +497,9 @@
 		cd max-pack-size &&
 		test_commit base &&
 		# two blobs which exceed the maximum pack size
-		test-tool genrandom foo 1048576 >foo &&
+		test-tool genrandom foo 1m >foo &&
 		git hash-object -w foo &&
-		test-tool genrandom bar 1048576 >bar &&
+		test-tool genrandom bar 1m >bar &&
 		git hash-object -w bar &&
 		git add foo bar &&
 		git commit -m "adding foo and bar"
@@ -534,6 +536,7 @@
 	(
 		cd midx &&
 		git config core.multiPackIndex true &&
+		git config maintenance.auto false &&
 
 		test_commit base
 	)
diff --git a/t/t7703-repack-geometric.sh b/t/t7703-repack-geometric.sh
index 9fc1626..04d5d8f 100755
--- a/t/t7703-repack-geometric.sh
+++ b/t/t7703-repack-geometric.sh
@@ -287,6 +287,41 @@
 	)
 '
 
+test_expect_success '--geometric --write-midx retains up-to-date MIDX without bitmap index' '
+	test_when_finished "rm -fr repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit initial &&
+
+		test_path_is_missing .git/objects/pack/multi-pack-index &&
+		git repack --geometric=2 --write-midx --no-write-bitmap-index &&
+		test_path_is_file .git/objects/pack/multi-pack-index &&
+		test-tool chmtime =0 .git/objects/pack/multi-pack-index &&
+
+		ls -l .git/objects/pack/ >expect &&
+		git repack --geometric=2 --write-midx --no-write-bitmap-index &&
+		ls -l .git/objects/pack/ >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--geometric --write-midx retains up-to-date MIDX with bitmap index' '
+	test_when_finished "rm -fr repo" &&
+	git init repo &&
+	test_commit -C repo initial &&
+
+	test_path_is_missing repo/.git/objects/pack/multi-pack-index &&
+	git -C repo repack --geometric=2 --write-midx --write-bitmap-index &&
+	test_path_is_file repo/.git/objects/pack/multi-pack-index &&
+	test-tool chmtime =0 repo/.git/objects/pack/multi-pack-index &&
+
+	ls -l repo/.git/objects/pack/ >expect &&
+	git -C repo repack --geometric=2 --write-midx --write-bitmap-index &&
+	ls -l repo/.git/objects/pack/ >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success '--geometric --write-midx with packfiles in main and alternate ODB' '
 	test_when_finished "rm -fr shared member" &&
 
@@ -445,4 +480,65 @@
 	test_path_is_file member/.git/objects/pack/multi-pack-index-*.bitmap
 '
 
+write_packfile () {
+	NR="$1"
+	PREFIX="$2"
+
+	printf "blob\ndata <<EOB\n$PREFIX %s\nEOB\n" $(test_seq $NR) |
+		git fast-import &&
+	git pack-objects --pack-loose-unreachable .git/objects/pack/pack &&
+	git prune-packed
+}
+
+write_promisor_packfile () {
+	PACKFILE=$(write_packfile "$@") &&
+	touch .git/objects/pack/pack-$PACKFILE.promisor &&
+	echo "$PACKFILE"
+}
+
+test_expect_success 'geometric repack works with promisor packs' '
+	test_when_finished "rm -fr repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		git config set maintenance.auto false &&
+		git remote add promisor garbage &&
+		git config set remote.promisor.promisor true &&
+
+		# Packs A and B need to be merged.
+		NORMAL_A=$(write_packfile 2 normal-a) &&
+		NORMAL_B=$(write_packfile 2 normal-b) &&
+		NORMAL_C=$(write_packfile 14 normal-c) &&
+
+		# Packs A, B and C need to be merged.
+		PROMISOR_A=$(write_promisor_packfile 1 promisor-a) &&
+		PROMISOR_B=$(write_promisor_packfile 3 promisor-b) &&
+		PROMISOR_C=$(write_promisor_packfile 3 promisor-c) &&
+		PROMISOR_D=$(write_promisor_packfile 20 promisor-d) &&
+		PROMISOR_E=$(write_promisor_packfile 40 promisor-e) &&
+
+		git cat-file --batch-all-objects --batch-check="%(objectname)" >objects-expect &&
+
+		ls .git/objects/pack/*.pack >packs-before &&
+		test_line_count = 8 packs-before &&
+		git repack --geometric=2 -d &&
+		ls .git/objects/pack/*.pack >packs-after &&
+		test_line_count = 5 packs-after &&
+		test_grep ! "$NORMAL_A" packs-after &&
+		test_grep ! "$NORMAL_B" packs-after &&
+		test_grep "$NORMAL_C" packs-after &&
+		test_grep ! "$PROMISOR_A" packs-after &&
+		test_grep ! "$PROMISOR_B" packs-after &&
+		test_grep ! "$PROMISOR_C" packs-after &&
+		test_grep "$PROMISOR_D" packs-after &&
+		test_grep "$PROMISOR_E" packs-after &&
+
+		ls .git/objects/pack/*.promisor >promisors &&
+		test_line_count = 3 promisors &&
+
+		git cat-file --batch-all-objects --batch-check="%(objectname)" >objects-actual &&
+		test_cmp objects-expect objects-actual
+	)
+'
+
 test_done
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index 9b74db5..8a91ff3 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -647,21 +647,21 @@
 '
 
 write_script modify-right-file <<\EOF
-echo "new content" >"$2/file"
+echo "modified content" >"$2/file"
 EOF
 
 run_dir_diff_test 'difftool --dir-diff syncs worktree with unstaged change' '
 	test_when_finished git reset --hard &&
 	echo "orig content" >file &&
 	git difftool -d $symlinks --extcmd "$PWD/modify-right-file" branch &&
-	echo "new content" >expect &&
+	echo "modified content" >expect &&
 	test_cmp expect file
 '
 
 run_dir_diff_test 'difftool --dir-diff syncs worktree without unstaged change' '
 	test_when_finished git reset --hard &&
 	git difftool -d $symlinks --extcmd "$PWD/modify-right-file" branch &&
-	echo "new content" >expect &&
+	echo "modified content" >expect &&
 	test_cmp expect file
 '
 
@@ -752,11 +752,11 @@
 		c
 	EOF
 	git difftool --symlinks --dir-diff --extcmd ls >output &&
-	grep -v ^/ output >actual &&
+	grep -v ":\$" output >actual &&
 	test_cmp expect actual &&
 
 	git difftool --no-symlinks --dir-diff --extcmd ls >output &&
-	grep -v ^/ output >actual &&
+	grep -v ":\$" output >actual &&
 	test_cmp expect actual &&
 
 	# The left side contains symlink "c" that points to "b"
@@ -786,11 +786,11 @@
 
 	EOF
 	git difftool --symlinks --dir-diff --extcmd ls >output &&
-	grep -v ^/ output >actual &&
+	grep -v ":\$" output >actual &&
 	test_cmp expect actual &&
 
 	git difftool --no-symlinks --dir-diff --extcmd ls >output &&
-	grep -v ^/ output >actual &&
+	grep -v ":\$" output >actual &&
 	test_cmp expect actual
 '
 
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index 614184a..4700bea 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -7,6 +7,9 @@
 GIT_TEST_COMMIT_GRAPH=0
 GIT_TEST_MULTI_PACK_INDEX=0
 
+# Ensure that auto-maintenance detaches as usual.
+sane_unset GIT_TEST_MAINT_AUTO_DETACH
+
 test_lazy_prereq XMLLINT '
 	xmllint --version
 '
@@ -42,14 +45,17 @@
 	test_grep "usage: git maintenance" err
 '
 
-test_expect_success 'run [--auto|--quiet]' '
+test_expect_success 'run [--auto|--quiet] with gc strategy' '
+	test_config maintenance.strategy gc &&
 	GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" \
 		git maintenance run 2>/dev/null &&
 	GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" \
 		git maintenance run --auto 2>/dev/null &&
 	GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \
 		git maintenance run --no-quiet 2>/dev/null &&
+	git maintenance is-needed &&
 	test_subcommand git gc --quiet --no-detach --skip-foreground-tasks <run-no-auto.txt &&
+	! git maintenance is-needed --auto &&
 	test_subcommand ! git gc --auto --quiet --no-detach --skip-foreground-tasks <run-auto.txt &&
 	test_subcommand git gc --no-quiet --no-detach --skip-foreground-tasks <run-no-quiet.txt
 '
@@ -180,6 +186,11 @@
 
 	test_commit first &&
 
+	! git -c maintenance.commit-graph.auto=0 \
+		maintenance is-needed --auto --task=commit-graph &&
+	git -c maintenance.commit-graph.auto=1 \
+		maintenance is-needed --auto --task=commit-graph &&
+
 	GIT_TRACE2_EVENT="$(pwd)/cg-zero-means-no.txt" \
 		git -c maintenance.commit-graph.auto=0 $COMMAND &&
 	GIT_TRACE2_EVENT="$(pwd)/cg-one-satisfied.txt" \
@@ -199,6 +210,31 @@
 	test_subcommand $COMMIT_GRAPH_WRITE <cg-two-satisfied.txt
 '
 
+test_expect_success 'commit-graph auto condition with merges' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		git config set maintenance.auto false &&
+		test_commit initial &&
+		git switch --create feature &&
+		test_commit feature-1 &&
+		test_commit feature-2 &&
+		git switch - &&
+		test_commit main-1 &&
+		test_commit main-2 &&
+		git merge feature &&
+
+		# We have 6 commits, none of which are covered by a commit
+		# graph. So this must be the boundary at which we start to
+		# perform maintenance.
+		test_must_fail git -c maintenance.commit-graph.auto=7 \
+			maintenance is-needed --auto --task=commit-graph &&
+		git -c maintenance.commit-graph.auto=6 \
+			maintenance is-needed --auto --task=commit-graph
+	)
+'
+
 test_expect_success 'run --task=bogus' '
 	test_must_fail git maintenance run --task=bogus 2>err &&
 	test_grep "is not a valid task" err
@@ -290,16 +326,23 @@
 		git -c maintenance.loose-objects.auto=1 maintenance \
 		run --auto --task=loose-objects 2>/dev/null &&
 	test_subcommand ! git prune-packed --quiet <trace-lo1.txt &&
+
 	printf data-A | git hash-object -t blob --stdin -w &&
+	! git -c maintenance.loose-objects.auto=2 \
+		maintenance is-needed --auto --task=loose-objects &&
 	GIT_TRACE2_EVENT="$(pwd)/trace-loA" \
 		git -c maintenance.loose-objects.auto=2 \
 		maintenance run --auto --task=loose-objects 2>/dev/null &&
 	test_subcommand ! git prune-packed --quiet <trace-loA &&
+
 	printf data-B | git hash-object -t blob --stdin -w &&
+	git -c maintenance.loose-objects.auto=2 \
+		maintenance is-needed --auto --task=loose-objects &&
 	GIT_TRACE2_EVENT="$(pwd)/trace-loB" \
 		git -c maintenance.loose-objects.auto=2 \
 		maintenance run --auto --task=loose-objects 2>/dev/null &&
 	test_subcommand git prune-packed --quiet <trace-loB &&
+
 	GIT_TRACE2_EVENT="$(pwd)/trace-loC" \
 		git -c maintenance.loose-objects.auto=2 \
 		maintenance run --auto --task=loose-objects 2>/dev/null &&
@@ -421,10 +464,13 @@
 	test_commit A &&
 	git repack -adk &&
 	git multi-pack-index write &&
+	! git -c maintenance.incremental-repack.auto=1 \
+		maintenance is-needed --auto --task=incremental-repack &&
 	GIT_TRACE2_EVENT="$(pwd)/midx-init.txt" git \
 		-c maintenance.incremental-repack.auto=1 \
 		maintenance run --auto --task=incremental-repack 2>/dev/null &&
 	test_subcommand ! git multi-pack-index write --no-progress <midx-init.txt &&
+
 	test_commit B &&
 	git pack-objects --revs .git/objects/pack/pack <<-\EOF &&
 	HEAD
@@ -434,11 +480,14 @@
 		-c maintenance.incremental-repack.auto=2 \
 		maintenance run --auto --task=incremental-repack 2>/dev/null &&
 	test_subcommand ! git multi-pack-index write --no-progress <trace-A &&
+
 	test_commit C &&
 	git pack-objects --revs .git/objects/pack/pack <<-\EOF &&
 	HEAD
 	^HEAD~1
 	EOF
+	git -c maintenance.incremental-repack.auto=2 \
+		maintenance is-needed --auto --task=incremental-repack &&
 	GIT_TRACE2_EVENT=$(pwd)/trace-B git \
 		-c maintenance.incremental-repack.auto=2 \
 		maintenance run --auto --task=incremental-repack 2>/dev/null &&
@@ -451,6 +500,7 @@
 	(
 		cd incremental-repack-true &&
 		git config core.multiPackIndex true &&
+		git config maintenance.auto false &&
 		run_incremental_repack_and_verify
 	)
 '
@@ -461,6 +511,7 @@
 	(
 		cd incremental-repack-unset &&
 		test_unconfig core.multiPackIndex &&
+		git config maintenance.auto false &&
 		run_incremental_repack_and_verify
 	)
 '
@@ -571,6 +622,7 @@
 	git init repo &&
 	(
 		cd repo &&
+		git config set maintenance.auto false &&
 
 		# An empty repository does not need repacking, except when
 		# explicitly told to do it.
@@ -655,9 +707,15 @@
 	git reflog expire --all --expire=now &&
 	test_commit reflog-one &&
 	test_commit reflog-two &&
+
+	! git -c maintenance.reflog-expire.auto=3 \
+		maintenance is-needed --auto --task=reflog-expire &&
 	GIT_TRACE2_EVENT="$(pwd)/reflog-expire-auto.txt" \
 		git -c maintenance.reflog-expire.auto=3 maintenance run --auto --task=reflog-expire &&
 	test_subcommand ! git reflog expire --all <reflog-expire-auto.txt &&
+
+	git -c maintenance.reflog-expire.auto=2 \
+		maintenance is-needed --auto --task=reflog-expire &&
 	GIT_TRACE2_EVENT="$(pwd)/reflog-expire-auto.txt" \
 		git -c maintenance.reflog-expire.auto=2 maintenance run --auto --task=reflog-expire &&
 	test_subcommand git reflog expire --all <reflog-expire-auto.txt
@@ -684,6 +742,7 @@
 	test_expect_worktree_prune ! git maintenance run --auto --task=worktree-prune &&
 	mkdir .git/worktrees &&
 	: >.git/worktrees/abc &&
+	git maintenance is-needed --auto --task=worktree-prune &&
 	test_expect_worktree_prune git maintenance run --auto --task=worktree-prune
 '
 
@@ -700,22 +759,7 @@
 	test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=0 maintenance run --auto --task=worktree-prune &&
 	# A positive value should require at least this many prunable worktrees.
 	test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=4 maintenance run --auto --task=worktree-prune &&
-	test_expect_worktree_prune git -c maintenance.worktree-prune.auto=3 maintenance run --auto --task=worktree-prune
-'
-
-test_expect_success 'worktree-prune task with --auto honors maintenance.worktree-prune.auto' '
-	# A negative value should always prune.
-	test_expect_worktree_prune git -c maintenance.worktree-prune.auto=-1 maintenance run --auto --task=worktree-prune &&
-
-	mkdir .git/worktrees &&
-	: >.git/worktrees/first &&
-	: >.git/worktrees/second &&
-	: >.git/worktrees/third &&
-
-	# Zero should never prune.
-	test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=0 maintenance run --auto --task=worktree-prune &&
-	# A positive value should require at least this many prunable worktrees.
-	test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=4 maintenance run --auto --task=worktree-prune &&
+	git -c maintenance.worktree-prune.auto=3 maintenance is-needed --auto --task=worktree-prune &&
 	test_expect_worktree_prune git -c maintenance.worktree-prune.auto=3 maintenance run --auto --task=worktree-prune
 '
 
@@ -724,11 +768,13 @@
 	rm -rf worktree &&
 
 	rm -f worktree-prune.txt &&
+	! git -c gc.worktreePruneExpire=1.week.ago maintenance is-needed --auto --task=worktree-prune &&
 	GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" git -c gc.worktreePruneExpire=1.week.ago maintenance run --auto --task=worktree-prune &&
 	test_subcommand ! git worktree prune --expire 1.week.ago <worktree-prune.txt &&
 	test_path_is_dir .git/worktrees/worktree &&
 
 	rm -f worktree-prune.txt &&
+	git -c gc.worktreePruneExpire=now maintenance is-needed --auto --task=worktree-prune &&
 	GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" git -c gc.worktreePruneExpire=now maintenance run --auto --task=worktree-prune &&
 	test_subcommand git worktree prune --expire now <worktree-prune.txt &&
 	test_path_is_missing .git/worktrees/worktree
@@ -753,10 +799,13 @@
 
 test_expect_success 'rerere-gc task with --auto only prunes with prunable entries' '
 	test_when_finished "rm -rf .git/rr-cache" &&
+	! git maintenance is-needed --auto --task=rerere-gc &&
 	test_expect_rerere_gc ! git maintenance run --auto --task=rerere-gc &&
 	mkdir .git/rr-cache &&
+	! git maintenance is-needed --auto --task=rerere-gc &&
 	test_expect_rerere_gc ! git maintenance run --auto --task=rerere-gc &&
 	: >.git/rr-cache/entry &&
+	git maintenance is-needed --auto --task=rerere-gc &&
 	test_expect_rerere_gc git maintenance run --auto --task=rerere-gc
 '
 
@@ -764,17 +813,22 @@
 	test_when_finished "rm -rf .git/rr-cache" &&
 
 	# A negative value should always prune.
+	git -c maintenance.rerere-gc.auto=-1 maintenance is-needed --auto --task=rerere-gc &&
 	test_expect_rerere_gc git -c maintenance.rerere-gc.auto=-1 maintenance run --auto --task=rerere-gc &&
 
 	# A positive value prunes when there is at least one entry.
+	! git -c maintenance.rerere-gc.auto=9000 maintenance is-needed --auto --task=rerere-gc &&
 	test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc &&
 	mkdir .git/rr-cache &&
+	! git -c maintenance.rerere-gc.auto=9000 maintenance is-needed --auto --task=rerere-gc &&
 	test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc &&
 	: >.git/rr-cache/entry-1 &&
+	git -c maintenance.rerere-gc.auto=9000 maintenance is-needed --auto --task=rerere-gc &&
 	test_expect_rerere_gc git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc &&
 
 	# Zero should never prune.
 	: >.git/rr-cache/entry-1 &&
+	! git -c maintenance.rerere-gc.auto=0 maintenance is-needed --auto --task=rerere-gc &&
 	test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=0 maintenance run --auto --task=rerere-gc
 '
 
diff --git a/t/t8012-blame-colors.sh b/t/t8012-blame-colors.sh
index 3d77352..5562eba 100755
--- a/t/t8012-blame-colors.sh
+++ b/t/t8012-blame-colors.sh
@@ -28,6 +28,20 @@
 	test_line_count = 3 H.expect
 '
 
+test_expect_success 'color lines becoming contiguous due to --ignore-rev' '
+	mv hello.c hello.orig &&
+	sed "s/	/    /g" <hello.orig >hello.c &&
+	git add hello.c &&
+	git commit -m"tabs to spaces" &&
+	git -c color.blame.repeatedLines=yellow blame --color-lines --ignore-rev=HEAD hello.c >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	grep "<YELLOW>" <actual >darkened &&
+	grep "(F" darkened > F.expect &&
+	grep "(H" darkened > H.expect &&
+	test_line_count = 2 F.expect &&
+	test_line_count = 3 H.expect
+'
+
 test_expect_success 'color by age consistently colors old code' '
 	git blame --color-by-age hello.c >actual.raw &&
 	git -c blame.coloring=highlightRecent blame hello.c >actual.raw.2 &&
diff --git a/t/t8015-blame-diff-algorithm.sh b/t/t8015-blame-diff-algorithm.sh
new file mode 100755
index 0000000..cd70953
--- /dev/null
+++ b/t/t8015-blame-diff-algorithm.sh
@@ -0,0 +1,203 @@
+#!/bin/sh
+
+test_description='git blame with specific diff algorithm'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+	cat >file.c <<-\EOF &&
+	int f(int x, int y)
+	{
+	  if (x == 0)
+	  {
+	    return y;
+	  }
+	  return x;
+	}
+
+	int g(size_t u)
+	{
+	  while (u < 30)
+	  {
+	    u++;
+	  }
+	  return u;
+	}
+	EOF
+	test_write_lines x x x x >file.txt &&
+	git add file.c file.txt &&
+	GIT_AUTHOR_NAME=Commit_1 git commit -m Commit_1 &&
+
+	cat >file.c <<-\EOF &&
+	int g(size_t u)
+	{
+	  while (u < 30)
+	  {
+	    u++;
+	  }
+	  return u;
+	}
+
+	int h(int x, int y, int z)
+	{
+	  if (z == 0)
+	  {
+	    return x;
+	  }
+	  return y;
+	}
+	EOF
+	test_write_lines x x x A B C D x E F G >file.txt &&
+	git add file.c file.txt &&
+	GIT_AUTHOR_NAME=Commit_2 git commit -m Commit_2
+'
+
+test_expect_success 'blame uses Myers diff algorithm by default' '
+	cat >expected <<-\EOF &&
+	Commit_2 int g(size_t u)
+	Commit_1 {
+	Commit_2   while (u < 30)
+	Commit_1   {
+	Commit_2     u++;
+	Commit_1   }
+	Commit_2   return u;
+	Commit_1 }
+	Commit_1
+	Commit_2 int h(int x, int y, int z)
+	Commit_1 {
+	Commit_2   if (z == 0)
+	Commit_1   {
+	Commit_2     return x;
+	Commit_1   }
+	Commit_2   return y;
+	Commit_1 }
+	EOF
+
+	git blame file.c >output &&
+	sed -e "s/^[^ ]* (\([^ ]*\) [^)]*)/\1/g" output >without_varying_parts &&
+	sed -e "s/ *$//g" without_varying_parts >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'blame honors --diff-algorithm option' '
+	cat >expected <<-\EOF &&
+	Commit_1 int g(size_t u)
+	Commit_1 {
+	Commit_1   while (u < 30)
+	Commit_1   {
+	Commit_1     u++;
+	Commit_1   }
+	Commit_1   return u;
+	Commit_1 }
+	Commit_2
+	Commit_2 int h(int x, int y, int z)
+	Commit_2 {
+	Commit_2   if (z == 0)
+	Commit_2   {
+	Commit_2     return x;
+	Commit_2   }
+	Commit_2   return y;
+	Commit_2 }
+	EOF
+
+	git blame file.c --diff-algorithm histogram >output &&
+	sed -e "s/^[^ ]* (\([^ ]*\) [^)]*)/\1/g" output >without_varying_parts &&
+	sed -e "s/ *$//g" without_varying_parts >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'blame honors diff.algorithm config variable' '
+	cat >expected <<-\EOF &&
+	Commit_1 int g(size_t u)
+	Commit_1 {
+	Commit_1   while (u < 30)
+	Commit_1   {
+	Commit_1     u++;
+	Commit_1   }
+	Commit_1   return u;
+	Commit_1 }
+	Commit_2
+	Commit_2 int h(int x, int y, int z)
+	Commit_2 {
+	Commit_2   if (z == 0)
+	Commit_2   {
+	Commit_2     return x;
+	Commit_2   }
+	Commit_2   return y;
+	Commit_2 }
+	EOF
+
+	git -c diff.algorithm=histogram blame file.c >output &&
+	sed -e "s/^[^ ]* (\([^ ]*\) [^)]*)/\1/g" \
+	    -e "s/ *$//g" output >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'blame gives priority to --diff-algorithm over diff.algorithm' '
+	cat >expected <<-\EOF &&
+	Commit_1 int g(size_t u)
+	Commit_1 {
+	Commit_1   while (u < 30)
+	Commit_1   {
+	Commit_1     u++;
+	Commit_1   }
+	Commit_1   return u;
+	Commit_1 }
+	Commit_2
+	Commit_2 int h(int x, int y, int z)
+	Commit_2 {
+	Commit_2   if (z == 0)
+	Commit_2   {
+	Commit_2     return x;
+	Commit_2   }
+	Commit_2   return y;
+	Commit_2 }
+	EOF
+
+	git -c diff.algorithm=myers blame file.c --diff-algorithm histogram >output &&
+	sed -e "s/^[^ ]* (\([^ ]*\) [^)]*)/\1/g" \
+	    -e "s/ *$//g" output >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'blame honors --minimal option' '
+	cat >expected <<-\EOF &&
+	Commit_1 x
+	Commit_1 x
+	Commit_1 x
+	Commit_2 A
+	Commit_2 B
+	Commit_2 C
+	Commit_2 D
+	Commit_1 x
+	Commit_2 E
+	Commit_2 F
+	Commit_2 G
+	EOF
+
+	git blame file.txt --minimal >output &&
+	sed -e "s/^[^ ]* (\([^ ]*\) [^)]*)/\1/g" output >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'blame respects the order of diff options' '
+	cat >expected <<-\EOF &&
+	Commit_1 x
+	Commit_1 x
+	Commit_1 x
+	Commit_2 A
+	Commit_2 B
+	Commit_2 C
+	Commit_2 D
+	Commit_2 x
+	Commit_2 E
+	Commit_2 F
+	Commit_2 G
+	EOF
+
+	git blame file.txt --minimal --diff-algorithm myers >output &&
+	sed -e "s/^[^ ]* (\([^ ]*\) [^)]*)/\1/g" output >actual &&
+	test_cmp expected actual
+'
+
+test_done
diff --git a/t/t8020-last-modified.sh b/t/t8020-last-modified.sh
index a4c1114..9dba4b9 100755
--- a/t/t8020-last-modified.sh
+++ b/t/t8020-last-modified.sh
@@ -8,14 +8,11 @@
 	test_commit 1 file &&
 	mkdir a &&
 	test_commit 2 a/file &&
+	git tag -mA t2 2 &&
 	mkdir a/b &&
 	test_commit 3 a/b/file
 '
 
-test_expect_success 'cannot run last-modified on two trees' '
-	test_must_fail git last-modified HEAD HEAD~1
-'
-
 check_last_modified() {
 	local indir= &&
 	while test $# != 0
@@ -34,7 +31,7 @@
 
 	cat >expect &&
 	git ${indir:+-C "$indir"} last-modified "$@" >tmp.1 &&
-	git name-rev --annotate-stdin --name-only --tags \
+	git name-rev --annotate-stdin --name-only --tags --exclude=t2 \
 		<tmp.1 >tmp.2 &&
 	tr '\t' ' ' <tmp.2 >actual &&
 	test_cmp expect actual
@@ -55,6 +52,13 @@
 	EOF
 '
 
+test_expect_success 'last-modified on annotated tag' '
+	check_last_modified t2 <<-\EOF
+	2 a
+	1 file
+	EOF
+'
+
 test_expect_success 'last-modified recursive with show-trees' '
 	check_last_modified -r -t <<-\EOF
 	3 a/b
@@ -78,6 +82,14 @@
 	EOF
 '
 
+test_expect_success 'last-modified in sparse checkout' '
+	test_when_finished "git sparse-checkout disable" &&
+	git sparse-checkout set b &&
+	check_last_modified -- a <<-\EOF
+	3 a
+	EOF
+'
+
 test_expect_success 'last-modified subdir recursive' '
 	check_last_modified -r a <<-\EOF
 	3 a/b/file
@@ -85,6 +97,41 @@
 	EOF
 '
 
+test_expect_success 'last-modified subdir non-recursive' '
+	check_last_modified a <<-\EOF
+	3 a
+	EOF
+'
+
+test_expect_success 'last-modified path in subdir non-recursive' '
+	check_last_modified a/file <<-\EOF
+	2 a/file
+	EOF
+'
+
+test_expect_success 'last-modified subdir with wildcard non-recursive' '
+	check_last_modified a/* <<-\EOF
+	3 a/b
+	2 a/file
+	EOF
+'
+
+test_expect_success 'last-modified with negative max-depth' '
+	check_last_modified --max-depth=-1 <<-\EOF
+	3 a/b/file
+	2 a/file
+	1 file
+	EOF
+'
+
+test_expect_success 'last-modified with max-depth of 1' '
+	check_last_modified --max-depth=1 <<-\EOF
+	3 a/b
+	2 a/file
+	1 file
+	EOF
+'
+
 test_expect_success 'last-modified from non-HEAD commit' '
 	check_last_modified HEAD^ <<-\EOF
 	2 a
@@ -222,9 +269,19 @@
 	EOF
 '
 
+test_expect_success 'cannot run last-modified on two commits' '
+	test_must_fail git last-modified HEAD HEAD~1 2>err &&
+	test_grep "last-modified can only operate on one commit at a time" err
+'
+
 test_expect_success 'last-modified complains about unknown arguments' '
 	test_must_fail git last-modified --foo 2>err &&
-	grep "unknown last-modified argument: --foo" err
+	test_grep "unknown last-modified argument: --foo" err
+'
+
+test_expect_success 'last-modified expects commit-ish' '
+	test_must_fail git last-modified HEAD^{tree} 2>err &&
+	test_grep "revision argument ${SQ}HEAD^{tree}${SQ} is a tree, not a commit-ish" err
 '
 
 test_done
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index e56e0c8..24f6c76 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -1691,7 +1691,7 @@
 			email-using-8bit >stdout &&
 	grep "do not declare a Content-Transfer-Encoding" stdout &&
 	grep email-using-8bit stdout &&
-	grep "Which 8bit encoding" stdout &&
+	grep "Declare which 8bit encoding to use" stdout &&
 	grep -E "Content|MIME" msgtxt1 >actual &&
 	test_cmp content-type-decl actual
 '
diff --git a/t/t9123-git-svn-rebuild-with-rewriteroot.sh b/t/t9123-git-svn-rebuild-with-rewriteroot.sh
index ead4045..8fa5940 100755
--- a/t/t9123-git-svn-rebuild-with-rewriteroot.sh
+++ b/t/t9123-git-svn-rebuild-with-rewriteroot.sh
@@ -7,12 +7,15 @@
 
 . ./lib-git-svn.sh
 
-mkdir import
-(cd import
-	touch foo
-	svn_cmd import -m 'import for git svn' . "$svnrepo" >/dev/null
-)
-rm -rf import
+test_expect_success 'setup svn repository' '
+	test_when_finished "rm -rf import" &&
+	mkdir import &&
+	(
+		cd import &&
+		touch foo &&
+		svn_cmd import -m "import for git svn" . "$svnrepo" >/dev/null
+	)
+	'
 
 test_expect_success 'init, fetch and checkout repository' '
 	git svn init --rewrite-root=http://invalid.invalid/ "$svnrepo" &&
diff --git a/t/t9160-git-svn-preserve-empty-dirs.sh b/t/t9160-git-svn-preserve-empty-dirs.sh
index 36c6b1a..de32cf2 100755
--- a/t/t9160-git-svn-preserve-empty-dirs.sh
+++ b/t/t9160-git-svn-preserve-empty-dirs.sh
@@ -61,15 +61,15 @@
 
 # "$GIT_REPO"/1 should only contain the placeholder file.
 test_expect_success 'directory empty from inception' '
-	test -f "$GIT_REPO"/1/.gitignore &&
+	test_path_is_file "$GIT_REPO"/1/.gitignore &&
 	test $(find "$GIT_REPO"/1 -type f | wc -l) = "1"
 '
 
 # "$GIT_REPO"/2 and "$GIT_REPO"/3 should only contain the placeholder file.
 test_expect_success 'directory empty from subsequent svn commit' '
-	test -f "$GIT_REPO"/2/.gitignore &&
+	test_path_is_file "$GIT_REPO"/2/.gitignore &&
 	test $(find "$GIT_REPO"/2 -type f | wc -l) = "1" &&
-	test -f "$GIT_REPO"/3/.gitignore &&
+	test_path_is_file "$GIT_REPO"/3/.gitignore &&
 	test $(find "$GIT_REPO"/3 -type f | wc -l) = "1"
 '
 
@@ -77,7 +77,7 @@
 # generated for every sub-directory at some point in the repo's history.
 test_expect_success 'add entry to previously empty directory' '
 	test $(find "$GIT_REPO"/4 -type f | wc -l) = "1" &&
-	test -f "$GIT_REPO"/4/a/b/c/foo
+	test_path_is_file "$GIT_REPO"/4/a/b/c/foo
 '
 
 # The HEAD~2 commit should not have introduced .gitignore placeholder files.
@@ -102,14 +102,14 @@
 
 # "$GIT_REPO"/5/.placeholder should be a file, and non-empty.
 test_expect_success 'placeholder namespace conflict with file' '
-	test -s "$GIT_REPO"/5/.placeholder
+	test_file_not_empty "$GIT_REPO"/5/.placeholder
 '
 
 # "$GIT_REPO"/6/.placeholder should be a directory, and the "$GIT_REPO"/6 tree
 # should only contain one file: the placeholder.
 test_expect_success 'placeholder namespace conflict with directory' '
-	test -d "$GIT_REPO"/6/.placeholder &&
-	test -f "$GIT_REPO"/6/.placeholder/.placeholder &&
+	test_path_is_dir "$GIT_REPO"/6/.placeholder &&
+	test_path_is_file "$GIT_REPO"/6/.placeholder/.placeholder &&
 	test $(find "$GIT_REPO"/6 -type f | wc -l) = "1"
 '
 
@@ -133,19 +133,19 @@
 
 # Check that --preserve-empty-dirs and --placeholder-file flag state
 # stays persistent over multiple invocations.
-test_expect_success 'flag persistence during subsqeuent rebase' '
-	test -f "$GIT_REPO"/7/.placeholder &&
+test_expect_success 'flag persistence during subsequent rebase' '
+	test_path_is_file "$GIT_REPO"/7/.placeholder &&
 	test $(find "$GIT_REPO"/7 -type f | wc -l) = "1"
 '
 
 # Check that placeholder files are properly removed when unnecessary,
 # even across multiple invocations.
-test_expect_success 'placeholder list persistence during subsqeuent rebase' '
-	test -f "$GIT_REPO"/1/file1.txt &&
+test_expect_success 'placeholder list persistence during subsequent rebase' '
+	test_path_is_file "$GIT_REPO"/1/file1.txt &&
 	test $(find "$GIT_REPO"/1 -type f | wc -l) = "1" &&
 
-	test -f "$GIT_REPO"/5/file1.txt &&
-	test -f "$GIT_REPO"/5/.placeholder &&
+	test_path_is_file "$GIT_REPO"/5/file1.txt &&
+	test_path_is_file "$GIT_REPO"/5/.placeholder &&
 	test $(find "$GIT_REPO"/5 -type f | wc -l) = "2"
 '
 
diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh
index bd6f0c4..009437a 100755
--- a/t/t9210-scalar.sh
+++ b/t/t9210-scalar.sh
@@ -202,14 +202,17 @@
 test_expect_success 'scalar reconfigure' '
 	git init one/src &&
 	scalar register one &&
-	git -C one/src config core.preloadIndex false &&
+	git -C one/src config unset gui.gcwarning &&
 	scalar reconfigure one &&
-	test true = "$(git -C one/src config core.preloadIndex)" &&
-	git -C one/src config core.preloadIndex false &&
+	test false = "$(git -C one/src config gui.gcwarning)" &&
+	git -C one/src config unset gui.gcwarning &&
 	rm one/src/cron.txt &&
 	GIT_TRACE2_EVENT="$(pwd)/reconfigure" scalar reconfigure -a &&
 	test_path_is_file one/src/cron.txt &&
-	test true = "$(git -C one/src config core.preloadIndex)" &&
+	test false = "$(git -C one/src config gui.gcwarning)" &&
+	test_grep "GCWarning = false # set by scalar" one/src/.git/config &&
+	test_grep "excludeDecoration = refs/prefetch/\* # set by scalar" one/src/.git/config &&
+
 	test_subcommand git maintenance start <reconfigure &&
 	test_subcommand ! git maintenance unregister --force <reconfigure &&
 
@@ -231,25 +234,29 @@
 		git init $num/src &&
 		scalar register $num/src &&
 		git -C $num/src config includeif."onbranch:foo".path something &&
-		git -C $num/src config core.preloadIndex false || return 1
+		git -C $num/src config unset gui.gcwarning || return 1
 	done &&
 
 	scalar reconfigure --all &&
 
 	for num in $repos
 	do
-		test true = "$(git -C $num/src config core.preloadIndex)" || return 1
+		test false = "$(git -C $num/src config gui.gcwarning)" || return 1
 	done
 '
 
 test_expect_success 'scalar reconfigure --all with detached HEADs' '
+	# This test demonstrates an issue with index.skipHash=true and
+	# this test variable for the split index. Disable the test variable.
+	sane_unset GIT_TEST_SPLIT_INDEX &&
+
 	repos="two three four" &&
 	for num in $repos
 	do
 		rm -rf $num/src &&
 		git init $num/src &&
 		scalar register $num/src &&
-		git -C $num/src config core.preloadIndex false &&
+		git -C $num/src config unset gui.gcwarning &&
 		test_commit -C $num/src initial &&
 		git -C $num/src switch --detach HEAD || return 1
 	done &&
@@ -258,7 +265,7 @@
 
 	for num in $repos
 	do
-		test true = "$(git -C $num/src config core.preloadIndex)" || return 1
+		test false = "$(git -C $num/src config gui.gcwarning)" || return 1
 	done
 '
 
@@ -290,7 +297,7 @@
 	git init sub &&
 	scalar -C sub -c status.aheadBehind=bogus register &&
 	test -z "$(git -C sub config --local status.aheadBehind)" &&
-	test true = "$(git -C sub config core.preloadIndex)"
+	test false = "$(git -C sub config gui.gcwarning)"
 '
 
 test_expect_success '`scalar [...] <dir>` errors out when dir is missing' '
diff --git a/t/t9305-fast-import-signatures.sh b/t/t9305-fast-import-signatures.sh
index c2b4271..a540679 100755
--- a/t/t9305-fast-import-signatures.sh
+++ b/t/t9305-fast-import-signatures.sh
@@ -70,7 +70,7 @@
 	test_must_be_empty log
 '
 
-test_expect_success GPG 'setup a commit with dual OpenPGP signatures on its SHA-1 and SHA-256 formats' '
+test_expect_success RUST,GPG 'setup a commit with dual OpenPGP signatures on its SHA-1 and SHA-256 formats' '
 	# Create a signed SHA-256 commit
 	git init --object-format=sha256 explicit-sha256 &&
 	git -C explicit-sha256 config extensions.compatObjectFormat sha1 &&
@@ -79,7 +79,7 @@
 	echo B >explicit-sha256/B &&
 	git -C explicit-sha256 add B &&
 	test_tick &&
-	git -C explicit-sha256 commit -S -m "signed" B &&
+	git -C explicit-sha256 commit -S -m "signed commit" B &&
 	SHA256_B=$(git -C explicit-sha256 rev-parse dual-signed) &&
 
 	# Create the corresponding SHA-1 commit
@@ -91,7 +91,7 @@
 	test_grep -E "^gpgsig-sha256 " out
 '
 
-test_expect_success GPG 'strip both OpenPGP signatures with --signed-commits=warn-strip' '
+test_expect_success RUST,GPG 'strip both OpenPGP signatures with --signed-commits=warn-strip' '
 	git -C explicit-sha256 fast-export --signed-commits=verbatim dual-signed >output &&
 	test_grep -E "^gpgsig sha1 openpgp" output &&
 	test_grep -E "^gpgsig sha256 openpgp" output &&
@@ -103,4 +103,71 @@
 	test_line_count = 2 out
 '
 
+test_expect_success GPG 'import commit with no signature with --signed-commits=strip-if-invalid' '
+	git fast-export main >output &&
+	git -C new fast-import --quiet --signed-commits=strip-if-invalid <output >log 2>&1 &&
+	test_must_be_empty log
+'
+
+test_expect_success GPG 'keep valid OpenPGP signature with --signed-commits=strip-if-invalid' '
+	rm -rf new &&
+	git init new &&
+
+	git fast-export --signed-commits=verbatim openpgp-signing >output &&
+	git -C new fast-import --quiet --signed-commits=strip-if-invalid <output >log 2>&1 &&
+	IMPORTED=$(git -C new rev-parse --verify refs/heads/openpgp-signing) &&
+	test $OPENPGP_SIGNING = $IMPORTED &&
+	git -C new cat-file commit "$IMPORTED" >actual &&
+	test_grep -E "^gpgsig(-sha256)? " actual &&
+	test_must_be_empty log
+'
+
+test_expect_success GPG 'strip signature invalidated by message change with --signed-commits=strip-if-invalid' '
+	rm -rf new &&
+	git init new &&
+
+	git fast-export --signed-commits=verbatim openpgp-signing >output &&
+
+	# Change the commit message, which invalidates the signature.
+	# The commit message length should not change though, otherwise the
+	# corresponding `data <length>` command would have to be changed too.
+	sed "s/OpenPGP signed commit/OpenPGP forged commit/" output >modified &&
+
+	git -C new fast-import --quiet --signed-commits=strip-if-invalid <modified >log 2>&1 &&
+
+	IMPORTED=$(git -C new rev-parse --verify refs/heads/openpgp-signing) &&
+	test $OPENPGP_SIGNING != $IMPORTED &&
+	git -C new cat-file commit "$IMPORTED" >actual &&
+	test_grep ! -E "^gpgsig" actual &&
+	test_grep "stripping invalid signature" log
+'
+
+test_expect_success GPGSM 'keep valid X.509 signature with --signed-commits=strip-if-invalid' '
+	rm -rf new &&
+	git init new &&
+
+	git fast-export --signed-commits=verbatim x509-signing >output &&
+	git -C new fast-import --quiet --signed-commits=strip-if-invalid <output >log 2>&1 &&
+	IMPORTED=$(git -C new rev-parse --verify refs/heads/x509-signing) &&
+	test $X509_SIGNING = $IMPORTED &&
+	git -C new cat-file commit "$IMPORTED" >actual &&
+	test_grep -E "^gpgsig(-sha256)? " actual &&
+	test_must_be_empty log
+'
+
+test_expect_success GPGSSH 'keep valid SSH signature with --signed-commits=strip-if-invalid' '
+	rm -rf new &&
+	git init new &&
+
+	test_config -C new gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+
+	git fast-export --signed-commits=verbatim ssh-signing >output &&
+	git -C new fast-import --quiet --signed-commits=strip-if-invalid <output >log 2>&1 &&
+	IMPORTED=$(git -C new rev-parse --verify refs/heads/ssh-signing) &&
+	test $SSH_SIGNING = $IMPORTED &&
+	git -C new cat-file commit "$IMPORTED" >actual &&
+	test_grep -E "^gpgsig(-sha256)? " actual &&
+	test_must_be_empty log
+'
+
 test_done
diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh
index 3d153a4..784d68b 100755
--- a/t/t9350-fast-export.sh
+++ b/t/t9350-fast-export.sh
@@ -972,7 +972,7 @@
 	test_cmp expect actual
 '
 
-test_expect_success GPG 'setup a commit with dual signatures on its SHA-1 and SHA-256 formats' '
+test_expect_success GPG,RUST 'setup a commit with dual signatures on its SHA-1 and SHA-256 formats' '
 	# Create a signed SHA-256 commit
 	git init --object-format=sha256 explicit-sha256 &&
 	git -C explicit-sha256 config extensions.compatObjectFormat sha1 &&
@@ -993,7 +993,7 @@
 	test_grep -E "^gpgsig-sha256 " out
 '
 
-test_expect_success GPG 'export and import of doubly signed commit' '
+test_expect_success GPG,RUST 'export and import of doubly signed commit' '
 	git -C explicit-sha256 fast-export --signed-commits=verbatim dual-signed >output &&
 	test_grep -E "^gpgsig sha1 openpgp" output &&
 	test_grep -E "^gpgsig sha256 openpgp" output &&
diff --git a/t/t9700/test.pl b/t/t9700/test.pl
index 58a9b32..f83e616 100755
--- a/t/t9700/test.pl
+++ b/t/t9700/test.pl
@@ -117,7 +117,12 @@
 unlink $tmpfile;
 
 # paths
-is($r->repo_path, $abs_repo_dir . "/.git", "repo_path");
+my $abs_git_dir = $abs_repo_dir . "/.git";
+if ($^O eq 'msys') {
+  $abs_git_dir = `cygpath -am "$abs_repo_dir/.git"`;
+  $abs_git_dir =~ s/\r?\n?$//;
+}
+is($r->repo_path, $abs_git_dir, "repo_path");
 is($r->wc_path, $abs_repo_dir . "/", "wc_path");
 is($r->wc_subdir, "", "wc_subdir initial");
 $r->wc_chdir("directory1");
@@ -127,7 +132,7 @@
 # Object generation in sub directory
 chdir("directory2");
 my $r2 = Git->repository();
-is($r2->repo_path, $abs_repo_dir . "/.git", "repo_path (2)");
+is($r2->repo_path, $abs_git_dir, "repo_path (2)");
 is($r2->wc_path, $abs_repo_dir . "/", "wc_path (2)");
 is($r2->wc_subdir, "directory2/", "wc_subdir initial (2)");
 
diff --git a/t/t9812-git-p4-wildcards.sh b/t/t9812-git-p4-wildcards.sh
index 254a7c2..e91004e 100755
--- a/t/t9812-git-p4-wildcards.sh
+++ b/t/t9812-git-p4-wildcards.sh
@@ -30,13 +30,13 @@
 	test_when_finished cleanup_git &&
 	(
 		cd "$git" &&
-		test -f file-wild#hash &&
+		test_path_is_file file-wild#hash &&
 		if test_have_prereq !MINGW,!CYGWIN
 		then
-			test -f file-wild\*star
+			test_path_is_file file-wild\*star
 		fi &&
-		test -f file-wild@at &&
-		test -f file-wild%percent
+		test_path_is_file file-wild@at &&
+		test_path_is_file file-wild%percent
 	)
 '
 
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 964e1f1..2f9a597 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2601,6 +2601,7 @@
 	--ignore-skip-worktree-bits Z
 	--ignore-other-worktrees Z
 	--recurse-submodules Z
+	--auto-advance Z
 	--progress Z
 	--guess Z
 	--no-guess Z
@@ -3053,6 +3054,7 @@
 	submodule.sub.fetchRecurseSubmodules Z
 	submodule.sub.ignore Z
 	submodule.sub.active Z
+	submodule.sub.gitdir Z
 	EOF
 '
 
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 52d7759..14e238d 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -1724,7 +1724,7 @@
 	esac
 }
 
-# Detect the hash algorithm in use.
+# Detect the ref format in use.
 test_detect_ref_format () {
 	echo "${GIT_TEST_DEFAULT_REF_FORMAT:-files}"
 }
diff --git a/t/test-lib.sh b/t/test-lib.sh
index ef0ab7e..70fd3e9 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -77,6 +77,7 @@
 # want that one to complain to stderr).
 prepend_var ASAN_OPTIONS : $GIT_SAN_OPTIONS
 prepend_var ASAN_OPTIONS : detect_leaks=0
+prepend_var ASAN_OPTIONS : strict_string_checks=1
 export ASAN_OPTIONS
 
 prepend_var LSAN_OPTIONS : $GIT_SAN_OPTIONS
@@ -1719,7 +1720,6 @@
 ( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1
 test -z "$NO_CURL" && test_set_prereq LIBCURL
 test -z "$NO_GITWEB" && test_set_prereq GITWEB
-test -z "$NO_ICONV" && test_set_prereq ICONV
 test -z "$NO_PERL" && test_set_prereq PERL
 test -z "$NO_PTHREADS" && test_set_prereq PTHREADS
 test -z "$NO_PYTHON" && test_set_prereq PYTHON
@@ -1730,6 +1730,17 @@
 test -n "$GIT_VALGRIND_ENABLED" && test_set_prereq VALGRIND
 test -n "$PERL_PATH" && test_set_prereq PERL_TEST_HELPERS
 
+test_lazy_prereq ICONV '
+	# We require Git to be built with iconv support, and we require the
+	# iconv binary to exist.
+	#
+	# NEEDSWORK: We might eventually want to split this up into two
+	# prerequisites: one for NO_ICONV, and one for the iconv(1) binary, as
+	# some tests only depend on either of these.
+	test -z "$NO_ICONV" &&
+	iconv -f utf8 -t utf8 </dev/null
+'
+
 if test -z "$GIT_TEST_CHECK_CACHE_TREE"
 then
 	GIT_TEST_CHECK_CACHE_TREE=true
@@ -1890,6 +1901,10 @@
 	test 8 -le "$(build_option sizeof-long)"
 '
 
+test_lazy_prereq RUST '
+	test "$(build_option rust)" = enabled
+'
+
 test_lazy_prereq TIME_IS_64BIT 'test-tool date is64bit'
 test_lazy_prereq TIME_T_IS_64BIT 'test-tool date time_t-is64bit'
 
@@ -1946,6 +1961,10 @@
 GIT_TEST_MAINT_SCHEDULER="none:exit 1"
 export GIT_TEST_MAINT_SCHEDULER
 
+# Ensure that tests cannot race with background maintenance by default.
+GIT_TEST_MAINT_AUTO_DETACH="false"
+export GIT_TEST_MAINT_AUTO_DETACH
+
 # Does this platform support `git fsmonitor--daemon`
 #
 test_lazy_prereq FSMONITOR_DAEMON '
diff --git a/t/unit-tests/clar/.github/workflows/ci.yml b/t/unit-tests/clar/.github/workflows/ci.yml
index 4d47242..14cb4ed 100644
--- a/t/unit-tests/clar/.github/workflows/ci.yml
+++ b/t/unit-tests/clar/.github/workflows/ci.yml
@@ -53,7 +53,7 @@
       if: matrix.platform.image == 'i386/debian:latest'
       run: apt -q update && apt -q -y install cmake gcc libc6-amd64 lib64stdc++6 make python3
     - name: Check out
-      uses: actions/checkout@v4
+      uses: actions/checkout@v6
     - name: Build
       shell: bash
       run: |
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
index d6176e5..e959a5a 100644
--- a/t/unit-tests/clar/clar.c
+++ b/t/unit-tests/clar/clar.c
@@ -24,6 +24,14 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 
+#ifndef va_copy
+#	ifdef __va_copy
+#		define va_copy(dst, src) __va_copy(dst, src)
+#	else
+#		define va_copy(dst, src) ((dst) = (src))
+#	endif
+#endif
+
 #if defined(__UCLIBC__) && ! defined(__UCLIBC_HAS_WCHAR__)
 	/*
 	 * uClibc can optionally be built without wchar support, in which case
@@ -76,8 +84,10 @@
 #			define S_ISDIR(x) ((x & _S_IFDIR) != 0)
 #		endif
 #		define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__)
+#		define p_vsnprintf _vsnprintf
 #	else
 #		define p_snprintf snprintf
+#		define p_vsnprintf vsnprintf
 #	endif
 
 #	define localtime_r(timer, buf) (localtime_s(buf, timer) == 0 ? buf : NULL)
@@ -86,6 +96,7 @@
 #	include <unistd.h>
 #	define _MAIN_CC
 #	define p_snprintf snprintf
+#	define p_vsnprintf vsnprintf
 	typedef struct stat STAT_T;
 #endif
 
@@ -699,13 +710,14 @@ void clar__skip(void)
 	abort_test();
 }
 
-void clar__fail(
+static void clar__failv(
 	const char *file,
 	const char *function,
 	size_t line,
+	int should_abort,
 	const char *error_msg,
 	const char *description,
-	int should_abort)
+	va_list args)
 {
 	struct clar_error *error;
 
@@ -725,9 +737,19 @@ void clar__fail(
 	error->line_number = _clar.invoke_line ? _clar.invoke_line : line;
 	error->error_msg = error_msg;
 
-	if (description != NULL &&
-	    (error->description = strdup(description)) == NULL)
-		clar_abort("Failed to allocate description.\n");
+	if (description != NULL) {
+		va_list args_copy;
+		int len;
+
+		va_copy(args_copy, args);
+		if ((len = p_vsnprintf(NULL, 0, description, args_copy)) < 0)
+			clar_abort("Failed to compute description.");
+		va_end(args_copy);
+
+		if ((error->description = calloc(1, len + 1)) == NULL)
+			clar_abort("Failed to allocate buffer.");
+		p_vsnprintf(error->description, len + 1, description, args);
+	}
 
 	_clar.total_errors++;
 	_clar.last_report->status = CL_TEST_FAILURE;
@@ -736,6 +758,34 @@ void clar__fail(
 		abort_test();
 }
 
+void clar__failf(
+	const char *file,
+	const char *function,
+	size_t line,
+	int should_abort,
+	const char *error_msg,
+	const char *description,
+	...)
+{
+	va_list args;
+	va_start(args, description);
+	clar__failv(file, function, line, should_abort, error_msg,
+		    description, args);
+	va_end(args);
+}
+
+void clar__fail(
+	const char *file,
+	const char *function,
+	size_t line,
+	const char *error_msg,
+	const char *description,
+	int should_abort)
+{
+	clar__failf(file, function, line, should_abort, error_msg,
+		    description ? "%s" : NULL, description);
+}
+
 void clar__assert(
 	int condition,
 	const char *file,
@@ -889,6 +939,92 @@ void clar__assert_equal(
 		clar__fail(file, function, line, err, buf, should_abort);
 }
 
+void clar__assert_compare_i(
+	const char *file,
+	const char *func,
+	size_t line,
+	int should_abort,
+	enum clar_comparison cmp,
+	intmax_t value1,
+	intmax_t value2,
+	const char *error,
+	const char *description,
+	...)
+{
+	int fulfilled;
+	switch (cmp) {
+	case CLAR_COMPARISON_EQ:
+		fulfilled = value1 == value2;
+		break;
+	case CLAR_COMPARISON_LT:
+		fulfilled = value1 < value2;
+		break;
+	case CLAR_COMPARISON_LE:
+		fulfilled = value1 <= value2;
+		break;
+	case CLAR_COMPARISON_GT:
+		fulfilled = value1 > value2;
+		break;
+	case CLAR_COMPARISON_GE:
+		fulfilled = value1 >= value2;
+		break;
+	default:
+		cl_assert(0);
+		return;
+	}
+
+	if (!fulfilled) {
+		va_list args;
+		va_start(args, description);
+		clar__failv(file, func, line, should_abort, error,
+			    description, args);
+		va_end(args);
+	}
+}
+
+void clar__assert_compare_u(
+	const char *file,
+	const char *func,
+	size_t line,
+	int should_abort,
+	enum clar_comparison cmp,
+	uintmax_t value1,
+	uintmax_t value2,
+	const char *error,
+	const char *description,
+	...)
+{
+	int fulfilled;
+	switch (cmp) {
+	case CLAR_COMPARISON_EQ:
+		fulfilled = value1 == value2;
+		break;
+	case CLAR_COMPARISON_LT:
+		fulfilled = value1 < value2;
+		break;
+	case CLAR_COMPARISON_LE:
+		fulfilled = value1 <= value2;
+		break;
+	case CLAR_COMPARISON_GT:
+		fulfilled = value1 > value2;
+		break;
+	case CLAR_COMPARISON_GE:
+		fulfilled = value1 >= value2;
+		break;
+	default:
+		cl_assert(0);
+		return;
+	}
+
+	if (!fulfilled) {
+		va_list args;
+		va_start(args, description);
+		clar__failv(file, func, line, should_abort, error,
+			    description, args);
+		va_end(args);
+	}
+}
+
 void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
 {
 	_clar.local_cleanup = cleanup;
diff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h
index ca72292..f7e4363 100644
--- a/t/unit-tests/clar/clar.h
+++ b/t/unit-tests/clar/clar.h
@@ -7,6 +7,7 @@
 #ifndef __CLAR_TEST_H__
 #define __CLAR_TEST_H__
 
+#include <inttypes.h>
 #include <stdlib.h>
 #include <limits.h>
 
@@ -149,6 +150,7 @@ const char *cl_fixture_basename(const char *fixture_name);
  * Forced failure/warning
  */
 #define cl_fail(desc) clar__fail(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Test failed.", desc, 1)
+#define cl_failf(desc,...) clar__failf(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, 1, "Test failed.", desc, __VA_ARGS__)
 #define cl_warning(desc) clar__fail(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Warning during test execution:", desc, 0)
 
 #define cl_skip() clar__skip()
@@ -168,9 +170,42 @@ const char *cl_fixture_basename(const char *fixture_name);
 #define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len))
 #define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len))
 
-#define cl_assert_equal_i(i1,i2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2))
-#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2))
-#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2))
+#define cl_assert_compare_i_(i1, i2, cmp, error, ...) clar__assert_compare_i(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, 1, cmp, \
+									     (i1), (i2), "Expected comparison to hold: " error, __VA_ARGS__)
+#define cl_assert_compare_i(i1, i2, cmp, error, fmt) do { \
+	intmax_t v1 = (i1), v2 = (i2); \
+	clar__assert_compare_i(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, 1, cmp, \
+			       v1, v2, "Expected comparison to hold: " error, fmt, v1, v2); \
+} while (0)
+#define cl_assert_equal_i_(i1, i2, ...)    cl_assert_compare_i_(i1, i2, CLAR_COMPARISON_EQ, #i1 " == " #i2, __VA_ARGS__)
+#define cl_assert_equal_i(i1, i2)          cl_assert_compare_i (i1, i2, CLAR_COMPARISON_EQ, #i1 " == " #i2, "%"PRIdMAX " != %"PRIdMAX)
+#define cl_assert_equal_i_fmt(i1, i2, fmt) cl_assert_compare_i_(i1, i2, CLAR_COMPARISON_EQ, #i1 " == " #i2,  fmt " != " fmt, (int)(i1), (int)(i2))
+#define cl_assert_lt_i_(i1, i2, ...) cl_assert_compare_i_(i1, i2, CLAR_COMPARISON_LT, #i1 " < " #i2, __VA_ARGS__)
+#define cl_assert_lt_i(i1, i2)       cl_assert_compare_i (i1, i2, CLAR_COMPARISON_LT, #i1 " < " #i2, "%"PRIdMAX " >= %"PRIdMAX)
+#define cl_assert_le_i_(i1, i2, ...) cl_assert_compare_i_(i1, i2, CLAR_COMPARISON_LE, #i1 " <= " #i2, __VA_ARGS__)
+#define cl_assert_le_i(i1, i2)       cl_assert_compare_i (i1, i2, CLAR_COMPARISON_LE, #i1 " <= " #i2, "%"PRIdMAX " > %"PRIdMAX)
+#define cl_assert_gt_i_(i1, i2, ...) cl_assert_compare_i_(i1, i2, CLAR_COMPARISON_GT, #i1 " > " #i2, __VA_ARGS__)
+#define cl_assert_gt_i(i1, i2)       cl_assert_compare_i (i1, i2, CLAR_COMPARISON_GT, #i1 " > " #i2, "%"PRIdMAX " <= %"PRIdMAX)
+#define cl_assert_ge_i_(i1, i2, ...) cl_assert_compare_i_(i1, i2, CLAR_COMPARISON_GE, #i1 " >= " #i2, __VA_ARGS__)
+#define cl_assert_ge_i(i1, i2)       cl_assert_compare_i (i1, i2, CLAR_COMPARISON_GE, #i1 " >= " #i2, "%"PRIdMAX " < %"PRIdMAX)
+
+#define cl_assert_compare_u_(u1, u2, cmp, error, ...) clar__assert_compare_u(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, 1, cmp, \
+									     (u1), (u2), "Expected comparison to hold: " error, __VA_ARGS__)
+#define cl_assert_compare_u(u1, u2, cmp, error, fmt) do { \
+	uintmax_t v1 = (u1), v2 = (u2); \
+	clar__assert_compare_u(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, 1, cmp, \
+			       v1, v2, "Expected comparison to hold: " error, fmt, v1, v2); \
+} while (0)
+#define cl_assert_equal_u_(u1, u2, ...) cl_assert_compare_u_(u1, u2, CLAR_COMPARISON_EQ, #u1 " == " #u2, __VA_ARGS__)
+#define cl_assert_equal_u(u1, u2)       cl_assert_compare_u (u1, u2, CLAR_COMPARISON_EQ, #u1 " == " #u2, "%"PRIuMAX " != %"PRIuMAX)
+#define cl_assert_lt_u_(u1, u2, ...) cl_assert_compare_u_(u1, u2, CLAR_COMPARISON_LT, #u1 " < " #u2, __VA_ARGS__)
+#define cl_assert_lt_u(u1, u2)       cl_assert_compare_u (u1, u2, CLAR_COMPARISON_LT, #u1 " < " #u2, "%"PRIuMAX " >= %"PRIuMAX)
+#define cl_assert_le_u_(u1, u2, ...) cl_assert_compare_u_(u1, u2, CLAR_COMPARISON_LE, #u1 " <= " #u2, __VA_ARGS__)
+#define cl_assert_le_u(u1, u2)       cl_assert_compare_u (u1, u2, CLAR_COMPARISON_LE, #u1 " <= " #u2, "%"PRIuMAX " > %"PRIuMAX)
+#define cl_assert_gt_u_(u1, u2, ...) cl_assert_compare_u_(u1, u2, CLAR_COMPARISON_GT, #u1 " > " #u2, __VA_ARGS__)
+#define cl_assert_gt_u(u1, u2)       cl_assert_compare_u (u1, u2, CLAR_COMPARISON_GT, #u1 " > " #u2, "%"PRIuMAX " <= %"PRIuMAX)
+#define cl_assert_ge_u_(u1, u2, ...) cl_assert_compare_u_(u1, u2, CLAR_COMPARISON_GE, #u1 " >= " #u2, __VA_ARGS__)
+#define cl_assert_ge_u(u1, u2)       cl_assert_compare_u (u1, u2, CLAR_COMPARISON_GE, #u1 " >= " #u2, "%"PRIuMAX " < %"PRIuMAX)
 
 #define cl_assert_equal_b(b1,b2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0))
 
@@ -186,6 +221,15 @@ void clar__fail(
 	const char *description,
 	int should_abort);
 
+void clar__failf(
+	const char *file,
+	const char *func,
+	size_t line,
+	int should_abort,
+	const char *error,
+	const char *description,
+	...);
+
 void clar__assert(
 	int condition,
 	const char *file,
@@ -204,6 +248,38 @@ void clar__assert_equal(
 	const char *fmt,
 	...);
 
+enum clar_comparison {
+	CLAR_COMPARISON_EQ,
+	CLAR_COMPARISON_LT,
+	CLAR_COMPARISON_LE,
+	CLAR_COMPARISON_GT,
+	CLAR_COMPARISON_GE,
+};
+
+void clar__assert_compare_i(
+	const char *file,
+	const char *func,
+	size_t line,
+	int should_abort,
+	enum clar_comparison cmp,
+	intmax_t value1,
+	intmax_t value2,
+	const char *error,
+	const char *description,
+	...);
+
+void clar__assert_compare_u(
+	const char *file,
+	const char *func,
+	size_t line,
+	int should_abort,
+	enum clar_comparison cmp,
+	uintmax_t value1,
+	uintmax_t value2,
+	const char *error,
+	const char *description,
+	...);
+
 void clar__set_invokepoint(
 	const char *file,
 	const char *func,
diff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h
index 89b6659..59b7dc1 100644
--- a/t/unit-tests/clar/clar/print.h
+++ b/t/unit-tests/clar/clar/print.h
@@ -127,7 +127,7 @@ static void clar_print_tap_error(int num, const struct clar_report *report, cons
 
 static void print_escaped(const char *str)
 {
-	char *c;
+	const char *c;
 
 	while ((c = strchr(str, '\'')) != NULL) {
 		printf("%.*s", (int)(c - str), str);
@@ -164,7 +164,7 @@ static void clar_print_tap_ontest(const char *suite_name, const char *test_name,
 			printf("      file: '"); print_escaped(error->file); printf("'\n");
 			printf("      line: %" PRIuMAX "\n", error->line_number);
 			printf("      function: '%s'\n", error->function);
-			printf("    ---\n");
+			printf("    ...\n");
 		}
 
 		break;
diff --git a/t/unit-tests/clar/test/expected/quiet b/t/unit-tests/clar/test/expected/quiet
index 280c99d..a93273b 100644
--- a/t/unit-tests/clar/test/expected/quiet
+++ b/t/unit-tests/clar/test/expected/quiet
@@ -18,27 +18,57 @@
 
   5) Failure:
 combined::int [file:42]
-  101 != value ("extra note on failing test")
+  Expected comparison to hold: 101 == value
   101 != 100
 
   6) Failure:
-combined::int_fmt [file:42]
-  022 != value
-  0022 != 0144
+combined::int_note [file:42]
+  Expected comparison to hold: 101 == value
+  extra note on failing test
 
   7) Failure:
+combined::int_fmt [file:42]
+  Expected comparison to hold: 022 == value
+  0022 != 0144
+
+  8) Failure:
 combined::bool [file:42]
   0 != value
   0 != 1
 
-  8) Failure:
+  9) Failure:
 combined::multiline_description [file:42]
   Function call failed: -1
   description line 1
   description line 2
 
-  9) Failure:
+  10) Failure:
 combined::null_string [file:42]
   String mismatch: "expected" != actual ("this one fails")
   'expected' != NULL
 
+  11) Failure:
+combined::failf [file:42]
+  Test failed.
+  some reason: foo
+
+  12) Failure:
+combined::compare_i [file:42]
+  Expected comparison to hold: two < 1
+  2 >= 1
+
+  13) Failure:
+combined::compare_i_with_format [file:42]
+  Expected comparison to hold: two < 1
+  foo: bar
+
+  14) Failure:
+combined::compare_u [file:42]
+  Expected comparison to hold: two < 1
+  2 >= 1
+
+  15) Failure:
+combined::compare_u_with_format [file:42]
+  Expected comparison to hold: two < 1
+  foo: bar
+
diff --git a/t/unit-tests/clar/test/expected/summary_with_filename b/t/unit-tests/clar/test/expected/summary_with_filename
index 4601607..a9471cc 100644
--- a/t/unit-tests/clar/test/expected/summary_with_filename
+++ b/t/unit-tests/clar/test/expected/summary_with_filename
@@ -1,6 +1,6 @@
 Loaded 1 suites:
 Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')
-FFFFFFFFF
+FFFFFFFFFFFFFFF
 
   1) Failure:
 combined::1 [file:42]
@@ -22,28 +22,58 @@
 
   5) Failure:
 combined::int [file:42]
-  101 != value ("extra note on failing test")
+  Expected comparison to hold: 101 == value
   101 != 100
 
   6) Failure:
-combined::int_fmt [file:42]
-  022 != value
-  0022 != 0144
+combined::int_note [file:42]
+  Expected comparison to hold: 101 == value
+  extra note on failing test
 
   7) Failure:
+combined::int_fmt [file:42]
+  Expected comparison to hold: 022 == value
+  0022 != 0144
+
+  8) Failure:
 combined::bool [file:42]
   0 != value
   0 != 1
 
-  8) Failure:
+  9) Failure:
 combined::multiline_description [file:42]
   Function call failed: -1
   description line 1
   description line 2
 
-  9) Failure:
+  10) Failure:
 combined::null_string [file:42]
   String mismatch: "expected" != actual ("this one fails")
   'expected' != NULL
 
+  11) Failure:
+combined::failf [file:42]
+  Test failed.
+  some reason: foo
+
+  12) Failure:
+combined::compare_i [file:42]
+  Expected comparison to hold: two < 1
+  2 >= 1
+
+  13) Failure:
+combined::compare_i_with_format [file:42]
+  Expected comparison to hold: two < 1
+  foo: bar
+
+  14) Failure:
+combined::compare_u [file:42]
+  Expected comparison to hold: two < 1
+  2 >= 1
+
+  15) Failure:
+combined::compare_u_with_format [file:42]
+  Expected comparison to hold: two < 1
+  foo: bar
+
 written summary file to different.xml
diff --git a/t/unit-tests/clar/test/expected/summary_without_filename b/t/unit-tests/clar/test/expected/summary_without_filename
index 7874c1d..83ba770 100644
--- a/t/unit-tests/clar/test/expected/summary_without_filename
+++ b/t/unit-tests/clar/test/expected/summary_without_filename
@@ -1,6 +1,6 @@
 Loaded 1 suites:
 Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')
-FFFFFFFFF
+FFFFFFFFFFFFFFF
 
   1) Failure:
 combined::1 [file:42]
@@ -22,28 +22,58 @@
 
   5) Failure:
 combined::int [file:42]
-  101 != value ("extra note on failing test")
+  Expected comparison to hold: 101 == value
   101 != 100
 
   6) Failure:
-combined::int_fmt [file:42]
-  022 != value
-  0022 != 0144
+combined::int_note [file:42]
+  Expected comparison to hold: 101 == value
+  extra note on failing test
 
   7) Failure:
+combined::int_fmt [file:42]
+  Expected comparison to hold: 022 == value
+  0022 != 0144
+
+  8) Failure:
 combined::bool [file:42]
   0 != value
   0 != 1
 
-  8) Failure:
+  9) Failure:
 combined::multiline_description [file:42]
   Function call failed: -1
   description line 1
   description line 2
 
-  9) Failure:
+  10) Failure:
 combined::null_string [file:42]
   String mismatch: "expected" != actual ("this one fails")
   'expected' != NULL
 
+  11) Failure:
+combined::failf [file:42]
+  Test failed.
+  some reason: foo
+
+  12) Failure:
+combined::compare_i [file:42]
+  Expected comparison to hold: two < 1
+  2 >= 1
+
+  13) Failure:
+combined::compare_i_with_format [file:42]
+  Expected comparison to hold: two < 1
+  foo: bar
+
+  14) Failure:
+combined::compare_u [file:42]
+  Expected comparison to hold: two < 1
+  2 >= 1
+
+  15) Failure:
+combined::compare_u_with_format [file:42]
+  Expected comparison to hold: two < 1
+  foo: bar
+
 written summary file to summary.xml
diff --git a/t/unit-tests/clar/test/expected/tap b/t/unit-tests/clar/test/expected/tap
index bddbd5d..e67118d 100644
--- a/t/unit-tests/clar/test/expected/tap
+++ b/t/unit-tests/clar/test/expected/tap
@@ -8,7 +8,7 @@
       file: 'file'
       line: 42
       function: 'func'
-    ---
+    ...
 not ok 2 - combined::2
     ---
     reason: |
@@ -17,7 +17,7 @@
       file: 'file'
       line: 42
       function: 'func'
-    ---
+    ...
 not ok 3 - combined::strings
     ---
     reason: |
@@ -27,7 +27,7 @@
       file: 'file'
       line: 42
       function: 'func'
-    ---
+    ...
 not ok 4 - combined::strings_with_length
     ---
     reason: |
@@ -37,28 +37,38 @@
       file: 'file'
       line: 42
       function: 'func'
-    ---
+    ...
 not ok 5 - combined::int
     ---
     reason: |
-      101 != value ("extra note on failing test")
+      Expected comparison to hold: 101 == value
       101 != 100
     at:
       file: 'file'
       line: 42
       function: 'func'
-    ---
-not ok 6 - combined::int_fmt
+    ...
+not ok 6 - combined::int_note
     ---
     reason: |
-      022 != value
+      Expected comparison to hold: 101 == value
+      extra note on failing test
+    at:
+      file: 'file'
+      line: 42
+      function: 'func'
+    ...
+not ok 7 - combined::int_fmt
+    ---
+    reason: |
+      Expected comparison to hold: 022 == value
       0022 != 0144
     at:
       file: 'file'
       line: 42
       function: 'func'
-    ---
-not ok 7 - combined::bool
+    ...
+not ok 8 - combined::bool
     ---
     reason: |
       0 != value
@@ -67,8 +77,8 @@
       file: 'file'
       line: 42
       function: 'func'
-    ---
-not ok 8 - combined::multiline_description
+    ...
+not ok 9 - combined::multiline_description
     ---
     reason: |
       Function call failed: -1
@@ -78,8 +88,8 @@
       file: 'file'
       line: 42
       function: 'func'
-    ---
-not ok 9 - combined::null_string
+    ...
+not ok 10 - combined::null_string
     ---
     reason: |
       String mismatch: "expected" != actual ("this one fails")
@@ -88,5 +98,55 @@
       file: 'file'
       line: 42
       function: 'func'
+    ...
+not ok 11 - combined::failf
     ---
-1..9
+    reason: |
+      Test failed.
+      some reason: foo
+    at:
+      file: 'file'
+      line: 42
+      function: 'func'
+    ...
+not ok 12 - combined::compare_i
+    ---
+    reason: |
+      Expected comparison to hold: two < 1
+      2 >= 1
+    at:
+      file: 'file'
+      line: 42
+      function: 'func'
+    ...
+not ok 13 - combined::compare_i_with_format
+    ---
+    reason: |
+      Expected comparison to hold: two < 1
+      foo: bar
+    at:
+      file: 'file'
+      line: 42
+      function: 'func'
+    ...
+not ok 14 - combined::compare_u
+    ---
+    reason: |
+      Expected comparison to hold: two < 1
+      2 >= 1
+    at:
+      file: 'file'
+      line: 42
+      function: 'func'
+    ...
+not ok 15 - combined::compare_u_with_format
+    ---
+    reason: |
+      Expected comparison to hold: two < 1
+      foo: bar
+    at:
+      file: 'file'
+      line: 42
+      function: 'func'
+    ...
+1..15
diff --git a/t/unit-tests/clar/test/expected/without_arguments b/t/unit-tests/clar/test/expected/without_arguments
index 1111d41..9891f45 100644
--- a/t/unit-tests/clar/test/expected/without_arguments
+++ b/t/unit-tests/clar/test/expected/without_arguments
@@ -1,6 +1,6 @@
 Loaded 1 suites:
 Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')
-FFFFFFFFF
+FFFFFFFFFFFFFFF
 
   1) Failure:
 combined::1 [file:42]
@@ -22,27 +22,57 @@
 
   5) Failure:
 combined::int [file:42]
-  101 != value ("extra note on failing test")
+  Expected comparison to hold: 101 == value
   101 != 100
 
   6) Failure:
-combined::int_fmt [file:42]
-  022 != value
-  0022 != 0144
+combined::int_note [file:42]
+  Expected comparison to hold: 101 == value
+  extra note on failing test
 
   7) Failure:
+combined::int_fmt [file:42]
+  Expected comparison to hold: 022 == value
+  0022 != 0144
+
+  8) Failure:
 combined::bool [file:42]
   0 != value
   0 != 1
 
-  8) Failure:
+  9) Failure:
 combined::multiline_description [file:42]
   Function call failed: -1
   description line 1
   description line 2
 
-  9) Failure:
+  10) Failure:
 combined::null_string [file:42]
   String mismatch: "expected" != actual ("this one fails")
   'expected' != NULL
 
+  11) Failure:
+combined::failf [file:42]
+  Test failed.
+  some reason: foo
+
+  12) Failure:
+combined::compare_i [file:42]
+  Expected comparison to hold: two < 1
+  2 >= 1
+
+  13) Failure:
+combined::compare_i_with_format [file:42]
+  Expected comparison to hold: two < 1
+  foo: bar
+
+  14) Failure:
+combined::compare_u [file:42]
+  Expected comparison to hold: two < 1
+  2 >= 1
+
+  15) Failure:
+combined::compare_u_with_format [file:42]
+  Expected comparison to hold: two < 1
+  foo: bar
+
diff --git a/t/unit-tests/clar/test/selftest.c b/t/unit-tests/clar/test/selftest.c
index eed83e4..6eadc64 100644
--- a/t/unit-tests/clar/test/selftest.c
+++ b/t/unit-tests/clar/test/selftest.c
@@ -298,7 +298,7 @@ void test_selftest__help(void)
 
 void test_selftest__without_arguments(void)
 {
-	cl_invoke(assert_output("combined", "without_arguments", 9, NULL));
+	cl_invoke(assert_output("combined", "without_arguments", 15, NULL));
 }
 
 void test_selftest__specific_test(void)
@@ -313,12 +313,12 @@ void test_selftest__stop_on_failure(void)
 
 void test_selftest__quiet(void)
 {
-	cl_invoke(assert_output("combined", "quiet", 9, "-q", NULL));
+	cl_invoke(assert_output("combined", "quiet", 15, "-q", NULL));
 }
 
 void test_selftest__tap(void)
 {
-	cl_invoke(assert_output("combined", "tap", 9, "-t", NULL));
+	cl_invoke(assert_output("combined", "tap", 15, "-t", NULL));
 }
 
 void test_selftest__suite_names(void)
@@ -329,7 +329,7 @@ void test_selftest__suite_names(void)
 void test_selftest__summary_without_filename(void)
 {
 	struct stat st;
-	cl_invoke(assert_output("combined", "summary_without_filename", 9, "-r", NULL));
+	cl_invoke(assert_output("combined", "summary_without_filename", 15, "-r", NULL));
 	/* The summary contains timestamps, so we cannot verify its contents. */
 	cl_must_pass(stat("summary.xml", &st));
 }
@@ -337,7 +337,7 @@ void test_selftest__summary_without_filename(void)
 void test_selftest__summary_with_filename(void)
 {
 	struct stat st;
-	cl_invoke(assert_output("combined", "summary_with_filename", 9, "-rdifferent.xml", NULL));
+	cl_invoke(assert_output("combined", "summary_with_filename", 15, "-rdifferent.xml", NULL));
 	/* The summary contains timestamps, so we cannot verify its contents. */
 	cl_must_pass(stat("different.xml", &st));
 }
diff --git a/t/unit-tests/clar/test/suites/combined.c b/t/unit-tests/clar/test/suites/combined.c
index e8b41c9..9e9dbc2 100644
--- a/t/unit-tests/clar/test/suites/combined.c
+++ b/t/unit-tests/clar/test/suites/combined.c
@@ -55,7 +55,12 @@ void test_combined__strings_with_length(void)
 void test_combined__int(void)
 {
 	int value = 100;
-	cl_assert_equal_i(100, value);
+	cl_assert_equal_i(101, value);
+}
+
+void test_combined__int_note(void)
+{
+	int value = 100;
 	cl_assert_equal_i_(101, value, "extra note on failing test");
 }
 
@@ -83,3 +88,61 @@ void test_combined__null_string(void)
 	cl_assert_equal_s(actual, actual);
 	cl_assert_equal_s_("expected", actual, "this one fails");
 }
+
+void test_combined__failf(void)
+{
+	cl_failf("some reason: %s", "foo");
+}
+
+void test_combined__compare_i(void)
+{
+	int one = 1, two = 2;
+
+	cl_assert_equal_i(one, 1);
+	cl_assert_equal_i(one, 1);
+	cl_assert_equal_i_(one, 1, "format");
+	cl_assert_lt_i(one, 2);
+	cl_assert_lt_i_(one, 2, "format");
+	cl_assert_le_i(one, 2);
+	cl_assert_le_i(two, 2);
+	cl_assert_le_i_(two, 2, "format");
+	cl_assert_gt_i(two, 1);
+	cl_assert_gt_i_(two, 1, "format");
+	cl_assert_ge_i(two, 2);
+	cl_assert_ge_i(3, two);
+	cl_assert_ge_i_(3, two, "format");
+
+	cl_assert_lt_i(two, 1); /* this one fails */
+}
+
+void test_combined__compare_i_with_format(void)
+{
+	int two = 2;
+	cl_assert_lt_i_(two, 1, "foo: %s", "bar");
+}
+
+void test_combined__compare_u(void)
+{
+	unsigned one = 1, two = 2;
+
+	cl_assert_equal_u(one, 1);
+	cl_assert_equal_u_(one, 1, "format");
+	cl_assert_lt_u(one, 2);
+	cl_assert_lt_u_(one, 2, "format");
+	cl_assert_le_u(one, 2);
+	cl_assert_le_u(two, 2);
+	cl_assert_le_u_(two, 2, "format");
+	cl_assert_gt_u(two, 1);
+	cl_assert_gt_u_(two, 1, "format");
+	cl_assert_ge_u(two, 2);
+	cl_assert_ge_u(3, two);
+	cl_assert_ge_u_(3, two, "format");
+
+	cl_assert_lt_u(two, 1); /* this one fails */
+}
+
+void test_combined__compare_u_with_format(void)
+{
+	unsigned two = 2;
+	cl_assert_lt_u_(two, 1, "foo: %s", "bar");
+}
diff --git a/t/unit-tests/u-list-objects-filter-options.c b/t/unit-tests/u-list-objects-filter-options.c
new file mode 100644
index 0000000..f7d7370
--- /dev/null
+++ b/t/unit-tests/u-list-objects-filter-options.c
@@ -0,0 +1,53 @@
+#include "unit-test.h"
+#include "list-objects-filter-options.h"
+#include "strbuf.h"
+
+/* Helper to test gently_parse_list_objects_filter() */
+static void check_gentle_parse(const char *filter_spec,
+			       int expect_success,
+			       int allow_auto,
+			       enum list_objects_filter_choice expected_choice)
+{
+	struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
+	struct strbuf errbuf = STRBUF_INIT;
+	int ret;
+
+	filter_options.allow_auto_filter = allow_auto;
+
+	ret = gently_parse_list_objects_filter(&filter_options, filter_spec, &errbuf);
+
+	if (expect_success) {
+		cl_assert_equal_i(ret, 0);
+		cl_assert_equal_i(expected_choice, filter_options.choice);
+		cl_assert_equal_i(errbuf.len, 0);
+	} else {
+		cl_assert(ret != 0);
+		cl_assert(errbuf.len > 0);
+	}
+
+	strbuf_release(&errbuf);
+	list_objects_filter_release(&filter_options);
+}
+
+void test_list_objects_filter_options__regular_filters(void)
+{
+	check_gentle_parse("blob:none", 1, 0, LOFC_BLOB_NONE);
+	check_gentle_parse("blob:none", 1, 1, LOFC_BLOB_NONE);
+	check_gentle_parse("blob:limit=5k", 1, 0, LOFC_BLOB_LIMIT);
+	check_gentle_parse("blob:limit=5k", 1, 1, LOFC_BLOB_LIMIT);
+	check_gentle_parse("combine:blob:none+tree:0", 1, 0, LOFC_COMBINE);
+	check_gentle_parse("combine:blob:none+tree:0", 1, 1, LOFC_COMBINE);
+}
+
+void test_list_objects_filter_options__auto_allowed(void)
+{
+	check_gentle_parse("auto", 1, 1, LOFC_AUTO);
+	check_gentle_parse("auto", 0, 0, 0);
+}
+
+void test_list_objects_filter_options__combine_auto_fails(void)
+{
+	check_gentle_parse("combine:auto+blob:none", 0, 1, 0);
+	check_gentle_parse("combine:blob:none+auto", 0, 1, 0);
+	check_gentle_parse("combine:auto+auto", 0, 1, 0);
+}
diff --git a/t/unit-tests/u-oidmap.c b/t/unit-tests/u-oidmap.c
index b23af44..00481df 100644
--- a/t/unit-tests/u-oidmap.c
+++ b/t/unit-tests/u-oidmap.c
@@ -14,6 +14,13 @@ struct test_entry {
 	char name[FLEX_ARRAY];
 };
 
+static int freed;
+
+static void test_free_fn(void *p) {
+	freed++;
+	free(p);
+}
+
 static const char *const key_val[][2] = { { "11", "one" },
 					  { "22", "two" },
 					  { "33", "three" } };
@@ -134,3 +141,37 @@ void test_oidmap__iterate(void)
 	cl_assert_equal_i(count, ARRAY_SIZE(key_val));
 	cl_assert_equal_i(hashmap_get_size(&map.map), ARRAY_SIZE(key_val));
 }
+
+void test_oidmap__clear_without_free_callback(void)
+{
+	struct oidmap local_map = OIDMAP_INIT;
+	struct test_entry *entry;
+
+	freed = 0;
+
+	FLEX_ALLOC_STR(entry, name, "one");
+	cl_parse_any_oid("11", &entry->entry.oid);
+	cl_assert(oidmap_put(&local_map, entry) == NULL);
+
+	oidmap_clear_with_free(&local_map, NULL);
+
+	cl_assert_equal_i(freed, 0);
+
+	free(entry);
+}
+
+void test_oidmap__clear_with_free_callback(void)
+{
+	struct oidmap local_map = OIDMAP_INIT;
+	struct test_entry *entry;
+
+	freed = 0;
+
+	FLEX_ALLOC_STR(entry, name, "one");
+	cl_parse_any_oid("11", &entry->entry.oid);
+	cl_assert(oidmap_put(&local_map, entry) == NULL);
+
+	oidmap_clear_with_free(&local_map, test_free_fn);
+
+	cl_assert_equal_i(freed, 1);
+}
diff --git a/t/unit-tests/u-reftable-record.c b/t/unit-tests/u-reftable-record.c
index 6c8c0d5..1bf2e17 100644
--- a/t/unit-tests/u-reftable-record.c
+++ b/t/unit-tests/u-reftable-record.c
@@ -51,10 +51,10 @@ void test_reftable_record__varint_roundtrip(void)
 		int n = put_var_int(&out, in);
 		uint64_t got = 0;
 
-		cl_assert(n > 0);
+		cl_assert_gt_i(n, 0);
 		out.len = n;
 		n = get_var_int(&got, &out);
-		cl_assert(n > 0);
+		cl_assert_gt_i(n, 0);
 
 		cl_assert_equal_i(got, in);
 	}
@@ -110,7 +110,7 @@ void test_reftable_record__ref_record_comparison(void)
 	cl_assert(reftable_record_equal(&in[1], &in[2],
 					REFTABLE_HASH_SIZE_SHA1) == 0);
 	cl_assert_equal_i(reftable_record_cmp(&in[1], &in[2], &cmp), 0);
-	cl_assert(cmp > 0);
+	cl_assert_gt_i(cmp, 0);
 
 	in[1].u.ref.value_type = in[0].u.ref.value_type;
 	cl_assert(reftable_record_equal(&in[0], &in[1],
@@ -184,7 +184,7 @@ void test_reftable_record__ref_record_roundtrip(void)
 
 		reftable_record_key(&in, &key);
 		n = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);
-		cl_assert(n > 0);
+		cl_assert_gt_i(n, 0);
 
 		/* decode into a non-zero reftable_record to test for leaks. */
 		m = reftable_record_decode(&out, key, i, dest, REFTABLE_HASH_SIZE_SHA1, &scratch);
@@ -228,11 +228,11 @@ void test_reftable_record__log_record_comparison(void)
 	cl_assert_equal_i(reftable_record_equal(&in[1], &in[2],
 						REFTABLE_HASH_SIZE_SHA1), 0);
 	cl_assert_equal_i(reftable_record_cmp(&in[1], &in[2], &cmp), 0);
-	cl_assert(cmp > 0);
+	cl_assert_gt_i(cmp, 0);
 	/* comparison should be reversed for equal keys, because
 	 * comparison is now performed on the basis of update indices */
 	cl_assert_equal_i(reftable_record_cmp(&in[0], &in[1], &cmp), 0);
-	cl_assert(cmp < 0);
+	cl_assert_lt_i(cmp, 0);
 
 	in[1].u.log.update_index = in[0].u.log.update_index;
 	cl_assert(reftable_record_equal(&in[0], &in[1],
@@ -344,7 +344,7 @@ void test_reftable_record__log_record_roundtrip(void)
 		reftable_record_key(&rec, &key);
 
 		n = reftable_record_encode(&rec, dest, REFTABLE_HASH_SIZE_SHA1);
-		cl_assert(n >= 0);
+		cl_assert_ge_i(n, 0);
 		valtype = reftable_record_val_type(&rec);
 		m = reftable_record_decode(&out, key, valtype, dest,
 					   REFTABLE_HASH_SIZE_SHA1, &scratch);
@@ -382,7 +382,7 @@ void test_reftable_record__key_roundtrip(void)
 	extra = 6;
 	n = reftable_encode_key(&restart, dest, last_key, key, extra);
 	cl_assert(!restart);
-	cl_assert(n > 0);
+	cl_assert_gt_i(n, 0);
 
 	cl_assert_equal_i(reftable_buf_addstr(&roundtrip,
 					      "refs/heads/master"), 0);
@@ -432,7 +432,7 @@ void test_reftable_record__obj_record_comparison(void)
 	cl_assert_equal_i(reftable_record_equal(&in[1], &in[2],
 						REFTABLE_HASH_SIZE_SHA1), 0);
 	cl_assert_equal_i(reftable_record_cmp(&in[1], &in[2], &cmp), 0);
-	cl_assert(cmp > 0);
+	cl_assert_gt_i(cmp, 0);
 
 	in[1].u.obj.offset_len = in[0].u.obj.offset_len;
 	cl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) != 0);
@@ -485,7 +485,7 @@ void test_reftable_record__obj_record_roundtrip(void)
 		t_copy(&in);
 		reftable_record_key(&in, &key);
 		n = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);
-		cl_assert(n > 0);
+		cl_assert_gt_i(n, 0);
 		extra = reftable_record_val_type(&in);
 		m = reftable_record_decode(&out, key, extra, dest,
 					   REFTABLE_HASH_SIZE_SHA1, &scratch);
@@ -535,7 +535,7 @@ void test_reftable_record__index_record_comparison(void)
 	cl_assert_equal_i(reftable_record_equal(&in[1], &in[2],
 						REFTABLE_HASH_SIZE_SHA1), 0);
 	cl_assert_equal_i(reftable_record_cmp(&in[1], &in[2], &cmp), 0);
-	cl_assert(cmp > 0);
+	cl_assert_gt_i(cmp, 0);
 
 	in[1].u.idx.offset = in[0].u.idx.offset;
 	cl_assert(reftable_record_equal(&in[0], &in[1],
diff --git a/t/unit-tests/u-reftable-stack.c b/t/unit-tests/u-reftable-stack.c
index a8b9181..b8110cd 100644
--- a/t/unit-tests/u-reftable-stack.c
+++ b/t/unit-tests/u-reftable-stack.c
@@ -1067,6 +1067,7 @@ void test_reftable_stack__add_performs_auto_compaction(void)
 			.value_type = REFTABLE_REF_SYMREF,
 			.value.symref = (char *) "master",
 		};
+		bool required = false;
 		char buf[128];
 
 		/*
@@ -1087,10 +1088,17 @@ void test_reftable_stack__add_performs_auto_compaction(void)
 		 * auto compaction is disabled. When enabled, we should merge
 		 * all tables in the stack.
 		 */
-		if (i != n)
+		cl_assert_equal_i(reftable_stack_compaction_required(st, true, &required), 0);
+		if (i != n) {
 			cl_assert_equal_i(st->merged->tables_len, i + 1);
-		else
+			if (i < 1)
+				cl_assert_equal_b(required, false);
+			else
+				cl_assert_equal_b(required, true);
+		} else {
 			cl_assert_equal_i(st->merged->tables_len, 1);
+			cl_assert_equal_b(required, false);
+		}
 	}
 
 	reftable_stack_destroy(st);
diff --git a/t/unit-tests/u-string-list.c b/t/unit-tests/u-string-list.c
index a2457d7..7ad84cc 100644
--- a/t/unit-tests/u-string-list.c
+++ b/t/unit-tests/u-string-list.c
@@ -243,6 +243,138 @@ void test_string_list__filter(void)
 	t_string_list_clear(&list, 0);
 }
 
+static void t_string_list_has_string(
+	struct string_list *list,
+	const char *string,
+	int expected)
+{
+	int has_string = string_list_has_string(list, string);
+	cl_assert_equal_i(has_string, expected);
+}
+
+void test_string_list__has_string(void)
+{
+	struct string_list list = STRING_LIST_INIT_DUP;
+
+	t_create_string_list_dup(&list, 0, NULL);
+	t_string_list_has_string(&list, "", 0);
+
+	t_create_string_list_dup(&list, 0, "a", "b", "c", NULL);
+	t_string_list_has_string(&list, "a", 1);
+	t_string_list_has_string(&list, "b", 1);
+	t_string_list_has_string(&list, "c", 1);
+	t_string_list_has_string(&list, "d", 0);
+
+	t_string_list_clear(&list, 0);
+}
+
+static void t_string_list_insert(struct string_list *expected_strings, ...)
+{
+	struct string_list strings_to_insert = STRING_LIST_INIT_DUP;
+	struct string_list list = STRING_LIST_INIT_DUP;
+	va_list ap;
+
+	va_start(ap, expected_strings);
+	t_vcreate_string_list_dup(&strings_to_insert, 0, ap);
+	va_end(ap);
+
+	for (size_t i = 0; i < strings_to_insert.nr; i++)
+		string_list_insert(&list, strings_to_insert.items[i].string);
+
+	t_string_list_equal(&list, expected_strings);
+
+	string_list_clear(&strings_to_insert, 0);
+	string_list_clear(&list, 0);
+}
+
+void test_string_list__insert(void)
+{
+	struct string_list expected_strings = STRING_LIST_INIT_DUP;
+
+	t_create_string_list_dup(&expected_strings, 0, NULL);
+	t_string_list_insert(&expected_strings, NULL);
+
+	t_create_string_list_dup(&expected_strings, 0, "a", "b", NULL);
+	t_string_list_insert(&expected_strings, "b", "a", "a", "b", NULL);
+
+	t_create_string_list_dup(&expected_strings, 0, "a", "b", "c", NULL);
+	t_string_list_insert(&expected_strings, "c", "b", "a", "c", "b", NULL);
+
+	t_create_string_list_dup(&expected_strings, 0, "", "a", NULL);
+	t_string_list_insert(&expected_strings, "a", "a", "a", "", NULL);
+
+	t_string_list_clear(&expected_strings, 0);
+}
+
+static void t_string_list_sort(struct string_list *list, ...)
+{
+	struct string_list expected_strings = STRING_LIST_INIT_DUP;
+	va_list ap;
+
+	va_start(ap, list);
+	t_vcreate_string_list_dup(&expected_strings, 0, ap);
+	va_end(ap);
+
+	string_list_sort(list);
+	t_string_list_equal(list, &expected_strings);
+
+	string_list_clear(&expected_strings, 0);
+}
+
+void test_string_list__sort(void)
+{
+	struct string_list list = STRING_LIST_INIT_DUP;
+
+	t_create_string_list_dup(&list, 0, NULL);
+	t_string_list_sort(&list, NULL);
+
+	t_create_string_list_dup(&list, 0, "b", "", "a", NULL);
+	t_string_list_sort(&list, "", "a", "b", NULL);
+
+	t_create_string_list_dup(&list, 0, "c", "a", "b", "a", NULL);
+	t_string_list_sort(&list, "a", "a", "b", "c", NULL);
+
+	t_string_list_clear(&list, 0);
+}
+
+static void t_string_list_remove(
+	struct string_list *expected_strings,
+	struct string_list *list,
+	char const *str)
+{
+	string_list_remove(list, str, 0);
+	t_string_list_equal(list, expected_strings);
+}
+
+void test_string_list__remove(void)
+{
+	struct string_list expected_strings = STRING_LIST_INIT_DUP;
+	struct string_list list = STRING_LIST_INIT_DUP;
+
+	t_create_string_list_dup(&expected_strings, 0, NULL);
+	t_create_string_list_dup(&list, 0, NULL);
+	t_string_list_remove(&expected_strings, &list, "");
+
+	t_create_string_list_dup(&expected_strings, 0, "a", NULL);
+	t_create_string_list_dup(&list, 0, "a", "a", NULL);
+	t_string_list_remove(&expected_strings, &list, "a");
+
+	t_create_string_list_dup(&expected_strings, 0, "a", "b", "b", NULL);
+	t_create_string_list_dup(&list, 0, "a", "b", "b", "c", NULL);
+	t_string_list_remove(&expected_strings, &list, "c");
+
+	t_create_string_list_dup(&expected_strings, 0, "a", "b", "d", NULL);
+	t_create_string_list_dup(&list, 0, "a", "b", "c", "d", NULL);
+	t_string_list_remove(&expected_strings, &list, "c");
+
+	t_create_string_list_dup(&expected_strings, 0, "a", "b", "c", "d", NULL);
+	t_create_string_list_dup(&list, 0, "a", "b", "c", "d", NULL);
+	t_string_list_remove(&expected_strings, &list, "e");
+
+	t_string_list_clear(&expected_strings, 0);
+	t_string_list_clear(&list, 0);
+}
+
 static void t_string_list_remove_duplicates(struct string_list *list, ...)
 {
 	struct string_list expected_strings = STRING_LIST_INIT_DUP;
@@ -304,3 +436,114 @@ void test_string_list__remove_duplicates(void)
 
 	t_string_list_clear(&list, 0);
 }
+
+static void t_string_list_sort_u(struct string_list *list, ...)
+{
+	struct string_list expected_strings = STRING_LIST_INIT_DUP;
+	va_list ap;
+
+	va_start(ap, list);
+	t_vcreate_string_list_dup(&expected_strings, 0, ap);
+	va_end(ap);
+
+	string_list_sort_u(list, 0);
+	t_string_list_equal(list, &expected_strings);
+
+	string_list_clear(&expected_strings, 0);
+}
+
+void test_string_list__sort_u(void)
+{
+	struct string_list list = STRING_LIST_INIT_DUP;
+
+	t_create_string_list_dup(&list, 0, NULL);
+	t_string_list_sort_u(&list, NULL);
+
+	t_create_string_list_dup(&list, 0, "", "", "", "", NULL);
+	t_string_list_sort_u(&list, "", NULL);
+
+	t_create_string_list_dup(&list, 0, "b", "a", "a", "", NULL);
+	t_string_list_sort_u(&list, "", "a", "b", NULL);
+
+	t_create_string_list_dup(&list, 0, "b", "a", "a", "d", "c", "c", NULL);
+	t_string_list_sort_u(&list, "a", "b", "c", "d", NULL);
+
+	t_string_list_clear(&list, 0);
+}
+
+static void t_string_list_remove_empty_items(
+	struct string_list *expected_strings,
+	struct string_list *list)
+{
+	string_list_remove_empty_items(list, 0);
+	t_string_list_equal(list, expected_strings);
+}
+
+void test_string_list__remove_empty_items(void)
+{
+	struct string_list expected_strings = STRING_LIST_INIT_DUP;
+	struct string_list list = STRING_LIST_INIT_DUP;
+
+	t_create_string_list_dup(&expected_strings, 0, NULL);
+	t_create_string_list_dup(&list, 0, "", "", "", NULL);
+	t_string_list_remove_empty_items(&expected_strings, &list);
+
+	t_create_string_list_dup(&expected_strings, 0, "a", "b", NULL);
+	t_create_string_list_dup(&list, 0, "a", "", "b", "", NULL);
+	t_string_list_remove_empty_items(&expected_strings, &list);
+
+	t_string_list_clear(&expected_strings, 0);
+	t_string_list_clear(&list, 0);
+}
+
+static void t_string_list_unsorted_string_list_has_string(
+	struct string_list *list,
+	const char *str, int expected)
+{
+	int has_string = unsorted_string_list_has_string(list, str);
+	cl_assert_equal_i(has_string, expected);
+}
+
+void test_string_list__unsorted_string_list_has_string(void)
+{
+	struct string_list list = STRING_LIST_INIT_DUP;
+
+	t_create_string_list_dup(&list, 0, "b", "d", "a", NULL);
+	t_string_list_unsorted_string_list_has_string(&list, "a", 1);
+	t_string_list_unsorted_string_list_has_string(&list, "b", 1);
+	t_string_list_unsorted_string_list_has_string(&list, "c", 0);
+	t_string_list_unsorted_string_list_has_string(&list, "d", 1);
+
+	t_string_list_clear(&list, 0);
+}
+
+static void t_string_list_unsorted_string_list_delete_item(
+	struct string_list *expected_list,
+	struct string_list *list,
+	int i)
+{
+	unsorted_string_list_delete_item(list, i, 0);
+
+	t_string_list_equal(list, expected_list);
+}
+
+void test_string_list__unsorted_string_list_delete_item(void)
+{
+	struct string_list expected_strings = STRING_LIST_INIT_DUP;
+	struct string_list list = STRING_LIST_INIT_DUP;
+
+	t_create_string_list_dup(&expected_strings, 0, "a", "c", "b", NULL);
+	t_create_string_list_dup(&list, 0, "a", "d", "b", "c", NULL);
+	t_string_list_unsorted_string_list_delete_item(&expected_strings, &list, 1);
+
+	t_create_string_list_dup(&expected_strings, 0, NULL);
+	t_create_string_list_dup(&list, 0, "", NULL);
+	t_string_list_unsorted_string_list_delete_item(&expected_strings, &list, 0);
+
+	t_create_string_list_dup(&expected_strings, 0, "a", "d", "c", "b", NULL);
+	t_create_string_list_dup(&list, 0,  "a", "d", "c", "b", "d", NULL);
+	t_string_list_unsorted_string_list_delete_item(&expected_strings, &list, 4);
+
+	t_string_list_clear(&expected_strings, 0);
+	t_string_list_clear(&list, 0);
+}
diff --git a/t/unit-tests/u-utf8-width.c b/t/unit-tests/u-utf8-width.c
new file mode 100644
index 0000000..86e09c3
--- /dev/null
+++ b/t/unit-tests/u-utf8-width.c
@@ -0,0 +1,134 @@
+#include "unit-test.h"
+#include "utf8.h"
+#include "strbuf.h"
+
+/*
+ * Test utf8_strnwidth with various Chinese strings
+ * Chinese characters typically have a width of 2 columns when displayed
+ */
+void test_utf8_width__strnwidth_chinese(void)
+{
+	const char *str;
+
+	/* Test basic ASCII - each character should have width 1 */
+	cl_assert_equal_i(5, utf8_strnwidth("Hello", 5, 0));
+	/* skip_ansi = 1 */
+	cl_assert_equal_i(5, utf8_strnwidth("Hello", 5, 1));
+
+	/* Test simple Chinese characters - each should have width 2 */
+	/* "你好" is 6 bytes (3 bytes per char in UTF-8), 4 display columns */
+	cl_assert_equal_i(4, utf8_strnwidth("你好", 6, 0));
+
+	/* Test mixed ASCII and Chinese - ASCII = 1 column, Chinese = 2 columns */
+	/* "h"(1) + "i"(1) + "你"(2) + "好"(2) = 6 */
+	cl_assert_equal_i(6, utf8_strnwidth("Hi你好", 8, 0));
+
+	/* Test longer Chinese string */
+	/* 5 Chinese chars = 10 display columns */
+	cl_assert_equal_i(10, utf8_strnwidth("你好世界!", 15, 0));
+
+	/* Test individual Chinese character width */
+	cl_assert_equal_i(2, utf8_strnwidth("中", 3, 0));
+
+	/* Test empty string */
+	cl_assert_equal_i(0, utf8_strnwidth("", 0, 0));
+
+	/* Test length limiting */
+	str = "你好世界";
+	/* Only first char "你"(2 columns) within 3 bytes */
+	cl_assert_equal_i(2, utf8_strnwidth(str, 3, 0));
+	/* First two chars "你好"(4 columns) in 6 bytes */
+	cl_assert_equal_i(4, utf8_strnwidth(str, 6, 0));
+}
+
+/*
+ * Tests for utf8_strwidth (simpler version without length limit)
+ */
+void test_utf8_width__strwidth_chinese(void)
+{
+	/* Test basic ASCII */
+	cl_assert_equal_i(5, utf8_strwidth("Hello"));
+
+	/* Test Chinese characters */
+	/* 2 Chinese chars = 4 display columns */
+	cl_assert_equal_i(4, utf8_strwidth("你好"));
+
+	/* Test longer Chinese string */
+	/* 5 Chinese chars = 10 display columns */
+	cl_assert_equal_i(10, utf8_strwidth("你好世界!"));
+
+	/* Test mixed ASCII and Chinese */
+	/* 5 ASCII (5 cols) + 2 Chinese (4 cols) = 9 */
+	cl_assert_equal_i(9, utf8_strwidth("Hello世界"));
+	/* 2 ASCII (2 cols) + 2 Chinese (4 cols) + 1 ASCII (1 col) = 7 */
+	cl_assert_equal_i(7, utf8_strwidth("Hi世界!"));
+}
+
+/*
+ * Additional tests with other East Asian characters
+ */
+void test_utf8_width__strnwidth_japanese_korean(void)
+{
+	/* Japanese characters (should also be 2 columns each) */
+	/* 5 Japanese chars x 2 cols each = 10 display columns */
+	cl_assert_equal_i(10, utf8_strnwidth("こんにちは", 15, 0));
+
+	/* Korean characters (should also be 2 columns each) */
+	/* 5 Korean chars x 2 cols each = 10 display columns */
+	cl_assert_equal_i(10, utf8_strnwidth("안녕하세요", 15, 0));
+}
+
+/*
+ * Test utf8_strnwidth with CJK strings and ANSI sequences
+ */
+void test_utf8_width__strnwidth_cjk_with_ansi(void)
+{
+	/* Test CJK with ANSI sequences */
+	const char *ansi_test = "\033[1m你好\033[0m";
+	int width = utf8_strnwidth(ansi_test, strlen(ansi_test), 1);
+	/* Should skip ANSI sequences and count "你好" as 4 columns */
+	cl_assert_equal_i(4, width);
+
+	/* Test mixed ASCII, CJK, and ANSI */
+	ansi_test = "Hello\033[32m世界\033[0m!";
+	width = utf8_strnwidth(ansi_test, strlen(ansi_test), 1);
+	/* "Hello"(5) + "世界"(4) + "!"(1) = 10 */
+	cl_assert_equal_i(10, width);
+}
+
+/*
+ * Test the strbuf_utf8_align function with CJK characters
+ */
+void test_utf8_width__strbuf_utf8_align(void)
+{
+	struct strbuf buf = STRBUF_INIT;
+
+	/* Test left alignment with CJK */
+	strbuf_utf8_align(&buf, ALIGN_LEFT, 10, "你好");
+	/* Since "你好" is 4 display columns, we need 6 more spaces to reach 10 */
+	cl_assert_equal_s("你好      ", buf.buf);
+	strbuf_reset(&buf);
+
+	/* Test right alignment with CJK */
+	strbuf_utf8_align(&buf, ALIGN_RIGHT, 8, "世界");
+	/* "世界" is 4 display columns, so we need 4 leading spaces */
+	cl_assert_equal_s("    世界", buf.buf);
+	strbuf_reset(&buf);
+
+	/* Test center alignment with CJK */
+	strbuf_utf8_align(&buf, ALIGN_MIDDLE, 10, "中");
+	/* "中" is 2 display columns, so (10-2)/2 = 4 spaces on left, 4 on right */
+	cl_assert_equal_s("    中    ", buf.buf);
+	strbuf_reset(&buf);
+
+	strbuf_utf8_align(&buf, ALIGN_MIDDLE, 5, "中");
+	/* "中" is 2 display columns, so (5-2)/2 = 1 spaces on left, 2 on right */
+	cl_assert_equal_s(" 中  ", buf.buf);
+	strbuf_reset(&buf);
+
+	/* Test alignment that is smaller than string width */
+	strbuf_utf8_align(&buf, ALIGN_LEFT, 2, "你好");
+	/* Since "你好" is 4 display columns, it should not be truncated */
+	cl_assert_equal_s("你好", buf.buf);
+	strbuf_release(&buf);
+}
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
index 5af6450..752fb38 100644
--- a/t/unit-tests/unit-test.c
+++ b/t/unit-tests/unit-test.c
@@ -29,6 +29,7 @@ int cmd_main(int argc, const char **argv)
 		OPT_NOOP_NOARG('d', "debug"),
 		OPT_NOOP_NOARG(0, "github-workflow-markup"),
 		OPT_NOOP_NOARG(0, "no-bin-wrappers"),
+		OPT_NOOP_ARG(0, "no-chain-lint"),
 		OPT_NOOP_ARG(0, "root"),
 		OPT_NOOP_ARG(0, "stress"),
 		OPT_NOOP_NOARG(0, "tee"),
diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
index 39a0b72..5398b44 100644
--- a/t/unit-tests/unit-test.h
+++ b/t/unit-tests/unit-test.h
@@ -7,9 +7,3 @@
 #else
 # include GIT_CLAR_DECLS_H
 #endif
-
-#define cl_failf(fmt, ...) do { \
-	char desc[4096]; \
-	snprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \
-	clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \
-} while (0)
diff --git a/tag.c b/tag.c
index 1d52686..2f12e51 100644
--- a/tag.c
+++ b/tag.c
@@ -1,4 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
 #define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "git-compat-util.h"
@@ -13,6 +12,7 @@
 #include "gpg-interface.h"
 #include "hex.h"
 #include "packfile.h"
+#include "repository.h"
 
 const char *tag_type = "tag";
 
@@ -44,28 +44,28 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)
 	return ret;
 }
 
-int gpg_verify_tag(const struct object_id *oid, const char *name_to_report,
-		unsigned flags)
+int gpg_verify_tag(struct repository *r, const struct object_id *oid,
+		   const char *name_to_report, unsigned flags)
 {
 	enum object_type type;
 	char *buf;
 	unsigned long size;
 	int ret;
 
-	type = odb_read_object_info(the_repository->objects, oid, NULL);
+	type = odb_read_object_info(r->objects, oid, NULL);
 	if (type != OBJ_TAG)
 		return error("%s: cannot verify a non-tag object of type %s.",
 				name_to_report ?
 				name_to_report :
-				repo_find_unique_abbrev(the_repository, oid, DEFAULT_ABBREV),
+				oid_to_hex(oid),
 				type_name(type));
 
-	buf = odb_read_object(the_repository->objects, oid, &type, &size);
+	buf = odb_read_object(r->objects, oid, &type, &size);
 	if (!buf)
 		return error("%s: unable to read file.",
 				name_to_report ?
 				name_to_report :
-				repo_find_unique_abbrev(the_repository, oid, DEFAULT_ABBREV));
+				oid_to_hex(oid));
 
 	ret = run_gpg_verify(buf, size, flags);
 
@@ -94,18 +94,6 @@ struct object *deref_tag(struct repository *r, struct object *o, const char *war
 	return o;
 }
 
-struct object *deref_tag_noverify(struct repository *r, struct object *o)
-{
-	while (o && o->type == OBJ_TAG) {
-		o = parse_object(r, &o->oid);
-		if (o && o->type == OBJ_TAG && ((struct tag *)o)->tagged)
-			o = ((struct tag *)o)->tagged;
-		else
-			o = NULL;
-	}
-	return o;
-}
-
 struct tag *lookup_tag(struct repository *r, const struct object_id *oid)
 {
 	struct object *obj = lookup_object(r, oid);
@@ -160,9 +148,11 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u
 		FREE_AND_NULL(item->tag);
 	}
 
-	if (size < the_hash_algo->hexsz + 24)
+	if (size < r->hash_algo->hexsz + 24)
 		return -1;
-	if (memcmp("object ", bufptr, 7) || parse_oid_hex(bufptr + 7, &oid, &bufptr) || *bufptr++ != '\n')
+	if (memcmp("object ", bufptr, 7) ||
+	    parse_oid_hex_algop(bufptr + 7, &oid, &bufptr, r->hash_algo) ||
+	    *bufptr++ != '\n')
 		return -1;
 
 	if (!starts_with(bufptr, "type "))
@@ -213,7 +203,7 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u
 	return 0;
 }
 
-int parse_tag(struct tag *item)
+int parse_tag(struct repository *r, struct tag *item)
 {
 	enum object_type type;
 	void *data;
@@ -222,8 +212,7 @@ int parse_tag(struct tag *item)
 
 	if (item->object.parsed)
 		return 0;
-	data = odb_read_object(the_repository->objects, &item->object.oid,
-			       &type, &size);
+	data = odb_read_object(r->objects, &item->object.oid, &type, &size);
 	if (!data)
 		return error("Could not read %s",
 			     oid_to_hex(&item->object.oid));
@@ -232,7 +221,7 @@ int parse_tag(struct tag *item)
 		return error("Object %s not a tag",
 			     oid_to_hex(&item->object.oid));
 	}
-	ret = parse_tag_buffer(the_repository, item, data, size);
+	ret = parse_tag_buffer(r, item, data, size);
 	free(data);
 	return ret;
 }
diff --git a/tag.h b/tag.h
index c49d7c1..534687c 100644
--- a/tag.h
+++ b/tag.h
@@ -13,11 +13,10 @@ struct tag {
 };
 struct tag *lookup_tag(struct repository *r, const struct object_id *oid);
 int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, unsigned long size);
-int parse_tag(struct tag *item);
+int parse_tag(struct repository *r, struct tag *item);
 void release_tag_memory(struct tag *t);
 struct object *deref_tag(struct repository *r, struct object *, const char *, int);
-struct object *deref_tag_noverify(struct repository *r, struct object *);
-int gpg_verify_tag(const struct object_id *oid,
+int gpg_verify_tag(struct repository *r, const struct object_id *oid,
 		   const char *name_to_report, unsigned flags);
 struct object_id *get_tagged_oid(struct tag *tag);
 
diff --git a/templates/hooks/commit-msg.sample b/templates/hooks/commit-msg.sample
index b58d118..f7458ef 100755
--- a/templates/hooks/commit-msg.sample
+++ b/templates/hooks/commit-msg.sample
@@ -15,10 +15,60 @@
 # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
 # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
 
-# This example catches duplicate Signed-off-by lines.
+# This example catches duplicate Signed-off-by lines and messages that
+# would confuse 'git am'.
+
+ret=0
 
 test "" = "$(grep '^Signed-off-by: ' "$1" |
 	 sort | uniq -c | sed -e '/^[ 	]*1[ 	]/d')" || {
 	echo >&2 Duplicate Signed-off-by lines.
-	exit 1
+	ret=1
 }
+
+comment_re="$(
+	{
+		git config --get-regexp "^core\.comment(char|string)\$" ||
+			echo '#'
+	} | sed -n -e '
+		${
+			s/^[^ ]* //
+			s|[][*./\]|\\&|g
+			s/^auto$/[#;@!$%^&|:]/
+			p
+		}'
+)"
+scissors_line="^${comment_re} -\{8,\} >8 -\{8,\}\$"
+comment_line="^${comment_re}.*"
+blank_line='^[ 	]*$'
+# Disallow lines starting with "diff -" or "Index: " in the body of the
+# message. Stop looking if we see a scissors line.
+line="$(sed -n -e "
+	# Skip comments and blank lines at the start of the file.
+	/${scissors_line}/q
+	/${comment_line}/d
+	/${blank_line}/d
+	# The first paragraph will become the subject header so
+	# does not need to be checked.
+	: subject
+	n
+	/${scissors_line}/q
+	/${blank_line}/!b subject
+	# Check the body of the message for problematic
+	# prefixes.
+	: body
+	n
+	/${scissors_line}/q
+	/${comment_line}/b body
+	/^diff -/{p;q;}
+	/^Index: /{p;q;}
+	b body
+	" "$1")"
+if test -n "$line"
+then
+	echo >&2 "Message contains a diff that will confuse 'git am'."
+	echo >&2 "To fix this indent the diff."
+	ret=1
+fi
+
+exit $ret
diff --git a/templates/hooks/fsmonitor-watchman.sample b/templates/hooks/fsmonitor-watchman.sample
index 23e856f..429e0a5 100755
--- a/templates/hooks/fsmonitor-watchman.sample
+++ b/templates/hooks/fsmonitor-watchman.sample
@@ -29,8 +29,6 @@
 
 my $git_work_tree = get_working_dir();
 
-my $retry = 1;
-
 my $json_pkg;
 eval {
 	require JSON::XS;
@@ -123,8 +121,7 @@
 sub is_work_tree_watched {
 	my ($output) = @_;
 	my $error = $output->{error};
-	if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
-		$retry--;
+	if ($error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
 		my $response = qx/watchman watch "$git_work_tree"/;
 		die "Failed to make watchman watch '$git_work_tree'.\n" .
 		    "Falling back to scanning...\n" if $? != 0;
@@ -142,15 +139,12 @@
 		# Watchman query just to get it over with now so we won't pay
 		# the cost in git to look up each individual file.
 		my $o = watchman_clock();
-		$error = $output->{error};
+		$error = $o->{error};
 
 		die "Watchman: $error.\n" .
 		"Falling back to scanning...\n" if $error;
 
 		output_result($o->{clock}, ("/"));
-		$last_update_token = $o->{clock};
-
-		eval { launch_watchman() };
 		return 0;
 	}
 
diff --git a/tmp-objdir.c b/tmp-objdir.c
index 9f5a178..e436eed 100644
--- a/tmp-objdir.c
+++ b/tmp-objdir.c
@@ -36,6 +36,21 @@ static void tmp_objdir_free(struct tmp_objdir *t)
 	free(t);
 }
 
+static void tmp_objdir_reparent(const char *name UNUSED,
+				const char *old_cwd,
+				const char *new_cwd,
+				void *cb_data)
+{
+	struct tmp_objdir *t = cb_data;
+	char *path;
+
+	path = reparent_relative_path(old_cwd, new_cwd,
+				      t->path.buf);
+	strbuf_reset(&t->path);
+	strbuf_addstr(&t->path, path);
+	free(path);
+}
+
 int tmp_objdir_destroy(struct tmp_objdir *t)
 {
 	int err;
@@ -51,6 +66,7 @@ int tmp_objdir_destroy(struct tmp_objdir *t)
 
 	err = remove_dir_recursively(&t->path, 0);
 
+	chdir_notify_unregister(NULL, tmp_objdir_reparent, t);
 	tmp_objdir_free(t);
 
 	return err;
@@ -137,6 +153,9 @@ struct tmp_objdir *tmp_objdir_create(struct repository *r,
 	strbuf_addf(&t->path, "%s/tmp_objdir-%s-XXXXXX",
 		    repo_get_object_directory(r), prefix);
 
+	if (!is_absolute_path(t->path.buf))
+		chdir_notify_register(NULL, tmp_objdir_reparent, t);
+
 	if (!mkdtemp(t->path.buf)) {
 		/* free, not destroy, as we never touched the filesystem */
 		tmp_objdir_free(t);
@@ -315,26 +334,3 @@ void tmp_objdir_replace_primary_odb(struct tmp_objdir *t, int will_destroy)
 							  t->path.buf, will_destroy);
 	t->will_destroy = will_destroy;
 }
-
-struct tmp_objdir *tmp_objdir_unapply_primary_odb(void)
-{
-	if (!the_tmp_objdir || !the_tmp_objdir->prev_source)
-		return NULL;
-
-	odb_restore_primary_source(the_tmp_objdir->repo->objects,
-				   the_tmp_objdir->prev_source, the_tmp_objdir->path.buf);
-	the_tmp_objdir->prev_source = NULL;
-	return the_tmp_objdir;
-}
-
-void tmp_objdir_reapply_primary_odb(struct tmp_objdir *t, const char *old_cwd,
-		const char *new_cwd)
-{
-	char *path;
-
-	path = reparent_relative_path(old_cwd, new_cwd, t->path.buf);
-	strbuf_reset(&t->path);
-	strbuf_addstr(&t->path, path);
-	free(path);
-	tmp_objdir_replace_primary_odb(t, t->will_destroy);
-}
diff --git a/tmp-objdir.h b/tmp-objdir.h
index fceda14..ccf800f 100644
--- a/tmp-objdir.h
+++ b/tmp-objdir.h
@@ -68,19 +68,4 @@ void tmp_objdir_add_as_alternate(const struct tmp_objdir *);
  */
 void tmp_objdir_replace_primary_odb(struct tmp_objdir *, int will_destroy);
 
-/*
- * If the primary object database was replaced by a temporary object directory,
- * restore it to its original value while keeping the directory contents around.
- * Returns NULL if the primary object database was not replaced.
- */
-struct tmp_objdir *tmp_objdir_unapply_primary_odb(void);
-
-/*
- * Reapplies the former primary temporary object database, after potentially
- * changing its relative path.
- */
-void tmp_objdir_reapply_primary_odb(struct tmp_objdir *, const char *old_cwd,
-		const char *new_cwd);
-
-
 #endif /* TMP_OBJDIR_H */
diff --git a/transport.c b/transport.c
index c7f06a7..107f4fa 100644
--- a/transport.c
+++ b/transport.c
@@ -1219,6 +1219,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
 		 */
 		struct git_transport_data *data = xcalloc(1, sizeof(*data));
 		list_objects_filter_init(&data->options.filter_options);
+		data->options.filter_options.allow_auto_filter = 1;
 		ret->data = data;
 		ret->vtable = &builtin_smart_vtable;
 		ret->smart_options = &(data->options);
@@ -1316,65 +1317,85 @@ static void die_with_unpushed_submodules(struct string_list *needs_pushing)
 	die(_("Aborting."));
 }
 
+struct feed_pre_push_hook_data {
+	struct strbuf buf;
+	const struct ref *refs;
+};
+
+static int pre_push_hook_feed_stdin(int hook_stdin_fd, void *pp_cb UNUSED, void *pp_task_cb)
+{
+	struct feed_pre_push_hook_data *data = pp_task_cb;
+	const struct ref *r = data->refs;
+	int ret = 0;
+
+	if (!r)
+		return 1; /* no more refs */
+
+	data->refs = r->next;
+
+	switch (r->status) {
+	case REF_STATUS_REJECT_NONFASTFORWARD:
+	case REF_STATUS_REJECT_REMOTE_UPDATED:
+	case REF_STATUS_REJECT_STALE:
+	case REF_STATUS_UPTODATE:
+		return 0; /* skip refs which won't be pushed */
+	default:
+		break;
+	}
+
+	if (!r->peer_ref)
+		return 0;
+
+	strbuf_reset(&data->buf);
+	strbuf_addf(&data->buf, "%s %s %s %s\n",
+		    r->peer_ref->name, oid_to_hex(&r->new_oid),
+		    r->name, oid_to_hex(&r->old_oid));
+
+	ret = write_in_full(hook_stdin_fd, data->buf.buf, data->buf.len);
+	if (ret < 0 && errno != EPIPE)
+		return ret; /* We do not mind if a hook does not read all refs. */
+
+	return 0;
+}
+
+static void *pre_push_hook_data_alloc(void *feed_pipe_ctx)
+{
+	struct feed_pre_push_hook_data *data = xmalloc(sizeof(*data));
+	strbuf_init(&data->buf, 0);
+	data->refs = (struct ref *)feed_pipe_ctx;
+	return data;
+}
+
+static void pre_push_hook_data_free(void *data)
+{
+	struct feed_pre_push_hook_data *d = data;
+	if (!d)
+		return;
+	strbuf_release(&d->buf);
+	free(d);
+}
+
 static int run_pre_push_hook(struct transport *transport,
 			     struct ref *remote_refs)
 {
-	int ret = 0, x;
-	struct ref *r;
-	struct child_process proc = CHILD_PROCESS_INIT;
-	struct strbuf buf;
-	const char *hook_path = find_hook(the_repository, "pre-push");
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+	int ret = 0;
 
-	if (!hook_path)
-		return 0;
+	strvec_push(&opt.args, transport->remote->name);
+	strvec_push(&opt.args, transport->url);
 
-	strvec_push(&proc.args, hook_path);
-	strvec_push(&proc.args, transport->remote->name);
-	strvec_push(&proc.args, transport->url);
+	opt.feed_pipe = pre_push_hook_feed_stdin;
+	opt.feed_pipe_ctx = remote_refs;
+	opt.feed_pipe_cb_data_alloc = pre_push_hook_data_alloc;
+	opt.feed_pipe_cb_data_free = pre_push_hook_data_free;
 
-	proc.in = -1;
-	proc.trace2_hook_name = "pre-push";
+	/*
+	 * pre-push hooks expect stdout & stderr to be separate, so don't merge
+	 * them to keep backwards compatibility with existing hooks.
+	 */
+	opt.stdout_to_stderr = 0;
 
-	if (start_command(&proc)) {
-		finish_command(&proc);
-		return -1;
-	}
-
-	sigchain_push(SIGPIPE, SIG_IGN);
-
-	strbuf_init(&buf, 256);
-
-	for (r = remote_refs; r; r = r->next) {
-		if (!r->peer_ref) continue;
-		if (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue;
-		if (r->status == REF_STATUS_REJECT_STALE) continue;
-		if (r->status == REF_STATUS_REJECT_REMOTE_UPDATED) continue;
-		if (r->status == REF_STATUS_UPTODATE) continue;
-
-		strbuf_reset(&buf);
-		strbuf_addf( &buf, "%s %s %s %s\n",
-			 r->peer_ref->name, oid_to_hex(&r->new_oid),
-			 r->name, oid_to_hex(&r->old_oid));
-
-		if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
-			/* We do not mind if a hook does not read all refs. */
-			if (errno != EPIPE)
-				ret = -1;
-			break;
-		}
-	}
-
-	strbuf_release(&buf);
-
-	x = close(proc.in);
-	if (!ret)
-		ret = x;
-
-	sigchain_pop(SIGPIPE);
-
-	x = finish_command(&proc);
-	if (!ret)
-		ret = x;
+	ret = run_hooks_opt(the_repository, "pre-push", &opt);
 
 	return ret;
 }
@@ -1657,7 +1678,7 @@ int transport_disconnect(struct transport *transport)
  */
 char *transport_anonymize_url(const char *url)
 {
-	char *scheme_prefix, *anon_part;
+	const char *scheme_prefix, *anon_part;
 	size_t anon_len, prefix_len = 0;
 
 	anon_part = strchr(url, '@');
diff --git a/tree-diff.c b/tree-diff.c
index 5988148..2f5c956 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -2,7 +2,6 @@
  * Helper functions for tree diff generation
  */
 
-#define USE_THE_REPOSITORY_VARIABLE
 #define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "git-compat-util.h"
@@ -11,7 +10,6 @@
 #include "hash.h"
 #include "tree.h"
 #include "tree-walk.h"
-#include "environment.h"
 #include "repository.h"
 #include "dir.h"
 
@@ -253,7 +251,7 @@ static void emit_path(struct combine_diff_path ***tail,
 
 		strbuf_add(base, path, pathlen);
 		p = combine_diff_path_new(base->buf, base->len, mode,
-					  oid ? oid : null_oid(the_hash_algo),
+					  oid ? oid : null_oid(opt->repo->hash_algo),
 					  nparent);
 		strbuf_setlen(base, old_baselen);
 
@@ -278,7 +276,7 @@ static void emit_path(struct combine_diff_path ***tail,
 				mode_i = tp[i].entry.mode;
 			}
 			else {
-				oid_i = null_oid(the_hash_algo);
+				oid_i = null_oid(opt->repo->hash_algo);
 				mode_i = 0;
 			}
 
@@ -439,7 +437,7 @@ static void ll_diff_tree_paths(
 	void *ttree, **tptree;
 	int i;
 
-	if (depth > max_allowed_tree_depth)
+	if (depth > opt->repo->settings.max_allowed_tree_depth)
 		die("exceeded maximum allowed tree depth");
 
 	FAST_ARRAY_ALLOC(tp, nparent);
diff --git a/tree-walk.c b/tree-walk.c
index e449a13..7e1b956 100644
--- a/tree-walk.c
+++ b/tree-walk.c
@@ -12,6 +12,7 @@
 #include "pathspec.h"
 #include "json-writer.h"
 #include "environment.h"
+#include "read-cache-ll.h"
 
 static int decode_tree_entry(struct tree_desc *desc, const char *buf, unsigned long size, struct strbuf *err)
 {
@@ -441,8 +442,9 @@ int traverse_trees(struct index_state *istate,
 	struct strbuf base = STRBUF_INIT;
 	int interesting = 1;
 	char *traverse_path;
+	struct repository *r = istate ? istate->repo : the_repository;
 
-	if (traverse_trees_cur_depth > max_allowed_tree_depth)
+	if (traverse_trees_cur_depth > r->settings.max_allowed_tree_depth)
 		return error("exceeded maximum allowed tree depth");
 
 	traverse_trees_count++;
diff --git a/tree.c b/tree.c
index 1ef743d..d703ab9 100644
--- a/tree.c
+++ b/tree.c
@@ -1,5 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
-
 #include "git-compat-util.h"
 #include "hex.h"
 #include "tree.h"
@@ -25,10 +23,10 @@ int read_tree_at(struct repository *r,
 	int len, oldlen = base->len;
 	enum interesting retval = entry_not_interesting;
 
-	if (depth > max_allowed_tree_depth)
+	if (depth > r->settings.max_allowed_tree_depth)
 		return error("exceeded maximum allowed tree depth");
 
-	if (parse_tree(tree))
+	if (repo_parse_tree(r, tree))
 		return -1;
 
 	init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size);
@@ -185,7 +183,8 @@ int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size)
 	return 0;
 }
 
-int parse_tree_gently(struct tree *item, int quiet_on_missing)
+int repo_parse_tree_gently(struct repository *r, struct tree *item,
+			   int quiet_on_missing)
 {
 	 enum object_type type;
 	 void *buffer;
@@ -193,8 +192,7 @@ int parse_tree_gently(struct tree *item, int quiet_on_missing)
 
 	if (item->object.parsed)
 		return 0;
-	buffer = odb_read_object(the_repository->objects, &item->object.oid,
-				 &type, &size);
+	buffer = odb_read_object(r->objects, &item->object.oid, &type, &size);
 	if (!buffer)
 		return quiet_on_missing ? -1 :
 			error("Could not read %s",
@@ -214,9 +212,9 @@ void free_tree_buffer(struct tree *tree)
 	tree->object.parsed = 0;
 }
 
-struct tree *parse_tree_indirect(const struct object_id *oid)
+struct tree *repo_parse_tree_indirect(struct repository *r,
+				      const struct object_id *oid)
 {
-	struct repository *r = the_repository;
 	struct object *obj = parse_object(r, oid);
 	return (struct tree *)repo_peel_to_type(r, NULL, 0, obj, OBJ_TREE);
 }
diff --git a/tree.h b/tree.h
index cc6ddf5..677382e 100644
--- a/tree.h
+++ b/tree.h
@@ -19,15 +19,20 @@ struct tree *lookup_tree(struct repository *r, const struct object_id *oid);
 
 int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size);
 
-int parse_tree_gently(struct tree *tree, int quiet_on_missing);
-static inline int parse_tree(struct tree *tree)
+#define parse_tree_gently(t, q) repo_parse_tree_gently(the_repository, t, q)
+int repo_parse_tree_gently(struct repository *r, struct tree *item,
+			   int quiet_on_missing);
+#define parse_tree(t) repo_parse_tree(the_repository, t)
+static inline int repo_parse_tree(struct repository *r, struct tree *item)
 {
-	return parse_tree_gently(tree, 0);
+	return repo_parse_tree_gently(r, item, 0);
 }
 void free_tree_buffer(struct tree *tree);
 
 /* Parses and returns the tree in the given ent, chasing tags and commits. */
-struct tree *parse_tree_indirect(const struct object_id *oid);
+#define parse_tree_indirect(o) repo_parse_tree_indirect(the_repository, o)
+struct tree *repo_parse_tree_indirect(struct repository *r,
+				      const struct object_id *oid);
 
 /*
  * Functions for comparing pathnames
diff --git a/unpack-trees.c b/unpack-trees.c
index f38c761..998a1e6 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -1888,6 +1888,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
 	struct pattern_list pl;
 	int free_pattern_list = 0;
 	struct dir_struct dir = DIR_INIT;
+	struct repo_config_values *cfg = repo_config_values(the_repository);
 
 	if (o->reset == UNPACK_RESET_INVALID)
 		BUG("o->reset had a value of 1; should be UNPACK_TREES_*_UNTRACKED");
@@ -1924,7 +1925,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
 	if (o->prefix)
 		update_sparsity_for_prefix(o->prefix, o->src_index);
 
-	if (!core_apply_sparse_checkout || !o->update)
+	if (!cfg->apply_sparse_checkout || !o->update)
 		o->skip_sparse_checkout = 1;
 	if (!o->skip_sparse_checkout) {
 		memset(&pl, 0, sizeof(pl));
diff --git a/upload-pack.c b/upload-pack.c
index 1e87ae9..e8c5cce 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -607,10 +607,13 @@ static int allow_hidden_refs(enum allow_uor allow_uor)
 	return !(allow_uor & (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1));
 }
 
-static void for_each_namespaced_ref_1(each_ref_fn fn,
+static void for_each_namespaced_ref_1(refs_for_each_cb fn,
 				      struct upload_pack_data *data)
 {
-	const char **excludes = NULL;
+	struct refs_for_each_ref_options opts = {
+		.namespace = get_git_namespace(),
+	};
+
 	/*
 	 * If `data->allow_uor` allows fetching hidden refs, we need to
 	 * mark all references (including hidden ones), to check in
@@ -621,10 +624,10 @@ static void for_each_namespaced_ref_1(each_ref_fn fn,
 	 * hidden references.
 	 */
 	if (allow_hidden_refs(data->allow_uor))
-		excludes = hidden_refs_to_excludes(&data->hidden_refs);
+		opts.exclude_patterns = hidden_refs_to_excludes(&data->hidden_refs);
 
-	refs_for_each_namespaced_ref(get_main_ref_store(the_repository),
-				     excludes, fn, data);
+	refs_for_each_ref_ext(get_main_ref_store(the_repository),
+			      fn, data, &opts);
 }
 
 
@@ -704,56 +707,6 @@ static int do_reachable_revlist(struct child_process *cmd,
 	return -1;
 }
 
-static int get_reachable_list(struct upload_pack_data *data,
-			      struct object_array *reachable)
-{
-	struct child_process cmd = CHILD_PROCESS_INIT;
-	int i;
-	struct object *o;
-	char namebuf[GIT_MAX_HEXSZ + 2]; /* ^ + hash + LF */
-	const unsigned hexsz = the_hash_algo->hexsz;
-	int ret;
-
-	if (do_reachable_revlist(&cmd, &data->shallows, reachable,
-				 data->allow_uor) < 0) {
-		ret = -1;
-		goto out;
-	}
-
-	while ((i = read_in_full(cmd.out, namebuf, hexsz + 1)) == hexsz + 1) {
-		struct object_id oid;
-		const char *p;
-
-		if (parse_oid_hex(namebuf, &oid, &p) || *p != '\n')
-			break;
-
-		o = lookup_object(the_repository, &oid);
-		if (o && o->type == OBJ_COMMIT) {
-			o->flags &= ~TMP_MARK;
-		}
-	}
-	for (i = get_max_object_index(the_repository); 0 < i; i--) {
-		o = get_indexed_object(the_repository, i - 1);
-		if (o && o->type == OBJ_COMMIT &&
-		    (o->flags & TMP_MARK)) {
-			add_object_array(o, NULL, reachable);
-				o->flags &= ~TMP_MARK;
-		}
-	}
-	close(cmd.out);
-
-	if (finish_command(&cmd)) {
-		ret = -1;
-		goto out;
-	}
-
-	ret = 0;
-
-out:
-	child_process_clear(&cmd);
-	return ret;
-}
-
 static int has_unreachable(struct object_array *src, enum allow_uor allow_uor)
 {
 	struct child_process cmd = CHILD_PROCESS_INIT;
@@ -870,8 +823,8 @@ static void send_unshallow(struct upload_pack_data *data)
 	}
 }
 
-static int check_ref(const char *refname_full, const char *referent UNUSED, const struct object_id *oid,
-		     int flag, void *cb_data);
+static int check_ref(const struct reference *ref, void *cb_data);
+
 static void deepen(struct upload_pack_data *data, int depth)
 {
 	if (depth == INFINITE_DEPTH && !is_repository_shallow(the_repository)) {
@@ -881,29 +834,11 @@ static void deepen(struct upload_pack_data *data, int depth)
 			struct object *object = data->shallows.objects[i].item;
 			object->flags |= NOT_SHALLOW;
 		}
-	} else if (data->deepen_relative) {
-		struct object_array reachable_shallows = OBJECT_ARRAY_INIT;
-		struct commit_list *result;
-
-		/*
-		 * Checking for reachable shallows requires that our refs be
-		 * marked with OUR_REF.
-		 */
-		refs_head_ref_namespaced(get_main_ref_store(the_repository),
-					 check_ref, data);
-		for_each_namespaced_ref_1(check_ref, data);
-
-		get_reachable_list(data, &reachable_shallows);
-		result = get_shallow_commits(&reachable_shallows,
-					     depth + 1,
-					     SHALLOW, NOT_SHALLOW);
-		send_shallow(data, result);
-		free_commit_list(result);
-		object_array_clear(&reachable_shallows);
 	} else {
 		struct commit_list *result;
 
-		result = get_shallow_commits(&data->want_obj, depth,
+		result = get_shallow_commits(&data->want_obj, &data->shallows,
+					     data->deepen_relative, depth,
 					     SHALLOW, NOT_SHALLOW);
 		send_shallow(data, result);
 		free_commit_list(result);
@@ -1224,13 +1159,12 @@ static int mark_our_ref(const char *refname, const char *refname_full,
 	return 0;
 }
 
-static int check_ref(const char *refname_full, const char *referent UNUSED,const struct object_id *oid,
-		     int flag UNUSED, void *cb_data)
+static int check_ref(const struct reference *ref, void *cb_data)
 {
-	const char *refname = strip_namespace(refname_full);
+	const char *refname = strip_namespace(ref->name);
 	struct upload_pack_data *data = cb_data;
 
-	mark_our_ref(refname, refname_full, oid, &data->hidden_refs);
+	mark_our_ref(refname, ref->name, ref->oid, &data->hidden_refs);
 	return 0;
 }
 
@@ -1250,15 +1184,15 @@ static void format_session_id(struct strbuf *buf, struct upload_pack_data *d) {
 }
 
 static void write_v0_ref(struct upload_pack_data *data,
-			const char *refname, const char *refname_nons,
-			const struct object_id *oid)
+			 const struct reference *ref,
+			 const char *refname_nons)
 {
 	static const char *capabilities = "multi_ack thin-pack side-band"
 		" side-band-64k ofs-delta shallow deepen-since deepen-not"
 		" deepen-relative no-progress include-tag multi_ack_detailed";
 	struct object_id peeled;
 
-	if (mark_our_ref(refname_nons, refname, oid, &data->hidden_refs))
+	if (mark_our_ref(refname_nons, ref->name, ref->oid, &data->hidden_refs))
 		return;
 
 	if (capabilities) {
@@ -1268,7 +1202,7 @@ static void write_v0_ref(struct upload_pack_data *data,
 		format_symref_info(&symref_info, &data->symref);
 		format_session_id(&session_id, data);
 		packet_fwrite_fmt(stdout, "%s %s%c%s%s%s%s%s%s%s object-format=%s agent=%s\n",
-			     oid_to_hex(oid), refname_nons,
+			     oid_to_hex(ref->oid), refname_nons,
 			     0, capabilities,
 			     (data->allow_uor & ALLOW_TIP_SHA1) ?
 				     " allow-tip-sha1-in-want" : "",
@@ -1284,35 +1218,33 @@ static void write_v0_ref(struct upload_pack_data *data,
 		strbuf_release(&session_id);
 		data->sent_capabilities = 1;
 	} else {
-		packet_fwrite_fmt(stdout, "%s %s\n", oid_to_hex(oid), refname_nons);
+		packet_fwrite_fmt(stdout, "%s %s\n", oid_to_hex(ref->oid), refname_nons);
 	}
 	capabilities = NULL;
-	if (!peel_iterated_oid(the_repository, oid, &peeled))
+	if (!reference_get_peeled_oid(the_repository, ref, &peeled))
 		packet_fwrite_fmt(stdout, "%s %s^{}\n", oid_to_hex(&peeled), refname_nons);
 	return;
 }
 
-static int send_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
-		    int flag UNUSED, void *cb_data)
+static int send_ref(const struct reference *ref, void *cb_data)
 {
-	write_v0_ref(cb_data, refname, strip_namespace(refname), oid);
+	write_v0_ref(cb_data, ref, strip_namespace(ref->name));
 	return 0;
 }
 
-static int find_symref(const char *refname, const char *referent UNUSED,
-		       const struct object_id *oid UNUSED,
-		       int flag, void *cb_data)
+static int find_symref(const struct reference *ref, void *cb_data)
 {
 	const char *symref_target;
 	struct string_list_item *item;
+	int flag;
 
-	if ((flag & REF_ISSYMREF) == 0)
+	if ((ref->flags & REF_ISSYMREF) == 0)
 		return 0;
 	symref_target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
-						refname, 0, NULL, &flag);
+						ref->name, 0, NULL, &flag);
 	if (!symref_target || (flag & REF_ISSYMREF) == 0)
-		die("'%s' is a symref but it is not?", refname);
-	item = string_list_append(cb_data, strip_namespace(refname));
+		die("'%s' is a symref but it is not?", ref->name);
+	item = string_list_append(cb_data, strip_namespace(ref->name));
 	item->util = xstrdup(strip_namespace(symref_target));
 	return 0;
 }
@@ -1445,8 +1377,12 @@ void upload_pack(const int advertise_refs, const int stateless_rpc,
 					 send_ref, &data);
 		for_each_namespaced_ref_1(send_ref, &data);
 		if (!data.sent_capabilities) {
-			const char *refname = "capabilities^{}";
-			write_v0_ref(&data, refname, refname, null_oid(the_hash_algo));
+			struct reference ref = {
+				.name = "capabilities^{}",
+				.oid = null_oid(the_hash_algo),
+			};
+
+			write_v0_ref(&data, &ref, ref.name);
 		}
 		/*
 		 * fflush stdout before calling advertise_shallow_grafts because send_ref
diff --git a/url.c b/url.c
index 282b124..3ca5987 100644
--- a/url.c
+++ b/url.c
@@ -3,6 +3,19 @@
 #include "strbuf.h"
 #include "url.h"
 
+int is_rfc3986_unreserved(char ch)
+{
+	return isalnum(ch) ||
+		ch == '-' || ch == '_' || ch == '.' || ch == '~';
+}
+
+int is_casefolding_rfc3986_unreserved(char c)
+{
+	return (c >= 'a' && c <= 'z') ||
+	       (c >= '0' && c <= '9') ||
+	       c == '-' || c == '.' || c == '_' || c == '~';
+}
+
 int is_urlschemechar(int first_flag, int ch)
 {
 	/*
diff --git a/url.h b/url.h
index 2a27c34..cd9140e 100644
--- a/url.h
+++ b/url.h
@@ -21,4 +21,18 @@ char *url_decode_parameter_value(const char **query);
 void end_url_with_slash(struct strbuf *buf, const char *url);
 void str_end_url_with_slash(const char *url, char **dest);
 
+/*
+ * The set of unreserved characters as per STD66 (RFC3986) is
+ * '[A-Za-z0-9-._~]'. These characters are safe to appear in URI
+ * components without percent-encoding.
+ */
+int is_rfc3986_unreserved(char ch);
+
+/*
+ * This is a variant of is_rfc3986_unreserved() that treats uppercase
+ * letters as "reserved". This forces them to be percent-encoded, allowing
+ * 'Foo' (%46oo) and 'foo' (foo) to be distinct on case-folding filesystems.
+ */
+int is_casefolding_rfc3986_unreserved(char c);
+
 #endif /* URL_H */
diff --git a/utf8.c b/utf8.c
index 35a0251..96460cc 100644
--- a/utf8.c
+++ b/utf8.c
@@ -515,6 +515,19 @@ char *reencode_string_iconv(const char *in, size_t insz, iconv_t conv,
 			out = xrealloc(out, outalloc);
 			outpos = out + sofar;
 			outsz = outalloc - sofar - 1;
+#ifdef ICONV_RESTART_RESET
+			/*
+			 * If iconv(3) messes up piecemeal conversions
+			 * then restore the original pointers, sizes,
+			 * and converter state, then retry converting
+			 * the full string using the reallocated buffer.
+			 */
+			insz += cp - (iconv_ibp)in; /* Restore insz */
+			cp = (iconv_ibp)in;         /* original start value */
+			outpos = out + bom_len;     /* original start value */
+			outsz = outalloc - bom_len - 1; /* new len */
+			iconv(conv, NULL, NULL, NULL, NULL); /* reset iconv machinery */
+#endif
 		}
 		else {
 			*outpos = '\0';
diff --git a/version.c b/version.c
index 279269c..21ec7c0 100644
--- a/version.c
+++ b/version.c
@@ -1,5 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
-
 #include "git-compat-util.h"
 #include "version.h"
 #include "strbuf.h"
diff --git a/version.h b/version.h
index bbde6d3..7d502dc 100644
--- a/version.h
+++ b/version.h
@@ -1,8 +1,6 @@
 #ifndef VERSION_H
 #define VERSION_H
 
-struct repository;
-
 extern const char git_version_string[];
 extern const char git_built_from_commit_string[];
 
diff --git a/walker.c b/walker.c
index 8073754..9133253 100644
--- a/walker.c
+++ b/walker.c
@@ -45,7 +45,7 @@ static int process_tree(struct walker *walker, struct tree *tree)
 	struct tree_desc desc;
 	struct name_entry entry;
 
-	if (parse_tree(tree))
+	if (repo_parse_tree(the_repository, tree))
 		return -1;
 
 	init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size);
@@ -115,7 +115,7 @@ static int process_commit(struct walker *walker, struct commit *commit)
 
 static int process_tag(struct walker *walker, struct tag *tag)
 {
-	if (parse_tag(tag))
+	if (parse_tag(the_repository, tag))
 		return -1;
 	return process(walker, tag->tagged);
 }
@@ -226,14 +226,10 @@ static int interpret_target(struct walker *walker, char *target, struct object_i
 	return -1;
 }
 
-static int mark_complete(const char *path UNUSED,
-			const char *referent UNUSED,
-			 const struct object_id *oid,
-			 int flag UNUSED,
-			 void *cb_data UNUSED)
+static int mark_complete(const struct reference *ref, void *cb_data UNUSED)
 {
 	struct commit *commit = lookup_commit_reference_gently(the_repository,
-							       oid, 1);
+							       ref->oid, 1);
 
 	if (commit) {
 		commit->object.flags |= COMPLETE;
diff --git a/worktree.c b/worktree.c
index a2a5f51..6552e30 100644
--- a/worktree.c
+++ b/worktree.c
@@ -66,6 +66,26 @@ static int is_current_worktree(struct worktree *wt)
 	return is_current;
 }
 
+struct worktree *get_worktree_from_repository(struct repository *repo)
+{
+	struct worktree *wt = xcalloc(1, sizeof(*wt));
+	char *gitdir = absolute_pathdup(repo->gitdir);
+	char *commondir = absolute_pathdup(repo->commondir);
+
+	wt->repo = repo;
+	wt->path = absolute_pathdup(repo->worktree ? repo->worktree
+						   : repo->gitdir);
+	wt->is_bare = !repo->worktree;
+	if (fspathcmp(gitdir, commondir))
+		wt->id = xstrdup(find_last_dir_sep(gitdir) + 1);
+	wt->is_current = is_current_worktree(wt);
+	add_head_info(wt);
+
+	free(gitdir);
+	free(commondir);
+	return wt;
+}
+
 /*
 * When in a secondary worktree, and when extensions.worktreeConfig
 * is true, only $commondir/config and $commondir/worktrees/<id>/
@@ -288,7 +308,7 @@ const char *worktree_lock_reason(struct worktree *wt)
 	if (!wt->lock_reason_valid) {
 		struct strbuf path = STRBUF_INIT;
 
-		strbuf_addstr(&path, worktree_git_path(the_repository, wt, "locked"));
+		strbuf_addstr(&path, worktree_git_path(wt, "locked"));
 		if (file_exists(path.buf)) {
 			struct strbuf lock_reason = STRBUF_INIT;
 			if (strbuf_read_file(&lock_reason, path.buf, 0) < 0)
@@ -575,7 +595,7 @@ void strbuf_worktree_ref(const struct worktree *wt,
 	strbuf_addstr(sb, refname);
 }
 
-int other_head_refs(each_ref_fn fn, void *cb_data)
+int other_head_refs(refs_for_each_cb fn, void *cb_data)
 {
 	struct worktree **worktrees, **p;
 	struct strbuf refname = STRBUF_INIT;
@@ -595,8 +615,15 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
 		if (refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
 					    refname.buf,
 					    RESOLVE_REF_READING,
-					    &oid, &flag))
-			ret = fn(refname.buf, NULL, &oid, flag, cb_data);
+					    &oid, &flag)) {
+			struct reference ref = {
+				.name = refname.buf,
+				.oid = &oid,
+				.flags = flag,
+			};
+
+			ret = fn(&ref, cb_data);
+		}
 		if (ret)
 			break;
 	}
@@ -646,7 +673,8 @@ static void repair_gitfile(struct worktree *wt,
 		}
 	}
 
-	if (err == READ_GITFILE_ERR_NOT_A_FILE)
+	if (err == READ_GITFILE_ERR_NOT_A_FILE ||
+		err == READ_GITFILE_ERR_IS_A_DIR)
 		fn(1, wt->path, _(".git is not a file"), cb_data);
 	else if (err)
 		repair = _(".git file broken");
@@ -826,7 +854,8 @@ void repair_worktree_at_path(const char *path,
 			strbuf_addstr(&backlink, dotgit_contents);
 			strbuf_realpath_forgiving(&backlink, backlink.buf, 0);
 		}
-	} else if (err == READ_GITFILE_ERR_NOT_A_FILE) {
+	} else if (err == READ_GITFILE_ERR_NOT_A_FILE ||
+			err == READ_GITFILE_ERR_IS_A_DIR) {
 		fn(1, dotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
 		goto done;
 	} else if (err == READ_GITFILE_ERR_NOT_A_REPO) {
diff --git a/worktree.h b/worktree.h
index e4bcccd..e450d1a 100644
--- a/worktree.h
+++ b/worktree.h
@@ -39,6 +39,12 @@ struct worktree **get_worktrees(void);
 struct worktree **get_worktrees_without_reading_head(void);
 
 /*
+ * Construct a struct worktree corresponding to repo->gitdir and
+ * repo->worktree.
+ */
+struct worktree *get_worktree_from_repository(struct repository *repo);
+
+/*
  * Returns 1 if linked worktrees exist, 0 otherwise.
  */
 int submodule_uses_worktrees(const char *path);
@@ -191,7 +197,7 @@ int is_shared_symref(const struct worktree *wt,
  * Similar to head_ref() for all HEADs _except_ one from the current
  * worktree, which is covered by head_ref().
  */
-int other_head_refs(each_ref_fn fn, void *cb_data);
+int other_head_refs(refs_for_each_cb fn, void *cb_data);
 
 int is_worktree_being_rebased(const struct worktree *wt, const char *target);
 int is_worktree_being_bisected(const struct worktree *wt, const char *target);
diff --git a/wrapper.c b/wrapper.c
index 3d507d4..16f5a63 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -115,7 +115,7 @@ void *xmemdupz(const void *data, size_t len)
 
 char *xstrndup(const char *str, size_t len)
 {
-	char *p = memchr(str, '\0', len);
+	const char *p = memchr(str, '\0', len);
 	return xmemdupz(str, p ? p - str : len);
 }
 
@@ -421,24 +421,7 @@ FILE *fopen_or_warn(const char *path, const char *mode)
 
 int xmkstemp(char *filename_template)
 {
-	int fd;
-	char origtemplate[PATH_MAX];
-	strlcpy(origtemplate, filename_template, sizeof(origtemplate));
-
-	fd = mkstemp(filename_template);
-	if (fd < 0) {
-		int saved_errno = errno;
-		const char *nonrelative_template;
-
-		if (strlen(filename_template) != strlen(origtemplate))
-			filename_template = origtemplate;
-
-		nonrelative_template = absolute_path(filename_template);
-		errno = saved_errno;
-		die_errno("Unable to create temporary file '%s'",
-			nonrelative_template);
-	}
-	return fd;
+	return xmkstemp_mode(filename_template, 0600);
 }
 
 /* Adapted from libiberty's mkstemp.c. */
@@ -446,7 +429,11 @@ int xmkstemp(char *filename_template)
 #undef TMP_MAX
 #define TMP_MAX 16384
 
-int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
+/*
+ * Returns -1 on error, 0 if it created a directory, or an open file
+ * descriptor to the created regular file.
+ */
+static int git_mkdstemps_mode(char *pattern, int suffix_len, int mode, bool dir)
 {
 	static const char letters[] =
 		"abcdefghijklmnopqrstuvwxyz"
@@ -488,7 +475,10 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
 			v /= num_letters;
 		}
 
-		fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, mode);
+		if (dir)
+			fd = mkdir(pattern, mode);
+		else
+			fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, mode);
 		if (fd >= 0)
 			return fd;
 		/*
@@ -503,6 +493,16 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
 	return -1;
 }
 
+char *git_mkdtemp(char *pattern)
+{
+	return git_mkdstemps_mode(pattern, 0, 0700, true) ? NULL : pattern;
+}
+
+int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
+{
+	return git_mkdstemps_mode(pattern, suffix_len, mode, false);
+}
+
 int git_mkstemp_mode(char *pattern, int mode)
 {
 	/* mkstemp is just mkstemps with no suffix */
diff --git a/wrapper.h b/wrapper.h
index 44a8597..15ac3ba 100644
--- a/wrapper.h
+++ b/wrapper.h
@@ -37,6 +37,8 @@ int xsnprintf(char *dst, size_t max, const char *fmt, ...);
 
 int xgethostname(char *buf, size_t len);
 
+char *git_mkdtemp(char *pattern);
+
 /* set default permissions by passing mode arguments to open(2) */
 int git_mkstemps_mode(char *pattern, int suffix_len, int mode);
 int git_mkstemp_mode(char *pattern, int mode);
diff --git a/write-or-die.h b/write-or-die.h
index 65a5c42..ff0408b 100644
--- a/write-or-die.h
+++ b/write-or-die.h
@@ -21,6 +21,7 @@ enum fsync_component {
 	FSYNC_COMPONENT_COMMIT_GRAPH		= 1 << 3,
 	FSYNC_COMPONENT_INDEX			= 1 << 4,
 	FSYNC_COMPONENT_REFERENCE		= 1 << 5,
+	FSYNC_COMPONENT_OBJECT_MAP		= 1 << 6,
 };
 
 #define FSYNC_COMPONENTS_OBJECTS (FSYNC_COMPONENT_LOOSE_OBJECT | \
@@ -44,7 +45,8 @@ enum fsync_component {
 			      FSYNC_COMPONENT_PACK_METADATA | \
 			      FSYNC_COMPONENT_COMMIT_GRAPH | \
 			      FSYNC_COMPONENT_INDEX | \
-			      FSYNC_COMPONENT_REFERENCE)
+			      FSYNC_COMPONENT_REFERENCE | \
+			      FSYNC_COMPONENT_OBJECT_MAP)
 
 #ifndef FSYNC_COMPONENTS_PLATFORM_DEFAULT
 #define FSYNC_COMPONENTS_PLATFORM_DEFAULT FSYNC_COMPONENTS_DEFAULT
diff --git a/ws.c b/ws.c
index 70acee3..6cc2466 100644
--- a/ws.c
+++ b/ws.c
@@ -26,6 +26,7 @@ static struct whitespace_rule {
 	{ "blank-at-eol", WS_BLANK_AT_EOL, 0 },
 	{ "blank-at-eof", WS_BLANK_AT_EOF, 0 },
 	{ "tab-in-indent", WS_TAB_IN_INDENT, 0, 1 },
+	{ "incomplete-line", WS_INCOMPLETE_LINE, 0, 0 },
 };
 
 unsigned parse_whitespace_rule(const char *string)
@@ -139,6 +140,11 @@ char *whitespace_error_string(unsigned ws)
 			strbuf_addstr(&err, ", ");
 		strbuf_addstr(&err, "tab in indent");
 	}
+	if (ws & WS_INCOMPLETE_LINE) {
+		if (err.len)
+			strbuf_addstr(&err, ", ");
+		strbuf_addstr(&err, "no newline at the end of file");
+	}
 	return strbuf_detach(&err, NULL);
 }
 
@@ -180,6 +186,9 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
 	if (trailing_whitespace == -1)
 		trailing_whitespace = len;
 
+	if (!trailing_newline && (ws_rule & WS_INCOMPLETE_LINE))
+		result |= WS_INCOMPLETE_LINE;
+
 	/* Check indentation */
 	for (i = 0; i < trailing_whitespace; i++) {
 		if (line[i] == ' ')
@@ -292,6 +301,17 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule,
 	int need_fix_leading_space = 0;
 
 	/*
+	 * Remembering that we need to add '\n' at the end
+	 * is sufficient to fix an incomplete line.
+	 */
+	if (ws_rule & WS_INCOMPLETE_LINE) {
+		if (0 < len && src[len - 1] != '\n') {
+			fixed = 1;
+			add_nl_to_tail = 1;
+		}
+	}
+
+	/*
 	 * Strip trailing whitespace
 	 */
 	if (ws_rule & WS_BLANK_AT_EOL) {
diff --git a/ws.h b/ws.h
index 5ba676c..06d5cb7 100644
--- a/ws.h
+++ b/ws.h
@@ -7,19 +7,23 @@ struct strbuf;
 /*
  * whitespace rules.
  * used by both diff and apply
- * last two digits are tab width
+ * last two octal-digits are tab width (we support only up to 63).
  */
-#define WS_BLANK_AT_EOL         0100
-#define WS_SPACE_BEFORE_TAB     0200
-#define WS_INDENT_WITH_NON_TAB  0400
-#define WS_CR_AT_EOL           01000
-#define WS_BLANK_AT_EOF        02000
-#define WS_TAB_IN_INDENT       04000
-#define WS_TRAILING_SPACE      (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF)
+#define WS_BLANK_AT_EOL         (1<<6)
+#define WS_SPACE_BEFORE_TAB     (1<<7)
+#define WS_INDENT_WITH_NON_TAB  (1<<8)
+#define WS_CR_AT_EOL            (1<<9)
+#define WS_BLANK_AT_EOF         (1<<10)
+#define WS_TAB_IN_INDENT        (1<<11)
+#define WS_INCOMPLETE_LINE      (1<<12)
+
+#define WS_TRAILING_SPACE       (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF)
 #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|8)
-#define WS_TAB_WIDTH_MASK        077
-/* All WS_* -- when extended, adapt diff.c emit_symbol */
-#define WS_RULE_MASK           07777
+#define WS_TAB_WIDTH_MASK       ((1<<6)-1)
+
+/* All WS_* -- when extended, adapt constants defined after diff.c:diff_symbol */
+#define WS_RULE_MASK            ((1<<16)-1)
+
 extern unsigned whitespace_rule_cfg;
 unsigned whitespace_rule(struct index_state *, const char *);
 unsigned parse_whitespace_rule(const char *);
diff --git a/wt-status.c b/wt-status.c
index e12adb2..479ccc3 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -150,11 +150,11 @@ void wt_status_prepare(struct repository *r, struct wt_status *s)
 	s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
 	s->use_color = GIT_COLOR_UNKNOWN;
 	s->relative_paths = 1;
-	s->branch = refs_resolve_refdup(get_main_ref_store(the_repository),
+	s->branch = refs_resolve_refdup(get_main_ref_store(r),
 					"HEAD", 0, NULL, NULL);
 	s->reference = "HEAD";
 	s->fp = stdout;
-	s->index_file = repo_get_index_file(the_repository);
+	s->index_file = repo_get_index_file(r);
 	s->change.strdup_strings = 1;
 	s->untracked.strdup_strings = 1;
 	s->ignored.strdup_strings = 1;
@@ -612,6 +612,30 @@ static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
 	}
 }
 
+void wt_status_collect_changes_trees(struct wt_status *s,
+				     const struct object_id *old_treeish,
+				     const struct object_id *new_treeish)
+{
+	struct diff_options opts = { 0 };
+
+	repo_diff_setup(s->repo, &opts);
+	opts.output_format = DIFF_FORMAT_CALLBACK;
+	opts.format_callback = wt_status_collect_updated_cb;
+	opts.format_callback_data = s;
+	opts.detect_rename = s->detect_rename >= 0 ? s->detect_rename : opts.detect_rename;
+	opts.rename_limit = s->rename_limit >= 0 ? s->rename_limit : opts.rename_limit;
+	opts.rename_score = s->rename_score >= 0 ? s->rename_score : opts.rename_score;
+	opts.flags.recursive = 1;
+	diff_setup_done(&opts);
+
+	diff_tree_oid(old_treeish, new_treeish, "", &opts);
+	diffcore_std(&opts);
+	diff_flush(&opts);
+	wt_status_get_state(s->repo, &s->state, 0);
+
+	diff_free(&opts);
+}
+
 static void wt_status_collect_changes_worktree(struct wt_status *s)
 {
 	struct rev_info rev;
@@ -646,7 +670,7 @@ static void wt_status_collect_changes_index(struct wt_status *s)
 
 	repo_init_revisions(s->repo, &rev, NULL);
 	memset(&opt, 0, sizeof(opt));
-	opt.def = s->is_initial ? empty_tree_oid_hex(the_repository->hash_algo) : s->reference;
+	opt.def = s->is_initial ? empty_tree_oid_hex(s->repo->hash_algo) : s->reference;
 	setup_revisions(0, NULL, &rev, &opt);
 
 	rev.diffopt.flags.override_submodule_config = 1;
@@ -984,17 +1008,17 @@ static int stash_count_refs(const char *refname UNUSED,
 	return 0;
 }
 
-static int count_stash_entries(void)
+static int count_stash_entries(struct repository *r)
 {
 	int n = 0;
-	refs_for_each_reflog_ent(get_main_ref_store(the_repository),
+	refs_for_each_reflog_ent(get_main_ref_store(r),
 				 "refs/stash", stash_count_refs, &n);
 	return n;
 }
 
 static void wt_longstatus_print_stash_summary(struct wt_status *s)
 {
-	int stash_count = count_stash_entries();
+	int stash_count = count_stash_entries(s->repo);
 
 	if (stash_count > 0)
 		status_printf_ln(s, GIT_COLOR_NORMAL,
@@ -1146,7 +1170,7 @@ static void wt_longstatus_print_verbose(struct wt_status *s)
 	rev.diffopt.ita_invisible_in_index = 1;
 
 	memset(&opt, 0, sizeof(opt));
-	opt.def = s->is_initial ? empty_tree_oid_hex(the_repository->hash_algo) : s->reference;
+	opt.def = s->is_initial ? empty_tree_oid_hex(s->repo->hash_algo) : s->reference;
 	setup_revisions(0, NULL, &rev, &opt);
 
 	rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
@@ -1287,10 +1311,10 @@ static void show_am_in_progress(struct wt_status *s,
 	wt_longstatus_print_trailer(s);
 }
 
-static char *read_line_from_git_path(const char *filename)
+static char *read_line_from_git_path(struct repository *r, const char *filename)
 {
 	struct strbuf buf = STRBUF_INIT;
-	FILE *fp = fopen_or_warn(repo_git_path_append(the_repository, &buf,
+	FILE *fp = fopen_or_warn(repo_git_path_append(r, &buf,
 						      "%s", filename), "r");
 
 	if (!fp) {
@@ -1317,16 +1341,16 @@ static int split_commit_in_progress(struct wt_status *s)
 	    !s->branch || strcmp(s->branch, "HEAD"))
 		return 0;
 
-	if (refs_read_ref_full(get_main_ref_store(the_repository), "HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
+	if (refs_read_ref_full(get_main_ref_store(s->repo), "HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
 			       &head_oid, &head_flags) ||
-	    refs_read_ref_full(get_main_ref_store(the_repository), "ORIG_HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
+	    refs_read_ref_full(get_main_ref_store(s->repo), "ORIG_HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
 			       &orig_head_oid, &orig_head_flags))
 		return 0;
 	if (head_flags & REF_ISSYMREF || orig_head_flags & REF_ISSYMREF)
 		return 0;
 
-	rebase_amend = read_line_from_git_path("rebase-merge/amend");
-	rebase_orig_head = read_line_from_git_path("rebase-merge/orig-head");
+	rebase_amend = read_line_from_git_path(s->repo, "rebase-merge/amend");
+	rebase_orig_head = read_line_from_git_path(s->repo, "rebase-merge/orig-head");
 
 	if (!rebase_amend || !rebase_orig_head)
 		; /* fall through, no split in progress */
@@ -1350,7 +1374,7 @@ static int split_commit_in_progress(struct wt_status *s)
  * The function assumes that the line does not contain useless spaces
  * before or after the command.
  */
-static void abbrev_oid_in_line(struct strbuf *line)
+static void abbrev_oid_in_line(struct repository *r, struct strbuf *line)
 {
 	struct string_list split = STRING_LIST_INIT_DUP;
 	struct object_id oid;
@@ -1362,7 +1386,7 @@ static void abbrev_oid_in_line(struct strbuf *line)
 		return;
 
 	if ((2 <= string_list_split(&split, line->buf, " ", 2)) &&
-	    !repo_get_oid(the_repository, split.items[1].string, &oid)) {
+	    !repo_get_oid(r, split.items[1].string, &oid)) {
 		strbuf_reset(line);
 		strbuf_addf(line, "%s ", split.items[0].string);
 		strbuf_add_unique_abbrev(line, &oid, DEFAULT_ABBREV);
@@ -1372,10 +1396,10 @@ static void abbrev_oid_in_line(struct strbuf *line)
 	string_list_clear(&split, 0);
 }
 
-static int read_rebase_todolist(const char *fname, struct string_list *lines)
+static int read_rebase_todolist(struct repository *r, const char *fname, struct string_list *lines)
 {
 	struct strbuf buf = STRBUF_INIT;
-	FILE *f = fopen(repo_git_path_append(the_repository, &buf, "%s", fname), "r");
+	FILE *f = fopen(repo_git_path_append(r, &buf, "%s", fname), "r");
 	int ret;
 
 	if (!f) {
@@ -1384,7 +1408,7 @@ static int read_rebase_todolist(const char *fname, struct string_list *lines)
 			goto out;
 		}
 		die_errno("Could not open file %s for reading",
-			  repo_git_path_replace(the_repository, &buf, "%s", fname));
+			  repo_git_path_replace(r, &buf, "%s", fname));
 	}
 	while (!strbuf_getline_lf(&buf, f)) {
 		if (starts_with(buf.buf, comment_line_str))
@@ -1392,7 +1416,7 @@ static int read_rebase_todolist(const char *fname, struct string_list *lines)
 		strbuf_trim(&buf);
 		if (!buf.len)
 			continue;
-		abbrev_oid_in_line(&buf);
+		abbrev_oid_in_line(r, &buf);
 		string_list_append(lines, buf.buf);
 	}
 	fclose(f);
@@ -1413,8 +1437,8 @@ static void show_rebase_information(struct wt_status *s,
 		struct string_list have_done = STRING_LIST_INIT_DUP;
 		struct string_list yet_to_do = STRING_LIST_INIT_DUP;
 
-		read_rebase_todolist("rebase-merge/done", &have_done);
-		if (read_rebase_todolist("rebase-merge/git-rebase-todo",
+		read_rebase_todolist(s->repo, "rebase-merge/done", &have_done);
+		if (read_rebase_todolist(s->repo, "rebase-merge/git-rebase-todo",
 					 &yet_to_do))
 			status_printf_ln(s, color,
 				_("git-rebase-todo is missing."));
@@ -1432,7 +1456,7 @@ static void show_rebase_information(struct wt_status *s,
 				i++)
 				status_printf_ln(s, color, "   %s", have_done.items[i].string);
 			if (have_done.nr > nr_lines_to_show && s->hints) {
-				char *path = repo_git_path(the_repository, "rebase-merge/done");
+				char *path = repo_git_path(s->repo, "rebase-merge/done");
 				status_printf_ln(s, color,
 					_("  (see more in file %s)"), path);
 				free(path);
@@ -1534,7 +1558,7 @@ static void show_cherry_pick_in_progress(struct wt_status *s,
 	else
 		status_printf_ln(s, color,
 			_("You are currently cherry-picking commit %s."),
-			repo_find_unique_abbrev(the_repository, &s->state.cherry_pick_head_oid,
+			repo_find_unique_abbrev(s->repo, &s->state.cherry_pick_head_oid,
 						DEFAULT_ABBREV));
 
 	if (s->hints) {
@@ -1564,7 +1588,7 @@ static void show_revert_in_progress(struct wt_status *s,
 	else
 		status_printf_ln(s, color,
 			_("You are currently reverting commit %s."),
-			repo_find_unique_abbrev(the_repository, &s->state.revert_head_oid,
+			repo_find_unique_abbrev(s->repo, &s->state.revert_head_oid,
 						DEFAULT_ABBREV));
 	if (s->hints) {
 		if (has_unmerged(s))
@@ -1624,7 +1648,7 @@ static char *get_branch(const struct worktree *wt, const char *path)
 	struct object_id oid;
 	const char *branch_name;
 
-	if (strbuf_read_file(&sb, worktree_git_path(the_repository, wt, "%s", path), 0) <= 0)
+	if (strbuf_read_file(&sb, worktree_git_path(wt, "%s", path), 0) <= 0)
 		goto got_nothing;
 
 	while (sb.len && sb.buf[sb.len - 1] == '\n')
@@ -1691,7 +1715,7 @@ static void wt_status_get_detached_from(struct repository *r,
 	char *ref = NULL;
 
 	strbuf_init(&cb.buf, 0);
-	if (refs_for_each_reflog_ent_reverse(get_main_ref_store(the_repository), "HEAD", grab_1st_switch, &cb) <= 0) {
+	if (refs_for_each_reflog_ent_reverse(get_main_ref_store(r), "HEAD", grab_1st_switch, &cb) <= 0) {
 		strbuf_release(&cb.buf);
 		return;
 	}
@@ -1723,18 +1747,21 @@ int wt_status_check_rebase(const struct worktree *wt,
 {
 	struct stat st;
 
-	if (!stat(worktree_git_path(the_repository, wt, "rebase-apply"), &st)) {
-		if (!stat(worktree_git_path(the_repository, wt, "rebase-apply/applying"), &st)) {
+	if (!wt)
+		BUG("wt_status_check_rebase() called with NULL worktree");
+
+	if (!stat(worktree_git_path(wt, "rebase-apply"), &st)) {
+		if (!stat(worktree_git_path(wt, "rebase-apply/applying"), &st)) {
 			state->am_in_progress = 1;
-			if (!stat(worktree_git_path(the_repository, wt, "rebase-apply/patch"), &st) && !st.st_size)
+			if (!stat(worktree_git_path(wt, "rebase-apply/patch"), &st) && !st.st_size)
 				state->am_empty_patch = 1;
 		} else {
 			state->rebase_in_progress = 1;
 			state->branch = get_branch(wt, "rebase-apply/head-name");
 			state->onto = get_branch(wt, "rebase-apply/onto");
 		}
-	} else if (!stat(worktree_git_path(the_repository, wt, "rebase-merge"), &st)) {
-		if (!stat(worktree_git_path(the_repository, wt, "rebase-merge/interactive"), &st))
+	} else if (!stat(worktree_git_path(wt, "rebase-merge"), &st)) {
+		if (!stat(worktree_git_path(wt, "rebase-merge/interactive"), &st))
 			state->rebase_interactive_in_progress = 1;
 		else
 			state->rebase_in_progress = 1;
@@ -1750,7 +1777,10 @@ int wt_status_check_bisect(const struct worktree *wt,
 {
 	struct stat st;
 
-	if (!stat(worktree_git_path(the_repository, wt, "BISECT_LOG"), &st)) {
+	if (!wt)
+		BUG("wt_status_check_bisect() called with NULL worktree");
+
+	if (!stat(worktree_git_path(wt, "BISECT_LOG"), &st)) {
 		state->bisect_in_progress = 1;
 		state->bisecting_from = get_branch(wt, "BISECT_START");
 		return 1;
@@ -1763,8 +1793,10 @@ static void wt_status_check_sparse_checkout(struct repository *r,
 {
 	int skip_worktree = 0;
 	int i;
+	struct repo_config_values *cfg = repo_config_values(the_repository);
 
-	if (!core_apply_sparse_checkout || r->index->cache_nr == 0) {
+	if (!cfg->apply_sparse_checkout ||
+	    r->index->cache_nr == 0) {
 		/*
 		 * Don't compute percentage of checked out files if we
 		 * aren't in a sparse checkout or would get division by 0.
@@ -1795,18 +1827,19 @@ void wt_status_get_state(struct repository *r,
 	struct stat st;
 	struct object_id oid;
 	enum replay_action action;
+	struct worktree *wt = get_worktree_from_repository(r);
 
 	if (!stat(git_path_merge_head(r), &st)) {
-		wt_status_check_rebase(NULL, state);
+		wt_status_check_rebase(wt, state);
 		state->merge_in_progress = 1;
-	} else if (wt_status_check_rebase(NULL, state)) {
+	} else if (wt_status_check_rebase(wt, state)) {
 		;		/* all set */
 	} else if (refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") &&
 		   !repo_get_oid(r, "CHERRY_PICK_HEAD", &oid)) {
 		state->cherry_pick_in_progress = 1;
 		oidcpy(&state->cherry_pick_head_oid, &oid);
 	}
-	wt_status_check_bisect(NULL, state);
+	wt_status_check_bisect(wt, state);
 	if (refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD") &&
 	    !repo_get_oid(r, "REVERT_HEAD", &oid)) {
 		state->revert_in_progress = 1;
@@ -1815,15 +1848,17 @@ void wt_status_get_state(struct repository *r,
 	if (!sequencer_get_last_command(r, &action)) {
 		if (action == REPLAY_PICK && !state->cherry_pick_in_progress) {
 			state->cherry_pick_in_progress = 1;
-			oidcpy(&state->cherry_pick_head_oid, null_oid(the_hash_algo));
+			oidcpy(&state->cherry_pick_head_oid, null_oid(r->hash_algo));
 		} else if (action == REPLAY_REVERT && !state->revert_in_progress) {
 			state->revert_in_progress = 1;
-			oidcpy(&state->revert_head_oid, null_oid(the_hash_algo));
+			oidcpy(&state->revert_head_oid, null_oid(r->hash_algo));
 		}
 	}
 	if (get_detached_from)
 		wt_status_get_detached_from(r, state);
 	wt_status_check_sparse_checkout(r, state);
+
+	free_worktree(wt);
 }
 
 static void wt_longstatus_print_state(struct wt_status *s)
@@ -2099,7 +2134,7 @@ static void wt_shortstatus_print_tracking(struct wt_status *s)
 		upstream_is_gone = 1;
 	}
 
-	short_base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+	short_base = refs_shorten_unambiguous_ref(get_main_ref_store(s->repo),
 						  base, 0);
 	color_fprintf(s->fp, header_color, "...");
 	color_fprintf(s->fp, branch_color_remote, "%s", short_base);
@@ -2233,7 +2268,7 @@ static void wt_porcelain_v2_print_tracking(struct wt_status *s)
 		ab_info = stat_tracking_info(branch, &nr_ahead, &nr_behind,
 					     &base, 0, s->ahead_behind_flags);
 		if (base) {
-			base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+			base = refs_shorten_unambiguous_ref(get_main_ref_store(s->repo),
 							    base, 0);
 			fprintf(s->fp, "# branch.upstream %s%c", base, eol);
 			free((char *)base);
@@ -2259,7 +2294,7 @@ static void wt_porcelain_v2_print_tracking(struct wt_status *s)
  */
 static void wt_porcelain_v2_print_stash(struct wt_status *s)
 {
-	int stash_count = count_stash_entries();
+	int stash_count = count_stash_entries(s->repo);
 	char eol = s->null_termination ? '\0' : '\n';
 
 	if (stash_count > 0)
@@ -2630,7 +2665,7 @@ int has_uncommitted_changes(struct repository *r,
 		 * We have no head (or it's corrupt); use the empty tree,
 		 * which will complain if the index is non-empty.
 		 */
-		struct tree *tree = lookup_tree(r, the_hash_algo->empty_tree);
+		struct tree *tree = lookup_tree(r, r->hash_algo->empty_tree);
 		add_pending_object(&rev_info, &tree->object, "");
 	}
 
diff --git a/wt-status.h b/wt-status.h
index e40a272..e9fe32e 100644
--- a/wt-status.h
+++ b/wt-status.h
@@ -153,6 +153,15 @@ void wt_status_add_cut_line(struct wt_status *s);
 void wt_status_prepare(struct repository *r, struct wt_status *s);
 void wt_status_print(struct wt_status *s);
 void wt_status_collect(struct wt_status *s);
+
+/*
+ * Collect all changes between the two trees. Changes will be displayed as if
+ * they were staged into the index.
+ */
+void wt_status_collect_changes_trees(struct wt_status *s,
+				     const struct object_id *old_treeish,
+				     const struct object_id *new_treeish);
+
 /*
  * Frees the buffers allocated by wt_status_collect.
  */
diff --git a/xdiff-interface.c b/xdiff-interface.c
index 4971f72..f043330 100644
--- a/xdiff-interface.c
+++ b/xdiff-interface.c
@@ -1,4 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
 #define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "git-compat-util.h"
@@ -177,18 +176,19 @@ int read_mmfile(mmfile_t *ptr, const char *filename)
 	return 0;
 }
 
-void read_mmblob(mmfile_t *ptr, const struct object_id *oid)
+void read_mmblob(mmfile_t *ptr, struct object_database *odb,
+		 const struct object_id *oid)
 {
 	unsigned long size;
 	enum object_type type;
 
-	if (oideq(oid, null_oid(the_hash_algo))) {
+	if (is_null_oid(oid)) {
 		ptr->ptr = xstrdup("");
 		ptr->size = 0;
 		return;
 	}
 
-	ptr->ptr = odb_read_object(the_repository->objects, oid, &type, &size);
+	ptr->ptr = odb_read_object(odb, oid, &type, &size);
 	if (!ptr->ptr || type != OBJ_BLOB)
 		die("unable to read blob object %s", oid_to_hex(oid));
 	ptr->size = size;
@@ -300,7 +300,7 @@ void xdiff_clear_find_func(xdemitconf_t *xecfg)
 
 unsigned long xdiff_hash_string(const char *s, size_t len, long flags)
 {
-	return xdl_hash_record(&s, s + len, flags);
+	return xdl_hash_record((uint8_t const**)&s, (uint8_t const*)s + len, flags);
 }
 
 int xdiff_compare_lines(const char *l1, long s1,
diff --git a/xdiff-interface.h b/xdiff-interface.h
index dfc55da..fbc4cee 100644
--- a/xdiff-interface.h
+++ b/xdiff-interface.h
@@ -4,6 +4,8 @@
 #include "hash.h"
 #include "xdiff/xdiff.h"
 
+struct object_database;
+
 /*
  * xdiff isn't equipped to handle content over a gigabyte;
  * we make the cutoff 1GB - 1MB to give some breathing
@@ -45,7 +47,8 @@ int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2,
 		  void *consume_callback_data,
 		  xpparam_t const *xpp, xdemitconf_t const *xecfg);
 int read_mmfile(mmfile_t *ptr, const char *filename);
-void read_mmblob(mmfile_t *ptr, const struct object_id *oid);
+void read_mmblob(mmfile_t *ptr, struct object_database *odb,
+		 const struct object_id *oid);
 int buffer_is_binary(const char *ptr, unsigned long size);
 
 void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);
diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h
index 2cecde5..dc37071 100644
--- a/xdiff/xdiff.h
+++ b/xdiff/xdiff.h
@@ -43,7 +43,7 @@ extern "C" {
 
 #define XDF_PATIENCE_DIFF (1 << 14)
 #define XDF_HISTOGRAM_DIFF (1 << 15)
-#define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF)
+#define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF | XDF_NEED_MINIMAL)
 #define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK)
 
 #define XDF_INDENT_HEURISTIC (1 << 23)
diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c
index 6f3998e..4376f94 100644
--- a/xdiff/xdiffi.c
+++ b/xdiff/xdiffi.c
@@ -22,9 +22,9 @@
 
 #include "xinclude.h"
 
-static unsigned long get_hash(xdfile_t *xdf, long index)
+static size_t get_hash(xdfile_t *xdf, long index)
 {
-	return xdf->recs[xdf->rindex[index]].ha;
+	return xdf->recs[xdf->reference_index[index]].minimal_perfect_hash;
 }
 
 #define XDL_MAX_COST_MIN 256
@@ -278,10 +278,10 @@ int xdl_recs_cmp(xdfile_t *xdf1, long off1, long lim1,
 	 */
 	if (off1 == lim1) {
 		for (; off2 < lim2; off2++)
-			xdf2->changed[xdf2->rindex[off2]] = true;
+			xdf2->changed[xdf2->reference_index[off2]] = true;
 	} else if (off2 == lim2) {
 		for (; off1 < lim1; off1++)
-			xdf1->changed[xdf1->rindex[off1]] = true;
+			xdf1->changed[xdf1->reference_index[off1]] = true;
 	} else {
 		xdpsplit_t spl;
 		spl.i1 = spl.i2 = 0;
@@ -385,7 +385,7 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1,
 
 static int recs_match(xrecord_t *rec1, xrecord_t *rec2)
 {
-	return (rec1->ha == rec2->ha);
+	return rec1->minimal_perfect_hash == rec2->minimal_perfect_hash;
 }
 
 /*
@@ -403,11 +403,10 @@ static int recs_match(xrecord_t *rec1, xrecord_t *rec2)
  */
 static int get_indent(xrecord_t *rec)
 {
-	long i;
 	int ret = 0;
 
-	for (i = 0; i < rec->size; i++) {
-		char c = rec->ptr[i];
+	for (size_t i = 0; i < rec->size; i++) {
+		char c = (char) rec->ptr[i];
 
 		if (!XDL_ISSPACE(c))
 			return ret;
@@ -484,7 +483,7 @@ static void measure_split(const xdfile_t *xdf, long split,
 {
 	long i;
 
-	if (split >= xdf->nrec) {
+	if (split >= (long)xdf->nrec) {
 		m->end_of_file = 1;
 		m->indent = -1;
 	} else {
@@ -507,7 +506,7 @@ static void measure_split(const xdfile_t *xdf, long split,
 
 	m->post_blank = 0;
 	m->post_indent = -1;
-	for (i = split + 1; i < xdf->nrec; i++) {
+	for (i = split + 1; i < (long)xdf->nrec; i++) {
 		m->post_indent = get_indent(&xdf->recs[i]);
 		if (m->post_indent != -1)
 			break;
@@ -718,7 +717,7 @@ static void group_init(xdfile_t *xdf, struct xdlgroup *g)
  */
 static inline int group_next(xdfile_t *xdf, struct xdlgroup *g)
 {
-	if (g->end == xdf->nrec)
+	if (g->end == (long)xdf->nrec)
 		return -1;
 
 	g->start = g->end + 1;
@@ -751,7 +750,7 @@ static inline int group_previous(xdfile_t *xdf, struct xdlgroup *g)
  */
 static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g)
 {
-	if (g->end < xdf->nrec &&
+	if (g->end < (long)xdf->nrec &&
 	    recs_match(&xdf->recs[g->start], &xdf->recs[g->end])) {
 		xdf->changed[g->start++] = false;
 		xdf->changed[g->end++] = true;
@@ -993,11 +992,11 @@ static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags)
 
 		rec = &xe->xdf1.recs[xch->i1];
 		for (i = 0; i < xch->chg1 && ignore; i++)
-			ignore = xdl_blankline(rec[i].ptr, rec[i].size, flags);
+			ignore = xdl_blankline((const char *)rec[i].ptr, (long)rec[i].size, flags);
 
 		rec = &xe->xdf2.recs[xch->i2];
 		for (i = 0; i < xch->chg2 && ignore; i++)
-			ignore = xdl_blankline(rec[i].ptr, rec[i].size, flags);
+			ignore = xdl_blankline((const char *)rec[i].ptr, (long)rec[i].size, flags);
 
 		xch->ignore = ignore;
 	}
@@ -1008,7 +1007,7 @@ static int record_matches_regex(xrecord_t *rec, xpparam_t const *xpp) {
 	size_t i;
 
 	for (i = 0; i < xpp->ignore_regex_nr; i++)
-		if (!regexec_buf(xpp->ignore_regex[i], rec->ptr, rec->size, 1,
+		if (!regexec_buf(xpp->ignore_regex[i], (const char *)rec->ptr, rec->size, 1,
 				 &regmatch, 0))
 			return 1;
 
diff --git a/xdiff/xemit.c b/xdiff/xemit.c
index b2f1f30..04f7e91 100644
--- a/xdiff/xemit.c
+++ b/xdiff/xemit.c
@@ -27,7 +27,7 @@ static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *
 {
 	xrecord_t *rec = &xdf->recs[ri];
 
-	if (xdl_emit_diffrec(rec->ptr, rec->size, pre, strlen(pre), ecb) < 0)
+	if (xdl_emit_diffrec((char const *)rec->ptr, (long)rec->size, pre, strlen(pre), ecb) < 0)
 		return -1;
 
 	return 0;
@@ -113,8 +113,8 @@ static long match_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri,
 	xrecord_t *rec = &xdf->recs[ri];
 
 	if (!xecfg->find_func)
-		return def_ff(rec->ptr, rec->size, buf, sz);
-	return xecfg->find_func(rec->ptr, rec->size, buf, sz, xecfg->find_func_priv);
+		return def_ff((const char *)rec->ptr, (long)rec->size, buf, sz);
+	return xecfg->find_func((const char *)rec->ptr, (long)rec->size, buf, sz, xecfg->find_func_priv);
 }
 
 static int is_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri)
@@ -137,7 +137,7 @@ static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg,
 	buf = func_line ? func_line->buf : dummy;
 	size = func_line ? sizeof(func_line->buf) : sizeof(dummy);
 
-	for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) {
+	for (l = start; l != limit && 0 <= l && l < (long)xe->xdf1.nrec; l += step) {
 		long len = match_func_rec(&xe->xdf1, xecfg, l, buf, size);
 		if (len >= 0) {
 			if (func_line)
@@ -151,7 +151,7 @@ static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg,
 static int is_empty_rec(xdfile_t *xdf, long ri)
 {
 	xrecord_t *rec = &xdf->recs[ri];
-	long i = 0;
+	size_t i = 0;
 
 	for (; i < rec->size && XDL_ISSPACE(rec->ptr[i]); i++);
 
@@ -179,14 +179,14 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
 			long fs1, i1 = xch->i1;
 
 			/* Appended chunk? */
-			if (i1 >= xe->xdf1.nrec) {
+			if (i1 >= (long)xe->xdf1.nrec) {
 				long i2 = xch->i2;
 
 				/*
 				 * We don't need additional context if
 				 * a whole function was added.
 				 */
-				while (i2 < xe->xdf2.nrec) {
+				while (i2 < (long)xe->xdf2.nrec) {
 					if (is_func_rec(&xe->xdf2, xecfg, i2))
 						goto post_context_calculation;
 					i2++;
@@ -196,7 +196,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
 				 * Otherwise get more context from the
 				 * pre-image.
 				 */
-				i1 = xe->xdf1.nrec - 1;
+				i1 = (long)xe->xdf1.nrec - 1;
 			}
 
 			fs1 = get_func_line(xe, xecfg, NULL, i1, -1);
@@ -228,8 +228,8 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
 
  post_context_calculation:
 		lctx = xecfg->ctxlen;
-		lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1));
-		lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2));
+		lctx = XDL_MIN(lctx, (long)xe->xdf1.nrec - (xche->i1 + xche->chg1));
+		lctx = XDL_MIN(lctx, (long)xe->xdf2.nrec - (xche->i2 + xche->chg2));
 
 		e1 = xche->i1 + xche->chg1 + lctx;
 		e2 = xche->i2 + xche->chg2 + lctx;
@@ -237,13 +237,13 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
 		if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
 			long fe1 = get_func_line(xe, xecfg, NULL,
 						 xche->i1 + xche->chg1,
-						 xe->xdf1.nrec);
+						 (long)xe->xdf1.nrec);
 			while (fe1 > 0 && is_empty_rec(&xe->xdf1, fe1 - 1))
 				fe1--;
 			if (fe1 < 0)
-				fe1 = xe->xdf1.nrec;
+				fe1 = (long)xe->xdf1.nrec;
 			if (fe1 > e1) {
-				e2 = XDL_MIN(e2 + (fe1 - e1), xe->xdf2.nrec);
+				e2 = XDL_MIN(e2 + (fe1 - e1), (long)xe->xdf2.nrec);
 				e1 = fe1;
 			}
 
@@ -254,7 +254,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
 			 */
 			if (xche->next) {
 				long l = XDL_MIN(xche->next->i1,
-						 xe->xdf1.nrec - 1);
+						 (long)xe->xdf1.nrec - 1);
 				if (l - xecfg->ctxlen <= e1 ||
 				    get_func_line(xe, xecfg, NULL, l, e1) < 0) {
 					xche = xche->next;
diff --git a/xdiff/xhistogram.c b/xdiff/xhistogram.c
index 6dc450b..5ae1282 100644
--- a/xdiff/xhistogram.c
+++ b/xdiff/xhistogram.c
@@ -90,7 +90,7 @@ struct region {
 
 static int cmp_recs(xrecord_t *r1, xrecord_t *r2)
 {
-	return r1->ha == r2->ha;
+	return r1->minimal_perfect_hash == r2->minimal_perfect_hash;
 
 }
 
@@ -98,7 +98,7 @@ static int cmp_recs(xrecord_t *r1, xrecord_t *r2)
 	(cmp_recs(REC(i->env, s1, l1), REC(i->env, s2, l2)))
 
 #define TABLE_HASH(index, side, line) \
-	XDL_HASHLONG((REC(index->env, side, line))->ha, index->table_bits)
+	XDL_HASHLONG((REC(index->env, side, line))->minimal_perfect_hash, index->table_bits)
 
 static int scanA(struct histindex *index, int line1, int count1)
 {
diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c
index fd600cb..29dad98 100644
--- a/xdiff/xmerge.c
+++ b/xdiff/xmerge.c
@@ -101,8 +101,8 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,
 	xrecord_t *rec2 = xe2->xdf2.recs + i2;
 
 	for (i = 0; i < line_count; i++) {
-		int result = xdl_recmatch(rec1[i].ptr, rec1[i].size,
-			rec2[i].ptr, rec2[i].size, flags);
+		int result = xdl_recmatch((const char *)rec1[i].ptr, (long)rec1[i].size,
+			(const char *)rec2[i].ptr, (long)rec2[i].size, flags);
 		if (!result)
 			return -1;
 	}
@@ -119,11 +119,11 @@ static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int nee
 	if (count < 1)
 		return 0;
 
-	for (i = 0; i < count; size += recs[i++].size)
+	for (i = 0; i < count; size += (int)recs[i++].size)
 		if (dest)
 			memcpy(dest + size, recs[i].ptr, recs[i].size);
 	if (add_nl) {
-		i = recs[count - 1].size;
+		i = (int)recs[count - 1].size;
 		if (i == 0 || recs[count - 1].ptr[i - 1] != '\n') {
 			if (needs_cr) {
 				if (dest)
@@ -156,9 +156,9 @@ static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int needs_cr, int add_n
  */
 static int is_eol_crlf(xdfile_t *file, int i)
 {
-	long size;
+	size_t size;
 
-	if (i < file->nrec - 1)
+	if (i < (long)file->nrec - 1)
 		/* All lines before the last *must* end in LF */
 		return (size = file->recs[i].size) > 1 &&
 			file->recs[i].ptr[size - 2] == '\r';
@@ -317,15 +317,15 @@ static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
 			continue;
 		i = m->i1 + m->chg1;
 	}
-	size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, 0,
+	size += xdl_recs_copy(xe1, i, (int)xe1->xdf2.nrec - i, 0, 0,
 			      dest ? dest + size : NULL);
 	return size;
 }
 
 static int recmatch(xrecord_t *rec1, xrecord_t *rec2, unsigned long flags)
 {
-	return xdl_recmatch(rec1->ptr, rec1->size,
-			    rec2->ptr, rec2->size, flags);
+	return xdl_recmatch((const char *)rec1->ptr, (long)rec1->size,
+			    (const char *)rec2->ptr, (long)rec2->size, flags);
 }
 
 /*
@@ -382,10 +382,10 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
 		 * we have a very simple mmfile structure.
 		 */
 		t1.ptr = (char *)xe1->xdf2.recs[m->i1].ptr;
-		t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1].ptr
+		t1.size = (char *)xe1->xdf2.recs[m->i1 + m->chg1 - 1].ptr
 			+ xe1->xdf2.recs[m->i1 + m->chg1 - 1].size - t1.ptr;
 		t2.ptr = (char *)xe2->xdf2.recs[m->i2].ptr;
-		t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1].ptr
+		t2.size = (char *)xe2->xdf2.recs[m->i2 + m->chg2 - 1].ptr
 			+ xe2->xdf2.recs[m->i2 + m->chg2 - 1].size - t2.ptr;
 		if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0)
 			return -1;
@@ -440,8 +440,8 @@ static int line_contains_alnum(const char *ptr, long size)
 static int lines_contain_alnum(xdfenv_t *xe, int i, int chg)
 {
 	for (; chg; chg--, i++)
-		if (line_contains_alnum(xe->xdf2.recs[i].ptr,
-				xe->xdf2.recs[i].size))
+		if (line_contains_alnum((const char *)xe->xdf2.recs[i].ptr,
+				(long)xe->xdf2.recs[i].size))
 			return 1;
 	return 0;
 }
@@ -622,7 +622,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1,
 			changes = c;
 		i0 = xscr1->i1;
 		i1 = xscr1->i2;
-		i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec;
+		i2 = xscr1->i1 + (long)xe2->xdf2.nrec - (long)xe2->xdf1.nrec;
 		chg0 = xscr1->chg1;
 		chg1 = xscr1->chg2;
 		chg2 = xscr1->chg1;
@@ -637,7 +637,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1,
 		if (!changes)
 			changes = c;
 		i0 = xscr2->i1;
-		i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec;
+		i1 = xscr2->i1 + (long)xe1->xdf2.nrec - (long)xe1->xdf1.nrec;
 		i2 = xscr2->i2;
 		chg0 = xscr2->chg1;
 		chg1 = xscr2->chg1;
diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c
index 669b653..7953490 100644
--- a/xdiff/xpatience.c
+++ b/xdiff/xpatience.c
@@ -48,7 +48,7 @@
 struct hashmap {
 	int nr, alloc;
 	struct entry {
-		unsigned long hash;
+		size_t minimal_perfect_hash;
 		/*
 		 * 0 = unused entry, 1 = first line, 2 = second, etc.
 		 * line2 is NON_UNIQUE if the line is not unique
@@ -61,12 +61,6 @@ struct hashmap {
 		 * initially, "next" reflects only the order in file1.
 		 */
 		struct entry *next, *previous;
-
-		/*
-		 * If 1, this entry can serve as an anchor. See
-		 * Documentation/diff-options.adoc for more information.
-		 */
-		unsigned anchor : 1;
 	} *entries, *first, *last;
 	/* were common records found? */
 	unsigned long has_matches;
@@ -85,8 +79,7 @@ static int is_anchor(xpparam_t const *xpp, const char *line)
 }
 
 /* The argument "pass" is 1 for the first file, 2 for the second. */
-static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
-			  int pass)
+static void insert_record(int line, struct hashmap *map, int pass)
 {
 	xrecord_t *records = pass == 1 ?
 		map->env->xdf1.recs : map->env->xdf2.recs;
@@ -101,10 +94,10 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
 	 * So we multiply ha by 2 in the hope that the hashing was
 	 * "unique enough".
 	 */
-	int index = (int)((record->ha << 1) % map->alloc);
+	int index = (int)((record->minimal_perfect_hash << 1) % map->alloc);
 
 	while (map->entries[index].line1) {
-		if (map->entries[index].hash != record->ha) {
+		if (map->entries[index].minimal_perfect_hash != record->minimal_perfect_hash) {
 			if (++index >= map->alloc)
 				index = 0;
 			continue;
@@ -120,8 +113,7 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
 	if (pass == 2)
 		return;
 	map->entries[index].line1 = line;
-	map->entries[index].hash = record->ha;
-	map->entries[index].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1].ptr);
+	map->entries[index].minimal_perfect_hash = record->minimal_perfect_hash;
 	if (!map->first)
 		map->first = map->entries + index;
 	if (map->last) {
@@ -153,11 +145,11 @@ static int fill_hashmap(xpparam_t const *xpp, xdfenv_t *env,
 
 	/* First, fill with entries from the first file */
 	while (count1--)
-		insert_record(xpp, line1++, result, 1);
+		insert_record(line1++, result, 1);
 
 	/* Then search for matches in the second file */
 	while (count2--)
-		insert_record(xpp, line2++, result, 2);
+		insert_record(line2++, result, 2);
 
 	return 0;
 }
@@ -194,6 +186,8 @@ static int binary_search(struct entry **sequence, int longest,
  */
 static int find_longest_common_sequence(struct hashmap *map, struct entry **res)
 {
+	xpparam_t const *xpp = map->xpp;
+	xrecord_t const *recs = map->env->xdf2.recs;
 	struct entry **sequence;
 	int longest = 0, i;
 	struct entry *entry;
@@ -211,13 +205,16 @@ static int find_longest_common_sequence(struct hashmap *map, struct entry **res)
 	for (entry = map->first; entry; entry = entry->next) {
 		if (!entry->line2 || entry->line2 == NON_UNIQUE)
 			continue;
-		i = binary_search(sequence, longest, entry);
+		if (longest == 0 || entry->line2 > sequence[longest - 1]->line2)
+			i = longest - 1;
+		else
+			i = binary_search(sequence, longest, entry);
 		entry->previous = i < 0 ? NULL : sequence[i];
 		++i;
 		if (i <= anchor_i)
 			continue;
 		sequence[i] = entry;
-		if (entry->anchor) {
+		if (is_anchor(xpp, (const char*)recs[entry->line2 - 1].ptr)) {
 			anchor_i = i;
 			longest = anchor_i + 1;
 		} else if (i == longest) {
@@ -248,7 +245,7 @@ static int match(struct hashmap *map, int line1, int line2)
 {
 	xrecord_t *record1 = &map->env->xdf1.recs[line1 - 1];
 	xrecord_t *record2 = &map->env->xdf2.recs[line2 - 1];
-	return record1->ha == record2->ha;
+	return record1->minimal_perfect_hash == record2->minimal_perfect_hash;
 }
 
 static int patience_diff(xpparam_t const *xpp, xdfenv_t *env,
@@ -370,5 +367,5 @@ static int patience_diff(xpparam_t const *xpp, xdfenv_t *env,
 
 int xdl_do_patience_diff(xpparam_t const *xpp, xdfenv_t *env)
 {
-	return patience_diff(xpp, env, 1, env->xdf1.nrec, 1, env->xdf2.nrec);
+	return patience_diff(xpp, env, 1, (int)env->xdf1.nrec, 1, (int)env->xdf2.nrec);
 }
diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c
index 192334f..cd4fc40 100644
--- a/xdiff/xprepare.c
+++ b/xdiff/xprepare.c
@@ -34,8 +34,10 @@
 #define INVESTIGATE 2
 
 typedef struct s_xdlclass {
+	uint64_t line_hash;
 	struct s_xdlclass *next;
-	xrecord_t rec;
+	const uint8_t *ptr;
+	size_t size;
 	long idx;
 	long len1, len2;
 } xdlclass_t;
@@ -92,15 +94,16 @@ static void xdl_free_classifier(xdlclassifier_t *cf) {
 }
 
 
-static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t *rec) {
-	long hi;
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t *rec,
+			       uint64_t line_hash) {
+	size_t hi;
 	xdlclass_t *rcrec;
 
-	hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
+	hi = XDL_HASHLONG(line_hash, cf->hbits);
 	for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next)
-		if (rcrec->rec.ha == rec->ha &&
-				xdl_recmatch(rcrec->rec.ptr, rcrec->rec.size,
-					rec->ptr, rec->size, cf->flags))
+		if (rcrec->line_hash == line_hash &&
+				xdl_recmatch((const char *)rcrec->ptr, (long)rcrec->size,
+					(const char *)rec->ptr, (long)rec->size, cf->flags))
 			break;
 
 	if (!rcrec) {
@@ -112,7 +115,9 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
 		if (XDL_ALLOC_GROW(cf->rcrecs, cf->count, cf->alloc))
 				return -1;
 		cf->rcrecs[rcrec->idx] = rcrec;
-		rcrec->rec = *rec;
+		rcrec->line_hash = line_hash;
+		rcrec->ptr = rec->ptr;
+		rcrec->size = rec->size;
 		rcrec->len1 = rcrec->len2 = 0;
 		rcrec->next = cf->rchash[hi];
 		cf->rchash[hi] = rcrec;
@@ -120,7 +125,7 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
 
 	(pass == 1) ? rcrec->len1++ : rcrec->len2++;
 
-	rec->ha = (unsigned long) rcrec->idx;
+	rec->minimal_perfect_hash = (size_t)rcrec->idx;
 
 	return 0;
 }
@@ -128,7 +133,7 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
 
 static void xdl_free_ctx(xdfile_t *xdf)
 {
-	xdl_free(xdf->rindex);
+	xdl_free(xdf->reference_index);
 	xdl_free(xdf->changed - 1);
 	xdl_free(xdf->recs);
 }
@@ -137,11 +142,11 @@ static void xdl_free_ctx(xdfile_t *xdf)
 static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
 			   xdlclassifier_t *cf, xdfile_t *xdf) {
 	long bsize;
-	unsigned long hav;
-	char const *blk, *cur, *top, *prev;
+	uint64_t hav;
+	uint8_t const *blk, *cur, *top, *prev;
 	xrecord_t *crec;
 
-	xdf->rindex = NULL;
+	xdf->reference_index = NULL;
 	xdf->changed = NULL;
 	xdf->recs = NULL;
 
@@ -153,13 +158,12 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 		for (top = blk + bsize; cur < top; ) {
 			prev = cur;
 			hav = xdl_hash_record(&cur, top, xpp->flags);
-			if (XDL_ALLOC_GROW(xdf->recs, xdf->nrec + 1, narec))
+			if (XDL_ALLOC_GROW(xdf->recs, (long)xdf->nrec + 1, narec))
 				goto abort;
 			crec = &xdf->recs[xdf->nrec++];
 			crec->ptr = prev;
-			crec->size = (long) (cur - prev);
-			crec->ha = hav;
-			if (xdl_classify_record(pass, cf, crec) < 0)
+			crec->size = cur - prev;
+			if (xdl_classify_record(pass, cf, crec, hav) < 0)
 				goto abort;
 		}
 	}
@@ -169,7 +173,7 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 
 	if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
 	    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) {
-		if (!XDL_ALLOC_ARRAY(xdf->rindex, xdf->nrec + 1))
+		if (!XDL_ALLOC_ARRAY(xdf->reference_index, xdf->nrec + 1))
 			goto abort;
 	}
 
@@ -264,7 +268,7 @@ static bool xdl_clean_mmatch(uint8_t const *action, long i, long s, long e) {
  * might be potentially discarded if they appear in a run of discardable.
  */
 static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
-	long i, nm, nreff, mlim;
+	long i, nm, mlim;
 	xrecord_t *recs;
 	xdlclass_t *rcrec;
 	uint8_t *action1 = NULL, *action2 = NULL;
@@ -287,18 +291,18 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd
 	/*
 	 * Initialize temporary arrays with DISCARD, KEEP, or INVESTIGATE.
 	 */
-	if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT)
+	if ((mlim = xdl_bogosqrt((long)xdf1->nrec)) > XDL_MAX_EQLIMIT)
 		mlim = XDL_MAX_EQLIMIT;
 	for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {
-		rcrec = cf->rcrecs[recs->ha];
+		rcrec = cf->rcrecs[recs->minimal_perfect_hash];
 		nm = rcrec ? rcrec->len2 : 0;
 		action1[i] = (nm == 0) ? DISCARD: (nm >= mlim && !need_min) ? INVESTIGATE: KEEP;
 	}
 
-	if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT)
+	if ((mlim = xdl_bogosqrt((long)xdf2->nrec)) > XDL_MAX_EQLIMIT)
 		mlim = XDL_MAX_EQLIMIT;
 	for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {
-		rcrec = cf->rcrecs[recs->ha];
+		rcrec = cf->rcrecs[recs->minimal_perfect_hash];
 		nm = rcrec ? rcrec->len1 : 0;
 		action2[i] = (nm == 0) ? DISCARD: (nm >= mlim && !need_min) ? INVESTIGATE: KEEP;
 	}
@@ -307,29 +311,29 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd
 	 * Use temporary arrays to decide if changed[i] should remain
 	 * false, or become true.
 	 */
-	for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart];
+	xdf1->nreff = 0;
+	for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart];
 	     i <= xdf1->dend; i++, recs++) {
 		if (action1[i] == KEEP ||
 		    (action1[i] == INVESTIGATE && !xdl_clean_mmatch(action1, i, xdf1->dstart, xdf1->dend))) {
-			xdf1->rindex[nreff++] = i;
+			xdf1->reference_index[xdf1->nreff++] = i;
 			/* changed[i] remains false, i.e. keep */
 		} else
 			xdf1->changed[i] = true;
 			/* i.e. discard */
 	}
-	xdf1->nreff = nreff;
 
-	for (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart];
+	xdf2->nreff = 0;
+	for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart];
 	     i <= xdf2->dend; i++, recs++) {
 		if (action2[i] == KEEP ||
 		    (action2[i] == INVESTIGATE && !xdl_clean_mmatch(action2, i, xdf2->dstart, xdf2->dend))) {
-			xdf2->rindex[nreff++] = i;
+			xdf2->reference_index[xdf2->nreff++] = i;
 			/* changed[i] remains false, i.e. keep */
 		} else
 			xdf2->changed[i] = true;
 			/* i.e. discard */
 	}
-	xdf2->nreff = nreff;
 
 cleanup:
 	xdl_free(action1);
@@ -348,9 +352,9 @@ static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
 
 	recs1 = xdf1->recs;
 	recs2 = xdf2->recs;
-	for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim;
+	for (i = 0, lim = (long)XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim;
 	     i++, recs1++, recs2++)
-		if (recs1->ha != recs2->ha)
+		if (recs1->minimal_perfect_hash != recs2->minimal_perfect_hash)
 			break;
 
 	xdf1->dstart = xdf2->dstart = i;
@@ -358,11 +362,11 @@ static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
 	recs1 = xdf1->recs + xdf1->nrec - 1;
 	recs2 = xdf2->recs + xdf2->nrec - 1;
 	for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--)
-		if (recs1->ha != recs2->ha)
+		if (recs1->minimal_perfect_hash != recs2->minimal_perfect_hash)
 			break;
 
-	xdf1->dend = xdf1->nrec - i - 1;
-	xdf2->dend = xdf2->nrec - i - 1;
+	xdf1->dend = (long)xdf1->nrec - i - 1;
+	xdf2->dend = (long)xdf2->nrec - i - 1;
 
 	return 0;
 }
diff --git a/xdiff/xtypes.h b/xdiff/xtypes.h
index f145abb..50aee77 100644
--- a/xdiff/xtypes.h
+++ b/xdiff/xtypes.h
@@ -39,18 +39,18 @@ typedef struct s_chastore {
 } chastore_t;
 
 typedef struct s_xrecord {
-	char const *ptr;
-	long size;
-	unsigned long ha;
+	uint8_t const *ptr;
+	size_t size;
+	size_t minimal_perfect_hash;
 } xrecord_t;
 
 typedef struct s_xdfile {
 	xrecord_t *recs;
-	long nrec;
-	long dstart, dend;
+	size_t nrec;
+	ptrdiff_t dstart, dend;
 	bool *changed;
-	long *rindex;
-	long nreff;
+	size_t *reference_index;
+	size_t nreff;
 } xdfile_t;
 
 typedef struct s_xdfenv {
diff --git a/xdiff/xutils.c b/xdiff/xutils.c
index 447e66c..77ee1ad 100644
--- a/xdiff/xutils.c
+++ b/xdiff/xutils.c
@@ -249,11 +249,11 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
 	return 1;
 }
 
-unsigned long xdl_hash_record_with_whitespace(char const **data,
-		char const *top, long flags) {
-	unsigned long ha = 5381;
-	char const *ptr = *data;
-	int cr_at_eol_only = (flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL;
+uint64_t xdl_hash_record_with_whitespace(uint8_t const **data,
+		uint8_t const *top, uint64_t flags) {
+	uint64_t ha = 5381;
+	uint8_t const *ptr = *data;
+	bool cr_at_eol_only = (flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL;
 
 	for (; ptr < top && *ptr != '\n'; ptr++) {
 		if (cr_at_eol_only) {
@@ -263,8 +263,8 @@ unsigned long xdl_hash_record_with_whitespace(char const **data,
 				continue;
 		}
 		else if (XDL_ISSPACE(*ptr)) {
-			const char *ptr2 = ptr;
-			int at_eol;
+			const uint8_t *ptr2 = ptr;
+			bool at_eol;
 			while (ptr + 1 < top && XDL_ISSPACE(ptr[1])
 					&& ptr[1] != '\n')
 				ptr++;
@@ -274,20 +274,20 @@ unsigned long xdl_hash_record_with_whitespace(char const **data,
 			else if (flags & XDF_IGNORE_WHITESPACE_CHANGE
 				 && !at_eol) {
 				ha += (ha << 5);
-				ha ^= (unsigned long) ' ';
+				ha ^= (uint64_t) ' ';
 			}
 			else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL
 				 && !at_eol) {
 				while (ptr2 != ptr + 1) {
 					ha += (ha << 5);
-					ha ^= (unsigned long) *ptr2;
+					ha ^= (uint64_t) *ptr2;
 					ptr2++;
 				}
 			}
 			continue;
 		}
 		ha += (ha << 5);
-		ha ^= (unsigned long) *ptr;
+		ha ^= (uint64_t) *ptr;
 	}
 	*data = ptr < top ? ptr + 1: ptr;
 
@@ -304,9 +304,9 @@ unsigned long xdl_hash_record_with_whitespace(char const **data,
 #define REASSOC_FENCE(x, y)
 #endif
 
-unsigned long xdl_hash_record_verbatim(char const **data, char const *top) {
-	unsigned long ha = 5381, c0, c1;
-	char const *ptr = *data;
+uint64_t xdl_hash_record_verbatim(uint8_t const **data, uint8_t const *top) {
+	uint64_t ha = 5381, c0, c1;
+	uint8_t const *ptr = *data;
 #if 0
 	/*
 	 * The baseline form of the optimized loop below. This is the djb2
@@ -314,7 +314,7 @@ unsigned long xdl_hash_record_verbatim(char const **data, char const *top) {
 	 */
 	for (; ptr < top && *ptr != '\n'; ptr++) {
 		ha += (ha << 5);
-		ha += (unsigned long) *ptr;
+		ha += (uint64_t) *ptr;
 	}
 	*data = ptr < top ? ptr + 1: ptr;
 #else
@@ -465,10 +465,10 @@ int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
 	xdfenv_t env;
 
 	subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1].ptr;
-	subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2].ptr +
+	subfile1.size = (char *)diff_env->xdf1.recs[line1 + count1 - 2].ptr +
 		diff_env->xdf1.recs[line1 + count1 - 2].size - subfile1.ptr;
 	subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1].ptr;
-	subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2].ptr +
+	subfile2.size = (char *)diff_env->xdf2.recs[line2 + count2 - 2].ptr +
 		diff_env->xdf2.recs[line2 + count2 - 2].size - subfile2.ptr;
 	if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0)
 		return -1;
diff --git a/xdiff/xutils.h b/xdiff/xutils.h
index 13f6831..615b4a9 100644
--- a/xdiff/xutils.h
+++ b/xdiff/xutils.h
@@ -34,9 +34,9 @@ void *xdl_cha_alloc(chastore_t *cha);
 long xdl_guess_lines(mmfile_t *mf, long sample);
 int xdl_blankline(const char *line, long size, long flags);
 int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags);
-unsigned long xdl_hash_record_verbatim(char const **data, char const *top);
-unsigned long xdl_hash_record_with_whitespace(char const **data, char const *top, long flags);
-static inline unsigned long xdl_hash_record(char const **data, char const *top, long flags)
+uint64_t xdl_hash_record_verbatim(uint8_t const **data, uint8_t const *top);
+uint64_t xdl_hash_record_with_whitespace(uint8_t const **data, uint8_t const *top, uint64_t flags);
+static inline uint64_t xdl_hash_record(uint8_t const **data, uint8_t const *top, uint64_t flags)
 {
 	if (flags & XDF_WHITESPACE_FLAGS)
 		return xdl_hash_record_with_whitespace(data, top, flags);