Merge branch 'ps/symlink-symref-deprecation'

"Symlink symref" has been added to the list of things that will
disappear at Git 3.0 boundary.

* ps/symlink-symref-deprecation:
  refs/files: deprecate writing symrefs as symbolic links
diff --git a/.github/workflows/check-style.yml b/.github/workflows/check-style.yml
index c052a5d..19a145d 100644
--- a/.github/workflows/check-style.yml
+++ b/.github/workflows/check-style.yml
@@ -20,7 +20,7 @@
       jobname: ClangFormat
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v5
       with:
         fetch-depth: 0
 
diff --git a/.github/workflows/check-whitespace.yml b/.github/workflows/check-whitespace.yml
index d0a78fc..928fd4c 100644
--- a/.github/workflows/check-whitespace.yml
+++ b/.github/workflows/check-whitespace.yml
@@ -19,7 +19,7 @@
   check-whitespace:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v5
       with:
         fetch-depth: 0
 
diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml
index 01a0437..cfa17d3 100644
--- a/.github/workflows/coverity.yml
+++ b/.github/workflows/coverity.yml
@@ -38,7 +38,7 @@
       COVERITY_LANGUAGE: cxx
       COVERITY_PLATFORM: overridden-below
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - name: install minimal Git for Windows SDK
         if: contains(matrix.os, 'windows')
         uses: git-for-windows/setup-git-for-windows-sdk@v1
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 393ea4d..cc54824 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -63,7 +63,7 @@
           echo "skip_concurrent=$skip_concurrent" >>$GITHUB_OUTPUT
       - name: skip if the commit or tree was already tested
         id: skip-if-redundant
-        uses: actions/github-script@v7
+        uses: actions/github-script@v8
         if: steps.check-ref.outputs.enabled == 'yes'
         with:
           github-token: ${{secrets.GITHUB_TOKEN}}
@@ -112,7 +112,7 @@
       group: windows-build-${{ github.ref }}
       cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v5
     - uses: git-for-windows/setup-git-for-windows-sdk@v1
     - name: build
       shell: bash
@@ -140,7 +140,7 @@
       cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}
     steps:
     - name: download tracked files and build artifacts
-      uses: actions/download-artifact@v4
+      uses: actions/download-artifact@v5
       with:
         name: windows-artifacts
         path: ${{github.workspace}}
@@ -173,10 +173,10 @@
       group: vs-build-${{ github.ref }}
       cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v5
     - uses: git-for-windows/setup-git-for-windows-sdk@v1
     - name: initialize vcpkg
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
       with:
         repository: 'microsoft/vcpkg'
         path: 'compat/vcbuild/vcpkg'
@@ -226,7 +226,7 @@
     steps:
     - uses: git-for-windows/setup-git-for-windows-sdk@v1
     - name: download tracked files and build artifacts
-      uses: actions/download-artifact@v4
+      uses: actions/download-artifact@v5
       with:
         name: vs-artifacts
         path: ${{github.workspace}}
@@ -258,8 +258,8 @@
       group: windows-meson-build-${{ github.ref }}
       cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}
     steps:
-    - uses: actions/checkout@v4
-    - uses: actions/setup-python@v5
+    - uses: actions/checkout@v5
+    - uses: actions/setup-python@v6
     - name: Set up dependencies
       shell: pwsh
       run: pip install meson ninja
@@ -286,13 +286,13 @@
       group: windows-meson-test-${{ matrix.nr }}-${{ github.ref }}
       cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}
     steps:
-    - uses: actions/checkout@v4
-    - uses: actions/setup-python@v5
+    - uses: actions/checkout@v5
+    - uses: actions/setup-python@v6
     - name: Set up dependencies
       shell: pwsh
       run: pip install meson ninja
     - name: Download build artifacts
-      uses: actions/download-artifact@v4
+      uses: actions/download-artifact@v5
       with:
         name: windows-meson-artifacts
         path: build
@@ -331,7 +331,7 @@
       TEST_OUTPUT_DIRECTORY: ${{github.workspace}}/t
     runs-on: ${{matrix.vector.pool}}
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v5
     - run: ci/install-dependencies.sh
     - run: ci/run-build-and-tests.sh
     - name: print test failures
@@ -352,7 +352,7 @@
       CI_JOB_IMAGE: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v5
     - run: ci/install-dependencies.sh
     - run: ci/run-build-and-minimal-fuzzers.sh
   dockerized:
@@ -429,7 +429,7 @@
         else
           apt-get -q update && apt-get -q -y install git
         fi
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v5
     - run: ci/install-dependencies.sh
     - run: useradd builder --create-home
     - run: chown -R builder .
@@ -454,10 +454,25 @@
       group: static-analysis-${{ github.ref }}
       cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v5
     - run: ci/install-dependencies.sh
     - run: ci/run-static-analysis.sh
     - run: ci/check-directional-formatting.bash
+  rust-analysis:
+    needs: ci-config
+    if: needs.ci-config.outputs.enabled == 'yes'
+    env:
+      jobname: RustAnalysis
+      CI_JOB_IMAGE: ubuntu:rolling
+    runs-on: ubuntu-latest
+    container: ubuntu:rolling
+    concurrency:
+      group: rust-analysis-${{ github.ref }}
+      cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}
+    steps:
+    - uses: actions/checkout@v4
+    - run: ci/install-dependencies.sh
+    - run: ci/run-rust-checks.sh
   sparse:
     needs: ci-config
     if: needs.ci-config.outputs.enabled == 'yes'
@@ -469,7 +484,7 @@
       group: sparse-${{ github.ref }}
       cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v5
     - name: Install other dependencies
       run: ci/install-dependencies.sh
     - run: make sparse
@@ -485,6 +500,6 @@
       CI_JOB_IMAGE: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v5
     - run: ci/install-dependencies.sh
     - run: ci/test-documentation.sh
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f7d57d1..b419a84 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -161,7 +161,7 @@
     - saas-windows-medium-amd64
   before_script:
     - *windows_before_script
-    - choco install -y git meson ninja
+    - choco install -y git meson ninja rust-ms
     - Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1
     - refreshenv
 
@@ -212,6 +212,17 @@
     - ./ci/run-static-analysis.sh
     - ./ci/check-directional-formatting.bash
 
+rust-analysis:
+  image: ubuntu:rolling
+  stage: analyze
+  needs: [ ]
+  variables:
+    jobname: RustAnalysis
+  before_script:
+    - ./ci/install-dependencies.sh
+  script:
+    - ./ci/run-rust-checks.sh
+
 check-whitespace:
   image: ubuntu:latest
   stage: analyze
diff --git a/Cargo.toml b/Cargo.toml
index 45c9b34..2f51bf5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,7 @@
 name = "gitcore"
 version = "0.1.0"
 edition = "2018"
+rust-version = "1.49.0"
 
 [lib]
 crate-type = ["staticlib"]
diff --git a/Documentation/Makefile b/Documentation/Makefile
index a3fbd29..04e9e10 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -34,6 +34,7 @@
 MAN5_TXT += gitformat-chunk.adoc
 MAN5_TXT += gitformat-commit-graph.adoc
 MAN5_TXT += gitformat-index.adoc
+MAN5_TXT += gitformat-loose.adoc
 MAN5_TXT += gitformat-pack.adoc
 MAN5_TXT += gitformat-signature.adoc
 MAN5_TXT += githooks.adoc
@@ -122,6 +123,7 @@
 TECH_DOCS += technical/commit-graph
 TECH_DOCS += technical/directory-rename-detection
 TECH_DOCS += technical/hash-function-transition
+TECH_DOCS += technical/large-object-promisors
 TECH_DOCS += technical/long-running-process-protocol
 TECH_DOCS += technical/multi-pack-index
 TECH_DOCS += technical/packfile-uri
diff --git a/Documentation/RelNotes/2.51.1.adoc b/Documentation/RelNotes/2.51.1.adoc
new file mode 100644
index 0000000..b8bd49c
--- /dev/null
+++ b/Documentation/RelNotes/2.51.1.adoc
@@ -0,0 +1,99 @@
+Git 2.51.1 Release Notes
+========================
+
+There shouldn't be anything exciting to see here.  This is primarily
+to flush the "do you still use it?" improvements that has landed on
+the master front, together with a handful of low-hanging, low-impact
+fixes that should be safe.
+
+
+Fixes since Git 2.51.0
+----------------------
+
+ * The "do you still use it?" message given by a command that is
+   deeply deprecated and allow us to suggest alternatives has been
+   updated.
+
+ * The compatObjectFormat extension is used to hide an incomplete
+   feature that is not yet usable for any purpose other than
+   developing the feature further.  Document it as such to discourage
+   its use by mere mortals.
+
+ * Manual page for "gitk" is updated with the current maintainer's
+   name.
+
+ * Update the instructions for using GGG in the MyFirstContribution
+   document to say that a GitHub PR could be made against `git/git`
+   instead of `gitgitgadget/git`.
+
+ * Clang-format update to let our control macros be formatted the way we
+   had them traditionally, e.g., "for_each_string_list_item()" without
+   space before the parentheses.
+
+ * A few places where a size_t value was cast to curl_off_t without
+   checking has been updated to use the existing helper function.
+
+ * The start_delayed_progress() function in the progress eye-candy API
+   did not clear its internal state, making an initial delay value
+   larger than 1 second ineffective, which has been corrected.
+
+ * Makefile tried to run multiple "cargo build" which would not work
+   very well; serialize their execution to work around this problem.
+
+ * Adjust to the way newer versions of cURL selectively enable tracing
+   options, so that our tests can continue to work.
+
+ * During interactive rebase, using 'drop' on a merge commit led to
+   an error, which has been corrected.
+
+ * "git refs migrate" to migrate the reflog entries from a refs
+   backend to another had a handful of bugs squashed.
+
+ * "git push" had a code path that led to BUG() but it should have
+   been a die(), as it is a response to a usual but invalid end-user
+   action to attempt pushing an object that does not exist.
+
+ * Various bugs about rename handling in "ort" merge strategy have
+   been fixed.
+
+ * "git diff --no-index" run inside a subdirectory under control of a
+   Git repository operated at the top of the working tree and stripped
+   the prefix from the output, and oddballs like "-" (stdin) did not
+   work correctly because of it.  Correct the set-up by undoing what
+   the set-up sequence did to cwd and prefix.
+
+ * Various options to "git diff" that make comparison ignore certain
+   aspects of the differences (like "space changes are ignored",
+   "differences in lines that match these regular expressions are
+   ignored") did not work well with "--name-only" and friends.
+
+ * Under a race against another process that is repacking the
+   repository, especially a partially cloned one, "git fetch" may
+   mistakenly think some objects we do have are missing, which has
+   been corrected.
+
+ * "git repack --path-walk" lost objects in some corner cases, which
+   has been corrected.
+   cf. <CABPp-BHFxxGrqKc0m==TjQNjDGdO=H5Rf6EFsf2nfE1=TuraOQ@mail.gmail.com>
+
+ * Fixes multiple crashes around midx write-out codepaths.
+
+ * A broken or malicious "git fetch" can say that it has the same
+   object for many many times, and the upload-pack serving it can
+   exhaust memory storing them redundantly, which has been corrected.
+
+ * A corner case bug in "git log -L..." has been corrected.
+
+ * Some among "git add -p" and friends ignored color.diff and/or
+   color.ui configuration variables, which is an old regression, which
+   has been corrected.
+
+ * "git rebase -i" failed to clean-up the commit log message when the
+   command commits the final one in a chain of "fixup" commands, which
+   has been corrected.
+
+ * Deal more gracefully with directory / file conflicts when the files
+   backend is used for ref storage, by failing only the ones that are
+   involved in the conflict while allowing others.
+
+Also contains various documentation updates, code cleanups and minor fixups.
diff --git a/Documentation/RelNotes/2.51.2.adoc b/Documentation/RelNotes/2.51.2.adoc
new file mode 100644
index 0000000..f0be603
--- /dev/null
+++ b/Documentation/RelNotes/2.51.2.adoc
@@ -0,0 +1,45 @@
+Git 2.51.2 Release Notes
+========================
+
+In addition to fixes for an unfortunate regression introduced in Git
+2.51.1 that caused "git diff --quiet -w" to be not so quiet when there
+are additions, deletions and conflicts, this maintenance release merges
+more fixes/improvements that have landed on the master front, primarily
+to make the CI part of the system a bit more robust.
+
+
+Fixes since Git 2.51.1
+----------------------
+
+ * Recently we attempted to improve "git diff -w --quiet" and friends
+   to handle cases where patch output would be suppressed, but it
+   introduced a bug that emits unnecessary output, which has been
+   corrected.
+
+ * The code to squelch output from "git diff -w --name-status"
+   etc. for paths that "git diff -w -p" would have stayed silent
+   leaked output from dry-run patch generation, which has been
+   corrected.
+
+ * Windows "real-time monitoring" interferes with the execution of
+   tests and affects negatively in both correctness and performance,
+   which has been disabled in Gitlab CI.
+
+ * An earlier addition to "git diff --no-index A B" to limit the
+   output with pathspec after the two directories misbehaved when
+   these directories were given with a trailing slash, which has been
+   corrected.
+
+ * The "--short" option of "git status" that meant output for humans
+   and "-z" option to show NUL delimited output format did not mix
+   well, and colored some but not all things.  The command has been
+   updated to color all elements consistently in such a case.
+
+ * Unicode width table update.
+
+ * Recent OpenSSH creates the Unix domain socket to communicate with
+   ssh-agent under $HOME instead of /tmp, which causes our test to
+   fail doe to overly long pathname in our test environment, which has
+   been worked around by using "ssh-agent -T".
+
+Also contains various documentation updates, code cleanups and minor fixups.
diff --git a/Documentation/RelNotes/2.52.0.adoc b/Documentation/RelNotes/2.52.0.adoc
index 1c5d59b..a7f9afc 100644
--- a/Documentation/RelNotes/2.52.0.adoc
+++ b/Documentation/RelNotes/2.52.0.adoc
@@ -47,6 +47,23 @@
  * "git fast-import" learned that "--signed-commits=<how>" option that
    corresponds to that of "git fast-export".
 
+ * Marking a hunk 'selected' in "git add -p" and then splitting made
+   all the split pieces 'selected'; this has been changed to make them
+   all 'undecided', which gives better end-user experience.
+
+ * Configuration variables that take a pathname as a value
+   (e.g. blame.ignorerevsfile) can be marked as optional by prefixing
+   ":(optoinal)" before its value.
+
+ * Show 'P'ipe command in "git add -p".
+
+ * "git sparse-checkout" subcommand learned a new "clean" action to
+   prune otherwise unused working-tree files that are outside the
+   areas of interest.
+
+ * "git fast-import" is taught to handle signed tags, just like it
+   recently learned to handle signed commits, in different ways.
+
 
 Performance, Internal Implementation, Development Support etc.
 --------------------------------------------------------------
@@ -92,9 +109,8 @@
 
  * CodingGuidelines now spells out how bitfields are to be written.
 
- * Adjust to the way newer versions of cURL selectivel enables tracing
+ * Adjust to the way newer versions of cURL selectively enable tracing
    options, so that our tests can continue to work.
-   (merge 1b5a6bfff3 jk/curl-global-trace-components later to maint).
 
  * The clear_alloc_state() API function was not fully clearing the
    structure for reuse, but since nobody reuses it, replace it with a
@@ -116,6 +132,23 @@
  * Dip our toes a bit to (optionally) use Rust implemented helper
    called from our C code.
 
+ * Documentation for "git log --pretty" options has been updated
+   to make it easier to translate.
+
+ * Instead of three library archives (one for git, one for reftable,
+   and one for xdiff), roll everything into a single libgit.a archive.
+   This would help later effort to FFI into Rust.
+
+ * The beginning of SHA1-SHA256 interoperability work.
+
+ * Build procedure for a few credential helpers (in contrib/) have
+   been updated.
+
+ * CI improvements to handle the recent Rust integration better.
+
+ * The code in "git repack" machinery has been cleaned up to prepare
+   for incremental update of midx files.
+
 
 Fixes since v2.51
 -----------------
@@ -125,11 +158,9 @@
 
  * During interactive rebase, using 'drop' on a merge commit lead to
    an error, which was incorrect.
-   (merge 4d491ade8f js/rebase-i-allow-drop-on-a-merge later to maint).
 
  * "git refs migrate" to migrate the reflog entries from a refs
    backend to another had a handful of bugs squashed.
-   (merge 465eff81de ps/reflog-migrate-fixes later to maint).
 
  * "git remote rename origin upstream" failed to move origin/HEAD to
    upstream/HEAD when origin/HEAD is unborn and performed other
@@ -142,11 +173,9 @@
  * "git push" had a code path that led to BUG() but it should have
    been a die(), as it is a response to a usual but invalid end-user
    action to attempt pushing an object that does not exist.
-   (merge dfbfc2221b dl/push-missing-object-error later to maint).
 
  * Various bugs about rename handling in "ort" merge strategy have
    been fixed.
-   (merge f6ecb603ff en/ort-rename-fixes later to maint).
 
  * "git jump" (in contrib/) fails to parse the diff header correctly
    when a file has a space in its name, which has been corrected.
@@ -157,7 +186,6 @@
    the prefix from the output, and oddballs like "-" (stdin) did not
    work correctly because of it.  Correct the set-up by undoing what
    the set-up sequence did to cwd and prefix.
-   (merge e1d3d61a45 jc/diff-no-index-in-subdir later to maint).
 
  * Various options to "git diff" that makes comparison ignore certain
    aspects of the differences (like "space changes are ignored",
@@ -165,19 +193,19 @@
    ignored") did not work well with "--name-only" and friends.
    (merge b55e6d36eb ly/diff-name-only-with-diff-from-content later to maint).
 
+ * The above caused regressions, which has been corrected.
+
  * Documentation for "git rebase" has been updated.
    (merge 3f7f2b0359 je/doc-rebase later to maint).
 
  * The start_delayed_progress() function in the progress eye-candy API
    did not clear its internal state, making an initial delay value
    larger than 1 second ineffective, which has been corrected.
-   (merge 457534d041 js/progress-delay-fix later to maint).
 
  * The compatObjectFormat extension is used to hide an incomplete
    feature that is not yet usable for any purpose other than
    developing the feature further.  Document it as such to discourage
    its use by mere mortals.
-   (merge 716d905792 bc/doc-compat-object-format-not-working later to maint).
 
  * "git log -L..." compared trees of multiple parents with the tree of the
    merge result in an unnecessarily inefficient way.
@@ -187,7 +215,6 @@
    repository, especially a partially cloned one, "git fetch" may
    mistakenly think some objects we do have are missing, which has
    been corrected.
-   (merge 8f32a5a6c0 jk/fetch-check-graph-objects-fix later to maint).
 
  * "git fetch" can clobber a symref that is dangling when the
    remote-tracking HEAD is set to auto update, which has been
@@ -199,20 +226,16 @@
 
  * Manual page for "gitk" is updated with the current maintainer's
    name.
-   (merge bcb20dda83 js/doc-gitk-history later to maint).
 
- * Update the instruction to use of GGG in the MyFirstContribution
+ * Update the instructions for using GGG in the MyFirstContribution
    document to say that a GitHub PR could be made against `git/git`
    instead of `gitgitgadget/git`.
-   (merge 37001cdbc4 ds/doc-ggg-pr-fork-clarify later to maint).
 
  * Makefile tried to run multiple "cargo build" which would not work
-   very well; serialize their execution to work it around.
-   (merge 0eeacde50e da/cargo-serialize later to maint).
+   very well; serialize their execution to work around this problem.
 
  * "git repack --path-walk" lost objects in some corner cases, which
    has been corrected.
-   (merge 93afe9b060 ds/path-walk-repack-fix later to maint).
 
  * "git ls-files <pathspec>..." should not necessarily have to expand
    the index fully if a sparsified directory is excluded by the
@@ -223,15 +246,12 @@
  * Windows "real-time monitoring" interferes with the execution of
    tests and affects negatively in both correctness and performance,
    which has been disabled in Gitlab CI.
-   (merge 608cf5b793 ps/gitlab-ci-disable-windows-monitoring later to maint).
 
  * A broken or malicious "git fetch" can say that it has the same
    object for many many times, and the upload-pack serving it can
    exhaust memory storing them redundantly, which has been corrected.
-   (merge 88a2dc68c8 ps/upload-pack-oom-protection later to maint).
 
  * A corner case bug in "git log -L..." has been corrected.
-   (merge e3106998ff sg/line-log-boundary-fixes later to maint).
 
  * "git rev-parse --short" and friends failed to disambiguate two
    objects with object names that share common prefix longer than 32
@@ -241,7 +261,6 @@
  * Some among "git add -p" and friends ignored color.diff and/or
    color.ui configuration variables, which is an old regression, which
    has been corrected.
-   (merge 1092cd6435 jk/add-i-color later to maint).
 
  * "git subtree" (in contrib/) did not work correctly when splitting
    squashed subtrees, which has been improved.
@@ -257,7 +276,6 @@
  * "git rebase -i" failed to clean-up the commit log message when the
    command commits the final one in a chain of "fixup" commands, which
    has been corrected.
-   (merge 82a0a73e15 pw/rebase-i-cleanup-fix later to maint).
 
  * There are double frees and leaks around setup_revisions() API used
    in "git stash show", which has been fixed, and setup_revisions()
@@ -268,7 +286,6 @@
  * Deal more gracefully with directory / file conflicts when the files
    backend is used for ref storage, by failing only the ones that are
    involved in the conflict while allowing others.
-   (merge 948b2ab0d8 kn/refs-files-case-insensitive later to maint).
 
  * "git last-modified" operating in non-recursive mode used to trigger
    a BUG(), which has been corrected.
@@ -281,16 +298,13 @@
  * The "do you still use it?" message given by a command that is
    deeply deprecated and allow us to suggest alternatives has been
    updated.
-   (merge 54a60e5b38 kh/you-still-use-whatchanged-fix later to maint).
 
- * Clang-format update to let our control macros formatted the way we
+ * Clang-format update to let our control macros be formatted the way we
    had them traditionally, e.g., "for_each_string_list_item()" without
    space before the parentheses.
-   (merge 3721541d35 jt/clang-format-foreach-wo-space-before-parenthesis later to maint).
 
- * A few places where an size_t value was cast to curl_off_t without
+ * A few places where a size_t value was cast to curl_off_t without
    checking has been updated to use the existing helper function.
-   (merge ecc5749578 js/curl-off-t-fixes later to maint).
 
  * "git reflog write" did not honor the configured user.name/email
    which has been corrected.
@@ -302,38 +316,57 @@
    environment, but Ubuntu replaced with "sudo" with an implementation
    that lacks the feature.  Work this around by reinstalling the
    original version.
-   (merge fddb484255 ps/ci-avoid-broken-sudo-on-ubuntu later to maint).
 
  * The reftable backend learned to sanity check its on-disk data more
    carefully.
    (merge 466a3a1afd kn/reftable-consistency-checks later to maint).
 
+ * A lot of code clean-up of xdiff.
+   Split out of a larger topic.
+   (merge 8b9c5d2e3a en/xdiff-cleanup later to maint).
+
+ * "git format-patch --range-diff=... --notes=..." did not drive the
+   underlying range-diff with correct --notes parameter, ending up
+   comparing with different set of notes from its main patch output
+   you would get from "git format-patch --notes=..." for a singleton
+   patch.
+
+ * The code in "git add -p" and friends to iterate over hunks was
+   riddled with bugs, which has been corrected.
+
+ * A few more things that patch authors can do to help maintainer to
+   keep track of their topics better.
+   (merge 1a41698841 tb/doc-submitting-patches later to maint).
+
+ * An earlier addition to "git diff --no-index A B" to limit the
+   output with pathspec after the two directories misbehaved when
+   these directories were given with a trailing slash, which has been
+   corrected.
+
+ * The "--short" option of "git status" that meant output for humans
+   and "-z" option to show NUL delimited output format did not mix
+   well, and colored some but not all things.  The command has been
+   updated to color all elements consistently in such a case.
+
+ * Unicode width table update.
+
+ * GPG signing test set-up has been broken for a year, which has been
+   corrected.
+   (merge 516bf45749 jc/t1016-setup-fix later to maint).
+
+ * Recent OpenSSH creates the Unix domain socket to communicate with
+   ssh-agent under $HOME instead of /tmp, which causes our test to
+   fail doe to overly long pathname in our test environment, which has
+   been worked around by using "ssh-agent -T".
+
  * Other code cleanup, docfix, build fix, etc.
-   (merge 823d537fa7 kh/doc-git-log-markup-fix later to maint).
-   (merge cf7efa4f33 rj/t6137-cygwin-fix later to maint).
    (merge 529a60a885 ua/t1517-short-help-tests later to maint).
    (merge 22d421fed9 ac/deglobal-fmt-merge-log-config later to maint).
-   (merge 741f36c7d9 kr/clone-synopsis-fix later to maint).
    (merge a60203a015 dk/t7005-editor-updates later to maint).
-   (merge 7d4a5fef7d ds/doc-count-objects-fix later to maint).
    (merge 16684b6fae ps/reftable-libgit2-cleanup later to maint).
-   (merge f38786baa7 ja/asciidoc-doctor-verbatim-fixes later to maint).
-   (merge 374579c6d4 kh/doc-interpret-trailers-markup-fix later to maint).
-   (merge 44dce6541c kh/doc-config-typofix later to maint).
-   (merge 785628b173 js/doc-sending-patch-via-thunderbird later to maint).
    (merge e5c27bd3d8 je/doc-add later to maint).
    (merge 13296ac909 ps/object-store-midx-dedup-info later to maint).
-   (merge 2f4bf83ffc km/alias-doc-markup-fix later to maint).
-   (merge b0d97aac19 kh/doc-markup-fixes later to maint).
    (merge f9a6705d9a tc/t0450-harden later to maint).
-   (merge c25651aefd ds/midx-write-fixes later to maint).
-   (merge 069c15d256 rs/object-name-extend-abbrev-len-update later to maint).
-   (merge bf5c224537 mm/worktree-doc-typofix later to maint).
-   (merge 31397bc4f7 kh/doc-fast-import-markup-fix later to maint).
-   (merge ac7096723b jc/doc-includeif-hasconfig-remote-url-fix later to maint).
-   (merge fafc9b08b8 ag/doc-sendmail-gmail-example-update later to maint).
    (merge a66fc22bf9 rs/get-oid-with-flags-cleanup later to maint).
-   (merge e1d062e8ba ps/odb-clean-stale-wrappers later to maint).
-   (merge fdd21ba116 mh/doc-credential-url-prefix later to maint).
-   (merge 1c573a3451 en/doc-merge-tree-describe-merge-base later to maint).
-   (merge 84a6bf7965 ja/doc-markup-attached-paragraph-fix later to maint).
+   (merge 15b8abde07 js/mingw-includes-cleanup later to maint).
+   (merge 2cebca0582 tb/cat-file-objectmode-update later to maint).
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index 86ca7f6..d620bd9 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -579,14 +579,27 @@
 [[the-topic-summary]]
 *This is EXPERIMENTAL*.
 
-When sending a topic, you can propose a one-paragraph summary that
-should appear in the "What's cooking" report when it is picked up to
-explain the topic.  If you choose to do so, please write a 2-5 line
-paragraph that will fit well in our release notes (see many bulleted
-entries in the Documentation/RelNotes/* files for examples), and make
-it the first paragraph of the cover letter.  For a single-patch
-series, use the space between the three-dash line and the diffstat, as
-described earlier.
+When sending a topic, you can optionally propose a topic name and/or a
+one-paragraph summary that should appear in the "What's cooking"
+report when it is picked up to explain the topic.  If you choose to do
+so, please write a 2-5 line paragraph that will fit well in our
+release notes (see many bulleted entries in the
+Documentation/RelNotes/* files for examples), and make it the first
+(or second, if including a suggested topic name) paragraph of the
+cover letter.  If suggesting a topic name, use the format
+"XX/your-topic-name", where "XX" is a stand-in for the primary
+author's initials, and "your-topic-name" is a brief, dash-delimited
+description of what your topic does.  For a single-patch series, use
+the space between the three-dash line and the diffstat, as described
+earlier.
+
+[[multi-series-efforts]]
+If your patch series is part of a larger effort spanning multiple
+patch series, briefly describe the broader goal, and state where the
+current series fits into that goal.  If you are suggesting a topic
+name as in <<the-topic-summary, section above>>, consider
+"XX/the-broader-goal-part-one", "XX/the-broader-goal-part-two", and so
+on.
 
 [[attachment]]
 Do not attach the patch as a MIME attachment, compressed or not.
diff --git a/Documentation/config.adoc b/Documentation/config.adoc
index 05f1ca7..62eebe7 100644
--- a/Documentation/config.adoc
+++ b/Documentation/config.adoc
@@ -357,7 +357,9 @@
 substituted instead. In the unlikely event that a literal path needs to
 be specified that should _not_ be expanded, it needs to be prefixed by
 `./`, like so: `./%(prefix)/bin`.
-
++
+If prefixed with `:(optional)`, the configuration variable is treated
+as if it does not exist, if the named path does not exist.
 
 Variables
 ~~~~~~~~~
diff --git a/Documentation/config/commitgraph.adoc b/Documentation/config/commitgraph.adoc
index 7f8c9d6..70a56c5 100644
--- a/Documentation/config/commitgraph.adoc
+++ b/Documentation/config/commitgraph.adoc
@@ -8,6 +8,17 @@
 	Specifies the default value for the `--max-new-filters` option of `git
 	commit-graph write` (c.f., linkgit:git-commit-graph[1]).
 
+commitGraph.changedPaths::
+	If true, then `git commit-graph write` will compute and write
+	changed-path Bloom filters by default, equivalent to passing
+	`--changed-paths`. If false or unset, changed-paths Bloom filters will
+	be written during `git commit-graph write` only if the filters already
+	exist in the current commit-graph file. This matches the default
+	behavior of `git commit-graph write` without any `--[no-]changed-paths`
+	option. To rewrite a commit-graph file without any filters, use the
+	`--no-changed-paths` option. Command-line option `--[no-]changed-paths`
+	always takes precedence over this configuration. Defaults to unset.
+
 commitGraph.readChangedPaths::
 	Deprecated. Equivalent to commitGraph.changedPathsVersion=-1 if true, and
 	commitGraph.changedPathsVersion=0 if false. (If commitGraph.changedPathVersion
diff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc
index 406d702..11efad1 100644
--- a/Documentation/config/core.adoc
+++ b/Documentation/config/core.adoc
@@ -75,8 +75,8 @@
 limited set of supported platforms.  Currently, this includes Windows
 and MacOS.
 +
-	Otherwise, this variable contains the pathname of the "fsmonitor"
-	hook command.
+Otherwise, this variable contains the pathname of the "fsmonitor"
+hook command.
 +
 This hook command is used to identify all files that may have changed
 since the requested date/time. This information is used to speed up
diff --git a/Documentation/config/stash.adoc b/Documentation/config/stash.adoc
index e556105..a1197ff 100644
--- a/Documentation/config/stash.adoc
+++ b/Documentation/config/stash.adoc
@@ -1,19 +1,32 @@
-stash.index::
+ifndef::git-stash[]
+:see-show: See the description of the 'show' command in linkgit:git-stash[1].
+endif::git-stash[]
+
+ifdef::git-stash[]
+:see-show:
+endif::git-stash[]
+
+`stash.index`::
 	If this is set to true, `git stash apply` and `git stash pop` will
-	behave as if `--index` was supplied. Defaults to false. See the
-	descriptions in linkgit:git-stash[1].
+	behave as if `--index` was supplied. Defaults to false.
+ifndef::git-stash[]
+See the descriptions in linkgit:git-stash[1].
++
+This also affects invocations of linkgit:git-stash[1] via `--autostash` from
+commands like linkgit:git-merge[1], linkgit:git-rebase[1], and
+linkgit:git-pull[1].
+endif::git-stash[]
 
-stash.showIncludeUntracked::
+`stash.showIncludeUntracked`::
 	If this is set to true, the `git stash show` command will show
-	the untracked files of a stash entry.  Defaults to false. See
-	the description of the 'show' command in linkgit:git-stash[1].
+	the untracked files of a stash entry. Defaults to false. {see-show}
 
-stash.showPatch::
+`stash.showPatch`::
 	If this is set to true, the `git stash show` command without an
 	option will show the stash entry in patch form.  Defaults to false.
-	See the description of the 'show' command in linkgit:git-stash[1].
+	{see-show}
 
-stash.showStat::
+`stash.showStat`::
 	If this is set to true, the `git stash show` command without an
 	option will show a diffstat of the stash entry.  Defaults to true.
-	See the description of the 'show' command in linkgit:git-stash[1].
+	{see-show}
diff --git a/Documentation/config/tag.adoc b/Documentation/config/tag.adoc
index 5062a05..d878da9 100644
--- a/Documentation/config/tag.adoc
+++ b/Documentation/config/tag.adoc
@@ -1,17 +1,23 @@
-tag.forceSignAnnotated::
+`tag.forceSignAnnotated`::
 	A boolean to specify whether annotated tags created should be GPG signed.
 	If `--annotate` is specified on the command line, it takes
 	precedence over this option.
 
-tag.sort::
-	This variable controls the sort ordering of tags when displayed by
-	linkgit:git-tag[1]. Without the "--sort=<value>" option provided, the
-	value of this variable will be used as the default.
+`tag.sort`::
+ifdef::git-tag[]
+This variable controls the sort ordering of tags when displayed by `git-tag`.
+endif::git-tag[]
+ifndef::git-tag[]
+This variable controls the sort ordering of tags when displayed by
+linkgit:git-tag[1].
+endif::git-tag[]
+Without the `--sort=<value>` option provided, the value of this variable will
+be used as the default.
 
-tag.gpgSign::
+`tag.gpgSign`::
 	A boolean to specify whether all tags should be GPG signed.
 	Use of this option when running in an automated script can
 	result in a large number of tags being signed. It is therefore
-	convenient to use an agent to avoid typing your gpg passphrase
+	convenient to use an agent to avoid typing your GPG passphrase
 	several times. Note that this option doesn't affect tag signing
-	behavior enabled by "-u <keyid>" or "--local-user=<keyid>" options.
+	behavior enabled by `-u <keyid>` or `--local-user=<keyid>` options.
diff --git a/Documentation/config/worktree.adoc b/Documentation/config/worktree.adoc
index 9e3f84f..a248076 100644
--- a/Documentation/config/worktree.adoc
+++ b/Documentation/config/worktree.adoc
@@ -1,4 +1,4 @@
-worktree.guessRemote::
+`worktree.guessRemote`::
 	If no branch is specified and neither `-b` nor `-B` nor
 	`--detach` is used, then `git worktree add` defaults to
 	creating a new branch from HEAD.  If `worktree.guessRemote` is
@@ -6,14 +6,14 @@
 	branch whose name uniquely matches the new branch name.  If
 	such a branch exists, it is checked out and set as "upstream"
 	for the new branch.  If no such match can be found, it falls
-	back to creating a new branch from the current HEAD.
+	back to creating a new branch from the current `HEAD`.
 
-worktree.useRelativePaths::
-	Link worktrees using relative paths (when "true") or absolute
-	paths (when "false"). This is particularly useful for setups
+`worktree.useRelativePaths`::
+	Link worktrees using relative paths (when "`true`") or absolute
+	paths (when "`false`"). This is particularly useful for setups
 	where the repository and worktrees may be moved between
-	different locations or environments. Defaults to "false".
+	different locations or environments. Defaults to "`false`".
 +
-Note that setting `worktree.useRelativePaths` to "true" implies enabling the
+Note that setting `worktree.useRelativePaths` to "`true`" implies enabling the
 `extensions.relativeWorktrees` config (see linkgit:git-config[1]),
 thus making it incompatible with older versions of Git.
diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc
index 81f11ba..acac968 100644
--- a/Documentation/fsck-msgids.adoc
+++ b/Documentation/fsck-msgids.adoc
@@ -10,6 +10,12 @@
 `badFilemode`::
 	(INFO) A tree contains a bad filemode entry.
 
+`badGpgsig`::
+	(ERROR) A tag contains a bad (truncated) signature (e.g., `gpgsig`) header.
+
+`badHeaderContinuation`::
+	(ERROR) A continuation header (such as for `gpgsig`) is unexpectedly truncated.
+
 `badName`::
 	(ERROR) An author/committer name is empty.
 
diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc
index ad629c4..6192dae 100644
--- a/Documentation/git-add.adoc
+++ b/Documentation/git-add.adoc
@@ -342,13 +342,14 @@
        d - do not stage this hunk or any of the later hunks in the file
        g - select a hunk to go to
        / - search for a hunk matching the given regex
-       j - leave this hunk undecided, see next undecided hunk
-       J - leave this hunk undecided, see next hunk
-       k - leave this hunk undecided, see previous undecided hunk
-       K - leave this hunk undecided, see previous hunk
+       j - go to the next undecided hunk, roll over at the bottom
+       J - go to the next hunk, roll over at the bottom
+       k - go to the previous undecided hunk, roll over at the top
+       K - go to the previous hunk, roll over at the top
        s - split the current hunk into smaller hunks
        e - manually edit the current hunk
        p - print the current hunk
+       P - print the current hunk using the pager
        ? - print help
 +
 After deciding the fate for all hunks, if there is any hunk
diff --git a/Documentation/git-commit-graph.adoc b/Documentation/git-commit-graph.adoc
index e955817..6d19026 100644
--- a/Documentation/git-commit-graph.adoc
+++ b/Documentation/git-commit-graph.adoc
@@ -71,7 +71,7 @@
 for getting history of a directory or a file with `git log -- <path>`. If
 this option is given, future commit-graph writes will automatically assume
 that this option was intended. Use `--no-changed-paths` to stop storing this
-data.
+data. `--changed-paths` is implied by config `commitGraph.changedPaths=true`.
 +
 With the `--max-new-filters=<n>` option, generate at most `n` new Bloom
 filters (if `--changed-paths` is specified). If `n` is `-1`, no limit is
diff --git a/Documentation/git-config.adoc b/Documentation/git-config.adoc
index 36d2845..cc054fa 100644
--- a/Documentation/git-config.adoc
+++ b/Documentation/git-config.adoc
@@ -117,15 +117,15 @@
 
 --comment <message>::
 	Append a comment at the end of new or modified lines.
-
-	If _<message>_ begins with one or more whitespaces followed
-	by "#", it is used as-is.  If it begins with "#", a space is
-	prepended before it is used.  Otherwise, a string " # " (a
-	space followed by a hash followed by a space) is prepended
-	to it.  And the resulting string is placed immediately after
-	the value defined for the variable.  The _<message>_ must
-	not contain linefeed characters (no multi-line comments are
-	permitted).
++
+If _<message>_ begins with one or more whitespaces followed
+by "#", it is used as-is.  If it begins with "#", a space is
+prepended before it is used.  Otherwise, a string " # " (a
+space followed by a hash followed by a space) is prepended
+to it.  And the resulting string is placed immediately after
+the value defined for the variable.  The _<message>_ must
+not contain linefeed characters (no multi-line comments are
+permitted).
 
 --all::
 	With `get`, return all values for a multi-valued key.
diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc
index 85ed7a7..b74179a 100644
--- a/Documentation/git-fast-import.adoc
+++ b/Documentation/git-fast-import.adoc
@@ -66,6 +66,11 @@
 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-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
diff --git a/Documentation/git-patch-id.adoc b/Documentation/git-patch-id.adoc
index 1d15fa4..45da0f2 100644
--- a/Documentation/git-patch-id.adoc
+++ b/Documentation/git-patch-id.adoc
@@ -33,27 +33,30 @@
 --verbatim::
 	Calculate the patch-id of the input as it is given, do not strip
 	any whitespace.
-
-	This is the default if patchid.verbatim is true.
++
+This is the default if patchid.verbatim is true.
 
 --stable::
 	Use a "stable" sum of hashes as the patch ID. With this option:
-	 - Reordering file diffs that make up a patch does not affect the ID.
-	   In particular, two patches produced by comparing the same two trees
-	   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;
++
+--
+- Reordering file diffs that make up a patch does not affect the ID.
+  In particular, two patches produced by comparing the same two trees
+  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;
 
-	 - 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.
+- 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.
 
-	 - All whitespace within the patch is ignored and does not affect the id.
-
-	This is the default if patchid.stable is set to true.
+- 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,
@@ -61,8 +64,8 @@
 	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.
++
+This is the default.
 
 GIT
 ---
diff --git a/Documentation/git-pull.adoc b/Documentation/git-pull.adoc
index 48e924a..cd3bbc9 100644
--- a/Documentation/git-pull.adoc
+++ b/Documentation/git-pull.adoc
@@ -15,68 +15,54 @@
 DESCRIPTION
 -----------
 
-Incorporates changes from a remote repository into the current branch.
-If the current branch is behind the remote, then by default it will
-fast-forward the current branch to match the remote.  If the current
-branch and the remote have diverged, the user needs to specify how to
-reconcile the divergent branches with `--rebase` or `--no-rebase` (or
-the corresponding configuration option in `pull.rebase`).
+Integrate changes from a remote repository into the current branch.
 
-More precisely, `git pull` runs `git fetch` with the given parameters
-and then depending on configuration options or command line flags,
-will call either `git rebase` or `git merge` to reconcile diverging
-branches.
+First, `git pull` runs `git fetch` with the same arguments
+(excluding merge options) to fetch remote branch(es).
+Then it decides which remote branch to integrate: if you run `git pull`
+with no arguments this defaults to the <<UPSTREAM-BRANCHES,upstream>>
+for the current branch.
+Then it integrates that branch into the current branch.
 
-<repository> should be the name of a remote repository as
-passed to linkgit:git-fetch[1].  <refspec> can name an
-arbitrary remote ref (for example, the name of a tag) or even
-a collection of refs with corresponding remote-tracking branches
-(e.g., refs/heads/{asterisk}:refs/remotes/origin/{asterisk}),
-but usually it is the name of a branch in the remote repository.
+There are 4 main options for integrating the remote branch:
 
-Default values for <repository> and <branch> are read from the
-"remote" and "merge" configuration for the current branch
-as set by linkgit:git-branch[1] `--track`.
+1. `git pull --ff-only` will only do "fast-forward" updates: it
+   fails if your local branch has diverged from the remote branch.
+   This is the default.
+2. `git pull --rebase` runs `git rebase`
+3. `git pull --no-rebase` runs `git merge`.
+4. `git pull --squash` runs `git merge --squash`
 
-Assume the following history exists and the current branch is
-"`master`":
+You can also set the configuration options `pull.rebase`, `pull.squash`,
+or `pull.ff` with your preferred behaviour.
 
-------------
-	  A---B---C master on origin
-	 /
-    D---E---F---G master
-	^
-	origin/master in your repository
-------------
-
-Then "`git pull`" will fetch and replay the changes from the remote
-`master` branch since it diverged from the local `master` (i.e., `E`)
-until its current commit (`C`) on top of `master` and record the
-result in a new commit along with the names of the two parent commits
-and a log message from the user describing the changes.
-
-------------
-	  A---B---C origin/master
-	 /         \
-    D---E---F---G---H master
-------------
-
-See linkgit:git-merge[1] for details, including how conflicts
-are presented and handled.
-
-In Git 1.7.0 or later, to cancel a conflicting merge, use
-`git reset --merge`.  *Warning*: In older versions of Git, running 'git pull'
-with uncommitted changes is discouraged: while possible, it leaves you
-in a state that may be hard to back out of in the case of a conflict.
-
-If any of the remote changes overlap with local uncommitted changes,
-the merge will be automatically canceled and the work tree untouched.
-It is generally best to get any local changes in working order before
-pulling or stash them away with linkgit:git-stash[1].
+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`.
 
 OPTIONS
 -------
 
+<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).
++
+Defaults to the configured upstream for the current branch, or `origin`.
+See <<UPSTREAM-BRANCHES,UPSTREAM BRANCHES>> below for more on how to
+configure upstreams.
+
+<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"
+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::
 	This is passed to both underlying git-fetch to squelch reporting of
@@ -145,6 +131,7 @@
 
 include::merge-strategies.adoc[]
 
+[[DEFAULT-BEHAVIOUR]]
 DEFAULT BEHAVIOUR
 -----------------
 
diff --git a/Documentation/git-push.adoc b/Documentation/git-push.adoc
index cc5cadc..864b0d0 100644
--- a/Documentation/git-push.adoc
+++ b/Documentation/git-push.adoc
@@ -19,31 +19,36 @@
 DESCRIPTION
 -----------
 
-Updates remote refs using local refs, while sending objects
-necessary to complete the given refs.
+Updates one or more branches, tags, or other references in a remote
+repository from your local repository, and sends all necessary data
+that isn't already on the remote.
+
+The simplest way to push is `git push <remote> <branch>`.
+`git push origin main` will push the local `main` branch to the `main`
+branch on the remote named `origin`.
+
+The `<repository>` argument defaults to the upstream for the current branch,
+or `origin` if there's no configured upstream.
+
+To decide which branches, tags, or other refs to push, Git uses
+(in order of precedence):
+
+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
+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`.
+
+`git push` may fail if you haven't set an upstream for the current branch,
+depending on what `push.default` is set to.
+See the <<UPSTREAM-BRANCHES,UPSTREAM BRANCHES>> section below for more
+on how to set and use upstreams.
 
 You can make interesting things happen to a repository
 every time you push into it, by setting up 'hooks' there.  See
 documentation for linkgit:git-receive-pack[1].
 
-When the command line does not specify where to push with the
-`<repository>` argument, `branch.*.remote` configuration for the
-current branch is consulted to determine where to push.  If the
-configuration is missing, it defaults to 'origin'.
-
-When the command line does not specify what to push with `<refspec>...`
-arguments or `--all`, `--mirror`, `--tags` options, the command finds
-the default `<refspec>` by consulting `remote.*.push` configuration,
-and if it is not found, honors `push.default` configuration to decide
-what to push (See linkgit:git-config[1] for the meaning of `push.default`).
-
-When neither the command-line nor the configuration specifies what to
-push, the default behavior is used, which corresponds to the `simple`
-value for `push.default`: the current branch is pushed to the
-corresponding upstream branch, but as a safety measure, the push is
-aborted if the upstream branch does not have the same name as the
-local one.
-
 
 OPTIONS[[OPTIONS]]
 ------------------
@@ -710,6 +715,7 @@
 
 include::transfer-data-leaks.adoc[]
 
+[[CONFIGURATION]]
 CONFIGURATION
 -------------
 
diff --git a/Documentation/git-rev-parse.adoc b/Documentation/git-rev-parse.adoc
index cc32b4b..5398691 100644
--- a/Documentation/git-rev-parse.adoc
+++ b/Documentation/git-rev-parse.adoc
@@ -174,13 +174,13 @@
 
 	Allow oids to be input from any object format that the current
 	repository supports.
-
-	Specifying "sha1" translates if necessary and returns a sha1 oid.
-
-	Specifying "sha256" translates if necessary and returns a sha256 oid.
-
-	Specifying "storage" translates if necessary and returns an oid in
-	encoded in the storage hash algorithm.
++
+Specifying "sha1" translates if necessary and returns a sha1 oid.
++
+Specifying "sha256" translates if necessary and returns a sha256 oid.
++
+Specifying "storage" translates if necessary and returns an oid in
+encoded in the storage hash algorithm.
 
 Options for Objects
 ~~~~~~~~~~~~~~~~~~~
@@ -324,11 +324,12 @@
 	path of the current directory relative to the top-level
 	directory.
 
---show-object-format[=(storage|input|output)]::
-	Show the object format (hash algorithm) used for the repository
-	for storage inside the `.git` directory, input, or output. For
-	input, multiple algorithms may be printed, space-separated.
-	If not specified, the default is "storage".
+--show-object-format[=(storage|input|output|compat)]::
+	Show the object format (hash algorithm) used for the repository for storage
+	inside the `.git` directory, input, output, or compatibility. For input,
+	multiple algorithms may be printed, space-separated. If `compat` is
+	requested and no compatibility algorithm is enabled, prints an empty line. If
+	not specified, the default is "storage".
 
 --show-ref-format::
 	Show the reference storage format used for the repository.
diff --git a/Documentation/git-shortlog.adoc b/Documentation/git-shortlog.adoc
index d8ab38d..aa92800 100644
--- a/Documentation/git-shortlog.adoc
+++ b/Documentation/git-shortlog.adoc
@@ -44,8 +44,8 @@
 	describe each commit.  '<format>' can be any string accepted
 	by the `--format` option of 'git log', such as '* [%h] %s'.
 	(See the "PRETTY FORMATS" section of linkgit:git-log[1].)
-
-	Each pretty-printed commit will be rewrapped before it is shown.
++
+Each pretty-printed commit will be rewrapped before it is shown.
 
 --date=<format>::
 	Show dates formatted according to the given date string. (See
diff --git a/Documentation/git-sparse-checkout.adoc b/Documentation/git-sparse-checkout.adoc
index 529a8ed..0d1618f 100644
--- a/Documentation/git-sparse-checkout.adoc
+++ b/Documentation/git-sparse-checkout.adoc
@@ -9,7 +9,7 @@
 SYNOPSIS
 --------
 [verse]
-'git sparse-checkout' (init | list | set | add | reapply | disable | check-rules) [<options>]
+'git sparse-checkout' (init | list | set | add | reapply | disable | check-rules | clean) [<options>]
 
 
 DESCRIPTION
@@ -111,6 +111,37 @@
 to change which sparsity mode you are using without needing to also respecify
 all sparsity paths.
 
+'clean'::
+	Opportunistically remove files outside of the sparse-checkout
+	definition. This command requires cone mode to use recursive
+	directory matches to determine which files should be removed. A
+	file is considered for removal if it is contained within a tracked
+	directory that is outside of the sparse-checkout definition.
++
+Some special cases, such as merge conflicts or modified files outside of
+the sparse-checkout definition could lead to keeping files that would
+otherwise be removed. Resolve conflicts, stage modifications, and use
+`git sparse-checkout reapply` in conjunction with `git sparse-checkout
+clean` to resolve these cases.
++
+This command can be used to be sure the sparse index works efficiently,
+though it does not require enabling the sparse index feature via the
+`index.sparse=true` configuration.
++
+To prevent accidental deletion of worktree files, the `clean` subcommand
+will not delete any files without the `-f` or `--force` option, unless
+the `clean.requireForce` config option is set to `false`.
++
+The `--dry-run` option will list the directories that would be removed
+without deleting them. Running in this mode can be helpful to predict the
+behavior of the clean comand or to determine which kinds of files are left
+in the sparse directories.
++
+The `--verbose` option will list every file within the directories that
+are considered for removal. This option is helpful to determine if those
+files are actually important or perhaps to explain why the directory is
+still present despite the current sparse-checkout.
+
 'disable'::
 	Disable the `core.sparseCheckout` config setting, and restore the
 	working directory to include all files.
@@ -264,34 +295,50 @@
     inconsistent.
 
   * It has edge cases where the "right" behavior is unclear.  Two examples:
-
-    First, two users are in a subdirectory, and the first runs
-       git sparse-checkout set '/toplevel-dir/*.c'
-    while the second runs
-       git sparse-checkout set relative-dir
-    Should those arguments be transliterated into
-       current/subdirectory/toplevel-dir/*.c
-    and
-       current/subdirectory/relative-dir
-    before inserting into the sparse-checkout file?  The user who typed
-    the first command is probably aware that arguments to set/add are
-    supposed to be patterns in non-cone mode, and probably would not be
-    happy with such a transliteration.  However, many gitignore-style
-    patterns are just paths, which might be what the user who typed the
-    second command was thinking, and they'd be upset if their argument
-    wasn't transliterated.
-
-    Second, what should bash-completion complete on for set/add commands
-    for non-cone users?  If it suggests paths, is it exacerbating the
-    problem above?  Also, if it suggests paths, what if the user has a
-    file or directory that begins with either a '!' or '#' or has a '*',
-    '\', '?', '[', or ']' in its name?  And if it suggests paths, will
-    it complete "/pro" to "/proc" (in the root filesystem) rather than to
-    "/progress.txt" in the current directory?  (Note that users are
-    likely to want to start paths with a leading '/' in non-cone mode,
-    for the same reason that .gitignore files often have one.)
-    Completing on files or directories might give nasty surprises in
-    all these cases.
++
+First, two users are in a subdirectory, and the first runs
++
+----
+git sparse-checkout set '/toplevel-dir/*.c'
+----
++
+while the second runs
++
+----
+git sparse-checkout set relative-dir
+----
++
+Should those arguments be transliterated into
++
+----
+current/subdirectory/toplevel-dir/*.c
+----
++
+and
++
+----
+current/subdirectory/relative-dir
+----
++
+before inserting into the sparse-checkout file?  The user who typed
+the first command is probably aware that arguments to set/add are
+supposed to be patterns in non-cone mode, and probably would not be
+happy with such a transliteration.  However, many gitignore-style
+patterns are just paths, which might be what the user who typed the
+second command was thinking, and they'd be upset if their argument
+wasn't transliterated.
++
+Second, what should bash-completion complete on for set/add commands
+for non-cone users?  If it suggests paths, is it exacerbating the
+problem above?  Also, if it suggests paths, what if the user has a
+file or directory that begins with either a '!' or '#' or has a '*',
+'\', '?', '[', or ']' in its name?  And if it suggests paths, will
+it complete "/pro" to "/proc" (in the root filesystem) rather than to
+"/progress.txt" in the current directory?  (Note that users are
+likely to want to start paths with a leading '/' in non-cone mode,
+for the same reason that .gitignore files often have one.)
+Completing on files or directories might give nasty surprises in
+all these cases.
 
   * The excessive flexibility made other extensions essentially
     impractical.  `--sparse-index` is likely impossible in non-cone
diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc
index e2300a1..235d57d 100644
--- a/Documentation/git-stash.adoc
+++ b/Documentation/git-stash.adoc
@@ -7,24 +7,24 @@
 
 SYNOPSIS
 --------
-[verse]
-'git stash' list [<log-options>]
-'git stash' show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]
-'git stash' drop [-q | --quiet] [<stash>]
-'git stash' pop [--index] [-q | --quiet] [<stash>]
-'git stash' apply [--index] [-q | --quiet] [<stash>]
-'git stash' branch <branchname> [<stash>]
-'git stash' [push [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]
+[synopsis]
+git stash list [<log-options>]
+git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]
+git stash drop [-q | --quiet] [<stash>]
+git stash pop [--index] [-q | --quiet] [<stash>]
+git stash apply [--index] [-q | --quiet] [<stash>]
+git stash branch <branchname> [<stash>]
+git stash [push [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]
 	     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]
 	     [--pathspec-from-file=<file> [--pathspec-file-nul]]
 	     [--] [<pathspec>...]]
-'git stash' save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]
-	     [-u | --include-untracked] [-a | --all] [<message>]
-'git stash' clear
-'git stash' create [<message>]
-'git stash' store [(-m | --message) <message>] [-q | --quiet] <commit>
-'git stash' export (--print | --to-ref <ref>) [<stash>...]
-'git stash' import <commit>
+git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]
+           [-u | --include-untracked] [-a | --all] [<message>]
+git stash clear
+git stash create [<message>]
+git stash store [(-m | --message) <message>] [-q | --quiet] <commit>
+git stash export (--print | --to-ref <ref>) [<stash>...]
+git stash import <commit>
 
 DESCRIPTION
 -----------
@@ -38,7 +38,7 @@
 `git stash list`, inspected with `git stash show`, and restored
 (potentially on top of a different commit) with `git stash apply`.
 Calling `git stash` without any arguments is equivalent to `git stash push`.
-A stash is by default listed as "WIP on 'branchname' ...", but
+A stash is by default listed as "WIP on '<branchname>' ...", but
 you can give a more descriptive message on the command line when
 you create one.
 
@@ -47,16 +47,16 @@
 the usual reflog syntax (e.g. `stash@{0}` is the most recently
 created stash, `stash@{1}` is the one before it, `stash@{2.hours.ago}`
 is also possible). Stashes may also be referenced by specifying just the
-stash index (e.g. the integer `n` is equivalent to `stash@{n}`).
+stash index (e.g. the integer `<n>` is equivalent to `stash@{<n>}`).
 
 COMMANDS
 --------
 
-push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [(-m|--message) <message>] [--pathspec-from-file=<file> [--pathspec-file-nul]] [--] [<pathspec>...]::
+`push [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-u | --include-untracked] [ -a | --all] [-q | --quiet] [(-m|--message) <message>] [--pathspec-from-file=<file> [--pathspec-file-nul]] [--] [<pathspec>...]`::
 
 	Save your local modifications to a new 'stash entry' and roll them
-	back to HEAD (in the working tree and in the index).
-	The <message> part is optional and gives
+	back to `HEAD` (in the working tree and in the index).
+	The _<message>_ part is optional and gives
 	the description along with the stashed state.
 +
 For quickly making a snapshot, you can omit "push".  In this mode,
@@ -65,14 +65,14 @@
 are `stash -p` which acts as alias for `stash push -p` and pathspec elements,
 which are allowed after a double hyphen `--` for disambiguation.
 
-save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::
+`save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-u | --include-untracked] [-a | --all] [-q | --quiet] [<message>]`::
 
 	This option is deprecated in favour of 'git stash push'.  It
 	differs from "stash push" in that it cannot take pathspec.
 	Instead, all non-option arguments are concatenated to form the stash
 	message.
 
-list [<log-options>]::
+`list [<log-options>]`::
 
 	List the stash entries that you currently have.  Each 'stash entry' is
 	listed with its name (e.g. `stash@{0}` is the latest entry, `stash@{1}` is
@@ -88,7 +88,7 @@
 The command takes options applicable to the 'git log'
 command to control what is shown and how. See linkgit:git-log[1].
 
-show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]::
+`show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]`::
 
 	Show the changes recorded in the stash entry as a diff between the
 	stashed contents and the commit back when the stash entry was first
@@ -96,12 +96,12 @@
 	By default, the command shows the diffstat, but it will accept any
 	format known to 'git diff' (e.g., `git stash show -p stash@{1}`
 	to view the second most recent entry in patch form).
-	If no `<diff-option>` is provided, the default behavior will be given
+	If no _<diff-option>_ is provided, the default behavior will be given
 	by the `stash.showStat`, and `stash.showPatch` config variables. You
 	can also use `stash.showIncludeUntracked` to set whether
 	`--include-untracked` is enabled by default.
 
-pop [--index] [-q|--quiet] [<stash>]::
+`pop [--index] [-q | --quiet] [<stash>]`::
 
 	Remove a single stashed state from the stash list and apply it
 	on top of the current working tree state, i.e., do the inverse
@@ -112,19 +112,19 @@
 removed from the stash list. You need to resolve the conflicts by hand
 and call `git stash drop` manually afterwards.
 
-apply [--index] [-q|--quiet] [<stash>]::
+`apply [--index] [-q | --quiet] [<stash>]`::
 
 	Like `pop`, but do not remove the state from the stash list. Unlike `pop`,
 	`<stash>` may be any commit that looks like a commit created by
 	`stash push` or `stash create`.
 
-branch <branchname> [<stash>]::
+`branch <branchname> [<stash>]`::
 
-	Creates and checks out a new branch named `<branchname>` starting from
-	the commit at which the `<stash>` was originally created, applies the
-	changes recorded in `<stash>` to the new working tree and index.
-	If that succeeds, and `<stash>` is a reference of the form
-	`stash@{<revision>}`, it then drops the `<stash>`.
+	Creates and checks out a new branch named _<branchname>_ starting from
+	the commit at which the _<stash>_ was originally created, applies the
+	changes recorded in _<stash>_ to the new working tree and index.
+	If that succeeds, and _<stash>_ is a reference of the form
+	`stash@{<revision>}`, it then drops the _<stash>_.
 +
 This is useful if the branch on which you ran `git stash push` has
 changed enough that `git stash apply` fails due to conflicts. Since
@@ -132,54 +132,51 @@
 time `git stash` was run, it restores the originally stashed state
 with no conflicts.
 
-clear::
+`clear`::
 	Remove all the stash entries. Note that those entries will then
 	be subject to pruning, and may be impossible to recover (see
-	'Examples' below for a possible strategy).
+	'EXAMPLES' below for a possible strategy).
 
-drop [-q|--quiet] [<stash>]::
-
+`drop [-q | --quiet] [<stash>]`::
 	Remove a single stash entry from the list of stash entries.
 
-create::
-
+`create`::
 	Create a stash entry (which is a regular commit object) and
 	return its object name, without storing it anywhere in the ref
 	namespace.
 	This is intended to be useful for scripts.  It is probably not
 	the command you want to use; see "push" above.
 
-store::
+`store`::
 
 	Store a given stash created via 'git stash create' (which is a
 	dangling merge commit) in the stash ref, updating the stash
 	reflog.  This is intended to be useful for scripts.  It is
 	probably not the command you want to use; see "push" above.
 
-export ( --print | --to-ref <ref> ) [<stash>...]::
+`export ( --print | --to-ref <ref> ) [<stash>...]`::
 
 	Export the specified stashes, or all of them if none are specified, to
 	a chain of commits which can be transferred using the normal fetch and
 	push mechanisms, then imported using the `import` subcommand.
 
-import <commit>::
-
+`import <commit>`::
 	Import the specified stashes from the specified commit, which must have been
 	created by `export`, and add them to the list of stashes.  To replace the
 	existing stashes, use `clear` first.
 
 OPTIONS
 -------
--a::
---all::
+`-a`::
+`--all`::
 	This option is only valid for `push` and `save` commands.
 +
 All ignored and untracked files are also stashed and then cleaned
 up with `git clean`.
 
--u::
---include-untracked::
---no-include-untracked::
+`-u`::
+`--include-untracked`::
+`--no-include-untracked`::
 	When used with the `push` and `save` commands,
 	all untracked files are also stashed and then cleaned up with
 	`git clean`.
@@ -187,12 +184,12 @@
 When used with the `show` command, show the untracked files in the stash
 entry as part of the diff.
 
---only-untracked::
+`--only-untracked`::
 	This option is only valid for the `show` command.
 +
 Show only the untracked files in the stash entry as part of the diff.
 
---index::
+`--index`::
 	This option is only valid for `pop` and `apply` commands.
 +
 Tries to reinstate not only the working tree's changes, but also
@@ -200,15 +197,15 @@
 (which are stored in the index, where you therefore can no longer
 apply the changes as they were originally).
 
--k::
---keep-index::
---no-keep-index::
+`-k`::
+`--keep-index`::
+`--no-keep-index`::
 	This option is only valid for `push` and `save` commands.
 +
 All changes already added to the index are left intact.
 
--p::
---patch::
+`-p`::
+`--patch`::
 	This option is only valid for `push` and `save` commands.
 +
 Interactively select hunks from the diff between HEAD and the
@@ -224,8 +221,8 @@
 
 include::diff-context-options.adoc[]
 
--S::
---staged::
+`-S`::
+`--staged`::
 	This option is only valid for `push` and `save` commands.
 +
 Stash only the changes that are currently staged. This is similar to
@@ -234,49 +231,49 @@
 +
 The `--patch` option has priority over this one.
 
---pathspec-from-file=<file>::
+`--pathspec-from-file=<file>`::
 	This option is only valid for `push` command.
 +
-Pathspec is passed in `<file>` instead of commandline args. If
-`<file>` is exactly `-` then standard input is used. Pathspec
+Pathspec is passed in _<file>_ instead of commandline args. If
+_<file>_ is exactly `-` then standard input is used. Pathspec
 elements are separated by LF or CR/LF. Pathspec elements can be
 quoted as explained for the configuration variable `core.quotePath`
 (see linkgit:git-config[1]). See also `--pathspec-file-nul` and
 global `--literal-pathspecs`.
 
---pathspec-file-nul::
+`--pathspec-file-nul`::
 	This option is only valid for `push` command.
 +
 Only meaningful with `--pathspec-from-file`. Pathspec elements are
 separated with NUL character and all other characters are taken
 literally (including newlines and quotes).
 
--q::
---quiet::
+`-q`::
+`--quiet`::
 	This option is only valid for `apply`, `drop`, `pop`, `push`,
 	`save`, `store` commands.
 +
 Quiet, suppress feedback messages.
 
---print::
+`--print`::
 	This option is only valid for the `export` command.
 +
 Create the chain of commits representing the exported stashes without
 storing it anywhere in the ref namespace and print the object ID to
 standard output.  This is designed for scripts.
 
---to-ref::
+`--to-ref`::
 	This option is only valid for the `export` command.
 +
 Create the chain of commits representing the exported stashes and store
 it to the specified ref.
 
-\--::
+`--`::
 	This option is only valid for `push` command.
 +
 Separates pathspec from options for disambiguation purposes.
 
-<pathspec>...::
+`<pathspec>...`::
 	This option is only valid for `push` command.
 +
 The new stash entry records the modified states only for the files
@@ -286,11 +283,11 @@
 +
 For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
 
-<stash>::
+_<stash>_::
 	This option is only valid for `apply`, `branch`, `drop`, `pop`,
 	`show`, and `export` commands.
 +
-A reference of the form `stash@{<revision>}`. When no `<stash>` is
+A reference of the form `stash@{<revision>}`. When no _<stash>_ is
 given, the latest stash is assumed (that is, `stash@{0}`).
 
 DISCUSSION
@@ -419,6 +416,7 @@
 
 include::includes/cmd-config-section-all.adoc[]
 
+:git-stash: 1
 include::config/stash.adoc[]
 
 
diff --git a/Documentation/git-tag.adoc b/Documentation/git-tag.adoc
index a4b1c0e..cea3202 100644
--- a/Documentation/git-tag.adoc
+++ b/Documentation/git-tag.adoc
@@ -3,26 +3,26 @@
 
 NAME
 ----
-git-tag - Create, list, delete or verify a tag object signed with GPG
+git-tag - Create, list, delete or verify tags
 
 
 SYNOPSIS
 --------
-[verse]
-'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]
+[synopsis]
+git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]
 	[(--trailer <token>[(=|:)<value>])...]
 	<tagname> [<commit> | <object>]
-'git tag' -d <tagname>...
-'git tag' [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]
+git tag -d <tagname>...
+git tag [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]
 	[--points-at <object>] [--column[=<options>] | --no-column]
 	[--create-reflog] [--sort=<key>] [--format=<format>]
 	[--merged <commit>] [--no-merged <commit>] [<pattern>...]
-'git tag' -v [--format=<format>] <tagname>...
+git tag -v [--format=<format>] <tagname>...
 
 DESCRIPTION
 -----------
 
-Add a tag reference in `refs/tags/`, unless `-d/-l/-v` is given
+Add a tag reference in `refs/tags/`, unless `-d`/`-l`/`-v` is given
 to delete, list or verify tags.
 
 Unless `-f` is given, the named tag must not yet exist.
@@ -38,15 +38,17 @@
 Otherwise, a tag reference that points directly at the given object
 (i.e., a lightweight tag) is created.
 
-A GnuPG signed tag object will be created when `-s` or `-u
-<key-id>` is used.  When `-u <key-id>` is not used, the
-committer identity for the current user is used to find the
-GnuPG key for signing. 	The configuration variable `gpg.program`
-is used to specify custom GnuPG binary.
+A cryptographically signed tag object will be created when `-s` or
+`-u <key-id>` is used. The signing backend (GPG, X.509, SSH, etc.) is
+controlled by the `gpg.format` configuration variable, defaulting to
+OpenPGP. When `-u <key-id>` is not used, the committer identity for
+the current user is used to find the key for signing. The
+configuration variable `gpg.program` is used to specify a custom
+signing binary.
 
 Tag objects (created with `-a`, `-s`, or `-u`) are called "annotated"
 tags; they contain a creation date, the tagger name and e-mail, a
-tagging message, and an optional GnuPG signature. Whereas a
+tagging message, and an optional cryptographic signature. Whereas a
 "lightweight" tag is simply a name for an object (usually a commit
 object).
 
@@ -58,129 +60,134 @@
 
 OPTIONS
 -------
--a::
---annotate::
+`-a`::
+`--annotate`::
 	Make an unsigned, annotated tag object
 
--s::
---sign::
-	Make a GPG-signed tag, using the default e-mail address's key.
-	The default behavior of tag GPG-signing is controlled by `tag.gpgSign`
-	configuration variable if it exists, or disabled otherwise.
-	See linkgit:git-config[1].
+`-s`::
+`--sign`::
+	Make a cryptographically signed tag, using the default signing
+	key. The signing backend used depends on the `gpg.format`
+	configuration variable. The default key is determined by the
+	backend. For GPG, it's based on the committer's email address,
+	while for SSH it may be a specific key file or agent
+	identity. See linkgit:git-config[1].
 
---no-sign::
+`--no-sign`::
 	Override `tag.gpgSign` configuration variable that is
 	set to force each and every tag to be signed.
 
--u <key-id>::
---local-user=<key-id>::
-	Make a GPG-signed tag, using the given key.
+`-u <key-id>`::
+`--local-user=<key-id>`::
+	Make a cryptographically signed tag using the given key. The
+	format of the <key-id> and the backend used depend on the
+	`gpg.format` configuration variable. See
+	linkgit:git-config[1].
 
--f::
---force::
+`-f`::
+`--force`::
 	Replace an existing tag with the given name (instead of failing)
 
--d::
---delete::
+`-d`::
+`--delete`::
 	Delete existing tags with the given names.
 
--v::
---verify::
-	Verify the GPG signature of the given tag names.
+`-v`::
+`--verify`::
+	Verify the cryptographic signature of the given tags.
 
--n<num>::
-	<num> specifies how many lines from the annotation, if any,
-	are printed when using -l. Implies `--list`.
+`-n<num>`::
+	_<num>_ specifies how many lines from the annotation, if any,
+	are printed when using `-l`. Implies `--list`.
 +
 The default is not to print any annotation lines.
 If no number is given to `-n`, only the first line is printed.
 If the tag is not annotated, the commit message is displayed instead.
 
--l::
---list::
+`-l`::
+`--list`::
 	List tags. With optional `<pattern>...`, e.g. `git tag --list
 	'v-*'`, list only the tags that match the pattern(s).
 +
-Running "git tag" without arguments also lists all tags. The pattern
-is a shell wildcard (i.e., matched using fnmatch(3)). Multiple
+Running `git tag` without arguments also lists all tags. The pattern
+is a shell wildcard (i.e., matched using `fnmatch`(3)). Multiple
 patterns may be given; if any of them matches, the tag is shown.
 +
 This option is implicitly supplied if any other list-like option such
 as `--contains` is provided. See the documentation for each of those
 options for details.
 
---sort=<key>::
+`--sort=<key>`::
 	Sort based on the key given.  Prefix `-` to sort in
-	descending order of the value. You may use the --sort=<key> option
-	multiple times, in which case the last key becomes the primary
-	key. Also supports "version:refname" or "v:refname" (tag
-	names are treated as versions). The "version:refname" sort
-	order can also be affected by the "versionsort.suffix"
+	descending order of the value. You may use the `--sort=<key>` option
+	multiple times, in which case the last _<key>_ becomes the primary
+	key. Also supports "`version:refname`" or "`v:refname`" (tag
+	names are treated as versions). The "`version:refname`" sort
+	order can also be affected by the "`versionsort.suffix`"
 	configuration variable.
 	The keys supported are the same as those in `git for-each-ref`.
 	Sort order defaults to the value configured for the `tag.sort`
 	variable if it exists, or lexicographic order otherwise. See
 	linkgit:git-config[1].
 
---color[=<when>]::
+`--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).
 
--i::
---ignore-case::
+`-i`::
+`--ignore-case`::
 	Sorting and filtering tags are case insensitive.
 
---omit-empty::
+`--omit-empty`::
 	Do not print a newline after formatted refs where the format expands
 	to the empty string.
 
---column[=<options>]::
---no-column::
+`--column[=<options>]`::
+`--no-column`::
 	Display tag listing in columns. See configuration variable
 	`column.tag` for option syntax. `--column` and `--no-column`
-	without options are equivalent to 'always' and 'never' respectively.
+	without options are equivalent to `always` and `never` respectively.
 +
 This option is only applicable when listing tags without annotation lines.
 
---contains [<commit>]::
-	Only list tags which contain the specified commit (HEAD if not
+`--contains [<commit>]`::
+	Only list tags which contain _<commit>_ (`HEAD` if not
 	specified). Implies `--list`.
 
---no-contains [<commit>]::
-	Only list tags which don't contain the specified commit (HEAD if
+`--no-contains [<commit>]`::
+	Only list tags which don't contain _<commit>_ (`HEAD` if
 	not specified). Implies `--list`.
 
---merged [<commit>]::
-	Only list tags whose commits are reachable from the specified
-	commit (`HEAD` if not specified).
+`--merged [<commit>]`::
+	Only list tags whose commits are reachable from
+	_<commit>_ (`HEAD` if not specified).
 
---no-merged [<commit>]::
-	Only list tags whose commits are not reachable from the specified
-	commit (`HEAD` if not specified).
+`--no-merged [<commit>]`::
+	Only list tags whose commits are not reachable from
+	_<commit>_ (`HEAD` if not specified).
 
---points-at <object>::
-	Only list tags of the given object (HEAD if not
+`--points-at [<object>]`::
+	Only list tags of _<object>_ (`HEAD` if not
 	specified). Implies `--list`.
 
--m <msg>::
---message=<msg>::
-	Use the given tag message (instead of prompting).
+`-m <msg>`::
+`--message=<msg>`::
+	Use _<msg>_ (instead of prompting).
 	If multiple `-m` options are given, their values are
 	concatenated as separate paragraphs.
 	Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`
 	is given.
 
--F <file>::
---file=<file>::
-	Take the tag message from the given file.  Use '-' to
+`-F <file>`::
+`--file=<file>`::
+	Take the tag message from _<file>_.  Use `-` to
 	read the message from the standard input.
 	Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`
 	is given.
 
---trailer <token>[(=|:)<value>]::
-	Specify a (<token>, <value>) pair that should be applied as a
+`--trailer <token>[(=|:)<value>]`::
+	Specify a (_<token>_, _<value>_) pair that should be applied as a
 	trailer. (e.g. `git tag --trailer "Custom-Key: value"`
 	will add a "Custom-Key" trailer to the tag message.)
 	The `trailer.*` configuration variables
@@ -190,58 +197,68 @@
 	The trailers can be extracted in `git tag --list`, using
 	`--format="%(trailers)"` placeholder.
 
--e::
---edit::
-	The message taken from file with `-F` and command line with
-	`-m` are usually used as the tag message unmodified.
-	This option lets you further edit the message taken from these sources.
+`-e`::
+`--edit`::
+	Let further edit the message taken from file with `-F` and command line with
+	`-m`.
 
---cleanup=<mode>::
-	This option sets how the tag message is cleaned up.
-	The  '<mode>' can be one of 'verbatim', 'whitespace' and 'strip'.  The
-	'strip' mode is default. The 'verbatim' mode does not change message at
-	all, 'whitespace' removes just leading/trailing whitespace lines and
-	'strip' removes both whitespace and commentary.
+`--cleanup=<mode>`::
+	Set how the tag message is cleaned up.
+	The  _<mode>_ can be one of `verbatim`, `whitespace` and `strip`.  The
+	`strip` mode is default. The `verbatim` mode does not change message at
+	all, `whitespace` removes just leading/trailing whitespace lines and
+	`strip` removes both whitespace and commentary.
 
---create-reflog::
+`--create-reflog`::
 	Create a reflog for the tag. To globally enable reflogs for tags, see
 	`core.logAllRefUpdates` in linkgit:git-config[1].
 	The negated form `--no-create-reflog` only overrides an earlier
 	`--create-reflog`, but currently does not negate the setting of
 	`core.logAllRefUpdates`.
 
---format=<format>::
+`--format=<format>`::
 	A string that interpolates `%(fieldname)` from a tag ref being shown
 	and the object it points at.  The format is the same as
 	that of linkgit:git-for-each-ref[1].  When unspecified,
 	defaults to `%(refname:strip=2)`.
 
-<tagname>::
+_<tagname>_::
 	The name of the tag to create, delete, or describe.
 	The new tag name must pass all checks defined by
 	linkgit:git-check-ref-format[1].  Some of these checks
 	may restrict the characters allowed in a tag name.
 
-<commit>::
-<object>::
+_<commit>_::
+_<object>_::
 	The object that the new tag will refer to, usually a commit.
-	Defaults to HEAD.
+	Defaults to `HEAD`.
 
 CONFIGURATION
 -------------
-By default, 'git tag' in sign-with-default mode (-s) will use your
+By default, `git tag` in sign-with-default mode (`-s`) will use your
 committer identity (of the form `Your Name <your@email.address>`) to
 find a key.  If you want to use a different default key, you can specify
 it in the repository configuration as follows:
 
 -------------------------------------
 [user]
-    signingKey = <gpg-key-id>
+    signingKey = <key-id>
 -------------------------------------
 
+The signing backend can be chosen via the `gpg.format` configuration
+variable, which defaults to `openpgp`. See linkgit:git-config[1]
+for a list of other supported formats.
+
+The path to the program used for each signing backend can be specified
+with the `gpg.<format>.program` configuration variable. For the
+`openpgp` backend, `gpg.program` can be used as a synonym for
+`gpg.openpgp.program`. See linkgit:git-config[1] for details.
+
 `pager.tag` is only respected when listing tags, i.e., when `-l` is
 used or implied. The default is to use a pager.
-See linkgit:git-config[1].
+
+See linkgit:git-config[1] for more details and other configuration
+variables.
 
 DISCUSSION
 ----------
@@ -252,7 +269,7 @@
 What should you do when you tag a wrong commit and you would
 want to re-tag?
 
-If you never pushed anything out, just re-tag it. Use "-f" to
+If you never pushed anything out, just re-tag it. Use `-f` to
 replace the old one. And you're done.
 
 But if you have pushed things out (or others could just read
@@ -268,12 +285,12 @@
 
 . The insane thing.
   You really want to call the new version "X" too, 'even though'
-  others have already seen the old one. So just use 'git tag -f'
+  others have already seen the old one. So just use `git tag -f`
   again, as if you hadn't already published the old one.
 
 However, Git does *not* (and it should not) change tags behind
 users back. So if somebody already got the old tag, doing a
-'git pull' on your tree shouldn't just make them overwrite the old
+`git pull` on your tree shouldn't just make them overwrite the old
 one.
 
 If somebody got a release tag from you, you cannot just change
@@ -325,7 +342,7 @@
 
 Often, "please pull" messages on the mailing list just provide
 two pieces of information: a repo URL and a branch name; this
-is designed to be easily cut&pasted at the end of a 'git fetch'
+is designed to be easily cut&pasted at the end of a `git fetch`
 command line:
 
 ------------
@@ -403,6 +420,14 @@
 	user in an editor session will be available in this file, but
 	may be overwritten by the next invocation of `git tag`.
 
+CONFIGURATION
+-------------
+
+include::includes/cmd-config-section-all.adoc[]
+
+:git-tag: 1
+include::config/tag.adoc[]
+
 NOTES
 -----
 
diff --git a/Documentation/git-worktree.adoc b/Documentation/git-worktree.adoc
index 389e669..f272f79 100644
--- a/Documentation/git-worktree.adoc
+++ b/Documentation/git-worktree.adoc
@@ -8,16 +8,16 @@
 
 SYNOPSIS
 --------
-[verse]
-'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason <string>]]
-		   [--orphan] [(-b | -B) <new-branch>] <path> [<commit-ish>]
-'git worktree list' [-v | --porcelain [-z]]
-'git worktree lock' [--reason <string>] <worktree>
-'git worktree move' <worktree> <new-path>
-'git worktree prune' [-n] [-v] [--expire <expire>]
-'git worktree remove' [-f] <worktree>
-'git worktree repair' [<path>...]
-'git worktree unlock' <worktree>
+[synopsis]
+git worktree add [-f] [--detach] [--checkout] [--lock [--reason <string>]]
+		 [--orphan] [(-b | -B) <new-branch>] <path> [<commit-ish>]
+git worktree list [-v | --porcelain [-z]]
+git worktree lock [--reason <string>] <worktree>
+git worktree move <worktree> <new-path>
+git worktree prune [-n] [-v] [--expire <expire>]
+git worktree remove [-f] <worktree>
+git worktree repair [<path>...]
+git worktree unlock <worktree>
 
 DESCRIPTION
 -----------
@@ -37,7 +37,7 @@
 remove it with `git worktree remove`.
 
 In its simplest form, `git worktree add <path>` automatically creates a
-new branch whose name is the final component of `<path>`, which is
+new branch whose name is the final component of _<path>_, which is
 convenient if you plan to work on a new topic. For instance, `git
 worktree add ../hotfix` creates new branch `hotfix` and checks it out at
 path `../hotfix`. To instead work on an existing branch in a new worktree,
@@ -63,16 +63,16 @@
 
 COMMANDS
 --------
-add <path> [<commit-ish>]::
+`add <path> [<commit-ish>]`::
 
-Create a worktree at `<path>` and checkout `<commit-ish>` into it. The new worktree
+Create a worktree at _<path>_ and checkout _<commit-ish>_ into it. The new worktree
 is linked to the current repository, sharing everything except per-worktree
-files such as `HEAD`, `index`, etc. As a convenience, `<commit-ish>` may
+files such as `HEAD`, `index`, etc. As a convenience, _<commit-ish>_ may
 be a bare "`-`", which is synonymous with `@{-1}`.
 +
-If `<commit-ish>` is a branch name (call it `<branch>`) and is not found,
+If _<commit-ish>_ is a branch name (call it _<branch>_) and is not found,
 and neither `-b` nor `-B` nor `--detach` are used, but there does
-exist a tracking branch in exactly one remote (call it `<remote>`)
+exist a tracking branch in exactly one remote (call it _<remote>_)
 with a matching name, treat as equivalent to:
 +
 ------------
@@ -81,32 +81,32 @@
 +
 If the branch exists in multiple remotes and one of them is named by
 the `checkout.defaultRemote` configuration variable, we'll use that
-one for the purposes of disambiguation, even if the `<branch>` isn't
+one for the purposes of disambiguation, even if the _<branch>_ isn't
 unique across all remotes. Set it to
 e.g. `checkout.defaultRemote=origin` to always checkout remote
-branches from there if `<branch>` is ambiguous but exists on the
+branches from there if _<branch>_ is ambiguous but exists on the
 `origin` remote. See also `checkout.defaultRemote` in
 linkgit:git-config[1].
 +
-If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
+If _<commit-ish>_ is omitted and neither `-b` nor `-B` nor `--detach` used,
 then, as a convenience, the new worktree is associated with a branch (call
-it `<branch>`) named after `$(basename <path>)`.  If `<branch>` doesn't
+it _<branch>_) named after `$(basename <path>)`.  If _<branch>_ doesn't
 exist, a new branch based on `HEAD` is automatically created as if
-`-b <branch>` was given.  If `<branch>` does exist, it will be checked out
+`-b <branch>` was given.  If _<branch>_ does exist, it will be checked out
 in the new worktree, if it's not checked out anywhere else, otherwise the
 command will refuse to create the worktree (unless `--force` is used).
 +
-If `<commit-ish>` is omitted, neither `--detach`, or `--orphan` is
+If _<commit-ish>_ is omitted, neither `--detach`, or `--orphan` is
 used, and there are no valid local branches (or remote branches if
 `--guess-remote` is specified) then, as a convenience, the new worktree is
-associated with a new unborn branch named `<branch>` (after
+associated with a new unborn branch named _<branch>_ (after
 `$(basename <path>)` if neither `-b` or `-B` is used) as if `--orphan` was
 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`).
 
-list::
+`list`::
 
 List details of each worktree.  The main worktree is listed first,
 followed by each of the linked worktrees.  The output details include
@@ -115,32 +115,32 @@
 the worktree is locked, "prunable" if the worktree can be pruned by the
 `prune` command.
 
-lock::
+`lock`::
 
 If a worktree is on a portable device or network share which is not always
 mounted, lock it to prevent its administrative files from being pruned
 automatically. This also prevents it from being moved or deleted.
 Optionally, specify a reason for the lock with `--reason`.
 
-move::
+`move`::
 
 Move a worktree to a new location. Note that the main worktree or linked
 worktrees containing submodules cannot be moved with this command. (The
 `git worktree repair` command, however, can reestablish the connection
 with linked worktrees if you move the main worktree manually.)
 
-prune::
+`prune`::
 
 Prune worktree information in `$GIT_DIR/worktrees`.
 
-remove::
+`remove`::
 
 Remove a worktree. Only clean worktrees (no untracked files and no
 modification in tracked files) can be removed. Unclean worktrees or ones
 with submodules can be removed with `--force`. The main worktree cannot be
 removed.
 
-repair [<path>...]::
+`repair [<path>...]`::
 
 Repair worktree administrative files, if possible, if they have become
 corrupted or outdated due to external factors.
@@ -154,72 +154,72 @@
 using `git worktree move`, the main worktree (or bare repository) will be
 unable to locate it. Running `repair` within the recently-moved worktree
 will reestablish the connection. If multiple linked worktrees are moved,
-running `repair` from any worktree with each tree's new `<path>` as an
+running `repair` from any worktree with each tree's new _<path>_ as an
 argument, will reestablish the connection to all the specified paths.
 +
 If both the main worktree and linked worktrees have been moved or copied manually,
-then running `repair` in the main worktree and specifying the new `<path>`
+then running `repair` in the main worktree and specifying the new _<path>_
 of each linked worktree will reestablish all connections in both
 directions.
 
-unlock::
+`unlock`::
 
 Unlock a worktree, allowing it to be pruned, moved or deleted.
 
 OPTIONS
 -------
 
--f::
---force::
+`-f`::
+`--force`::
 	By default, `add` refuses to create a new worktree when
-	`<commit-ish>` is a branch name and is already checked out by
-	another worktree, or if `<path>` is already assigned to some
-	worktree but is missing (for instance, if `<path>` was deleted
+	_<commit-ish>_ is a branch name and is already checked out by
+	another worktree, or if _<path>_ is already assigned to some
+	worktree but is missing (for instance, if _<path>_ was deleted
 	manually). This option overrides these safeguards. To add a missing but
 	locked worktree path, specify `--force` twice.
 +
 `move` refuses to move a locked worktree unless `--force` is specified
 twice. If the destination is already assigned to some other worktree but is
-missing (for instance, if `<new-path>` was deleted manually), then `--force`
+missing (for instance, if _<new-path>_ was deleted manually), then `--force`
 allows the move to proceed; use `--force` twice if the destination is locked.
 +
 `remove` refuses to remove an unclean worktree unless `--force` is used.
 To remove a locked worktree, specify `--force` twice.
 
--b <new-branch>::
--B <new-branch>::
-	With `add`, create a new branch named `<new-branch>` starting at
-	`<commit-ish>`, and check out `<new-branch>` into the new worktree.
-	If `<commit-ish>` is omitted, it defaults to `HEAD`.
+`-b <new-branch>`::
+`-B <new-branch>`::
+	With `add`, create a new branch named _<new-branch>_ starting at
+	_<commit-ish>_, and check out _<new-branch>_ into the new worktree.
+	If _<commit-ish>_ is omitted, it defaults to `HEAD`.
 	By default, `-b` refuses to create a new branch if it already
-	exists. `-B` overrides this safeguard, resetting `<new-branch>` to
-	`<commit-ish>`.
+	exists. `-B` overrides this safeguard, resetting _<new-branch>_ to
+	_<commit-ish>_.
 
--d::
---detach::
+`-d`::
+`--detach`::
 	With `add`, detach `HEAD` in the new worktree. See "DETACHED HEAD"
 	in linkgit:git-checkout[1].
 
---checkout::
---no-checkout::
-	By default, `add` checks out `<commit-ish>`, however, `--no-checkout` can
+`--checkout`::
+`--no-checkout`::
+	By default, `add` checks out _<commit-ish>_, however, `--no-checkout` can
 	be used to suppress checkout in order to make customizations,
 	such as configuring sparse-checkout. See "Sparse checkout"
 	in linkgit:git-read-tree[1].
 
---guess-remote::
---no-guess-remote::
-	With `worktree add <path>`, without `<commit-ish>`, instead
+`--guess-remote`::
+`--no-guess-remote`::
+	With `worktree add <path>`, without _<commit-ish>_, instead
 	of creating a new branch from `HEAD`, if there exists a tracking
-	branch in exactly one remote matching the basename of `<path>`,
+	branch in exactly one remote matching the basename of _<path>_,
 	base the new branch on the remote-tracking branch, and mark
 	the remote-tracking branch as "upstream" from the new branch.
 +
 This can also be set up as the default behaviour by using the
 `worktree.guessRemote` config option.
 
---relative-paths::
---no-relative-paths::
+`--relative-paths`::
+`--no-relative-paths`::
 	Link worktrees using relative paths or absolute paths (default).
 	Overrides the `worktree.useRelativePaths` config option, see
 	linkgit:git-config[1].
@@ -227,60 +227,60 @@
 With `repair`, the linking files will be updated if there's an absolute/relative
 mismatch, even if the links are correct.
 
---track::
---no-track::
-	When creating a new branch, if `<commit-ish>` is a branch,
+`--track`::
+`--no-track`::
+	When creating a new branch, if _<commit-ish>_ is a branch,
 	mark it as "upstream" from the new branch.  This is the
-	default if `<commit-ish>` is a remote-tracking branch.  See
+	default if _<commit-ish>_ is a remote-tracking branch.  See
 	`--track` in linkgit:git-branch[1] for details.
 
---lock::
+`--lock`::
 	Keep the worktree locked after creation. This is the
 	equivalent of `git worktree lock` after `git worktree add`,
 	but without a race condition.
 
--n::
---dry-run::
+`-n`::
+`--dry-run`::
 	With `prune`, do not remove anything; just report what it would
 	remove.
 
---orphan::
+`--orphan`::
 	With `add`, make the new worktree and index empty, associating
-	the worktree with a new unborn branch named `<new-branch>`.
+	the worktree with a new unborn branch named _<new-branch>_.
 
---porcelain::
+`--porcelain`::
 	With `list`, output in an easy-to-parse format for scripts.
 	This format will remain stable across Git versions and regardless of user
 	configuration.  It is recommended to combine this with `-z`.
 	See below for details.
 
--z::
-	Terminate each line with a NUL rather than a newline when
+`-z`::
+	Terminate each line with a _NUL_ rather than a newline when
 	`--porcelain` is specified with `list`. This makes it possible
 	to parse the output when a worktree path contains a newline
 	character.
 
--q::
---quiet::
+`-q`::
+`--quiet`::
 	With `add`, suppress feedback messages.
 
--v::
---verbose::
+`-v`::
+`--verbose`::
 	With `prune`, report all removals.
 +
 With `list`, output additional information about worktrees (see below).
 
---expire <time>::
-	With `prune`, only expire unused worktrees older than `<time>`.
+`--expire <time>`::
+	With `prune`, only expire unused worktrees older than _<time>_.
 +
 With `list`, annotate missing worktrees as prunable if they are older than
-`<time>`.
+_<time>_.
 
---reason <string>::
+`--reason <string>`::
 	With `lock` or with `add --lock`, an explanation why the worktree
 	is locked.
 
-<worktree>::
+_<worktree>_::
 	Worktrees can be identified by path, either relative or absolute.
 +
 If the last path components in the worktree's path is unique among
@@ -522,6 +522,13 @@
 $ git worktree remove ../temp
 ------------
 
+CONFIGURATION
+-------------
+
+include::includes/cmd-config-section-all.adoc[]
+
+include::config/worktree.adoc[]
+
 BUGS
 ----
 Multiple checkout in general is still experimental, and the support
diff --git a/Documentation/gitcli.adoc b/Documentation/gitcli.adoc
index 1ea681b..ef2a0a3 100644
--- a/Documentation/gitcli.adoc
+++ b/Documentation/gitcli.adoc
@@ -216,6 +216,20 @@
 $ git describe --abbrev 10 HEAD  # NOT WHAT YOU MEANT
 ----------------------------
 
+
+Magic filename options
+~~~~~~~~~~~~~~~~~~~~~~
+Options that take a filename allow a prefix `:(optional)`. For example:
+
+----------------------------
+git commit -F :(optional)COMMIT_EDITMSG
+# if COMMIT_EDITMSG does not exist, equivalent to
+git commit
+----------------------------
+
+Like with configuration values, if the named file is missing Git behaves as if
+the option was not given at all. See "Values" in linkgit:git-config[1].
+
 NOTES ON FREQUENTLY CONFUSED OPTIONS
 ------------------------------------
 
diff --git a/Documentation/gitformat-loose.adoc b/Documentation/gitformat-loose.adoc
new file mode 100644
index 0000000..9479936
--- /dev/null
+++ b/Documentation/gitformat-loose.adoc
@@ -0,0 +1,53 @@
+gitformat-loose(5)
+==================
+
+NAME
+----
+gitformat-loose - Git loose object format
+
+
+SYNOPSIS
+--------
+[verse]
+$GIT_DIR/objects/[0-9a-f][0-9a-f]/*
+
+DESCRIPTION
+-----------
+
+Loose objects are how Git stores individual objects, where every object is
+written as a separate file.
+
+Over the lifetime of a repository, objects are usually written as loose objects
+initially.  Eventually, these loose objects will be compacted into packfiles
+via repository maintenance to improve disk space usage and speed up the lookup
+of these objects.
+
+== Loose objects
+
+Each loose object contains a prefix, followed immediately by the data of the
+object.  The prefix contains `<type> <size>\0`.  `<type>` is one of `blob`,
+`tree`, `commit`, or `tag` and `size` is the size of the data (without the
+prefix) as a decimal integer expressed in ASCII.
+
+The entire contents, prefix and data concatenated, is then compressed with zlib
+and the compressed data is stored in the file.  The object ID of the object is
+the SHA-1 or SHA-256 (as appropriate) hash of the uncompressed data.
+
+The file for the loose object is stored under the `objects` directory, with the
+first two hex characters of the object ID being the directory and the remaining
+characters being the file name.  This is done to shard the data and avoid too
+many files being in one directory, since some file systems perform poorly with
+many items in a directory.
+
+As an example, the empty tree contains the data (when uncompressed) `tree 0\0`
+and, in a SHA-256 repository, would have the object ID
+`6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321` and would be
+stored under
+`$GIT_DIR/objects/6e/f19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321`.
+
+Similarly, a blob containing the contents `abc` would have the uncompressed
+data of `blob 3\0abc`.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gitformat-pack.adoc b/Documentation/gitformat-pack.adoc
index d6ae229..1b4db4a 100644
--- a/Documentation/gitformat-pack.adoc
+++ b/Documentation/gitformat-pack.adoc
@@ -32,6 +32,10 @@
 and object IDs (object names) mentioned below are all computed using SHA-1.
 Similarly, in SHA-256 repositories, these values are computed using SHA-256.
 
+CRC32 checksums are always computed over the entire packed object, including
+the header (n-byte type and length); the base object name or offset, if any;
+and the entire compressed object.  The CRC32 algorithm used is that of zlib.
+
 == pack-*.pack files have the following format:
 
    - A header appears at the beginning and consists of the following:
@@ -80,6 +84,16 @@
 
 Type 5 is reserved for future expansion. Type 0 is invalid.
 
+=== Object encoding
+
+Unlike loose objects, packed objects do not have a prefix containing the type,
+size, and a NUL byte. These are not necessary because they can be determined by
+the n-byte type and length that prefixes the data and so they are omitted from
+the compressed and deltified data.
+
+The computation of the object ID still uses this prefix by reconstructing it
+from the type and length as needed.
+
 === Size encoding
 
 This document uses the following "size encoding" of non-negative
@@ -92,6 +106,11 @@
 This size encoding should not be confused with the "offset encoding",
 which is also used in this document.
 
+When encoding the size of an undeltified object in a pack, the size is that of
+the uncompressed raw object. For deltified objects, it is the size of the
+uncompressed delta.  The base object name or offset is not included in the size
+computation.
+
 === Deltified representation
 
 Conceptually there are only four object types: commit, tree, tag and
diff --git a/Documentation/meson.build b/Documentation/meson.build
index 44f94cd..9d24f2d 100644
--- a/Documentation/meson.build
+++ b/Documentation/meson.build
@@ -173,6 +173,7 @@
   'gitformat-chunk.adoc' : 5,
   'gitformat-commit-graph.adoc' : 5,
   'gitformat-index.adoc' : 5,
+  'gitformat-loose.adoc' : 5,
   'gitformat-pack.adoc' : 5,
   'gitformat-signature.adoc' : 5,
   'githooks.adoc' : 5,
diff --git a/Documentation/pretty-options.adoc b/Documentation/pretty-options.adoc
index 8aac51d..658e462 100644
--- a/Documentation/pretty-options.adoc
+++ b/Documentation/pretty-options.adoc
@@ -61,15 +61,16 @@
 ifndef::git-rev-list[]
 `--notes[=<ref>]`::
 	Show the notes (see linkgit:git-notes[1]) that annotate the
-	commit, when showing the commit log message.  This is the default
+	commit, when showing the commit log message.
 ifndef::with-breaking-changes[]
-	for `git log`, `git show` and `git whatchanged` commands when
+This is the default for `git log`, `git show` and `git whatchanged`
+commands when there is no `--pretty`, `--format`, or `--oneline` option given
+on the command line.
 endif::with-breaking-changes[]
 ifdef::with-breaking-changes[]
-	for `git log` and `git show` commands when
+This is the default for `git log` and `git show` commands when there is no
+`--pretty`, `--format`, or `--oneline` option given on the command line.
 endif::with-breaking-changes[]
-	there is no `--pretty`, `--format`, or `--oneline` option given
-	on the command line.
 +
 By default, the notes shown are from the notes refs listed in the
 `core.notesRef` and `notes.displayRef` variables (or corresponding
diff --git a/Documentation/pull-fetch-param.adoc b/Documentation/pull-fetch-param.adoc
index d79d2f6..bb2cf6a 100644
--- a/Documentation/pull-fetch-param.adoc
+++ b/Documentation/pull-fetch-param.adoc
@@ -11,6 +11,7 @@
 	(See linkgit:git-config[1]).
 endif::git-pull[]
 
+[[fetch-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
diff --git a/Documentation/technical/commit-graph.adoc b/Documentation/technical/commit-graph.adoc
index 2c26e95..a259d15 100644
--- a/Documentation/technical/commit-graph.adoc
+++ b/Documentation/technical/commit-graph.adoc
@@ -39,6 +39,7 @@
 Values 1-4 satisfy the requirements of parse_commit_gently().
 
 There are two definitions of generation number:
+
 1. Corrected committer dates (generation number v2)
 2. Topological levels (generation number v1)
 
@@ -158,7 +159,8 @@
 we enable fast writes of new commit data without rewriting the entire commit
 history -- at least, most of the time.
 
-## File Layout
+File Layout
+~~~~~~~~~~~
 
 A commit-graph chain uses multiple files, and we use a fixed naming convention
 to organize these files. Each commit-graph file has a name
@@ -170,11 +172,11 @@
 
 For example, if the `commit-graph-chain` file contains the lines
 
-```
+----
 	{hash0}
 	{hash1}
 	{hash2}
-```
+----
 
 then the commit-graph chain looks like the following diagram:
 
@@ -213,7 +215,8 @@
 `graph-{hash1}.graph` contains `{hash0}` while `graph-{hash2}.graph` contains
 `{hash0}` and `{hash1}`.
 
-## Merging commit-graph files
+Merging commit-graph files
+~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 If we only added a new commit-graph file on every write, we would run into a
 linear search problem through many commit-graph files.  Instead, we use a merge
@@ -225,6 +228,7 @@
 the commits in `graph-{hash1}` should be combined into a new `graph-{hash3}`
 file.
 
+....
 			    +---------------------+
 			    |                     |
 			    |    (new commits)    |
@@ -250,6 +254,7 @@
  |                       |
  |                       |
  +-----------------------+
+....
 
 During this process, the commits to write are combined, sorted and we write the
 contents to a temporary file, all while holding a `commit-graph-chain.lock`
@@ -257,14 +262,15 @@
 according to the computed `{hash3}`. Finally, we write the new chain data to
 `commit-graph-chain.lock`:
 
-```
+----
 	{hash3}
 	{hash0}
-```
+----
 
 We then close the lock-file.
 
-## Merge Strategy
+Merge Strategy
+~~~~~~~~~~~~~~
 
 When writing a set of commits that do not exist in the commit-graph stack of
 height N, we default to creating a new file at level N + 1. We then decide to
@@ -289,7 +295,8 @@
 number of commits) could be extracted into config settings for full
 flexibility.
 
-## Handling Mixed Generation Number Chains
+Handling Mixed Generation Number Chains
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 With the introduction of generation number v2 and generation data chunk, the
 following scenario is possible:
@@ -318,7 +325,8 @@
 rewriting split commit-graph as a single file (`--split=replace`) creates a
 single layer with corrected commit dates.
 
-## Deleting graph-{hash} files
+Deleting graph-\{hash\} files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 After a new tip file is written, some `graph-{hash}` files may no longer
 be part of a chain. It is important to remove these files from disk, eventually.
@@ -333,7 +341,8 @@
 defaults to zero, but can be changed using command-line arguments or a config
 setting.
 
-## Chains across multiple object directories
+Chains across multiple object directories
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 In a repo with alternates, we look for the `commit-graph-chain` file starting
 in the local object directory and then in each alternate. The first file that
diff --git a/Documentation/technical/hash-function-transition.adoc b/Documentation/technical/hash-function-transition.adoc
index f047fd8..2359d7d 100644
--- a/Documentation/technical/hash-function-transition.adoc
+++ b/Documentation/technical/hash-function-transition.adoc
@@ -227,9 +227,9 @@
     ** 4-byte length in bytes of shortened object names. This is the
       shortest possible length needed to make names in the shortened
       object name table unambiguous.
-    ** 4-byte integer, recording where tables relating to this format
+    ** 8-byte integer, recording where tables relating to this format
       are stored in this index file, as an offset from the beginning.
-  * 4-byte offset to the trailer from the beginning of this file.
+  * 8-byte offset to the trailer from the beginning of this file.
   * Zero or more additional key/value pairs (4-byte key, 4-byte
     value). Only one key is supported: 'PSRC'. See the "Loose objects
     and unreachable objects" section for supported values and how this
@@ -260,12 +260,10 @@
     compressed data to be copied directly from pack to pack during
     repacking without undetected data corruption.
 
-  * A table of 4-byte offset values. For an object in the table of
-    sorted shortened object names, the value at the corresponding
-    index in this table indicates where that object can be found in
-    the pack file. These are usually 31-bit pack file offsets, but
-    large offsets are encoded as an index into the next table with the
-    most significant bit set.
+  * A table of 4-byte offset values. The index of this table in pack order
+    indicates where that object can be found in the pack file. These are
+    usually 31-bit pack file offsets, but large offsets are encoded as
+    an index into the next table with the most significant bit set.
 
   * A table of 8-byte offset entries (empty for pack files less than
     2 GiB). Pack files are organized with heavily used objects toward
@@ -276,10 +274,14 @@
   up to and not including the table of CRC32 values.
 - Zero or more NUL bytes.
 - The trailer consists of the following:
-  * A copy of the 20-byte SHA-256 checksum at the end of the
+  * A copy of the full main hash checksum at the end of the
     corresponding packfile.
 
-  * 20-byte SHA-256 checksum of all of the above.
+  * Full main hash checksum of all of the above.
+
+The "full main hash" is a full-length hash of the main (not compatibility)
+algorithm in the repository.  Thus, if the main algorithm is SHA-256, this is
+a 32-byte SHA-256 hash and for SHA-1, it's a 20-byte SHA-1 hash.
 
 Loose object index
 ~~~~~~~~~~~~~~~~~~
@@ -427,17 +429,19 @@
 
 Signed Tags
 ~~~~~~~~~~~
-We add a new field "gpgsig-sha256" to the tag object format to allow
-signing tags without relying on SHA-1. Its signed payload is the
-SHA-256 content of the tag with its gpgsig-sha256 field and "-----BEGIN PGP
-SIGNATURE-----" delimited in-body signature removed.
+We add new fields "gpgsig" and "gpgsig-sha256" to the tag object format to
+allow signing tags in both formats.  The in-body signature is used for the
+signature in the current hash algorithm and the header is used for the
+signature in the other algorithm.  Thus, a dual-signature tag will contain both
+an in-body signature and a gpgsig-sha256 header for the SHA-1 format of an
+object or both an in-body signature and a gpgsig header for the SHA-256 format
+of and object.
 
-This means tags can be signed
+The signed payload of the tag is the content of the tag in the current
+algorithm with both its gpgsig and gpgsig-sha256 fields and
+"-----BEGIN PGP SIGNATURE-----" delimited in-body signature removed.
 
-1. using SHA-1 only, as in existing signed tag objects
-2. using both SHA-1 and SHA-256, by using gpgsig-sha256 and an in-body
-   signature.
-3. using only SHA-256, by only using the gpgsig-sha256 field.
+This means tags can be signed using one or both algorithms.
 
 Mergetag embedding
 ~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/technical/large-object-promisors.adoc b/Documentation/technical/large-object-promisors.adoc
index dea8daf..2aa815e 100644
--- a/Documentation/technical/large-object-promisors.adoc
+++ b/Documentation/technical/large-object-promisors.adoc
@@ -34,8 +34,8 @@
 
 https://lore.kernel.org/git/xmqqbkdometi.fsf@gitster.g/
 
-0) Non goals
-------------
+Non goals
+---------
 
 - We will not discuss those client side improvements here, as they
   would require changes in different parts of Git than this effort.
@@ -90,8 +90,8 @@
     even more to host content with larger blobs or more large blobs
     than currently.
 
-I) Issues with the current situation
-------------------------------------
+I Issues with the current situation
+-----------------------------------
 
 - Some statistics made on GitLab repos have shown that more than 75%
   of the disk space is used by blobs that are larger than 1MB and
@@ -138,8 +138,8 @@
   complaining that these tools require significant effort to set up,
   learn and use correctly.
 
-II) Main features of the "Large Object Promisors" solution
-----------------------------------------------------------
+II Main features of the "Large Object Promisors" solution
+---------------------------------------------------------
 
 The main features below should give a rough overview of how the
 solution may work. Details about needed elements can be found in
@@ -166,7 +166,7 @@
 other objects.
 
 Note 1
-++++++
+^^^^^^
 
 To clarify, a LOP is a normal promisor remote, except that:
 
@@ -178,7 +178,7 @@
   itself.
 
 Note 2
-++++++
+^^^^^^
 
 Git already makes it possible for a main remote to also be a promisor
 remote storing both regular objects and large blobs for a client that
@@ -186,13 +186,13 @@
 to avoid that.
 
 Rationale
-+++++++++
+^^^^^^^^^
 
 LOPs aim to be good at handling large blobs while main remotes are
 already good at handling other objects.
 
 Implementation
-++++++++++++++
+^^^^^^^^^^^^^^
 
 Git already has support for multiple promisor remotes, see
 link:partial-clone.html#using-many-promisor-remotes[the partial clone documentation].
@@ -213,19 +213,19 @@
 underlying object storage appear like a remote to Git.
 
 Note
-++++
+^^^^
 
 A LOP can be a promisor remote accessed using a remote helper by
 both some clients and the main remote.
 
 Rationale
-+++++++++
+^^^^^^^^^
 
 This looks like the simplest way to create LOPs that can cheaply
 handle many large blobs.
 
 Implementation
-++++++++++++++
+^^^^^^^^^^^^^^
 
 Remote helpers are quite easy to write as shell scripts, but it might
 be more efficient and maintainable to write them using other languages
@@ -247,7 +247,7 @@
 storage for large files handled by Git LFS.
 
 Rationale
-+++++++++
+^^^^^^^^^
 
 This would simplify the server side if it wants to both use a LOP and
 act as a Git LFS server.
@@ -259,7 +259,7 @@
 LOP all its blobs with a size over a configurable threshold.
 
 Rationale
-+++++++++
+^^^^^^^^^
 
 This makes it easy to set things up and to clean things up. For
 example, an admin could use this to manually convert a repo not using
@@ -268,7 +268,7 @@
 to regularly make sure the large blobs are moved to the LOP.
 
 Implementation
-++++++++++++++
+^^^^^^^^^^^^^^
 
 Using something based on `git repack --filter=...` to separate the
 blobs we want to offload from the other Git objects could be a good
@@ -284,13 +284,13 @@
 perhaps pushed, into it.
 
 Rationale
-+++++++++
+^^^^^^^^^
 
 A main remote containing many oversize blobs would defeat the purpose
 of LOPs.
 
 Implementation
-++++++++++++++
+^^^^^^^^^^^^^^
 
 The way to offload to a LOP discussed in 4) above can be used to
 regularly offload oversize blobs. About preventing oversize blobs from
@@ -326,18 +326,18 @@
 fetch those blobs from the LOP to be able to serve the client.
 
 Note
-++++
+^^^^
 
 For fetches instead of clones, a protocol negotiation might not always
 happen, see the "What about fetches?" FAQ entry below for details.
 
 Rationale
-+++++++++
+^^^^^^^^^
 
 Security, configurability and efficiency of setting things up.
 
 Implementation
-++++++++++++++
+^^^^^^^^^^^^^^
 
 A "promisor-remote" protocol v2 capability looks like a good way to
 implement this. The way the client and server use this capability
@@ -356,7 +356,7 @@
 but might not need anymore, to the LOP.
 
 Note
-++++
+^^^^
 
 It might depend on the context if it should be OK or not for clients
 to offload large blobs they have created, instead of fetched, directly
@@ -367,13 +367,13 @@
 implementing this feature.
 
 Rationale
-+++++++++
+^^^^^^^^^
 
 On the client, the easiest way to deal with unneeded large blobs is to
 offload them.
 
 Implementation
-++++++++++++++
+^^^^^^^^^^^^^^
 
 This is very similar to what 4) above is about, except on the client
 side instead of the server side. So a good solution to 4) could likely
@@ -385,8 +385,8 @@
 a LOP, it is likely, and can easily be confirmed, that the LOP still
 has them, so that they can just be removed from the client.
 
-III) Benefits of using LOPs
----------------------------
+III Benefits of using LOPs
+--------------------------
 
 Many benefits are related to the issues discussed in "I) Issues with
 the current situation" above:
@@ -406,8 +406,8 @@
 
 - Reduced storage needs on the client side.
 
-IV) FAQ
--------
+IV FAQ
+------
 
 What about using multiple LOPs on the server and client side?
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -533,7 +533,7 @@
 on a promisor remote.
 
 Regular fetch
-+++++++++++++
+^^^^^^^^^^^^^
 
 In a regular fetch, the client will contact the main remote and a
 protocol negotiation will happen between them. It's a good thing that
@@ -551,7 +551,7 @@
 using, or not using, the same LOP(s) as last time.
 
 "Backfill" or "lazy" fetch
-++++++++++++++++++++++++++
+^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 When there is a backfill fetch, the client doesn't necessarily contact
 the main remote first. It will try to fetch from its promisor remotes
@@ -576,8 +576,8 @@
 token when performing a protocol negotiation with the main remote (see
 section II.6 above).
 
-V) Future improvements
-----------------------
+V Future improvements
+---------------------
 
 It is expected that at the beginning using LOPs will be mostly worth
 it either in a corporate context where the Git version that clients
diff --git a/Documentation/technical/meson.build b/Documentation/technical/meson.build
index 858af81..be698ef 100644
--- a/Documentation/technical/meson.build
+++ b/Documentation/technical/meson.build
@@ -13,6 +13,7 @@
   'commit-graph.adoc',
   'directory-rename-detection.adoc',
   'hash-function-transition.adoc',
+  'large-object-promisors.adoc',
   'long-running-process-protocol.adoc',
   'multi-pack-index.adoc',
   'packfile-uri.adoc',
diff --git a/Documentation/technical/remembering-renames.adoc b/Documentation/technical/remembering-renames.adoc
index 73f4176..6155f36 100644
--- a/Documentation/technical/remembering-renames.adoc
+++ b/Documentation/technical/remembering-renames.adoc
@@ -10,32 +10,32 @@
 
 Outline:
 
-  0. Assumptions
+  1. Assumptions
 
-  1. How rebasing and cherry-picking work
+  2. How rebasing and cherry-picking work
 
-  2. Why the renames on MERGE_SIDE1 in any given pick are *always* a
+  3. Why the renames on MERGE_SIDE1 in any given pick are *always* a
      superset of the renames on MERGE_SIDE1 for the next pick.
 
-  3. Why any rename on MERGE_SIDE1 in any given pick is _almost_ always also
+  4. Why any rename on MERGE_SIDE1 in any given pick is _almost_ always also
      a rename on MERGE_SIDE1 for the next pick
 
-  4. A detailed description of the counter-examples to #3.
+  5. A detailed description of the counter-examples to #4.
 
-  5. Why the special cases in #4 are still fully reasonable to use to pair
+  6. Why the special cases in #5 are still fully reasonable to use to pair
      up files for three-way content merging in the merge machinery, and why
      they do not affect the correctness of the merge.
 
-  6. Interaction with skipping of "irrelevant" renames
+  7. Interaction with skipping of "irrelevant" renames
 
-  7. Additional items that need to be cached
+  8. Additional items that need to be cached
 
-  8. How directory rename detection interacts with the above and why this
+  9. How directory rename detection interacts with the above and why this
      optimization is still safe even if merge.directoryRenames is set to
      "true".
 
 
-=== 0. Assumptions ===
+== 1. Assumptions ==
 
 There are two assumptions that will hold throughout this document:
 
@@ -44,8 +44,8 @@
 
   * All merges are fully automatic
 
-and a third that will hold in sections 2-5 for simplicity, that I'll later
-address in section 8:
+and a third that will hold in sections 3-6 for simplicity, that I'll later
+address in section 9:
 
   * No directory renames occur
 
@@ -77,9 +77,9 @@
 stored on disk, and thus is thrown away as soon as the rebase or cherry
 pick stops for the user to resolve the operation.
 
-The third assumption makes sections 2-5 simpler, and allows people to
+The third assumption makes sections 3-6 simpler, and allows people to
 understand the basics of why this optimization is safe and effective, and
-then I can go back and address the specifics in section 8.  It is probably
+then I can go back and address the specifics in section 9.  It is probably
 also worth noting that if directory renames do occur, then the default of
 merge.directoryRenames being set to "conflict" means that the operation
 will stop for users to resolve the conflicts and the cache will be thrown
@@ -88,22 +88,26 @@
 users will have set merge.directoryRenames to "true" to allow the merges to
 continue to proceed automatically.  The optimization is still safe with
 this config setting, but we have to discuss a few more cases to show why;
-this discussion is deferred until section 8.
+this discussion is deferred until section 9.
 
 
-=== 1. How rebasing and cherry-picking work ===
+== 2. How rebasing and cherry-picking work ==
 
 Consider the following setup (from the git-rebase manpage):
 
+------------
 		     A---B---C topic
 		    /
 	       D---E---F---G main
+------------
 
 After rebasing or cherry-picking topic onto main, this will appear as:
 
+------------
 			     A'--B'--C' topic
 			    /
 	       D---E---F---G main
+------------
 
 The way the commits A', B', and C' are created is through a series of
 merges, where rebase or cherry-pick sequentially uses each of the three
@@ -111,6 +115,7 @@
 in the merge operation as MERGE_BASE, MERGE_SIDE1, and MERGE_SIDE2.  For
 this picture, the three commits for each of the three merges would be:
 
+....
 To create A':
    MERGE_BASE:   E
    MERGE_SIDE1:  G
@@ -125,6 +130,7 @@
    MERGE_BASE:   B
    MERGE_SIDE1:  B'
    MERGE_SIDE2:  C
+....
 
 Sometimes, folks are surprised that these three-way merges are done.  It
 can be useful in understanding these three-way merges to view them in a
@@ -138,8 +144,7 @@
 B, B', and C, at least the parts before you decide to record a commit.
 
 
-=== 2. Why the renames on MERGE_SIDE1 in any given pick are always a ===
-===    superset of the renames on MERGE_SIDE1 for the next pick.     ===
+== 3. Why the renames on MERGE_SIDE1 in any given pick are always a superset of the renames on MERGE_SIDE1 for the next pick. ==
 
 The merge machinery uses the filenames it is fed from MERGE_BASE,
 MERGE_SIDE1, and MERGE_SIDE2.  It will only move content to a different
@@ -156,6 +161,7 @@
 First, let's remember what commits are involved in the first and second
 picks of the cherry-pick or rebase sequence:
 
+....
 To create A':
    MERGE_BASE:   E
    MERGE_SIDE1:  G
@@ -165,6 +171,7 @@
    MERGE_BASE:   A
    MERGE_SIDE1:  A'
    MERGE_SIDE2:  B
+....
 
 So, in particular, we need to show that the renames between E and G are a
 superset of those between A and A'.
@@ -181,11 +188,11 @@
 and G are a superset of those between A and A'.
 
 
-=== 3. Why any rename on MERGE_SIDE1 in any given pick is _almost_   ===
-===    always also a rename on MERGE_SIDE1 for the next pick.        ===
+== 4. Why any rename on MERGE_SIDE1 in any given pick is _almost_ always also a rename on MERGE_SIDE1 for the next pick. ==
 
 Let's again look at the first two picks:
 
+....
 To create A':
    MERGE_BASE:   E
    MERGE_SIDE1:  G
@@ -195,17 +202,25 @@
    MERGE_BASE:   A
    MERGE_SIDE1:  A'
    MERGE_SIDE2:  B
+....
 
 Now let's look at any given rename from MERGE_SIDE1 of the first pick, i.e.
 any given rename from E to G.  Let's use the filenames 'oldfile' and
 'newfile' for demonstration purposes.  That first pick will function as
 follows; when the rename is detected, the merge machinery will do a
 three-way content merge of the following:
+
+....
     E:oldfile
     G:newfile
     A:oldfile
+....
+
 and produce a new result:
+
+....
     A':newfile
+....
 
 Note above that I've assumed that E->A did not rename oldfile.  If that
 side did rename, then we most likely have a rename/rename(1to2) conflict
@@ -254,19 +269,21 @@
 detectable as renames almost always.
 
 
-=== 4. A detailed description of the counter-examples to #3.         ===
+== 5. A detailed description of the counter-examples to #4. ==
 
-We already noted in section 3 that rename/rename(1to1) (i.e. both sides
+We already noted in section 4 that rename/rename(1to1) (i.e. both sides
 renaming a file the same way) was one counter-example.  The more
 interesting bit, though, is why did we need to use the "almost" qualifier
 when stating that A:oldfile and A':newfile are "almost" always detectable
 as renames?
 
-Let's repeat an earlier point that section 3 made:
+Let's repeat an earlier point that section 4 made:
 
+....
   A':newfile was created by applying the changes between E:oldfile and
   G:newfile to A:oldfile.  The changes between E:oldfile and G:newfile were
   <50% of the size of E:oldfile.
+....
 
 If those changes that were <50% of the size of E:oldfile are also <50% of
 the size of A:oldfile, then A:oldfile and A':newfile will be detectable as
@@ -276,18 +293,21 @@
 detect A:oldfile and A':newfile as renames.
 
 Here's an example where that can happen:
+
   * E:oldfile had 20 lines
   * G:newfile added 10 new lines at the beginning of the file
   * A:oldfile kept the first 3 lines of the file, and deleted all the rest
+
 then
+
+....
   => A':newfile would have 13 lines, 3 of which matches those in A:oldfile.
-E:oldfile -> G:newfile would be detected as a rename, but A:oldfile and
-A':newfile would not be.
+  E:oldfile -> G:newfile would be detected as a rename, but A:oldfile and
+  A':newfile would not be.
+....
 
 
-=== 5. Why the special cases in #4 are still fully reasonable to use to    ===
-===    pair up files for three-way content merging in the merge machinery, ===
-===    and why they do not affect the correctness of the merge.            ===
+== 6. Why the special cases in #5 are still fully reasonable to use to pair up files for three-way content merging in the merge machinery, and why they do not affect the correctness of the merge. ==
 
 In the rename/rename(1to1) case, A:newfile and A':newfile are not renames
 since they use the *same* filename.  However, files with the same filename
@@ -295,14 +315,14 @@
 machinery has never employed break detection).  The interesting
 counter-example case is thus not the rename/rename(1to1) case, but the case
 where A did not rename oldfile.  That was the case that we spent most of
-the time discussing in sections 3 and 4.  The remainder of this section
+the time discussing in sections 4 and 5.  The remainder of this section
 will be devoted to that case as well.
 
 So, even if A:oldfile and A':newfile aren't detectable as renames, why is
 it still reasonable to pair them up for three-way content merging in the
 merge machinery?  There are multiple reasons:
 
-  * As noted in sections 3 and 4, the diff between A:oldfile and A':newfile
+  * As noted in sections 4 and 5, the diff between A:oldfile and A':newfile
     is *exactly* the same as the diff between E:oldfile and G:newfile.  The
     latter pair were detected as renames, so it seems unlikely to surprise
     users for us to treat A:oldfile and A':newfile as renames.
@@ -394,7 +414,7 @@
 optimization than without.
 
 
-=== 6. Interaction with skipping of "irrelevant" renames ===
+== 7. Interaction with skipping of "irrelevant" renames ==
 
 Previous optimizations involved skipping rename detection for paths
 considered to be "irrelevant".  See for example the following commits:
@@ -421,24 +441,27 @@
 already detected renames.
 
 
-=== 7. Additional items that need to be cached ===
+== 8. Additional items that need to be cached ==
 
 It turns out we have to cache more than just renames; we also cache:
 
+....
   A) non-renames (i.e. unpaired deletes)
   B) counts of renames within directories
   C) sources that were marked as RELEVANT_LOCATION, but which were
      downgraded to RELEVANT_NO_MORE
   D) the toplevel trees involved in the merge
+....
 
 These are all stored in struct rename_info, and respectively appear in
+
   * cached_pairs (along side actual renames, just with a value of NULL)
   * dir_rename_counts
   * cached_irrelevant
   * merge_trees
 
-The reason for (A) comes from the irrelevant renames skipping
-optimization discussed in section 6.  The fact that irrelevant renames
+The reason for `(A)` comes from the irrelevant renames skipping
+optimization discussed in section 7.  The fact that irrelevant renames
 are skipped means we only get a subset of the potential renames
 detected and subsequent commits may need to run rename detection on
 the upstream side on a subset of the remaining renames (to get the
@@ -447,23 +470,24 @@
 repeatedly check that those paths remain unpaired on the upstream side
 with every commit we are transplanting.
 
-The reason for (B) is that diffcore_rename_extended() is what
+The reason for `(B)` is that diffcore_rename_extended() is what
 generates the counts of renames by directory which is needed in
 directory rename detection, and if we don't run
 diffcore_rename_extended() again then we need to have the output from
 it, including dir_rename_counts, from the previous run.
 
-The reason for (C) is that merge-ort's tree traversal will again think
+The reason for `(C)` is that merge-ort's tree traversal will again think
 those paths are relevant (marking them as RELEVANT_LOCATION), but the
 fact that they were downgraded to RELEVANT_NO_MORE means that
 dir_rename_counts already has the information we need for directory
 rename detection.  (A path which becomes RELEVANT_CONTENT in a
 subsequent commit will be removed from cached_irrelevant.)
 
-The reason for (D) is that is how we determine whether the remember
+The reason for `(D)` is that is how we determine whether the remember
 renames optimization can be used.  In particular, remembering that our
 sequence of merges looks like:
 
+....
    Merge 1:
    MERGE_BASE:   E
    MERGE_SIDE1:  G
@@ -475,6 +499,7 @@
    MERGE_SIDE1:  A'
    MERGE_SIDE2:  B
    => Creates    B'
+....
 
 It is the fact that the trees A and A' appear both in Merge 1 and in
 Merge 2, with A as a parent of A' that allows this optimization.  So
@@ -482,12 +507,11 @@
 time.
 
 
-=== 8. How directory rename detection interacts with the above and   ===
-===    why this optimization is still safe even if                   ===
-===    merge.directoryRenames is set to "true".                      ===
+== 9. How directory rename detection interacts with the above and why this optimization is still safe even if merge.directoryRenames is set to "true". ==
 
 As noted in the assumptions section:
 
+....
     """
     ...if directory renames do occur, then the default of
     merge.directoryRenames being set to "conflict" means that the operation
@@ -497,11 +521,13 @@
     is that some users will have set merge.directoryRenames to "true" to
     allow the merges to continue to proceed automatically.
     """
+....
 
 Let's remember that we need to look at how any given pick affects the next
 one.  So let's again use the first two picks from the diagram in section
 one:
 
+....
   First pick does this three-way merge:
     MERGE_BASE:   E
     MERGE_SIDE1:  G
@@ -513,6 +539,7 @@
     MERGE_SIDE1:  A'
     MERGE_SIDE2:  B
     => creates B'
+....
 
 Now, directory rename detection exists so that if one side of history
 renames a directory, and the other side adds a new file to the old
@@ -545,7 +572,7 @@
     concerned; see the assumptions section).  Two interesting sub-notes
     about these counts:
 
-    * If we need to perform rename-detection again on the given side (e.g.
+   ** If we need to perform rename-detection again on the given side (e.g.
       some paths are relevant for rename detection that weren't before),
       then we clear dir_rename_counts and recompute it, making use of
       cached_pairs.  The reason it is important to do this is optimizations
@@ -556,7 +583,7 @@
       easiest way to "fix up" dir_rename_counts in such cases is to just
       recompute it.
 
-    * If we prune rename/rename(1to1) entries from the cache, then we also
+   ** If we prune rename/rename(1to1) entries from the cache, then we also
       need to update dir_rename_counts to decrement the counts for the
       involved directory and any relevant parent directories (to undo what
       update_dir_rename_counts() in diffcore-rename.c incremented when the
@@ -578,6 +605,7 @@
 
 Case 1: MERGE_SIDE1 renames old dir, MERGE_SIDE2 adds new file to old dir
 
+....
   This case looks like this:
 
     MERGE_BASE:   E,   Has olddir/
@@ -595,10 +623,13 @@
     * MERGE_SIDE1 has cached olddir/newfile -> newdir/newfile
   Given the cached rename noted above, the second merge can proceed as
   expected without needing to perform rename detection from A -> A'.
+....
 
 Case 2: MERGE_SIDE1 renames old dir, MERGE_SIDE2 renames  file into old dir
 
+....
   This case looks like this:
+
     MERGE_BASE:   E    oldfile, olddir/
     MERGE_SIDE1:  G    oldfile, olddir/ -> newdir/
     MERGE_SIDE2:  A    oldfile -> olddir/newfile
@@ -617,9 +648,11 @@
 
   Given the cached rename noted above, the second merge can proceed as
   expected without needing to perform rename detection from A -> A'.
+....
 
 Case 3: MERGE_SIDE1 adds new file to   old dir, MERGE_SIDE2 renames old dir
 
+....
   This case looks like this:
 
     MERGE_BASE:   E,   Has olddir/
@@ -635,9 +668,11 @@
   In this case, with the optimization, note that after the first commit there
   were no renames on MERGE_SIDE1, and any renames on MERGE_SIDE2 are tossed.
   But the second merge didn't need any renames so this is fine.
+....
 
 Case 4: MERGE_SIDE1 renames  file into old dir, MERGE_SIDE2 renames old dir
 
+....
   This case looks like this:
 
     MERGE_BASE:   E,   Has olddir/
@@ -658,6 +693,7 @@
 
   Given the cached rename noted above, the second merge can proceed as
   expected without needing to perform rename detection from A -> A'.
+....
 
 Finally, I'll just note here that interactions with the
 skip-irrelevant-renames optimization means we sometimes don't detect
diff --git a/Documentation/technical/sparse-checkout.adoc b/Documentation/technical/sparse-checkout.adoc
index 0f750ef..3fa8e53 100644
--- a/Documentation/technical/sparse-checkout.adoc
+++ b/Documentation/technical/sparse-checkout.adoc
@@ -14,37 +14,41 @@
   * Reference Emails
 
 
-=== Terminology ===
+== Terminology ==
 
-cone mode: one of two modes for specifying the desired subset of files
+*`cone mode`*::
+	one of two modes for specifying the desired subset of files
 	in a sparse-checkout.  In cone-mode, the user specifies
 	directories (getting both everything under that directory as
 	well as everything in leading directories), while in non-cone
 	mode, the user specifies gitignore-style patterns.  Controlled
 	by the --[no-]cone option to sparse-checkout init|set.
 
-SKIP_WORKTREE: When tracked files do not match the sparse specification and
+*`SKIP_WORKTREE`*::
+	When tracked files do not match the sparse specification and
 	are removed from the working tree, the file in the index is marked
 	with a SKIP_WORKTREE bit.  Note that if a tracked file has the
 	SKIP_WORKTREE bit set but the file is later written by the user to
 	the working tree anyway, the SKIP_WORKTREE bit will be cleared at
 	the beginning of any subsequent Git operation.
++
+Most sparse checkout users are unaware of this implementation
+detail, and the term should generally be avoided in user-facing
+descriptions and command flags.  Unfortunately, prior to the
+`sparse-checkout` subcommand this low-level detail was exposed,
+and as of time of writing, is still exposed in various places.
 
-	Most sparse checkout users are unaware of this implementation
-	detail, and the term should generally be avoided in user-facing
-	descriptions and command flags.  Unfortunately, prior to the
-	`sparse-checkout` subcommand this low-level detail was exposed,
-	and as of time of writing, is still exposed in various places.
-
-sparse-checkout: a subcommand in git used to reduce the files present in
+*`sparse-checkout`*::
+	a subcommand in git used to reduce the files present in
 	the working tree to a subset of all tracked files.  Also, the
 	name of the file in the $GIT_DIR/info directory used to track
 	the sparsity patterns corresponding to the user's desired
 	subset.
 
-sparse cone: see cone mode
+*`sparse cone`*:: see cone mode
 
-sparse directory: An entry in the index corresponding to a directory, which
+*`sparse directory`*::
+	An entry in the index corresponding to a directory, which
 	appears in the index instead of all the files under that directory
 	that would normally appear.  See also sparse-index.  Something that
 	can cause confusion is that the "sparse directory" does NOT match
@@ -52,7 +56,8 @@
 	working tree.  May be renamed in the future (e.g. to "skipped
 	directory").
 
-sparse index: A special mode for sparse-checkout that also makes the
+*`sparse index`*::
+	A special mode for sparse-checkout that also makes the
 	index sparse by recording a directory entry in lieu of all the
 	files underneath that directory (thus making that a "skipped
 	directory" which unfortunately has also been called a "sparse
@@ -60,7 +65,8 @@
 	directories.  Controlled by the --[no-]sparse-index option to
 	init|set|reapply.
 
-sparsity patterns: patterns from $GIT_DIR/info/sparse-checkout used to
+*`sparsity patterns`*::
+	patterns from $GIT_DIR/info/sparse-checkout used to
 	define the set of files of interest.  A warning: It is easy to
 	over-use this term (or the shortened "patterns" term), for two
 	reasons: (1) users in cone mode specify directories rather than
@@ -70,7 +76,8 @@
 	transiently differ in the working tree or index from the sparsity
 	patterns (see "Sparse specification vs. sparsity patterns").
 
-sparse specification: The set of paths in the user's area of focus.  This
+*`sparse specification`*::
+	The set of paths in the user's area of focus.  This
 	is typically just the tracked files that match the sparsity
 	patterns, but the sparse specification can temporarily differ and
 	include additional files.  (See also "Sparse specification
@@ -87,12 +94,13 @@
 	* If working with the index and the working copy, the sparse
 	  specification is the union of the paths from above.
 
-vivifying: When a command restores a tracked file to the working tree (and
+*`vivifying`*::
+	When a command restores a tracked file to the working tree (and
 	hopefully also clears the SKIP_WORKTREE bit in the index for that
 	file), this is referred to as "vivifying" the file.
 
 
-=== Purpose of sparse-checkouts ===
+== Purpose of sparse-checkouts ==
 
 sparse-checkouts exist to allow users to work with a subset of their
 files.
@@ -120,14 +128,12 @@
 half dozen different ways.  Let's start by considering the high level
 usecases:
 
-  A) Users are _only_ interested in the sparse portion of the repo
-
-  A*) Users are _only_ interested in the sparse portion of the repo
-      that they have downloaded so far
-
-  B) Users want a sparse working tree, but are working in a larger whole
-
-  C) sparse-checkout is a behind-the-scenes implementation detail allowing
+[horizontal]
+A):: Users are _only_ interested in the sparse portion of the repo
+A*):: Users are _only_ interested in the sparse portion of the repo
+     that they have downloaded so far
+B):: Users want a sparse working tree, but are working in a larger whole
+C):: sparse-checkout is a behind-the-scenes implementation detail allowing
      Git to work with a specially crafted in-house virtual file system;
      users are actually working with a "full" working tree that is
      lazily populated, and sparse-checkout helps with the lazy population
@@ -136,7 +142,7 @@
 It may be worth explaining each of these in a bit more detail:
 
 
-  (Behavior A) Users are _only_ interested in the sparse portion of the repo
+=== (Behavior A) Users are _only_ interested in the sparse portion of the repo
 
 These folks might know there are other things in the repository, but
 don't care.  They are uninterested in other parts of the repository, and
@@ -163,8 +169,7 @@
 after a merge or pull) can lead to worries about local repository size
 growing unnecessarily[10].
 
-  (Behavior A*) Users are _only_ interested in the sparse portion of the repo
-      that they have downloaded so far (a variant on the first usecase)
+=== (Behavior A*) Users are _only_ interested in the sparse portion of the repo that they have downloaded so far (a variant on the first usecase)
 
 This variant is driven by folks who using partial clones together with
 sparse checkouts and do disconnected development (so far sounding like a
@@ -173,15 +178,14 @@
 through history within their sparse specification may be too much, so they
 only download some.  They would still like operations to succeed without
 network connectivity, though, so things like `git log -S${SEARCH_TERM} -p`
-or `git grep ${SEARCH_TERM} OLDREV ` would need to be prepared to provide
+or `git grep ${SEARCH_TERM} OLDREV` would need to be prepared to provide
 partial results that depend on what happens to have been downloaded.
 
 This variant could be viewed as Behavior A with the sparse specification
 for history querying operations modified from "sparsity patterns" to
 "sparsity patterns limited to the blobs we have already downloaded".
 
-  (Behavior B) Users want a sparse working tree, but are working in a
-      larger whole
+=== (Behavior B) Users want a sparse working tree, but are working in a larger whole
 
 Stolee described this usecase this way[11]:
 
@@ -229,8 +233,7 @@
 prefer getting "unrelated" results from their history queries over having
 slow commands.
 
-  (Behavior C) sparse-checkout is an implementational detail supporting a
-	       special VFS.
+=== (Behavior C) sparse-checkout is an implementational detail supporting a special VFS.
 
 This usecase goes slightly against the traditional definition of
 sparse-checkout in that it actually tries to present a full or dense
@@ -255,13 +258,13 @@
 all files are present.
 
 
-=== Usecases of primary concern ===
+== Usecases of primary concern ==
 
 Most of the rest of this document will focus on Behavior A and Behavior
 B.  Some notes about the other two cases and why we are not focusing on
 them:
 
-  (Behavior A*)
+=== (Behavior A*)
 
 Supporting this usecase is estimated to be difficult and a lot of work.
 There are no plans to implement it currently, but it may be a potential
@@ -275,7 +278,7 @@
 sparse specification to restrict it to already-downloaded blobs.  The hard
 part is in making commands capable of respecting that modified definition.
 
-  (Behavior C)
+=== (Behavior C)
 
 This usecase violates some of the early sparse-checkout documented
 assumptions (since files marked as SKIP_WORKTREE will be displayed to users
@@ -300,20 +303,20 @@
 patches that break things for the real Behavior B folks.
 
 
-=== Oversimplified mental models ===
+== Oversimplified mental models ==
 
 An oversimplification of the differences in the above behaviors is:
 
-  Behavior A: Restrict worktree and history operations to sparse specification
-  Behavior B: Restrict worktree operations to sparse specification; have any
-	      history operations work across all files
-  Behavior C: Do not restrict either worktree or history operations to the
-	      sparse specification...with the exception of branch checkouts or
-	      switches which avoid writing files that will match the index so
-	      they can later lazily be populated instead.
+(Behavior A):: Restrict worktree and history operations to sparse specification
+(Behavior B):: Restrict worktree operations to sparse specification; have any
+	     history operations work across all files
+(Behavior C):: Do not restrict either worktree or history operations to the
+	     sparse specification...with the exception of branch checkouts or
+	     switches which avoid writing files that will match the index so
+	     they can later lazily be populated instead.
 
 
-=== Desired behavior ===
+== Desired behavior ==
 
 As noted previously, despite the simple idea of just working with a subset
 of files, there are a range of different behavioral changes that need to be
@@ -326,37 +329,38 @@
 
 * Commands behaving the same regardless of high-level use-case
 
-  * commands that only look at files within the sparsity specification
+  ** commands that only look at files within the sparsity specification
 
-      * diff (without --cached or REVISION arguments)
-      * grep (without --cached or REVISION arguments)
-      * diff-files
+      *** diff (without --cached or REVISION arguments)
+      *** grep (without --cached or REVISION arguments)
+      *** diff-files
 
-  * commands that restore files to the working tree that match sparsity
+  ** commands that restore files to the working tree that match sparsity
     patterns, and remove unmodified files that don't match those
     patterns:
 
-      * switch
-      * checkout (the switch-like half)
-      * read-tree
-      * reset --hard
+      *** switch
+      *** checkout (the switch-like half)
+      *** read-tree
+      *** reset --hard
 
-  * commands that write conflicted files to the working tree, but otherwise
+  ** commands that write conflicted files to the working tree, but otherwise
     will omit writing files to the working tree that do not match the
     sparsity patterns:
 
-      * merge
-      * rebase
-      * cherry-pick
-      * revert
+      *** merge
+      *** rebase
+      *** cherry-pick
+      *** revert
 
-      * `am` and `apply --cached` should probably be in this section but
+      *** `am` and `apply --cached` should probably be in this section but
 	are buggy (see the "Known bugs" section below)
 
     The behavior for these commands somewhat depends upon the merge
     strategy being used:
-      * `ort` behaves as described above
-      * `octopus` and `resolve` will always vivify any file changed in the merge
+
+      *** `ort` behaves as described above
+      *** `octopus` and `resolve` will always vivify any file changed in the merge
 	relative to the first parent, which is rather suboptimal.
 
     It is also important to note that these commands WILL update the index
@@ -372,21 +376,21 @@
     specification and the sparsity patterns (much like the commands in the
     previous section).
 
-  * commands that always ignore sparsity since commits must be full-tree
+  ** commands that always ignore sparsity since commits must be full-tree
 
-      * archive
-      * bundle
-      * commit
-      * format-patch
-      * fast-export
-      * fast-import
-      * commit-tree
+      *** archive
+      *** bundle
+      *** commit
+      *** format-patch
+      *** fast-export
+      *** fast-import
+      *** commit-tree
 
-  * commands that write any modified file to the working tree (conflicted
+  ** commands that write any modified file to the working tree (conflicted
     or not, and whether those paths match sparsity patterns or not):
 
-      * stash
-      * apply (without `--index` or `--cached`)
+      *** stash
+      *** apply (without `--index` or `--cached`)
 
 * Commands that may slightly differ for behavior A vs. behavior B:
 
@@ -394,19 +398,20 @@
   behaviors, but may differ in verbosity and types of warning and error
   messages.
 
-  * commands that make modifications to which files are tracked:
-      * add
-      * rm
-      * mv
-      * update-index
+  ** commands that make modifications to which files are tracked:
+
+      *** add
+      *** rm
+      *** mv
+      *** update-index
 
     The fact that files can move between the 'tracked' and 'untracked'
     categories means some commands will have to treat untracked files
     differently.  But if we have to treat untracked files differently,
     then additional commands may also need changes:
 
-      * status
-      * clean
+      *** status
+      *** clean
 
     In particular, `status` may need to report any untracked files outside
     the sparsity specification as an erroneous condition (especially to
@@ -420,9 +425,10 @@
     may need to ignore the sparse specification by its nature.  Also, its
     current --[no-]ignore-skip-worktree-entries default is totally bogus.
 
-  * commands for manually tweaking paths in both the index and the working tree
-      * `restore`
-      * the restore-like half of `checkout`
+  ** commands for manually tweaking paths in both the index and the working tree
+
+      *** `restore`
+      *** the restore-like half of `checkout`
 
     These commands should be similar to add/rm/mv in that they should
     only operate on the sparse specification by default, and require a
@@ -433,18 +439,19 @@
 
 * Commands that significantly differ for behavior A vs. behavior B:
 
-  * commands that query history
-      * diff (with --cached or REVISION arguments)
-      * grep (with --cached or REVISION arguments)
-      * show (when given commit arguments)
-      * blame (only matters when one or more -C flags are passed)
-	* and annotate
-      * log
-      * whatchanged (may not exist anymore)
-      * ls-files
-      * diff-index
-      * diff-tree
-      * ls-tree
+  ** commands that query history
+
+      *** diff (with --cached or REVISION arguments)
+      *** grep (with --cached or REVISION arguments)
+      *** show (when given commit arguments)
+      *** blame (only matters when one or more -C flags are passed)
+	**** and annotate
+      *** log
+      *** whatchanged (may not exist anymore)
+      *** ls-files
+      *** diff-index
+      *** diff-tree
+      *** ls-tree
 
     Note: for log and whatchanged, revision walking logic is unaffected
     but displaying of patches is affected by scoping the command to the
@@ -458,91 +465,91 @@
 
 * Commands I don't know how to classify
 
-  * range-diff
+  ** range-diff
 
     Is this like `log` or `format-patch`?
 
-  * cherry
+  ** cherry
 
     See range-diff
 
 * Commands unaffected by sparse-checkouts
 
-  * shortlog
-  * show-branch
-  * rev-list
-  * bisect
+  ** shortlog
+  ** show-branch
+  ** rev-list
+  ** bisect
 
-  * branch
-  * describe
-  * fetch
-  * gc
-  * init
-  * maintenance
-  * notes
-  * pull (merge & rebase have the necessary changes)
-  * push
-  * submodule
-  * tag
+  ** branch
+  ** describe
+  ** fetch
+  ** gc
+  ** init
+  ** maintenance
+  ** notes
+  ** pull (merge & rebase have the necessary changes)
+  ** push
+  ** submodule
+  ** tag
 
-  * config
-  * filter-branch (works in separate checkout without sparse-checkout setup)
-  * pack-refs
-  * prune
-  * remote
-  * repack
-  * replace
+  ** config
+  ** filter-branch (works in separate checkout without sparse-checkout setup)
+  ** pack-refs
+  ** prune
+  ** remote
+  ** repack
+  ** replace
 
-  * bugreport
-  * count-objects
-  * fsck
-  * gitweb
-  * help
-  * instaweb
-  * merge-tree (doesn't touch worktree or index, and merges always compute full-tree)
-  * rerere
-  * verify-commit
-  * verify-tag
+  ** bugreport
+  ** count-objects
+  ** fsck
+  ** gitweb
+  ** help
+  ** instaweb
+  ** merge-tree (doesn't touch worktree or index, and merges always compute full-tree)
+  ** rerere
+  ** verify-commit
+  ** verify-tag
 
-  * commit-graph
-  * hash-object
-  * index-pack
-  * mktag
-  * mktree
-  * multi-pack-index
-  * pack-objects
-  * prune-packed
-  * symbolic-ref
-  * unpack-objects
-  * update-ref
-  * write-tree (operates on index, possibly optimized to use sparse dir entries)
+  ** commit-graph
+  ** hash-object
+  ** index-pack
+  ** mktag
+  ** mktree
+  ** multi-pack-index
+  ** pack-objects
+  ** prune-packed
+  ** symbolic-ref
+  ** unpack-objects
+  ** update-ref
+  ** write-tree (operates on index, possibly optimized to use sparse dir entries)
 
-  * for-each-ref
-  * get-tar-commit-id
-  * ls-remote
-  * merge-base (merges are computed full tree, so merge base should be too)
-  * name-rev
-  * pack-redundant
-  * rev-parse
-  * show-index
-  * show-ref
-  * unpack-file
-  * var
-  * verify-pack
+  ** for-each-ref
+  ** get-tar-commit-id
+  ** ls-remote
+  ** merge-base (merges are computed full tree, so merge base should be too)
+  ** name-rev
+  ** pack-redundant
+  ** rev-parse
+  ** show-index
+  ** show-ref
+  ** unpack-file
+  ** var
+  ** verify-pack
 
-  * <Everything under 'Interacting with Others' in 'git help --all'>
-  * <Everything under 'Low-level...Syncing' in 'git help --all'>
-  * <Everything under 'Low-level...Internal Helpers' in 'git help --all'>
-  * <Everything under 'External commands' in 'git help --all'>
+  ** <Everything under 'Interacting with Others' in 'git help --all'>
+  ** <Everything under 'Low-level...Syncing' in 'git help --all'>
+  ** <Everything under 'Low-level...Internal Helpers' in 'git help --all'>
+  ** <Everything under 'External commands' in 'git help --all'>
 
 * Commands that might be affected, but who cares?
 
-  * merge-file
-  * merge-index
-  * gitk?
+  ** merge-file
+  ** merge-index
+  ** gitk?
 
 
-=== Behavior classes ===
+== Behavior classes ==
 
 From the above there are a few classes of behavior:
 
@@ -573,18 +580,19 @@
 
     Commands in this class generally behave like the "restrict" class,
     except that:
-      (1) they will ignore the sparse specification and write files with
-	  conflicts to the working tree (thus temporarily expanding the
-	  sparse specification to include such files.)
-      (2) they are grouped with commands which move to a new commit, since
-	  they often create a commit and then move to it, even though we
-	  know there are many exceptions to moving to the new commit.  (For
-	  example, the user may rebase a commit that becomes empty, or have
-	  a cherry-pick which conflicts, or a user could run `merge
-	  --no-commit`, and we also view `apply --index` kind of like `am
-	  --no-commit`.)  As such, these commands can make changes to index
-	  files outside the sparse specification, though they'll mark such
-	  files with SKIP_WORKTREE.
+
+	(1) they will ignore the sparse specification and write files with
+	    conflicts to the working tree (thus temporarily expanding the
+	    sparse specification to include such files.)
+	(2) they are grouped with commands which move to a new commit, since
+	    they often create a commit and then move to it, even though we
+	    know there are many exceptions to moving to the new commit.  (For
+	    example, the user may rebase a commit that becomes empty, or have
+	    a cherry-pick which conflicts, or a user could run `merge
+	    --no-commit`, and we also view `apply --index` kind of like `am
+	    --no-commit`.)  As such, these commands can make changes to index
+	    files outside the sparse specification, though they'll mark such
+	    files with SKIP_WORKTREE.
 
   * "restrict also specially applied to untracked files"
 
@@ -609,37 +617,39 @@
     specification.
 
 
-=== Subcommand-dependent defaults ===
+== Subcommand-dependent defaults ==
 
 Note that we have different defaults depending on the command for the
 desired behavior :
 
   * Commands defaulting to "restrict":
-    * diff-files
-    * diff (without --cached or REVISION arguments)
-    * grep (without --cached or REVISION arguments)
-    * switch
-    * checkout (the switch-like half)
-    * reset (<commit>)
 
-    * restore
-    * checkout (the restore-like half)
-    * checkout-index
-    * reset (with pathspec)
+    ** diff-files
+    ** diff (without --cached or REVISION arguments)
+    ** grep (without --cached or REVISION arguments)
+    ** switch
+    ** checkout (the switch-like half)
+    ** reset (<commit>)
+
+    ** restore
+    ** checkout (the restore-like half)
+    ** checkout-index
+    ** reset (with pathspec)
 
     This behavior makes sense; these interact with the working tree.
 
   * Commands defaulting to "restrict modulo conflicts":
-    * merge
-    * rebase
-    * cherry-pick
-    * revert
 
-    * am
-    * apply --index (which is kind of like an `am --no-commit`)
+    ** merge
+    ** rebase
+    ** cherry-pick
+    ** revert
 
-    * read-tree (especially with -m or -u; is kind of like a --no-commit merge)
-    * reset (<tree-ish>, due to similarity to read-tree)
+    ** am
+    ** apply --index (which is kind of like an `am --no-commit`)
+
+    ** read-tree (especially with -m or -u; is kind of like a --no-commit merge)
+    ** reset (<tree-ish>, due to similarity to read-tree)
 
     These also interact with the working tree, but require slightly
     different behavior either so that (a) conflicts can be resolved or (b)
@@ -648,16 +658,17 @@
     (See also the "Known bugs" section below regarding `am` and `apply`)
 
   * Commands defaulting to "no restrict":
-    * archive
-    * bundle
-    * commit
-    * format-patch
-    * fast-export
-    * fast-import
-    * commit-tree
 
-    * stash
-    * apply (without `--index`)
+    ** archive
+    ** bundle
+    ** commit
+    ** format-patch
+    ** fast-export
+    ** fast-import
+    ** commit-tree
+
+    ** stash
+    ** apply (without `--index`)
 
     These have completely different defaults and perhaps deserve the most
     detailed explanation:
@@ -679,53 +690,59 @@
     sparse specification then we'll lose changes from the user.
 
   * Commands defaulting to "restrict also specially applied to untracked files":
-    * add
-    * rm
-    * mv
-    * update-index
-    * status
-    * clean (?)
 
-    Our original implementation for the first three of these commands was
-    "no restrict", but it had some severe usability issues:
-      * `git add <somefile>` if honored and outside the sparse
-	specification, can result in the file randomly disappearing later
-	when some subsequent command is run (since various commands
-	automatically clean up unmodified files outside the sparse
-	specification).
-      * `git rm '*.jpg'` could very negatively surprise users if it deletes
-	files outside the range of the user's interest.
-      * `git mv` has similar surprises when moving into or out of the cone,
-	so best to restrict by default
+    ** add
+    ** rm
+    ** mv
+    ** update-index
+    ** status
+    ** clean (?)
 
-    So, we switched `add` and `rm` to default to "restrict", which made
-    usability problems much less severe and less frequent, but we still got
-    complaints because commands like:
-	git add <file-outside-sparse-specification>
-	git rm <file-outside-sparse-specification>
-    would silently do nothing.  We should instead print an error in those
-    cases to get usability right.
+....
+        Our original implementation for the first three of these commands was
+        "no restrict", but it had some severe usability issues:
 
-    update-index needs to be updated to match, and status and maybe clean
-    also need to be updated to specially handle untracked paths.
+          * `git add <somefile>` if honored and outside the sparse
+	    specification, can result in the file randomly disappearing later
+	    when some subsequent command is run (since various commands
+	    automatically clean up unmodified files outside the sparse
+	    specification).
+          * `git rm '*.jpg'` could very negatively surprise users if it deletes
+	    files outside the range of the user's interest.
+          * `git mv` has similar surprises when moving into or out of the cone,
+	    so best to restrict by default
 
-    There may be a difference in here between behavior A and behavior B in
-    terms of verboseness of errors or additional warnings.
+        So, we switched `add` and `rm` to default to "restrict", which made
+        usability problems much less severe and less frequent, but we still got
+        complaints because commands like:
+
+	    git add <file-outside-sparse-specification>
+	    git rm <file-outside-sparse-specification>
+
+        would silently do nothing.  We should instead print an error in those
+        cases to get usability right.
+
+        update-index needs to be updated to match, and status and maybe clean
+        also need to be updated to specially handle untracked paths.
+
+        There may be a difference in here between behavior A and behavior B in
+        terms of verboseness of errors or additional warnings.
+....
 
   * Commands falling under "restrict or no restrict dependent upon behavior
     A vs. behavior B"
 
-    * diff (with --cached or REVISION arguments)
-    * grep (with --cached or REVISION arguments)
-    * show (when given commit arguments)
-    * blame (only matters when one or more -C flags passed)
-      * and annotate
-    * log
-      * and variants: shortlog, gitk, show-branch, whatchanged, rev-list
-    * ls-files
-    * diff-index
-    * diff-tree
-    * ls-tree
+    ** diff (with --cached or REVISION arguments)
+    ** grep (with --cached or REVISION arguments)
+    ** show (when given commit arguments)
+    ** blame (only matters when one or more -C flags passed)
+      *** and annotate
+    ** log
+      *** and variants: shortlog, gitk, show-branch, whatchanged, rev-list
+    ** ls-files
+    ** diff-index
+    ** diff-tree
+    ** ls-tree
 
     For now, we default to behavior B for these, which want a default of
     "no restrict".
@@ -749,7 +766,7 @@
     implemented.
 
 
-=== Sparse specification vs. sparsity patterns ===
+== Sparse specification vs. sparsity patterns ==
 
 In a well-behaved situation, the sparse specification is given directly
 by the $GIT_DIR/info/sparse-checkout file.  However, it can transiently
@@ -821,45 +838,48 @@
 operate full-tree.
 
 
-=== Implementation Questions ===
+== Implementation Questions ==
 
-  * Do the options --scope={sparse,all} sound good to others?  Are there better
-    options?
-    * Names in use, or appearing in patches, or previously suggested:
-      * --sparse/--dense
-      * --ignore-skip-worktree-bits
-      * --ignore-skip-worktree-entries
-      * --ignore-sparsity
-      * --[no-]restrict-to-sparse-paths
-      * --full-tree/--sparse-tree
-      * --[no-]restrict
-      * --scope={sparse,all}
-      * --focus/--unfocus
-      * --limit/--unlimited
-    * Rationale making me lean slightly towards --scope={sparse,all}:
-      * We want a name that works for many commands, so we need a name that
+  * Do the options --scope={sparse,all} sound good to others?  Are there better options?
+
+    ** Names in use, or appearing in patches, or previously suggested:
+
+      *** --sparse/--dense
+      *** --ignore-skip-worktree-bits
+      *** --ignore-skip-worktree-entries
+      *** --ignore-sparsity
+      *** --[no-]restrict-to-sparse-paths
+      *** --full-tree/--sparse-tree
+      *** --[no-]restrict
+      *** --scope={sparse,all}
+      *** --focus/--unfocus
+      *** --limit/--unlimited
+
+    ** Rationale making me lean slightly towards --scope={sparse,all}:
+
+      *** We want a name that works for many commands, so we need a name that
 	does not conflict
-      * We know that we have more than two possible usecases, so it is best
+      *** We know that we have more than two possible usecases, so it is best
 	to avoid a flag that appears to be binary.
-      * --scope={sparse,all} isn't overly long and seems relatively
+      *** --scope={sparse,all} isn't overly long and seems relatively
 	explanatory
-      * `--sparse`, as used in add/rm/mv, is totally backwards for
+      *** `--sparse`, as used in add/rm/mv, is totally backwards for
 	grep/log/etc.  Changing the meaning of `--sparse` for these
 	commands would fix the backwardness, but possibly break existing
 	scripts.  Using a new name pairing would allow us to treat
 	`--sparse` in these commands as a deprecated alias.
-      * There is a different `--sparse`/`--dense` pair for commands using
+      *** There is a different `--sparse`/`--dense` pair for commands using
 	revision machinery, so using that naming might cause confusion
-      * There is also a `--sparse` in both pack-objects and show-branch, which
+      *** There is also a `--sparse` in both pack-objects and show-branch, which
 	don't conflict but do suggest that `--sparse` is overloaded
-      * The name --ignore-skip-worktree-bits is a double negative, is
+      *** The name --ignore-skip-worktree-bits is a double negative, is
 	quite a mouthful, refers to an implementation detail that many
 	users may not be familiar with, and we'd need a negation for it
 	which would probably be even more ridiculously long.  (But we
 	can make --ignore-skip-worktree-bits a deprecated alias for
 	--no-restrict.)
 
-  * If a config option is added (sparse.scope?) what should the values and
+  ** If a config option is added (sparse.scope?) what should the values and
     description be?  "sparse" (behavior A), "worktree-sparse-history-dense"
     (behavior B), "dense" (behavior C)?  There's a risk of confusion,
     because even for Behaviors A and B we want some commands to be
@@ -868,19 +888,20 @@
     the primary difference we are focusing is just the history-querying
     commands (log/diff/grep).  Previous config suggestion here: [13]
 
-  * Is `--no-expand` a good alias for ls-files's `--sparse` option?
+  ** Is `--no-expand` a good alias for ls-files's `--sparse` option?
     (`--sparse` does not map to either `--scope=sparse` or `--scope=all`,
     because in non-cone mode it does nothing and in cone-mode it shows the
     sparse directory entries which are technically outside the sparse
     specification)
 
-  * Under Behavior A:
-    * Does ls-files' `--no-expand` override the default `--scope=all`, or
-      does it need an extra flag?
-    * Does ls-files' `-t` option imply `--scope=all`?
-    * Does update-index's `--[no-]skip-worktree` option imply `--scope=all`?
+  ** Under Behavior A:
 
-  * sparse-checkout: once behavior A is fully implemented, should we take
+    *** Does ls-files' `--no-expand` override the default `--scope=all`, or
+      does it need an extra flag?
+    *** Does ls-files' `-t` option imply `--scope=all`?
+    *** Does update-index's `--[no-]skip-worktree` option imply `--scope=all`?
+
+  ** sparse-checkout: once behavior A is fully implemented, should we take
     an interim measure to ease people into switching the default?  Namely,
     if folks are not already in a sparse checkout, then require
     `sparse-checkout init/set` to take a
@@ -892,7 +913,7 @@
     is seamless for them.
 
 
-=== Implementation Goals/Plans ===
+== Implementation Goals/Plans ==
 
  * Get buy-in on this document in general.
 
@@ -910,25 +931,26 @@
    request that they not trigger this bug." flag
 
  * Flags & Config
-   * Make `--sparse` in add/rm/mv a deprecated alias for `--scope=all`
-   * Make `--ignore-skip-worktree-bits` in checkout-index/checkout/restore
+
+   ** Make `--sparse` in add/rm/mv a deprecated alias for `--scope=all`
+   ** Make `--ignore-skip-worktree-bits` in checkout-index/checkout/restore
      a deprecated aliases for `--scope=all`
-   * Create config option (sparse.scope?), tie it to the "Cliff notes"
+   ** Create config option (sparse.scope?), tie it to the "Cliff notes"
      overview
 
-   * Add --scope=sparse (and --scope=all) flag to each of the history querying
+   ** Add --scope=sparse (and --scope=all) flag to each of the history querying
      commands.  IMPORTANT: make sure diff machinery changes don't mess with
      format-patch, fast-export, etc.
 
-=== Known bugs ===
+== Known bugs ==
 
 This list used to be a lot longer (see e.g. [1,2,3,4,5,6,7,8,9]), but we've
 been working on it.
 
-0. Behavior A is not well supported in Git.  (Behavior B didn't used to
+1. Behavior A is not well supported in Git.  (Behavior B didn't used to
    be either, but was the easier of the two to implement.)
 
-1. am and apply:
+2. am and apply:
 
    apply, without `--index` or `--cached`, relies on files being present
    in the working copy, and also writes to them unconditionally.  As
@@ -948,7 +970,7 @@
    files and then complain that those vivified files would be
    overwritten by merge.
 
-2. reset --hard:
+3. reset --hard:
 
    reset --hard provides confusing error message (works correctly, but
    misleads the user into believing it didn't):
@@ -971,13 +993,13 @@
     `git reset --hard` DID remove addme from the index and the working tree, contrary
     to the error message, but in line with how reset --hard should behave.
 
-3. read-tree
+4. read-tree
 
    `read-tree` doesn't apply the 'SKIP_WORKTREE' bit to *any* of the
    entries it reads into the index, resulting in all your files suddenly
    appearing to be "deleted".
 
-4. Checkout, restore:
+5. Checkout, restore:
 
    These command do not handle path & revision arguments appropriately:
 
@@ -1030,7 +1052,7 @@
     S tracked
     H tracked-but-maybe-skipped
 
-5. checkout and restore --staged, continued:
+6. checkout and restore --staged, continued:
 
    These commands do not correctly scope operations to the sparse
    specification, and make it worse by not setting important SKIP_WORKTREE
@@ -1046,56 +1068,82 @@
    the sparse specification, but then it will be important to set the
    SKIP_WORKTREE bits appropriately.
 
-6. Performance issues; see:
-    https://lore.kernel.org/git/CABPp-BEkJQoKZsQGCYioyga_uoDQ6iBeW+FKr8JhyuuTMK1RDw@mail.gmail.com/
+7. Performance issues; see:
+
+   https://lore.kernel.org/git/CABPp-BEkJQoKZsQGCYioyga_uoDQ6iBeW+FKr8JhyuuTMK1RDw@mail.gmail.com/
 
 
-=== Reference Emails ===
+== Reference Emails ==
 
 Emails that detail various bugs we've had in sparse-checkout:
 
-[1] (Original descriptions of behavior A & behavior B)
-    https://lore.kernel.org/git/CABPp-BGJ_Nvi5TmgriD9Bh6eNXE2EDq2f8e8QKXAeYG3BxZafA@mail.gmail.com/
-[2] (Fix stash applications in sparse checkouts; bugs from behavioral differences)
-    https://lore.kernel.org/git/ccfedc7140dbf63ba26a15f93bd3885180b26517.1606861519.git.gitgitgadget@gmail.com/
-[3] (Present-despite-skipped entries)
-    https://lore.kernel.org/git/11d46a399d26c913787b704d2b7169cafc28d639.1642175983.git.gitgitgadget@gmail.com/
-[4] (Clone --no-checkout interaction)
-    https://lore.kernel.org/git/pull.801.v2.git.git.1591324899170.gitgitgadget@gmail.com/ (clone --no-checkout)
-[5] (The need for update_sparsity() and avoiding `read-tree -mu HEAD`)
-    https://lore.kernel.org/git/3a1f084641eb47515b5a41ed4409a36128913309.1585270142.git.gitgitgadget@gmail.com/
-[6] (SKIP_WORKTREE is advisory, not mandatory)
-    https://lore.kernel.org/git/844306c3e86ef67591cc086decb2b760e7d710a3.1585270142.git.gitgitgadget@gmail.com/
-[7] (`worktree add` should copy sparsity settings from current worktree)
-    https://lore.kernel.org/git/c51cb3714e7b1d2f8c9370fe87eca9984ff4859f.1644269584.git.gitgitgadget@gmail.com/
-[8] (Avoid negative surprises in add, rm, and mv)
-    https://lore.kernel.org/git/cover.1617914011.git.matheus.bernardino@usp.br/
-    https://lore.kernel.org/git/pull.1018.v4.git.1632497954.gitgitgadget@gmail.com/
-[9] (Move from out-of-cone to in-cone)
-    https://lore.kernel.org/git/20220630023737.473690-6-shaoxuan.yuan02@gmail.com/
-    https://lore.kernel.org/git/20220630023737.473690-4-shaoxuan.yuan02@gmail.com/
-[10] (Unnecessarily downloading objects outside sparse specification)
-     https://lore.kernel.org/git/CAOLTT8QfwOi9yx_qZZgyGa8iL8kHWutEED7ok_jxwTcYT_hf9Q@mail.gmail.com/
+[1] (Original descriptions of behavior A & behavior B):
 
-[11] (Stolee's comments on high-level usecases)
-     https://lore.kernel.org/git/1a1e33f6-3514-9afc-0a28-5a6b85bd8014@gmail.com/
+https://lore.kernel.org/git/CABPp-BGJ_Nvi5TmgriD9Bh6eNXE2EDq2f8e8QKXAeYG3BxZafA@mail.gmail.com/
+
+[2] (Fix stash applications in sparse checkouts; bugs from behavioral differences):
+
+https://lore.kernel.org/git/ccfedc7140dbf63ba26a15f93bd3885180b26517.1606861519.git.gitgitgadget@gmail.com/
+
+[3] (Present-despite-skipped entries):
+
+https://lore.kernel.org/git/11d46a399d26c913787b704d2b7169cafc28d639.1642175983.git.gitgitgadget@gmail.com/
+
+[4] (Clone --no-checkout interaction):
+
+https://lore.kernel.org/git/pull.801.v2.git.git.1591324899170.gitgitgadget@gmail.com/ (clone --no-checkout)
+
+[5] (The need for update_sparsity() and avoiding `read-tree -mu HEAD`):
+
+https://lore.kernel.org/git/3a1f084641eb47515b5a41ed4409a36128913309.1585270142.git.gitgitgadget@gmail.com/
+
+[6] (SKIP_WORKTREE is advisory, not mandatory):
+
+https://lore.kernel.org/git/844306c3e86ef67591cc086decb2b760e7d710a3.1585270142.git.gitgitgadget@gmail.com/
+
+[7] (`worktree add` should copy sparsity settings from current worktree):
+
+https://lore.kernel.org/git/c51cb3714e7b1d2f8c9370fe87eca9984ff4859f.1644269584.git.gitgitgadget@gmail.com/
+
+[8] (Avoid negative surprises in add, rm, and mv):
+
+  * https://lore.kernel.org/git/cover.1617914011.git.matheus.bernardino@usp.br/
+  * https://lore.kernel.org/git/pull.1018.v4.git.1632497954.gitgitgadget@gmail.com/
+
+[9] (Move from out-of-cone to in-cone):
+
+  * https://lore.kernel.org/git/20220630023737.473690-6-shaoxuan.yuan02@gmail.com/
+  * https://lore.kernel.org/git/20220630023737.473690-4-shaoxuan.yuan02@gmail.com/
+
+[10] (Unnecessarily downloading objects outside sparse specification):
+
+https://lore.kernel.org/git/CAOLTT8QfwOi9yx_qZZgyGa8iL8kHWutEED7ok_jxwTcYT_hf9Q@mail.gmail.com/
+
+[11] (Stolee's comments on high-level usecases):
+
+https://lore.kernel.org/git/1a1e33f6-3514-9afc-0a28-5a6b85bd8014@gmail.com/
 
 [12] Others commenting on eventually switching default to behavior A:
+
   * https://lore.kernel.org/git/xmqqh719pcoo.fsf@gitster.g/
   * https://lore.kernel.org/git/xmqqzgeqw0sy.fsf@gitster.g/
   * https://lore.kernel.org/git/a86af661-cf58-a4e5-0214-a67d3a794d7e@github.com/
 
-[13] Previous config name suggestion and description
-  * https://lore.kernel.org/git/CABPp-BE6zW0nJSStcVU=_DoDBnPgLqOR8pkTXK3dW11=T01OhA@mail.gmail.com/
+[13] Previous config name suggestion and description:
+
+   https://lore.kernel.org/git/CABPp-BE6zW0nJSStcVU=_DoDBnPgLqOR8pkTXK3dW11=T01OhA@mail.gmail.com/
 
 [14] Tangential issue: switch to cone mode as default sparse specification mechanism:
-  https://lore.kernel.org/git/a1b68fd6126eb341ef3637bb93fedad4309b36d0.1650594746.git.gitgitgadget@gmail.com/
+
+https://lore.kernel.org/git/a1b68fd6126eb341ef3637bb93fedad4309b36d0.1650594746.git.gitgitgadget@gmail.com/
 
 [15] Lengthy email on grep behavior, covering what should be searched:
-  * https://lore.kernel.org/git/CABPp-BGVO3QdbfE84uF_3QDF0-y2iHHh6G5FAFzNRfeRitkuHw@mail.gmail.com/
+
+https://lore.kernel.org/git/CABPp-BGVO3QdbfE84uF_3QDF0-y2iHHh6G5FAFzNRfeRitkuHw@mail.gmail.com/
 
 [16] Email explaining sparsity patterns vs. SKIP_WORKTREE and history operations,
      search for the parenthetical comment starting "We do not check".
-    https://lore.kernel.org/git/CABPp-BFsCPPNOZ92JQRJeGyNd0e-TCW-LcLyr0i_+VSQJP+GCg@mail.gmail.com/
+
+https://lore.kernel.org/git/CABPp-BFsCPPNOZ92JQRJeGyNd0e-TCW-LcLyr0i_+VSQJP+GCg@mail.gmail.com/
 
 [17] https://lore.kernel.org/git/20220207190320.2960362-1-jonathantanmy@google.com/
diff --git a/Documentation/urls-remotes.adoc b/Documentation/urls-remotes.adoc
index 9b10151..57b1646 100644
--- a/Documentation/urls-remotes.adoc
+++ b/Documentation/urls-remotes.adoc
@@ -92,5 +92,47 @@
 ------------
 
 
+[[UPSTREAM-BRANCHES]]
+UPSTREAM BRANCHES
+-----------------
 
+Branches in Git can optionally have an upstream remote branch.
+Git defaults to using the upstream branch for remote operations, for example:
 
+* It's the default for `git pull` or `git fetch` with no arguments.
+* It's the default for `git push` with no arguments, with some exceptions.
+  For example, you can use the `branch.<name>.pushRemote` option to push
+  to a different remote than you pull from, and by default with
+  `push.default=simple` the upstream branch you configure must have
+  the same name.
+* Various commands, including `git checkout` and `git status`, will
+  show you how many commits have been added to your current branch and
+  the upstream since you forked from it, for example "Your branch and
+  '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"
+fields. For example, if `main`'s upstream is `origin/main`:
+
+------------
+[branch "main"]
+   remote = origin
+   merge = refs/heads/main
+------------
+
+You can set an upstream branch explicitly with
+`git push --set-upstream <remote> <branch>`
+but Git will often automatically set the upstream for you, for example:
+
+* When you clone a repository, Git will automatically set the upstream
+  for the default branch.
+* If you have the `push.autoSetupRemote` configuration option set,
+  `git push` will automatically set the upstream the first time you push
+  a branch.
+* Checking out a remote-tracking branch with `git checkout <branch>`
+  will automatically create a local branch with that name and set
+  the upstream to the remote branch.
+
+[NOTE]
+Upstream branches are sometimes referred to as "tracking information",
+as in "set the branch's tracking information".
diff --git a/Makefile b/Makefile
index f79c905..7e0f77e 100644
--- a/Makefile
+++ b/Makefile
@@ -927,16 +927,20 @@
 TEST_SHELL_PATH = $(SHELL_PATH)
 
 LIB_FILE = libgit.a
-XDIFF_LIB = xdiff/lib.a
-REFTABLE_LIB = reftable/libreftable.a
+
 ifdef DEBUG
-RUST_LIB = target/debug/libgitcore.a
+RUST_TARGET_DIR = target/debug
 else
-RUST_LIB = target/release/libgitcore.a
+RUST_TARGET_DIR = target/release
 endif
 
-# xdiff and reftable libs may in turn depend on what is in libgit.a
-GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)
+ifeq ($(uname_S),Windows)
+RUST_LIB = $(RUST_TARGET_DIR)/gitcore.lib
+else
+RUST_LIB = $(RUST_TARGET_DIR)/libgitcore.a
+endif
+
+GITLIBS = common-main.o $(LIB_FILE)
 EXTLIBS =
 
 GIT_USER_AGENT = git/$(GIT_VERSION)
@@ -1248,7 +1252,27 @@
 LIB_OBJS += refs/packed-backend.o
 LIB_OBJS += refs/ref-cache.o
 LIB_OBJS += refspec.o
+LIB_OBJS += reftable/basics.o
+LIB_OBJS += reftable/block.o
+LIB_OBJS += reftable/blocksource.o
+LIB_OBJS += reftable/error.o
+LIB_OBJS += reftable/fsck.o
+LIB_OBJS += reftable/iter.o
+LIB_OBJS += reftable/merged.o
+LIB_OBJS += reftable/pq.o
+LIB_OBJS += reftable/record.o
+LIB_OBJS += reftable/stack.o
+LIB_OBJS += reftable/system.o
+LIB_OBJS += reftable/table.o
+LIB_OBJS += reftable/tree.o
+LIB_OBJS += reftable/writer.o
 LIB_OBJS += remote.o
+LIB_OBJS += repack.o
+LIB_OBJS += repack-cruft.o
+LIB_OBJS += repack-filtered.o
+LIB_OBJS += repack-geometry.o
+LIB_OBJS += repack-midx.o
+LIB_OBJS += repack-promisor.o
 LIB_OBJS += replace-object.o
 LIB_OBJS += repo-settings.o
 LIB_OBJS += repository.o
@@ -1322,6 +1346,13 @@
 LIB_OBJS += ws.o
 LIB_OBJS += wt-status.o
 LIB_OBJS += xdiff-interface.o
+LIB_OBJS += xdiff/xdiffi.o
+LIB_OBJS += xdiff/xemit.o
+LIB_OBJS += xdiff/xhistogram.o
+LIB_OBJS += xdiff/xmerge.o
+LIB_OBJS += xdiff/xpatience.o
+LIB_OBJS += xdiff/xprepare.o
+LIB_OBJS += xdiff/xutils.o
 
 BUILTIN_OBJS += builtin/add.o
 BUILTIN_OBJS += builtin/am.o
@@ -1538,6 +1569,9 @@
 ifdef WITH_RUST
 BASIC_CFLAGS += -DWITH_RUST
 GITLIBS += $(RUST_LIB)
+ifeq ($(uname_S),Windows)
+EXTLIBS += -luserenv
+endif
 endif
 
 ifdef SANITIZE
@@ -2756,31 +2790,6 @@
 .PHONY: reconfigure # This is a convenience target.
 endif
 
-XDIFF_OBJS += xdiff/xdiffi.o
-XDIFF_OBJS += xdiff/xemit.o
-XDIFF_OBJS += xdiff/xhistogram.o
-XDIFF_OBJS += xdiff/xmerge.o
-XDIFF_OBJS += xdiff/xpatience.o
-XDIFF_OBJS += xdiff/xprepare.o
-XDIFF_OBJS += xdiff/xutils.o
-.PHONY: xdiff-objs
-xdiff-objs: $(XDIFF_OBJS)
-
-REFTABLE_OBJS += reftable/basics.o
-REFTABLE_OBJS += reftable/block.o
-REFTABLE_OBJS += reftable/blocksource.o
-REFTABLE_OBJS += reftable/error.o
-REFTABLE_OBJS += reftable/fsck.o
-REFTABLE_OBJS += reftable/iter.o
-REFTABLE_OBJS += reftable/merged.o
-REFTABLE_OBJS += reftable/pq.o
-REFTABLE_OBJS += reftable/record.o
-REFTABLE_OBJS += reftable/stack.o
-REFTABLE_OBJS += reftable/system.o
-REFTABLE_OBJS += reftable/table.o
-REFTABLE_OBJS += reftable/tree.o
-REFTABLE_OBJS += reftable/writer.o
-
 TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
 
 .PHONY: test-objs
@@ -2801,9 +2810,8 @@
 OBJECTS += $(SCALAR_OBJS)
 OBJECTS += $(PROGRAM_OBJS)
 OBJECTS += $(TEST_OBJS)
-OBJECTS += $(XDIFF_OBJS)
 OBJECTS += $(FUZZ_OBJS)
-OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
+OBJECTS += $(REFTABLE_TEST_OBJS)
 OBJECTS += $(UNIT_TEST_OBJS)
 OBJECTS += $(CLAR_TEST_OBJS)
 OBJECTS += $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
@@ -2961,12 +2969,6 @@
 .PHONY: rust
 rust: $(RUST_LIB)
 
-$(XDIFF_LIB): $(XDIFF_OBJS)
-	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
-
-$(REFTABLE_LIB): $(REFTABLE_OBJS)
-	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
-
 export DEFAULT_EDITOR DEFAULT_PAGER
 
 Documentation/GIT-EXCLUDED-PROGRAMS: FORCE
@@ -3805,7 +3807,7 @@
 	$(RM) git.rc git.res
 	$(RM) $(OBJECTS)
 	$(RM) headless-git.o
-	$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)
+	$(RM) $(LIB_FILE)
 	$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS)
 	$(RM) $(TEST_PROGRAMS)
 	$(RM) $(FUZZ_PROGRAMS)
@@ -3999,8 +4001,6 @@
 
 LIBGIT_PUB_OBJS += contrib/libgit-sys/public_symbol_export.o
 LIBGIT_PUB_OBJS += libgit.a
-LIBGIT_PUB_OBJS += reftable/libreftable.a
-LIBGIT_PUB_OBJS += xdiff/lib.a
 
 LIBGIT_PARTIAL_EXPORT = contrib/libgit-sys/partial_symbol_export.o
 
diff --git a/add-interactive.c b/add-interactive.c
index 6ffe64c..68fc095 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -244,7 +244,8 @@ static void find_unique_prefixes(struct prefix_item_list *list)
 
 static ssize_t find_unique(const char *string, struct prefix_item_list *list)
 {
-	int index = string_list_find_insert_index(&list->sorted, string, 1);
+	bool exact_match;
+	size_t index = string_list_find_insert_index(&list->sorted, string, &exact_match);
 	struct string_list_item *item;
 
 	if (list->items.nr != list->sorted.nr)
@@ -252,8 +253,8 @@ static ssize_t find_unique(const char *string, struct prefix_item_list *list)
 		    " vs %"PRIuMAX")",
 		    (uintmax_t)list->items.nr, (uintmax_t)list->sorted.nr);
 
-	if (index < 0)
-		item = list->sorted.items[-1 - index].util;
+	if (exact_match)
+		item = list->sorted.items[index].util;
 	else if (index > 0 &&
 		 starts_with(list->sorted.items[index - 1].string, string))
 		return -1;
diff --git a/add-patch.c b/add-patch.c
index b0389c5..ae9a20d 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -956,6 +956,7 @@ static int split_hunk(struct add_p_state *s, struct file_diff *file_diff,
 			* sizeof(*hunk));
 	hunk = file_diff->hunk + hunk_index;
 	hunk->splittable_into = 1;
+	hunk->use = UNDECIDED_HUNK;
 	memset(hunk + 1, 0, (splittable_into - 1) * sizeof(*hunk));
 
 	header = &hunk->header;
@@ -1057,7 +1058,7 @@ static int split_hunk(struct add_p_state *s, struct file_diff *file_diff,
 
 		hunk++;
 		hunk->splittable_into = 1;
-		hunk->use = hunk[-1].use;
+		hunk->use = UNDECIDED_HUNK;
 		header = &hunk->header;
 
 		header->old_count = header->new_count = context_line_count;
@@ -1184,19 +1185,29 @@ static ssize_t recount_edited_hunk(struct add_p_state *s, struct hunk *hunk,
 {
 	struct hunk_header *header = &hunk->header;
 	size_t i;
+	char ch, marker = ' ';
 
+	hunk->splittable_into = 0;
 	header->old_count = header->new_count = 0;
 	for (i = hunk->start; i < hunk->end; ) {
-		switch(normalize_marker(&s->plain.buf[i])) {
+		ch = normalize_marker(&s->plain.buf[i]);
+		switch (ch) {
 		case '-':
 			header->old_count++;
+			if (marker == ' ')
+				hunk->splittable_into++;
+			marker = ch;
 			break;
 		case '+':
 			header->new_count++;
+			if (marker == ' ')
+				hunk->splittable_into++;
+			marker = ch;
 			break;
 		case ' ':
 			header->old_count++;
 			header->new_count++;
+			marker = ch;
 			break;
 		}
 
@@ -1397,17 +1408,39 @@ static size_t display_hunks(struct add_p_state *s,
 }
 
 static const char help_patch_remainder[] =
-N_("j - leave this hunk undecided, see next undecided hunk\n"
-   "J - leave this hunk undecided, see next hunk\n"
-   "k - leave this hunk undecided, see previous undecided hunk\n"
-   "K - leave this hunk undecided, see previous hunk\n"
+N_("j - go to the next undecided hunk, roll over at the bottom\n"
+   "J - go to the next hunk, roll over at the bottom\n"
+   "k - go to the previous undecided hunk, roll over at the top\n"
+   "K - go to the previous hunk, roll over at the top\n"
    "g - select a hunk to go to\n"
    "/ - search for a hunk matching the given regex\n"
    "s - split the current hunk into smaller hunks\n"
    "e - manually edit the current hunk\n"
-   "p - print the current hunk, 'P' to use the pager\n"
+   "p - print the current hunk\n"
+   "P - print the current hunk using the pager\n"
    "? - print help\n");
 
+static size_t dec_mod(size_t a, size_t m)
+{
+	return a > 0 ? a - 1 : m - 1;
+}
+
+static size_t inc_mod(size_t a, size_t m)
+{
+	return a < m - 1 ? a + 1 : 0;
+}
+
+static bool get_first_undecided(const struct file_diff *file_diff, size_t *idx)
+{
+	for (size_t i = 0; i < file_diff->hunk_nr; i++) {
+		if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
+			*idx = i;
+			return true;
+		}
+	}
+	return false;
+}
+
 static int patch_update_file(struct add_p_state *s,
 			     struct file_diff *file_diff)
 {
@@ -1418,15 +1451,6 @@ static int patch_update_file(struct add_p_state *s,
 	struct child_process cp = CHILD_PROCESS_INIT;
 	int colored = !!s->colored.len, quit = 0, use_pager = 0;
 	enum prompt_mode_type prompt_mode_type;
-	enum {
-		ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0,
-		ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1,
-		ALLOW_GOTO_NEXT_HUNK = 1 << 2,
-		ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3,
-		ALLOW_SEARCH_AND_GOTO = 1 << 4,
-		ALLOW_SPLIT = 1 << 5,
-		ALLOW_EDIT = 1 << 6
-	} permitted = 0;
 
 	/* Empty added files have no hunks */
 	if (!file_diff->hunk_nr && !file_diff->added)
@@ -1436,6 +1460,16 @@ static int patch_update_file(struct add_p_state *s,
 	render_diff_header(s, file_diff, colored, &s->buf);
 	fputs(s->buf.buf, stdout);
 	for (;;) {
+		enum {
+			ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0,
+			ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1,
+			ALLOW_GOTO_NEXT_HUNK = 1 << 2,
+			ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3,
+			ALLOW_SEARCH_AND_GOTO = 1 << 4,
+			ALLOW_SPLIT = 1 << 5,
+			ALLOW_EDIT = 1 << 6
+		} permitted = 0;
+
 		if (hunk_index >= file_diff->hunk_nr)
 			hunk_index = 0;
 		hunk = file_diff->hunk_nr
@@ -1445,13 +1479,17 @@ static int patch_update_file(struct add_p_state *s,
 		undecided_next = -1;
 
 		if (file_diff->hunk_nr) {
-			for (i = hunk_index - 1; i >= 0; i--)
+			for (i = dec_mod(hunk_index, file_diff->hunk_nr);
+			     i != hunk_index;
+			     i = dec_mod(i, file_diff->hunk_nr))
 				if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
 					undecided_previous = i;
 					break;
 				}
 
-			for (i = hunk_index + 1; i < file_diff->hunk_nr; i++)
+			for (i = inc_mod(hunk_index, file_diff->hunk_nr);
+			     i != hunk_index;
+			     i = inc_mod(i, file_diff->hunk_nr))
 				if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
 					undecided_next = i;
 					break;
@@ -1485,7 +1523,7 @@ static int patch_update_file(struct add_p_state *s,
 				permitted |= ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK;
 				strbuf_addstr(&s->buf, ",k");
 			}
-			if (hunk_index) {
+			if (file_diff->hunk_nr > 1) {
 				permitted |= ALLOW_GOTO_PREVIOUS_HUNK;
 				strbuf_addstr(&s->buf, ",K");
 			}
@@ -1493,7 +1531,7 @@ static int patch_update_file(struct add_p_state *s,
 				permitted |= ALLOW_GOTO_NEXT_UNDECIDED_HUNK;
 				strbuf_addstr(&s->buf, ",j");
 			}
-			if (hunk_index + 1 < file_diff->hunk_nr) {
+			if (file_diff->hunk_nr > 1) {
 				permitted |= ALLOW_GOTO_NEXT_HUNK;
 				strbuf_addstr(&s->buf, ",J");
 			}
@@ -1510,7 +1548,7 @@ static int patch_update_file(struct add_p_state *s,
 				permitted |= ALLOW_EDIT;
 				strbuf_addstr(&s->buf, ",e");
 			}
-			strbuf_addstr(&s->buf, ",p");
+			strbuf_addstr(&s->buf, ",p,P");
 		}
 		if (file_diff->deleted)
 			prompt_mode_type = PROMPT_DELETION;
@@ -1558,6 +1596,8 @@ static int patch_update_file(struct add_p_state *s,
 					if (hunk->use == UNDECIDED_HUNK)
 						hunk->use = USE_HUNK;
 				}
+				if (!get_first_undecided(file_diff, &hunk_index))
+					hunk_index = 0;
 			} else if (hunk->use == UNDECIDED_HUNK) {
 				hunk->use = USE_HUNK;
 			}
@@ -1568,6 +1608,8 @@ static int patch_update_file(struct add_p_state *s,
 					if (hunk->use == UNDECIDED_HUNK)
 						hunk->use = SKIP_HUNK;
 				}
+				if (!get_first_undecided(file_diff, &hunk_index))
+					hunk_index = 0;
 			} else if (hunk->use == UNDECIDED_HUNK) {
 				hunk->use = SKIP_HUNK;
 			}
@@ -1577,24 +1619,25 @@ static int patch_update_file(struct add_p_state *s,
 			}
 		} else if (s->answer.buf[0] == 'K') {
 			if (permitted & ALLOW_GOTO_PREVIOUS_HUNK)
-				hunk_index--;
+				hunk_index = dec_mod(hunk_index,
+						     file_diff->hunk_nr);
 			else
-				err(s, _("No previous hunk"));
+				err(s, _("No other hunk"));
 		} else if (s->answer.buf[0] == 'J') {
 			if (permitted & ALLOW_GOTO_NEXT_HUNK)
 				hunk_index++;
 			else
-				err(s, _("No next hunk"));
+				err(s, _("No other hunk"));
 		} else if (s->answer.buf[0] == 'k') {
 			if (permitted & ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK)
 				hunk_index = undecided_previous;
 			else
-				err(s, _("No previous hunk"));
+				err(s, _("No other undecided hunk"));
 		} else if (s->answer.buf[0] == 'j') {
 			if (permitted & ALLOW_GOTO_NEXT_UNDECIDED_HUNK)
 				hunk_index = undecided_next;
 			else
-				err(s, _("No next hunk"));
+				err(s, _("No other undecided hunk"));
 		} else if (s->answer.buf[0] == 'g') {
 			char *pend;
 			unsigned long response;
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index ee6715f..5ca2ca3 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -495,7 +495,7 @@ static void batch_object_write(const char *obj_name,
 							    OBJECT_INFO_LOOKUP_REPLACE);
 		if (ret < 0) {
 			if (data->mode == S_IFGITLINK)
-				report_object_status(opt, oid_to_hex(&data->oid), &data->oid, "submodule");
+				report_object_status(opt, NULL, &data->oid, "submodule");
 			else
 				report_object_status(opt, obj_name, &data->oid, "missing");
 			return;
diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c
index fe3ebaa..d62005e 100644
--- a/builtin/commit-graph.c
+++ b/builtin/commit-graph.c
@@ -210,6 +210,8 @@ static int git_commit_graph_write_config(const char *var, const char *value,
 {
 	if (!strcmp(var, "commitgraph.maxnewfilters"))
 		write_opts.max_new_filters = git_config_int(var, value, ctx->kvi);
+	else if (!strcmp(var, "commitgraph.changedpaths"))
+		opts.enable_changed_paths = git_config_bool(var, value) ? 1 : -1;
 	/*
 	 * No need to fall-back to 'git_default_config', since this was already
 	 * called in 'cmd_commit_graph()'.
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index dc2486f..7adbc55 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -931,9 +931,8 @@ static void handle_tag(const char *name, struct tag *tag)
 
 	/* handle signed tags */
 	if (message) {
-		const char *signature = strstr(message,
-					       "\n-----BEGIN PGP SIGNATURE-----\n");
-		if (signature)
+		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 "
@@ -950,7 +949,7 @@ static void handle_tag(const char *name, struct tag *tag)
 					oid_to_hex(&tag->object.oid));
 				/* fallthru */
 			case SIGN_STRIP:
-				message_size = signature + 1 - message;
+				message_size = sig_offset;
 				break;
 			}
 	}
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index 606c6ae..8714edf 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -188,6 +188,7 @@ static int global_argc;
 static const char **global_argv;
 static const char *global_prefix;
 
+static enum sign_mode signed_tag_mode = SIGN_VERBATIM;
 static enum sign_mode signed_commit_mode = SIGN_VERBATIM;
 
 /* Memory pools */
@@ -2963,6 +2964,43 @@ static void parse_new_commit(const char *arg)
 	b->last_commit = object_count_by_type[OBJ_COMMIT];
 }
 
+static void handle_tag_signature(struct strbuf *msg, const char *name)
+{
+	size_t sig_offset = parse_signed_buffer(msg->buf, msg->len);
+
+	/* If there is no signature, there is nothing to do. */
+	if (sig_offset >= msg->len)
+		return;
+
+	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 */
+	case SIGN_VERBATIM:
+		/* Nothing to do, the signature will be put into the imported tag. */
+		break;
+
+	/* Second, modes that remove the signature */
+	case SIGN_WARN_STRIP:
+		warning(_("stripping a tag signature for tag '%s'"), name);
+		/* fallthru */
+	case SIGN_STRIP:
+		/* Truncate the buffer to remove the signature */
+		strbuf_setlen(msg, sig_offset);
+		break;
+
+	/* Third, BUG */
+	default:
+		BUG("invalid signed_tag_mode value %d from tag '%s'",
+		    signed_tag_mode, name);
+	}
+}
+
 static void parse_new_tag(const char *arg)
 {
 	static struct strbuf msg = STRBUF_INIT;
@@ -3026,6 +3064,8 @@ static void parse_new_tag(const char *arg)
 	/* tag payload/message */
 	parse_data(&msg, 0, NULL);
 
+	handle_tag_signature(&msg, t->name);
+
 	/* build the tag object */
 	strbuf_reset(&new_data);
 
@@ -3546,6 +3586,9 @@ static int parse_one_option(const char *option)
 	} else if (skip_prefix(option, "signed-commits=", &option)) {
 		if (parse_sign_mode(option, &signed_commit_mode))
 			usagef(_("unknown --signed-commits mode '%s'"), option);
+	} else if (skip_prefix(option, "signed-tags=", &option)) {
+		if (parse_sign_mode(option, &signed_tag_mode))
+			usagef(_("unknown --signed-tags mode '%s'"), option);
 	} else if (!strcmp(option, "quiet")) {
 		show_stats = 0;
 		quiet = 1;
diff --git a/builtin/log.c b/builtin/log.c
index 8aa1777..c8319b8 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1406,13 +1406,12 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
 		 * can be added later if deemed desirable.
 		 */
 		struct diff_options opts;
-		struct strvec other_arg = STRVEC_INIT;
 		struct range_diff_options range_diff_opts = {
 			.creation_factor = rev->creation_factor,
 			.dual_color = 1,
 			.max_memory = RANGE_DIFF_MAX_MEMORY_DEFAULT,
 			.diffopt = &opts,
-			.other_arg = &other_arg
+			.log_arg = &rev->rdiff_log_arg
 		};
 
 		repo_diff_setup(the_repository, &opts);
@@ -1420,9 +1419,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
 		opts.use_color = rev->diffopt.use_color;
 		diff_setup_done(&opts);
 		fprintf_ln(rev->diffopt.file, "%s", rev->rdiff_title);
-		get_notes_args(&other_arg, rev);
 		show_range_diff(rev->rdiff1, rev->rdiff2, &range_diff_opts);
-		strvec_clear(&other_arg);
 	}
 }
 
@@ -2334,6 +2331,7 @@ int cmd_format_patch(int argc,
 		rev.rdiff_title = diff_title(&rdiff_title, reroll_count,
 					     _("Range-diff:"),
 					     _("Range-diff against v%d:"));
+		get_notes_args(&(rev.rdiff_log_arg), &rev);
 	}
 
 	/*
@@ -2493,6 +2491,7 @@ int cmd_format_patch(int argc,
 	rev.diffopt.no_free = 0;
 	release_revisions(&rev);
 	format_config_release(&cfg);
+	strvec_clear(&rev.rdiff_log_arg);
 	return 0;
 }
 
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 1bc082a..e54c0f7 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -38,13 +38,13 @@ int cmd_range_diff(int argc,
 		   struct repository *repo UNUSED)
 {
 	struct diff_options diffopt = { NULL };
-	struct strvec other_arg = STRVEC_INIT;
+	struct strvec log_arg = STRVEC_INIT;
 	struct strvec diff_merges_arg = STRVEC_INIT;
 	struct range_diff_options range_diff_opts = {
 		.creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT,
 		.max_memory = RANGE_DIFF_MAX_MEMORY_DEFAULT,
 		.diffopt = &diffopt,
-		.other_arg = &other_arg
+		.log_arg = &log_arg
 	};
 	int simple_color = -1, left_only = 0, right_only = 0;
 	struct option range_diff_options[] = {
@@ -53,7 +53,7 @@ int cmd_range_diff(int argc,
 			    N_("percentage by which creation is weighted")),
 		OPT_BOOL(0, "no-dual-color", &simple_color,
 			    N_("use simple diff colors")),
-		OPT_PASSTHRU_ARGV(0, "notes", &other_arg,
+		OPT_PASSTHRU_ARGV(0, "notes", &log_arg,
 				  N_("notes"), N_("passed to 'git log'"),
 				  PARSE_OPT_OPTARG),
 		OPT_PASSTHRU_ARGV(0, "diff-merges", &diff_merges_arg,
@@ -93,7 +93,7 @@ int cmd_range_diff(int argc,
 	/* If `--diff-merges` was specified, imply `--merges` */
 	if (diff_merges_arg.nr) {
 		range_diff_opts.include_merges = 1;
-		strvec_pushv(&other_arg, diff_merges_arg.v);
+		strvec_pushv(&log_arg, diff_merges_arg.v);
 	}
 
 	for (i = 0; i < argc; i++)
@@ -125,7 +125,7 @@ int cmd_range_diff(int argc,
 		strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
 		strbuf_addf(&range2, "%s..%s", argv[0], argv[2]);
 
-		strvec_pushv(&other_arg, argv +
+		strvec_pushv(&log_arg, argv +
 			     (dash_dash < 0 ? 3 : dash_dash));
 	} else if (dash_dash == 2 ||
 		   (dash_dash < 0 && argc > 1 &&
@@ -145,7 +145,7 @@ int cmd_range_diff(int argc,
 		strbuf_addstr(&range1, argv[0]);
 		strbuf_addstr(&range2, argv[1]);
 
-		strvec_pushv(&other_arg, argv +
+		strvec_pushv(&log_arg, argv +
 			     (dash_dash < 0 ? 2 : dash_dash));
 	} else if (dash_dash == 1 ||
 		   (dash_dash < 0 && argc > 0 &&
@@ -176,7 +176,7 @@ int cmd_range_diff(int argc,
 		strbuf_addf(&range1, "%s..%.*s", b, a_len, a);
 		strbuf_addf(&range2, "%.*s..%s", a_len, a, b);
 
-		strvec_pushv(&other_arg, argv +
+		strvec_pushv(&log_arg, argv +
 			     (dash_dash < 0 ? 1 : dash_dash));
 	} else
 		usage_msg_opt(_("need two commit ranges"),
@@ -188,7 +188,7 @@ int cmd_range_diff(int argc,
 	range_diff_opts.right_only = right_only;
 	res = show_range_diff(range1.buf, range2.buf, &range_diff_opts);
 
-	strvec_clear(&other_arg);
+	strvec_clear(&log_arg);
 	strvec_clear(&diff_merges_arg);
 	strbuf_release(&range1);
 	strbuf_release(&range2);
diff --git a/builtin/repack.c b/builtin/repack.c
index e873080..cfdb4c0 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -3,27 +3,18 @@
 
 #include "builtin.h"
 #include "config.h"
-#include "dir.h"
 #include "environment.h"
-#include "gettext.h"
-#include "hex.h"
 #include "parse-options.h"
 #include "path.h"
 #include "run-command.h"
 #include "server-info.h"
-#include "strbuf.h"
 #include "string-list.h"
-#include "strvec.h"
 #include "midx.h"
 #include "packfile.h"
 #include "prune-packed.h"
-#include "odb.h"
 #include "promisor-remote.h"
+#include "repack.h"
 #include "shallow.h"
-#include "pack.h"
-#include "pack-bitmap.h"
-#include "refs.h"
-#include "list-objects-filter-options.h"
 
 #define ALL_INTO_ONE 1
 #define LOOSEN_UNREACHABLE 2
@@ -33,8 +24,6 @@
 #define RETAIN_PACK 2
 
 static int pack_everything;
-static int delta_base_offset = 1;
-static int pack_kept_objects = -1;
 static int write_bitmaps = -1;
 static int use_delta_islands;
 static int run_update_server_info = 1;
@@ -53,31 +42,23 @@ static const char incremental_bitmap_conflict_error[] = N_(
 "--no-write-bitmap-index or disable the pack.writeBitmaps configuration."
 );
 
-struct pack_objects_args {
-	char *window;
-	char *window_memory;
-	char *depth;
-	char *threads;
-	unsigned long max_pack_size;
-	int no_reuse_delta;
-	int no_reuse_object;
-	int quiet;
-	int local;
-	int name_hash_version;
-	int path_walk;
-	struct list_objects_filter_options filter_options;
+struct repack_config_ctx {
+	struct pack_objects_args *po_args;
+	struct pack_objects_args *cruft_po_args;
 };
 
 static int repack_config(const char *var, const char *value,
 			 const struct config_context *ctx, void *cb)
 {
-	struct pack_objects_args *cruft_po_args = cb;
+	struct repack_config_ctx *repack_ctx = cb;
+	struct pack_objects_args *po_args = repack_ctx->po_args;
+	struct pack_objects_args *cruft_po_args = repack_ctx->cruft_po_args;
 	if (!strcmp(var, "repack.usedeltabaseoffset")) {
-		delta_base_offset = git_config_bool(var, value);
+		po_args->delta_base_offset = git_config_bool(var, value);
 		return 0;
 	}
 	if (!strcmp(var, "repack.packkeptobjects")) {
-		pack_kept_objects = git_config_bool(var, value);
+		po_args->pack_kept_objects = git_config_bool(var, value);
 		return 0;
 	}
 	if (!strcmp(var, "repack.writebitmaps") ||
@@ -116,1138 +97,10 @@ static int repack_config(const char *var, const char *value,
 	return git_default_config(var, value, ctx, cb);
 }
 
-static void pack_objects_args_release(struct pack_objects_args *args)
-{
-	free(args->window);
-	free(args->window_memory);
-	free(args->depth);
-	free(args->threads);
-	list_objects_filter_release(&args->filter_options);
-}
-
-struct existing_packs {
-	struct string_list kept_packs;
-	struct string_list non_kept_packs;
-	struct string_list cruft_packs;
-};
-
-#define EXISTING_PACKS_INIT { \
-	.kept_packs = STRING_LIST_INIT_DUP, \
-	.non_kept_packs = STRING_LIST_INIT_DUP, \
-	.cruft_packs = STRING_LIST_INIT_DUP, \
-}
-
-static int has_existing_non_kept_packs(const struct existing_packs *existing)
-{
-	return existing->non_kept_packs.nr || existing->cruft_packs.nr;
-}
-
-static void pack_mark_for_deletion(struct string_list_item *item)
-{
-	item->util = (void*)((uintptr_t)item->util | DELETE_PACK);
-}
-
-static void pack_unmark_for_deletion(struct string_list_item *item)
-{
-	item->util = (void*)((uintptr_t)item->util & ~DELETE_PACK);
-}
-
-static int pack_is_marked_for_deletion(struct string_list_item *item)
-{
-	return (uintptr_t)item->util & DELETE_PACK;
-}
-
-static void pack_mark_retained(struct string_list_item *item)
-{
-	item->util = (void*)((uintptr_t)item->util | RETAIN_PACK);
-}
-
-static int pack_is_retained(struct string_list_item *item)
-{
-	return (uintptr_t)item->util & RETAIN_PACK;
-}
-
-static void mark_packs_for_deletion_1(struct string_list *names,
-				      struct string_list *list)
-{
-	struct string_list_item *item;
-	const int hexsz = the_hash_algo->hexsz;
-
-	for_each_string_list_item(item, list) {
-		char *sha1;
-		size_t len = strlen(item->string);
-		if (len < hexsz)
-			continue;
-		sha1 = item->string + len - hexsz;
-
-		if (pack_is_retained(item)) {
-			pack_unmark_for_deletion(item);
-		} else if (!string_list_has_string(names, sha1)) {
-			/*
-			 * Mark this pack for deletion, which ensures
-			 * that this pack won't be included in a MIDX
-			 * (if `--write-midx` was given) and that we
-			 * will actually delete this pack (if `-d` was
-			 * given).
-			 */
-			pack_mark_for_deletion(item);
-		}
-	}
-}
-
-static void retain_cruft_pack(struct existing_packs *existing,
-			      struct packed_git *cruft)
-{
-	struct strbuf buf = STRBUF_INIT;
-	struct string_list_item *item;
-
-	strbuf_addstr(&buf, pack_basename(cruft));
-	strbuf_strip_suffix(&buf, ".pack");
-
-	item = string_list_lookup(&existing->cruft_packs, buf.buf);
-	if (!item)
-		BUG("could not find cruft pack '%s'", pack_basename(cruft));
-
-	pack_mark_retained(item);
-	strbuf_release(&buf);
-}
-
-static void mark_packs_for_deletion(struct existing_packs *existing,
-				    struct string_list *names)
-
-{
-	mark_packs_for_deletion_1(names, &existing->non_kept_packs);
-	mark_packs_for_deletion_1(names, &existing->cruft_packs);
-}
-
-static void remove_redundant_pack(const char *dir_name, const char *base_name)
-{
-	struct strbuf buf = STRBUF_INIT;
-	struct odb_source *source = the_repository->objects->sources;
-	struct multi_pack_index *m = get_multi_pack_index(source);
-	strbuf_addf(&buf, "%s.pack", base_name);
-	if (m && source->local && midx_contains_pack(m, buf.buf))
-		clear_midx_file(the_repository);
-	strbuf_insertf(&buf, 0, "%s/", dir_name);
-	unlink_pack_path(buf.buf, 1);
-	strbuf_release(&buf);
-}
-
-static void remove_redundant_packs_1(struct string_list *packs)
-{
-	struct string_list_item *item;
-	for_each_string_list_item(item, packs) {
-		if (!pack_is_marked_for_deletion(item))
-			continue;
-		remove_redundant_pack(packdir, item->string);
-	}
-}
-
-static void remove_redundant_existing_packs(struct existing_packs *existing)
-{
-	remove_redundant_packs_1(&existing->non_kept_packs);
-	remove_redundant_packs_1(&existing->cruft_packs);
-}
-
-static void existing_packs_release(struct existing_packs *existing)
-{
-	string_list_clear(&existing->kept_packs, 0);
-	string_list_clear(&existing->non_kept_packs, 0);
-	string_list_clear(&existing->cruft_packs, 0);
-}
-
-/*
- * Adds all packs hex strings (pack-$HASH) to either packs->non_kept
- * or packs->kept based on whether each pack has a corresponding
- * .keep file or not.  Packs without a .keep file are not to be kept
- * if we are going to pack everything into one file.
- */
-static void collect_pack_filenames(struct existing_packs *existing,
-				   const struct string_list *extra_keep)
-{
-	struct packfile_store *packs = the_repository->objects->packfiles;
-	struct packed_git *p;
-	struct strbuf buf = STRBUF_INIT;
-
-	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
-		int i;
-		const char *base;
-
-		if (!p->pack_local)
-			continue;
-
-		base = pack_basename(p);
-
-		for (i = 0; i < extra_keep->nr; i++)
-			if (!fspathcmp(base, extra_keep->items[i].string))
-				break;
-
-		strbuf_reset(&buf);
-		strbuf_addstr(&buf, base);
-		strbuf_strip_suffix(&buf, ".pack");
-
-		if ((extra_keep->nr > 0 && i < extra_keep->nr) || p->pack_keep)
-			string_list_append(&existing->kept_packs, buf.buf);
-		else if (p->is_cruft)
-			string_list_append(&existing->cruft_packs, buf.buf);
-		else
-			string_list_append(&existing->non_kept_packs, buf.buf);
-	}
-
-	string_list_sort(&existing->kept_packs);
-	string_list_sort(&existing->non_kept_packs);
-	string_list_sort(&existing->cruft_packs);
-	strbuf_release(&buf);
-}
-
-static void prepare_pack_objects(struct child_process *cmd,
-				 const struct pack_objects_args *args,
-				 const char *out)
-{
-	strvec_push(&cmd->args, "pack-objects");
-	if (args->window)
-		strvec_pushf(&cmd->args, "--window=%s", args->window);
-	if (args->window_memory)
-		strvec_pushf(&cmd->args, "--window-memory=%s", args->window_memory);
-	if (args->depth)
-		strvec_pushf(&cmd->args, "--depth=%s", args->depth);
-	if (args->threads)
-		strvec_pushf(&cmd->args, "--threads=%s", args->threads);
-	if (args->max_pack_size)
-		strvec_pushf(&cmd->args, "--max-pack-size=%lu", args->max_pack_size);
-	if (args->no_reuse_delta)
-		strvec_pushf(&cmd->args, "--no-reuse-delta");
-	if (args->no_reuse_object)
-		strvec_pushf(&cmd->args, "--no-reuse-object");
-	if (args->name_hash_version)
-		strvec_pushf(&cmd->args, "--name-hash-version=%d", args->name_hash_version);
-	if (args->path_walk)
-		strvec_pushf(&cmd->args, "--path-walk");
-	if (args->local)
-		strvec_push(&cmd->args,  "--local");
-	if (args->quiet)
-		strvec_push(&cmd->args,  "--quiet");
-	if (delta_base_offset)
-		strvec_push(&cmd->args,  "--delta-base-offset");
-	strvec_push(&cmd->args, out);
-	cmd->git_cmd = 1;
-	cmd->out = -1;
-}
-
-/*
- * Write oid to the given struct child_process's stdin, starting it first if
- * necessary.
- */
-static int write_oid(const struct object_id *oid,
-		     struct packed_git *pack UNUSED,
-		     uint32_t pos UNUSED, void *data)
-{
-	struct child_process *cmd = data;
-
-	if (cmd->in == -1) {
-		if (start_command(cmd))
-			die(_("could not start pack-objects to repack promisor objects"));
-	}
-
-	if (write_in_full(cmd->in, oid_to_hex(oid), the_hash_algo->hexsz) < 0 ||
-	    write_in_full(cmd->in, "\n", 1) < 0)
-		die(_("failed to feed promisor objects to pack-objects"));
-	return 0;
-}
-
-static struct {
-	const char *name;
-	unsigned optional:1;
-} exts[] = {
-	{".pack"},
-	{".rev", 1},
-	{".mtimes", 1},
-	{".bitmap", 1},
-	{".promisor", 1},
-	{".idx"},
-};
-
-struct generated_pack_data {
-	struct tempfile *tempfiles[ARRAY_SIZE(exts)];
-};
-
-static struct generated_pack_data *populate_pack_exts(const char *name)
-{
-	struct stat statbuf;
-	struct strbuf path = STRBUF_INIT;
-	struct generated_pack_data *data = xcalloc(1, sizeof(*data));
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(exts); i++) {
-		strbuf_reset(&path);
-		strbuf_addf(&path, "%s-%s%s", packtmp, name, exts[i].name);
-
-		if (stat(path.buf, &statbuf))
-			continue;
-
-		data->tempfiles[i] = register_tempfile(path.buf);
-	}
-
-	strbuf_release(&path);
-	return data;
-}
-
-static int has_pack_ext(const struct generated_pack_data *data,
-			const char *ext)
-{
-	int i;
-	for (i = 0; i < ARRAY_SIZE(exts); i++) {
-		if (strcmp(exts[i].name, ext))
-			continue;
-		return !!data->tempfiles[i];
-	}
-	BUG("unknown pack extension: '%s'", ext);
-}
-
-static void repack_promisor_objects(const struct pack_objects_args *args,
-				    struct string_list *names)
-{
-	struct child_process cmd = CHILD_PROCESS_INIT;
-	FILE *out;
-	struct strbuf line = STRBUF_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.
-	 */
-	for_each_packed_object(the_repository, write_oid, &cmd,
-			       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");
-	while (strbuf_getline_lf(&line, out) != EOF) {
-		struct string_list_item *item;
-		char *promisor_name;
-
-		if (line.len != the_hash_algo->hexsz)
-			die(_("repack: Expecting full hex object ID lines only from pack-objects."));
-		item = string_list_append(names, line.buf);
-
-		/*
-		 * pack-objects creates the .pack and .idx files, but not the
-		 * .promisor file. Create the .promisor file, which is empty.
-		 *
-		 * NEEDSWORK: fetch-pack sometimes generates non-empty
-		 * .promisor files containing the ref names and associated
-		 * hashes at the point of generation of the corresponding
-		 * packfile, but this would not preserve their contents. Maybe
-		 * concatenate the contents of all .promisor files instead of
-		 * just creating a new empty file.
-		 */
-		promisor_name = mkpathdup("%s-%s.promisor", packtmp,
-					  line.buf);
-		write_promisor_file(promisor_name, NULL, 0);
-
-		item->util = populate_pack_exts(item->string);
-
-		free(promisor_name);
-	}
-
-	fclose(out);
-	if (finish_command(&cmd))
-		die(_("could not finish pack-objects to repack promisor objects"));
-	strbuf_release(&line);
-}
-
-struct pack_geometry {
-	struct packed_git **pack;
-	uint32_t pack_nr, pack_alloc;
-	uint32_t split;
-
-	int split_factor;
-};
-
-static uint32_t geometry_pack_weight(struct packed_git *p)
-{
-	if (open_pack_index(p))
-		die(_("cannot open index for %s"), p->pack_name);
-	return p->num_objects;
-}
-
-static int geometry_cmp(const void *va, const void *vb)
-{
-	uint32_t aw = geometry_pack_weight(*(struct packed_git **)va),
-		 bw = geometry_pack_weight(*(struct packed_git **)vb);
-
-	if (aw < bw)
-		return -1;
-	if (aw > bw)
-		return 1;
-	return 0;
-}
-
-static void init_pack_geometry(struct pack_geometry *geometry,
-			       struct existing_packs *existing,
-			       const struct pack_objects_args *args)
-{
-	struct packfile_store *packs = the_repository->objects->packfiles;
-	struct packed_git *p;
-	struct strbuf buf = STRBUF_INIT;
-
-	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
-		if (args->local && !p->pack_local)
-			/*
-			 * When asked to only repack local packfiles we skip
-			 * over any packfiles that are borrowed from alternate
-			 * object directories.
-			 */
-			continue;
-
-		if (!pack_kept_objects) {
-			/*
-			 * Any pack that has its pack_keep bit set will
-			 * appear in existing->kept_packs below, but
-			 * this saves us from doing a more expensive
-			 * check.
-			 */
-			if (p->pack_keep)
-				continue;
-
-			/*
-			 * The pack may be kept via the --keep-pack
-			 * option; check 'existing->kept_packs' to
-			 * determine whether to ignore it.
-			 */
-			strbuf_reset(&buf);
-			strbuf_addstr(&buf, pack_basename(p));
-			strbuf_strip_suffix(&buf, ".pack");
-
-			if (string_list_has_string(&existing->kept_packs, buf.buf))
-				continue;
-		}
-		if (p->is_cruft)
-			continue;
-
-		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, geometry_cmp);
-	strbuf_release(&buf);
-}
-
-static void split_pack_geometry(struct pack_geometry *geometry)
-{
-	uint32_t i;
-	uint32_t split;
-	off_t total_size = 0;
-
-	if (!geometry->pack_nr) {
-		geometry->split = geometry->pack_nr;
-		return;
-	}
-
-	/*
-	 * 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];
-
-		if (unsigned_mult_overflows(geometry->split_factor,
-					    geometry_pack_weight(prev)))
-			die(_("pack %s too large to consider in geometric "
-			      "progression"),
-			    prev->pack_name);
-
-		if (geometry_pack_weight(ours) <
-		    geometry->split_factor * geometry_pack_weight(prev))
-			break;
-	}
-
-	split = i;
-
-	if (split) {
-		/*
-		 * Move the split one to the right, since the top element in the
-		 * last-compared pair can't be in the progression. Only do this
-		 * when we split in the middle of the array (otherwise if we got
-		 * to the end, then the split is in the right place).
-		 */
-		split++;
-	}
-
-	/*
-	 * Then, anything to the left of 'split' must be in a new pack. But,
-	 * creating that new pack may cause packs in the heavy half to no longer
-	 * form a geometric progression.
-	 *
-	 * Compute an expected size of the new pack, and then determine how many
-	 * packs in the heavy half need to be joined into it (if any) to restore
-	 * the geometric progression.
-	 */
-	for (i = 0; i < split; i++) {
-		struct packed_git *p = geometry->pack[i];
-
-		if (unsigned_add_overflows(total_size, geometry_pack_weight(p)))
-			die(_("pack %s too large to roll up"), p->pack_name);
-		total_size += geometry_pack_weight(p);
-	}
-	for (i = split; i < geometry->pack_nr; i++) {
-		struct packed_git *ours = geometry->pack[i];
-
-		if (unsigned_mult_overflows(geometry->split_factor,
-					    total_size))
-			die(_("pack %s too large to roll up"), ours->pack_name);
-
-		if (geometry_pack_weight(ours) <
-		    geometry->split_factor * total_size) {
-			if (unsigned_add_overflows(total_size,
-						   geometry_pack_weight(ours)))
-				die(_("pack %s too large to roll up"),
-				    ours->pack_name);
-
-			split++;
-			total_size += geometry_pack_weight(ours);
-		} else
-			break;
-	}
-
-	geometry->split = split;
-}
-
-static struct packed_git *get_preferred_pack(struct pack_geometry *geometry)
-{
-	uint32_t i;
-
-	if (!geometry) {
-		/*
-		 * No geometry means either an all-into-one repack (in which
-		 * case there is only one pack left and it is the largest) or an
-		 * incremental one.
-		 *
-		 * If repacking incrementally, then we could check the size of
-		 * all packs to determine which should be preferred, but leave
-		 * this for later.
-		 */
-		return NULL;
-	}
-	if (geometry->split == geometry->pack_nr)
-		return NULL;
-
-	/*
-	 * The preferred pack is the largest pack above the split line. In
-	 * other words, it is the largest pack that does not get rolled up in
-	 * the geometric repack.
-	 */
-	for (i = geometry->pack_nr; i > geometry->split; i--)
-		/*
-		 * A pack that is not local would never be included in a
-		 * multi-pack index. We thus skip over any non-local packs.
-		 */
-		if (geometry->pack[i - 1]->pack_local)
-			return geometry->pack[i - 1];
-
-	return NULL;
-}
-
-static void geometry_remove_redundant_packs(struct pack_geometry *geometry,
-					    struct string_list *names,
-					    struct existing_packs *existing)
-{
-	struct strbuf buf = STRBUF_INIT;
-	uint32_t i;
-
-	for (i = 0; i < geometry->split; i++) {
-		struct packed_git *p = geometry->pack[i];
-		if (string_list_has_string(names, hash_to_hex(p->hash)))
-			continue;
-
-		strbuf_reset(&buf);
-		strbuf_addstr(&buf, pack_basename(p));
-		strbuf_strip_suffix(&buf, ".pack");
-
-		if ((p->pack_keep) ||
-		    (string_list_has_string(&existing->kept_packs, buf.buf)))
-			continue;
-
-		remove_redundant_pack(packdir, buf.buf);
-	}
-
-	strbuf_release(&buf);
-}
-
-static void free_pack_geometry(struct pack_geometry *geometry)
-{
-	if (!geometry)
-		return;
-
-	free(geometry->pack);
-}
-
-static int midx_has_unknown_packs(char **midx_pack_names,
-				  size_t midx_pack_names_nr,
-				  struct string_list *include,
-				  struct pack_geometry *geometry,
-				  struct existing_packs *existing)
-{
-	size_t i;
-
-	string_list_sort(include);
-
-	for (i = 0; i < midx_pack_names_nr; i++) {
-		const char *pack_name = midx_pack_names[i];
-
-		/*
-		 * Determine whether or not each MIDX'd pack from the existing
-		 * MIDX (if any) is represented in the new MIDX. For each pack
-		 * in the MIDX, it must either be:
-		 *
-		 *  - In the "include" list of packs to be included in the new
-		 *    MIDX. Note this function is called before the include
-		 *    list is populated with any cruft pack(s).
-		 *
-		 *  - Below the geometric split line (if using pack geometry),
-		 *    indicating that the pack won't be included in the new
-		 *    MIDX, but its contents were rolled up as part of the
-		 *    geometric repack.
-		 *
-		 *  - In the existing non-kept packs list (if not using pack
-		 *    geometry), and marked as non-deleted.
-		 */
-		if (string_list_has_string(include, pack_name)) {
-			continue;
-		} else if (geometry) {
-			struct strbuf buf = STRBUF_INIT;
-			uint32_t j;
-
-			for (j = 0; j < geometry->split; j++) {
-				strbuf_reset(&buf);
-				strbuf_addstr(&buf, pack_basename(geometry->pack[j]));
-				strbuf_strip_suffix(&buf, ".pack");
-				strbuf_addstr(&buf, ".idx");
-
-				if (!strcmp(pack_name, buf.buf)) {
-					strbuf_release(&buf);
-					break;
-				}
-			}
-
-			strbuf_release(&buf);
-
-			if (j < geometry->split)
-				continue;
-		} else {
-			struct string_list_item *item;
-
-			item = string_list_lookup(&existing->non_kept_packs,
-						  pack_name);
-			if (item && !pack_is_marked_for_deletion(item))
-				continue;
-		}
-
-		/*
-		 * If we got to this point, the MIDX includes some pack that we
-		 * don't know about.
-		 */
-		return 1;
-	}
-
-	return 0;
-}
-
-struct midx_snapshot_ref_data {
-	struct tempfile *f;
-	struct oidset seen;
-	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)
-{
-	struct midx_snapshot_ref_data *data = _data;
-	struct object_id peeled;
-
-	if (!peel_iterated_oid(the_repository, oid, &peeled))
-		oid = &peeled;
-
-	if (oidset_insert(&data->seen, oid))
-		return 0; /* already seen */
-
-	if (odb_read_object_info(the_repository->objects, oid, NULL) != OBJ_COMMIT)
-		return 0;
-
-	fprintf(data->f->fp, "%s%s\n", data->preferred ? "+" : "",
-		oid_to_hex(oid));
-
-	return 0;
-}
-
-static void midx_snapshot_refs(struct tempfile *f)
-{
-	struct midx_snapshot_ref_data data;
-	const struct string_list *preferred = bitmap_preferred_tips(the_repository);
-
-	data.f = f;
-	data.preferred = 0;
-	oidset_init(&data.seen, 0);
-
-	if (!fdopen_tempfile(f, "w"))
-		 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(the_repository),
-					     item->string,
-					     midx_snapshot_ref_one, &data);
-		data.preferred = 0;
-	}
-
-	refs_for_each_ref(get_main_ref_store(the_repository),
-			  midx_snapshot_ref_one, &data);
-
-	if (close_tempfile_gently(f)) {
-		int save_errno = errno;
-		delete_tempfile(&f);
-		errno = save_errno;
-		die_errno(_("could not close refs snapshot tempfile"));
-	}
-
-	oidset_clear(&data.seen);
-}
-
-static void midx_included_packs(struct string_list *include,
-				struct existing_packs *existing,
-				char **midx_pack_names,
-				size_t midx_pack_names_nr,
-				struct string_list *names,
-				struct pack_geometry *geometry)
-{
-	struct string_list_item *item;
-	struct strbuf buf = STRBUF_INIT;
-
-	for_each_string_list_item(item, &existing->kept_packs) {
-		strbuf_reset(&buf);
-		strbuf_addf(&buf, "%s.idx", item->string);
-		string_list_insert(include, buf.buf);
-	}
-
-	for_each_string_list_item(item, names) {
-		strbuf_reset(&buf);
-		strbuf_addf(&buf, "pack-%s.idx", item->string);
-		string_list_insert(include, buf.buf);
-	}
-
-	if (geometry->split_factor) {
-		uint32_t i;
-
-		for (i = geometry->split; i < geometry->pack_nr; i++) {
-			struct packed_git *p = geometry->pack[i];
-
-			/*
-			 * The multi-pack index never refers to packfiles part
-			 * of an alternate object database, so we skip these.
-			 * While git-multi-pack-index(1) would silently ignore
-			 * them anyway, this allows us to skip executing the
-			 * command completely when we have only non-local
-			 * packfiles.
-			 */
-			if (!p->pack_local)
-				continue;
-
-			strbuf_reset(&buf);
-			strbuf_addstr(&buf, pack_basename(p));
-			strbuf_strip_suffix(&buf, ".pack");
-			strbuf_addstr(&buf, ".idx");
-
-			string_list_insert(include, buf.buf);
-		}
-	} else {
-		for_each_string_list_item(item, &existing->non_kept_packs) {
-			if (pack_is_marked_for_deletion(item))
-				continue;
-
-			strbuf_reset(&buf);
-			strbuf_addf(&buf, "%s.idx", item->string);
-			string_list_insert(include, buf.buf);
-		}
-	}
-
-	if (midx_must_contain_cruft ||
-	    midx_has_unknown_packs(midx_pack_names, midx_pack_names_nr,
-				   include, geometry, existing)) {
-		/*
-		 * If there are one or more unknown pack(s) present (see
-		 * midx_has_unknown_packs() for what makes a pack
-		 * "unknown") in the MIDX before the repack, keep them
-		 * as they may be required to form a reachability
-		 * closure if the MIDX is bitmapped.
-		 *
-		 * For example, a cruft pack can be required to form a
-		 * reachability closure if the MIDX is bitmapped and one
-		 * or more of the bitmap's selected commits reaches a
-		 * once-cruft object that was later made reachable.
-		 */
-		for_each_string_list_item(item, &existing->cruft_packs) {
-			/*
-			 * When doing a --geometric repack, there is no
-			 * need to check for deleted packs, since we're
-			 * by definition not doing an ALL_INTO_ONE
-			 * repack (hence no packs will be deleted).
-			 * Otherwise we must check for and exclude any
-			 * packs which are enqueued for deletion.
-			 *
-			 * So we could omit the conditional below in the
-			 * --geometric case, but doing so is unnecessary
-			 *  since no packs are marked as pending
-			 *  deletion (since we only call
-			 *  `mark_packs_for_deletion()` when doing an
-			 *  all-into-one repack).
-			 */
-			if (pack_is_marked_for_deletion(item))
-				continue;
-
-			strbuf_reset(&buf);
-			strbuf_addf(&buf, "%s.idx", item->string);
-			string_list_insert(include, buf.buf);
-		}
-	} else {
-		/*
-		 * Modern versions of Git (with the appropriate
-		 * configuration setting) will write new copies of
-		 * once-cruft objects when doing a --geometric repack.
-		 *
-		 * If the MIDX has no cruft pack, new packs written
-		 * during a --geometric repack will not rely on the
-		 * cruft pack to form a reachability closure, so we can
-		 * avoid including them in the MIDX in that case.
-		 */
-		;
-	}
-
-	strbuf_release(&buf);
-}
-
-static int write_midx_included_packs(struct string_list *include,
-				     struct pack_geometry *geometry,
-				     struct string_list *names,
-				     const char *refs_snapshot,
-				     int show_progress, int write_bitmaps)
-{
-	struct child_process cmd = CHILD_PROCESS_INIT;
-	struct string_list_item *item;
-	struct packed_git *preferred = get_preferred_pack(geometry);
-	FILE *in;
-	int ret;
-
-	if (!include->nr)
-		return 0;
-
-	cmd.in = -1;
-	cmd.git_cmd = 1;
-
-	strvec_push(&cmd.args, "multi-pack-index");
-	strvec_pushl(&cmd.args, "write", "--stdin-packs", NULL);
-
-	if (show_progress)
-		strvec_push(&cmd.args, "--progress");
-	else
-		strvec_push(&cmd.args, "--no-progress");
-
-	if (write_bitmaps)
-		strvec_push(&cmd.args, "--bitmap");
-
-	if (preferred)
-		strvec_pushf(&cmd.args, "--preferred-pack=%s",
-			     pack_basename(preferred));
-	else if (names->nr) {
-		/* The largest pack was repacked, meaning that either
-		 * one or two packs exist depending on whether the
-		 * repository has a cruft pack or not.
-		 *
-		 * Select the non-cruft one as preferred to encourage
-		 * pack-reuse among packs containing reachable objects
-		 * over unreachable ones.
-		 *
-		 * (Note we could write multiple packs here if
-		 * `--max-pack-size` was given, but any one of them
-		 * will suffice, so pick the first one.)
-		 */
-		for_each_string_list_item(item, names) {
-			struct generated_pack_data *data = item->util;
-			if (has_pack_ext(data, ".mtimes"))
-				continue;
-
-			strvec_pushf(&cmd.args, "--preferred-pack=pack-%s.pack",
-				     item->string);
-			break;
-		}
-	} else {
-		/*
-		 * No packs were kept, and no packs were written. The
-		 * only thing remaining are .keep packs (unless
-		 * --pack-kept-objects was given).
-		 *
-		 * Set the `--preferred-pack` arbitrarily here.
-		 */
-		;
-	}
-
-	if (refs_snapshot)
-		strvec_pushf(&cmd.args, "--refs-snapshot=%s", refs_snapshot);
-
-	ret = start_command(&cmd);
-	if (ret)
-		return ret;
-
-	in = xfdopen(cmd.in, "w");
-	for_each_string_list_item(item, include)
-		fprintf(in, "%s\n", item->string);
-	fclose(in);
-
-	return finish_command(&cmd);
-}
-
-static void remove_redundant_bitmaps(struct string_list *include,
-				     const char *packdir)
-{
-	struct strbuf path = STRBUF_INIT;
-	struct string_list_item *item;
-	size_t packdir_len;
-
-	strbuf_addstr(&path, packdir);
-	strbuf_addch(&path, '/');
-	packdir_len = path.len;
-
-	/*
-	 * Remove any pack bitmaps corresponding to packs which are now
-	 * included in the MIDX.
-	 */
-	for_each_string_list_item(item, include) {
-		strbuf_addstr(&path, item->string);
-		strbuf_strip_suffix(&path, ".idx");
-		strbuf_addstr(&path, ".bitmap");
-
-		if (unlink(path.buf) && errno != ENOENT)
-			warning_errno(_("could not remove stale bitmap: %s"),
-				      path.buf);
-
-		strbuf_setlen(&path, packdir_len);
-	}
-	strbuf_release(&path);
-}
-
-static int finish_pack_objects_cmd(struct child_process *cmd,
-				   struct string_list *names,
-				   int local)
-{
-	FILE *out;
-	struct strbuf line = STRBUF_INIT;
-
-	out = xfdopen(cmd->out, "r");
-	while (strbuf_getline_lf(&line, out) != EOF) {
-		struct string_list_item *item;
-
-		if (line.len != the_hash_algo->hexsz)
-			die(_("repack: Expecting full hex object ID lines only "
-			      "from pack-objects."));
-		/*
-		 * Avoid putting packs written outside of the repository in the
-		 * list of names.
-		 */
-		if (local) {
-			item = string_list_append(names, line.buf);
-			item->util = populate_pack_exts(line.buf);
-		}
-	}
-	fclose(out);
-
-	strbuf_release(&line);
-
-	return finish_command(cmd);
-}
-
-static int write_filtered_pack(const struct pack_objects_args *args,
-			       const char *destination,
-			       const char *pack_prefix,
-			       struct existing_packs *existing,
-			       struct string_list *names)
-{
-	struct child_process cmd = CHILD_PROCESS_INIT;
-	struct string_list_item *item;
-	FILE *in;
-	int ret;
-	const char *caret;
-	const char *scratch;
-	int local = skip_prefix(destination, packdir, &scratch);
-
-	prepare_pack_objects(&cmd, args, destination);
-
-	strvec_push(&cmd.args, "--stdin-packs");
-
-	if (!pack_kept_objects)
-		strvec_push(&cmd.args, "--honor-pack-keep");
-	for_each_string_list_item(item, &existing->kept_packs)
-		strvec_pushf(&cmd.args, "--keep-pack=%s", item->string);
-
-	cmd.in = -1;
-
-	ret = start_command(&cmd);
-	if (ret)
-		return ret;
-
-	/*
-	 * Here 'names' contains only the pack(s) that were just
-	 * written, which is exactly the packs we want to keep. Also
-	 * 'existing_kept_packs' already contains the packs in
-	 * 'keep_pack_list'.
-	 */
-	in = xfdopen(cmd.in, "w");
-	for_each_string_list_item(item, names)
-		fprintf(in, "^%s-%s.pack\n", pack_prefix, item->string);
-	for_each_string_list_item(item, &existing->non_kept_packs)
-		fprintf(in, "%s.pack\n", item->string);
-	for_each_string_list_item(item, &existing->cruft_packs)
-		fprintf(in, "%s.pack\n", item->string);
-	caret = pack_kept_objects ? "" : "^";
-	for_each_string_list_item(item, &existing->kept_packs)
-		fprintf(in, "%s%s.pack\n", caret, item->string);
-	fclose(in);
-
-	return finish_pack_objects_cmd(&cmd, names, local);
-}
-
-static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,
-				      struct existing_packs *existing)
-{
-	struct packfile_store *packs = the_repository->objects->packfiles;
-	struct packed_git *p;
-	struct strbuf buf = STRBUF_INIT;
-	size_t i;
-
-	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
-		if (!(p->is_cruft && p->pack_local))
-			continue;
-
-		strbuf_reset(&buf);
-		strbuf_addstr(&buf, pack_basename(p));
-		strbuf_strip_suffix(&buf, ".pack");
-
-		if (!string_list_has_string(&existing->cruft_packs, buf.buf))
-			continue;
-
-		if (p->pack_size < combine_cruft_below_size) {
-			fprintf(in, "-%s\n", pack_basename(p));
-		} else {
-			retain_cruft_pack(existing, p);
-			fprintf(in, "%s\n", pack_basename(p));
-		}
-	}
-
-	for (i = 0; i < existing->non_kept_packs.nr; i++)
-		fprintf(in, "-%s.pack\n",
-			existing->non_kept_packs.items[i].string);
-
-	strbuf_release(&buf);
-}
-
-static int write_cruft_pack(const struct pack_objects_args *args,
-			    const char *destination,
-			    const char *pack_prefix,
-			    const char *cruft_expiration,
-			    unsigned long combine_cruft_below_size,
-			    struct string_list *names,
-			    struct existing_packs *existing)
-{
-	struct child_process cmd = CHILD_PROCESS_INIT;
-	struct string_list_item *item;
-	FILE *in;
-	int ret;
-	const char *scratch;
-	int local = skip_prefix(destination, packdir, &scratch);
-
-	prepare_pack_objects(&cmd, args, destination);
-
-	strvec_push(&cmd.args, "--cruft");
-	if (cruft_expiration)
-		strvec_pushf(&cmd.args, "--cruft-expiration=%s",
-			     cruft_expiration);
-
-	strvec_push(&cmd.args, "--honor-pack-keep");
-	strvec_push(&cmd.args, "--non-empty");
-
-	cmd.in = -1;
-
-	ret = start_command(&cmd);
-	if (ret)
-		return ret;
-
-	/*
-	 * names has a confusing double use: it both provides the list
-	 * of just-written new packs, and accepts the name of the cruft
-	 * pack we are writing.
-	 *
-	 * By the time it is read here, it contains only the pack(s)
-	 * that were just written, which is exactly the set of packs we
-	 * want to consider kept.
-	 *
-	 * If `--expire-to` is given, the double-use served by `names`
-	 * ensures that the pack written to `--expire-to` excludes any
-	 * objects contained in the cruft pack.
-	 */
-	in = xfdopen(cmd.in, "w");
-	for_each_string_list_item(item, names)
-		fprintf(in, "%s-%s.pack\n", pack_prefix, item->string);
-	if (combine_cruft_below_size && !cruft_expiration) {
-		combine_small_cruft_packs(in, combine_cruft_below_size,
-					  existing);
-	} else {
-		for_each_string_list_item(item, &existing->non_kept_packs)
-			fprintf(in, "-%s.pack\n", item->string);
-		for_each_string_list_item(item, &existing->cruft_packs)
-			fprintf(in, "-%s.pack\n", item->string);
-	}
-	for_each_string_list_item(item, &existing->kept_packs)
-		fprintf(in, "%s.pack\n", item->string);
-	fclose(in);
-
-	return finish_pack_objects_cmd(&cmd, names, local);
-}
-
-static const char *find_pack_prefix(const char *packdir, const char *packtmp)
-{
-	const char *pack_prefix;
-	if (!skip_prefix(packtmp, packdir, &pack_prefix))
-		die(_("pack prefix %s does not begin with objdir %s"),
-		    packtmp, packdir);
-	if (*pack_prefix == '/')
-		pack_prefix++;
-	return pack_prefix;
-}
-
 int cmd_repack(int argc,
 	       const char **argv,
 	       const char *prefix,
-	       struct repository *repo UNUSED)
+	       struct repository *repo)
 {
 	struct child_process cmd = CHILD_PROCESS_INIT;
 	struct string_list_item *item;
@@ -1255,18 +108,17 @@ int cmd_repack(int argc,
 	struct existing_packs existing = EXISTING_PACKS_INIT;
 	struct pack_geometry geometry = { 0 };
 	struct tempfile *refs_snapshot = NULL;
-	int i, ext, ret;
+	int i, ret;
 	int show_progress;
-	char **midx_pack_names = NULL;
-	size_t midx_pack_names_nr = 0;
 
 	/* variables to be filled by option parsing */
+	struct repack_config_ctx config_ctx;
 	int delete_redundant = 0;
 	const char *unpack_unreachable = NULL;
 	int keep_unreachable = 0;
 	struct string_list keep_pack_list = STRING_LIST_INIT_NODUP;
-	struct pack_objects_args po_args = { 0 };
-	struct pack_objects_args cruft_po_args = { 0 };
+	struct pack_objects_args po_args = PACK_OBJECTS_ARGS_INIT;
+	struct pack_objects_args cruft_po_args = PACK_OBJECTS_ARGS_INIT;
 	int write_midx = 0;
 	const char *cruft_expiration = NULL;
 	const char *expire_to = NULL;
@@ -1327,7 +179,7 @@ int cmd_repack(int argc,
 		OPT_UNSIGNED(0, "max-pack-size", &po_args.max_pack_size,
 			     N_("maximum size of each packfile")),
 		OPT_PARSE_LIST_OBJECTS_FILTER(&po_args.filter_options),
-		OPT_BOOL(0, "pack-kept-objects", &pack_kept_objects,
+		OPT_BOOL(0, "pack-kept-objects", &po_args.pack_kept_objects,
 				N_("repack objects in packs marked with .keep")),
 		OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"),
 				N_("do not repack this pack")),
@@ -1344,7 +196,11 @@ int cmd_repack(int argc,
 
 	list_objects_filter_init(&po_args.filter_options);
 
-	repo_config(the_repository, repack_config, &cruft_po_args);
+	memset(&config_ctx, 0, sizeof(config_ctx));
+	config_ctx.po_args = &po_args;
+	config_ctx.cruft_po_args = &cruft_po_args;
+
+	repo_config(repo, repack_config, &config_ctx);
 
 	argc = parse_options(argc, argv, prefix, builtin_repack_options,
 				git_repack_usage, 0);
@@ -1354,7 +210,7 @@ int cmd_repack(int argc,
 	po_args.depth = xstrdup_or_null(opt_depth);
 	po_args.threads = xstrdup_or_null(opt_threads);
 
-	if (delete_redundant && the_repository->repository_format_precious_objects)
+	if (delete_redundant && repo->repository_format_precious_objects)
 		die(_("cannot delete packs in a precious-objects repo"));
 
 	die_for_incompatible_opt3(unpack_unreachable || (pack_everything & LOOSEN_UNREACHABLE), "-A",
@@ -1369,14 +225,14 @@ int cmd_repack(int argc,
 		    (!(pack_everything & ALL_INTO_ONE) || !is_bare_repository()))
 			write_bitmaps = 0;
 	}
-	if (pack_kept_objects < 0)
-		pack_kept_objects = write_bitmaps > 0 && !write_midx;
+	if (po_args.pack_kept_objects < 0)
+		po_args.pack_kept_objects = write_bitmaps > 0 && !write_midx;
 
 	if (write_bitmaps && !(pack_everything & ALL_INTO_ONE) && !write_midx)
 		die(_(incremental_bitmap_conflict_error));
 
 	if (write_bitmaps && po_args.local &&
-	    odb_has_alternates(the_repository->objects)) {
+	    odb_has_alternates(repo->objects)) {
 		/*
 		 * When asked to do a local repack, but we have
 		 * packfiles that are inherited from an alternate, then
@@ -1391,26 +247,28 @@ int cmd_repack(int argc,
 	if (write_midx && write_bitmaps) {
 		struct strbuf path = STRBUF_INIT;
 
-		strbuf_addf(&path, "%s/%s_XXXXXX", repo_get_object_directory(the_repository),
+		strbuf_addf(&path, "%s/%s_XXXXXX",
+			    repo_get_object_directory(repo),
 			    "bitmap-ref-tips");
 
 		refs_snapshot = xmks_tempfile(path.buf);
-		midx_snapshot_refs(refs_snapshot);
+		midx_snapshot_refs(repo, refs_snapshot);
 
 		strbuf_release(&path);
 	}
 
-	packdir = mkpathdup("%s/pack", repo_get_object_directory(the_repository));
+	packdir = mkpathdup("%s/pack", repo_get_object_directory(repo));
 	packtmp_name = xstrfmt(".tmp-%d-pack", (int)getpid());
 	packtmp = mkpathdup("%s/%s", packdir, packtmp_name);
 
-	collect_pack_filenames(&existing, &keep_pack_list);
+	existing.repo = repo;
+	existing_packs_collect(&existing, &keep_pack_list);
 
 	if (geometry.split_factor) {
 		if (pack_everything)
 			die(_("options '%s' and '%s' cannot be used together"), "--geometric", "-A/-a");
-		init_pack_geometry(&geometry, &existing, &po_args);
-		split_pack_geometry(&geometry);
+		pack_geometry_init(&geometry, &existing, &po_args);
+		pack_geometry_split(&geometry);
 	}
 
 	prepare_pack_objects(&cmd, &po_args, packtmp);
@@ -1418,8 +276,6 @@ int cmd_repack(int argc,
 	show_progress = !po_args.quiet && isatty(2);
 
 	strvec_push(&cmd.args, "--keep-true-parents");
-	if (!pack_kept_objects)
-		strvec_push(&cmd.args, "--honor-pack-keep");
 	for (i = 0; i < keep_pack_list.nr; i++)
 		strvec_pushf(&cmd.args, "--keep-pack=%s",
 			     keep_pack_list.items[i].string);
@@ -1439,7 +295,7 @@ int cmd_repack(int argc,
 		strvec_push(&cmd.args, "--reflog");
 		strvec_push(&cmd.args, "--indexed-objects");
 	}
-	if (repo_has_promisor_remote(the_repository))
+	if (repo_has_promisor_remote(repo))
 		strvec_push(&cmd.args, "--exclude-promisor-objects");
 	if (!write_midx) {
 		if (write_bitmaps > 0)
@@ -1451,9 +307,9 @@ int cmd_repack(int argc,
 		strvec_push(&cmd.args, "--delta-islands");
 
 	if (pack_everything & ALL_INTO_ONE) {
-		repack_promisor_objects(&po_args, &names);
+		repack_promisor_objects(repo, &po_args, &names, packtmp);
 
-		if (has_existing_non_kept_packs(&existing) &&
+		if (existing_packs_has_non_kept(&existing) &&
 		    delete_redundant &&
 		    !(pack_everything & PACK_CRUFT)) {
 			for_each_string_list_item(item, &names) {
@@ -1515,9 +371,17 @@ int cmd_repack(int argc,
 		fclose(in);
 	}
 
-	ret = finish_pack_objects_cmd(&cmd, &names, 1);
-	if (ret)
-		goto cleanup;
+	{
+		struct write_pack_opts opts = {
+			.packdir = packdir,
+			.destination = packdir,
+			.packtmp = packtmp,
+		};
+		ret = finish_pack_objects_cmd(repo->hash_algo, &opts, &cmd,
+					      &names);
+		if (ret)
+			goto cleanup;
+	}
 
 	if (!names.nr) {
 		if (!po_args.quiet)
@@ -1535,12 +399,17 @@ int cmd_repack(int argc,
 		 * midx_has_unknown_packs() will make the decision for
 		 * us.
 		 */
-		if (!get_multi_pack_index(the_repository->objects->sources))
+		if (!get_multi_pack_index(repo->objects->sources))
 			midx_must_contain_cruft = 1;
 	}
 
 	if (pack_everything & PACK_CRUFT) {
-		const char *pack_prefix = find_pack_prefix(packdir, packtmp);
+		struct write_pack_opts opts = {
+			.po_args = &cruft_po_args,
+			.destination = packtmp,
+			.packtmp = packtmp,
+			.packdir = packdir,
+		};
 
 		if (!cruft_po_args.window)
 			cruft_po_args.window = xstrdup_or_null(po_args.window);
@@ -1555,9 +424,10 @@ int cmd_repack(int argc,
 
 		cruft_po_args.local = po_args.local;
 		cruft_po_args.quiet = po_args.quiet;
+		cruft_po_args.delta_base_offset = po_args.delta_base_offset;
+		cruft_po_args.pack_kept_objects = 0;
 
-		ret = write_cruft_pack(&cruft_po_args, packtmp, pack_prefix,
-				       cruft_expiration,
+		ret = write_cruft_pack(&opts, cruft_expiration,
 				       combine_cruft_below_size, &names,
 				       &existing);
 		if (ret)
@@ -1592,11 +462,8 @@ int cmd_repack(int argc,
 			 * pack, but rather removing all cruft packs from the
 			 * main repository regardless of size.
 			 */
-			ret = write_cruft_pack(&cruft_po_args, expire_to,
-					       pack_prefix,
-					       NULL,
-					       0ul,
-					       &names,
+			opts.destination = expire_to;
+			ret = write_cruft_pack(&opts, NULL, 0ul, &names,
 					       &existing);
 			if (ret)
 				goto cleanup;
@@ -1604,99 +471,63 @@ int cmd_repack(int argc,
 	}
 
 	if (po_args.filter_options.choice) {
-		if (!filter_to)
-			filter_to = packtmp;
+		struct write_pack_opts opts = {
+			.po_args = &po_args,
+			.destination = filter_to,
+			.packdir = packdir,
+			.packtmp = packtmp,
+		};
 
-		ret = write_filtered_pack(&po_args,
-					  filter_to,
-					  find_pack_prefix(packdir, packtmp),
-					  &existing,
-					  &names);
+		if (!opts.destination)
+			opts.destination = packtmp;
+
+		ret = write_filtered_pack(&opts, &existing, &names);
 		if (ret)
 			goto cleanup;
 	}
 
 	string_list_sort(&names);
 
-	if (get_multi_pack_index(the_repository->objects->sources)) {
-		struct multi_pack_index *m =
-			get_multi_pack_index(the_repository->objects->sources);
-
-		ALLOC_ARRAY(midx_pack_names,
-			    m->num_packs + m->num_packs_in_base);
-
-		for (; m; m = m->base_midx)
-			for (uint32_t i = 0; i < m->num_packs; i++)
-				midx_pack_names[midx_pack_names_nr++] =
-					xstrdup(m->pack_names[i]);
-	}
-
-	close_object_store(the_repository->objects);
+	close_object_store(repo->objects);
 
 	/*
 	 * Ok we have prepared all new packfiles.
 	 */
-	for_each_string_list_item(item, &names) {
-		struct generated_pack_data *data = item->util;
-
-		for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
-			char *fname;
-
-			fname = mkpathdup("%s/pack-%s%s",
-					packdir, item->string, exts[ext].name);
-
-			if (data->tempfiles[ext]) {
-				const char *fname_old = get_tempfile_path(data->tempfiles[ext]);
-				struct stat statbuffer;
-
-				if (!stat(fname_old, &statbuffer)) {
-					statbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
-					chmod(fname_old, statbuffer.st_mode);
-				}
-
-				if (rename_tempfile(&data->tempfiles[ext], fname))
-					die_errno(_("renaming pack to '%s' failed"), fname);
-			} else if (!exts[ext].optional)
-				die(_("pack-objects did not write a '%s' file for pack %s-%s"),
-				    exts[ext].name, packtmp, item->string);
-			else if (unlink(fname) < 0 && errno != ENOENT)
-				die_errno(_("could not unlink: %s"), fname);
-
-			free(fname);
-		}
-	}
+	for_each_string_list_item(item, &names)
+		generated_pack_install(item->util, item->string, packdir,
+				       packtmp);
 	/* End of pack replacement. */
 
 	if (delete_redundant && pack_everything & ALL_INTO_ONE)
-		mark_packs_for_deletion(&existing, &names);
+		existing_packs_mark_for_deletion(&existing, &names);
 
 	if (write_midx) {
-		struct string_list include = STRING_LIST_INIT_DUP;
-		midx_included_packs(&include, &existing, midx_pack_names,
-				    midx_pack_names_nr, &names, &geometry);
+		struct repack_write_midx_opts opts = {
+			.existing = &existing,
+			.geometry = &geometry,
+			.names = &names,
+			.refs_snapshot = refs_snapshot ? get_tempfile_path(refs_snapshot) : NULL,
+			.packdir = packdir,
+			.show_progress = show_progress,
+			.write_bitmaps = write_bitmaps > 0,
+			.midx_must_contain_cruft = midx_must_contain_cruft
+		};
 
-		ret = write_midx_included_packs(&include, &geometry, &names,
-						refs_snapshot ? get_tempfile_path(refs_snapshot) : NULL,
-						show_progress, write_bitmaps > 0);
-
-		if (!ret && write_bitmaps)
-			remove_redundant_bitmaps(&include, packdir);
-
-		string_list_clear(&include, 0);
+		ret = write_midx_included_packs(&opts);
 
 		if (ret)
 			goto cleanup;
 	}
 
-	odb_reprepare(the_repository->objects);
+	odb_reprepare(repo->objects);
 
 	if (delete_redundant) {
 		int opts = 0;
-		remove_redundant_existing_packs(&existing);
+		existing_packs_remove_redundant(&existing, packdir);
 
 		if (geometry.split_factor)
-			geometry_remove_redundant_packs(&geometry, &names,
-							&existing);
+			pack_geometry_remove_redundant(&geometry, &names,
+						       &existing, packdir);
 		if (show_progress)
 			opts |= PRUNE_PACKED_VERBOSE;
 		prune_packed_objects(opts);
@@ -1704,18 +535,18 @@ int cmd_repack(int argc,
 		if (!keep_unreachable &&
 		    (!(pack_everything & LOOSEN_UNREACHABLE) ||
 		     unpack_unreachable) &&
-		    is_repository_shallow(the_repository))
+		    is_repository_shallow(repo))
 			prune_shallow(PRUNE_QUICK);
 	}
 
 	if (run_update_server_info)
-		update_server_info(the_repository, 0);
+		update_server_info(repo, 0);
 
 	if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) {
 		unsigned flags = 0;
 		if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL, 0))
 			flags |= MIDX_WRITE_INCREMENTAL;
-		write_midx_file(the_repository->objects->sources,
+		write_midx_file(repo->objects->sources,
 				NULL, NULL, flags);
 	}
 
@@ -1723,10 +554,7 @@ int cmd_repack(int argc,
 	string_list_clear(&keep_pack_list, 0);
 	string_list_clear(&names, 1);
 	existing_packs_release(&existing);
-	free_pack_geometry(&geometry);
-	for (size_t i = 0; i < midx_pack_names_nr; i++)
-		free(midx_pack_names[i]);
-	free(midx_pack_names);
+	pack_geometry_release(&geometry);
 	pack_objects_args_release(&po_args);
 	pack_objects_args_release(&cruft_po_args);
 
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 9da92b9..7b3711c 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -1107,11 +1107,20 @@ int cmd_rev_parse(int argc,
 				const char *val = arg ? arg : "storage";
 
 				if (strcmp(val, "storage") &&
+				    strcmp(val, "compat") &&
 				    strcmp(val, "input") &&
 				    strcmp(val, "output"))
 					die(_("unknown mode for --show-object-format: %s"),
 					    arg);
-				puts(the_hash_algo->name);
+
+				if (!strcmp(val, "compat")) {
+					if (the_repository->compat_hash_algo)
+						puts(the_repository->compat_hash_algo->name);
+					else
+						putchar('\n');
+				} else {
+					puts(the_hash_algo->name);
+				}
 				continue;
 			}
 			if (!strcmp(arg, "--show-ref-format")) {
diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
index 8c333b3..15d51e6 100644
--- a/builtin/sparse-checkout.c
+++ b/builtin/sparse-checkout.c
@@ -2,6 +2,7 @@
 #define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "builtin.h"
+#include "abspath.h"
 #include "config.h"
 #include "dir.h"
 #include "environment.h"
@@ -23,7 +24,7 @@
 static const char *empty_base = "";
 
 static char const * const builtin_sparse_checkout_usage[] = {
-	N_("git sparse-checkout (init | list | set | add | reapply | disable | check-rules) [<options>]"),
+	N_("git sparse-checkout (init | list | set | add | reapply | disable | check-rules | clean) [<options>]"),
 	NULL
 };
 
@@ -204,12 +205,12 @@ static void clean_tracked_sparse_directories(struct repository *r)
 		ensure_full_index(r->index);
 }
 
-static int update_working_directory(struct pattern_list *pl)
+static int update_working_directory(struct repository *r,
+				    struct pattern_list *pl)
 {
 	enum update_sparsity_result result;
 	struct unpack_trees_options o;
 	struct lock_file lock_file = LOCK_INIT;
-	struct repository *r = the_repository;
 	struct pattern_list *old_pl;
 
 	/* If no branch has been checked out, there are no updates to make. */
@@ -327,7 +328,8 @@ static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
 	string_list_clear(&sl, 0);
 }
 
-static int write_patterns_and_update(struct pattern_list *pl)
+static int write_patterns_and_update(struct repository *repo,
+				     struct pattern_list *pl)
 {
 	char *sparse_filename;
 	FILE *fp;
@@ -336,15 +338,15 @@ static int write_patterns_and_update(struct pattern_list *pl)
 
 	sparse_filename = get_sparse_checkout_filename();
 
-	if (safe_create_leading_directories(the_repository, sparse_filename))
+	if (safe_create_leading_directories(repo, sparse_filename))
 		die(_("failed to create directory for sparse-checkout file"));
 
 	hold_lock_file_for_update(&lk, sparse_filename, LOCK_DIE_ON_ERROR);
 
-	result = update_working_directory(pl);
+	result = update_working_directory(repo, pl);
 	if (result) {
 		rollback_lock_file(&lk);
-		update_working_directory(NULL);
+		update_working_directory(repo, NULL);
 		goto out;
 	}
 
@@ -372,25 +374,26 @@ enum sparse_checkout_mode {
 	MODE_CONE_PATTERNS = 2,
 };
 
-static int set_config(enum sparse_checkout_mode mode)
+static int set_config(struct repository *repo,
+		      enum sparse_checkout_mode mode)
 {
 	/* Update to use worktree config, if not already. */
-	if (init_worktree_config(the_repository)) {
+	if (init_worktree_config(repo)) {
 		error(_("failed to initialize worktree config"));
 		return 1;
 	}
 
-	if (repo_config_set_worktree_gently(the_repository,
+	if (repo_config_set_worktree_gently(repo,
 					    "core.sparseCheckout",
 					    mode ? "true" : "false") ||
-	    repo_config_set_worktree_gently(the_repository,
+	    repo_config_set_worktree_gently(repo,
 					    "core.sparseCheckoutCone",
 					    mode == MODE_CONE_PATTERNS ?
 						"true" : "false"))
 		return 1;
 
 	if (mode == MODE_NO_PATTERNS)
-		return set_sparse_index_config(the_repository, 0);
+		return set_sparse_index_config(repo, 0);
 
 	return 0;
 }
@@ -410,7 +413,7 @@ static enum sparse_checkout_mode update_cone_mode(int *cone_mode) {
 	return MODE_ALL_PATTERNS;
 }
 
-static int update_modes(int *cone_mode, int *sparse_index)
+static int update_modes(struct repository *repo, int *cone_mode, int *sparse_index)
 {
 	int mode, record_mode;
 
@@ -418,20 +421,20 @@ static int update_modes(int *cone_mode, int *sparse_index)
 	record_mode = (*cone_mode != -1) || !core_apply_sparse_checkout;
 
 	mode = update_cone_mode(cone_mode);
-	if (record_mode && set_config(mode))
+	if (record_mode && set_config(repo, mode))
 		return 1;
 
 	/* Set sparse-index/non-sparse-index mode if specified */
 	if (*sparse_index >= 0) {
-		if (set_sparse_index_config(the_repository, *sparse_index) < 0)
+		if (set_sparse_index_config(repo, *sparse_index) < 0)
 			die(_("failed to modify sparse-index config"));
 
 		/* force an index rewrite */
-		repo_read_index(the_repository);
-		the_repository->index->updated_workdir = 1;
+		repo_read_index(repo);
+		repo->index->updated_workdir = 1;
 
 		if (!*sparse_index)
-			ensure_full_index(the_repository->index);
+			ensure_full_index(repo->index);
 	}
 
 	return 0;
@@ -448,7 +451,7 @@ static struct sparse_checkout_init_opts {
 } init_opts;
 
 static int sparse_checkout_init(int argc, const char **argv, const char *prefix,
-				struct repository *repo UNUSED)
+				struct repository *repo)
 {
 	struct pattern_list pl;
 	char *sparse_filename;
@@ -464,7 +467,7 @@ static int sparse_checkout_init(int argc, const char **argv, const char *prefix,
 	};
 
 	setup_work_tree();
-	repo_read_index(the_repository);
+	repo_read_index(repo);
 
 	init_opts.cone_mode = -1;
 	init_opts.sparse_index = -1;
@@ -473,7 +476,7 @@ static int sparse_checkout_init(int argc, const char **argv, const char *prefix,
 			     builtin_sparse_checkout_init_options,
 			     builtin_sparse_checkout_init_usage, 0);
 
-	if (update_modes(&init_opts.cone_mode, &init_opts.sparse_index))
+	if (update_modes(repo, &init_opts.cone_mode, &init_opts.sparse_index))
 		return 1;
 
 	memset(&pl, 0, sizeof(pl));
@@ -485,14 +488,14 @@ static int sparse_checkout_init(int argc, const char **argv, const char *prefix,
 	if (res >= 0) {
 		free(sparse_filename);
 		clear_pattern_list(&pl);
-		return update_working_directory(NULL);
+		return update_working_directory(repo, NULL);
 	}
 
-	if (repo_get_oid(the_repository, "HEAD", &oid)) {
+	if (repo_get_oid(repo, "HEAD", &oid)) {
 		FILE *fp;
 
 		/* assume we are in a fresh repo, but update the sparse-checkout file */
-		if (safe_create_leading_directories(the_repository, sparse_filename))
+		if (safe_create_leading_directories(repo, sparse_filename))
 			die(_("unable to create leading directories of %s"),
 			    sparse_filename);
 		fp = xfopen(sparse_filename, "w");
@@ -511,7 +514,7 @@ static int sparse_checkout_init(int argc, const char **argv, const char *prefix,
 	add_pattern("!/*/", empty_base, 0, &pl, 0);
 	pl.use_cone_patterns = init_opts.cone_mode;
 
-	return write_patterns_and_update(&pl);
+	return write_patterns_and_update(repo, &pl);
 }
 
 static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *path)
@@ -674,7 +677,8 @@ static void add_patterns_literal(int argc, const char **argv,
 	add_patterns_from_input(pl, argc, argv, use_stdin ? stdin : NULL);
 }
 
-static int modify_pattern_list(struct strvec *args, int use_stdin,
+static int modify_pattern_list(struct repository *repo,
+			       struct strvec *args, int use_stdin,
 			       enum modify_type m)
 {
 	int result;
@@ -696,22 +700,23 @@ static int modify_pattern_list(struct strvec *args, int use_stdin,
 	}
 
 	if (!core_apply_sparse_checkout) {
-		set_config(MODE_ALL_PATTERNS);
+		set_config(repo, MODE_ALL_PATTERNS);
 		core_apply_sparse_checkout = 1;
 		changed_config = 1;
 	}
 
-	result = write_patterns_and_update(pl);
+	result = write_patterns_and_update(repo, pl);
 
 	if (result && changed_config)
-		set_config(MODE_NO_PATTERNS);
+		set_config(repo, MODE_NO_PATTERNS);
 
 	clear_pattern_list(pl);
 	free(pl);
 	return result;
 }
 
-static void sanitize_paths(struct strvec *args,
+static void sanitize_paths(struct repository *repo,
+			   struct strvec *args,
 			   const char *prefix, int skip_checks)
 {
 	int i;
@@ -752,7 +757,7 @@ static void sanitize_paths(struct strvec *args,
 
 	for (i = 0; i < args->nr; i++) {
 		struct cache_entry *ce;
-		struct index_state *index = the_repository->index;
+		struct index_state *index = repo->index;
 		int pos = index_name_pos(index, args->v[i], strlen(args->v[i]));
 
 		if (pos < 0)
@@ -779,7 +784,7 @@ static struct sparse_checkout_add_opts {
 } add_opts;
 
 static int sparse_checkout_add(int argc, const char **argv, const char *prefix,
-			       struct repository *repo UNUSED)
+			       struct repository *repo)
 {
 	static struct option builtin_sparse_checkout_add_options[] = {
 		OPT_BOOL_F(0, "skip-checks", &add_opts.skip_checks,
@@ -796,7 +801,7 @@ static int sparse_checkout_add(int argc, const char **argv, const char *prefix,
 	if (!core_apply_sparse_checkout)
 		die(_("no sparse-checkout to add to"));
 
-	repo_read_index(the_repository);
+	repo_read_index(repo);
 
 	argc = parse_options(argc, argv, prefix,
 			     builtin_sparse_checkout_add_options,
@@ -804,9 +809,9 @@ static int sparse_checkout_add(int argc, const char **argv, const char *prefix,
 
 	for (int i = 0; i < argc; i++)
 		strvec_push(&patterns, argv[i]);
-	sanitize_paths(&patterns, prefix, add_opts.skip_checks);
+	sanitize_paths(repo, &patterns, prefix, add_opts.skip_checks);
 
-	ret = modify_pattern_list(&patterns, add_opts.use_stdin, ADD);
+	ret = modify_pattern_list(repo, &patterns, add_opts.use_stdin, ADD);
 
 	strvec_clear(&patterns);
 	return ret;
@@ -825,7 +830,7 @@ static struct sparse_checkout_set_opts {
 } set_opts;
 
 static int sparse_checkout_set(int argc, const char **argv, const char *prefix,
-			       struct repository *repo UNUSED)
+			       struct repository *repo)
 {
 	int default_patterns_nr = 2;
 	const char *default_patterns[] = {"/*", "!/*/", NULL};
@@ -847,7 +852,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix,
 	int ret;
 
 	setup_work_tree();
-	repo_read_index(the_repository);
+	repo_read_index(repo);
 
 	set_opts.cone_mode = -1;
 	set_opts.sparse_index = -1;
@@ -856,7 +861,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix,
 			     builtin_sparse_checkout_set_options,
 			     builtin_sparse_checkout_set_usage, 0);
 
-	if (update_modes(&set_opts.cone_mode, &set_opts.sparse_index))
+	if (update_modes(repo, &set_opts.cone_mode, &set_opts.sparse_index))
 		return 1;
 
 	/*
@@ -870,10 +875,10 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix,
 	} else {
 		for (int i = 0; i < argc; i++)
 			strvec_push(&patterns, argv[i]);
-		sanitize_paths(&patterns, prefix, set_opts.skip_checks);
+		sanitize_paths(repo, &patterns, prefix, set_opts.skip_checks);
 	}
 
-	ret = modify_pattern_list(&patterns, set_opts.use_stdin, REPLACE);
+	ret = modify_pattern_list(repo, &patterns, set_opts.use_stdin, REPLACE);
 
 	strvec_clear(&patterns);
 	return ret;
@@ -891,7 +896,7 @@ static struct sparse_checkout_reapply_opts {
 
 static int sparse_checkout_reapply(int argc, const char **argv,
 				   const char *prefix,
-				   struct repository *repo UNUSED)
+				   struct repository *repo)
 {
 	static struct option builtin_sparse_checkout_reapply_options[] = {
 		OPT_BOOL(0, "cone", &reapply_opts.cone_mode,
@@ -912,12 +917,107 @@ static int sparse_checkout_reapply(int argc, const char **argv,
 			     builtin_sparse_checkout_reapply_options,
 			     builtin_sparse_checkout_reapply_usage, 0);
 
-	repo_read_index(the_repository);
+	repo_read_index(repo);
 
-	if (update_modes(&reapply_opts.cone_mode, &reapply_opts.sparse_index))
+	if (update_modes(repo, &reapply_opts.cone_mode, &reapply_opts.sparse_index))
 		return 1;
 
-	return update_working_directory(NULL);
+	return update_working_directory(repo, NULL);
+}
+
+static char const * const builtin_sparse_checkout_clean_usage[] = {
+	"git sparse-checkout clean [-n|--dry-run]",
+	NULL
+};
+
+static int list_file_iterator(const char *path, const void *data)
+{
+	const char *msg = data;
+
+	printf(msg, path);
+	return 0;
+}
+
+static void list_every_file_in_dir(const char *msg,
+				   const char *directory)
+{
+	struct strbuf path = STRBUF_INIT;
+
+	strbuf_addstr(&path, directory);
+	for_each_file_in_dir(&path, list_file_iterator, msg);
+	strbuf_release(&path);
+}
+
+static const char *msg_remove = N_("Removing %s\n");
+static const char *msg_would_remove = N_("Would remove %s\n");
+
+static int sparse_checkout_clean(int argc, const char **argv,
+				   const char *prefix,
+				   struct repository *repo)
+{
+	struct strbuf full_path = STRBUF_INIT;
+	const char *msg = msg_remove;
+	size_t worktree_len;
+	int force = 0, dry_run = 0, verbose = 0;
+	int require_force = 1;
+
+	struct option builtin_sparse_checkout_clean_options[] = {
+		OPT__DRY_RUN(&dry_run, N_("dry run")),
+		OPT__FORCE(&force, N_("force"), PARSE_OPT_NOCOMPLETE),
+		OPT__VERBOSE(&verbose, N_("report each affected file, not just directories")),
+		OPT_END(),
+	};
+
+	setup_work_tree();
+	if (!core_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"));
+
+	argc = parse_options(argc, argv, prefix,
+			     builtin_sparse_checkout_clean_options,
+			     builtin_sparse_checkout_clean_usage, 0);
+
+	repo_config_get_bool(repo, "clean.requireforce", &require_force);
+	if (require_force && !force && !dry_run)
+		die(_("for safety, refusing to clean without one of --force or --dry-run"));
+
+	if (dry_run)
+		msg = msg_would_remove;
+
+	if (repo_read_index(repo) < 0)
+		die(_("failed to read index"));
+
+	if (convert_to_sparse(repo->index, SPARSE_INDEX_MEMORY_ONLY) ||
+	    repo->index->sparse_index == INDEX_EXPANDED)
+		die(_("failed to convert index to a sparse index; resolve merge conflicts and try again"));
+
+	strbuf_addstr(&full_path, repo->worktree);
+	strbuf_addch(&full_path, '/');
+	worktree_len = full_path.len;
+
+	for (size_t i = 0; i < repo->index->cache_nr; i++) {
+		struct cache_entry *ce = repo->index->cache[i];
+		if (!S_ISSPARSEDIR(ce->ce_mode))
+			continue;
+		strbuf_setlen(&full_path, worktree_len);
+		strbuf_add(&full_path, ce->name, ce->ce_namelen);
+
+		if (!is_directory(full_path.buf))
+			continue;
+
+		if (verbose)
+			list_every_file_in_dir(msg, ce->name);
+		else
+			printf(msg, ce->name);
+
+		if (dry_run <= 0 &&
+		    remove_dir_recursively(&full_path, 0))
+			warning_errno(_("failed to remove '%s'"), ce->name);
+	}
+
+	strbuf_release(&full_path);
+	return 0;
 }
 
 static char const * const builtin_sparse_checkout_disable_usage[] = {
@@ -927,7 +1027,7 @@ static char const * const builtin_sparse_checkout_disable_usage[] = {
 
 static int sparse_checkout_disable(int argc, const char **argv,
 				   const char *prefix,
-				   struct repository *repo UNUSED)
+				   struct repository *repo)
 {
 	static struct option builtin_sparse_checkout_disable_options[] = {
 		OPT_END(),
@@ -955,7 +1055,7 @@ static int sparse_checkout_disable(int argc, const char **argv,
 	 * are expecting to do that when disabling sparse-checkout.
 	 */
 	give_advice_on_expansion = 0;
-	repo_read_index(the_repository);
+	repo_read_index(repo);
 
 	memset(&pl, 0, sizeof(pl));
 	hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0);
@@ -966,13 +1066,13 @@ static int sparse_checkout_disable(int argc, const char **argv,
 	add_pattern("/*", empty_base, 0, &pl, 0);
 
 	prepare_repo_settings(the_repository);
-	the_repository->settings.sparse_index = 0;
+	repo->settings.sparse_index = 0;
 
-	if (update_working_directory(&pl))
+	if (update_working_directory(repo, &pl))
 		die(_("error while refreshing working directory"));
 
 	clear_pattern_list(&pl);
-	return set_config(MODE_NO_PATTERNS);
+	return set_config(repo, MODE_NO_PATTERNS);
 }
 
 static char const * const builtin_sparse_checkout_check_rules_usage[] = {
@@ -987,14 +1087,17 @@ static struct sparse_checkout_check_rules_opts {
 	char *rules_file;
 } check_rules_opts;
 
-static int check_rules(struct pattern_list *pl, int null_terminated) {
+static int check_rules(struct repository *repo,
+		       struct pattern_list *pl,
+		       int null_terminated)
+{
 	struct strbuf line = STRBUF_INIT;
 	struct strbuf unquoted = STRBUF_INIT;
 	char *path;
 	int line_terminator = null_terminated ? 0 : '\n';
 	strbuf_getline_fn getline_fn = null_terminated ? strbuf_getline_nul
 		: strbuf_getline;
-	the_repository->index->sparse_checkout_patterns = pl;
+	repo->index->sparse_checkout_patterns = pl;
 	while (!getline_fn(&line, stdin)) {
 		path = line.buf;
 		if (!null_terminated && line.buf[0] == '"') {
@@ -1006,7 +1109,7 @@ static int check_rules(struct pattern_list *pl, int null_terminated) {
 			path = unquoted.buf;
 		}
 
-		if (path_in_sparse_checkout(path, the_repository->index))
+		if (path_in_sparse_checkout(path, repo->index))
 			write_name_quoted(path, stdout, line_terminator);
 	}
 	strbuf_release(&line);
@@ -1016,7 +1119,7 @@ static int check_rules(struct pattern_list *pl, int null_terminated) {
 }
 
 static int sparse_checkout_check_rules(int argc, const char **argv, const char *prefix,
-				       struct repository *repo UNUSED)
+				       struct repository *repo)
 {
 	static struct option builtin_sparse_checkout_check_rules_options[] = {
 		OPT_BOOL('z', NULL, &check_rules_opts.null_termination,
@@ -1055,7 +1158,7 @@ static int sparse_checkout_check_rules(int argc, const char **argv, const char *
 		free(sparse_filename);
 	}
 
-	ret = check_rules(&pl, check_rules_opts.null_termination);
+	ret = check_rules(repo, &pl, check_rules_opts.null_termination);
 	clear_pattern_list(&pl);
 	free(check_rules_opts.rules_file);
 	return ret;
@@ -1073,6 +1176,7 @@ int cmd_sparse_checkout(int argc,
 		OPT_SUBCOMMAND("set", &fn, sparse_checkout_set),
 		OPT_SUBCOMMAND("add", &fn, sparse_checkout_add),
 		OPT_SUBCOMMAND("reapply", &fn, sparse_checkout_reapply),
+		OPT_SUBCOMMAND("clean", &fn, sparse_checkout_clean),
 		OPT_SUBCOMMAND("disable", &fn, sparse_checkout_disable),
 		OPT_SUBCOMMAND("check-rules", &fn, sparse_checkout_check_rules),
 		OPT_END(),
@@ -1084,8 +1188,8 @@ int cmd_sparse_checkout(int argc,
 
 	repo_config(the_repository, git_default_config, NULL);
 
-	prepare_repo_settings(the_repository);
-	the_repository->settings.command_requires_full_index = 0;
+	prepare_repo_settings(repo);
+	repo->settings.command_requires_full_index = 0;
 
 	return fn(argc, argv, prefix, repo);
 }
diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh
index a8dcd9b..50628ee 100755
--- a/ci/install-dependencies.sh
+++ b/ci/install-dependencies.sh
@@ -10,6 +10,8 @@
 P4WHENCE=https://cdist2.perforce.com/perforce/r23.2
 LFSWHENCE=https://github.com/github/git-lfs/releases/download/v$LINUX_GIT_LFS_VERSION
 JGITWHENCE=https://repo1.maven.org/maven2/org/eclipse/jgit/org.eclipse.jgit.pgm/6.8.0.202311291450-r/org.eclipse.jgit.pgm-6.8.0.202311291450-r.sh
+CARGO_MSRV_VERSION=0.18.4
+CARGO_MSRV_WHENCE=https://github.com/foresterre/cargo-msrv/releases/download/v$CARGO_MSRV_VERSION/cargo-msrv-x86_64-unknown-linux-musl-v$CARGO_MSRV_VERSION.tgz
 
 # Make sudo a no-op and execute the command directly when running as root.
 # While using sudo would be fine on most platforms when we are root already,
@@ -129,21 +131,28 @@
 
 case "$jobname" in
 ClangFormat)
-	sudo apt-get -q update
 	sudo apt-get -q -y install clang-format
 	;;
 StaticAnalysis)
-	sudo apt-get -q update
 	sudo apt-get -q -y install coccinelle libcurl4-openssl-dev libssl-dev \
 		libexpat-dev gettext make
 	;;
+RustAnalysis)
+	sudo apt-get -q -y install rustup
+	rustup default stable
+	rustup component add clippy rustfmt
+
+	wget -q "$CARGO_MSRV_WHENCE" -O "cargo-msvc.tgz"
+	sudo mkdir -p "$CUSTOM_PATH"
+	sudo tar -xf "cargo-msvc.tgz" --strip-components=1 \
+		--directory "$CUSTOM_PATH" --wildcards "*/cargo-msrv"
+	sudo chmod a+x "$CUSTOM_PATH/cargo-msrv"
+	;;
 sparse)
-	sudo apt-get -q update -q
 	sudo apt-get -q -y install libssl-dev libcurl4-openssl-dev \
 		libexpat-dev gettext zlib1g-dev sparse
 	;;
 Documentation)
-	sudo apt-get -q update
 	sudo apt-get -q -y install asciidoc xmlto docbook-xsl-ns make
 
 	test -n "$ALREADY_HAVE_ASCIIDOCTOR" ||
diff --git a/ci/run-rust-checks.sh b/ci/run-rust-checks.sh
new file mode 100755
index 0000000..b5ad9e8
--- /dev/null
+++ b/ci/run-rust-checks.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+. ${0%/*}/lib.sh
+
+set +x
+
+if ! group "Check Rust formatting" cargo fmt --all --check
+then
+	RET=1
+fi
+
+if ! group "Check for common Rust mistakes" cargo clippy --all-targets --all-features -- -Dwarnings
+then
+	RET=1
+fi
+
+if ! group "Check for minimum required Rust version" cargo msrv verify
+then
+	RET=1
+fi
+
+exit $RET
diff --git a/compat/mingw.c b/compat/mingw.c
index 8538e3d..736a07a 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1,26 +1,26 @@
 #define USE_THE_REPOSITORY_VARIABLE
 #define DISABLE_SIGN_COMPARE_WARNINGS
 
-#include "../git-compat-util.h"
-#include "win32.h"
-#include <aclapi.h>
-#include <sddl.h>
-#include <conio.h>
-#include <wchar.h>
-#include "../strbuf.h"
-#include "../run-command.h"
-#include "../abspath.h"
-#include "../alloc.h"
-#include "win32/lazyload.h"
-#include "../config.h"
-#include "../environment.h"
-#include "../trace2.h"
-#include "../symlinks.h"
-#include "../wrapper.h"
+#include "git-compat-util.h"
+#include "abspath.h"
+#include "alloc.h"
+#include "config.h"
 #include "dir.h"
+#include "environment.h"
 #include "gettext.h"
+#include "run-command.h"
+#include "strbuf.h"
+#include "symlinks.h"
+#include "trace2.h"
+#include "win32.h"
+#include "win32/lazyload.h"
+#include "wrapper.h"
+#include <aclapi.h>
+#include <conio.h>
+#include <sddl.h>
 #define SECURITY_WIN32
 #include <sspi.h>
+#include <wchar.h>
 #include <winternl.h>
 
 #define STATUS_DELETE_PENDING ((NTSTATUS) 0xC0000056)
diff --git a/config.c b/config.c
index 74bf76a..71b136b 100644
--- a/config.c
+++ b/config.c
@@ -1278,11 +1278,23 @@ int git_config_string(char **dest, const char *var, const char *value)
 
 int git_config_pathname(char **dest, const char *var, const char *value)
 {
+	int is_optional;
+	char *path;
+
 	if (!value)
 		return config_error_nonbool(var);
-	*dest = interpolate_path(value, 0);
-	if (!*dest)
+
+	is_optional = skip_prefix(value, ":(optional)", &value);
+	path = interpolate_path(value, 0);
+	if (!path)
 		die(_("failed to expand user dir in: '%s'"), value);
+
+	if (is_optional && is_missing_file(path)) {
+		free(path);
+		return 0;
+	}
+
+	*dest = path;
 	return 0;
 }
 
diff --git a/contrib/credential/libsecret/Makefile b/contrib/credential/libsecret/Makefile
index 97ce9c9..7cacc57 100644
--- a/contrib/credential/libsecret/Makefile
+++ b/contrib/credential/libsecret/Makefile
@@ -1,28 +1,27 @@
 # The default target of this Makefile is...
-all::
-
-MAIN:=git-credential-libsecret
-all:: $(MAIN)
-
-CC = gcc
-RM = rm -f
-CFLAGS = -g -O2 -Wall
-PKG_CONFIG = pkg-config
+all:: git-credential-libsecret
 
 -include ../../../config.mak.autogen
 -include ../../../config.mak
 
+prefix ?= /usr/local
+gitexecdir ?= $(prefix)/libexec/git-core
+
+CC ?= gcc
+CFLAGS ?= -g -O2 -Wall
+PKG_CONFIG ?= pkg-config
+RM ?= rm -f
+
 INCS:=$(shell $(PKG_CONFIG) --cflags libsecret-1 glib-2.0)
 LIBS:=$(shell $(PKG_CONFIG) --libs libsecret-1 glib-2.0)
 
-SRCS:=$(MAIN).c
-OBJS:=$(SRCS:.c=.o)
-
 %.o: %.c
 	$(CC) $(CFLAGS) $(CPPFLAGS) $(INCS) -o $@ -c $<
 
-$(MAIN): $(OBJS)
-	$(CC) -o $@ $(LDFLAGS) $^ $(LIBS)
+git-credential-libsecret: git-credential-libsecret.o
+	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS)
 
 clean:
-	@$(RM) $(MAIN) $(OBJS)
+	$(RM) git-credential-libsecret git-credential-libsecret.o
+
+.PHONY: all clean
diff --git a/contrib/credential/osxkeychain/Makefile b/contrib/credential/osxkeychain/Makefile
index 0948297..c7d9121 100644
--- a/contrib/credential/osxkeychain/Makefile
+++ b/contrib/credential/osxkeychain/Makefile
@@ -1,19 +1,24 @@
 # The default target of this Makefile is...
 all:: git-credential-osxkeychain
 
-CC = gcc
-RM = rm -f
-CFLAGS = -g -O2 -Wall
-
 -include ../../../config.mak.autogen
 -include ../../../config.mak
 
-git-credential-osxkeychain: git-credential-osxkeychain.o
-	$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) \
-		-framework Security -framework CoreFoundation
+prefix ?= /usr/local
+gitexecdir ?= $(prefix)/libexec/git-core
 
-git-credential-osxkeychain.o: git-credential-osxkeychain.c
-	$(CC) -c $(CFLAGS) $<
+CC ?= gcc
+CFLAGS ?= -g -O2 -Wall
+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
 
 clean:
 	$(RM) git-credential-osxkeychain git-credential-osxkeychain.o
+
+.PHONY: all clean
diff --git a/diff-no-index.c b/diff-no-index.c
index 88ae4ce..f320424 100644
--- a/diff-no-index.c
+++ b/diff-no-index.c
@@ -21,30 +21,21 @@
 
 static int read_directory_contents(const char *path, struct string_list *list,
 				   const struct pathspec *pathspec,
-				   int skip)
+				   struct strbuf *match)
 {
-	struct strbuf match = STRBUF_INIT;
-	int len;
+	int len = match->len;
 	DIR *dir;
 	struct dirent *e;
 
 	if (!(dir = opendir(path)))
 		return error("Could not open directory %s", path);
 
-	if (pathspec) {
-		strbuf_addstr(&match, path);
-		strbuf_complete(&match, '/');
-		strbuf_remove(&match, 0, skip);
-
-		len = match.len;
-	}
-
 	while ((e = readdir_skip_dot_and_dotdot(dir))) {
 		if (pathspec) {
 			int is_dir = 0;
 
-			strbuf_setlen(&match, len);
-			strbuf_addstr(&match, e->d_name);
+			strbuf_setlen(match, len);
+			strbuf_addstr(match, e->d_name);
 			if (NOT_CONSTANT(DTYPE(e)) != DT_UNKNOWN) {
 				is_dir = (DTYPE(e) == DT_DIR);
 			} else {
@@ -57,7 +48,7 @@ static int read_directory_contents(const char *path, struct string_list *list,
 			}
 
 			if (!match_leading_pathspec(NULL, pathspec,
-						    match.buf, match.len,
+						    match->buf, match->len,
 						    0, NULL, is_dir))
 				continue;
 		}
@@ -65,7 +56,7 @@ static int read_directory_contents(const char *path, struct string_list *list,
 		string_list_insert(list, e->d_name);
 	}
 
-	strbuf_release(&match);
+	strbuf_setlen(match, len);
 	closedir(dir);
 	return 0;
 }
@@ -169,7 +160,8 @@ static struct diff_filespec *noindex_filespec(const struct git_hash_algo *algop,
 
 static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop,
 		      const char *name1, const char *name2, int recursing,
-		      const struct pathspec *ps, int skip1, int skip2)
+		      const struct pathspec *ps,
+		      struct strbuf *ps_match1, struct strbuf *ps_match2)
 {
 	int mode1 = 0, mode2 = 0;
 	enum special special1 = SPECIAL_NONE, special2 = SPECIAL_NONE;
@@ -208,10 +200,12 @@ static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop,
 		struct string_list p2 = STRING_LIST_INIT_DUP;
 		int i1, i2, ret = 0;
 		size_t len1 = 0, len2 = 0;
+		size_t match1_len = ps_match1->len;
+		size_t match2_len = ps_match2->len;
 
-		if (name1 && read_directory_contents(name1, &p1, ps, skip1))
+		if (name1 && read_directory_contents(name1, &p1, ps, ps_match1))
 			return -1;
-		if (name2 && read_directory_contents(name2, &p2, ps, skip2)) {
+		if (name2 && read_directory_contents(name2, &p2, ps, ps_match2)) {
 			string_list_clear(&p1, 0);
 			return -1;
 		}
@@ -235,6 +229,11 @@ static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop,
 			strbuf_setlen(&buffer1, len1);
 			strbuf_setlen(&buffer2, len2);
 
+			if (ps) {
+				strbuf_setlen(ps_match1, match1_len);
+				strbuf_setlen(ps_match2, match2_len);
+			}
+
 			if (i1 == p1.nr)
 				comp = 1;
 			else if (i2 == p2.nr)
@@ -245,18 +244,28 @@ static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop,
 			if (comp > 0)
 				n1 = NULL;
 			else {
-				strbuf_addstr(&buffer1, p1.items[i1++].string);
+				strbuf_addstr(&buffer1, p1.items[i1].string);
+				if (ps) {
+					strbuf_addstr(ps_match1, p1.items[i1].string);
+					strbuf_complete(ps_match1, '/');
+				}
 				n1 = buffer1.buf;
+				i1++;
 			}
 
 			if (comp < 0)
 				n2 = NULL;
 			else {
-				strbuf_addstr(&buffer2, p2.items[i2++].string);
+				strbuf_addstr(&buffer2, p2.items[i2].string);
+				if (ps) {
+					strbuf_addstr(ps_match2, p2.items[i2].string);
+					strbuf_complete(ps_match2, '/');
+				}
 				n2 = buffer2.buf;
+				i2++;
 			}
 
-			ret = queue_diff(o, algop, n1, n2, 1, ps, skip1, skip2);
+			ret = queue_diff(o, algop, n1, n2, 1, ps, ps_match1, ps_match2);
 		}
 		string_list_clear(&p1, 0);
 		string_list_clear(&p2, 0);
@@ -346,7 +355,8 @@ int diff_no_index(struct rev_info *revs, const struct git_hash_algo *algop,
 		  int implicit_no_index, int argc, const char **argv)
 {
 	struct pathspec pathspec, *ps = NULL;
-	int i, no_index, skip1 = 0, skip2 = 0;
+	struct strbuf ps_match1 = STRBUF_INIT, ps_match2 = STRBUF_INIT;
+	int i, no_index;
 	int ret = 1;
 	const char *paths[2];
 	char *to_free[ARRAY_SIZE(paths)] = { 0 };
@@ -387,11 +397,6 @@ int diff_no_index(struct rev_info *revs, const struct git_hash_algo *algop,
 			       NULL, &argv[2]);
 		if (pathspec.nr)
 			ps = &pathspec;
-
-		skip1 = strlen(paths[0]);
-		skip1 += paths[0][skip1] == '/' ? 0 : 1;
-		skip2 = strlen(paths[1]);
-		skip2 += paths[1][skip2] == '/' ? 0 : 1;
 	} else if (argc > 2) {
 		warning(_("Limiting comparison with pathspecs is only "
 			  "supported if both paths are directories."));
@@ -415,7 +420,7 @@ int diff_no_index(struct rev_info *revs, const struct git_hash_algo *algop,
 	revs->diffopt.flags.exit_with_status = 1;
 
 	if (queue_diff(&revs->diffopt, algop, paths[0], paths[1], 0, ps,
-		       skip1, skip2))
+		       &ps_match1, &ps_match2))
 		goto out;
 	diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/");
 	diffcore_std(&revs->diffopt);
@@ -431,6 +436,8 @@ int diff_no_index(struct rev_info *revs, const struct git_hash_algo *algop,
 	for (i = 0; i < ARRAY_SIZE(to_free); i++)
 		free(to_free[i]);
 	strbuf_release(&replacement);
+	strbuf_release(&ps_match1);
+	strbuf_release(&ps_match2);
 	if (ps)
 		clear_pathspec(ps);
 	return ret;
diff --git a/diff.c b/diff.c
index 87fa16b..a74e701 100644
--- a/diff.c
+++ b/diff.c
@@ -6828,18 +6828,38 @@ void diff_flush(struct diff_options *options)
 			     DIFF_FORMAT_NAME |
 			     DIFF_FORMAT_NAME_STATUS |
 			     DIFF_FORMAT_CHECKDIFF)) {
+		/*
+		 * make sure diff_Flush_patch_quietly() to be silent.
+		 */
+		FILE *dev_null = NULL;
+		int saved_color_moved = options->color_moved;
+
+		if (options->flags.diff_from_contents) {
+			dev_null = xfopen("/dev/null", "w");
+			options->color_moved = 0;
+		}
 		for (i = 0; i < q->nr; i++) {
 			struct diff_filepair *p = q->queue[i];
 
 			if (!check_pair_status(p))
 				continue;
 
-			if (options->flags.diff_from_contents &&
-			    !diff_flush_patch_quietly(p, options))
-				continue;
+			if (options->flags.diff_from_contents) {
+				FILE *saved_file = options->file;
+				int found_changes;
 
+				options->file = dev_null;
+				found_changes = diff_flush_patch_quietly(p, options);
+				options->file = saved_file;
+				if (!found_changes)
+					continue;
+			}
 			flush_one_pair(p, options);
 		}
+		if (options->flags.diff_from_contents) {
+			fclose(dev_null);
+			options->color_moved = saved_color_moved;
+		}
 		separator++;
 	}
 
@@ -6890,6 +6910,15 @@ void diff_flush(struct diff_options *options)
 	if (output_format & DIFF_FORMAT_NO_OUTPUT &&
 	    options->flags.exit_with_status &&
 	    options->flags.diff_from_contents) {
+		/*
+		 * run diff_flush_patch for the exit status. setting
+		 * options->file to /dev/null should be safe, because we
+		 * aren't supposed to produce any output anyway.
+		 */
+		diff_free_file(options);
+		options->file = xfopen("/dev/null", "w");
+		options->close_file = 1;
+		options->color_moved = 0;
 		for (i = 0; i < q->nr; i++) {
 			struct diff_filepair *p = q->queue[i];
 			if (check_pair_status(p))
diff --git a/dir.c b/dir.c
index 0a67a99..f683f8b 100644
--- a/dir.c
+++ b/dir.c
@@ -30,6 +30,7 @@
 #include "read-cache-ll.h"
 #include "setup.h"
 #include "sparse-index.h"
+#include "strbuf.h"
 #include "submodule-config.h"
 #include "symlinks.h"
 #include "trace2.h"
@@ -87,6 +88,33 @@ struct dirent *readdir_skip_dot_and_dotdot(DIR *dirp)
 	return e;
 }
 
+int for_each_file_in_dir(struct strbuf *path, file_iterator fn, const void *data)
+{
+	struct dirent *e;
+	int res = 0;
+	size_t baselen = path->len;
+	DIR *dir = opendir(path->buf);
+
+	if (!dir)
+		return 0;
+
+	while (!res && (e = readdir_skip_dot_and_dotdot(dir)) != NULL) {
+		unsigned char dtype = get_dtype(e, path, 0);
+		strbuf_setlen(path, baselen);
+		strbuf_addstr(path, e->d_name);
+
+		if (dtype == DT_REG) {
+			res = fn(path->buf, data);
+		} else if (dtype == DT_DIR) {
+			strbuf_addch(path, '/');
+			res = for_each_file_in_dir(path, fn, data);
+		}
+	}
+
+	closedir(dir);
+	return res;
+}
+
 int count_slashes(const char *s)
 {
 	int cnt = 0;
diff --git a/dir.h b/dir.h
index fc9be7b..20d4a07 100644
--- a/dir.h
+++ b/dir.h
@@ -537,6 +537,20 @@ int get_sparse_checkout_patterns(struct pattern_list *pl);
 int remove_dir_recursively(struct strbuf *path, int flag);
 
 /*
+ * This function pointer type is called on each file discovered in
+ * for_each_file_in_dir. The iteration stops if this method returns
+ * non-zero.
+ */
+typedef int (*file_iterator)(const char *path, const void *data);
+
+struct strbuf;
+/*
+ * Given a directory path, recursively visit each file within, including
+ * within subdirectories.
+ */
+int for_each_file_in_dir(struct strbuf *path, file_iterator fn, const void *data);
+
+/*
  * Tries to remove the path, along with leading empty directories so long as
  * those empty directories are not startup_info->original_cwd.  Ignores
  * ENOENT.
diff --git a/fsck.c b/fsck.c
index 171b424..341e100 100644
--- a/fsck.c
+++ b/fsck.c
@@ -1067,6 +1067,24 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer,
 	else
 		ret = fsck_ident(&buffer, 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);
+		if (!eol) {
+			ret = report(options, oid, OBJ_TAG, FSCK_MSG_BAD_GPGSIG, "invalid format - unexpected end after 'gpgsig' or 'gpgsig-sha256' line");
+			goto done;
+		}
+		buffer = eol + 1;
+
+		while (buffer < buffer_end && starts_with(buffer, " ")) {
+			eol = memchr(buffer, '\n', buffer_end - buffer);
+			if (!eol) {
+				ret = report(options, oid, OBJ_TAG, FSCK_MSG_BAD_HEADER_CONTINUATION, "invalid format - unexpected end in 'gpgsig' or 'gpgsig-sha256' continuation line");
+				goto done;
+			}
+			buffer = eol + 1;
+		}
+	}
+
 	if (buffer < buffer_end && !starts_with(buffer, "\n")) {
 		/*
 		 * The verify_headers() check will allow
diff --git a/fsck.h b/fsck.h
index 759df97..cb6ef32 100644
--- a/fsck.h
+++ b/fsck.h
@@ -25,9 +25,11 @@ enum fsck_msg_type {
 	FUNC(NUL_IN_HEADER, FATAL) \
 	FUNC(UNTERMINATED_HEADER, FATAL) \
 	/* errors */ \
+	FUNC(BAD_HEADER_CONTINUATION, ERROR) \
 	FUNC(BAD_DATE, ERROR) \
 	FUNC(BAD_DATE_OVERFLOW, ERROR) \
 	FUNC(BAD_EMAIL, ERROR) \
+	FUNC(BAD_GPGSIG, ERROR) \
 	FUNC(BAD_NAME, ERROR) \
 	FUNC(BAD_OBJECT_SHA1, ERROR) \
 	FUNC(BAD_PACKED_REF_ENTRY, ERROR) \
diff --git a/log-tree.c b/log-tree.c
index 67d9ace..7d917f2 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -718,7 +718,8 @@ static void show_diff_of_diff(struct rev_info *opt)
 			.creation_factor = opt->creation_factor,
 			.dual_color = 1,
 			.max_memory = RANGE_DIFF_MAX_MEMORY_DEFAULT,
-			.diffopt = &opts
+			.diffopt = &opts,
+			.log_arg = &opt->rdiff_log_arg
 		};
 
 		memcpy(&dq, &diff_queued_diff, sizeof(diff_queued_diff));
diff --git a/mailmap.c b/mailmap.c
index 56c7210..37fd158 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -1,5 +1,4 @@
 #define USE_THE_REPOSITORY_VARIABLE
-#define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "git-compat-util.h"
 #include "environment.h"
@@ -243,10 +242,9 @@ void clear_mailmap(struct string_list *map)
 static struct string_list_item *lookup_prefix(struct string_list *map,
 					      const char *string, size_t len)
 {
-	int i = string_list_find_insert_index(map, string, 1);
-	if (i < 0) {
-		/* exact match */
-		i = -1 - i;
+	bool exact_match;
+	size_t i = string_list_find_insert_index(map, string, &exact_match);
+	if (exact_match) {
 		if (!string[len])
 			return &map->items[i];
 		/*
@@ -267,7 +265,7 @@ static struct string_list_item *lookup_prefix(struct string_list *map,
 	 * overlong key would be inserted, which must come after the
 	 * real location of the key if one exists.
 	 */
-	while (0 <= --i && i < map->nr) {
+	while (i-- && i < map->nr) {
 		int cmp = strncasecmp(map->items[i].string, string, len);
 		if (cmp < 0)
 			/*
diff --git a/meson.build b/meson.build
index cee9424..2b763f7 100644
--- a/meson.build
+++ b/meson.build
@@ -463,6 +463,12 @@
   'reftable/tree.c',
   'reftable/writer.c',
   'remote.c',
+  'repack.c',
+  'repack-cruft.c',
+  'repack-filtered.c',
+  'repack-geometry.c',
+  'repack-midx.c',
+  'repack-promisor.c',
   'replace-object.c',
   'repo-settings.c',
   'repository.c',
@@ -1708,6 +1714,10 @@
 if rust_option.allowed()
   subdir('src')
   libgit_c_args += '-DWITH_RUST'
+
+  if host_machine.system() == 'windows'
+    libgit_dependencies += compiler.find_library('userenv')
+  endif
 else
   libgit_sources += [
     'varint.c',
diff --git a/parse-options.c b/parse-options.c
index 992ec96..5933468 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -133,7 +133,6 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
 {
 	const char *arg;
 	const int unset = flags & OPT_UNSET;
-	int err;
 
 	if (unset && p->opt)
 		return error(_("%s takes no value"), optname(opt, flags));
@@ -209,21 +208,31 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
 	case OPTION_FILENAME:
 	{
 		const char *value;
-
-		FREE_AND_NULL(*(char **)opt->value);
-
-		err = 0;
+		int is_optional;
 
 		if (unset)
 			value = NULL;
 		else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
-			value = (const char *) opt->defval;
-		else
-			err = get_arg(p, opt, flags, &value);
+			value = (char *)opt->defval;
+		else {
+			int err = get_arg(p, opt, flags, &value);
+			if (err)
+				return err;
+		}
+		if (!value)
+			return 0;
 
-		if (!err)
-			*(char **)opt->value = fix_filename(p->prefix, value);
-		return err;
+		is_optional = skip_prefix(value, ":(optional)", &value);
+		if (!value)
+			is_optional = 0;
+		value = fix_filename(p->prefix, value);
+		if (is_optional && is_empty_or_missing_file(value)) {
+			free((char *)value);
+		} else {
+			FREE_AND_NULL(*(char **)opt->value);
+			*(const char **)opt->value = value;
+		}
+		return 0;
 	}
 	case OPTION_CALLBACK:
 	{
diff --git a/range-diff.c b/range-diff.c
index ca449a0..57edff4 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -39,7 +39,7 @@ struct patch_util {
  * as struct object_id (will need to be free()d).
  */
 static int read_patches(const char *range, struct string_list *list,
-			const struct strvec *other_arg,
+			const struct strvec *log_arg,
 			unsigned int include_merges)
 {
 	struct child_process cp = CHILD_PROCESS_INIT;
@@ -69,8 +69,8 @@ static int read_patches(const char *range, struct string_list *list,
 	if (!include_merges)
 		strvec_push(&cp.args, "--no-merges");
 	strvec_push(&cp.args, range);
-	if (other_arg)
-		strvec_pushv(&cp.args, other_arg->v);
+	if (log_arg)
+		strvec_pushv(&cp.args, log_arg->v);
 	cp.out = -1;
 	cp.no_stdin = 1;
 	cp.git_cmd = 1;
@@ -594,9 +594,9 @@ int show_range_diff(const char *range1, const char *range2,
 	if (range_diff_opts->left_only && range_diff_opts->right_only)
 		res = error(_("options '%s' and '%s' cannot be used together"), "--left-only", "--right-only");
 
-	if (!res && read_patches(range1, &branch1, range_diff_opts->other_arg, include_merges))
+	if (!res && read_patches(range1, &branch1, range_diff_opts->log_arg, include_merges))
 		res = error(_("could not parse log for '%s'"), range1);
-	if (!res && read_patches(range2, &branch2, range_diff_opts->other_arg, include_merges))
+	if (!res && read_patches(range2, &branch2, range_diff_opts->log_arg, include_merges))
 		res = error(_("could not parse log for '%s'"), range2);
 
 	if (!res) {
diff --git a/range-diff.h b/range-diff.h
index 9d39818..9b70a80 100644
--- a/range-diff.h
+++ b/range-diff.h
@@ -23,7 +23,7 @@ struct range_diff_options {
 	unsigned include_merges:1;
 	size_t max_memory;
 	const struct diff_options *diffopt; /* may be NULL */
-	const struct strvec *other_arg; /* may be NULL */
+	const struct strvec *log_arg; /* may be NULL */
 };
 
 /*
diff --git a/refs.c b/refs.c
index c83e3d7..9653813 100644
--- a/refs.c
+++ b/refs.c
@@ -3,7 +3,6 @@
  */
 
 #define USE_THE_REPOSITORY_VARIABLE
-#define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "git-compat-util.h"
 #include "advice.h"
@@ -1714,8 +1713,6 @@ const char *find_descendant_ref(const char *dirname,
 				const struct string_list *extras,
 				const struct string_list *skip)
 {
-	int pos;
-
 	if (!extras)
 		return NULL;
 
@@ -1725,7 +1722,7 @@ const char *find_descendant_ref(const char *dirname,
 	 * with dirname (remember, dirname includes the trailing
 	 * slash) and is not in skip, then we have a conflict.
 	 */
-	for (pos = string_list_find_insert_index(extras, dirname, 0);
+	for (size_t pos = string_list_find_insert_index(extras, dirname, NULL);
 	     pos < extras->nr; pos++) {
 		const char *extra_refname = extras->items[pos].string;
 
@@ -2414,7 +2411,7 @@ static int run_transaction_hook(struct ref_transaction *transaction,
 	struct child_process proc = CHILD_PROCESS_INIT;
 	struct strbuf buf = STRBUF_INIT;
 	const char *hook;
-	int ret = 0, i;
+	int ret = 0;
 
 	hook = find_hook(transaction->ref_store->repo, "reference-transaction");
 	if (!hook)
@@ -2431,7 +2428,7 @@ static int run_transaction_hook(struct ref_transaction *transaction,
 
 	sigchain_push(SIGPIPE, SIG_IGN);
 
-	for (i = 0; i < transaction->nr; i++) {
+	for (size_t i = 0; i < transaction->nr; i++) {
 		struct ref_update *update = transaction->updates[i];
 
 		if (update->flags & REF_LOG_ONLY)
@@ -2824,9 +2821,7 @@ void ref_transaction_for_each_queued_update(struct ref_transaction *transaction,
 					    ref_transaction_for_each_queued_update_fn cb,
 					    void *cb_data)
 {
-	int i;
-
-	for (i = 0; i < transaction->nr; i++) {
+	for (size_t i = 0; i < transaction->nr; i++) {
 		struct ref_update *update = transaction->updates[i];
 
 		cb(update->refname,
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 2c48526..054cf42 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -3342,7 +3342,13 @@ static int files_transaction_finish(struct ref_store *ref_store,
 		 * next update. If not, we try and create a regular symref.
 		 */
 		if (update->new_target && refs->prefer_symlink_refs)
-			if (!create_ref_symlink(lock, update->new_target))
+			/*
+			 * By using the `NOT_CONSTANT()` trick, we can avoid
+			 * errors by `clang`'s `-Wunreachable` logic that would
+			 * report that the `continue` statement is not reachable
+			 * when `NO_SYMLINK_HEAD` is `#define`d.
+			 */
+			if (NOT_CONSTANT(!create_ref_symlink(lock, update->new_target)))
 				continue;
 
 		if (update->flags & REF_NEEDS_COMMIT) {
diff --git a/repack-cruft.c b/repack-cruft.c
new file mode 100644
index 0000000..c51df36
--- /dev/null
+++ b/repack-cruft.c
@@ -0,0 +1,99 @@
+#include "git-compat-util.h"
+#include "repack.h"
+#include "packfile.h"
+#include "repository.h"
+#include "run-command.h"
+
+static void combine_small_cruft_packs(FILE *in, off_t combine_cruft_below_size,
+				      struct existing_packs *existing)
+{
+	struct packfile_store *packs = existing->repo->objects->packfiles;
+	struct packed_git *p;
+	struct strbuf buf = STRBUF_INIT;
+	size_t i;
+
+	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
+		if (!(p->is_cruft && p->pack_local))
+			continue;
+
+		strbuf_reset(&buf);
+		strbuf_addstr(&buf, pack_basename(p));
+		strbuf_strip_suffix(&buf, ".pack");
+
+		if (!string_list_has_string(&existing->cruft_packs, buf.buf))
+			continue;
+
+		if (p->pack_size < combine_cruft_below_size) {
+			fprintf(in, "-%s\n", pack_basename(p));
+		} else {
+			existing_packs_retain_cruft(existing, p);
+			fprintf(in, "%s\n", pack_basename(p));
+		}
+	}
+
+	for (i = 0; i < existing->non_kept_packs.nr; i++)
+		fprintf(in, "-%s.pack\n",
+			existing->non_kept_packs.items[i].string);
+
+	strbuf_release(&buf);
+}
+
+int write_cruft_pack(const struct write_pack_opts *opts,
+		     const char *cruft_expiration,
+		     unsigned long combine_cruft_below_size,
+		     struct string_list *names,
+		     struct existing_packs *existing)
+{
+	struct child_process cmd = CHILD_PROCESS_INIT;
+	struct string_list_item *item;
+	FILE *in;
+	int ret;
+	const char *pack_prefix = write_pack_opts_pack_prefix(opts);
+
+	prepare_pack_objects(&cmd, opts->po_args, opts->destination);
+
+	strvec_push(&cmd.args, "--cruft");
+	if (cruft_expiration)
+		strvec_pushf(&cmd.args, "--cruft-expiration=%s",
+			     cruft_expiration);
+
+	strvec_push(&cmd.args, "--non-empty");
+
+	cmd.in = -1;
+
+	ret = start_command(&cmd);
+	if (ret)
+		return ret;
+
+	/*
+	 * names has a confusing double use: it both provides the list
+	 * of just-written new packs, and accepts the name of the cruft
+	 * pack we are writing.
+	 *
+	 * By the time it is read here, it contains only the pack(s)
+	 * that were just written, which is exactly the set of packs we
+	 * want to consider kept.
+	 *
+	 * If `--expire-to` is given, the double-use served by `names`
+	 * ensures that the pack written to `--expire-to` excludes any
+	 * objects contained in the cruft pack.
+	 */
+	in = xfdopen(cmd.in, "w");
+	for_each_string_list_item(item, names)
+		fprintf(in, "%s-%s.pack\n", pack_prefix, item->string);
+	if (combine_cruft_below_size && !cruft_expiration) {
+		combine_small_cruft_packs(in, combine_cruft_below_size,
+					  existing);
+	} else {
+		for_each_string_list_item(item, &existing->non_kept_packs)
+			fprintf(in, "-%s.pack\n", item->string);
+		for_each_string_list_item(item, &existing->cruft_packs)
+			fprintf(in, "-%s.pack\n", item->string);
+	}
+	for_each_string_list_item(item, &existing->kept_packs)
+		fprintf(in, "%s.pack\n", item->string);
+	fclose(in);
+
+	return finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,
+				       names);
+}
diff --git a/repack-filtered.c b/repack-filtered.c
new file mode 100644
index 0000000..edcf766
--- /dev/null
+++ b/repack-filtered.c
@@ -0,0 +1,51 @@
+#include "git-compat-util.h"
+#include "repack.h"
+#include "repository.h"
+#include "run-command.h"
+#include "string-list.h"
+
+int write_filtered_pack(const struct write_pack_opts *opts,
+			struct existing_packs *existing,
+			struct string_list *names)
+{
+	struct child_process cmd = CHILD_PROCESS_INIT;
+	struct string_list_item *item;
+	FILE *in;
+	int ret;
+	const char *caret;
+	const char *pack_prefix = write_pack_opts_pack_prefix(opts);
+
+	prepare_pack_objects(&cmd, opts->po_args, opts->destination);
+
+	strvec_push(&cmd.args, "--stdin-packs");
+
+	for_each_string_list_item(item, &existing->kept_packs)
+		strvec_pushf(&cmd.args, "--keep-pack=%s", item->string);
+
+	cmd.in = -1;
+
+	ret = start_command(&cmd);
+	if (ret)
+		return ret;
+
+	/*
+	 * Here 'names' contains only the pack(s) that were just
+	 * written, which is exactly the packs we want to keep. Also
+	 * 'existing_kept_packs' already contains the packs in
+	 * 'keep_pack_list'.
+	 */
+	in = xfdopen(cmd.in, "w");
+	for_each_string_list_item(item, names)
+		fprintf(in, "^%s-%s.pack\n", pack_prefix, item->string);
+	for_each_string_list_item(item, &existing->non_kept_packs)
+		fprintf(in, "%s.pack\n", item->string);
+	for_each_string_list_item(item, &existing->cruft_packs)
+		fprintf(in, "%s.pack\n", item->string);
+	caret = opts->po_args->pack_kept_objects ? "" : "^";
+	for_each_string_list_item(item, &existing->kept_packs)
+		fprintf(in, "%s%s.pack\n", caret, item->string);
+	fclose(in);
+
+	return finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,
+				       names);
+}
diff --git a/repack-geometry.c b/repack-geometry.c
new file mode 100644
index 0000000..e2f9794
--- /dev/null
+++ b/repack-geometry.c
@@ -0,0 +1,233 @@
+#define DISABLE_SIGN_COMPARE_WARNINGS
+
+#include "git-compat-util.h"
+#include "repack.h"
+#include "repository.h"
+#include "hex.h"
+#include "packfile.h"
+
+static uint32_t pack_geometry_weight(struct packed_git *p)
+{
+	if (open_pack_index(p))
+		die(_("cannot open index for %s"), p->pack_name);
+	return p->num_objects;
+}
+
+static int pack_geometry_cmp(const void *va, const void *vb)
+{
+	uint32_t aw = pack_geometry_weight(*(struct packed_git **)va),
+		 bw = pack_geometry_weight(*(struct packed_git **)vb);
+
+	if (aw < bw)
+		return -1;
+	if (aw > bw)
+		return 1;
+	return 0;
+}
+
+void pack_geometry_init(struct pack_geometry *geometry,
+			struct existing_packs *existing,
+			const struct pack_objects_args *args)
+{
+	struct packfile_store *packs = existing->repo->objects->packfiles;
+	struct packed_git *p;
+	struct strbuf buf = STRBUF_INIT;
+
+	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
+		if (args->local && !p->pack_local)
+			/*
+			 * When asked to only repack local packfiles we skip
+			 * over any packfiles that are borrowed from alternate
+			 * object directories.
+			 */
+			continue;
+
+		if (!args->pack_kept_objects) {
+			/*
+			 * Any pack that has its pack_keep bit set will
+			 * appear in existing->kept_packs below, but
+			 * this saves us from doing a more expensive
+			 * check.
+			 */
+			if (p->pack_keep)
+				continue;
+
+			/*
+			 * The pack may be kept via the --keep-pack
+			 * option; check 'existing->kept_packs' to
+			 * determine whether to ignore it.
+			 */
+			strbuf_reset(&buf);
+			strbuf_addstr(&buf, pack_basename(p));
+			strbuf_strip_suffix(&buf, ".pack");
+
+			if (string_list_has_string(&existing->kept_packs, buf.buf))
+				continue;
+		}
+		if (p->is_cruft)
+			continue;
+
+		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);
+	strbuf_release(&buf);
+}
+
+void pack_geometry_split(struct pack_geometry *geometry)
+{
+	uint32_t i;
+	uint32_t split;
+	off_t total_size = 0;
+
+	if (!geometry->pack_nr) {
+		geometry->split = geometry->pack_nr;
+		return;
+	}
+
+	/*
+	 * 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];
+
+		if (unsigned_mult_overflows(geometry->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))
+			break;
+	}
+
+	split = i;
+
+	if (split) {
+		/*
+		 * Move the split one to the right, since the top element in the
+		 * last-compared pair can't be in the progression. Only do this
+		 * when we split in the middle of the array (otherwise if we got
+		 * to the end, then the split is in the right place).
+		 */
+		split++;
+	}
+
+	/*
+	 * Then, anything to the left of 'split' must be in a new pack. But,
+	 * creating that new pack may cause packs in the heavy half to no longer
+	 * form a geometric progression.
+	 *
+	 * Compute an expected size of the new pack, and then determine how many
+	 * packs in the heavy half need to be joined into it (if any) to restore
+	 * the geometric progression.
+	 */
+	for (i = 0; i < split; i++) {
+		struct packed_git *p = geometry->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];
+
+		if (unsigned_mult_overflows(geometry->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 (unsigned_add_overflows(total_size,
+						   pack_geometry_weight(ours)))
+				die(_("pack %s too large to roll up"),
+				    ours->pack_name);
+
+			split++;
+			total_size += pack_geometry_weight(ours);
+		} else
+			break;
+	}
+
+	geometry->split = split;
+}
+
+struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry)
+{
+	uint32_t i;
+
+	if (!geometry) {
+		/*
+		 * No geometry means either an all-into-one repack (in which
+		 * case there is only one pack left and it is the largest) or an
+		 * incremental one.
+		 *
+		 * If repacking incrementally, then we could check the size of
+		 * all packs to determine which should be preferred, but leave
+		 * this for later.
+		 */
+		return NULL;
+	}
+	if (geometry->split == geometry->pack_nr)
+		return NULL;
+
+	/*
+	 * The preferred pack is the largest pack above the split line. In
+	 * other words, it is the largest pack that does not get rolled up in
+	 * the geometric repack.
+	 */
+	for (i = geometry->pack_nr; i > geometry->split; i--)
+		/*
+		 * A pack that is not local would never be included in a
+		 * multi-pack index. We thus skip over any non-local packs.
+		 */
+		if (geometry->pack[i - 1]->pack_local)
+			return geometry->pack[i - 1];
+
+	return NULL;
+}
+
+void pack_geometry_remove_redundant(struct pack_geometry *geometry,
+				    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];
+		if (string_list_has_string(names, hash_to_hex_algop(p->hash,
+								    algop)))
+			continue;
+
+		strbuf_reset(&buf);
+		strbuf_addstr(&buf, pack_basename(p));
+		strbuf_strip_suffix(&buf, ".pack");
+
+		if ((p->pack_keep) ||
+		    (string_list_has_string(&existing->kept_packs, buf.buf)))
+			continue;
+
+		repack_remove_redundant_pack(existing->repo, packdir, buf.buf);
+	}
+
+	strbuf_release(&buf);
+}
+
+void pack_geometry_release(struct pack_geometry *geometry)
+{
+	if (!geometry)
+		return;
+
+	free(geometry->pack);
+}
diff --git a/repack-midx.c b/repack-midx.c
new file mode 100644
index 0000000..6f6202c
--- /dev/null
+++ b/repack-midx.c
@@ -0,0 +1,372 @@
+#include "git-compat-util.h"
+#include "repack.h"
+#include "hash.h"
+#include "hex.h"
+#include "odb.h"
+#include "oidset.h"
+#include "pack-bitmap.h"
+#include "refs.h"
+#include "run-command.h"
+#include "tempfile.h"
+
+struct midx_snapshot_ref_data {
+	struct repository *repo;
+	struct tempfile *f;
+	struct oidset seen;
+	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)
+{
+	struct midx_snapshot_ref_data *data = _data;
+	struct object_id peeled;
+
+	if (!peel_iterated_oid(data->repo, oid, &peeled))
+		oid = &peeled;
+
+	if (oidset_insert(&data->seen, oid))
+		return 0; /* already seen */
+
+	if (odb_read_object_info(data->repo->objects, oid, NULL) != OBJ_COMMIT)
+		return 0;
+
+	fprintf(data->f->fp, "%s%s\n", data->preferred ? "+" : "",
+		oid_to_hex(oid));
+
+	return 0;
+}
+
+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;
+	data.preferred = 0;
+	oidset_init(&data.seen, 0);
+
+	if (!fdopen_tempfile(f, "w"))
+		 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;
+	}
+
+	refs_for_each_ref(get_main_ref_store(repo),
+			  midx_snapshot_ref_one, &data);
+
+	if (close_tempfile_gently(f)) {
+		int save_errno = errno;
+		delete_tempfile(&f);
+		errno = save_errno;
+		die_errno(_("could not close refs snapshot tempfile"));
+	}
+
+	oidset_clear(&data.seen);
+}
+
+static int midx_has_unknown_packs(struct string_list *include,
+				  struct pack_geometry *geometry,
+				  struct existing_packs *existing)
+{
+	struct string_list_item *item;
+
+	string_list_sort(include);
+
+	for_each_string_list_item(item, &existing->midx_packs) {
+		const char *pack_name = item->string;
+
+		/*
+		 * Determine whether or not each MIDX'd pack from the existing
+		 * MIDX (if any) is represented in the new MIDX. For each pack
+		 * in the MIDX, it must either be:
+		 *
+		 *  - In the "include" list of packs to be included in the new
+		 *    MIDX. Note this function is called before the include
+		 *    list is populated with any cruft pack(s).
+		 *
+		 *  - Below the geometric split line (if using pack geometry),
+		 *    indicating that the pack won't be included in the new
+		 *    MIDX, but its contents were rolled up as part of the
+		 *    geometric repack.
+		 *
+		 *  - In the existing non-kept packs list (if not using pack
+		 *    geometry), and marked as non-deleted.
+		 */
+		if (string_list_has_string(include, pack_name)) {
+			continue;
+		} else if (geometry) {
+			struct strbuf buf = STRBUF_INIT;
+			uint32_t j;
+
+			for (j = 0; j < geometry->split; j++) {
+				strbuf_reset(&buf);
+				strbuf_addstr(&buf, pack_basename(geometry->pack[j]));
+				strbuf_strip_suffix(&buf, ".pack");
+				strbuf_addstr(&buf, ".idx");
+
+				if (!strcmp(pack_name, buf.buf)) {
+					strbuf_release(&buf);
+					break;
+				}
+			}
+
+			strbuf_release(&buf);
+
+			if (j < geometry->split)
+				continue;
+		} else {
+			struct string_list_item *item;
+
+			item = string_list_lookup(&existing->non_kept_packs,
+						  pack_name);
+			if (item && !existing_pack_is_marked_for_deletion(item))
+				continue;
+		}
+
+		/*
+		 * If we got to this point, the MIDX includes some pack that we
+		 * don't know about.
+		 */
+		return 1;
+	}
+
+	return 0;
+}
+
+static void midx_included_packs(struct string_list *include,
+				struct repack_write_midx_opts *opts)
+{
+	struct existing_packs *existing = opts->existing;
+	struct pack_geometry *geometry = opts->geometry;
+	struct string_list *names = opts->names;
+	struct string_list_item *item;
+	struct strbuf buf = STRBUF_INIT;
+
+	for_each_string_list_item(item, &existing->kept_packs) {
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "%s.idx", item->string);
+		string_list_insert(include, buf.buf);
+	}
+
+	for_each_string_list_item(item, names) {
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "pack-%s.idx", item->string);
+		string_list_insert(include, buf.buf);
+	}
+
+	if (geometry->split_factor) {
+		uint32_t i;
+
+		for (i = geometry->split; i < geometry->pack_nr; i++) {
+			struct packed_git *p = geometry->pack[i];
+
+			/*
+			 * The multi-pack index never refers to packfiles part
+			 * of an alternate object database, so we skip these.
+			 * While git-multi-pack-index(1) would silently ignore
+			 * them anyway, this allows us to skip executing the
+			 * command completely when we have only non-local
+			 * packfiles.
+			 */
+			if (!p->pack_local)
+				continue;
+
+			strbuf_reset(&buf);
+			strbuf_addstr(&buf, pack_basename(p));
+			strbuf_strip_suffix(&buf, ".pack");
+			strbuf_addstr(&buf, ".idx");
+
+			string_list_insert(include, buf.buf);
+		}
+	} else {
+		for_each_string_list_item(item, &existing->non_kept_packs) {
+			if (existing_pack_is_marked_for_deletion(item))
+				continue;
+
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "%s.idx", item->string);
+			string_list_insert(include, buf.buf);
+		}
+	}
+
+	if (opts->midx_must_contain_cruft ||
+	    midx_has_unknown_packs(include, geometry, existing)) {
+		/*
+		 * If there are one or more unknown pack(s) present (see
+		 * midx_has_unknown_packs() for what makes a pack
+		 * "unknown") in the MIDX before the repack, keep them
+		 * as they may be required to form a reachability
+		 * closure if the MIDX is bitmapped.
+		 *
+		 * For example, a cruft pack can be required to form a
+		 * reachability closure if the MIDX is bitmapped and one
+		 * or more of the bitmap's selected commits reaches a
+		 * once-cruft object that was later made reachable.
+		 */
+		for_each_string_list_item(item, &existing->cruft_packs) {
+			/*
+			 * When doing a --geometric repack, there is no
+			 * need to check for deleted packs, since we're
+			 * by definition not doing an ALL_INTO_ONE
+			 * repack (hence no packs will be deleted).
+			 * Otherwise we must check for and exclude any
+			 * packs which are enqueued for deletion.
+			 *
+			 * So we could omit the conditional below in the
+			 * --geometric case, but doing so is unnecessary
+			 *  since no packs are marked as pending
+			 *  deletion (since we only call
+			 *  `existing_packs_mark_for_deletion()` when
+			 *  doing an all-into-one repack).
+			 */
+			if (existing_pack_is_marked_for_deletion(item))
+				continue;
+
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "%s.idx", item->string);
+			string_list_insert(include, buf.buf);
+		}
+	} else {
+		/*
+		 * Modern versions of Git (with the appropriate
+		 * configuration setting) will write new copies of
+		 * once-cruft objects when doing a --geometric repack.
+		 *
+		 * If the MIDX has no cruft pack, new packs written
+		 * during a --geometric repack will not rely on the
+		 * cruft pack to form a reachability closure, so we can
+		 * avoid including them in the MIDX in that case.
+		 */
+		;
+	}
+
+	strbuf_release(&buf);
+}
+
+static void remove_redundant_bitmaps(struct string_list *include,
+				     const char *packdir)
+{
+	struct strbuf path = STRBUF_INIT;
+	struct string_list_item *item;
+	size_t packdir_len;
+
+	strbuf_addstr(&path, packdir);
+	strbuf_addch(&path, '/');
+	packdir_len = path.len;
+
+	/*
+	 * Remove any pack bitmaps corresponding to packs which are now
+	 * included in the MIDX.
+	 */
+	for_each_string_list_item(item, include) {
+		strbuf_addstr(&path, item->string);
+		strbuf_strip_suffix(&path, ".idx");
+		strbuf_addstr(&path, ".bitmap");
+
+		if (unlink(path.buf) && errno != ENOENT)
+			warning_errno(_("could not remove stale bitmap: %s"),
+				      path.buf);
+
+		strbuf_setlen(&path, packdir_len);
+	}
+	strbuf_release(&path);
+}
+
+int write_midx_included_packs(struct repack_write_midx_opts *opts)
+{
+	struct child_process cmd = CHILD_PROCESS_INIT;
+	struct string_list include = STRING_LIST_INIT_DUP;
+	struct string_list_item *item;
+	struct packed_git *preferred = pack_geometry_preferred_pack(opts->geometry);
+	FILE *in;
+	int ret = 0;
+
+	midx_included_packs(&include, opts);
+	if (!include.nr)
+		goto done;
+
+	cmd.in = -1;
+	cmd.git_cmd = 1;
+
+	strvec_push(&cmd.args, "multi-pack-index");
+	strvec_pushl(&cmd.args, "write", "--stdin-packs", NULL);
+
+	if (opts->show_progress)
+		strvec_push(&cmd.args, "--progress");
+	else
+		strvec_push(&cmd.args, "--no-progress");
+
+	if (opts->write_bitmaps)
+		strvec_push(&cmd.args, "--bitmap");
+
+	if (preferred)
+		strvec_pushf(&cmd.args, "--preferred-pack=%s",
+			     pack_basename(preferred));
+	else if (opts->names->nr) {
+		/* The largest pack was repacked, meaning that either
+		 * one or two packs exist depending on whether the
+		 * repository has a cruft pack or not.
+		 *
+		 * Select the non-cruft one as preferred to encourage
+		 * pack-reuse among packs containing reachable objects
+		 * over unreachable ones.
+		 *
+		 * (Note we could write multiple packs here if
+		 * `--max-pack-size` was given, but any one of them
+		 * will suffice, so pick the first one.)
+		 */
+		for_each_string_list_item(item, opts->names) {
+			struct generated_pack *pack = item->util;
+			if (generated_pack_has_ext(pack, ".mtimes"))
+				continue;
+
+			strvec_pushf(&cmd.args, "--preferred-pack=pack-%s.pack",
+				     item->string);
+			break;
+		}
+	} else {
+		/*
+		 * No packs were kept, and no packs were written. The
+		 * only thing remaining are .keep packs (unless
+		 * --pack-kept-objects was given).
+		 *
+		 * Set the `--preferred-pack` arbitrarily here.
+		 */
+		;
+	}
+
+	if (opts->refs_snapshot)
+		strvec_pushf(&cmd.args, "--refs-snapshot=%s",
+			     opts->refs_snapshot);
+
+	ret = start_command(&cmd);
+	if (ret)
+		goto done;
+
+	in = xfdopen(cmd.in, "w");
+	for_each_string_list_item(item, &include)
+		fprintf(in, "%s\n", item->string);
+	fclose(in);
+
+	ret = finish_command(&cmd);
+done:
+	if (!ret && opts->write_bitmaps)
+		remove_redundant_bitmaps(&include, opts->packdir);
+
+	string_list_clear(&include, 0);
+
+	return ret;
+}
diff --git a/repack-promisor.c b/repack-promisor.c
new file mode 100644
index 0000000..ee6e066
--- /dev/null
+++ b/repack-promisor.c
@@ -0,0 +1,102 @@
+#include "git-compat-util.h"
+#include "repack.h"
+#include "hex.h"
+#include "pack.h"
+#include "packfile.h"
+#include "path.h"
+#include "repository.h"
+#include "run-command.h"
+
+struct write_oid_context {
+	struct child_process *cmd;
+	const struct git_hash_algo *algop;
+};
+
+/*
+ * Write oid to the given struct child_process's stdin, starting it first if
+ * necessary.
+ */
+static int write_oid(const struct object_id *oid,
+		     struct packed_git *pack UNUSED,
+		     uint32_t pos UNUSED, void *data)
+{
+	struct write_oid_context *ctx = data;
+	struct child_process *cmd = ctx->cmd;
+
+	if (cmd->in == -1) {
+		if (start_command(cmd))
+			die(_("could not start pack-objects to repack promisor objects"));
+	}
+
+	if (write_in_full(cmd->in, oid_to_hex(oid), ctx->algop->hexsz) < 0 ||
+	    write_in_full(cmd->in, "\n", 1) < 0)
+		die(_("failed to feed promisor objects to pack-objects"));
+	return 0;
+}
+
+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;
+	FILE *out;
+	struct strbuf line = STRBUF_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;
+	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");
+	while (strbuf_getline_lf(&line, out) != EOF) {
+		struct string_list_item *item;
+		char *promisor_name;
+
+		if (line.len != repo->hash_algo->hexsz)
+			die(_("repack: Expecting full hex object ID lines only from pack-objects."));
+		item = string_list_append(names, line.buf);
+
+		/*
+		 * pack-objects creates the .pack and .idx files, but not the
+		 * .promisor file. Create the .promisor file, which is empty.
+		 *
+		 * NEEDSWORK: fetch-pack sometimes generates non-empty
+		 * .promisor files containing the ref names and associated
+		 * hashes at the point of generation of the corresponding
+		 * packfile, but this would not preserve their contents. Maybe
+		 * concatenate the contents of all .promisor files instead of
+		 * just creating a new empty file.
+		 */
+		promisor_name = mkpathdup("%s-%s.promisor", packtmp,
+					  line.buf);
+		write_promisor_file(promisor_name, NULL, 0);
+
+		item->util = generated_pack_populate(item->string, packtmp);
+
+		free(promisor_name);
+	}
+
+	fclose(out);
+	if (finish_command(&cmd))
+		die(_("could not finish pack-objects to repack promisor objects"));
+	strbuf_release(&line);
+}
diff --git a/repack.c b/repack.c
new file mode 100644
index 0000000..2ab33c6
--- /dev/null
+++ b/repack.c
@@ -0,0 +1,360 @@
+#include "git-compat-util.h"
+#include "dir.h"
+#include "midx.h"
+#include "odb.h"
+#include "packfile.h"
+#include "path.h"
+#include "repack.h"
+#include "repository.h"
+#include "run-command.h"
+#include "tempfile.h"
+
+void prepare_pack_objects(struct child_process *cmd,
+			  const struct pack_objects_args *args,
+			  const char *out)
+{
+	strvec_push(&cmd->args, "pack-objects");
+	if (args->window)
+		strvec_pushf(&cmd->args, "--window=%s", args->window);
+	if (args->window_memory)
+		strvec_pushf(&cmd->args, "--window-memory=%s", args->window_memory);
+	if (args->depth)
+		strvec_pushf(&cmd->args, "--depth=%s", args->depth);
+	if (args->threads)
+		strvec_pushf(&cmd->args, "--threads=%s", args->threads);
+	if (args->max_pack_size)
+		strvec_pushf(&cmd->args, "--max-pack-size=%lu", args->max_pack_size);
+	if (args->no_reuse_delta)
+		strvec_pushf(&cmd->args, "--no-reuse-delta");
+	if (args->no_reuse_object)
+		strvec_pushf(&cmd->args, "--no-reuse-object");
+	if (args->name_hash_version)
+		strvec_pushf(&cmd->args, "--name-hash-version=%d", args->name_hash_version);
+	if (args->path_walk)
+		strvec_pushf(&cmd->args, "--path-walk");
+	if (args->local)
+		strvec_push(&cmd->args,  "--local");
+	if (args->quiet)
+		strvec_push(&cmd->args,  "--quiet");
+	if (args->delta_base_offset)
+		strvec_push(&cmd->args,  "--delta-base-offset");
+	if (!args->pack_kept_objects)
+		strvec_push(&cmd->args,  "--honor-pack-keep");
+	strvec_push(&cmd->args, out);
+	cmd->git_cmd = 1;
+	cmd->out = -1;
+}
+
+void pack_objects_args_release(struct pack_objects_args *args)
+{
+	free(args->window);
+	free(args->window_memory);
+	free(args->depth);
+	free(args->threads);
+	list_objects_filter_release(&args->filter_options);
+}
+
+void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,
+				  const char *base_name)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct odb_source *source = repo->objects->sources;
+	struct multi_pack_index *m = get_multi_pack_index(source);
+	strbuf_addf(&buf, "%s.pack", base_name);
+	if (m && source->local && midx_contains_pack(m, buf.buf))
+		clear_midx_file(repo);
+	strbuf_insertf(&buf, 0, "%s/", dir_name);
+	unlink_pack_path(buf.buf, 1);
+	strbuf_release(&buf);
+}
+
+const char *write_pack_opts_pack_prefix(const struct write_pack_opts *opts)
+{
+	const char *pack_prefix;
+	if (!skip_prefix(opts->packtmp, opts->packdir, &pack_prefix))
+		die(_("pack prefix %s does not begin with objdir %s"),
+		    opts->packtmp, opts->packdir);
+	if (*pack_prefix == '/')
+		pack_prefix++;
+	return pack_prefix;
+}
+
+bool write_pack_opts_is_local(const struct write_pack_opts *opts)
+{
+	return starts_with(opts->destination, opts->packdir);
+}
+
+int finish_pack_objects_cmd(const struct git_hash_algo *algop,
+			    const struct write_pack_opts *opts,
+			    struct child_process *cmd,
+			    struct string_list *names)
+{
+	FILE *out;
+	bool local = write_pack_opts_is_local(opts);
+	struct strbuf line = STRBUF_INIT;
+
+	out = xfdopen(cmd->out, "r");
+	while (strbuf_getline_lf(&line, out) != EOF) {
+		struct string_list_item *item;
+
+		if (line.len != algop->hexsz)
+			die(_("repack: Expecting full hex object ID lines only "
+			      "from pack-objects."));
+		/*
+		 * Avoid putting packs written outside of the repository in the
+		 * list of names.
+		 */
+		if (local) {
+			item = string_list_append(names, line.buf);
+			item->util = generated_pack_populate(line.buf,
+							     opts->packtmp);
+		}
+	}
+	fclose(out);
+
+	strbuf_release(&line);
+
+	return finish_command(cmd);
+}
+
+#define DELETE_PACK 1
+#define RETAIN_PACK 2
+
+void existing_packs_collect(struct existing_packs *existing,
+			    const struct string_list *extra_keep)
+{
+	struct packfile_store *packs = existing->repo->objects->packfiles;
+	struct packed_git *p;
+	struct strbuf buf = STRBUF_INIT;
+
+	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
+		size_t i;
+		const char *base;
+
+		if (p->multi_pack_index)
+			string_list_append(&existing->midx_packs,
+					    pack_basename(p));
+		if (!p->pack_local)
+			continue;
+
+		base = pack_basename(p);
+
+		for (i = 0; i < extra_keep->nr; i++)
+			if (!fspathcmp(base, extra_keep->items[i].string))
+				break;
+
+		strbuf_reset(&buf);
+		strbuf_addstr(&buf, base);
+		strbuf_strip_suffix(&buf, ".pack");
+
+		if ((extra_keep->nr > 0 && i < extra_keep->nr) || p->pack_keep)
+			string_list_append(&existing->kept_packs, buf.buf);
+		else if (p->is_cruft)
+			string_list_append(&existing->cruft_packs, buf.buf);
+		else
+			string_list_append(&existing->non_kept_packs, buf.buf);
+	}
+
+	string_list_sort(&existing->kept_packs);
+	string_list_sort(&existing->non_kept_packs);
+	string_list_sort(&existing->cruft_packs);
+	string_list_sort(&existing->midx_packs);
+	strbuf_release(&buf);
+}
+
+int existing_packs_has_non_kept(const struct existing_packs *existing)
+{
+	return existing->non_kept_packs.nr || existing->cruft_packs.nr;
+}
+
+static void existing_pack_mark_for_deletion(struct string_list_item *item)
+{
+	item->util = (void*)((uintptr_t)item->util | DELETE_PACK);
+}
+
+static void existing_pack_unmark_for_deletion(struct string_list_item *item)
+{
+	item->util = (void*)((uintptr_t)item->util & ~DELETE_PACK);
+}
+
+int existing_pack_is_marked_for_deletion(struct string_list_item *item)
+{
+	return (uintptr_t)item->util & DELETE_PACK;
+}
+
+static void existing_packs_mark_retained(struct string_list_item *item)
+{
+	item->util = (void*)((uintptr_t)item->util | RETAIN_PACK);
+}
+
+static int existing_pack_is_retained(struct string_list_item *item)
+{
+	return (uintptr_t)item->util & RETAIN_PACK;
+}
+
+static void existing_packs_mark_for_deletion_1(const struct git_hash_algo *algop,
+					       struct string_list *names,
+					       struct string_list *list)
+{
+	struct string_list_item *item;
+	const size_t hexsz = algop->hexsz;
+
+	for_each_string_list_item(item, list) {
+		char *sha1;
+		size_t len = strlen(item->string);
+		if (len < hexsz)
+			continue;
+		sha1 = item->string + len - hexsz;
+
+		if (existing_pack_is_retained(item)) {
+			existing_pack_unmark_for_deletion(item);
+		} else if (!string_list_has_string(names, sha1)) {
+			/*
+			 * Mark this pack for deletion, which ensures
+			 * that this pack won't be included in a MIDX
+			 * (if `--write-midx` was given) and that we
+			 * will actually delete this pack (if `-d` was
+			 * given).
+			 */
+			existing_pack_mark_for_deletion(item);
+		}
+	}
+}
+
+void existing_packs_retain_cruft(struct existing_packs *existing,
+				 struct packed_git *cruft)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct string_list_item *item;
+
+	strbuf_addstr(&buf, pack_basename(cruft));
+	strbuf_strip_suffix(&buf, ".pack");
+
+	item = string_list_lookup(&existing->cruft_packs, buf.buf);
+	if (!item)
+		BUG("could not find cruft pack '%s'", pack_basename(cruft));
+
+	existing_packs_mark_retained(item);
+	strbuf_release(&buf);
+}
+
+void existing_packs_mark_for_deletion(struct existing_packs *existing,
+				      struct string_list *names)
+
+{
+	const struct git_hash_algo *algop = existing->repo->hash_algo;
+	existing_packs_mark_for_deletion_1(algop, names,
+					   &existing->non_kept_packs);
+	existing_packs_mark_for_deletion_1(algop, names,
+					   &existing->cruft_packs);
+}
+
+static void remove_redundant_packs_1(struct repository *repo,
+				     struct string_list *packs,
+				     const char *packdir)
+{
+	struct string_list_item *item;
+	for_each_string_list_item(item, packs) {
+		if (!existing_pack_is_marked_for_deletion(item))
+			continue;
+		repack_remove_redundant_pack(repo, packdir, item->string);
+	}
+}
+
+void existing_packs_remove_redundant(struct existing_packs *existing,
+				     const char *packdir)
+{
+	remove_redundant_packs_1(existing->repo, &existing->non_kept_packs,
+				 packdir);
+	remove_redundant_packs_1(existing->repo, &existing->cruft_packs,
+				 packdir);
+}
+
+void existing_packs_release(struct existing_packs *existing)
+{
+	string_list_clear(&existing->kept_packs, 0);
+	string_list_clear(&existing->non_kept_packs, 0);
+	string_list_clear(&existing->cruft_packs, 0);
+	string_list_clear(&existing->midx_packs, 0);
+}
+
+static struct {
+	const char *name;
+	unsigned optional:1;
+} exts[] = {
+	{".pack"},
+	{".rev", 1},
+	{".mtimes", 1},
+	{".bitmap", 1},
+	{".promisor", 1},
+	{".idx"},
+};
+
+struct generated_pack {
+	struct tempfile *tempfiles[ARRAY_SIZE(exts)];
+};
+
+struct generated_pack *generated_pack_populate(const char *name,
+					       const char *packtmp)
+{
+	struct stat statbuf;
+	struct strbuf path = STRBUF_INIT;
+	struct generated_pack *pack = xcalloc(1, sizeof(*pack));
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(exts); i++) {
+		strbuf_reset(&path);
+		strbuf_addf(&path, "%s-%s%s", packtmp, name, exts[i].name);
+
+		if (stat(path.buf, &statbuf))
+			continue;
+
+		pack->tempfiles[i] = register_tempfile(path.buf);
+	}
+
+	strbuf_release(&path);
+	return pack;
+}
+
+int generated_pack_has_ext(const struct generated_pack *pack, const char *ext)
+{
+	size_t i;
+	for (i = 0; i < ARRAY_SIZE(exts); i++) {
+		if (strcmp(exts[i].name, ext))
+			continue;
+		return !!pack->tempfiles[i];
+	}
+	BUG("unknown pack extension: '%s'", ext);
+}
+
+void generated_pack_install(struct generated_pack *pack, const char *name,
+			    const char *packdir, const char *packtmp)
+{
+	size_t ext;
+	for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
+		char *fname;
+
+		fname = mkpathdup("%s/pack-%s%s", packdir, name,
+				  exts[ext].name);
+
+		if (pack->tempfiles[ext]) {
+			const char *fname_old = get_tempfile_path(pack->tempfiles[ext]);
+			struct stat statbuffer;
+
+			if (!stat(fname_old, &statbuffer)) {
+				statbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
+				chmod(fname_old, statbuffer.st_mode);
+			}
+
+			if (rename_tempfile(&pack->tempfiles[ext], fname))
+				die_errno(_("renaming pack to '%s' failed"),
+					  fname);
+		} else if (!exts[ext].optional)
+			die(_("pack-objects did not write a '%s' file for pack %s-%s"),
+			    exts[ext].name, packtmp, name);
+		else if (unlink(fname) < 0 && errno != ENOENT)
+			die_errno(_("could not unlink: %s"), fname);
+
+		free(fname);
+	}
+}
diff --git a/repack.h b/repack.h
new file mode 100644
index 0000000..3a688a1
--- /dev/null
+++ b/repack.h
@@ -0,0 +1,146 @@
+#ifndef REPACK_H
+#define REPACK_H
+
+#include "list-objects-filter-options.h"
+#include "string-list.h"
+
+struct pack_objects_args {
+	char *window;
+	char *window_memory;
+	char *depth;
+	char *threads;
+	unsigned long max_pack_size;
+	int no_reuse_delta;
+	int no_reuse_object;
+	int quiet;
+	int local;
+	int name_hash_version;
+	int path_walk;
+	int delta_base_offset;
+	int pack_kept_objects;
+	struct list_objects_filter_options filter_options;
+};
+
+#define PACK_OBJECTS_ARGS_INIT { \
+	.delta_base_offset = 1, \
+	.pack_kept_objects = -1, \
+}
+
+struct child_process;
+
+void prepare_pack_objects(struct child_process *cmd,
+			  const struct pack_objects_args *args,
+			  const char *out);
+void pack_objects_args_release(struct pack_objects_args *args);
+
+void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,
+				  const char *base_name);
+
+struct write_pack_opts {
+	struct pack_objects_args *po_args;
+	const char *destination;
+	const char *packdir;
+	const char *packtmp;
+};
+
+const char *write_pack_opts_pack_prefix(const struct write_pack_opts *opts);
+bool write_pack_opts_is_local(const struct write_pack_opts *opts);
+
+int finish_pack_objects_cmd(const struct git_hash_algo *algop,
+			    const struct write_pack_opts *opts,
+			    struct child_process *cmd,
+			    struct string_list *names);
+
+struct repository;
+struct packed_git;
+
+struct existing_packs {
+	struct repository *repo;
+	struct string_list kept_packs;
+	struct string_list non_kept_packs;
+	struct string_list cruft_packs;
+	struct string_list midx_packs;
+};
+
+#define EXISTING_PACKS_INIT { \
+	.kept_packs = STRING_LIST_INIT_DUP, \
+	.non_kept_packs = STRING_LIST_INIT_DUP, \
+	.cruft_packs = STRING_LIST_INIT_DUP, \
+}
+
+/*
+ * Adds all packs hex strings (pack-$HASH) to either packs->non_kept
+ * or packs->kept based on whether each pack has a corresponding
+ * .keep file or not.  Packs without a .keep file are not to be kept
+ * if we are going to pack everything into one file.
+ */
+void existing_packs_collect(struct existing_packs *existing,
+			    const struct string_list *extra_keep);
+int existing_packs_has_non_kept(const struct existing_packs *existing);
+int existing_pack_is_marked_for_deletion(struct string_list_item *item);
+void existing_packs_retain_cruft(struct existing_packs *existing,
+				 struct packed_git *cruft);
+void existing_packs_mark_for_deletion(struct existing_packs *existing,
+				      struct string_list *names);
+void existing_packs_remove_redundant(struct existing_packs *existing,
+				     const char *packdir);
+void existing_packs_release(struct existing_packs *existing);
+
+struct generated_pack;
+
+struct generated_pack *generated_pack_populate(const char *name,
+					       const char *packtmp);
+int generated_pack_has_ext(const struct generated_pack *pack, const char *ext);
+void generated_pack_install(struct generated_pack *pack, const char *name,
+			    const char *packdir, const char *packtmp);
+
+void repack_promisor_objects(struct repository *repo,
+			     const struct pack_objects_args *args,
+			     struct string_list *names, const char *packtmp);
+
+struct pack_geometry {
+	struct packed_git **pack;
+	uint32_t pack_nr, pack_alloc;
+	uint32_t split;
+
+	int split_factor;
+};
+
+void pack_geometry_init(struct pack_geometry *geometry,
+			struct existing_packs *existing,
+			const struct pack_objects_args *args);
+void pack_geometry_split(struct pack_geometry *geometry);
+struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry);
+void pack_geometry_remove_redundant(struct pack_geometry *geometry,
+				    struct string_list *names,
+				    struct existing_packs *existing,
+				    const char *packdir);
+void pack_geometry_release(struct pack_geometry *geometry);
+
+struct tempfile;
+
+struct repack_write_midx_opts {
+	struct existing_packs *existing;
+	struct pack_geometry *geometry;
+	struct string_list *names;
+	const char *refs_snapshot;
+	const char *packdir;
+	int show_progress;
+	int write_bitmaps;
+	int midx_must_contain_cruft;
+};
+
+void midx_snapshot_refs(struct repository *repo, struct tempfile *f);
+int write_midx_included_packs(struct repack_write_midx_opts *opts);
+
+int write_filtered_pack(const struct write_pack_opts *opts,
+			struct existing_packs *existing,
+			struct string_list *names);
+
+int write_cruft_pack(const struct write_pack_opts *opts,
+		     const char *cruft_expiration,
+		     unsigned long combine_cruft_below_size,
+		     struct string_list *names,
+		     struct existing_packs *existing);
+
+#endif /* REPACK_H */
diff --git a/revision.h b/revision.h
index a28e349..b36acfc 100644
--- a/revision.h
+++ b/revision.h
@@ -334,6 +334,7 @@ struct rev_info {
 	/* range-diff */
 	const char *rdiff1;
 	const char *rdiff2;
+	struct strvec rdiff_log_arg;
 	int creation_factor;
 	const char *rdiff_title;
 
@@ -410,6 +411,7 @@ struct rev_info {
 	.expand_tabs_in_log = -1, \
 	.commit_format = CMIT_FMT_DEFAULT, \
 	.expand_tabs_in_log_default = 8, \
+	.rdiff_log_arg = STRVEC_INIT, \
 }
 
 /**
diff --git a/scalar.c b/scalar.c
index 4a373c1..f754311 100644
--- a/scalar.c
+++ b/scalar.c
@@ -166,6 +166,7 @@ static int set_recommended_config(int reconfigure)
 #endif
 		/* Optional */
 		{ "status.aheadBehind", "false" },
+		{ "commitGraph.changedPaths", "true" },
 		{ "commitGraph.generationVersion", "1" },
 		{ "core.autoCRLF", "false" },
 		{ "core.safeCRLF", "false" },
diff --git a/sparse-index.c b/sparse-index.c
index 5634aba..76f90da 100644
--- a/sparse-index.c
+++ b/sparse-index.c
@@ -32,7 +32,9 @@ int give_advice_on_expansion = 1;
 	"Your working directory likely has contents that are outside of\n"     \
 	"your sparse-checkout patterns. Use 'git sparse-checkout list' to\n"   \
 	"see your sparse-checkout definition and compare it to your working\n" \
-	"directory contents. Running 'git clean' may assist in this cleanup."
+	"directory contents. Cleaning up any merge conflicts or staged\n"      \
+	"changes before running 'git sparse-checkout clean' or 'git\n"         \
+	"sparse-checkout reapply' may assist in this cleanup."
 
 struct modify_index_context {
 	struct index_state *write;
diff --git a/src/cargo-meson.sh b/src/cargo-meson.sh
index 9940098..3998db0 100755
--- a/src/cargo-meson.sh
+++ b/src/cargo-meson.sh
@@ -26,7 +26,14 @@
 	exit $RET
 fi
 
-if ! cmp "$BUILD_DIR/$BUILD_TYPE/libgitcore.a" "$BUILD_DIR/libgitcore.a" >/dev/null 2>&1
+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/libgitcore.a" "$BUILD_DIR/libgitcore.a"
+	cp "$BUILD_DIR/$BUILD_TYPE/$LIBNAME" "$BUILD_DIR/libgitcore.a"
 fi
diff --git a/src/varint.rs b/src/varint.rs
index 6e610bd..06492df 100644
--- a/src/varint.rs
+++ b/src/varint.rs
@@ -1,3 +1,10 @@
+/// Decode the variable-length integer stored in `bufp` and return the decoded value.
+///
+/// Returns 0 in case the decoded integer would overflow u64::MAX.
+///
+/// # Safety
+///
+/// The buffer must be NUL-terminated to ensure safety.
 #[no_mangle]
 pub unsafe extern "C" fn decode_varint(bufp: *mut *const u8) -> u64 {
     let mut buf = *bufp;
@@ -22,6 +29,14 @@
     val
 }
 
+/// Encode `value` into `buf` as a variable-length integer unless `buf` is null.
+///
+/// Returns the number of bytes written, or, if `buf` is null, the number of bytes that would be
+/// written to encode the integer.
+///
+/// # Safety
+///
+/// `buf` must either be null or point to at least 16 bytes of memory.
 #[no_mangle]
 pub unsafe extern "C" fn encode_varint(value: u64, buf: *mut u8) -> u8 {
     let mut varint: [u8; 16] = [0; 16];
diff --git a/string-list.c b/string-list.c
index 343cf1c..08dc009 100644
--- a/string-list.c
+++ b/string-list.c
@@ -16,7 +16,7 @@ void string_list_init_dup(struct string_list *list)
 /* if there is no exact match, point to the index where the entry could be
  * inserted */
 static size_t get_entry_index(const struct string_list *list, const char *string,
-			      int *exact_match)
+			      bool *exact_match)
 {
 	size_t left = 0, right = list->nr;
 	compare_strings_fn cmp = list->cmp ? list->cmp : strcmp;
@@ -29,18 +29,20 @@ static size_t get_entry_index(const struct string_list *list, const char *string
 		else if (compare > 0)
 			left = middle + 1;
 		else {
-			*exact_match = 1;
+			if (exact_match)
+				*exact_match = true;
 			return middle;
 		}
 	}
 
-	*exact_match = 0;
+	if (exact_match)
+		*exact_match = false;
 	return right;
 }
 
 static size_t add_entry(struct string_list *list, const char *string)
 {
-	int exact_match = 0;
+	bool exact_match;
 	size_t index = get_entry_index(list, string, &exact_match);
 
 	if (exact_match)
@@ -68,7 +70,7 @@ struct string_list_item *string_list_insert(struct string_list *list, const char
 void string_list_remove(struct string_list *list, const char *string,
 			int free_util)
 {
-	int exact_match;
+	bool exact_match;
 	int i = get_entry_index(list, string, &exact_match);
 
 	if (exact_match) {
@@ -82,26 +84,23 @@ void string_list_remove(struct string_list *list, const char *string,
 	}
 }
 
-int string_list_has_string(const struct string_list *list, const char *string)
+bool string_list_has_string(const struct string_list *list, const char *string)
 {
-	int exact_match;
+	bool exact_match;
 	get_entry_index(list, string, &exact_match);
 	return exact_match;
 }
 
-int string_list_find_insert_index(const struct string_list *list, const char *string,
-				  int negative_existing_index)
+size_t string_list_find_insert_index(const struct string_list *list, const char *string,
+				     bool *exact_match)
 {
-	int exact_match;
-	int index = get_entry_index(list, string, &exact_match);
-	if (exact_match)
-		index = -1 - (negative_existing_index ? index : 0);
-	return index;
+	return get_entry_index(list, string, exact_match);
 }
 
 struct string_list_item *string_list_lookup(struct string_list *list, const char *string)
 {
-	int exact_match, i = get_entry_index(list, string, &exact_match);
+	bool exact_match;
+	size_t i = get_entry_index(list, string, &exact_match);
 	if (!exact_match)
 		return NULL;
 	return list->items + i;
diff --git a/string-list.h b/string-list.h
index 2b438c7..fa6ba07 100644
--- a/string-list.h
+++ b/string-list.h
@@ -172,9 +172,15 @@ void string_list_remove_empty_items(struct string_list *list, int free_util);
 /* Use these functions only on sorted lists: */
 
 /** Determine if the string_list has a given string or not. */
-int string_list_has_string(const struct string_list *list, const char *string);
-int string_list_find_insert_index(const struct string_list *list, const char *string,
-				  int negative_existing_index);
+bool string_list_has_string(const struct string_list *list, const char *string);
+
+/**
+ * Find the index at which a new element should be inserted into the
+ * string_list to maintain sorted order. If exact_match is not NULL,
+ * it will be set to true if the string already exists in the list.
+ */
+size_t string_list_find_insert_index(const struct string_list *list, const char *string,
+				     bool *exact_match);
 
 /**
  * Insert a new element to the string_list. The returned pointer can
diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh
index 937b876..b99ae39 100644
--- a/t/lib-gpg.sh
+++ b/t/lib-gpg.sh
@@ -9,6 +9,16 @@
 GNUPGHOME="$(pwd)/gpghome"
 export GNUPGHOME
 
+# All the "test_lazy_prereq GPG*" below should use
+# `prepare_gnupghome()` either directly or through a call to
+# `test_have_prereq GPG*`. That's because `gpg` and `gpgsm`
+# only create the directory specified using "$GNUPGHOME" or
+# `--homedir` if it's the default (usually "~/.gnupg").
+prepare_gnupghome() {
+	mkdir -p "$GNUPGHOME" &&
+	chmod 0700 "$GNUPGHOME"
+}
+
 test_lazy_prereq GPG '
 	gpg_version=$(gpg --version 2>&1)
 	test $? != 127 || exit 1
@@ -38,8 +48,7 @@
 		# To export ownertrust:
 		#	gpg --homedir /tmp/gpghome --export-ownertrust \
 		#		> lib-gpg/ownertrust
-		mkdir "$GNUPGHOME" &&
-		chmod 0700 "$GNUPGHOME" &&
+		prepare_gnupghome &&
 		(gpgconf --kill all || : ) &&
 		gpg --homedir "${GNUPGHOME}" --import \
 			"$TEST_DIRECTORY"/lib-gpg/keyring.gpg &&
@@ -63,6 +72,14 @@
 		;;
 	*)
 		(gpgconf --kill all || : ) &&
+
+		# NEEDSWORK: prepare_gnupghome() should definitely be
+		# called here, but it looks like it exposes a
+		# pre-existing, hidden bug by allowing some tests in
+		# t1016-compatObjectFormat.sh to run instead of being
+		# skipped. See:
+		# https://lore.kernel.org/git/ZoV8b2RvYxLOotSJ@teonanacatl.net/
+
 		gpg --homedir "${GNUPGHOME}" --import \
 			"$TEST_DIRECTORY"/lib-gpg/keyring.gpg &&
 		gpg --homedir "${GNUPGHOME}" --import-ownertrust \
@@ -132,8 +149,7 @@
 	test $? = 0 || exit 1;
 
 	# Setup some keys and an allowed signers file
-	mkdir -p "${GNUPGHOME}" &&
-	chmod 0700 "${GNUPGHOME}" &&
+	prepare_gnupghome &&
 	(setfacl -k "${GNUPGHOME}" 2>/dev/null || true) &&
 	ssh-keygen -t ed25519 -N "" -C "git ed25519 key" -f "${GPGSSH_KEY_PRIMARY}" >/dev/null &&
 	ssh-keygen -t rsa -b 2048 -N "" -C "git rsa2048 key" -f "${GPGSSH_KEY_SECONDARY}" >/dev/null &&
diff --git a/t/meson.build b/t/meson.build
index 401b24e..c9ddd89 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -1037,6 +1037,7 @@
   't9303-fast-import-compression.sh',
   't9304-fast-import-marks.sh',
   't9305-fast-import-signatures.sh',
+  't9306-fast-import-signed-tags.sh',
   't9350-fast-export.sh',
   't9351-fast-export-anonymize.sh',
   't9400-git-cvsserver-server.sh',
diff --git a/t/t1010-mktree.sh b/t/t1010-mktree.sh
index e9973f7..312fe67 100755
--- a/t/t1010-mktree.sh
+++ b/t/t1010-mktree.sh
@@ -11,10 +11,13 @@
 		git add "$d" || return 1
 	done &&
 	echo zero >one &&
-	git update-index --add --info-only one &&
-	git write-tree --missing-ok >tree.missing &&
-	git ls-tree $(cat tree.missing) >top.missing &&
-	git ls-tree -r $(cat tree.missing) >all.missing &&
+	if test_have_prereq BROKEN_OBJECTS
+	then
+		git update-index --add --info-only one &&
+		git write-tree --missing-ok >tree.missing &&
+		git ls-tree $(cat tree.missing) >top.missing &&
+		git ls-tree -r $(cat tree.missing) >all.missing
+	fi &&
 	echo one >one &&
 	git add one &&
 	git write-tree >tree &&
@@ -53,7 +56,7 @@
 	test_cmp tree.withsub actual
 '
 
-test_expect_success 'allow missing object with --missing' '
+test_expect_success BROKEN_OBJECTS 'allow missing object with --missing' '
 	git mktree --missing <top.missing >actual &&
 	test_cmp tree.missing actual
 '
diff --git a/t/t1016-compatObjectFormat.sh b/t/t1016-compatObjectFormat.sh
index e88362f..a9af8b2 100755
--- a/t/t1016-compatObjectFormat.sh
+++ b/t/t1016-compatObjectFormat.sh
@@ -114,7 +114,7 @@
 		git config core.repositoryformatversion 1 &&
 		git config extensions.objectformat $hash &&
 		git config extensions.compatobjectformat $(compat_hash $hash) &&
-		test_config gpg.program $TEST_DIRECTORY/t1016/gpg &&
+		git config gpg.program $TEST_DIRECTORY/t1016/gpg &&
 		echo "Hello World!" >hello &&
 		eval hello_${hash}_oid=$(git hash-object hello) &&
 		git update-index --add hello &&
diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh
index ab3a105..b2da4fe 100755
--- a/t/t1091-sparse-checkout-builtin.sh
+++ b/t/t1091-sparse-checkout-builtin.sh
@@ -1050,5 +1050,180 @@
 	test_cmp expect actual
 '
 
+test_expect_success 'clean' '
+	git -C repo sparse-checkout set --cone deep/deeper1 &&
+	git -C repo sparse-checkout reapply &&
+	mkdir -p repo/deep/deeper2 repo/folder1/extra/inside &&
+
+	# Add untracked files
+	touch repo/deep/deeper2/file &&
+	touch repo/folder1/extra/inside/file &&
+
+	test_must_fail git -C repo sparse-checkout clean 2>err &&
+	grep "refusing to clean" err &&
+
+	git -C repo config clean.requireForce true &&
+	test_must_fail git -C repo sparse-checkout clean 2>err &&
+	grep "refusing to clean" err &&
+
+	cat >expect <<-\EOF &&
+	Would remove deep/deeper2/
+	Would remove folder1/
+	EOF
+
+	git -C repo sparse-checkout clean --dry-run >out &&
+	test_cmp expect out &&
+	test_path_exists repo/deep/deeper2 &&
+	test_path_exists repo/folder1/extra/inside/file &&
+
+	cat >expect <<-\EOF &&
+	Would remove deep/deeper2/file
+	Would remove folder1/extra/inside/file
+	EOF
+
+	git -C repo sparse-checkout clean --dry-run --verbose >out &&
+	test_cmp expect out &&
+
+	cat >expect <<-\EOF &&
+	Removing deep/deeper2/
+	Removing folder1/
+	EOF
+
+	git -C repo sparse-checkout clean -f >out &&
+	test_cmp expect out &&
+
+	test_path_is_missing repo/deep/deeper2 &&
+	test_path_is_missing repo/folder1
+'
+
+test_expect_success 'clean with sparse file states' '
+	test_when_finished git reset --hard &&
+	git -C repo sparse-checkout set --cone deep/deeper1 &&
+	mkdir repo/folder2 &&
+
+	# The previous test case checked the -f option, so
+	# test the config option in this one.
+	git -C repo config clean.requireForce false &&
+
+	# create an untracked file and a modified file
+	touch repo/folder2/file &&
+	echo dirty >repo/folder2/a &&
+
+	# First clean/reapply pass will do nothing.
+	git -C repo sparse-checkout clean >out &&
+	test_must_be_empty out &&
+	test_path_exists repo/folder2/a &&
+	test_path_exists repo/folder2/file &&
+
+	git -C repo sparse-checkout reapply 2>err &&
+	test_grep folder2 err &&
+	test_path_exists repo/folder2/a &&
+	test_path_exists repo/folder2/file &&
+
+	# Now, stage the change to the tracked file.
+	git -C repo add --sparse folder2/a &&
+
+	# Clean will continue not doing anything.
+	git -C repo sparse-checkout clean >out &&
+	test_line_count = 0 out &&
+	test_path_exists repo/folder2/a &&
+	test_path_exists repo/folder2/file &&
+
+	# But we can reapply to remove the staged change.
+	git -C repo sparse-checkout reapply 2>err &&
+	test_grep folder2 err &&
+	test_path_is_missing repo/folder2/a &&
+	test_path_exists repo/folder2/file &&
+
+	# We can clean now.
+	cat >expect <<-\EOF &&
+	Removing folder2/
+	EOF
+	git -C repo sparse-checkout clean >out &&
+	test_cmp expect out &&
+	test_path_is_missing repo/folder2 &&
+
+	# At the moment, the file is staged.
+	cat >expect <<-\EOF &&
+	M  folder2/a
+	EOF
+
+	git -C repo status -s >out &&
+	test_cmp expect out &&
+
+	# Reapply persists the modified state.
+	git -C repo sparse-checkout reapply &&
+	cat >expect <<-\EOF &&
+	M  folder2/a
+	EOF
+	git -C repo status -s >out &&
+	test_cmp expect out &&
+
+	# Committing the change leads to resolved status.
+	git -C repo commit -m "modified" &&
+	git -C repo status -s >out &&
+	test_must_be_empty out &&
+
+	# Repeat, but this time commit before reapplying.
+	mkdir repo/folder2/ &&
+	echo dirtier >repo/folder2/a &&
+	git -C repo add --sparse folder2/a &&
+	git -C repo sparse-checkout clean >out &&
+	test_must_be_empty out &&
+	test_path_exists repo/folder2/a &&
+
+	# Committing without reapplying makes it look like a deletion
+	# due to no skip-worktree bit.
+	git -C repo commit -m "dirtier" &&
+	git -C repo status -s >out &&
+	test_must_be_empty out &&
+
+	git -C repo sparse-checkout reapply &&
+	git -C repo status -s >out &&
+	test_must_be_empty out
+'
+
+test_expect_success 'sparse-checkout operations with merge conflicts' '
+	git clone repo merge &&
+
+	(
+		cd merge &&
+		mkdir -p folder1/even/more/dirs &&
+		echo base >folder1/even/more/dirs/file &&
+		git add folder1 &&
+		git commit -m "base" &&
+
+		git checkout -b right&&
+		echo right >folder1/even/more/dirs/file &&
+		git commit -a -m "right" &&
+
+		git checkout -b left HEAD~1 &&
+		echo left >folder1/even/more/dirs/file &&
+		git commit -a -m "left" &&
+
+		git checkout -b merge &&
+		git sparse-checkout set deep/deeper1 &&
+
+		test_must_fail git merge -m "will-conflict" right &&
+
+		test_must_fail git sparse-checkout clean -f 2>err &&
+		grep "failed to convert index to a sparse index" err &&
+
+		echo merged >folder1/even/more/dirs/file &&
+		git add --sparse folder1 &&
+		git merge --continue &&
+
+		test_path_exists folder1/even/more/dirs/file &&
+
+		# clean does not remove the file, because the
+		# SKIP_WORKTREE bit was not cleared by the merge command.
+		git sparse-checkout clean -f >out &&
+		test_line_count = 0 out &&
+		test_path_exists folder1/even/more/dirs/file &&
+
+		git sparse-checkout reapply &&
+		test_path_is_missing folder1
+	)
+'
 
 test_done
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index 5ae86c4..c4b651c 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -454,6 +454,60 @@
 	test_grep "error in tag $tag.*unterminated header: NUL at offset" out
 '
 
+test_expect_success 'tag accepts gpgsig header even if not validly signed' '
+	test_oid_cache <<-\EOF &&
+	header sha1:gpgsig-sha256
+	header sha256:gpgsig
+	EOF
+	header=$(test_oid header) &&
+	sha=$(git rev-parse HEAD) &&
+	cat >good-tag <<-EOF &&
+	object $sha
+	type commit
+	tag good
+	tagger T A Gger <tagger@example.com> 1234567890 -0000
+	$header -----BEGIN PGP SIGNATURE-----
+	 Not a valid signature
+	 -----END PGP SIGNATURE-----
+
+	This is a good tag.
+	EOF
+
+	tag=$(git hash-object --literally -t tag -w --stdin <good-tag) &&
+	test_when_finished "remove_object $tag" &&
+	git update-ref refs/tags/good $tag &&
+	test_when_finished "git update-ref -d refs/tags/good" &&
+	git -c fsck.extraHeaderEntry=error fsck --tags
+'
+
+test_expect_success 'tag rejects invalid headers' '
+	test_oid_cache <<-\EOF &&
+	header sha1:gpgsig-sha256
+	header sha256:gpgsig
+	EOF
+	header=$(test_oid header) &&
+	sha=$(git rev-parse HEAD) &&
+	cat >bad-tag <<-EOF &&
+	object $sha
+	type commit
+	tag good
+	tagger T A Gger <tagger@example.com> 1234567890 -0000
+	$header -----BEGIN PGP SIGNATURE-----
+	 Not a valid signature
+	 -----END PGP SIGNATURE-----
+	junk
+
+	This is a bad tag with junk at the end of the headers.
+	EOF
+
+	tag=$(git hash-object --literally -t tag -w --stdin <bad-tag) &&
+	test_when_finished "remove_object $tag" &&
+	git update-ref refs/tags/bad $tag &&
+	test_when_finished "git update-ref -d refs/tags/bad" &&
+	test_must_fail git -c fsck.extraHeaderEntry=error fsck --tags 2>out &&
+	test_grep "error in tag $tag.*invalid format - extra header" out
+'
+
 test_expect_success 'cleaned up' '
 	git fsck >actual 2>&1 &&
 	test_must_be_empty actual
diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh
index 58a4583..7739ab6 100755
--- a/t/t1500-rev-parse.sh
+++ b/t/t1500-rev-parse.sh
@@ -207,6 +207,40 @@
 	grep "unknown mode for --show-object-format: squeamish-ossifrage" err
 '
 
+
+test_expect_success 'rev-parse --show-object-format in repo with compat mode' '
+	mkdir repo &&
+	(
+		sane_unset GIT_DEFAULT_HASH &&
+		cd repo &&
+		git init --object-format=sha256 &&
+		git config extensions.compatobjectformat sha1 &&
+		echo sha256 >expect &&
+		git rev-parse --show-object-format >actual &&
+		test_cmp expect actual &&
+		git rev-parse --show-object-format=storage >actual &&
+		test_cmp expect actual &&
+		git rev-parse --show-object-format=input >actual &&
+		test_cmp expect actual &&
+		git rev-parse --show-object-format=output >actual &&
+		test_cmp expect actual &&
+		echo sha1 >expect &&
+		git rev-parse --show-object-format=compat >actual &&
+		test_cmp expect actual &&
+		test_must_fail git rev-parse --show-object-format=squeamish-ossifrage 2>err &&
+		grep "unknown mode for --show-object-format: squeamish-ossifrage" err
+	) &&
+	mkdir repo2 &&
+	(
+		sane_unset GIT_DEFAULT_HASH &&
+		cd repo2 &&
+		git init --object-format=sha256 &&
+		echo >expect &&
+		git rev-parse --show-object-format=compat >actual &&
+		test_cmp expect actual
+	)
+'
+
 test_expect_success 'rev-parse --show-ref-format' '
 	test_detect_ref_format >expect &&
 	git rev-parse --show-ref-format >actual &&
diff --git a/t/t2401-worktree-prune.sh b/t/t2401-worktree-prune.sh
index fe671d4..f8f28c7 100755
--- a/t/t2401-worktree-prune.sh
+++ b/t/t2401-worktree-prune.sh
@@ -24,8 +24,8 @@
 Removing worktrees/abc: not a valid directory
 EOF
 	test_cmp expect actual &&
-	! test -f .git/worktrees/abc &&
-	! test -d .git/worktrees
+	test_path_is_missing .git/worktrees/abc &&
+	test_path_is_missing .git/worktrees
 '
 
 test_expect_success 'prune directories without gitdir' '
@@ -36,8 +36,8 @@
 EOF
 	git worktree prune --verbose 2>actual &&
 	test_cmp expect actual &&
-	! test -d .git/worktrees/def &&
-	! test -d .git/worktrees
+	test_path_is_missing .git/worktrees/def &&
+	test_path_is_missing .git/worktrees
 '
 
 test_expect_success SANITY 'prune directories with unreadable gitdir' '
@@ -47,8 +47,8 @@
 	chmod u-r .git/worktrees/def/gitdir &&
 	git worktree prune --verbose 2>actual &&
 	test_grep "Removing worktrees/def: unable to read gitdir file" actual &&
-	! test -d .git/worktrees/def &&
-	! test -d .git/worktrees
+	test_path_is_missing .git/worktrees/def &&
+	test_path_is_missing .git/worktrees
 '
 
 test_expect_success 'prune directories with invalid gitdir' '
@@ -57,8 +57,8 @@
 	: >.git/worktrees/def/gitdir &&
 	git worktree prune --verbose 2>actual &&
 	test_grep "Removing worktrees/def: invalid gitdir file" actual &&
-	! test -d .git/worktrees/def &&
-	! test -d .git/worktrees
+	test_path_is_missing .git/worktrees/def &&
+	test_path_is_missing .git/worktrees
 '
 
 test_expect_success 'prune directories with gitdir pointing to nowhere' '
@@ -67,8 +67,8 @@
 	echo "$(pwd)"/nowhere >.git/worktrees/def/gitdir &&
 	git worktree prune --verbose 2>actual &&
 	test_grep "Removing worktrees/def: gitdir file points to non-existent location" actual &&
-	! test -d .git/worktrees/def &&
-	! test -d .git/worktrees
+	test_path_is_missing .git/worktrees/def &&
+	test_path_is_missing .git/worktrees
 '
 
 test_expect_success 'not prune locked checkout' '
@@ -76,23 +76,23 @@
 	mkdir -p .git/worktrees/ghi &&
 	: >.git/worktrees/ghi/locked &&
 	git worktree prune &&
-	test -d .git/worktrees/ghi
+	test_path_is_dir .git/worktrees/ghi
 '
 
 test_expect_success 'not prune recent checkouts' '
 	test_when_finished rm -r .git/worktrees &&
 	git worktree add jlm HEAD &&
-	test -d .git/worktrees/jlm &&
+	test_path_is_dir .git/worktrees/jlm &&
 	rm -rf jlm &&
 	git worktree prune --verbose --expire=2.days.ago &&
-	test -d .git/worktrees/jlm
+	test_path_is_dir .git/worktrees/jlm
 '
 
 test_expect_success 'not prune proper checkouts' '
 	test_when_finished rm -r .git/worktrees &&
 	git worktree add --detach "$PWD/nop" main &&
 	git worktree prune &&
-	test -d .git/worktrees/nop
+	test_path_is_dir .git/worktrees/nop
 '
 
 test_expect_success 'prune duplicate (linked/linked)' '
@@ -103,8 +103,8 @@
 	mv .git/worktrees/w2/gitdir.new .git/worktrees/w2/gitdir &&
 	git worktree prune --verbose 2>actual &&
 	test_grep "duplicate entry" actual &&
-	test -d .git/worktrees/w1 &&
-	! test -d .git/worktrees/w2
+	test_path_is_dir .git/worktrees/w1 &&
+	test_path_is_missing .git/worktrees/w2
 '
 
 test_expect_success 'prune duplicate (main/linked)' '
@@ -116,7 +116,7 @@
 	mv repo wt &&
 	git -C wt worktree prune --verbose 2>actual &&
 	test_grep "duplicate entry" actual &&
-	! test -d .git/worktrees/wt
+	test_path_is_missing .git/worktrees/wt
 '
 
 test_expect_success 'not prune proper worktrees inside linked worktree with relative paths' '
diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
index e091df6..1e812df 100755
--- a/t/t3206-range-diff.sh
+++ b/t/t3206-range-diff.sh
@@ -707,7 +707,7 @@
 	! grep "note" 0000-*
 '
 
-test_expect_success 'format-patch --notes=custom --range-diff only compares custom notes' '
+test_expect_success 'format-patch --notes=custom --range-diff --cover-letter only compares custom notes' '
 	test_when_finished "git notes remove topic unmodified || :" &&
 	git notes add -m "topic note" topic &&
 	git notes add -m "unmodified note" unmodified &&
@@ -721,6 +721,20 @@
 	! grep "## Notes ##" 0000-*
 '
 
+# --range-diff on a single commit requires --no-cover-letter
+test_expect_success 'format-patch --notes=custom --range-diff on single commit only compares custom notes' '
+	test_when_finished "git notes remove HEAD unmodified || :" &&
+	git notes add -m "topic note" HEAD &&
+	test_when_finished "git notes --ref=custom remove HEAD unmodified || :" &&
+	git notes add -m "unmodified note" unmodified &&
+	git notes --ref=custom add -m "topic note (custom)" HEAD &&
+	git notes --ref=custom add -m "unmodified note (custom)" unmodified &&
+	git format-patch --notes=custom --range-diff=$prev \
+		-1 --stdout >actual &&
+	test_grep "## Notes (custom) ##" actual &&
+	test_grep ! "## Notes ##" actual
+'
+
 test_expect_success 'format-patch --range-diff with --no-notes' '
 	test_when_finished "git notes remove topic unmodified || :" &&
 	git notes add -m "topic note" topic &&
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index d9fe289..851ca6d 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -48,8 +48,8 @@
 	git add -N command &&
 	git diff command >expect &&
 	cat >>expect <<-EOF &&
-	(1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)
-	(1/1) Stage addition [y,n,q,a,d,e,p,?]?$SP
+	(1/1) Stage addition [y,n,q,a,d,e,p,P,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)
+	(1/1) Stage addition [y,n,q,a,d,e,p,P,?]?$SP
 	EOF
 	git add -p -- command <command >actual 2>&1 &&
 	test_cmp expect actual
@@ -332,9 +332,9 @@
 	git -c core.filemode=true add -p >actual &&
 	sed -n "s/^\(([0-9/]*) Stage .*?\).*/\1/p" actual >actual.filtered &&
 	cat >expect <<-\EOF &&
-	(1/1) Stage deletion [y,n,q,a,d,p,?]?
-	(1/2) Stage mode change [y,n,q,a,d,j,J,g,/,p,?]?
-	(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]?
+	(1/1) Stage deletion [y,n,q,a,d,p,P,?]?
+	(1/2) Stage mode change [y,n,q,a,d,k,K,j,J,g,/,p,P,?]?
+	(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]?
 	EOF
 	test_cmp expect actual.filtered
 '
@@ -521,13 +521,13 @@
 test_expect_success 'goto hunk 1 with "g 1"' '
 	test_when_finished "git reset" &&
 	tr _ " " >expect <<-EOF &&
-	(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? + 1:  -1,2 +1,3          +15
+	(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]? + 1:  -1,2 +1,3          +15
 	_ 2:  -2,4 +3,8          +21
 	go to which hunk? @@ -1,2 +1,3 @@
 	_10
 	+15
 	_20
-	(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_
+	(1/2) Stage this hunk [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,j,J,g,/,e,p,?]?_
+	(1/2) Stage this hunk [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 &&
@@ -550,11 +550,11 @@
 test_expect_success 'navigate to hunk via regex /pattern' '
 	test_when_finished "git reset" &&
 	tr _ " " >expect <<-EOF &&
-	(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? @@ -1,2 +1,3 @@
+	(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]? @@ -1,2 +1,3 @@
 	_10
 	+15
 	_20
-	(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_
+	(1/2) Stage this hunk [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,j,J,g,/,e,p,?]?_
+	(1/2) Stage this hunk [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,j,J,g,/,e,p,?]? @@ -1,2 +1,3 @@
+	(1/2) Stage this hunk [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,j,J,g,/,e,p,?]?_
+	(1/2) Stage this hunk [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,j,J,g,/,e,p,?]? <RESET>PAGER <CYAN>@@ -1,2 +1,3 @@<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>
 	PAGER  10<RESET>
 	PAGER <GREEN>+<RESET><GREEN>15<RESET>
 	PAGER  20<RESET>
-	<BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET>
+	<BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>
 	EOF
 	test_write_lines s y g 1 P |
 	(
@@ -796,21 +796,21 @@
 	<BLUE>+<RESET><BLUE>new<RESET>
 	<CYAN> more-context<RESET>
 	<BLUE>+<RESET><BLUE>another-one<RESET>
-	<YELLOW>(1/1) Stage this hunk [y,n,q,a,d,s,e,p,?]? <RESET><BOLD>Split into 2 hunks.<RESET>
+	<YELLOW>(1/1) Stage this hunk [y,n,q,a,d,s,e,p,P,?]? <RESET><BOLD>Split into 2 hunks.<RESET>
 	<MAGENTA>@@ -1,3 +1,3 @@<RESET>
 	<CYAN> context<RESET>
 	<BOLD>-old<RESET>
 	<BLUE>+<RESET><BLUE>new<RESET>
 	<CYAN> more-context<RESET>
-	<YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET>
+	<YELLOW>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET>
 	<CYAN> more-context<RESET>
 	<BLUE>+<RESET><BLUE>another-one<RESET>
-	<YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET>
+	<YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET>
 	<CYAN> context<RESET>
 	<BOLD>-old<RESET>
 	<BLUE>+new<RESET>
 	<CYAN> more-context<RESET>
-	<YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET>
+	<YELLOW>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>
 	EOF
 	test_cmp expect actual
 '
@@ -1354,4 +1354,81 @@
 	'
 done
 
+test_expect_success 'splitting previous hunk marks split hunks as undecided' '
+	test_write_lines a " " b c d e f g h i j k >file &&
+	git add file &&
+	test_write_lines x " " b y d e f g h i j x >file &&
+	test_write_lines n K s n y q | git add -p file &&
+	git cat-file blob :file >actual &&
+	test_write_lines a " " b y d e f g h i j k >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'splitting edited hunk' '
+	# Before the first hunk is edited it can be split into two
+	# hunks, after editing it can be split into three hunks.
+
+	write_script fake-editor.sh <<-\EOF &&
+	sed "s/^ c/-c/" "$1" >"$1.tmp" &&
+	mv "$1.tmp" "$1"
+	EOF
+
+	test_write_lines a b c d e f g h i j k l m n >file &&
+	git add file &&
+	test_write_lines A b c d E f g h i j k l M n >file &&
+	(
+		test_set_editor "$(pwd)/fake-editor.sh" &&
+		test_write_lines e K s j y n y q | git add -p file
+	) &&
+	git cat-file blob :file >actual &&
+	test_write_lines a b d e f g h i j k l M n >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'options J, K roll over' '
+	test_write_lines a b c d e f g h i >file &&
+	git add file &&
+	test_write_lines X b c d e f g h X >file &&
+	test_write_lines J J K q | git add -p >out &&
+	test_write_lines 1 2 1 2 >expect &&
+	sed -n -e "s-/.*--" -e "s/^(//p" <out >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'options y, n, a, d, j, k, e roll over to next undecided (1)' '
+	test_write_lines a b c d e f g h i j k l m n o p q >file &&
+	git add file &&
+	test_write_lines X b c d e f g h X j k l m n o p X >file &&
+	test_set_editor : &&
+	test_write_lines g3 y g3 n g3 a g3 d g3 j g3 e k q | git add -p >out &&
+	test_write_lines 1  3 1  3 1  3 1  3 1  3 1  3 1 2 >expect &&
+	sed -n -e "s-/.*--" -e "s/^(//p" <out >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'options y, n, a, d, j, k, e roll over to next undecided (2)' '
+	test_write_lines a b c d e f g h i j k l m n o p q >file &&
+	git add file &&
+	test_write_lines X b c d e f g h X j k l m n o p X >file &&
+	test_set_editor : &&
+	test_write_lines y g3 y g3 n g3 a g3 d g3 j g3 e g1 k q | git add -p >out &&
+	test_write_lines 1 2  3 2  3 2  3 2  3 2  3 2  3 2  1 2 >expect &&
+	sed -n -e "s-/.*--" -e "s/^(//p" <out >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'invalid option s is rejected' '
+	test_write_lines a b c d e f g h i j k >file &&
+	git add file &&
+	test_write_lines X b X d e f g h i j X >file &&
+	test_write_lines j s q | git add -p >out &&
+	sed -ne "s/ @@.*//" -e "s/ \$//" -e "/^(/p" <out >actual &&
+	cat >expect <<-EOF &&
+	(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,s,e,p,P,?]?
+	(2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? Sorry, cannot split this hunk
+	(2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?
+	EOF
+	test_cmp expect actual
+'
+
 test_done
diff --git a/t/t4035-diff-quiet.sh b/t/t4035-diff-quiet.sh
index 0352bf8..35eaf08 100755
--- a/t/t4035-diff-quiet.sh
+++ b/t/t4035-diff-quiet.sh
@@ -50,6 +50,10 @@
 	test_expect_code 0 git diff-tree --quiet HEAD HEAD >cnt &&
 	test_line_count = 0 cnt
 '
+test_expect_success 'git diff-tree -w HEAD^ HEAD' '
+	test_expect_code 1 git diff-tree --quiet -w HEAD^ HEAD >cnt &&
+	test_line_count = 0 cnt
+'
 test_expect_success 'git diff-files' '
 	test_expect_code 0 git diff-files --quiet >cnt &&
 	test_line_count = 0 cnt
diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh
index 44b4b13..6959927 100755
--- a/t/t4053-diff-no-index.sh
+++ b/t/t4053-diff-no-index.sh
@@ -339,6 +339,22 @@
 	test_cmp expect actual
 '
 
+test_expect_success 'diff --no-index first path ending in slash with pathspec' '
+	test_expect_code 1 git diff --name-status --no-index a/ b 1 >actual &&
+	cat >expect <<-EOF &&
+	D	a/1
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'diff --no-index second path ending in slash with pathspec' '
+	test_expect_code 1 git diff --name-status --no-index a b/ 1 >actual &&
+	cat >expect <<-EOF &&
+	D	a/1
+	EOF
+	test_cmp expect actual
+'
+
 test_expect_success 'diff --no-index with pathspec no matches' '
 	test_expect_code 0 git diff --name-status --no-index a b missing
 '
diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh
index 0b3404f..98c6910 100755
--- a/t/t5318-commit-graph.sh
+++ b/t/t5318-commit-graph.sh
@@ -946,4 +946,48 @@
 	)
 '
 
+test_expect_success 'config commitGraph.changedPaths acts like --changed-paths' '
+	git init config-changed-paths &&
+	(
+		cd config-changed-paths &&
+
+		# commitGraph.changedPaths is not set and it should not write Bloom filters
+		test_commit first &&
+		GIT_PROGRESS_DELAY=0 git commit-graph write --reachable --progress 2>error &&
+		test_grep ! "Bloom filters" error &&
+
+		# Set commitGraph.changedPaths to true and it should write Bloom filters
+		test_commit second &&
+		git config commitGraph.changedPaths true &&
+		GIT_PROGRESS_DELAY=0 git commit-graph write --reachable --progress 2>error &&
+		test_grep "Bloom filters" error &&
+
+		# Add one more config commitGraph.changedPaths as false to disable the previous true config value
+		# It should still write Bloom filters due to existing filters
+		test_commit third &&
+		git config --add commitGraph.changedPaths false &&
+		GIT_PROGRESS_DELAY=0 git commit-graph write --reachable --progress 2>error &&
+		test_grep "Bloom filters" error &&
+
+		# commitGraph.changedPaths is still false and command line options should take precedence
+		test_commit fourth &&
+		GIT_PROGRESS_DELAY=0 git commit-graph write --no-changed-paths --reachable --progress 2>error &&
+		test_grep ! "Bloom filters" error &&
+		GIT_PROGRESS_DELAY=0 git commit-graph write --reachable --progress 2>error &&
+		test_grep ! "Bloom filters" error &&
+
+		# commitGraph.changedPaths is all cleared and then set to false again, command line options should take precedence
+		test_commit fifth &&
+		git config --unset-all commitGraph.changedPaths &&
+		git config commitGraph.changedPaths false &&
+		GIT_PROGRESS_DELAY=0 git commit-graph write --changed-paths --reachable --progress 2>error &&
+		test_grep "Bloom filters" error &&
+
+		# commitGraph.changedPaths is still false and it should write Bloom filters due to existing filters
+		test_commit sixth &&
+		GIT_PROGRESS_DELAY=0 git commit-graph write --reachable --progress 2>error &&
+		test_grep "Bloom filters" error
+	)
+'
+
 test_done
diff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh
index 4dca8d9..66aff8e 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -31,56 +31,83 @@
 	echo changes >> foo &&
 	git add foo &&
 	(
+		GIT_EDITOR="echo hello >" &&
+		export GIT_EDITOR &&
+		test_must_fail git commit --template "$(pwd)"/notexist
+	)
+'
+
+test_expect_success 'nonexistent optional template file on command line' '
+	echo changes >> foo &&
+	git add foo &&
+	(
 		GIT_EDITOR="echo hello >\"\$1\"" &&
 		export GIT_EDITOR &&
-		test_must_fail git commit --template "$PWD"/notexist
+		git commit --template ":(optional)$(pwd)/notexist"
 	)
 '
 
 test_expect_success 'nonexistent template file in config should return error' '
-	test_config commit.template "$PWD"/notexist &&
+	test_config commit.template "$(pwd)"/notexist &&
 	(
-		GIT_EDITOR="echo hello >\"\$1\"" &&
+		GIT_EDITOR="echo hello >" &&
 		export GIT_EDITOR &&
-		test_must_fail git commit
+		test_must_fail git commit --allow-empty
 	)
 '
 
+test_expect_success 'nonexistent optional template file in config' '
+	test_config commit.template ":(optional)$(pwd)"/notexist &&
+	GIT_EDITOR="echo hello >" git commit --allow-empty &&
+	git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
+	echo hello >expect &&
+	test_cmp expect actual
+'
+
 # From now on we'll use a template file that exists.
-TEMPLATE="$PWD"/template
+TEMPLATE="$(pwd)"/template
 
 test_expect_success 'unedited template should not commit' '
-	echo "template line" > "$TEMPLATE" &&
-	test_must_fail git commit --template "$TEMPLATE"
+	echo "template line" >"$TEMPLATE" &&
+	test_must_fail git commit --allow-empty --template "$TEMPLATE"
 '
 
 test_expect_success 'unedited template with comments should not commit' '
-	echo "# comment in template" >> "$TEMPLATE" &&
-	test_must_fail git commit --template "$TEMPLATE"
+	echo "# comment in template" >>"$TEMPLATE" &&
+	test_must_fail git commit --allow-empty --template "$TEMPLATE"
 '
 
 test_expect_success 'a Signed-off-by line by itself should not commit' '
 	(
 		test_set_editor "$TEST_DIRECTORY"/t7500/add-signed-off &&
-		test_must_fail git commit --template "$TEMPLATE"
+		test_must_fail git commit --allow-empty --template "$TEMPLATE"
 	)
 '
 
 test_expect_success 'adding comments to a template should not commit' '
 	(
 		test_set_editor "$TEST_DIRECTORY"/t7500/add-comments &&
-		test_must_fail git commit --template "$TEMPLATE"
+		test_must_fail git commit --allow-empty --template "$TEMPLATE"
 	)
 '
 
 test_expect_success 'adding real content to a template should commit' '
 	(
 		test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
-		git commit --template "$TEMPLATE"
+		git commit --allow-empty --template "$TEMPLATE"
 	) &&
 	commit_msg_is "template linecommit message"
 '
 
+test_expect_success 'existent template marked optional should commit' '
+	echo "existent template" >"$TEMPLATE" &&
+	(
+		test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
+		git commit --allow-empty --template ":(optional)$TEMPLATE"
+	) &&
+	commit_msg_is "existent templatecommit message"
+'
+
 test_expect_success '-t option should be short for --template' '
 	echo "short template" > "$TEMPLATE" &&
 	echo "new content" >> foo &&
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index cdc1d6f..abad229 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -717,6 +717,17 @@
 
 '
 
+test_expect_success TTY 'status -s keeps colors with -z' '
+	test_when_finished "rm -f output.*" &&
+	test_terminal git status -s -z >output.raw &&
+	# convert back to newlines to avoid portability issues with
+	# test_decode_color and test_cmp, and to let us use the same expected
+	# output as earlier tests
+	tr "\0" "\n" <output.raw >output.nl &&
+	test_decode_color <output.nl >output &&
+	test_cmp expect output
+'
+
 cat >expect <<\EOF
 ## <YELLOW>main<RESET>...<CYAN>upstream<RESET> [ahead <YELLOW>1<RESET>, behind <CYAN>2<RESET>]
  <RED>M<RESET> dir1/modified
diff --git a/t/t7528-signed-commit-ssh.sh b/t/t7528-signed-commit-ssh.sh
index 0f887a3..b50306b 100755
--- a/t/t7528-signed-commit-ssh.sh
+++ b/t/t7528-signed-commit-ssh.sh
@@ -82,7 +82,7 @@
 test_expect_success GPGSSH 'sign commits using literal public keys with ssh-agent' '
 	test_when_finished "test_unconfig commit.gpgsign" &&
 	test_config gpg.format ssh &&
-	eval $(ssh-agent) &&
+	eval $(ssh-agent -T || ssh-agent) &&
 	test_when_finished "kill ${SSH_AGENT_PID}" &&
 	test_when_finished "test_unconfig user.signingkey" &&
 	mkdir tmpdir &&
diff --git a/t/t9306-fast-import-signed-tags.sh b/t/t9306-fast-import-signed-tags.sh
new file mode 100755
index 0000000..363619e
--- /dev/null
+++ b/t/t9306-fast-import-signed-tags.sh
@@ -0,0 +1,80 @@
+#!/bin/sh
+
+test_description='git fast-import --signed-tags=<mode>'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
+
+test_expect_success 'set up unsigned initial commit and import repo' '
+	test_commit first &&
+	git init new
+'
+
+test_expect_success 'import no signed tag with --signed-tags=abort' '
+	git fast-export --signed-tags=verbatim >output &&
+	git -C new fast-import --quiet --signed-tags=abort <output
+'
+
+test_expect_success GPG 'set up OpenPGP signed tag' '
+	git tag -s -m "OpenPGP signed tag" openpgp-signed first &&
+	OPENPGP_SIGNED=$(git rev-parse --verify refs/tags/openpgp-signed) &&
+	git fast-export --signed-tags=verbatim openpgp-signed >output
+'
+
+test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=abort' '
+	test_must_fail git -C new fast-import --quiet --signed-tags=abort <output
+'
+
+test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=verbatim' '
+	git -C new fast-import --quiet --signed-tags=verbatim <output >log 2>&1 &&
+	IMPORTED=$(git -C new rev-parse --verify refs/tags/openpgp-signed) &&
+	test $OPENPGP_SIGNED = $IMPORTED &&
+	test_must_be_empty log
+'
+
+test_expect_success GPGSM 'setup X.509 signed tag' '
+	test_config gpg.format x509 &&
+	test_config user.signingkey $GIT_COMMITTER_EMAIL &&
+
+	git tag -s -m "X.509 signed tag" x509-signed first &&
+	X509_SIGNED=$(git rev-parse --verify refs/tags/x509-signed) &&
+	git fast-export --signed-tags=verbatim x509-signed >output
+'
+
+test_expect_success GPGSM 'import X.509 signed tag with --signed-tags=warn-strip' '
+	git -C new fast-import --quiet --signed-tags=warn-strip <output >log 2>&1 &&
+	test_grep "stripping a tag signature for tag '\''x509-signed'\''" log &&
+	IMPORTED=$(git -C new rev-parse --verify refs/tags/x509-signed) &&
+	test $X509_SIGNED != $IMPORTED &&
+	git -C new cat-file -p x509-signed >out &&
+	test_grep ! "SIGNED MESSAGE" out
+'
+
+test_expect_success GPGSSH 'setup SSH signed tag' '
+	test_config gpg.format ssh &&
+	test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+
+	git tag -s -m "SSH signed tag" ssh-signed first &&
+	SSH_SIGNED=$(git rev-parse --verify refs/tags/ssh-signed) &&
+	git fast-export --signed-tags=verbatim ssh-signed >output
+'
+
+test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=warn-verbatim' '
+	git -C new fast-import --quiet --signed-tags=warn-verbatim <output >log 2>&1 &&
+	test_grep "importing a tag signature verbatim for tag '\''ssh-signed'\''" log &&
+	IMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) &&
+	test $SSH_SIGNED = $IMPORTED
+'
+
+test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' '
+	git -C new fast-import --quiet --signed-tags=strip <output >log 2>&1 &&
+	test_must_be_empty log &&
+	IMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) &&
+	test $SSH_SIGNED != $IMPORTED &&
+	git -C new cat-file -p ssh-signed >out &&
+	test_grep ! "SSH SIGNATURE" out
+'
+
+test_done
diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh
index 8f85c69..3d153a4 100755
--- a/t/t9350-fast-export.sh
+++ b/t/t9350-fast-export.sh
@@ -35,6 +35,7 @@
 	git commit -m sitzt file2 &&
 	test_tick &&
 	git tag -a -m valentin muss &&
+	ANNOTATED_TAG_COUNT=1 &&
 	git merge -s ours main
 
 '
@@ -229,7 +230,8 @@
 
 test_expect_success 'set up faked signed tag' '
 
-	git fast-import <signed-tag-import
+	git fast-import <signed-tag-import &&
+	ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))
 
 '
 
@@ -277,6 +279,42 @@
 	test -s err
 '
 
+test_expect_success GPGSM 'setup X.509 signed tag' '
+	test_config gpg.format x509 &&
+	test_config user.signingkey $GIT_COMMITTER_EMAIL &&
+
+	git tag -s -m "X.509 signed tag" x509-signed $(git rev-parse HEAD) &&
+	ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))
+'
+
+test_expect_success GPGSM 'signed-tags=verbatim with X.509' '
+	git fast-export --signed-tags=verbatim x509-signed > output &&
+	test_grep "SIGNED MESSAGE" output
+'
+
+test_expect_success GPGSM 'signed-tags=strip with X.509' '
+	git fast-export --signed-tags=strip x509-signed > output &&
+	test_grep ! "SIGNED MESSAGE" output
+'
+
+test_expect_success GPGSSH 'setup SSH signed tag' '
+	test_config gpg.format ssh &&
+	test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+
+	git tag -s -m "SSH signed tag" ssh-signed $(git rev-parse HEAD) &&
+	ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))
+'
+
+test_expect_success GPGSSH 'signed-tags=verbatim with SSH' '
+	git fast-export --signed-tags=verbatim ssh-signed > output &&
+	test_grep "SSH SIGNATURE" output
+'
+
+test_expect_success GPGSSH 'signed-tags=strip with SSH' '
+	git fast-export --signed-tags=strip ssh-signed > output &&
+	test_grep ! "SSH SIGNATURE" output
+'
+
 test_expect_success GPG 'set up signed commit' '
 
 	# Generate a commit with both "gpgsig" and "encoding" set, so
@@ -491,8 +529,9 @@
 test_expect_success 'fast-export | fast-import when main is tagged' '
 
 	git tag -m msg last &&
+	ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) &&
 	git fast-export -C -C --signed-tags=strip --all > output &&
-	test $(grep -c "^tag " output) = 3
+	test $(grep -c "^tag " output) = $ANNOTATED_TAG_COUNT
 
 '
 
@@ -506,12 +545,13 @@
 
 	TAG=$(git hash-object --literally -t tag -w tag-content) &&
 	git update-ref refs/tags/sonnenschein $TAG &&
+	ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) &&
 	git fast-export -C -C --signed-tags=strip --all > output &&
-	test $(grep -c "^tag " output) = 4 &&
+	test $(grep -c "^tag " output) = $ANNOTATED_TAG_COUNT &&
 	! grep "Unspecified Tagger" output &&
 	git fast-export -C -C --signed-tags=strip --all \
 		--fake-missing-tagger > output &&
-	test $(grep -c "^tag " output) = 4 &&
+	test $(grep -c "^tag " output) = $ANNOTATED_TAG_COUNT &&
 	grep "Unspecified Tagger" output
 
 '
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index a28de7b..52d7759 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -1708,11 +1708,16 @@
 # Detect the hash algorithm in use.
 test_detect_hash () {
 	case "${GIT_TEST_DEFAULT_HASH:-$GIT_TEST_BUILTIN_HASH}" in
-	"sha256")
+	*:*)
+	    test_hash_algo="${GIT_TEST_DEFAULT_HASH%%:*}"
+	    test_compat_hash_algo="${GIT_TEST_DEFAULT_HASH##*:}"
+	    test_repo_compat_hash_algo="$test_compat_hash_algo"
+	    ;;
+	sha256)
 	    test_hash_algo=sha256
 	    test_compat_hash_algo=sha1
 	    ;;
-	*)
+	sha1)
 	    test_hash_algo=sha1
 	    test_compat_hash_algo=sha256
 	    ;;
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 562f950..ef0ab7e 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -1924,6 +1924,19 @@
 test_lazy_prereq DEFAULT_REPO_FORMAT '
 	test_have_prereq SHA1,REFFILES
 '
+# BROKEN_OBJECTS is a test whether we can write deliberately broken objects and
+# expect them to work.  When running using SHA-256 mode with SHA-1
+# compatibility, we cannot write such objects because there's no SHA-1
+# compatibility value for a nonexistent object.
+test_lazy_prereq BROKEN_OBJECTS '
+	! test_have_prereq COMPAT_HASH
+'
+
+# COMPAT_HASH is a test if we're operating in a repository with SHA-256 with
+# SHA-1 compatibility.
+test_lazy_prereq COMPAT_HASH '
+	test -n "$test_repo_compat_hash_algo"
+'
 
 # Ensure that no test accidentally triggers a Git command
 # that runs the actual maintenance scheduler, affecting a user's
diff --git a/unicode-width.h b/unicode-width.h
index 3ffee12..b701129 100644
--- a/unicode-width.h
+++ b/unicode-width.h
@@ -143,7 +143,8 @@
 { 0x1A65, 0x1A6C },
 { 0x1A73, 0x1A7C },
 { 0x1A7F, 0x1A7F },
-{ 0x1AB0, 0x1ACE },
+{ 0x1AB0, 0x1ADD },
+{ 0x1AE0, 0x1AEB },
 { 0x1B00, 0x1B03 },
 { 0x1B34, 0x1B34 },
 { 0x1B36, 0x1B3A },
@@ -229,7 +230,7 @@
 { 0x10D24, 0x10D27 },
 { 0x10D69, 0x10D6D },
 { 0x10EAB, 0x10EAC },
-{ 0x10EFC, 0x10EFF },
+{ 0x10EFA, 0x10EFF },
 { 0x10F46, 0x10F50 },
 { 0x10F82, 0x10F85 },
 { 0x11001, 0x11001 },
@@ -306,6 +307,9 @@
 { 0x11A59, 0x11A5B },
 { 0x11A8A, 0x11A96 },
 { 0x11A98, 0x11A99 },
+{ 0x11B60, 0x11B60 },
+{ 0x11B62, 0x11B64 },
+{ 0x11B66, 0x11B66 },
 { 0x11C30, 0x11C36 },
 { 0x11C38, 0x11C3D },
 { 0x11C3F, 0x11C3F },
@@ -362,6 +366,10 @@
 { 0x1E2EC, 0x1E2EF },
 { 0x1E4EC, 0x1E4EF },
 { 0x1E5EE, 0x1E5EF },
+{ 0x1E6E3, 0x1E6E3 },
+{ 0x1E6E6, 0x1E6E6 },
+{ 0x1E6EE, 0x1E6EF },
+{ 0x1E6F5, 0x1E6F5 },
 { 0x1E8D0, 0x1E8D6 },
 { 0x1E944, 0x1E94A },
 { 0xE0001, 0xE0001 },
@@ -429,10 +437,10 @@ static const struct interval double_width[] = {
 { 0xFF01, 0xFF60 },
 { 0xFFE0, 0xFFE6 },
 { 0x16FE0, 0x16FE4 },
-{ 0x16FF0, 0x16FF1 },
-{ 0x17000, 0x187F7 },
-{ 0x18800, 0x18CD5 },
-{ 0x18CFF, 0x18D08 },
+{ 0x16FF0, 0x16FF6 },
+{ 0x17000, 0x18CD5 },
+{ 0x18CFF, 0x18D1E },
+{ 0x18D80, 0x18DF2 },
 { 0x1AFF0, 0x1AFF3 },
 { 0x1AFF5, 0x1AFFB },
 { 0x1AFFD, 0x1AFFE },
@@ -474,7 +482,7 @@ static const struct interval double_width[] = {
 { 0x1F680, 0x1F6C5 },
 { 0x1F6CC, 0x1F6CC },
 { 0x1F6D0, 0x1F6D2 },
-{ 0x1F6D5, 0x1F6D7 },
+{ 0x1F6D5, 0x1F6D8 },
 { 0x1F6DC, 0x1F6DF },
 { 0x1F6EB, 0x1F6EC },
 { 0x1F6F4, 0x1F6FC },
@@ -484,11 +492,12 @@ static const struct interval double_width[] = {
 { 0x1F93C, 0x1F945 },
 { 0x1F947, 0x1F9FF },
 { 0x1FA70, 0x1FA7C },
-{ 0x1FA80, 0x1FA89 },
-{ 0x1FA8F, 0x1FAC6 },
-{ 0x1FACE, 0x1FADC },
-{ 0x1FADF, 0x1FAE9 },
-{ 0x1FAF0, 0x1FAF8 },
+{ 0x1FA80, 0x1FA8A },
+{ 0x1FA8E, 0x1FAC6 },
+{ 0x1FAC8, 0x1FAC8 },
+{ 0x1FACD, 0x1FADC },
+{ 0x1FADF, 0x1FAEA },
+{ 0x1FAEF, 0x1FAF8 },
 { 0x20000, 0x2FFFD },
 { 0x30000, 0x3FFFD }
 };
diff --git a/wrapper.c b/wrapper.c
index 2f00d2a..3d507d4 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -721,6 +721,19 @@ int xgethostname(char *buf, size_t len)
 	return ret;
 }
 
+int is_missing_file(const char *filename)
+{
+	struct stat st;
+
+	if (stat(filename, &st) < 0) {
+		if (errno == ENOENT)
+			return 1;
+		die_errno(_("could not stat %s"), filename);
+	}
+
+	return 0;
+}
+
 int is_empty_or_missing_file(const char *filename)
 {
 	struct stat st;
diff --git a/wrapper.h b/wrapper.h
index 7df824e..44a8597 100644
--- a/wrapper.h
+++ b/wrapper.h
@@ -66,7 +66,9 @@ void write_file_buf(const char *path, const char *buf, size_t len);
 __attribute__((format (printf, 2, 3)))
 void write_file(const char *path, const char *fmt, ...);
 
-/* Return 1 if the file is empty or does not exists, 0 otherwise. */
+/* Return 1 if the file does not exist, 0 otherwise. */
+int is_missing_file(const char *filename);
+/* Return 1 if the file is empty or does not exist, 0 otherwise. */
 int is_empty_or_missing_file(const char *filename);
 
 enum fsync_action {
diff --git a/wt-status.c b/wt-status.c
index 8ffe6d3..e12adb2 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -2042,13 +2042,13 @@ static void wt_shortstatus_status(struct string_list_item *it,
 static void wt_shortstatus_other(struct string_list_item *it,
 				 struct wt_status *s, const char *sign)
 {
+	color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign);
 	if (s->null_termination) {
-		fprintf(s->fp, "%s %s%c", sign, it->string, 0);
+		fprintf(s->fp, " %s%c", it->string, 0);
 	} else {
 		struct strbuf onebuf = STRBUF_INIT;
 		const char *one;
 		one = quote_path(it->string, s->prefix, &onebuf, QUOTE_PATH_QUOTE_SP);
-		color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign);
 		fprintf(s->fp, " %s\n", one);
 		strbuf_release(&onebuf);
 	}
diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c
index 5a96e36..6f3998e 100644
--- a/xdiff/xdiffi.c
+++ b/xdiff/xdiffi.c
@@ -22,6 +22,11 @@
 
 #include "xinclude.h"
 
+static unsigned long get_hash(xdfile_t *xdf, long index)
+{
+	return xdf->recs[xdf->rindex[index]].ha;
+}
+
 #define XDL_MAX_COST_MIN 256
 #define XDL_HEUR_MIN_COST 256
 #define XDL_LINE_MAX (long)((1UL << (CHAR_BIT * sizeof(long) - 1)) - 1)
@@ -42,8 +47,8 @@ typedef struct s_xdpsplit {
  * using this algorithm, so a little bit of heuristic is needed to cut the
  * search and to return a suboptimal point.
  */
-static long xdl_split(unsigned long const *ha1, long off1, long lim1,
-		      unsigned long const *ha2, long off2, long lim2,
+static long xdl_split(xdfile_t *xdf1, long off1, long lim1,
+		      xdfile_t *xdf2, long off2, long lim2,
 		      long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl,
 		      xdalgoenv_t *xenv) {
 	long dmin = off1 - lim2, dmax = lim1 - off2;
@@ -87,7 +92,7 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1,
 				i1 = kvdf[d + 1];
 			prev1 = i1;
 			i2 = i1 - d;
-			for (; i1 < lim1 && i2 < lim2 && ha1[i1] == ha2[i2]; i1++, i2++);
+			for (; i1 < lim1 && i2 < lim2 && get_hash(xdf1, i1) == get_hash(xdf2, i2); i1++, i2++);
 			if (i1 - prev1 > xenv->snake_cnt)
 				got_snake = 1;
 			kvdf[d] = i1;
@@ -124,7 +129,7 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1,
 				i1 = kvdb[d + 1] - 1;
 			prev1 = i1;
 			i2 = i1 - d;
-			for (; i1 > off1 && i2 > off2 && ha1[i1 - 1] == ha2[i2 - 1]; i1--, i2--);
+			for (; i1 > off1 && i2 > off2 && get_hash(xdf1, i1 - 1) == get_hash(xdf2, i2 - 1); i1--, i2--);
 			if (prev1 - i1 > xenv->snake_cnt)
 				got_snake = 1;
 			kvdb[d] = i1;
@@ -159,7 +164,7 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1,
 				if (v > XDL_K_HEUR * ec && v > best &&
 				    off1 + xenv->snake_cnt <= i1 && i1 < lim1 &&
 				    off2 + xenv->snake_cnt <= i2 && i2 < lim2) {
-					for (k = 1; ha1[i1 - k] == ha2[i2 - k]; k++)
+					for (k = 1; get_hash(xdf1, i1 - k) == get_hash(xdf2, i2 - k); k++)
 						if (k == xenv->snake_cnt) {
 							best = v;
 							spl->i1 = i1;
@@ -183,7 +188,7 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1,
 				if (v > XDL_K_HEUR * ec && v > best &&
 				    off1 < i1 && i1 <= lim1 - xenv->snake_cnt &&
 				    off2 < i2 && i2 <= lim2 - xenv->snake_cnt) {
-					for (k = 0; ha1[i1 + k] == ha2[i2 + k]; k++)
+					for (k = 0; get_hash(xdf1, i1 + k) == get_hash(xdf2, i2 + k); k++)
 						if (k == xenv->snake_cnt - 1) {
 							best = v;
 							spl->i1 = i1;
@@ -257,33 +262,26 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1,
  * sub-boxes by calling the box splitting function. Note that the real job
  * (marking changed lines) is done in the two boundary reaching checks.
  */
-int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
-		 diffdata_t *dd2, long off2, long lim2,
+int xdl_recs_cmp(xdfile_t *xdf1, long off1, long lim1,
+		 xdfile_t *xdf2, long off2, long lim2,
 		 long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv) {
-	unsigned long const *ha1 = dd1->ha, *ha2 = dd2->ha;
 
 	/*
 	 * Shrink the box by walking through each diagonal snake (SW and NE).
 	 */
-	for (; off1 < lim1 && off2 < lim2 && ha1[off1] == ha2[off2]; off1++, off2++);
-	for (; off1 < lim1 && off2 < lim2 && ha1[lim1 - 1] == ha2[lim2 - 1]; lim1--, lim2--);
+	for (; off1 < lim1 && off2 < lim2 && get_hash(xdf1, off1) == get_hash(xdf2, off2); off1++, off2++);
+	for (; off1 < lim1 && off2 < lim2 && get_hash(xdf1, lim1 - 1) == get_hash(xdf2, lim2 - 1); lim1--, lim2--);
 
 	/*
 	 * If one dimension is empty, then all records on the other one must
 	 * be obviously changed.
 	 */
 	if (off1 == lim1) {
-		char *rchg2 = dd2->rchg;
-		long *rindex2 = dd2->rindex;
-
 		for (; off2 < lim2; off2++)
-			rchg2[rindex2[off2]] = 1;
+			xdf2->changed[xdf2->rindex[off2]] = true;
 	} else if (off2 == lim2) {
-		char *rchg1 = dd1->rchg;
-		long *rindex1 = dd1->rindex;
-
 		for (; off1 < lim1; off1++)
-			rchg1[rindex1[off1]] = 1;
+			xdf1->changed[xdf1->rindex[off1]] = true;
 	} else {
 		xdpsplit_t spl;
 		spl.i1 = spl.i2 = 0;
@@ -291,7 +289,7 @@ int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
 		/*
 		 * Divide ...
 		 */
-		if (xdl_split(ha1, off1, lim1, ha2, off2, lim2, kvdf, kvdb,
+		if (xdl_split(xdf1, off1, lim1, xdf2, off2, lim2, kvdf, kvdb,
 			      need_min, &spl, xenv) < 0) {
 
 			return -1;
@@ -300,9 +298,9 @@ int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
 		/*
 		 * ... et Impera.
 		 */
-		if (xdl_recs_cmp(dd1, off1, spl.i1, dd2, off2, spl.i2,
+		if (xdl_recs_cmp(xdf1, off1, spl.i1, xdf2, off2, spl.i2,
 				 kvdf, kvdb, spl.min_lo, xenv) < 0 ||
-		    xdl_recs_cmp(dd1, spl.i1, lim1, dd2, spl.i2, lim2,
+		    xdl_recs_cmp(xdf1, spl.i1, lim1, xdf2, spl.i2, lim2,
 				 kvdf, kvdb, spl.min_hi, xenv) < 0) {
 
 			return -1;
@@ -318,7 +316,6 @@ int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
 	long ndiags;
 	long *kvd, *kvdf, *kvdb;
 	xdalgoenv_t xenv;
-	diffdata_t dd1, dd2;
 	int res;
 
 	if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0)
@@ -357,16 +354,7 @@ int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
 	xenv.snake_cnt = XDL_SNAKE_CNT;
 	xenv.heur_min = XDL_HEUR_MIN_COST;
 
-	dd1.nrec = xe->xdf1.nreff;
-	dd1.ha = xe->xdf1.ha;
-	dd1.rchg = xe->xdf1.rchg;
-	dd1.rindex = xe->xdf1.rindex;
-	dd2.nrec = xe->xdf2.nreff;
-	dd2.ha = xe->xdf2.ha;
-	dd2.rchg = xe->xdf2.rchg;
-	dd2.rindex = xe->xdf2.rindex;
-
-	res = xdl_recs_cmp(&dd1, 0, dd1.nrec, &dd2, 0, dd2.nrec,
+	res = xdl_recs_cmp(&xe->xdf1, 0, xe->xdf1.nreff, &xe->xdf2, 0, xe->xdf2.nreff,
 			   kvdf, kvdb, (xpp->flags & XDF_NEED_MINIMAL) != 0,
 			   &xenv);
 	xdl_free(kvd);
@@ -501,13 +489,13 @@ static void measure_split(const xdfile_t *xdf, long split,
 		m->indent = -1;
 	} else {
 		m->end_of_file = 0;
-		m->indent = get_indent(xdf->recs[split]);
+		m->indent = get_indent(&xdf->recs[split]);
 	}
 
 	m->pre_blank = 0;
 	m->pre_indent = -1;
 	for (i = split - 1; i >= 0; i--) {
-		m->pre_indent = get_indent(xdf->recs[i]);
+		m->pre_indent = get_indent(&xdf->recs[i]);
 		if (m->pre_indent != -1)
 			break;
 		m->pre_blank += 1;
@@ -520,7 +508,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++) {
-		m->post_indent = get_indent(xdf->recs[i]);
+		m->post_indent = get_indent(&xdf->recs[i]);
 		if (m->post_indent != -1)
 			break;
 		m->post_blank += 1;
@@ -720,7 +708,7 @@ struct xdlgroup {
 static void group_init(xdfile_t *xdf, struct xdlgroup *g)
 {
 	g->start = g->end = 0;
-	while (xdf->rchg[g->end])
+	while (xdf->changed[g->end])
 		g->end++;
 }
 
@@ -734,7 +722,7 @@ static inline int group_next(xdfile_t *xdf, struct xdlgroup *g)
 		return -1;
 
 	g->start = g->end + 1;
-	for (g->end = g->start; xdf->rchg[g->end]; g->end++)
+	for (g->end = g->start; xdf->changed[g->end]; g->end++)
 		;
 
 	return 0;
@@ -750,7 +738,7 @@ static inline int group_previous(xdfile_t *xdf, struct xdlgroup *g)
 		return -1;
 
 	g->end = g->start - 1;
-	for (g->start = g->end; xdf->rchg[g->start - 1]; g->start--)
+	for (g->start = g->end; xdf->changed[g->start - 1]; g->start--)
 		;
 
 	return 0;
@@ -764,11 +752,11 @@ 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 &&
-	    recs_match(xdf->recs[g->start], xdf->recs[g->end])) {
-		xdf->rchg[g->start++] = 0;
-		xdf->rchg[g->end++] = 1;
+	    recs_match(&xdf->recs[g->start], &xdf->recs[g->end])) {
+		xdf->changed[g->start++] = false;
+		xdf->changed[g->end++] = true;
 
-		while (xdf->rchg[g->end])
+		while (xdf->changed[g->end])
 			g->end++;
 
 		return 0;
@@ -785,11 +773,11 @@ static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g)
 static int group_slide_up(xdfile_t *xdf, struct xdlgroup *g)
 {
 	if (g->start > 0 &&
-	    recs_match(xdf->recs[g->start - 1], xdf->recs[g->end - 1])) {
-		xdf->rchg[--g->start] = 1;
-		xdf->rchg[--g->end] = 0;
+	    recs_match(&xdf->recs[g->start - 1], &xdf->recs[g->end - 1])) {
+		xdf->changed[--g->start] = true;
+		xdf->changed[--g->end] = false;
 
-		while (xdf->rchg[g->start - 1])
+		while (xdf->changed[g->start - 1])
 			g->start--;
 
 		return 0;
@@ -944,16 +932,16 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
 
 int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) {
 	xdchange_t *cscr = NULL, *xch;
-	char *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg;
+	bool *changed1 = xe->xdf1.changed, *changed2 = xe->xdf2.changed;
 	long i1, i2, l1, l2;
 
 	/*
 	 * Trivial. Collects "groups" of changes and creates an edit script.
 	 */
 	for (i1 = xe->xdf1.nrec, i2 = xe->xdf2.nrec; i1 >= 0 || i2 >= 0; i1--, i2--)
-		if (rchg1[i1 - 1] || rchg2[i2 - 1]) {
-			for (l1 = i1; rchg1[i1 - 1]; i1--);
-			for (l2 = i2; rchg2[i2 - 1]; i2--);
+		if (changed1[i1 - 1] || changed2[i2 - 1]) {
+			for (l1 = i1; changed1[i1 - 1]; i1--);
+			for (l2 = i2; changed2[i2 - 1]; i2--);
 
 			if (!(xch = xdl_add_change(cscr, i1, i2, l1 - i1, l2 - i2))) {
 				xdl_free_script(cscr);
@@ -1000,16 +988,16 @@ static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags)
 
 	for (xch = xscr; xch; xch = xch->next) {
 		int ignore = 1;
-		xrecord_t **rec;
+		xrecord_t *rec;
 		long i;
 
 		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(rec[i].ptr, 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(rec[i].ptr, rec[i].size, flags);
 
 		xch->ignore = ignore;
 	}
@@ -1033,7 +1021,7 @@ static void xdl_mark_ignorable_regex(xdchange_t *xscr, const xdfenv_t *xe,
 	xdchange_t *xch;
 
 	for (xch = xscr; xch; xch = xch->next) {
-		xrecord_t **rec;
+		xrecord_t *rec;
 		int ignore = 1;
 		long i;
 
@@ -1045,11 +1033,11 @@ static void xdl_mark_ignorable_regex(xdchange_t *xscr, const xdfenv_t *xe,
 
 		rec = &xe->xdf1.recs[xch->i1];
 		for (i = 0; i < xch->chg1 && ignore; i++)
-			ignore = record_matches_regex(rec[i], xpp);
+			ignore = record_matches_regex(&rec[i], xpp);
 
 		rec = &xe->xdf2.recs[xch->i2];
 		for (i = 0; i < xch->chg2 && ignore; i++)
-			ignore = record_matches_regex(rec[i], xpp);
+			ignore = record_matches_regex(&rec[i], xpp);
 
 		xch->ignore = ignore;
 	}
diff --git a/xdiff/xdiffi.h b/xdiff/xdiffi.h
index 126c9d8..49e52c6 100644
--- a/xdiff/xdiffi.h
+++ b/xdiff/xdiffi.h
@@ -24,13 +24,6 @@
 #define XDIFFI_H
 
 
-typedef struct s_diffdata {
-	long nrec;
-	unsigned long const *ha;
-	long *rindex;
-	char *rchg;
-} diffdata_t;
-
 typedef struct s_xdalgoenv {
 	long mxcost;
 	long snake_cnt;
@@ -46,8 +39,8 @@ typedef struct s_xdchange {
 
 
 
-int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
-		 diffdata_t *dd2, long off2, long lim2,
+int xdl_recs_cmp(xdfile_t *xdf1, long off1, long lim1,
+		 xdfile_t *xdf2, long off2, long lim2,
 		 long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv);
 int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
 		xdfenv_t *xe);
diff --git a/xdiff/xemit.c b/xdiff/xemit.c
index 1d40c9c..b2f1f30 100644
--- a/xdiff/xemit.c
+++ b/xdiff/xemit.c
@@ -22,23 +22,13 @@
 
 #include "xinclude.h"
 
-static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) {
 
-	*rec = xdf->recs[ri]->ptr;
+static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb)
+{
+	xrecord_t *rec = &xdf->recs[ri];
 
-	return xdf->recs[ri]->size;
-}
-
-
-static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb) {
-	long size, psize = strlen(pre);
-	char const *rec;
-
-	size = xdl_get_rec(xdf, ri, &rec);
-	if (xdl_emit_diffrec(rec, size, pre, psize, ecb) < 0) {
-
+	if (xdl_emit_diffrec(rec->ptr, rec->size, pre, strlen(pre), ecb) < 0)
 		return -1;
-	}
 
 	return 0;
 }
@@ -120,11 +110,11 @@ static long def_ff(const char *rec, long len, char *buf, long sz)
 static long match_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri,
 			   char *buf, long sz)
 {
-	const char *rec;
-	long len = xdl_get_rec(xdf, ri, &rec);
+	xrecord_t *rec = &xdf->recs[ri];
+
 	if (!xecfg->find_func)
-		return def_ff(rec, len, buf, sz);
-	return xecfg->find_func(rec, len, buf, sz, xecfg->find_func_priv);
+		return def_ff(rec->ptr, rec->size, buf, sz);
+	return xecfg->find_func(rec->ptr, rec->size, buf, sz, xecfg->find_func_priv);
 }
 
 static int is_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri)
@@ -160,14 +150,12 @@ static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg,
 
 static int is_empty_rec(xdfile_t *xdf, long ri)
 {
-	const char *rec;
-	long len = xdl_get_rec(xdf, ri, &rec);
+	xrecord_t *rec = &xdf->recs[ri];
+	long i = 0;
 
-	while (len > 0 && XDL_ISSPACE(*rec)) {
-		rec++;
-		len--;
-	}
-	return !len;
+	for (; i < rec->size && XDL_ISSPACE(rec->ptr[i]); i++);
+
+	return i == rec->size;
 }
 
 int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
diff --git a/xdiff/xhistogram.c b/xdiff/xhistogram.c
index 040d81e..6dc450b 100644
--- a/xdiff/xhistogram.c
+++ b/xdiff/xhistogram.c
@@ -86,7 +86,7 @@ struct region {
 	((LINE_MAP(index, ptr))->cnt)
 
 #define REC(env, s, l) \
-	(env->xdf##s.recs[l - 1])
+	(&env->xdf##s.recs[l - 1])
 
 static int cmp_recs(xrecord_t *r1, xrecord_t *r2)
 {
@@ -318,11 +318,11 @@ static int histogram_diff(xpparam_t const *xpp, xdfenv_t *env,
 
 	if (!count1) {
 		while(count2--)
-			env->xdf2.rchg[line2++ - 1] = 1;
+			env->xdf2.changed[line2++ - 1] = true;
 		return 0;
 	} else if (!count2) {
 		while(count1--)
-			env->xdf1.rchg[line1++ - 1] = 1;
+			env->xdf1.changed[line1++ - 1] = true;
 		return 0;
 	}
 
@@ -335,9 +335,9 @@ static int histogram_diff(xpparam_t const *xpp, xdfenv_t *env,
 	else {
 		if (lcs.begin1 == 0 && lcs.begin2 == 0) {
 			while (count1--)
-				env->xdf1.rchg[line1++ - 1] = 1;
+				env->xdf1.changed[line1++ - 1] = true;
 			while (count2--)
-				env->xdf2.rchg[line2++ - 1] = 1;
+				env->xdf2.changed[line2++ - 1] = true;
 			result = 0;
 		} else {
 			result = histogram_diff(xpp, env,
diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c
index af40c88..fd600cb 100644
--- a/xdiff/xmerge.c
+++ b/xdiff/xmerge.c
@@ -97,12 +97,12 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,
 		int line_count, long flags)
 {
 	int i;
-	xrecord_t **rec1 = xe1->xdf2.recs + i1;
-	xrecord_t **rec2 = xe2->xdf2.recs + i2;
+	xrecord_t *rec1 = xe1->xdf2.recs + i1;
+	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(rec1[i].ptr, rec1[i].size,
+			rec2[i].ptr, rec2[i].size, flags);
 		if (!result)
 			return -1;
 	}
@@ -111,7 +111,7 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,
 
 static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest)
 {
-	xrecord_t **recs;
+	xrecord_t *recs;
 	int size = 0;
 
 	recs = (use_orig ? xe->xdf1.recs : xe->xdf2.recs) + i;
@@ -119,12 +119,12 @@ 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 += recs[i++].size)
 		if (dest)
-			memcpy(dest + size, recs[i]->ptr, recs[i]->size);
+			memcpy(dest + size, recs[i].ptr, recs[i].size);
 	if (add_nl) {
-		i = recs[count - 1]->size;
-		if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') {
+		i = recs[count - 1].size;
+		if (i == 0 || recs[count - 1].ptr[i - 1] != '\n') {
 			if (needs_cr) {
 				if (dest)
 					dest[size] = '\r';
@@ -160,22 +160,22 @@ static int is_eol_crlf(xdfile_t *file, int i)
 
 	if (i < 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';
+		return (size = file->recs[i].size) > 1 &&
+			file->recs[i].ptr[size - 2] == '\r';
 	if (!file->nrec)
 		/* Cannot determine eol style from empty file */
 		return -1;
-	if ((size = file->recs[i]->size) &&
-			file->recs[i]->ptr[size - 1] == '\n')
+	if ((size = file->recs[i].size) &&
+			file->recs[i].ptr[size - 1] == '\n')
 		/* Last line; ends in LF; Is it CR/LF? */
 		return size > 1 &&
-			file->recs[i]->ptr[size - 2] == '\r';
+			file->recs[i].ptr[size - 2] == '\r';
 	if (!i)
 		/* The only line has no eol */
 		return -1;
 	/* Determine eol from second-to-last line */
-	return (size = file->recs[i - 1]->size) > 1 &&
-		file->recs[i - 1]->ptr[size - 2] == '\r';
+	return (size = file->recs[i - 1].size) > 1 &&
+		file->recs[i - 1].ptr[size - 2] == '\r';
 }
 
 static int is_cr_needed(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m)
@@ -334,22 +334,22 @@ static int recmatch(xrecord_t *rec1, xrecord_t *rec2, unsigned long flags)
 static void xdl_refine_zdiff3_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
 		xpparam_t const *xpp)
 {
-	xrecord_t **rec1 = xe1->xdf2.recs, **rec2 = xe2->xdf2.recs;
+	xrecord_t *rec1 = xe1->xdf2.recs, *rec2 = xe2->xdf2.recs;
 	for (; m; m = m->next) {
 		/* let's handle just the conflicts */
 		if (m->mode)
 			continue;
 
 		while(m->chg1 && m->chg2 &&
-		      recmatch(rec1[m->i1], rec2[m->i2], xpp->flags)) {
+		      recmatch(&rec1[m->i1], &rec2[m->i2], xpp->flags)) {
 			m->chg1--;
 			m->chg2--;
 			m->i1++;
 			m->i2++;
 		}
 		while (m->chg1 && m->chg2 &&
-		       recmatch(rec1[m->i1 + m->chg1 - 1],
-				rec2[m->i2 + m->chg2 - 1], xpp->flags)) {
+		       recmatch(&rec1[m->i1 + m->chg1 - 1],
+				&rec2[m->i2 + m->chg2 - 1], xpp->flags)) {
 			m->chg1--;
 			m->chg2--;
 		}
@@ -381,12 +381,12 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
 		 * This probably does not work outside git, since
 		 * 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
-			+ 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
-			+ xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr;
+		t1.ptr = (char *)xe1->xdf2.recs[m->i1].ptr;
+		t1.size = 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
+			+ xe2->xdf2.recs[m->i2 + m->chg2 - 1].size - t2.ptr;
 		if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0)
 			return -1;
 		if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||
@@ -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(xe->xdf2.recs[i].ptr,
+				xe->xdf2.recs[i].size))
 			return 1;
 	return 0;
 }
diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c
index 77dc411..669b653 100644
--- a/xdiff/xpatience.c
+++ b/xdiff/xpatience.c
@@ -88,9 +88,9 @@ static int is_anchor(xpparam_t const *xpp, const char *line)
 static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
 			  int pass)
 {
-	xrecord_t **records = pass == 1 ?
+	xrecord_t *records = pass == 1 ?
 		map->env->xdf1.recs : map->env->xdf2.recs;
-	xrecord_t *record = records[line - 1];
+	xrecord_t *record = &records[line - 1];
 	/*
 	 * After xdl_prepare_env() (or more precisely, due to
 	 * xdl_classify_record()), the "ha" member of the records (AKA lines)
@@ -121,7 +121,7 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
 		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].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1].ptr);
 	if (!map->first)
 		map->first = map->entries + index;
 	if (map->last) {
@@ -246,8 +246,8 @@ static int find_longest_common_sequence(struct hashmap *map, struct entry **res)
 
 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];
+	xrecord_t *record1 = &map->env->xdf1.recs[line1 - 1];
+	xrecord_t *record2 = &map->env->xdf2.recs[line2 - 1];
 	return record1->ha == record2->ha;
 }
 
@@ -331,11 +331,11 @@ static int patience_diff(xpparam_t const *xpp, xdfenv_t *env,
 	/* trivial case: one side is empty */
 	if (!count1) {
 		while(count2--)
-			env->xdf2.rchg[line2++ - 1] = 1;
+			env->xdf2.changed[line2++ - 1] = true;
 		return 0;
 	} else if (!count2) {
 		while(count1--)
-			env->xdf1.rchg[line1++ - 1] = 1;
+			env->xdf1.changed[line1++ - 1] = true;
 		return 0;
 	}
 
@@ -347,9 +347,9 @@ static int patience_diff(xpparam_t const *xpp, xdfenv_t *env,
 	/* are there any matching lines at all? */
 	if (!map.has_matches) {
 		while(count1--)
-			env->xdf1.rchg[line1++ - 1] = 1;
+			env->xdf1.changed[line1++ - 1] = true;
 		while(count2--)
-			env->xdf2.rchg[line2++ - 1] = 1;
+			env->xdf2.changed[line2++ - 1] = true;
 		xdl_free(map.entries);
 		return 0;
 	}
diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c
index e1d4017..192334f 100644
--- a/xdiff/xprepare.c
+++ b/xdiff/xprepare.c
@@ -29,12 +29,13 @@
 #define XDL_GUESS_NLINES1 256
 #define XDL_GUESS_NLINES2 20
 
+#define DISCARD 0
+#define KEEP 1
+#define INVESTIGATE 2
 
 typedef struct s_xdlclass {
 	struct s_xdlclass *next;
-	unsigned long ha;
-	char const *line;
-	long size;
+	xrecord_t rec;
 	long idx;
 	long len1, len2;
 } xdlclass_t;
@@ -53,21 +54,6 @@ typedef struct s_xdlclassifier {
 
 
 
-static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags);
-static void xdl_free_classifier(xdlclassifier_t *cf);
-static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
-			       unsigned int hbits, xrecord_t *rec);
-static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
-			   xdlclassifier_t *cf, xdfile_t *xdf);
-static void xdl_free_ctx(xdfile_t *xdf);
-static int xdl_clean_mmatch(char const *dis, long i, long s, long e);
-static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
-static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);
-static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
-
-
-
-
 static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
 	cf->flags = flags;
 
@@ -106,17 +92,14 @@ static void xdl_free_classifier(xdlclassifier_t *cf) {
 }
 
 
-static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
-			       unsigned int hbits, xrecord_t *rec) {
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t *rec) {
 	long hi;
-	char const *line;
 	xdlclass_t *rcrec;
 
-	line = rec->ptr;
 	hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
 	for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next)
-		if (rcrec->ha == rec->ha &&
-				xdl_recmatch(rcrec->line, rcrec->size,
+		if (rcrec->rec.ha == rec->ha &&
+				xdl_recmatch(rcrec->rec.ptr, rcrec->rec.size,
 					rec->ptr, rec->size, cf->flags))
 			break;
 
@@ -129,9 +112,7 @@ 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->line = line;
-		rcrec->size = rec->size;
-		rcrec->ha = rec->ha;
+		rcrec->rec = *rec;
 		rcrec->len1 = rcrec->len2 = 0;
 		rcrec->next = cf->rchash[hi];
 		cf->rchash[hi] = rcrec;
@@ -141,107 +122,263 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
 
 	rec->ha = (unsigned long) rcrec->idx;
 
-	hi = (long) XDL_HASHLONG(rec->ha, hbits);
-	rec->next = rhash[hi];
-	rhash[hi] = rec;
-
 	return 0;
 }
 
 
+static void xdl_free_ctx(xdfile_t *xdf)
+{
+	xdl_free(xdf->rindex);
+	xdl_free(xdf->changed - 1);
+	xdl_free(xdf->recs);
+}
+
+
 static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
 			   xdlclassifier_t *cf, xdfile_t *xdf) {
-	unsigned int hbits;
-	long nrec, hsize, bsize;
+	long bsize;
 	unsigned long hav;
 	char const *blk, *cur, *top, *prev;
 	xrecord_t *crec;
-	xrecord_t **recs;
-	xrecord_t **rhash;
-	unsigned long *ha;
-	char *rchg;
-	long *rindex;
 
-	ha = NULL;
-	rindex = NULL;
-	rchg = NULL;
-	rhash = NULL;
-	recs = NULL;
+	xdf->rindex = NULL;
+	xdf->changed = NULL;
+	xdf->recs = NULL;
 
-	if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
-		goto abort;
-	if (!XDL_ALLOC_ARRAY(recs, narec))
+	if (!XDL_ALLOC_ARRAY(xdf->recs, narec))
 		goto abort;
 
-	hbits = xdl_hashbits((unsigned int) narec);
-	hsize = 1 << hbits;
-	if (!XDL_CALLOC_ARRAY(rhash, hsize))
-		goto abort;
-
-	nrec = 0;
+	xdf->nrec = 0;
 	if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
 		for (top = blk + bsize; cur < top; ) {
 			prev = cur;
 			hav = xdl_hash_record(&cur, top, xpp->flags);
-			if (XDL_ALLOC_GROW(recs, nrec + 1, narec))
+			if (XDL_ALLOC_GROW(xdf->recs, xdf->nrec + 1, narec))
 				goto abort;
-			if (!(crec = xdl_cha_alloc(&xdf->rcha)))
-				goto abort;
+			crec = &xdf->recs[xdf->nrec++];
 			crec->ptr = prev;
 			crec->size = (long) (cur - prev);
 			crec->ha = hav;
-			recs[nrec++] = crec;
-			if (xdl_classify_record(pass, cf, rhash, hbits, crec) < 0)
+			if (xdl_classify_record(pass, cf, crec) < 0)
 				goto abort;
 		}
 	}
 
-	if (!XDL_CALLOC_ARRAY(rchg, nrec + 2))
+	if (!XDL_CALLOC_ARRAY(xdf->changed, xdf->nrec + 2))
 		goto abort;
 
 	if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
 	    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) {
-		if (!XDL_ALLOC_ARRAY(rindex, nrec + 1))
-			goto abort;
-		if (!XDL_ALLOC_ARRAY(ha, nrec + 1))
+		if (!XDL_ALLOC_ARRAY(xdf->rindex, xdf->nrec + 1))
 			goto abort;
 	}
 
-	xdf->nrec = nrec;
-	xdf->recs = recs;
-	xdf->hbits = hbits;
-	xdf->rhash = rhash;
-	xdf->rchg = rchg + 1;
-	xdf->rindex = rindex;
+	xdf->changed += 1;
 	xdf->nreff = 0;
-	xdf->ha = ha;
 	xdf->dstart = 0;
-	xdf->dend = nrec - 1;
+	xdf->dend = xdf->nrec - 1;
 
 	return 0;
 
 abort:
-	xdl_free(ha);
-	xdl_free(rindex);
-	xdl_free(rchg);
-	xdl_free(rhash);
-	xdl_free(recs);
-	xdl_cha_free(&xdf->rcha);
+	xdl_free_ctx(xdf);
 	return -1;
 }
 
 
-static void xdl_free_ctx(xdfile_t *xdf) {
+void xdl_free_env(xdfenv_t *xe) {
 
-	xdl_free(xdf->rhash);
-	xdl_free(xdf->rindex);
-	xdl_free(xdf->rchg - 1);
-	xdl_free(xdf->ha);
-	xdl_free(xdf->recs);
-	xdl_cha_free(&xdf->rcha);
+	xdl_free_ctx(&xe->xdf2);
+	xdl_free_ctx(&xe->xdf1);
 }
 
 
+static bool xdl_clean_mmatch(uint8_t const *action, long i, long s, long e) {
+	long r, rdis0, rpdis0, rdis1, rpdis1;
+
+	/*
+	 * Limits the window that is examined during the similar-lines
+	 * scan. The loops below stops when action[i - r] == KEEP
+	 * (line that has no match), but there are corner cases where
+	 * the loop proceed all the way to the extremities by causing
+	 * huge performance penalties in case of big files.
+	 */
+	if (i - s > XDL_SIMSCAN_WINDOW)
+		s = i - XDL_SIMSCAN_WINDOW;
+	if (e - i > XDL_SIMSCAN_WINDOW)
+		e = i + XDL_SIMSCAN_WINDOW;
+
+	/*
+	 * Scans the lines before 'i' to find a run of lines that either
+	 * have no match (action[j] == DISCARD) or have multiple matches
+	 * (action[j] == INVESTIGATE). Note that we always call this
+	 * function with action[i] == INVESTIGATE, so the current line
+	 * (i) is already a multimatch line.
+	 */
+	for (r = 1, rdis0 = 0, rpdis0 = 1; (i - r) >= s; r++) {
+		if (action[i - r] == DISCARD)
+			rdis0++;
+		else if (action[i - r] == INVESTIGATE)
+			rpdis0++;
+		else if (action[i - r] == KEEP)
+			break;
+		else
+			BUG("Illegal value for action[i - r]");
+	}
+	/*
+	 * If the run before the line 'i' found only multimatch lines,
+	 * we return false and hence we don't make the current line (i)
+	 * discarded. We want to discard multimatch lines only when
+	 * they appear in the middle of runs with nomatch lines
+	 * (action[j] == DISCARD).
+	 */
+	if (rdis0 == 0)
+		return 0;
+	for (r = 1, rdis1 = 0, rpdis1 = 1; (i + r) <= e; r++) {
+		if (action[i + r] == DISCARD)
+			rdis1++;
+		else if (action[i + r] == INVESTIGATE)
+			rpdis1++;
+		else if (action[i + r] == KEEP)
+			break;
+		else
+			BUG("Illegal value for action[i + r]");
+	}
+	/*
+	 * If the run after the line 'i' found only multimatch lines,
+	 * we return false and hence we don't make the current line (i)
+	 * discarded.
+	 */
+	if (rdis1 == 0)
+		return false;
+	rdis1 += rdis0;
+	rpdis1 += rpdis0;
+
+	return rpdis1 * XDL_KPDIS_RUN < (rpdis1 + rdis1);
+}
+
+
+/*
+ * Try to reduce the problem complexity, discard records that have no
+ * matches on the other file. Also, lines that have multiple matches
+ * 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;
+	xrecord_t *recs;
+	xdlclass_t *rcrec;
+	uint8_t *action1 = NULL, *action2 = NULL;
+	bool need_min = !!(cf->flags & XDF_NEED_MINIMAL);
+	int ret = 0;
+
+	/*
+	 * Create temporary arrays that will help us decide if
+	 * changed[i] should remain false, or become true.
+	 */
+	if (!XDL_CALLOC_ARRAY(action1, xdf1->nrec + 1)) {
+		ret = -1;
+		goto cleanup;
+	}
+	if (!XDL_CALLOC_ARRAY(action2, xdf2->nrec + 1)) {
+		ret = -1;
+		goto cleanup;
+	}
+
+	/*
+	 * Initialize temporary arrays with DISCARD, KEEP, or INVESTIGATE.
+	 */
+	if ((mlim = xdl_bogosqrt(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];
+		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)
+		mlim = XDL_MAX_EQLIMIT;
+	for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {
+		rcrec = cf->rcrecs[recs->ha];
+		nm = rcrec ? rcrec->len1 : 0;
+		action2[i] = (nm == 0) ? DISCARD: (nm >= mlim && !need_min) ? INVESTIGATE: KEEP;
+	}
+
+	/*
+	 * 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];
+	     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;
+			/* 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];
+	     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;
+			/* changed[i] remains false, i.e. keep */
+		} else
+			xdf2->changed[i] = true;
+			/* i.e. discard */
+	}
+	xdf2->nreff = nreff;
+
+cleanup:
+	xdl_free(action1);
+	xdl_free(action2);
+
+	return ret;
+}
+
+
+/*
+ * Early trim initial and terminal matching records.
+ */
+static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
+	long i, lim;
+	xrecord_t *recs1, *recs2;
+
+	recs1 = xdf1->recs;
+	recs2 = xdf2->recs;
+	for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim;
+	     i++, recs1++, recs2++)
+		if (recs1->ha != recs2->ha)
+			break;
+
+	xdf1->dstart = xdf2->dstart = i;
+
+	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)
+			break;
+
+	xdf1->dend = xdf1->nrec - i - 1;
+	xdf2->dend = xdf2->nrec - i - 1;
+
+	return 0;
+}
+
+
+static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
+
+	if (xdl_trim_ends(xdf1, xdf2) < 0 ||
+	    xdl_cleanup_records(cf, xdf1, xdf2) < 0) {
+
+		return -1;
+	}
+
+	return 0;
+}
+
 int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
 		    xdfenv_t *xe) {
 	long enl1, enl2, sample;
@@ -291,172 +428,3 @@ int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
 
 	return 0;
 }
-
-
-void xdl_free_env(xdfenv_t *xe) {
-
-	xdl_free_ctx(&xe->xdf2);
-	xdl_free_ctx(&xe->xdf1);
-}
-
-
-static int xdl_clean_mmatch(char const *dis, long i, long s, long e) {
-	long r, rdis0, rpdis0, rdis1, rpdis1;
-
-	/*
-	 * Limits the window the is examined during the similar-lines
-	 * scan. The loops below stops when dis[i - r] == 1 (line that
-	 * has no match), but there are corner cases where the loop
-	 * proceed all the way to the extremities by causing huge
-	 * performance penalties in case of big files.
-	 */
-	if (i - s > XDL_SIMSCAN_WINDOW)
-		s = i - XDL_SIMSCAN_WINDOW;
-	if (e - i > XDL_SIMSCAN_WINDOW)
-		e = i + XDL_SIMSCAN_WINDOW;
-
-	/*
-	 * Scans the lines before 'i' to find a run of lines that either
-	 * have no match (dis[j] == 0) or have multiple matches (dis[j] > 1).
-	 * Note that we always call this function with dis[i] > 1, so the
-	 * current line (i) is already a multimatch line.
-	 */
-	for (r = 1, rdis0 = 0, rpdis0 = 1; (i - r) >= s; r++) {
-		if (!dis[i - r])
-			rdis0++;
-		else if (dis[i - r] == 2)
-			rpdis0++;
-		else
-			break;
-	}
-	/*
-	 * If the run before the line 'i' found only multimatch lines, we
-	 * return 0 and hence we don't make the current line (i) discarded.
-	 * We want to discard multimatch lines only when they appear in the
-	 * middle of runs with nomatch lines (dis[j] == 0).
-	 */
-	if (rdis0 == 0)
-		return 0;
-	for (r = 1, rdis1 = 0, rpdis1 = 1; (i + r) <= e; r++) {
-		if (!dis[i + r])
-			rdis1++;
-		else if (dis[i + r] == 2)
-			rpdis1++;
-		else
-			break;
-	}
-	/*
-	 * If the run after the line 'i' found only multimatch lines, we
-	 * return 0 and hence we don't make the current line (i) discarded.
-	 */
-	if (rdis1 == 0)
-		return 0;
-	rdis1 += rdis0;
-	rpdis1 += rpdis0;
-
-	return rpdis1 * XDL_KPDIS_RUN < (rpdis1 + rdis1);
-}
-
-
-/*
- * Try to reduce the problem complexity, discard records that have no
- * matches on the other file. Also, lines that have multiple matches
- * might be potentially discarded if they happear in a run of discardable.
- */
-static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
-	long i, nm, nreff, mlim;
-	xrecord_t **recs;
-	xdlclass_t *rcrec;
-	char *dis, *dis1, *dis2;
-	int need_min = !!(cf->flags & XDF_NEED_MINIMAL);
-
-	if (!XDL_CALLOC_ARRAY(dis, xdf1->nrec + xdf2->nrec + 2))
-		return -1;
-	dis1 = dis;
-	dis2 = dis1 + xdf1->nrec + 1;
-
-	if ((mlim = xdl_bogosqrt(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];
-		nm = rcrec ? rcrec->len2 : 0;
-		dis1[i] = (nm == 0) ? 0: (nm >= mlim && !need_min) ? 2: 1;
-	}
-
-	if ((mlim = xdl_bogosqrt(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];
-		nm = rcrec ? rcrec->len1 : 0;
-		dis2[i] = (nm == 0) ? 0: (nm >= mlim && !need_min) ? 2: 1;
-	}
-
-	for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart];
-	     i <= xdf1->dend; i++, recs++) {
-		if (dis1[i] == 1 ||
-		    (dis1[i] == 2 && !xdl_clean_mmatch(dis1, i, xdf1->dstart, xdf1->dend))) {
-			xdf1->rindex[nreff] = i;
-			xdf1->ha[nreff] = (*recs)->ha;
-			nreff++;
-		} else
-			xdf1->rchg[i] = 1;
-	}
-	xdf1->nreff = nreff;
-
-	for (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart];
-	     i <= xdf2->dend; i++, recs++) {
-		if (dis2[i] == 1 ||
-		    (dis2[i] == 2 && !xdl_clean_mmatch(dis2, i, xdf2->dstart, xdf2->dend))) {
-			xdf2->rindex[nreff] = i;
-			xdf2->ha[nreff] = (*recs)->ha;
-			nreff++;
-		} else
-			xdf2->rchg[i] = 1;
-	}
-	xdf2->nreff = nreff;
-
-	xdl_free(dis);
-
-	return 0;
-}
-
-
-/*
- * Early trim initial and terminal matching records.
- */
-static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
-	long i, lim;
-	xrecord_t **recs1, **recs2;
-
-	recs1 = xdf1->recs;
-	recs2 = xdf2->recs;
-	for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim;
-	     i++, recs1++, recs2++)
-		if ((*recs1)->ha != (*recs2)->ha)
-			break;
-
-	xdf1->dstart = xdf2->dstart = i;
-
-	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)
-			break;
-
-	xdf1->dend = xdf1->nrec - i - 1;
-	xdf2->dend = xdf2->nrec - i - 1;
-
-	return 0;
-}
-
-
-static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
-
-	if (xdl_trim_ends(xdf1, xdf2) < 0 ||
-	    xdl_cleanup_records(cf, xdf1, xdf2) < 0) {
-
-		return -1;
-	}
-
-	return 0;
-}
diff --git a/xdiff/xtypes.h b/xdiff/xtypes.h
index 8442bd4..f145abb 100644
--- a/xdiff/xtypes.h
+++ b/xdiff/xtypes.h
@@ -39,23 +39,18 @@ typedef struct s_chastore {
 } chastore_t;
 
 typedef struct s_xrecord {
-	struct s_xrecord *next;
 	char const *ptr;
 	long size;
 	unsigned long ha;
 } xrecord_t;
 
 typedef struct s_xdfile {
-	chastore_t rcha;
+	xrecord_t *recs;
 	long nrec;
-	unsigned int hbits;
-	xrecord_t **rhash;
 	long dstart, dend;
-	xrecord_t **recs;
-	char *rchg;
+	bool *changed;
 	long *rindex;
 	long nreff;
-	unsigned long *ha;
 } xdfile_t;
 
 typedef struct s_xdfenv {
diff --git a/xdiff/xutils.c b/xdiff/xutils.c
index 78d1cf7..447e66c 100644
--- a/xdiff/xutils.c
+++ b/xdiff/xutils.c
@@ -464,17 +464,17 @@ int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
 	mmfile_t subfile1, subfile2;
 	xdfenv_t env;
 
-	subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr;
-	subfile1.size = 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 +
-		diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr;
+	subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1].ptr;
+	subfile1.size = 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 +
+		diff_env->xdf2.recs[line2 + count2 - 2].size - subfile2.ptr;
 	if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0)
 		return -1;
 
-	memcpy(diff_env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1);
-	memcpy(diff_env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2);
+	memcpy(diff_env->xdf1.changed + line1 - 1, env.xdf1.changed, count1);
+	memcpy(diff_env->xdf2.changed + line2 - 1, env.xdf2.changed, count2);
 
 	xdl_free_env(&env);