Merge branch 'js/mingw-includes-cleanup'

Code clean-up.

* js/mingw-includes-cleanup:
  mingw: order `#include`s alphabetically
  mingw: avoid relative `#include`s
diff --git a/.clang-format b/.clang-format
index dcfd0aa..86b4fe3 100644
--- a/.clang-format
+++ b/.clang-format
@@ -149,7 +149,7 @@
 #     f();
 #   }
 # }
-SpaceBeforeParens: ControlStatements
+SpaceBeforeParens: ControlStatementsExceptControlMacros
 
 # Don't insert spaces inside empty '()'
 SpaceInEmptyParentheses: false
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index d122e79..393ea4d 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -379,6 +379,8 @@
         - jobname: linux-breaking-changes
           cc: gcc
           image: ubuntu:rolling
+        - jobname: fedora-breaking-changes-meson
+          image: fedora:latest
         - jobname: linux-leaks
           image: ubuntu:rolling
           cc: gcc
@@ -396,8 +398,6 @@
         # Supported until 2025-04-02.
         - jobname: linux32
           image: i386/ubuntu:focal
-        - jobname: pedantic
-          image: fedora:latest
         # A RHEL 8 compatible distro.  Supported until 2029-05-31.
         - jobname: almalinux-8
           image: almalinux:8
diff --git a/.gitignore b/.gitignore
index 04c4444..78a45cb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
 /fuzz_corpora
+/target/
+/Cargo.lock
 /GIT-BUILD-DIR
 /GIT-BUILD-OPTIONS
 /GIT-CFLAGS
@@ -87,6 +89,7 @@
 /git-init-db
 /git-interpret-trailers
 /git-instaweb
+/git-last-modified
 /git-log
 /git-ls-files
 /git-ls-remote
@@ -139,6 +142,7 @@
 /git-repack
 /git-replace
 /git-replay
+/git-repo
 /git-request-pull
 /git-rerere
 /git-reset
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index af10ebb..f7d57d1 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -45,6 +45,8 @@
       - jobname: linux-breaking-changes
         image: ubuntu:20.04
         CC: gcc
+      - jobname: fedora-breaking-changes-meson
+        image: fedora:latest
       - jobname: linux-TEST-vars
         image: ubuntu:20.04
         CC: gcc
@@ -58,8 +60,6 @@
       - jobname: linux-asan-ubsan
         image: ubuntu:rolling
         CC: clang
-      - jobname: pedantic
-        image: fedora:latest
       - jobname: linux-musl-meson
         image: alpine:latest
       - jobname: linux32
@@ -70,6 +70,8 @@
   artifacts:
     paths:
       - t/failed-test-artifacts
+    reports:
+      junit: build/meson-logs/testlog.junit.xml
     when: on_failure
 
 test:osx:
@@ -110,8 +112,16 @@
   artifacts:
     paths:
       - t/failed-test-artifacts
+    reports:
+      junit: build/meson-logs/testlog.junit.xml
     when: on_failure
 
+.windows_before_script: &windows_before_script
+  # Disabling realtime monitoring fails on some of the runners, but it
+  # significantly speeds up test execution in the case where it works. We thus
+  # try our luck, but ignore any failures.
+  - Set-MpPreference -DisableRealtimeMonitoring $true; $true
+
 build:mingw64:
   stage: build
   tags:
@@ -119,6 +129,7 @@
   variables:
     NO_PERL: 1
   before_script:
+    - *windows_before_script
     - ./ci/install-sdk.ps1 -directory "git-sdk"
   script:
     - git-sdk/usr/bin/bash.exe -l -c 'ci/make-test-artifacts.sh artifacts'
@@ -135,6 +146,7 @@
     - job: "build:mingw64"
       artifacts: true
   before_script:
+    - *windows_before_script
     - git-sdk/usr/bin/bash.exe -l -c 'tar xf artifacts/artifacts.tar.gz'
     - New-Item -Path .git/info -ItemType Directory
     - New-Item .git/info/exclude -ItemType File -Value "/git-sdk"
@@ -148,17 +160,10 @@
   tags:
     - saas-windows-medium-amd64
   before_script:
-    - choco install -y git meson ninja openssl
+    - *windows_before_script
+    - choco install -y git meson ninja
     - Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1
     - refreshenv
-    # The certificate store for Python on Windows is broken and fails to fetch
-    # certificates, see https://bugs.python.org/issue36011. This seems to
-    # mostly be an issue with how the GitLab image is set up as it is a
-    # non-issue on GitHub Actions. Work around the issue by importing
-    # cetrificates manually.
-    - Invoke-WebRequest https://curl.haxx.se/ca/cacert.pem -OutFile cacert.pem
-    - openssl pkcs12 -export -nokeys -in cacert.pem -out certs.pfx -passout "pass:"
-    - Import-PfxCertificate -CertStoreLocation Cert:\LocalMachine\Root -FilePath certs.pfx
 
 build:msvc-meson:
   extends: .msvc-meson
@@ -180,6 +185,9 @@
   script:
     - meson test -C build --no-rebuild --print-errorlogs --slice $Env:CI_NODE_INDEX/$Env:CI_NODE_TOTAL
   parallel: 10
+  artifacts:
+    reports:
+      junit: build/meson-logs/testlog.junit.xml
 
 test:fuzz-smoke-tests:
   image: ubuntu:latest
diff --git a/.mailmap b/.mailmap
index 96c2740..7b31981 100644
--- a/.mailmap
+++ b/.mailmap
@@ -81,6 +81,8 @@
 Frédéric Heitzmann <frederic.heitzmann@gmail.com>
 Garry Dolley <gdolley@ucla.edu> <gdolley@arpnetworks.com>
 Glen Choo <glencbz@gmail.com> <chooglen@google.com>
+Greg Hurrell <greg@hurrell.net> <greg.hurrell@datadoghq.com>
+Greg Hurrell <greg@hurrell.net> <win@wincent.com>
 Greg Price <price@mit.edu> <price@MIT.EDU>
 Greg Price <price@mit.edu> <price@ksplice.com>
 Heiko Voigt <hvoigt@hvoigt.net> <git-list@hvoigt.net>
@@ -124,6 +126,7 @@
 Jon Seymour <jon.seymour@gmail.com> <jon@blackcubes.dyndns.org>
 Jonathan Nieder <jrnieder@gmail.com> <jrnieder@uchicago.edu>
 Jonathan del Strother <jon.delStrother@bestbefore.tv> <maillist@steelskies.com>
+Jonathan Tan <jonathantanmy@fastmail.com> <jonathantanmy@google.com>
 Josh Triplett <josh@joshtriplett.org> <josh@freedesktop.org>
 Josh Triplett <josh@joshtriplett.org> <josht@us.ibm.com>
 Julian Phillips <julian@quantumfyre.co.uk> <jp3@quantumfyre.co.uk>
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..45c9b349
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "gitcore"
+version = "0.1.0"
+edition = "2018"
+
+[lib]
+crate-type = ["staticlib"]
+
+[dependencies]
diff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc
index f8d2eba..90b53ab 100644
--- a/Documentation/BreakingChanges.adoc
+++ b/Documentation/BreakingChanges.adoc
@@ -165,6 +165,57 @@
 "reftable" format. Most importantly, alternative implementations of Git like
 JGit, libgit2 and Gitoxide need to support it.
 
+* In new repositories, the default branch name will be `main`. We have been
+  warning that the default name will change since 675704c74dd (init:
+  provide useful advice about init.defaultBranch, 2020-12-11).  The new name
+  matches the default branch name used in new repositories by many of the
+  big Git forges.
+
+* Git will require Rust as a mandatory part of the build process. While Git
+  already started to adopt Rust in Git 2.49, all parts written in Rust are
+  optional for the time being. This includes:
++
+  ** The Rust wrapper around libgit.a that is part of "contrib/" and which has
+     been introduced in Git 2.49.
+  ** Subsystems that have an alternative implementation in Rust to test
+     interoperability between our C and Rust codebase.
+  ** Newly written features that are not mission critical for a fully functional
+     Git client.
++
+These changes are meant as test balloons to allow distributors of Git to prepare
+for Rust becoming a mandatory part of the build process. There will be multiple
+milestones for the introduction of Rust:
++
+--
+1. Initially, with Git 2.52, support for Rust will be auto-detected by Meson and
+   disabled in our Makefile so that the project can sort out the initial
+   infrastructure.
+2. In Git 2.53, both build systems will default-enable support for Rust.
+   Consequently, builds will break by default if Rust is not available on the
+   build host. The use of Rust can still be explicitly disabled via build
+   flags.
+3. In Git 3.0, the build options will be removed and support for Rust is
+   mandatory.
+--
++
+You can explicitly ask both Meson and our Makefile-based system to enable Rust
+by saying `meson configure -Drust=enabled` and `make WITH_RUST=YesPlease`,
+respectively.
++
+The Git project will declare the last version before Git 3.0 to be a long-term
+support release. This long-term release will receive important bug fixes for at
+least four release cycles and security fixes for six release cycles. The Git
+project will hand over maintainership of the long-term release to distributors
+in case they need to extend the life of that long-term release even further.
+Details of how this long-term release will be handed over to the community will
+be discussed once the Git project decides to stop officially supporting it.
++
+We will evaluate the impact on downstream distributions before making Rust
+mandatory in Git 3.0. If we see that the impact on downstream distributions
+would be significant, we may decide to defer this change to a subsequent minor
+release. This evaluation will also take into account our own experience with
+how painful it is to keep Rust an optional component.
+
 === Removals
 
 * Support for grafting commits has long been superseded by git-replace(1).
@@ -235,10 +286,15 @@
   equivalent `git log --raw`.  We have nominated the command for
   removal, have changed the command to refuse to work unless the
   `--i-still-use-this` option is given, and asked the users to report
-  when they do so.  So far there hasn't been a single complaint.
+  when they do so.
 +
 The command will be removed.
 
+* Support for `core.commentString=auto` has been deprecated and will
+  be removed in Git 3.0.
++
+cf. <xmqqa59i45wc.fsf@gitster.g>
+
 == Superseded features that will not be deprecated
 
 Some features have gained newer replacements that aim to improve the design in
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
index 224f097..df72fe0 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -650,6 +650,12 @@
    cases. However, it is recommended to find a more descriptive name wherever
    possible to improve the readability and maintainability of the code.
 
+ - Bit fields should be defined without a space around the colon. E.g.
+
+   unsigned my_field:1;
+   unsigned other_field:1;
+   unsigned field_with_longer_name:1;
+
 For Perl programs:
 
  - Most of the C guidelines above apply.
diff --git a/Documentation/Makefile b/Documentation/Makefile
index df2ce18..a3fbd29 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -119,18 +119,26 @@
 TECH_DOCS += technical/bitmap-format
 TECH_DOCS += technical/build-systems
 TECH_DOCS += technical/bundle-uri
+TECH_DOCS += technical/commit-graph
+TECH_DOCS += technical/directory-rename-detection
 TECH_DOCS += technical/hash-function-transition
 TECH_DOCS += technical/long-running-process-protocol
 TECH_DOCS += technical/multi-pack-index
+TECH_DOCS += technical/packfile-uri
 TECH_DOCS += technical/pack-heuristics
 TECH_DOCS += technical/parallel-checkout
 TECH_DOCS += technical/partial-clone
 TECH_DOCS += technical/platform-support
 TECH_DOCS += technical/racy-git
 TECH_DOCS += technical/reftable
+TECH_DOCS += technical/remembering-renames
+TECH_DOCS += technical/repository-version
+TECH_DOCS += technical/rerere
 TECH_DOCS += technical/scalar
 TECH_DOCS += technical/send-pack-pipeline
 TECH_DOCS += technical/shallow
+TECH_DOCS += technical/sparse-checkout
+TECH_DOCS += technical/sparse-index
 TECH_DOCS += technical/trivial-merge
 TECH_DOCS += technical/unit-tests
 SP_ARTICLES += $(TECH_DOCS)
@@ -497,9 +505,26 @@
 	$(call mkdir_p_parent_template)
 	$(QUIET_GEN)$(PERL_PATH) lint-fsck-msgids.perl \
 		../fsck.h fsck-msgids.adoc $@
-
 lint-docs-fsck-msgids: $(LINT_DOCS_FSCK_MSGIDS)
 
+## Lint: delimited sections
+LINT_DOCS_DELIMITED_SECTIONS = $(patsubst %.adoc,.build/lint-docs/delimited-sections/%.ok,$(MAN_TXT))
+$(LINT_DOCS_DELIMITED_SECTIONS): lint-delimited-sections.perl
+$(LINT_DOCS_DELIMITED_SECTIONS): .build/lint-docs/delimited-sections/%.ok: %.adoc
+	$(call mkdir_p_parent_template)
+	$(QUIET_LINT_DELIMSEC)$(PERL_PATH) lint-delimited-sections.perl $< >$@
+.PHONY: lint-docs-delimited-sections
+lint-docs-delimited-sections: $(LINT_DOCS_DELIMITED_SECTIONS)
+
+## Lint: Documentation style
+LINT_DOCS_DOC_STYLE = $(patsubst %.adoc,.build/lint-docs/doc-style/%.ok,$(DOC_DEP_TXT))
+$(LINT_DOCS_DOC_STYLE): lint-documentation-style.perl
+$(LINT_DOCS_DOC_STYLE): .build/lint-docs/doc-style/%.ok: %.adoc
+	$(call mkdir_p_parent_template)
+	$(QUIET_LINT_DOCSTYLE)$(PERL_PATH) lint-documentation-style.perl $< >$@
+.PHONY: lint-docs-doc-style
+lint-docs-doc-style: $(LINT_DOCS_DOC_STYLE)
+
 lint-docs-manpages:
 	$(QUIET_GEN)./lint-manpages.sh
 
@@ -528,6 +553,8 @@
 lint-docs: lint-docs-gitlink
 lint-docs: lint-docs-man-end-blurb
 lint-docs: lint-docs-man-section-order
+lint-docs: lint-docs-delimited-sections
+lint-docs: lint-docs-doc-style
 lint-docs: lint-docs-manpages
 lint-docs: lint-docs-meson
 
diff --git a/Documentation/MyFirstContribution.adoc b/Documentation/MyFirstContribution.adoc
index aca7212..02ba8ba 100644
--- a/Documentation/MyFirstContribution.adoc
+++ b/Documentation/MyFirstContribution.adoc
@@ -52,6 +52,15 @@
 can be answered if you disconnect and so that others can learn from the
 conversation.
 
+==== https://discord.gg/GRFVkzgxRd[#discord] on Discord
+This is an unofficial Git Discord server for everyone, from people just
+starting out with Git to those who develop it. It's a great place to ask
+questions, share tips, and connect with the broader Git community in real time.
+
+The server has channels for general discussions and specific channels for those
+who use Git and those who develop it. The server's search functionality also
+allows you to find previous conversations and answers to common questions.
+
 [[getting-started]]
 == Getting Started
 
@@ -908,10 +917,13 @@
 === Sending a PR to GitGitGadget
 
 In order to have your code tested and formatted for review, you need to start by
-opening a Pull Request against `gitgitgadget/git`. Head to
-https://github.com/gitgitgadget/git and open a PR either with the "New pull
-request" button or the convenient "Compare & pull request" button that may
-appear with the name of your newly pushed branch.
+opening a Pull Request against either `gitgitgadget/git` or `git/git`. Head to
+https://github.com/gitgitgadget/git or https://github.com/git/git and open a PR
+either with the "New pull request" button or the convenient "Compare & pull
+request" button that may appear with the name of your newly pushed branch.
+
+The differences between using `gitgitgadget/git` and `git/git` as your base can
+be found [here](https://gitgitgadget.github.io/#should-i-use-gitgitgadget-on-gitgitgadgets-git-fork-or-on-gits-github-mirror)
 
 Review the PR's title and description, as they're used by GitGitGadget
 respectively as the subject and body of the cover letter for your change. Refer
diff --git a/Documentation/RelNotes/1.6.2.4.adoc b/Documentation/RelNotes/1.6.2.4.adoc
index f4bf1d0..053dbb6 100644
--- a/Documentation/RelNotes/1.6.2.4.adoc
+++ b/Documentation/RelNotes/1.6.2.4.adoc
@@ -37,3 +37,4 @@
 echo O=$(git describe maint)
 O=v1.6.2.3-38-g318b847
 git shortlog --no-merges $O..maint
+---
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.52.0.adoc b/Documentation/RelNotes/2.52.0.adoc
new file mode 100644
index 0000000..ef5f91f
--- /dev/null
+++ b/Documentation/RelNotes/2.52.0.adoc
@@ -0,0 +1,378 @@
+Git v2.52 Release Notes
+=======================
+
+UI, Workflows & Features
+------------------------
+
+ * The "list" subcommand of "git refs" acts as a front-end for
+   "git for-each-ref".
+
+ * "git cmd --help-all" now works outside repositories.
+
+ * "git diff-tree" learned "--max-depth" option.
+
+ * A new subcommand "git repo" gives users a way to grab various
+   repository characteristics.
+
+ * A new command "git last-modified" has been added to show the closest
+   ancestor commit that touched each path.
+
+ * "git refs exists" that works like "git show-ref --exists" has been
+   added.
+
+ * "repo info" learns a short-hand option "-z" that is the same as
+   "--format=nul", and learns to report the objects format used in the
+   repository.
+
+ * "core.commentChar=auto" that attempts to dynamically pick a
+   suitable comment character is non-workable, as it is too much
+   trouble to support for little benefit, and is marked as deprecated.
+
+ * "git send-email" learned to drive "git imap-send" to store already
+   sent e-mails in an IMAP folder.
+
+ * The "promisor-remote" capability mechanism has been updated to
+   allow the "partialCloneFilter" settings and the "token" value to be
+   communicated from the server side.
+
+ * Declare that "git init" that is not otherwise configured uses
+   'main' as the initial branch, not 'master', starting Git 3.0.
+
+ * Keep giving hint about the default initial branch name for users
+   who may be surprised after Git 3.0 switch-over.
+
+ * The stash.index configuration variable can be set to make "git stash
+   pop/apply" pretend that it was invoked with "--index".
+
+ * "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.
+
+
+Performance, Internal Implementation, Development Support etc.
+--------------------------------------------------------------
+
+ * string_list_split*() family of functions have been extended to
+   simplify common use cases.
+
+ * Arrays of strbuf is often a wrong data structure to use, and
+   strbuf_split*() family of functions that create them often have
+   better alternatives.  Update several code paths and replace
+   strbuf_split*().
+
+ * Revision traversal limited with pathspec, like "git log dir/*",
+   used to ignore changed-paths Bloom filter when the pathspec
+   contained wildcards; now they take advantage of the filter when
+   they can.
+
+ * Doc lint updates to encourage the newer and easier-to-use
+   `synopsis` format, with fixes to a handful of existing uses.
+
+ * Remove dependency on the_repository and other globals from the
+   commit-graph code, and other changes unrelated to de-globaling.
+
+ * Discord has been added to the first contribution documentation as
+   another way to ask for help.
+
+ * Inspired by Ezekiel's recent effort to showcase Rust interface, the
+   hash function implementation used to hash lines have been updated
+   to the one used for ELF symbol lookup by Glibc.
+
+ * Instead of scanning for the remaining items to see if there are
+   still commits to be explored in the queue, use khash to remember
+   which items are still on the queue (an unacceptable alternative is
+   to reserve one object flag bits).
+
+ * The bulk-checkin code used to depend on a file-scope static
+   singleton variable, which has been updated to pass an instance
+   throughout the callchain.
+
+ * The work to build on the bulk-checkin infrastructure to create many
+   objects at once in a transaction and to abstract it into the
+   generic object layer continues.
+
+ * CodingGuidelines now spells out how bitfields are to be written.
+
+ * 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
+   variant that frees the structure as well, making the callers simpler.
+
+ * "git range-diff" learned a way to limit the memory consumed by
+   O(N*N) cost matrix.
+
+ * Some places in the code confused a variable that is *not* a boolean
+   to enable color but is an enum that records what the user requested
+   to do about color.  A couple of bugs of this sort have been fixed,
+   while the code has been cleaned up to prevent similar bugs in the
+   future.
+
+ * The build procedure based on meson learned a target to only build
+   documentation, similar to "make doc".
+   (merge ff4ec8ded0 ps/meson-build-docs later to maint).
+
+ * 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.
+
+
+Fixes since v2.51
+-----------------
+
+Unless otherwise noted, all the changes in 2.51.X maintenance track,
+including security updates, are included in this release.
+
+ * 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
+   renames extremely inefficiently, which has been corrected.
+   (merge 16c4fa26b9 ps/remote-rename-fix later to maint).
+
+ * "git describe" has been optimized by using better data structure.
+   (merge 08bb69d70f rs/describe-with-prio-queue later to maint).
+
+ * "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.
+   (merge 621ce9c1c6 gh/git-jump-pathname-with-sp later to maint).
+
+ * "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.
+   (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",
+   "differences in lines that match these regular expressions are
+   ignored") did not work well with "--name-only" and friends.
+   (merge b55e6d36eb ly/diff-name-only-with-diff-from-content later to maint).
+
+ * 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.
+   (merge 0a15bb634c sg/line-log-merge-optim later to maint).
+
+ * 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.
+   (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
+   corrected.
+
+ * "git describe <blob>" misbehaves and/or crashes in some corner
+   cases, which has been taught to exit with failure gracefully.
+   (merge 7c10e48e81 jk/describe-blob later to maint).
+
+ * Manual page for "gitk" is updated with the current maintainer's
+   name.
+   (merge bcb20dda83 js/doc-gitk-history later to maint).
+
+ * 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 around this problem.
+   (merge 0eeacde50e da/cargo-serialize later to maint).
+
+ * "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
+   pathspec; the code is taught to expand the index on demand to avoid
+   this.
+   (merge 681f26bccc ds/ls-files-lazy-unsparse later to maint).
+
+ * 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
+   characters, which has been fixed.
+   (merge 8655908b9e jc/longer-disambiguation-fix later to maint).
+
+ * 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.
+
+ * Import a newer version of the clar unit testing framework.
+   (merge 93dbb6b3c5 ps/clar-updates later to maint).
+
+ * "git send-email --compose --reply-to=<address>" used to add
+   duplicated Reply-To: header, which made mailservers unhappy.  This
+   has been corrected.
+   (merge f448f65719 nb/send-email-no-dup-reply-to later to maint).
+
+ * "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()
+   API gained a wrapper to make it more ergonomic when using it with
+   strvec-manged argc/argv pairs.
+   (merge a04bc71725 jk/setup-revisions-freefix later to maint).
+
+ * 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.
+
+ * The use of "git config get" command to learn how ANSI color
+   sequence is for a particular type, e.g., "git config get
+   --type=color --default=reset no.such.thing", isn't very ergonomic.
+   (merge e4dabf4fd6 ps/config-get-color-fixes later to maint).
+
+ * 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 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 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.
+
+ * Handling of an empty subdirectory of .git/refs/ in the ref-files
+   backend has been corrected.
+
+ * Our CI script requires "sudo" that can be told to preserve
+   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.
+   (merge c0bec06cfe jk/diff-no-index-with-pathspec-fix later to maint).
+
+ * 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 399694384b kh/doc-patch-id-markup-fix 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/blame-options.adoc b/Documentation/blame-options.adoc
index 19ea187..1fb948f 100644
--- a/Documentation/blame-options.adoc
+++ b/Documentation/blame-options.adoc
@@ -75,7 +75,8 @@
 	iso format is used. For supported values, see the discussion
 	of the --date option at linkgit:git-log[1].
 
---[no-]progress::
+--progress::
+--no-progress::
 	Progress status is reported on the standard error stream
 	by default when it is attached to a terminal. This flag
 	enables progress reporting even if not attached to a
diff --git a/Documentation/config.adoc b/Documentation/config.adoc
index cc76925..62eebe7 100644
--- a/Documentation/config.adoc
+++ b/Documentation/config.adoc
@@ -114,8 +114,7 @@
 are:
 
 `gitdir`::
-
-	The data that follows the keyword `gitdir:` is used as a glob
+	The data that follows the keyword `gitdir` and a colon is used as a glob
 	pattern. If the location of the .git directory matches the
 	pattern, the include condition is met.
 +
@@ -148,7 +147,7 @@
 	case-insensitively (e.g. on case-insensitive file systems)
 
 `onbranch`::
-	The data that follows the keyword `onbranch:` is taken to be a
+	The data that follows the keyword `onbranch` and a colon is taken to be a
 	pattern with standard globbing wildcards and two additional
 	ones, `**/` and `/**`, that can match multiple path components.
 	If we are in a worktree where the name of the branch that is
@@ -161,8 +160,8 @@
 organized hierarchically and you would like to apply a configuration to
 all the branches in that hierarchy.
 
-`hasconfig:remote.*.url:`::
-	The data that follows this keyword is taken to
+`hasconfig:remote.*.url`::
+	The data that follows this keyword and a colon is taken to
 	be a pattern with standard globbing wildcards and two
 	additional ones, `**/` and `/**`, that can match multiple
 	components. The first time this keyword is seen, the rest of
@@ -358,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/alias.adoc b/Documentation/config/alias.adoc
index 2c5db0a..80ce17d 100644
--- a/Documentation/config/alias.adoc
+++ b/Documentation/config/alias.adoc
@@ -3,7 +3,8 @@
 	after defining `alias.last = cat-file commit HEAD`, the invocation
 	`git last` is equivalent to `git cat-file commit HEAD`. To avoid
 	confusion and troubles with script usage, aliases that
-	hide existing Git commands are ignored. Arguments are split by
+	hide existing Git commands are ignored except for deprecated
+	commands.  Arguments are split by
 	spaces, the usual shell quoting and escaping are supported.
 	A quote pair or a backslash can be used to quote them.
 +
@@ -38,6 +39,6 @@
 ** A convenient way to deal with this is to write your script
    operations in an inline function that is then called with any
    arguments from the command-line.  For example `alias.cmd = "!c() {
-   echo $1 | grep $2 ; }; c" will correctly execute the prior example.
+   echo $1 | grep $2 ; }; c"` will correctly execute the prior example.
 ** Setting `GIT_TRACE=1` can help you debug the command being run for
    your alias.
diff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc
index 3fbe83e..08739bb 100644
--- a/Documentation/config/core.adoc
+++ b/Documentation/config/core.adoc
@@ -531,9 +531,25 @@
 	commented, and removes them after the editor returns
 	(default '#').
 +
-If set to "auto", `git-commit` would select a character that is not
+ifndef::with-breaking-changes[]
+If set to "auto", `git-commit` will select a character that is not
 the beginning character of any line in existing commit messages.
+Support for this value is deprecated and will be removed in Git 3.0
+due to the following limitations:
 +
+--
+* It is incompatible with adding comments in a commit message
+  template. This includes the conflicts comments added to
+  the commit message by `cherry-pick`, `merge`, `rebase` and
+  `revert`.
+* It is incompatible with adding comments to the commit message
+  in the `prepare-commit-msg` hook.
+* It is incompatible with the `fixup` and `squash` commands when
+  rebasing,
+* It is not respected by `git notes`
+--
++
+endif::with-breaking-changes[]
 Note that these two variables are aliases of each other, and in modern
 versions of Git you are free to use a string (e.g., `//` or `⁑⁕⁑`) with
 `commentChar`. Versions of Git prior to v2.45.0 will ignore
diff --git a/Documentation/config/extensions.adoc b/Documentation/config/extensions.adoc
index 9e2f321..5324566 100644
--- a/Documentation/config/extensions.adoc
+++ b/Documentation/config/extensions.adoc
@@ -3,8 +3,7 @@
 	`core.repositoryFormatVersion` is not `1`. See
 	linkgit:gitrepository-layout[5].
 +
---
-compatObjectFormat::
+compatObjectFormat:::
 	Specify a compatibility hash algorithm to use.  The acceptable values
 	are `sha1` and `sha256`.  The value specified must be different from the
 	value of `extensions.objectFormat`.  This allows client level
@@ -14,19 +13,23 @@
 	compatObjectFormat.  As well as being able to use oids encoded in
 	compatObjectFormat in addition to oids encoded with objectFormat to
 	locally specify objects.
++
+Note that the functionality enabled by this extension is incomplete and subject
+to change.  It currently exists only to allow development and testing of
+the underlying feature and is not designed to be enabled by end users.
 
-noop::
+noop:::
 	This extension does not change git's behavior at all. It is useful only
 	for testing format-1 compatibility.
 +
 For historical reasons, this extension is respected regardless of the
 `core.repositoryFormatVersion` setting.
 
-noop-v1::
+noop-v1:::
 	This extension does not change git's behavior at all. It is useful only
 	for testing format-1 compatibility.
 
-objectFormat::
+objectFormat:::
 	Specify the hash algorithm to use.  The acceptable values are `sha1` and
 	`sha256`.  If not specified, `sha1` is assumed.
 +
@@ -34,7 +37,7 @@
 linkgit:git-clone[1].  Trying to change it after initialization will not
 work and will produce hard-to-diagnose issues.
 
-partialClone::
+partialClone:::
 	When enabled, indicates that the repo was created with a partial clone
 	(or later performed a partial fetch) and that the remote may have
 	omitted sending certain unwanted objects.  Such a remote is called a
@@ -46,30 +49,31 @@
 For historical reasons, this extension is respected regardless of the
 `core.repositoryFormatVersion` setting.
 
-preciousObjects::
+preciousObjects:::
 	If enabled, indicates that objects in the repository MUST NOT be deleted
 	(e.g., by `git-prune` or `git repack -d`).
 +
 For historical reasons, this extension is respected regardless of the
 `core.repositoryFormatVersion` setting.
 
-refStorage::
+refStorage:::
 	Specify the ref storage format to use. The acceptable values are:
 +
+--
 include::../ref-storage-format.adoc[]
-
+--
 +
 Note that this setting should only be set by linkgit:git-init[1] or
 linkgit:git-clone[1]. Trying to change it after initialization will not
 work and will produce hard-to-diagnose issues.
 
-relativeWorktrees::
+relativeWorktrees:::
 	If enabled, indicates at least one worktree has been linked with
 	relative paths. Automatically set if a worktree has been created or
 	repaired with either the `--relative-paths` option or with the
 	`worktree.useRelativePaths` config set to `true`.
 
-worktreeConfig::
+worktreeConfig:::
 	If enabled, then worktrees will load config settings from the
 	`$GIT_DIR/config.worktree` file in addition to the
 	`$GIT_COMMON_DIR/config` file. Note that `$GIT_COMMON_DIR` and
@@ -83,11 +87,12 @@
 certain values from the common config file to the main working tree's
 `config.worktree` file, if present:
 +
+--
 * `core.worktree` must be moved from `$GIT_COMMON_DIR/config` to
   `$GIT_COMMON_DIR/config.worktree`.
 * If `core.bare` is true, then it must be moved from `$GIT_COMMON_DIR/config`
   to `$GIT_COMMON_DIR/config.worktree`.
-
+--
 +
 It may also be beneficial to adjust the locations of `core.sparseCheckout`
 and `core.sparseCheckoutCone` depending on your desire for customizable
@@ -100,4 +105,3 @@
 +
 For historical reasons, this extension is respected regardless of the
 `core.repositoryFormatVersion` setting.
---
diff --git a/Documentation/config/log.adoc b/Documentation/config/log.adoc
index 16e00e8..f20cc25 100644
--- a/Documentation/config/log.adoc
+++ b/Documentation/config/log.adoc
@@ -23,14 +23,14 @@
 	Print out the ref names of any commits that are shown by the log
 	command. Possible values are:
 +
-----
+--
 `short`;; the ref name prefixes `refs/heads/`, `refs/tags/` and
 	`refs/remotes/` are not printed.
 `full`;; the full ref name (including prefix) are printed.
 `auto`;; if the output is going to a terminal,
 	the ref names are shown as if `short` were given, otherwise no ref
 	names are shown.
-----
+--
 +
 This is the same as the `--decorate` option of the `git log`.
 
diff --git a/Documentation/config/mergetool.adoc b/Documentation/config/mergetool.adoc
index 6be5061..7064f5a 100644
--- a/Documentation/config/mergetool.adoc
+++ b/Documentation/config/mergetool.adoc
@@ -65,7 +65,7 @@
 	During a merge, Git will automatically resolve as many conflicts as
 	possible and write the `$MERGED` file containing conflict markers around
 	any conflicts that it cannot resolve; `$LOCAL` and `$REMOTE` normally
-	are the versions of the file from before Git`s conflict
+	are the versions of the file from before Git's conflict
 	resolution. This flag causes `$LOCAL` and `$REMOTE` to be overwritten so
 	that only the unresolved conflicts are presented to the merge tool. Can
 	be configured per-tool via the `mergetool.<tool>.hideResolved`
diff --git a/Documentation/config/promisor.adoc b/Documentation/config/promisor.adoc
index 2638b01..93e5e0d 100644
--- a/Documentation/config/promisor.adoc
+++ b/Documentation/config/promisor.adoc
@@ -9,6 +9,28 @@
 	"false", which means the "promisor-remote" capability is not
 	advertised.
 
+promisor.sendFields::
+	A comma or space separated list of additional remote related
+	field names. A server sends these field names and the
+	associated field values from its configuration when
+	advertising its promisor remotes using the "promisor-remote"
+	capability, see linkgit:gitprotocol-v2[5]. Currently, only the
+	"partialCloneFilter" and "token" field names are supported.
++
+`partialCloneFilter`:: contains the partial clone filter
+used for the remote.
++
+`token`:: contains an authentication token for the remote.
++
+When a field name is part of this list and a corresponding
+"remote.foo.<field-name>" config variable is set on the server to a
+non-empty value, then the field name and value are sent when
+advertising the promisor remote "foo".
++
+This list has no effect unless the "promisor.advertise" config
+variable is set to "true", and the "name" and "url" fields are always
+advertised regardless of this setting.
+
 promisor.acceptFromServer::
 	If set to "all", a client will accept all the promisor remotes
 	a server might advertise using the "promisor-remote"
@@ -28,3 +50,42 @@
 	lazily fetchable from this promisor remote from its responses
 	to "fetch" and "clone" requests from the client. Name and URL
 	comparisons are case sensitive. See linkgit:gitprotocol-v2[5].
+
+promisor.checkFields::
+	A comma or space separated list of additional remote related
+	field names. A client checks if the values of these fields
+	transmitted by a server correspond to the values of these
+	fields in its own configuration before accepting a promisor
+	remote. Currently, "partialCloneFilter" and "token" are the
+	only supported field names.
++
+If one of these field names (e.g., "token") is being checked for an
+advertised promisor remote (e.g., "foo"), three conditions must be met
+for the check of this specific field to pass:
++
+1. The corresponding local configuration (e.g., `remote.foo.token`)
+   must be set.
+2. The server must advertise the "token" field for remote "foo".
+3. The value of the locally configured `remote.foo.token` must exactly
+   match the value advertised by the server for the "token" field.
++
+If any of these conditions is not met for any field name listed in
+`promisor.checkFields`, the advertised remote "foo" is rejected.
++
+For the "partialCloneFilter" field, this allows the client to ensure
+that the server's filter matches what it expects locally, preventing
+inconsistencies in filtering behavior. For the "token" field, this can
+be used to verify that authentication credentials match expected
+values.
++
+Field values are compared case-sensitively.
++
+The "name" and "url" fields are always checked according to the
+`promisor.acceptFromServer` policy, independently of this setting.
++
+The field names and values should be passed by the server through the
+"promisor-remote" capability by using the `promisor.sendFields` config
+variable. The fields are checked only if the
+`promisor.acceptFromServer` config variable is not set to "None". If
+set to "None", this config variable has no effect. See
+linkgit:gitprotocol-v2[5].
diff --git a/Documentation/config/sendemail.adoc b/Documentation/config/sendemail.adoc
index 4722334..90164c7 100644
--- a/Documentation/config/sendemail.adoc
+++ b/Documentation/config/sendemail.adoc
@@ -88,6 +88,8 @@
 sendemail.smtpServerPort::
 sendemail.smtpServerOption::
 sendemail.smtpUser::
+sendemail.imapSentFolder::
+sendemail.useImapOnly::
 sendemail.thread::
 sendemail.transferEncoding::
 sendemail.validate::
diff --git a/Documentation/config/stash.adoc b/Documentation/config/stash.adoc
index ec1edae..a1197ff 100644
--- a/Documentation/config/stash.adoc
+++ b/Documentation/config/stash.adoc
@@ -1,14 +1,32 @@
-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].
+ifndef::git-stash[]
+:see-show: See the description of the 'show' command in linkgit:git-stash[1].
+endif::git-stash[]
 
-stash.showPatch::
+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.
+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`::
+	If this is set to true, the `git stash show` command will show
+	the untracked files of a stash entry. Defaults to false. {see-show}
+
+`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 5e35c7d..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
-`extension.relativeWorktrees` config (see linkgit:git-config[1]),
+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/diff-format.adoc b/Documentation/diff-format.adoc
index 80e36e1..9f7e988 100644
--- a/Documentation/diff-format.adoc
+++ b/Documentation/diff-format.adoc
@@ -103,6 +103,7 @@
 followed by the name of the path in the merge commit.
 
 Examples for `-c` and `--cc` without `--combined-all-paths`:
+
 ------------------------------------------------
 ::100644 100644 100644 fabadb8 cc95eb0 4866510 MM	desc.c
 ::100755 100755 100755 52b7a2d 6d1ac04 d2ac7d7 RM	bar.sh
diff --git a/Documentation/diff-options.adoc b/Documentation/diff-options.adoc
index f3a35d8..ae31520 100644
--- a/Documentation/diff-options.adoc
+++ b/Documentation/diff-options.adoc
@@ -505,7 +505,8 @@
 	Turn off rename detection, even when the configuration
 	file gives the default to do so.
 
-`--[no-]rename-empty`::
+`--rename-empty`::
+`--no-rename-empty`::
 	Whether to use empty blobs as rename source.
 
 ifndef::git-format-patch[]
@@ -893,5 +894,33 @@
 	reverted with `--ita-visible-in-index`. Both options are
 	experimental and could be removed in future.
 
+--max-depth=<depth>::
+	For each pathspec given on command line, descend at most `<depth>`
+	levels of directories. A value of `-1` means no limit.
+	Cannot be combined with wildcards in the pathspec.
+	Given a tree containing `foo/bar/baz`, the following list shows the
+	matches generated by each set of options:
++
+--
+ - `--max-depth=0 -- foo`: `foo`
+
+ - `--max-depth=1 -- foo`: `foo/bar`
+
+ - `--max-depth=1 -- foo/bar`: `foo/bar/baz`
+
+ - `--max-depth=1 -- foo foo/bar`: `foo/bar/baz`
+
+ - `--max-depth=2 -- foo`: `foo/bar/baz`
+--
++
+If no pathspec is given, the depth is measured as if all
+top-level entries were specified. Note that this is different
+than measuring from the root, in that `--max-depth=0` would
+still return `foo`. This allows you to still limit depth while
+asking for a subset of the top-level entries.
++
+Note that this option is only supported for diffs between tree objects,
+not against the index or working tree.
+
 For more detailed explanation on these common options, see also
 linkgit:gitdiffcore[7].
diff --git a/Documentation/fetch-options.adoc b/Documentation/fetch-options.adoc
index b01372e..ad1e1f4 100644
--- a/Documentation/fetch-options.adoc
+++ b/Documentation/fetch-options.adoc
@@ -1,7 +1,8 @@
---[no-]all::
+--all::
+--no-all::
 	Fetch all remotes, except for the ones that has the
 	`remote.<name>.skipFetchAll` configuration variable set.
-	This overrides the configuration variable fetch.all`.
+	This overrides the configuration variable `fetch.all`.
 
 -a::
 --append::
@@ -88,7 +89,8 @@
 precedence over the `fetch.output` config option.
 
 ifndef::git-pull[]
---[no-]write-fetch-head::
+--write-fetch-head::
+--no-write-fetch-head::
 	Write the list of remote refs fetched in the `FETCH_HEAD`
 	file directly under `$GIT_DIR`.  This is the default.
 	Passing `--no-write-fetch-head` from the command line tells
@@ -118,13 +120,16 @@
 	Allow several <repository> and <group> arguments to be
 	specified. No <refspec>s may be specified.
 
---[no-]auto-maintenance::
---[no-]auto-gc::
+--auto-maintenance::
+--no-auto-maintenance::
+--auto-gc::
+--no-auto-gc::
 	Run `git maintenance run --auto` at the end to perform automatic
 	repository maintenance if needed. (`--[no-]auto-gc` is a synonym.)
 	This is enabled by default.
 
---[no-]write-commit-graph::
+--write-commit-graph::
+--no-write-commit-graph::
 	Write a commit-graph after fetching. This overrides the config
 	setting `fetch.writeCommitGraph`.
 endif::git-pull[]
diff --git a/Documentation/for-each-ref-options.adoc b/Documentation/for-each-ref-options.adoc
new file mode 100644
index 0000000..f13efb5
--- /dev/null
+++ b/Documentation/for-each-ref-options.adoc
@@ -0,0 +1,85 @@
+`<pattern>...`::
+	If one or more _<pattern>_ parameters are given, only refs are shown that
+	match against at least one pattern, either using `fnmatch`(3) or
+	literally, in the latter case matching completely or from the
+	beginning up to a slash.
+
+`--stdin`::
+	The list of patterns is read from standard input instead of from
+	the argument list.
+
+`--count=<count>`::
+	Stop after showing _<count>_ refs.
+
+`--sort=<key>`::
+	Sort on the field name _<key>_.  Prefix `-` to sort in
+	descending order of the value.  When unspecified,
+	`refname` is used.  You may use the `--sort=<key>` option
+	multiple times, in which case the last key becomes the primary
+	key.
+
+`--format[=<format>]`::
+	A string that interpolates `%(fieldname)` from a ref being shown and
+	the object it points at. In addition, the string literal `%%`
+	renders as `%` and `%xx` - where `xx` are hex digits - renders as
+	the character with hex code `xx`. For example, `%00` interpolates to
+	`\0` (_NUL_), `%09` to `\t` (_TAB_), and `%0a` to `\n` (_LF_).
+
+When unspecified, _<format>_ defaults to `%(objectname) SPC %(objecttype)
+TAB %(refname)`.
+
+`--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).
+
+`--shell`::
+`--perl`::
+`--python`::
+`--tcl`::
+	If given, strings that substitute `%(fieldname)`
+	placeholders are quoted as string literals suitable for
+	the specified host language.  This is meant to produce
+	a scriptlet that can directly be "eval"ed.
+
+`--points-at=<object>`::
+	Only list refs which points at the given object.
+
+`--merged[=<object>]`::
+	Only list refs whose tips are reachable from the
+	specified commit (`HEAD` if not specified).
+
+`--no-merged[=<object>]`::
+	Only list refs whose tips are not reachable from _<object>_(`HEAD` if not
+	specified).
+
+`--contains[=<object>]`::
+	Only list refs which contain _<object>_(`HEAD` if not specified).
+
+`--no-contains[=<object>]`::
+	Only list refs which don't contain _<object>_ (`HEAD`
+	if not specified).
+
+`--ignore-case`::
+	Sorting and filtering refs are case insensitive.
+
+`--omit-empty`::
+	Do not print a newline after formatted refs where the format expands
+	to the empty string.
+
+`--exclude=<excluded-pattern>`::
+	If one or more `--exclude` options are given, only refs which do not
+	match any _<excluded-pattern>_ parameters are shown. Matching is done
+	using the same rules as _<pattern>_ above.
+
+`--include-root-refs`::
+	List root refs (`HEAD` and pseudorefs) apart from regular refs.
+
+`--start-after=<marker>`::
+    Allows paginating the output by skipping references up to and including the
+    specified marker. When paging, it should be noted that references may be
+    deleted, modified or added between invocations. Output will only yield those
+    references which follow the marker lexicographically. Output begins from the
+    first reference that would come after the marker alphabetically. Cannot be
+    used with `--sort=<key>` or `--stdin` options, or the _<pattern>_ argument(s)
+    to limit the refs.
diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc
index 0ba4f9a..81f11ba 100644
--- a/Documentation/fsck-msgids.adoc
+++ b/Documentation/fsck-msgids.adoc
@@ -38,6 +38,9 @@
 `badReferentName`::
 	(ERROR) The referent name of a symref is invalid.
 
+`badReftableTableName`::
+	(WARN) A reftable table has an invalid name.
+
 `badTagName`::
 	(INFO) A tag has an invalid format.
 
@@ -104,9 +107,6 @@
 `gitmodulesParse`::
 	(INFO) Could not parse `.gitmodules` blob.
 
-`gitmodulesLarge`;
-	(ERROR) `.gitmodules` blob is too large to parse.
-
 `gitmodulesPath`::
 	(ERROR) `.gitmodules` path is invalid.
 
diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc
index b7a7358..3116a2c 100644
--- a/Documentation/git-add.adoc
+++ b/Documentation/git-add.adoc
@@ -16,18 +16,18 @@
 
 DESCRIPTION
 -----------
-This command updates the index using the current content found in
-the working tree, to prepare the content staged for the next commit.
-It typically adds the current content of existing paths as a whole,
-but with some options it can also be used to add content with
-only part of the changes made to the working tree files applied, or
-remove paths that do not exist in the working tree anymore.
+Add contents of new or changed files to the index. The "index" (also
+known as the "staging area") is what you use to prepare the contents of
+the next commit.
 
-The "index" holds a snapshot of the content of the working tree, and it
-is this snapshot that is taken as the contents of the next commit.  Thus
-after making any changes to the working tree, and before running
-the commit command, you must use the `add` command to add any new or
-modified files to the index.
+When you run `git commit` without any other arguments, it will only
+commit staged changes. For example, if you've edited `file.c` and want
+to commit your changes to that file, you can run:
+
+   git add file.c
+   git commit
+
+You can also add only part of your changes to a file with `git add -p`.
 
 This command can be performed multiple times before a commit.  It only
 adds the content of the specified file(s) at the time the add command is
@@ -37,12 +37,10 @@
 The `git status` command can be used to obtain a summary of which
 files have changes that are staged for the next commit.
 
-The `git add` command will not add ignored files by default.  If any
-ignored files were explicitly specified on the command line, `git add`
-will fail with a list of ignored files.  Ignored files reached by
-directory recursion or filename globbing performed by Git (quote your
-globs before the shell) will be silently ignored.  The `git add` command can
-be used to add ignored files with the `-f` (force) option.
+The `git add` command will not add ignored files by default. You can
+use the `--force` option to add ignored files. If you specify the exact
+filename of an ignored file, `git add` will fail with a list of ignored
+files. Otherwise it will silently ignore the file.
 
 Please see linkgit:git-commit[1] for alternative ways to add content to a
 commit.
@@ -344,10 +342,10 @@
        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
diff --git a/Documentation/git-am.adoc b/Documentation/git-am.adoc
index 221070d..b23b4fb 100644
--- a/Documentation/git-am.adoc
+++ b/Documentation/git-am.adoc
@@ -48,7 +48,8 @@
 --keep-non-patch::
 	Pass `-b` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
---[no-]keep-cr::
+--keep-cr::
+--no-keep-cr::
 	With `--keep-cr`, call 'git mailsplit' (see linkgit:git-mailsplit[1])
 	with the same option, to prevent it from stripping CR at the end of
 	lines. `am.keepcr` configuration variable can be used to specify the
diff --git a/Documentation/git-backfill.adoc b/Documentation/git-backfill.adoc
index 9562305..b8394dc 100644
--- a/Documentation/git-backfill.adoc
+++ b/Documentation/git-backfill.adoc
@@ -57,7 +57,8 @@
 	blobs seen at a given path. The default minimum batch size is
 	50,000.
 
-`--[no-]sparse`::
+`--sparse`::
+`--no-sparse`::
 	Only download objects if they appear at a path that matches the
 	current sparse-checkout. If the sparse-checkout feature is enabled,
 	then `--sparse` is assumed and can be disabled with `--no-sparse`.
diff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc
index 180d1ad..c139f55 100644
--- a/Documentation/git-cat-file.adoc
+++ b/Documentation/git-cat-file.adoc
@@ -62,8 +62,10 @@
 	or to ask for a "blob" with `<object>` being a tag object that
 	points at it.
 
---[no-]mailmap::
---[no-]use-mailmap::
+--mailmap::
+--no-mailmap::
+--use-mailmap::
+--no-use-mailmap::
        Use mailmap file to map author, committer and tagger names
        and email addresses to canonical real names and email addresses.
        See linkgit:git-shortlog[1].
diff --git a/Documentation/git-check-attr.adoc b/Documentation/git-check-attr.adoc
index 503b644..15a37a3 100644
--- a/Documentation/git-check-attr.adoc
+++ b/Documentation/git-check-attr.adoc
@@ -19,7 +19,8 @@
 
 OPTIONS
 -------
--a, --all::
+-a::
+--all::
 	List all attributes that are associated with the specified
 	paths.  If this option is used, then 'unspecified' attributes
 	will not be included in the output.
diff --git a/Documentation/git-check-ignore.adoc b/Documentation/git-check-ignore.adoc
index 3e3b4e3..a6c6c1b 100644
--- a/Documentation/git-check-ignore.adoc
+++ b/Documentation/git-check-ignore.adoc
@@ -25,11 +25,13 @@
 
 OPTIONS
 -------
--q, --quiet::
+-q::
+--quiet::
 	Don't output anything, just set exit status.  This is only
 	valid with a single pathname.
 
--v, --verbose::
+-v::
+--verbose::
 	Instead of printing the paths that are excluded, for each path
 	that matches an exclude pattern, print the exclude pattern
 	together with the path.  (Matching an exclude pattern usually
@@ -49,7 +51,8 @@
 	below).  If `--stdin` is also given, input paths are separated
 	with a NUL character instead of a linefeed character.
 
--n, --non-matching::
+-n::
+--non-matching::
 	Show given paths which don't match any pattern.  This only
 	makes sense when `--verbose` is enabled, otherwise it would
 	not be possible to distinguish between paths which match a
diff --git a/Documentation/git-check-ref-format.adoc b/Documentation/git-check-ref-format.adoc
index 2aacfd1..0c3abf9 100644
--- a/Documentation/git-check-ref-format.adoc
+++ b/Documentation/git-check-ref-format.adoc
@@ -98,7 +98,8 @@
 
 OPTIONS
 -------
---[no-]allow-onelevel::
+--allow-onelevel::
+--no-allow-onelevel::
 	Controls whether one-level refnames are accepted (i.e.,
 	refnames that do not contain multiple `/`-separated
 	components).  The default is `--no-allow-onelevel`.
diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc
index 40e02cf..431185c 100644
--- a/Documentation/git-checkout.adoc
+++ b/Documentation/git-checkout.adoc
@@ -12,25 +12,29 @@
 git checkout [-q] [-f] [-m] --detach [<branch>]
 git checkout [-q] [-f] [-m] [--detach] <commit>
 git checkout [-q] [-f] [-m] [[-b|-B|--orphan] <new-branch>] [<start-point>]
-git checkout [-f] <tree-ish> [--] <pathspec>...
-git checkout [-f] <tree-ish> --pathspec-from-file=<file> [--pathspec-file-nul]
+git checkout <tree-ish> [--] <pathspec>...
+git checkout <tree-ish> --pathspec-from-file=<file> [--pathspec-file-nul]
 git checkout [-f|--ours|--theirs|-m|--conflict=<style>] [--] <pathspec>...
 git checkout [-f|--ours|--theirs|-m|--conflict=<style>] --pathspec-from-file=<file> [--pathspec-file-nul]
 git checkout (-p|--patch) [<tree-ish>] [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
-Updates files in the working tree to match the version in the index
-or the specified tree.  If no pathspec was given, `git checkout` will
-also update `HEAD` to set the specified branch as the current
-branch.
+
+`git checkout` has two main modes:
+
+1. **Switch branches**, with `git checkout <branch>`
+2. **Restore a different version of a file**, for example with
+   `git checkout <commit> <filename>` or `git checkout <filename>`
+
+See ARGUMENT DISAMBIGUATION below for how Git decides which one to do.
 
 `git checkout [<branch>]`::
-	To prepare for working on _<branch>_, switch to it by updating
-	the index and the files in the working tree, and by pointing
-	`HEAD` at the branch. Local modifications to the files in the
-	working tree are kept, so that they can be committed to the
-	_<branch>_.
+	Switch to _<branch>_. This sets the current branch to _<branch>_ and
+	updates the files in your working directory. The checkout will fail
+	if there are uncommitted changes to any files where _<branch>_ and
+	your current commit have different content. Uncommitted changes will
+	otherwise be kept.
 +
 If _<branch>_ is not found but there does exist a tracking branch in
 exactly one remote (call it _<remote>_) with a matching name and
@@ -40,68 +44,63 @@
 $ git checkout -b <branch> --track <remote>/<branch>
 ------------
 +
-You could omit _<branch>_, in which case the command degenerates to
-"check out the current branch", which is a glorified no-op with
-rather expensive side-effects to show only the tracking information,
-if it exists, for the current branch.
+Running `git checkout` without specifying a branch has no effect except
+to print out the tracking information for the current branch.
 
-`git checkout (-b|-B) <new-branch> [<start-point>]`::
+`git checkout -b <new-branch> [<start-point>]`::
 
-	Specifying `-b` causes a new branch to be created as if
-	linkgit:git-branch[1] were called and then checked out.  In
-	this case you can use the `--track` or `--no-track` options,
-	which will be passed to `git branch`.  As a convenience,
-	`--track` without `-b` implies branch creation; see the
-	description of `--track` below.
+	Create a new branch named _<new-branch>_, start it at _<start-point>_
+	(defaults to the current commit), and check out the new branch.
+	You can use the `--track` or `--no-track` options to set the branch's
+	upstream tracking information.
 +
-If `-B` is given, _<new-branch>_ is created if it doesn't exist; otherwise, it
-is reset. This is the transactional equivalent of
-+
-------------
-$ git branch -f <branch> [<start-point>]
-$ git checkout <branch>
-------------
-+
-that is to say, the branch is not reset/created unless "git checkout" is
-successful (e.g., when the branch is in use in another worktree, not
-just the current branch stays the same, but the branch is not reset to
-the start-point, either).
+This will fail if there's an error checking out _<new-branch>_, for
+example if checking out the `<start-point>` commit would overwrite your
+uncommitted changes.
+
+`git checkout -B <branch> [<start-point>]`::
+
+	The same as `-b`, except that if the branch already exists it
+	resets `_<branch>_` to the start point instead of failing.
 
 `git checkout --detach [<branch>]`::
 `git checkout [--detach] <commit>`::
 
-	Prepare to work on top of _<commit>_, by detaching `HEAD` at it
-	(see "DETACHED HEAD" section), and updating the index and the
-	files in the working tree.  Local modifications to the files
-	in the working tree are kept, so that the resulting working
-	tree will be the state recorded in the commit plus the local
-	modifications.
-+
-When the _<commit>_ argument is a branch name, the `--detach` option can
-be used to detach `HEAD` at the tip of the branch (`git checkout
-<branch>` would check out that branch without detaching `HEAD`).
+	The same as `git checkout <branch>`, except that instead of pointing
+	`HEAD` at the branch, it points `HEAD` at the commit ID.
+	See the "DETACHED HEAD" section below for more.
 +
 Omitting _<branch>_ detaches `HEAD` at the tip of the current branch.
 
-`git checkout [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...`::
-`git checkout [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]`::
+`git checkout <tree-ish> [--] <pathspec>...`::
+`git checkout <tree-ish> --pathspec-from-file=<file> [--pathspec-file-nul]`::
 
-	Overwrite the contents of the files that match the pathspec.
-	When the _<tree-ish>_ (most often a commit) is not given,
-	overwrite working tree with the contents in the index.
-	When the _<tree-ish>_ is given, overwrite both the index and
-	the working tree with the contents at the _<tree-ish>_.
+	Replace the specified files and/or directories with the version from
+	the given commit or tree and add them to the index
+	(also known as "staging area").
 +
-The index may contain unmerged entries because of a previous failed merge.
-By default, if you try to check out such an entry from the index, the
-checkout operation will fail and nothing will be checked out.
-Using `-f` will ignore these unmerged entries.  The contents from a
-specific side of the merge can be checked out of the index by
-using `--ours` or `--theirs`.  With `-m`, changes made to the working tree
-file can be discarded to re-create the original conflicted merge result.
+For example, `git checkout main file.txt` will replace `file.txt`
+with the version from `main`.
+
+`git checkout [-f|--ours|--theirs|-m|--conflict=<style>] [--] <pathspec>...`::
+`git checkout [-f|--ours|--theirs|-m|--conflict=<style>] --pathspec-from-file=<file> [--pathspec-file-nul]`::
+
+	Replace the specified files and/or directories with the version from
+	the index.
++
+For example, if you check out a commit, edit `file.txt`, and then
+decide those changes were a mistake, `git checkout file.txt` will
+discard any unstaged changes to `file.txt`.
++
+This will fail if the file has a merge conflict and you haven't yet run
+`git add file.txt` (or something equivalent) to mark it as resolved.
+You can use `-f` to ignore the unmerged files instead of failing, use
+`--ours` or `--theirs` to replace them with the version from a specific
+side of the merge, or use `-m` to replace them with the original
+conflicted merge result.
 
 `git checkout (-p|--patch) [<tree-ish>] [--] [<pathspec>...]`::
-	This is similar to the previous mode, but lets you use the
+	This is similar to the previous two modes, but lets you use the
 	interactive interface to show the "diff" output and choose which
 	hunks to use in the result.  See below for the description of
 	`--patch` option.
@@ -155,16 +154,14 @@
 	see linkgit:git-branch[1] for details.
 
 `-B <new-branch>`::
-	Creates the branch _<new-branch>_, start it at _<start-point>_;
-	if it already exists, then reset it to _<start-point>_. And then
-	check the resulting branch out.  This is equivalent to running
-	`git branch` with `-f` followed by `git checkout` of that branch;
-	see linkgit:git-branch[1] for details.
+	The same as `-b`, except that if the branch already exists it
+	resets `_<branch>_` to the start point instead of failing.
 
 `-t`::
 `--track[=(direct|inherit)]`::
 	When creating a new branch, set up "upstream" configuration. See
-	`--track` in linkgit:git-branch[1] for details.
+	`--track` in linkgit:git-branch[1] for details. As a convenience,
+	--track without -b implies branch creation.
 +
 If no `-b` option is given, the name of the new branch will be
 derived from the remote-tracking branch, by looking at the local part of
@@ -334,7 +331,7 @@
 	separated with _NUL_ character and all other characters are taken
 	literally (including newlines and quotes).
 
-<branch>::
+`<branch>`::
 	Branch to checkout; if it refers to a branch (i.e., a name that,
 	when prepended with "refs/heads/", is a valid ref), then that
 	branch is checked out. Otherwise, if it refers to a valid
@@ -511,14 +508,18 @@
 ARGUMENT DISAMBIGUATION
 -----------------------
 
-When there is only one argument given and it is not `--` (e.g. `git
-checkout abc`), and when the argument is both a valid _<tree-ish>_
-(e.g. a branch `abc` exists) and a valid _<pathspec>_ (e.g. a file
-or a directory whose name is "abc" exists), Git would usually ask
-you to disambiguate.  Because checking out a branch is so common an
-operation, however, `git checkout abc` takes "abc" as a _<tree-ish>_
-in such a situation.  Use `git checkout -- <pathspec>` if you want
-to checkout these paths out of the index.
+When you run `git checkout <something>`, Git tries to guess whether
+`<something>` is intended to be a branch, a commit, or a set of file(s),
+and then either switches to that branch or commit, or restores the
+specified files.
+
+If there's any ambiguity, Git will treat `<something>` as a branch or
+commit, but you can use the double dash `--` to force Git to treat the
+parameter as a list of files and/or directories, like this:
+
+----------
+git checkout -- file.txt
+----------
 
 EXAMPLES
 --------
diff --git a/Documentation/git-clone.adoc b/Documentation/git-clone.adoc
index 222d558..57cdfb7 100644
--- a/Documentation/git-clone.adoc
+++ b/Documentation/git-clone.adoc
@@ -16,7 +16,7 @@
 	  [--depth <depth>] [--[no-]single-branch] [--[no-]tags]
 	  [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]
 	  [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow]
-	  [--filter=<filter-spec>] [--also-filter-submodules]] [--] <repository>
+	  [--filter=<filter-spec> [--also-filter-submodules]] [--] <repository>
 	  [<directory>]
 
 DESCRIPTION
@@ -272,7 +272,8 @@
 	reachable from a specified remote branch or tag.  This option
 	can be specified multiple times.
 
-`--[no-]single-branch`::
+`--single-branch`::
+`--no-single-branch`::
 	Clone only the history leading to the tip of a single branch,
 	either specified by the `--branch` option or the primary
 	branch remote's `HEAD` points at.
@@ -282,7 +283,8 @@
 	branch when `--single-branch` clone was made, no remote-tracking
 	branch is created.
 
-`--[no-]tags`::
+`--tags`::
+`--no-tags`::
 	Control whether or not tags will be cloned. When `--no-tags` is
 	given, the option will be become permanent by setting the
 	`remote.<remote>.tagOpt=--no-tags` configuration. This ensures that
@@ -313,10 +315,12 @@
 not have a worktree/checkout (i.e. if any of `--no-checkout`/`-n`, `--bare`,
 or `--mirror` is given)
 
-`--[no-]shallow-submodules`::
+`--shallow-submodules`::
+`--no-shallow-submodules`::
 	All submodules which are cloned will be shallow with a depth of 1.
 
-`--[no-]remote-submodules`::
+`--remote-submodules`::
+`--no-remote-submodules`::
 	All submodules which are cloned will use the status of the submodule's
 	remote-tracking branch to update the submodule, rather than the
 	superproject's recorded SHA-1. Equivalent to passing `--remote` to
diff --git a/Documentation/git-commit-graph.adoc b/Documentation/git-commit-graph.adoc
index 50b5016..e955817 100644
--- a/Documentation/git-commit-graph.adoc
+++ b/Documentation/git-commit-graph.adoc
@@ -34,7 +34,8 @@
 	object directory, `git commit-graph ...` will exit with non-zero
 	status.
 
---[no-]progress::
+--progress::
+--no-progress::
 	Turn progress on/off explicitly. If neither is specified, progress is
 	shown if standard error is connected to a terminal.
 
diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc
index ae988a8..54c207a 100644
--- a/Documentation/git-commit.adoc
+++ b/Documentation/git-commit.adoc
@@ -214,7 +214,8 @@
 	each trailer would appear, and other details.
 
 `-n`::
-`--[no-]verify`::
+`--verify`::
+`--no-verify`::
 	Bypass the `pre-commit` and `commit-msg` hooks.
 	See also linkgit:githooks[5].
 
@@ -281,6 +282,7 @@
 +
 --
 It is a rough equivalent for:
+
 ------
 	$ git reset --soft HEAD^
 	$ ... do something else to come up with the right tree ...
diff --git a/Documentation/git-config.adoc b/Documentation/git-config.adoc
index 511b2e2..36d2845 100644
--- a/Documentation/git-config.adoc
+++ b/Documentation/git-config.adoc
@@ -295,7 +295,8 @@
 	When the color setting for `name` is undefined, the command uses
 	`color.ui` as fallback.
 
---[no-]includes::
+--includes::
+--no-includes::
 	Respect `include.*` directives in config files when looking up
 	values. Defaults to `off` when a specific file is given (e.g.,
 	using `--file`, `--global`, etc) and `on` when searching all
diff --git a/Documentation/git-count-objects.adoc b/Documentation/git-count-objects.adoc
index 97f9f12..eeee6b9 100644
--- a/Documentation/git-count-objects.adoc
+++ b/Documentation/git-count-objects.adoc
@@ -28,6 +28,8 @@
 +
 in-pack: the number of in-pack objects
 +
+packs: the number of pack files
++
 size-pack: disk space consumed by the packs, in KiB (unless -H is specified)
 +
 prune-packable: the number of loose objects that are also present in
diff --git a/Documentation/git-difftool.adoc b/Documentation/git-difftool.adoc
index d596205..064bc68 100644
--- a/Documentation/git-difftool.adoc
+++ b/Documentation/git-difftool.adoc
@@ -77,7 +77,8 @@
 --tool-help::
 	Print a list of diff tools that may be used with `--tool`.
 
---[no-]symlinks::
+--symlinks::
+--no-symlinks::
 	'git difftool''s default behavior is to create symlinks to the
 	working tree when run in `--dir-diff` mode and the right-hand
 	side of the comparison yields the same content as the file in
@@ -94,7 +95,8 @@
 	Additionally, `$BASE` is set in the environment.
 
 -g::
---[no-]gui::
+--gui::
+--no-gui::
 	When 'git-difftool' is invoked with the `-g` or `--gui` option
 	the default diff tool will be read from the configured
 	`diff.guitool` variable instead of `diff.tool`. This may be
@@ -104,7 +106,8 @@
 	fallback in the order of `merge.guitool`, `diff.tool`,
 	`merge.tool` until a tool is found.
 
---[no-]trust-exit-code::
+--trust-exit-code::
+--no-trust-exit-code::
 	Errors reported by the diff tool are ignored by default.
 	Use `--trust-exit-code` to make 'git-difftool' exit when an
 	invoked diff tool returns a non-zero exit code.
diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc
index 6f9763c..85ed7a7 100644
--- a/Documentation/git-fast-import.adoc
+++ b/Documentation/git-fast-import.adoc
@@ -61,10 +61,15 @@
 	currently impacts only the `export-marks`, `import-marks`, and
 	`import-marks-if-exists` feature commands.
 +
-	Only enable this option if you trust the program generating the
-	fast-import stream! This option is enabled automatically for
-	remote-helpers that use the `import` capability, as they are
-	already trusted to run their own code.
+Only enable this option if you trust the program generating the
+fast-import stream! This option is enabled automatically for
+remote-helpers that use the `import` capability, as they are
+already trusted to run their own code.
+
+--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::
+	Specify how to handle signed commits.  Behaves in the same way
+	as the same option in linkgit:git-fast-export[1], except that
+	default is 'verbatim' (instead of 'abort').
 
 Options for Frontends
 ~~~~~~~~~~~~~~~~~~~~~
@@ -111,7 +116,8 @@
 	Like --import-marks but instead of erroring out, silently
 	skips the file if it does not exist.
 
---[no-]relative-marks::
+--relative-marks::
+--no-relative-marks::
 	After specifying --relative-marks the paths specified
 	with --import-marks= and --export-marks= are relative
 	to an internal directory in the current repository.
@@ -605,9 +611,11 @@
 
 The special case of restarting an incremental import from the
 current branch value should be written as:
+
 ----
 	from refs/heads/branch^0
 ----
+
 The `^0` suffix is necessary as fast-import does not permit a branch to
 start from itself, and the branch is created in memory before the
 `from` command is even read from the input.  Adding `^0` will force
@@ -644,7 +652,7 @@
 +
 Here usually `<dataref>` must be either a mark reference (`:<idnum>`)
 set by a prior `blob` command, or a full 40-byte SHA-1 of an
-existing Git blob object.  If `<mode>` is `040000`` then
+existing Git blob object.  If `<mode>` is `040000` then
 `<dataref>` must be the full 40-byte SHA-1 of an existing
 Git tree object or a mark reference set with `--import-marks`.
 
diff --git a/Documentation/git-fmt-merge-msg.adoc b/Documentation/git-fmt-merge-msg.adoc
index 0f33289..6d91620 100644
--- a/Documentation/git-fmt-merge-msg.adoc
+++ b/Documentation/git-fmt-merge-msg.adoc
@@ -35,7 +35,8 @@
 	Do not list one-line descriptions from the actual commits being
 	merged.
 
---[no-]summary::
+--summary::
+--no-summary::
 	Synonyms to --log and --no-log; these are deprecated and will be
 	removed in the future.
 
diff --git a/Documentation/git-for-each-ref.adoc b/Documentation/git-for-each-ref.adoc
index 0609409..c02cb7f 100644
--- a/Documentation/git-for-each-ref.adoc
+++ b/Documentation/git-for-each-ref.adoc
@@ -14,108 +14,21 @@
 		   [--merged[=<object>]] [--no-merged[=<object>]]
 		   [--contains[=<object>]] [--no-contains[=<object>]]
 		   [(--exclude=<pattern>)...] [--start-after=<marker>]
-		   [ --stdin | <pattern>... ]
+		   [ --stdin | (<pattern>...)]
 
 DESCRIPTION
 -----------
 
-Iterate over all refs that match `<pattern>` and show them
-according to the given `<format>`, after sorting them according
-to the given set of `<key>`.  If `<count>` is given, stop after
-showing that many refs.  The interpolated values in `<format>`
+Iterate over all refs that match _<pattern>_ and show them
+according to the given _<format>_, after sorting them according
+to the given set of _<key>_.  If _<count>_ is given, stop after
+showing that many refs.  The interpolated values in _<format>_
 can optionally be quoted as string literals in the specified
 host language allowing their direct evaluation in that language.
 
 OPTIONS
 -------
-<pattern>...::
-	If one or more patterns are given, only refs are shown that
-	match against at least one pattern, either using fnmatch(3) or
-	literally, in the latter case matching completely or from the
-	beginning up to a slash.
-
---stdin::
-	If `--stdin` is supplied, then the list of patterns is read from
-	standard input instead of from the argument list.
-
---count=<count>::
-	By default the command shows all refs that match
-	`<pattern>`.  This option makes it stop after showing
-	that many refs.
-
---sort=<key>::
-	A field name to sort on.  Prefix `-` to sort in
-	descending order of the value.  When unspecified,
-	`refname` is used.  You may use the --sort=<key> option
-	multiple times, in which case the last key becomes the primary
-	key.
-
---format=<format>::
-	A string that interpolates `%(fieldname)` from a ref being shown and
-	the object it points at. In addition, the string literal `%%`
-	renders as `%` and `%xx` - where `xx` are hex digits - renders as
-	the character with hex code `xx`. For example, `%00` interpolates to
-	`\0` (NUL), `%09` to `\t` (TAB), and `%0a` to `\n` (LF).
-+
-When unspecified, `<format>` defaults to `%(objectname) SPC %(objecttype)
-TAB %(refname)`.
-
---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).
-
---shell::
---perl::
---python::
---tcl::
-	If given, strings that substitute `%(fieldname)`
-	placeholders are quoted as string literals suitable for
-	the specified host language.  This is meant to produce
-	a scriptlet that can directly be `eval`ed.
-
---points-at=<object>::
-	Only list refs which points at the given object.
-
---merged[=<object>]::
-	Only list refs whose tips are reachable from the
-	specified commit (HEAD if not specified).
-
---no-merged[=<object>]::
-	Only list refs whose tips are not reachable from the
-	specified commit (HEAD if not specified).
-
---contains[=<object>]::
-	Only list refs which contain the specified commit (HEAD if not
-	specified).
-
---no-contains[=<object>]::
-	Only list refs which don't contain the specified commit (HEAD
-	if not specified).
-
---ignore-case::
-	Sorting and filtering refs are case insensitive.
-
---omit-empty::
-	Do not print a newline after formatted refs where the format expands
-	to the empty string.
-
---exclude=<pattern>::
-	If one or more patterns are given, only refs which do not match
-	any excluded pattern(s) are shown. Matching is done using the
-	same rules as `<pattern>` above.
-
---include-root-refs::
-	List root refs (HEAD and pseudorefs) apart from regular refs.
-
---start-after=<marker>::
-    Allows paginating the output by skipping references up to and including the
-    specified marker. When paging, it should be noted that references may be
-    deleted, modified or added between invocations. Output will only yield those
-    references which follow the marker lexicographically. Output begins from the
-    first reference that would come after the marker alphabetically. Cannot be
-    used with `--sort=<key>` or `--stdin` options, or the _<pattern>_ argument(s)
-    to limit the refs.
+include::for-each-ref-options.adoc[]
 
 FIELD NAMES
 -----------
@@ -126,44 +39,44 @@
 
 For all objects, the following names can be used:
 
-refname::
-	The name of the ref (the part after $GIT_DIR/).
+`refname`::
+	The name of the ref (the part after `$GIT_DIR/`).
 	For a non-ambiguous short name of the ref append `:short`.
-	The option core.warnAmbiguousRefs is used to select the strict
-	abbreviation mode. If `lstrip=<N>` (`rstrip=<N>`) is appended, strips `<N>`
+	The option `core.warnAmbiguousRefs` is used to select the strict
+	abbreviation mode. If `lstrip=<n>` (`rstrip=<n>`) is appended, strip _<n>_
 	slash-separated path components from the front (back) of the refname
 	(e.g. `%(refname:lstrip=2)` turns `refs/tags/foo` into `foo` and
 	`%(refname:rstrip=2)` turns `refs/tags/foo` into `refs`).
-	If `<N>` is a negative number, strip as many path components as
-	necessary from the specified end to leave `-<N>` path components
+	If _<n>_ is a negative number, strip as many path components as
+	necessary from the specified end to leave `-<n>` path components
 	(e.g. `%(refname:lstrip=-2)` turns
 	`refs/tags/foo` into `tags/foo` and `%(refname:rstrip=-1)`
 	turns `refs/tags/foo` into `refs`). When the ref does not have
 	enough components, the result becomes an empty string if
-	stripping with positive <N>, or it becomes the full refname if
-	stripping with negative <N>.  Neither is an error.
+	stripping with positive _<n>_, or it becomes the full refname if
+	stripping with negative _<N>_.  Neither is an error.
 +
 `strip` can be used as a synonym to `lstrip`.
 
-objecttype::
+`objecttype`::
 	The type of the object (`blob`, `tree`, `commit`, `tag`).
 
-objectsize::
+`objectsize`::
 	The size of the object (the same as 'git cat-file -s' reports).
 	Append `:disk` to get the size, in bytes, that the object takes up on
-	disk. See the note about on-disk sizes in the `CAVEATS` section below.
-objectname::
+	disk. See the note about on-disk sizes in the 'CAVEATS' section below.
+`objectname`::
 	The object name (aka SHA-1).
 	For a non-ambiguous abbreviation of the object name append `:short`.
 	For an abbreviation of the object name with desired length append
-	`:short=<length>`, where the minimum length is MINIMUM_ABBREV. The
+	`:short=<length>`, where the minimum length is `MINIMUM_ABBREV`. The
 	length may be exceeded to ensure unique object names.
-deltabase::
+`deltabase`::
 	This expands to the object name of the delta base for the
 	given object, if it is stored as a delta.  Otherwise it
 	expands to the null object name (all zeroes).
 
-upstream::
+`upstream`::
 	The name of a local ref which can be considered ``upstream''
 	from the displayed ref. Respects `:short`, `:lstrip` and
 	`:rstrip` in the same way as `refname` above.  Additionally
@@ -185,100 +98,103 @@
 with it.  All the options apart from `nobracket` are mutually exclusive,
 but if used together the last option is selected.
 
-push::
+`push`::
 	The name of a local ref which represents the `@{push}`
 	location for the displayed ref. Respects `:short`, `:lstrip`,
 	`:rstrip`, `:track`, `:trackshort`, `:remotename`, and `:remoteref`
 	options as `upstream` does. Produces an empty string if no `@{push}`
 	ref is configured.
 
-HEAD::
-	'*' if HEAD matches current ref (the checked out branch), ' '
+`HEAD`::
+	`*` if `HEAD` matches current ref (the checked out branch), ' '
 	otherwise.
 
-color::
+`color`::
 	Change output color. Followed by `:<colorname>`, where color
 	names are described under Values in the "CONFIGURATION FILE"
 	section of linkgit:git-config[1].  For example,
 	`%(color:bold red)`.
 
-align::
+`align`::
 	Left-, middle-, or right-align the content between
-	%(align:...) and %(end). The "align:" is followed by
+	`%(align:...)` and `%(end)`. The "`align:`" is followed by
 	`width=<width>` and `position=<position>` in any order
-	separated by a comma, where the `<position>` is either left,
-	right or middle, default being left and `<width>` is the total
+	separated by a comma, where the _<position>_ is either `left`,
+	`right` or `middle`, default being `left` and _<width>_ is the total
 	length of the content with alignment. For brevity, the
 	"width=" and/or "position=" prefixes may be omitted, and bare
-	<width> and <position> used instead.  For instance,
+	_<width>_ and _<position>_ used instead.  For instance,
 	`%(align:<width>,<position>)`. If the contents length is more
 	than the width then no alignment is performed. If used with
-	`--quote` everything in between %(align:...) and %(end) is
+	`--quote` everything in between `%(align:...)` and `%(end)` is
 	quoted, but if nested then only the topmost level performs
 	quoting.
 
-if::
-	Used as %(if)...%(then)...%(end) or
-	%(if)...%(then)...%(else)...%(end).  If there is an atom with
-	value or string literal after the %(if) then everything after
-	the %(then) is printed, else if the %(else) atom is used, then
+`if`::
+	Used as `%(if)...%(then)...%(end)` or
+	`%(if)...%(then)...%(else)...%(end)`.  If there is an atom with
+	value or string literal after the `%(if)` then everything after
+	the `%(then)` is printed, else if the `%(else)` atom is used, then
 	everything after %(else) is printed. We ignore space when
-	evaluating the string before %(then), this is useful when we
-	use the %(HEAD) atom which prints either "*" or " " and we
-	want to apply the 'if' condition only on the 'HEAD' ref.
-	Append ":equals=<string>" or ":notequals=<string>" to compare
-	the value between the %(if:...) and %(then) atoms with the
+	evaluating the string before `%(then)`, this is useful when we
+	use the `%(HEAD)` atom which prints either "`*`" or " " and we
+	want to apply the 'if' condition only on the `HEAD` ref.
+	Append "`:equals=<string>`" or "`:notequals=<string>`" to compare
+	the value between the `%(if:...)` and `%(then)` atoms with the
 	given string.
 
-symref::
+`symref`::
 	The ref which the given symbolic ref refers to. If not a
 	symbolic ref, nothing is printed. Respects the `:short`,
 	`:lstrip` and `:rstrip` options in the same way as `refname`
 	above.
 
-signature::
+`signature`::
 	The GPG signature of a commit.
 
-signature:grade::
-	Show "G" for a good (valid) signature, "B" for a bad
-	signature, "U" for a good signature with unknown validity, "X"
-	for a good signature that has expired, "Y" for a good
-	signature made by an expired key, "R" for a good signature
-	made by a revoked key, "E" if the signature cannot be
-	checked (e.g. missing key) and "N" for no signature.
+`signature:grade`::
+	Show
+`G`;; for a good (valid) signature
+`B`;; for a bad signature
+`U`;; for a good signature with unknown validity
+`X`;;	for a good signature that has expired
+`Y`;; for a good signature made by an expired key
+`R`;; for a good signature made by a revoked key
+`E`;; if the signature cannot be checked (e.g. missing key)
+`N`;; for no signature.
 
-signature:signer::
+`signature:signer`::
 	The signer of the GPG signature of a commit.
 
-signature:key::
+`signature:key`::
 	The key of the GPG signature of a commit.
 
-signature:fingerprint::
+`signature:fingerprint`::
 	The fingerprint of the GPG signature of a commit.
 
-signature:primarykeyfingerprint::
+`signature:primarykeyfingerprint`::
 	The primary key fingerprint of the GPG signature of a commit.
 
-signature:trustlevel::
+`signature:trustlevel`::
 	The trust level of the GPG signature of a commit. Possible
 	outputs are `ultimate`, `fully`, `marginal`, `never` and `undefined`.
 
-worktreepath::
+`worktreepath`::
 	The absolute path to the worktree in which the ref is checked
 	out, if it is checked out in any linked worktree. Empty string
 	otherwise.
 
-ahead-behind:<committish>::
+`ahead-behind:<commit-ish>`::
 	Two integers, separated by a space, demonstrating the number of
 	commits ahead and behind, respectively, when comparing the output
-	ref to the `<committish>` specified in the format.
+	ref to the _<committish>_ specified in the format.
 
-is-base:<committish>::
-	In at most one row, `(<committish>)` will appear to indicate the ref
+`is-base:<commit-ish>`::
+	In at most one row, `(<commit-ish>)` will appear to indicate the ref
 	that is most likely the ref used as a starting point for the branch
-	that produced `<committish>`. This choice is made using a heuristic:
+	that produced _<commit-ish>_. This choice is made using a heuristic:
 	choose the ref that minimizes the number of commits in the
-	first-parent history of `<committish>` and not in the first-parent
+	first-parent history of _<commit-ish>_ and not in the first-parent
 	history of the ref.
 +
 For example, consider the following figure of first-parent histories of
@@ -312,29 +228,29 @@
 earliest ref in the sorted order.
 +
 Note that this token will not appear if the first-parent history of
-`<committish>` does not intersect the first-parent histories of the
+_<commit-ish>_ does not intersect the first-parent histories of the
 filtered refs.
 
-describe[:options]::
+`describe[:<option>,...]`::
 	A human-readable name, like linkgit:git-describe[1];
 	empty string for undescribable commits. The `describe` string may
 	be followed by a colon and one or more comma-separated options.
 +
 --
-tags=<bool-value>;;
+`tags=<bool-value>`;;
 	Instead of only considering annotated tags, consider
 	lightweight tags as well; see the corresponding option in
 	linkgit:git-describe[1] for details.
-abbrev=<number>;;
-	Use at least <number> hexadecimal digits; see the corresponding
+`abbrev=<number>`;;
+	Use at least _<number>_ hexadecimal digits; see the corresponding
 	option in linkgit:git-describe[1] for details.
-match=<pattern>;;
-	Only consider tags matching the given `glob(7)` pattern,
-	excluding the "refs/tags/" prefix; see the corresponding option
+`match=<pattern>`;;
+	Only consider tags matching the `glob`(7) _<pattern>_,
+	excluding the `refs/tags/` prefix; see the corresponding option
 	in linkgit:git-describe[1] for details.
-exclude=<pattern>;;
-	Do not consider tags matching the given `glob(7)` pattern,
-	excluding the "refs/tags/" prefix; see the corresponding option
+`exclude=<pattern>`;;
+	Do not consider tags matching the `glob`(7) _<pattern>_,
+	excluding the `refs/tags/` prefix; see the corresponding option
 	in linkgit:git-describe[1] for details.
 --
 
@@ -366,7 +282,7 @@
 
 The raw data in an object is `raw`.
 
-raw:size::
+`raw:size`::
 	The raw data size of the object.
 
 Note that `--format=%(raw)` can not be used with `--python`, `--shell`, `--tcl`,
@@ -376,10 +292,10 @@
 The message in a commit or a tag object is `contents`, from which
 `contents:<part>` can be used to extract various parts out of:
 
-contents:size::
+`contents:size`::
 	The size in bytes of the commit or tag message.
 
-contents:subject::
+`contents:subject`::
 	The first paragraph of the message, which typically is a
 	single line, is taken as the "subject" of the commit or the
 	tag message.
@@ -387,19 +303,19 @@
 	obtain same results. `:sanitize` can be appended to `subject` for
 	subject line suitable for filename.
 
-contents:body::
+`contents:body`::
 	The remainder of the commit or the tag message that follows
 	the "subject".
 
-contents:signature::
+`contents:signature`::
 	The optional GPG signature of the tag.
 
-contents:lines=N::
-	The first `N` lines of the message.
+`contents:lines=<n>`::
+	The first _<n>_ lines of the message.
 
 Additionally, the trailers as interpreted by linkgit:git-interpret-trailers[1]
-are obtained as `trailers[:options]` (or by using the historical alias
-`contents:trailers[:options]`). For valid [:option] values see `trailers`
+are obtained as `trailers[:<option>,...]` (or by using the historical alias
+`contents:trailers[:<option>,...]`). For valid _<option>_ values see `trailers`
 section of linkgit:git-log[1].
 
 For sorting purposes, fields with numeric values sort in numeric order
@@ -419,8 +335,8 @@
 a `--sort` key, references will be sorted according to the byte-value of the
 formatted string rather than the numeric value of the underlying timestamp.
 
-Some atoms like %(align) and %(if) always require a matching %(end).
-We call them "opening atoms" and sometimes denote them as %($open).
+Some atoms like `%(align)` and `%(if)` always require a matching `%(end)`.
+We call them "opening atoms" and sometimes denote them as `%($open)`.
 
 When a scripting language specific quoting is in effect, everything
 between a top-level opening atom and its matching %(end) is evaluated
@@ -438,7 +354,7 @@
 #!/bin/sh
 
 git for-each-ref --count=3 --sort='-*authordate' \
---format='From: %(*authorname) %(*authoremail)
+`--format='From: %(*authorname) %(*authoremail)
 Subject: %(*subject)
 Date: %(*authordate)
 Ref: %(*refname)
@@ -449,7 +365,7 @@
 
 
 A simple example showing the use of shell eval on the output,
-demonstrating the use of --shell.  List the prefixes of all heads:
+demonstrating the use of `--shell`.  List the prefixes of all heads:
 
 ------------
 #!/bin/sh
@@ -517,7 +433,7 @@
 ------------
 
 
-An example to show the usage of %(if)...%(then)...%(else)...%(end).
+An example to show the usage of `%(if)...%(then)...%(else)...%(end)`.
 This prefixes the current branch with a star.
 
 ------------
@@ -525,7 +441,7 @@
 ------------
 
 
-An example to show the usage of %(if)...%(then)...%(end).
+An example to show the usage of `%(if)...%(then)...%(end)`.
 This prints the authorname, if present.
 
 ------------
diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc
index a8b53db..9a7807c 100644
--- a/Documentation/git-format-patch.adoc
+++ b/Documentation/git-format-patch.adoc
@@ -295,7 +295,8 @@
 transformation for you, and this option should not be used if you are
 feeding the result to `git send-email`.
 
---[no-]force-in-body-from::
+--force-in-body-from::
+--no-force-in-body-from::
 	With the e-mail sender specified via the `--from` option, by
 	default, an in-body "From:" to identify the real author of
 	the commit is added at the top of the commit log message if
@@ -314,7 +315,8 @@
 	`Cc:`, and custom) headers added so far from config or command
 	line.
 
---[no-]cover-letter::
+--cover-letter::
+--no-cover-letter::
 	In addition to the patches, generate a cover letter file
 	containing the branch description, shortlog and the overall diffstat.  You can
 	fill in a description in the file before sending it out.
@@ -379,7 +381,8 @@
 The default is `--no-notes`, unless the `format.notes` configuration is
 set.
 
---[no-]signature=<signature>::
+--signature=<signature>::
+--no-signature::
 	Add a signature to each message produced. Per RFC 3676 the signature
 	is separated from the body by a line with '-- ' on it. If the
 	signature option is omitted the signature defaults to the Git version
@@ -411,7 +414,8 @@
   Output an all-zero hash in each patch's From header instead
   of the hash of the commit.
 
---[no-]base[=<commit>]::
+--no-base::
+--base[=<commit>]::
 	Record the base tree information to identify the state the
 	patch series applies to.  See the BASE TREE INFORMATION section
 	below for details. If <commit> is "auto", a base commit is
@@ -587,13 +591,19 @@
 Approach #1 (add-on)
 ^^^^^^^^^^^^^^^^^^^^
 
-Install the Toggle Word Wrap add-on that is available from
-https://addons.mozilla.org/thunderbird/addon/toggle-word-wrap/
-It adds a menu entry "Enable Word Wrap" in the composer's "Options" menu
+Install the Toggle Line Wrap add-on that is available from
+https://addons.thunderbird.net/thunderbird/addon/toggle-line-wrap
+It adds a button "Line Wrap" to the composer's toolbar
 that you can tick off. Now you can compose the message as you otherwise do
 (cut + paste, 'git format-patch' | 'git imap-send', etc), but you have to
 insert line breaks manually in any text that you type.
 
+As a bonus feature, the add-on can detect patch text in the composer
+and warns when line wrapping has not yet been turned off.
+
+The add-on requires a few tweaks of the advanced configuration
+(about:config). These are listed on the download page.
+
 Approach #2 (configuration)
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
 Three steps:
diff --git a/Documentation/git-fsck.adoc b/Documentation/git-fsck.adoc
index 11203ba..1751f69 100644
--- a/Documentation/git-fsck.adoc
+++ b/Documentation/git-fsck.adoc
@@ -31,7 +31,8 @@
 	Print out objects that exist but that aren't reachable from any
 	of the reference nodes.
 
---[no-]dangling::
+--dangling::
+--no-dangling::
 	Print objects that exist but that are never 'directly' used (default).
 	`--no-dangling` can be used to omit this information from the output.
 
@@ -97,14 +98,16 @@
 	compatible with linkgit:git-rev-parse[1], e.g.
 	`HEAD@{1234567890}~25^2:src/`.
 
---[no-]progress::
+--progress::
+--no-progress::
 	Progress status is reported on the standard error stream by
 	default when it is attached to a terminal, unless
 	--no-progress or --verbose is specified. --progress forces
 	progress status even if the standard error stream is not
 	directed to a terminal.
 
---[no-]references::
+--references::
+--no-references::
 	Control whether to check the references database consistency
 	via 'git refs verify'. See linkgit:git-refs[1] for details.
 	The default is to check the references database.
diff --git a/Documentation/git-gc.adoc b/Documentation/git-gc.adoc
index 526ce01..6fed646 100644
--- a/Documentation/git-gc.adoc
+++ b/Documentation/git-gc.adoc
@@ -53,11 +53,13 @@
 other housekeeping tasks (e.g. rerere, working trees, reflog...) will
 be performed as well.
 
---[no-]detach::
+--detach::
+--no-detach::
 	Run in the background if the system supports it. This option overrides
 	the `gc.autoDetach` config.
 
---[no-]cruft::
+--cruft::
+--no-cruft::
 	When expiring unreachable objects, pack them separately into a
 	cruft pack instead of storing them as loose objects. `--cruft`
 	is on by default.
diff --git a/Documentation/git-http-fetch.adoc b/Documentation/git-http-fetch.adoc
index 4ec7c68..2200f07 100644
--- a/Documentation/git-http-fetch.adoc
+++ b/Documentation/git-http-fetch.adoc
@@ -25,8 +25,11 @@
         Either the hash or the filename under [URL]/refs/ to
         pull.
 
--a, -c, -t::
+-a::
+-c::
+-t::
 	These options are ignored for historical reasons.
+
 -v::
 	Report what is downloaded.
 
diff --git a/Documentation/git-index-pack.adoc b/Documentation/git-index-pack.adoc
index 270056c..1803695 100644
--- a/Documentation/git-index-pack.adoc
+++ b/Documentation/git-index-pack.adoc
@@ -36,7 +36,8 @@
 	fails if the name of packed archive does not end
 	with .pack).
 
---[no-]rev-index::
+--rev-index::
+--no-rev-index::
 	When this flag is provided, generate a reverse index
 	(a `.rev` file) corresponding to the given pack. If
 	`--verify` is given, ensure that the existing
diff --git a/Documentation/git-init.adoc b/Documentation/git-init.adoc
index a0dffba..bab99b9 100644
--- a/Documentation/git-init.adoc
+++ b/Documentation/git-init.adoc
@@ -77,9 +77,15 @@
 `-b <branch-name>`::
 `--initial-branch=<branch-name>`::
 Use _<branch-name>_ for the initial branch in the newly created
-repository.  If not specified, fall back to the default name (currently
-`master`, but this is subject to change in the future; the name can be
-customized via the `init.defaultBranch` configuration variable).
+repository.  If not specified, fall back to the default name
+ifndef::with-breaking-changes[]
+(currently `master`, but this will change to `main` when Git 3.0 is released).
+endif::with-breaking-changes[]
+ifdef::with-breaking-changes[]
+`main`.
+endif::with-breaking-changes[]
+The default name can be customized via the `init.defaultBranch` configuration
+variable.
 
 `--shared[=(false|true|umask|group|all|world|everybody|<perm>)]`::
 
diff --git a/Documentation/git-interpret-trailers.adoc b/Documentation/git-interpret-trailers.adoc
index 82c8780..fd335fe 100644
--- a/Documentation/git-interpret-trailers.adoc
+++ b/Documentation/git-interpret-trailers.adoc
@@ -142,8 +142,8 @@
 	provided with '--if-exists' overrides the `trailer.ifExists` and any
 	applicable `trailer.<keyAlias>.ifExists` configuration variables
 	and applies to all '--trailer' options until the next occurrence of
-	'--if-exists' or '--no-if-exists'. Upon encountering '--no-if-exists, clear the
-	effect of any previous use of '--if-exists, such that the relevant configuration
+	'--if-exists' or '--no-if-exists'. Upon encountering '--no-if-exists', clear the
+	effect of any previous use of '--if-exists', such that the relevant configuration
 	variables are no longer overridden. Possible actions are `addIfDifferent`,
 	`addIfDifferentNeighbor`, `add`, `replace` and `doNothing`.
 
@@ -154,8 +154,8 @@
 	provided with '--if-missing' overrides the `trailer.ifMissing` and any
 	applicable `trailer.<keyAlias>.ifMissing` configuration variables
 	and applies to all '--trailer' options until the next occurrence of
-	'--if-missing' or '--no-if-missing'. Upon encountering '--no-if-missing,
-	clear the effect of any previous use of '--if-missing, such that the relevant
+	'--if-missing' or '--no-if-missing'. Upon encountering '--no-if-missing',
+	clear the effect of any previous use of '--if-missing', such that the relevant
 	configuration variables are no longer overridden. Possible actions are `doNothing`
 	or `add`.
 
diff --git a/Documentation/git-last-modified.adoc b/Documentation/git-last-modified.adoc
new file mode 100644
index 0000000..602843e
--- /dev/null
+++ b/Documentation/git-last-modified.adoc
@@ -0,0 +1,54 @@
+git-last-modified(1)
+====================
+
+NAME
+----
+git-last-modified - EXPERIMENTAL: Show when files were last modified
+
+
+SYNOPSIS
+--------
+[synopsis]
+git last-modified [--recursive] [--show-trees] [<revision-range>] [[--] <path>...]
+
+DESCRIPTION
+-----------
+
+Shows which commit last modified each of the relevant files and subdirectories.
+A commit renaming a path, or changing it's mode is also taken into account.
+
+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
+
+OPTIONS
+-------
+
+`-r`::
+`--recursive`::
+	Instead of showing tree entries, step into subtrees and show all entries
+	inside them recursively.
+
+`-t`::
+`--show-trees`::
+	Show tree entries even when recursing into them. It has no effect
+	without `--recursive`.
+
+`<revision-range>`::
+	Only traverse commits in the specified revision range. When no
+	`<revision-range>` is specified, it defaults to `HEAD` (i.e. the whole
+	history leading to the current commit). For a complete list of ways to
+	spell `<revision-range>`, see the 'Specifying Ranges' section of
+	linkgit:gitrevisions[7].
+
+`[--] <path>...`::
+	For each _<path>_ given, the commit which last modified it is returned.
+	Without an optional path parameter, all files and subdirectories
+	in path traversal the are included in the output.
+
+SEE ALSO
+--------
+linkgit:git-blame[1],
+linkgit:git-log[1].
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-log.adoc b/Documentation/git-log.adoc
index b6f3d92..e304739 100644
--- a/Documentation/git-log.adoc
+++ b/Documentation/git-log.adoc
@@ -73,8 +73,10 @@
 	Print out the ref name given on the command line by which each
 	commit was reached.
 
-`--[no-]mailmap`::
-`--[no-]use-mailmap`::
+`--mailmap`::
+`--no-mailmap`::
+`--use-mailmap`::
+`--no-use-mailmap`::
 	Use mailmap file to map author and committer names and email
 	addresses to canonical real names and email addresses. See
 	linkgit:git-shortlog[1].
diff --git a/Documentation/git-merge-tree.adoc b/Documentation/git-merge-tree.adoc
index f824eea..4391bbe 100644
--- a/Documentation/git-merge-tree.adoc
+++ b/Documentation/git-merge-tree.adoc
@@ -59,7 +59,8 @@
 	do not list filenames multiple times if they have multiple
 	conflicting stages).
 
---[no-]messages::
+--messages::
+--no-messages::
 	Write any informational messages such as "Auto-merging <path>"
 	or CONFLICT notices to the end of stdout.  If unspecified, the
 	default is to include these messages if there are merge
@@ -78,11 +79,17 @@
 
 --merge-base=<tree-ish>::
 	Instead of finding the merge-bases for <branch1> and <branch2>,
-	specify a merge-base for the merge, and specifying multiple bases is
-	currently not supported. This option is incompatible with `--stdin`.
+	specify a merge-base for the merge.  This option is incompatible with
+	`--stdin`.
 +
-As the merge-base is provided directly, <branch1> and <branch2> do not need
-to specify commits; trees are enough.
+Specifying multiple bases is currently not supported, which means that when
+merging two branches with more than one merge-base, using this option may
+cause merge results to differ from what `git merge` would compute.  This
+can include potentially losing some changes made on one side of the history
+in the resulting merge.
++
+With this option, since the merge-base is provided directly, <branch1> and
+<branch2> do not need to specify commits; trees are enough.
 
 -X<option>::
 --strategy-option=<option>::
diff --git a/Documentation/git-multi-pack-index.adoc b/Documentation/git-multi-pack-index.adoc
index b6cd0d7..2f64269 100644
--- a/Documentation/git-multi-pack-index.adoc
+++ b/Documentation/git-multi-pack-index.adoc
@@ -25,10 +25,11 @@
 +
 `<dir>` must be an alternate of the current repository.
 
---[no-]progress::
+--progress::
+--no-progress::
 	Turn progress on/off explicitly. If neither is specified, progress is
 	shown if standard error is connected to a terminal. Supported by
-	sub-commands `write`, `verify`, `expire`, and `repack.
+	sub-commands `write`, `verify`, `expire`, and `repack`.
 
 The following subcommands are available:
 
diff --git a/Documentation/git-p4.adoc b/Documentation/git-p4.adoc
index f97b786..59edd24 100644
--- a/Documentation/git-p4.adoc
+++ b/Documentation/git-p4.adoc
@@ -66,6 +66,7 @@
 ~~~~~
 Generally, 'git p4 clone' is used to create a new Git directory
 from an existing p4 repository:
+
 ------------
 $ git p4 clone //depot/path/project
 ------------
diff --git a/Documentation/git-pack-objects.adoc b/Documentation/git-pack-objects.adoc
index eba014c..71b9682 100644
--- a/Documentation/git-pack-objects.adoc
+++ b/Documentation/git-pack-objects.adoc
@@ -243,7 +243,8 @@
 	Add --no-reuse-object if you want to force a uniform compression
 	level on all data no matter the source.
 
---[no-]sparse::
+--sparse::
+--no-sparse::
 	Toggle the "sparse" algorithm to determine which objects to include in
 	the pack, when combined with the "--revs" option. This algorithm
 	only walks trees that appear in paths that introduce new objects.
diff --git a/Documentation/git-pack-refs.adoc b/Documentation/git-pack-refs.adoc
index 42b9005..fde9f2f 100644
--- a/Documentation/git-pack-refs.adoc
+++ b/Documentation/git-pack-refs.adoc
@@ -45,58 +45,7 @@
 OPTIONS
 -------
 
---all::
-
-The command by default packs all tags and refs that are already
-packed, and leaves other refs
-alone.  This is because branches are expected to be actively
-developed and packing their tips does not help performance.
-This option causes all refs to be packed as well, with the exception
-of hidden refs, broken refs, and symbolic refs. Useful for a repository
-with many branches of historical interests.
-
---no-prune::
-
-The command usually removes loose refs under `$GIT_DIR/refs`
-hierarchy after packing them.  This option tells it not to.
-
---auto::
-
-Pack refs as needed depending on the current state of the ref database. The
-behavior depends on the ref format used by the repository and may change in the
-future.
-+
-	- "files": Loose references are packed into the `packed-refs` file
-	  based on the ratio of loose references to the size of the
-	  `packed-refs` file. The bigger the `packed-refs` file, the more loose
-	  references need to exist before we repack.
-+
-	- "reftable": Tables are compacted such that they form a geometric
-	  sequence. For two tables N and N+1, where N+1 is newer, this
-	  maintains the property that N is at least twice as big as N+1. Only
-	  tables that violate this property are compacted.
-
---include <pattern>::
-
-Pack refs based on a `glob(7)` pattern. Repetitions of this option
-accumulate inclusion patterns. If a ref is both included in `--include` and
-`--exclude`, `--exclude` takes precedence. Using `--include` will preclude all
-tags from being included by default. Symbolic refs and broken refs will never
-be packed. When used with `--all`, it will be a noop. Use `--no-include` to clear
-and reset the list of patterns.
-
---exclude <pattern>::
-
-Do not pack refs matching the given `glob(7)` pattern. Repetitions of this option
-accumulate exclusion patterns. Use `--no-exclude` to clear and reset the list of
-patterns. If a ref is already packed, including it with `--exclude` will not
-unpack it.
-+
-When used with `--all`, pack only loose refs which do not match any of
-the provided `--exclude` patterns.
-+
-When used with `--include`, refs provided to `--include`, minus refs that are
-provided to `--exclude` will be packed.
+include::pack-refs-options.adoc[]
 
 
 BUGS
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 3f4ecc4..48e924a 100644
--- a/Documentation/git-pull.adoc
+++ b/Documentation/git-pull.adoc
@@ -87,7 +87,8 @@
 --verbose::
 	Pass --verbose to git-fetch and git-merge.
 
---[no-]recurse-submodules[=(yes|on-demand|no)]::
+--recurse-submodules[=(yes|on-demand|no)]::
+--no-recurse-submodules::
 	This option controls if new commits of populated submodules should
 	be fetched, and if the working trees of active submodules should be
 	updated, too (see linkgit:git-fetch[1], linkgit:git-config[1] and
diff --git a/Documentation/git-push.adoc b/Documentation/git-push.adoc
index d197865..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]]
 ------------------
@@ -55,96 +60,66 @@
 
 <refspec>...::
 	Specify what destination ref to update with what source object.
-	The format of a <refspec> parameter is an optional plus
-	`+`, followed by the source object <src>, followed
-	by a colon `:`, followed by the destination ref <dst>.
 +
-The <src> is often the name of the branch you would want to push, but
-it can be any arbitrary "SHA-1 expression", such as `master~4` or
-`HEAD` (see linkgit:gitrevisions[7]).
+The format for a refspec is [+]<src>[:<dst>], for example `main`,
+`main:other`, or `HEAD^:refs/heads/main`.
 +
-The <dst> tells which ref on the remote side is updated with this
-push. Arbitrary expressions cannot be used here, an actual ref must
-be named.
-If `git push [<repository>]` without any `<refspec>` argument is set to
-update some ref at the destination with `<src>` with
-`remote.<repository>.push` configuration variable, `:<dst>` part can
-be omitted--such a push will update a ref that `<src>` normally updates
-without any `<refspec>` on the command line.  Otherwise, missing
-`:<dst>` means to update the same ref as the `<src>`.
+The `<src>` is often the name of the local branch to push, but it can be
+any arbitrary "SHA-1 expression" (see linkgit:gitrevisions[7]).
 +
-If <dst> doesn't start with `refs/` (e.g. `refs/heads/master`) we will
-try to infer where in `refs/*` on the destination <repository> it
-belongs based on the type of <src> being pushed and whether <dst>
-is ambiguous.
+The `<dst>` determines what ref to update on the remote side. It must be the
+name of a branch, tag, or other ref, not an arbitrary expression.
 +
---
-* If <dst> unambiguously refers to a ref on the <repository> remote,
-  then push to that ref.
+The `+` is optional and does the same thing as `--force`.
++
+You can write a refspec using the fully expanded form (for
+example `refs/heads/main:refs/heads/main`) which specifies the exact source
+and destination, or with a shorter form (for example `main` or
+`main:other`). Here are the rules for how refspecs are expanded,
+as well as various other special refspec forms:
++
+ *  `<src>` without a `:<dst>` means to update the same ref as the
+    `<src>`, unless the `remote.<repository>.push` configuration specifies a
+    different <dst>. For example, if `main` is a branch, then the refspec
+    `main` expands to `main:refs/heads/main`.
+ *  If `<dst>` unambiguously refers to a ref on the <repository> remote,
+    then expand it to that ref. For example, if `v1.0` is a tag on the
+    remote, then `HEAD:v1.0` expands to `HEAD:refs/tags/v1.0`.
+ *  If `<src>` resolves to a ref starting with `refs/heads/` or `refs/tags/`,
+    then prepend that to <dst>. For example, if `main` is a branch, then
+    `main:other` expands to `main:refs/heads/other`
+ *  The special refspec `:` (or `+:` to allow non-fast-forward updates)
+    directs Git to push "matching" branches: for every branch that exists on
+    the local side, the remote side is updated if a branch of the same name
+    already exists on the remote side.
+ *  <src> may contain a * to indicate a simple pattern match.
+    This works like a glob that matches any ref matching the pattern.
+    There must be only one * in both the `<src>` and `<dst>`.
+    It will map refs to the destination by replacing the * with the
+    contents matched from the source. For example, `refs/heads/*:refs/heads/*`
+    will push all branches.
+ *  A refspec starting with `^` is a negative refspec.
+    This specifies refs to exclude. A ref will be considered to
+    match if it matches at least one positive refspec, and does not
+    match any negative refspec. Negative refspecs can be pattern refspecs.
+    They must only contain a `<src>`.
+    Fully spelled out hex object names are also not supported.
+    For example, `git push origin 'refs/heads/*' '^refs/heads/dev-*'`
+    will push all branches except for those starting with `dev-`
+ *  If `<src>` is empty, it deletes the `<dst>` ref from the remote
+    repository. For example, `git push origin :dev` will
+    delete the `dev` branch.
+ *  `tag <tag>` expands to `refs/tags/<tag>:refs/tags/<tag>`.
+	This is technically a special syntax for `git push` and not a refspec,
+	since in `git push origin tag v1.0` the arguments `tag` and `v1.0`
+	are separate.
+ *  If the refspec can't be expanded unambiguously, error out
+    with an error indicating what was tried, and depending
+    on the `advice.pushUnqualifiedRefname` configuration (see
+    linkgit:git-config[1]) suggest what refs/ namespace you may have
+    wanted to push to.
 
-* If <src> resolves to a ref starting with refs/heads/ or refs/tags/,
-  then prepend that to <dst>.
-
-* Other ambiguity resolutions might be added in the future, but for
-  now any other cases will error out with an error indicating what we
-  tried, and depending on the `advice.pushUnqualifiedRefname`
-  configuration (see linkgit:git-config[1]) suggest what refs/
-  namespace you may have wanted to push to.
-
---
-+
-The object referenced by <src> is used to update the <dst> reference
-on the remote side. Whether this is allowed depends on where in
-`refs/*` the <dst> reference lives as described in detail below, in
-those sections "update" means any modifications except deletes, which
-as noted after the next few sections are treated differently.
-+
-The `refs/heads/*` namespace will only accept commit objects, and
-updates only if they can be fast-forwarded.
-+
-The `refs/tags/*` namespace will accept any kind of object (as
-commits, trees and blobs can be tagged), and any updates to them will
-be rejected.
-+
-It's possible to push any type of object to any namespace outside of
-`refs/{tags,heads}/*`. In the case of tags and commits, these will be
-treated as if they were the commits inside `refs/heads/*` for the
-purposes of whether the update is allowed.
-+
-I.e. a fast-forward of commits and tags outside `refs/{tags,heads}/*`
-is allowed, even in cases where what's being fast-forwarded is not a
-commit, but a tag object which happens to point to a new commit which
-is a fast-forward of the commit the last tag (or commit) it's
-replacing. Replacing a tag with an entirely different tag is also
-allowed, if it points to the same commit, as well as pushing a peeled
-tag, i.e. pushing the commit that existing tag object points to, or a
-new tag object which an existing commit points to.
-+
-Tree and blob objects outside of `refs/{tags,heads}/*` will be treated
-the same way as if they were inside `refs/tags/*`, any update of them
-will be rejected.
-+
-All of the rules described above about what's not allowed as an update
-can be overridden by adding an the optional leading `+` to a refspec
-(or using `--force` command line option). The only exception to this
-is that no amount of forcing will make the `refs/heads/*` namespace
-accept a non-commit object. Hooks and configuration can also override
-or amend these rules, see e.g. `receive.denyNonFastForwards` in
-linkgit:git-config[1] and `pre-receive` and `update` in
-linkgit:githooks[5].
-+
-Pushing an empty <src> allows you to delete the <dst> ref from the
-remote repository. Deletions are always accepted without a leading `+`
-in the refspec (or `--force`), except when forbidden by configuration
-or hooks. See `receive.denyDeletes` in linkgit:git-config[1] and
-`pre-receive` and `update` in linkgit:githooks[5].
-+
-The special refspec `:` (or `+:` to allow non-fast-forward updates)
-directs Git to push "matching" branches: for every branch that exists on
-the local side, the remote side is updated if a branch of the same name
-already exists on the remote side.
-+
-`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
+Not all updates are allowed: see PUSH RULES below for the details.
 
 --all::
 --branches::
@@ -197,7 +172,8 @@
 	with configuration variable `push.followTags`.  For more
 	information, see `push.followTags` in linkgit:git-config[1].
 
---[no-]signed::
+--signed::
+--no-signed::
 --signed=(true|false|if-asked)::
 	GPG-sign the push request to update refs on the receiving
 	side, to allow it to be checked by the hooks and/or be
@@ -208,7 +184,8 @@
 	will also fail if the actual call to `gpg --sign` fails.  See
 	linkgit:git-receive-pack[1] for the details on the receiving end.
 
---[no-]atomic::
+--atomic::
+--no-atomic::
 	Use an atomic transaction on the remote side if available.
 	Either all refs are updated, or on error, no refs are updated.
 	If the server does not support atomic pushes the push will fail.
@@ -232,7 +209,8 @@
 	repository over ssh, and you do not have the program in
 	a directory on the default $PATH.
 
---[no-]force-with-lease::
+--force-with-lease::
+--no-force-with-lease::
 --force-with-lease=<refname>::
 --force-with-lease=<refname>:<expect>::
 	Usually, "git push" refuses to update a remote ref that is
@@ -332,14 +310,12 @@
 
 -f::
 --force::
-	Usually, the command refuses to update a remote ref that is
-	not an ancestor of the local ref used to overwrite it.
-	Also, when `--force-with-lease` option is used, the command refuses
-	to update a remote ref whose current value does not match
-	what is expected.
+	Usually, `git push` will refuse to update a branch that is not an
+	ancestor of the commit being pushed.
 +
-This flag disables these checks, and can cause the remote repository
-to lose commits; use it with care.
+This flag disables that check, the other safety checks in PUSH RULES
+below, and the checks in --force-with-lease. It can cause the remote
+repository to lose commits; use it with care.
 +
 Note that `--force` applies to all the refs that are pushed, hence
 using it with `push.default` set to `matching` or with multiple push
@@ -350,7 +326,8 @@
 origin +master` to force a push to the `master` branch). See the
 `<refspec>...` section above for details.
 
---[no-]force-if-includes::
+--force-if-includes::
+--no-force-if-includes::
 	Force an update only if the tip of the remote-tracking ref
 	has been integrated locally.
 +
@@ -377,7 +354,8 @@
 	linkgit:git-pull[1] and other commands. For more information,
 	see `branch.<name>.merge` in linkgit:git-config[1].
 
---[no-]thin::
+--thin::
+--no-thin::
 	These options are passed to linkgit:git-send-pack[1]. A thin transfer
 	significantly reduces the amount of sent data when the sender and
 	receiver share many of the same objects in common. The default is
@@ -419,7 +397,8 @@
 "push.recurseSubmodules={on-demand,only}" or "submodule.recurse" configuration,
 further recursion will occur. In this case, "only" is treated as "on-demand".
 
---[no-]verify::
+--verify::
+--no-verify::
 	Toggle the pre-push hook (see linkgit:githooks[5]).  The
 	default is --verify, giving the hook a chance to prevent the
 	push.  With --no-verify, the hook is bypassed completely.
@@ -508,6 +487,45 @@
 	refs, no explanation is needed. For a failed ref, the reason for
 	failure is described.
 
+PUSH RULES
+----------
+
+As a safety feature, the `git push` command only allows certain kinds of
+updates to prevent you from accidentally losing data on the remote.
+
+Because branches and tags are intended to be used differently, the
+safety rules for pushing to a branch are different from the rules
+for pushing to a tag. In the following rules "update" means any
+modifications except deletions and creations. Deletions and creations
+are always allowed, except when forbidden by configuration or hooks.
+
+1. If the push destination is a **branch** (`refs/heads/*`): only
+   fast-forward updates are allowed, which means the destination must be
+   an ancestor of the source commit. The source must be a commit.
+2. If the push destination is a **tag** (`refs/tags/*`): all updates will
+   be rejected. The source can be any object.
+3. If the push destination is not a branch or tag:
+   * If the source is a tree or blob object, any updates will be rejected
+   * If the source is a tag or commit object, any fast-forward update
+     is allowed, even in cases where what's being fast-forwarded is not a
+     commit, but a tag object which happens to point to a new commit which
+     is a fast-forward of the commit the last tag (or commit) it's
+     replacing. Replacing a tag with an entirely different tag is also
+     allowed, if it points to the same commit, as well as pushing a peeled
+     tag, i.e. pushing the commit that existing tag object points to, or a
+     new tag object which an existing commit points to.
+
+You can override these rules by passing `--force` or by adding the
+optional leading `+` to a refspec. The only exceptions are that no
+amount of forcing will make a branch accept a non-commit object,
+and forcing won't make the remote repository accept a push that it's
+configured to deny.
+
+Hooks and configuration can also override or amend these rules,
+see e.g. `receive.denyNonFastForwards` and `receive.denyDeletes`
+in linkgit:git-config[1] and `pre-receive` and `update` in
+linkgit:githooks[5].
+
 NOTE ABOUT FAST-FORWARDS
 ------------------------
 
@@ -697,6 +715,7 @@
 
 include::transfer-data-leaks.adoc[]
 
+[[CONFIGURATION]]
 CONFIGURATION
 -------------
 
diff --git a/Documentation/git-range-diff.adoc b/Documentation/git-range-diff.adoc
index db0e427..b5e85d3 100644
--- a/Documentation/git-range-diff.adoc
+++ b/Documentation/git-range-diff.adoc
@@ -96,7 +96,8 @@
 --remerge-diff::
 	Convenience option, equivalent to `--diff-merges=remerge`.
 
---[no-]notes[=<ref>]::
+--notes[=<ref>]::
+--no-notes::
 	This flag is passed to the `git log` program
 	(see linkgit:git-log[1]) that generates the patches.
 
diff --git a/Documentation/git-read-tree.adoc b/Documentation/git-read-tree.adoc
index 1c48c28..1c04bba 100644
--- a/Documentation/git-read-tree.adoc
+++ b/Documentation/git-read-tree.adoc
@@ -100,7 +100,8 @@
 	directories the index file and index output file are
 	located in.
 
---[no-]recurse-submodules::
+--recurse-submodules::
+--no-recurse-submodules::
 	Using --recurse-submodules will update the content of all active
 	submodules according to the commit recorded in the superproject by
 	calling read-tree recursively, also setting the submodules' HEAD to be
diff --git a/Documentation/git-rebase.adoc b/Documentation/git-rebase.adoc
index 956d304..005caf6 100644
--- a/Documentation/git-rebase.adoc
+++ b/Documentation/git-rebase.adoc
@@ -16,49 +16,12 @@
 
 DESCRIPTION
 -----------
-If `<branch>` is specified, `git rebase` will perform an automatic
-`git switch <branch>` before doing anything else.  Otherwise
-it remains on the current branch.
+Transplant a series of commits onto a different starting point.
+You can also use `git rebase` to reorder or combine commits: see INTERACTIVE
+MODE below for how to do that.
 
-If `<upstream>` is not specified, the upstream configured in
-`branch.<name>.remote` and `branch.<name>.merge` options will be used (see
-linkgit:git-config[1] for details) and the `--fork-point` option is
-assumed.  If you are currently not on any branch or if the current
-branch does not have a configured upstream, the rebase will abort.
-
-All changes made by commits in the current branch but that are not
-in `<upstream>` are saved to a temporary area.  This is the same set
-of commits that would be shown by `git log <upstream>..HEAD`; or by
-`git log 'fork_point'..HEAD`, if `--fork-point` is active (see the
-description on `--fork-point` below); or by `git log HEAD`, if the
-`--root` option is specified.
-
-The current branch is reset to `<upstream>` or `<newbase>` if the
-`--onto` option was supplied.  This has the exact same effect as
-`git reset --hard <upstream>` (or `<newbase>`). `ORIG_HEAD` is set
-to point at the tip of the branch before the reset.
-
-[NOTE]
-`ORIG_HEAD` is not guaranteed to still point to the previous branch tip
-at the end of the rebase if other commands that write that pseudo-ref
-(e.g. `git reset`) are used during the rebase. The previous branch tip,
-however, is accessible using the reflog of the current branch
-(i.e. `@{1}`, see linkgit:gitrevisions[7]).
-
-The commits that were previously saved into the temporary area are
-then reapplied to the current branch, one by one, in order. Note that
-any commits in `HEAD` which introduce the same textual changes as a commit
-in `HEAD..<upstream>` are omitted (i.e., a patch already accepted upstream
-with a different commit message or timestamp will be skipped).
-
-It is possible that a merge failure will prevent this process from being
-completely automatic.  You will have to resolve any such merge failure
-and run `git rebase --continue`.  Another option is to bypass the commit
-that caused the merge failure with `git rebase --skip`.  To check out the
-original `<branch>` and remove the `.git/rebase-apply` working files, use
-the command `git rebase --abort` instead.
-
-Assume the following history exists and the current branch is "topic":
+For example, imagine that you have been working on the `topic` branch in this
+history, and you want to "catch up" to the work done on the `master` branch.
 
 ------------
           A---B---C topic
@@ -66,13 +29,11 @@
     D---E---F---G master
 ------------
 
-From this point, the result of either of the following commands:
-
-
-    git rebase master
-    git rebase master topic
-
-would be:
+You want to transplant the commits you made on `topic` since it diverged from
+`master` (i.e. A, B, and C), on top of the current `master`.  You can do this
+by running `git rebase master` while the `topic` branch is checked out.  If you
+want to rebase `topic` while on another branch, `git rebase master topic` is a
+shortcut for `git checkout topic && git rebase master`.
 
 ------------
                   A'--B'--C' topic
@@ -80,30 +41,56 @@
     D---E---F---G master
 ------------
 
-*NOTE:* The latter form is just a short-hand of `git checkout topic`
-followed by `git rebase master`. When rebase exits `topic` will
-remain the checked-out branch.
 
-If the upstream branch already contains a change you have made (e.g.,
-because you mailed a patch which was applied upstream), then that commit
-will be skipped and warnings will be issued (if the 'merge' backend is
-used).  For example, running `git rebase master` on the following
-history (in which `A'` and `A` introduce the same set of changes, but
-have different committer information):
+If there is a merge conflict during this process, `git rebase` will stop at the
+first problematic commit and leave conflict markers. If this happens, you can do
+one of these things:
 
-------------
-          A---B---C topic
-         /
-    D---E---A'---F master
-------------
+1. Resolve the conflict. You can use `git diff` to find the markers (<<<<<<)
+   and make edits to resolve the conflict. For each file you edit, you need to
+   tell Git that the conflict has been resolved. You can mark the conflict as
+   resolved with  `git add <filename>`. After resolving all of the conflicts,
+   you can continue the rebasing process with
 
-will result in:
+   git rebase --continue
 
-------------
-                   B'---C' topic
-                  /
-    D---E---A'---F master
-------------
+2. Stop the `git rebase` and return your branch to its original state with
+
+   git rebase --abort
+
+3. Skip the commit that caused the merge conflict with
+
+   git rebase --skip
+
+If you don't specify an `<upstream>` to rebase onto, the upstream configured in
+`branch.<name>.remote` and `branch.<name>.merge` options will be used (see
+linkgit:git-config[1] for details) and the `--fork-point` option is
+assumed.  If you are currently not on any branch or if the current
+branch does not have a configured upstream, the rebase will abort.
+
+Here is a simplified description of what `git rebase <upstream>` does:
+
+1. Make a list of all commits on your current branch since it branched
+   off from `<upstream>` that do not have an equivalent commit in
+   `<upstream>`.
+2. Check out `<upstream>` with the equivalent of
+   `git checkout --detach <upstream>`.
+3. Replay the commits, one by one, in order. This is similar to running
+   `git cherry-pick <commit>` for each commit. See REBASING MERGES for how merges
+   are handled.
+4. Update your branch to point to the final commit with the equivalent
+   of `git checkout -B <branch>`.
+
+[NOTE]
+When starting the rebase, `ORIG_HEAD` is set to point to the commit at the tip
+of the to-be-rebased branch. However, `ORIG_HEAD` is not guaranteed to still
+point to that commit at the end of the rebase if other commands that change
+`ORIG_HEAD` (like `git reset`) are used during the rebase. The previous branch
+tip, however, is accessible using the reflog of the current branch (i.e. `@{1}`,
+see linkgit:gitrevisions[7].
+
+TRANSPLANTING A TOPIC BRANCH WITH --ONTO
+----------------------------------------
 
 Here is how you would transplant a topic branch based on one
 branch to another, to pretend that you forked the topic branch
@@ -186,28 +173,6 @@
 part of topicA.  Note that the argument to `--onto` and the `<upstream>`
 parameter can be any valid commit-ish.
 
-In case of conflict, `git rebase` will stop at the first problematic commit
-and leave conflict markers in the tree.  You can use `git diff` to locate
-the markers (<<<<<<) and make edits to resolve the conflict.  For each
-file you edit, you need to tell Git that the conflict has been resolved,
-typically this would be done with
-
-
-    git add <filename>
-
-
-After resolving the conflict manually and updating the index with the
-desired resolution, you can continue the rebasing process with
-
-
-    git rebase --continue
-
-
-Alternatively, you can undo the 'git rebase' with
-
-
-    git rebase --abort
-
 MODE OPTIONS
 ------------
 
@@ -253,6 +218,8 @@
 merge base of A and B if there is exactly one merge base. You can
 leave out at most one of A and B, in which case it defaults to HEAD.
 
+See TRANSPLANTING A TOPIC BRANCH WITH --ONTO above for examples.
+
 --keep-base::
 	Set the starting point at which to create the new commits to the
 	merge base of `<upstream>` and `<branch>`. Running
@@ -687,7 +654,7 @@
  * --fork-point and --root
 
 BEHAVIORAL DIFFERENCES
------------------------
+----------------------
 
 `git rebase` has two primary backends: 'apply' and 'merge'.  (The 'apply'
 backend used to be known as the 'am' backend, but the name led to
diff --git a/Documentation/git-reflog.adoc b/Documentation/git-reflog.adoc
index 412f06b..38af0c9 100644
--- a/Documentation/git-reflog.adoc
+++ b/Documentation/git-reflog.adoc
@@ -8,16 +8,17 @@
 
 SYNOPSIS
 --------
-[verse]
-'git reflog' [show] [<log-options>] [<ref>]
-'git reflog list'
-'git reflog expire' [--expire=<time>] [--expire-unreachable=<time>]
+[synopsis]
+git reflog [show] [<log-options>] [<ref>]
+git reflog list
+git reflog exists <ref>
+git reflog write <ref> <old-oid> <new-oid> <message>
+git reflog delete [--rewrite] [--updateref]
+	[--dry-run | -n] [--verbose] <ref>@{<specifier>}...
+git reflog drop [--all [--single-worktree] | <refs>...]
+git reflog expire [--expire=<time>] [--expire-unreachable=<time>]
 	[--rewrite] [--updateref] [--stale-fix]
 	[--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]
-'git reflog delete' [--rewrite] [--updateref]
-	[--dry-run | -n] [--verbose] <ref>@{<specifier>}...
-'git reflog drop' [--all [--single-worktree] | <refs>...]
-'git reflog exists' <ref>
 
 DESCRIPTION
 -----------
@@ -43,11 +44,15 @@
 
 The "list" subcommand lists all refs which have a corresponding reflog.
 
-The "expire" subcommand prunes older reflog entries. Entries older
-than `expire` time, or entries older than `expire-unreachable` time
-and not reachable from the current tip, are removed from the reflog.
-This is typically not used directly by end users -- instead, see
-linkgit:git-gc[1].
+The "exists" subcommand checks whether a ref has a reflog.  It exits
+with zero status if the reflog exists, and non-zero status if it does
+not.
+
+The "write" subcommand writes a single entry to the reflog of a given
+reference. This new entry is appended to the reflog and will thus become
+the most recent entry. The reference name must be fully qualified. Both the old
+and new object IDs must not be abbreviated and must point to existing objects.
+The reflog message gets normalized.
 
 The "delete" subcommand deletes single entries from the reflog, but
 not the reflog itself. Its argument must be an _exact_ entry (e.g. "`git
@@ -58,9 +63,11 @@
 references. This is in contrast to "expire" and "delete", both of which
 can be used to delete reflog entries, but not the reflog itself.
 
-The "exists" subcommand checks whether a ref has a reflog.  It exits
-with zero status if the reflog exists, and non-zero status if it does
-not.
+The "expire" subcommand prunes older reflog entries. Entries older
+than `expire` time, or entries older than `expire-unreachable` time
+and not reachable from the current tip, are removed from the reflog.
+This is typically not used directly by end users -- instead, see
+linkgit:git-gc[1].
 
 OPTIONS
 -------
@@ -71,65 +78,6 @@
 `git reflog show` accepts any of the options accepted by `git log`.
 
 
-Options for `expire`
-~~~~~~~~~~~~~~~~~~~~
-
---all::
-	Process the reflogs of all references.
-
---single-worktree::
-	By default when `--all` is specified, reflogs from all working
-	trees are processed. This option limits the processing to reflogs
-	from the current working tree only.
-
---expire=<time>::
-	Prune entries older than the specified time. If this option is
-	not specified, the expiration time is taken from the
-	configuration setting `gc.reflogExpire`, which in turn
-	defaults to 90 days. `--expire=all` prunes entries regardless
-	of their age; `--expire=never` turns off pruning of reachable
-	entries (but see `--expire-unreachable`).
-
---expire-unreachable=<time>::
-	Prune entries older than `<time>` that are not reachable from
-	the current tip of the branch. If this option is not
-	specified, the expiration time is taken from the configuration
-	setting `gc.reflogExpireUnreachable`, which in turn defaults
-	to 30 days. `--expire-unreachable=all` prunes unreachable
-	entries regardless of their age; `--expire-unreachable=never`
-	turns off early pruning of unreachable entries (but see
-	`--expire`).
-
---updateref::
-	Update the reference to the value of the top reflog entry (i.e.
-	<ref>@\{0\}) if the previous top entry was pruned.  (This
-	option is ignored for symbolic references.)
-
---rewrite::
-	If a reflog entry's predecessor is pruned, adjust its "old"
-	SHA-1 to be equal to the "new" SHA-1 field of the entry that
-	now precedes it.
-
---stale-fix::
-	Prune any reflog entries that point to "broken commits". A
-	broken commit is a commit that is not reachable from any of
-	the reference tips and that refers, directly or indirectly, to
-	a missing commit, tree, or blob object.
-+
-This computation involves traversing all the reachable objects, i.e. it
-has the same cost as 'git prune'.  It is primarily intended to fix
-corruption caused by garbage collecting using older versions of Git,
-which didn't protect objects referred to by reflogs.
-
--n::
---dry-run::
-	Do not actually prune any entries; just show what would have
-	been pruned.
-
---verbose::
-	Print extra information on screen.
-
-
 Options for `delete`
 ~~~~~~~~~~~~~~~~~~~~
 
@@ -140,14 +88,74 @@
 Options for `drop`
 ~~~~~~~~~~~~~~~~~~
 
---all::
+`--all`::
 	Drop the reflogs of all references from all worktrees.
 
---single-worktree::
+`--single-worktree`::
 	By default when `--all` is specified, reflogs from all working
 	trees are dropped. This option limits the processing to reflogs
 	from the current working tree only.
 
+
+Options for `expire`
+~~~~~~~~~~~~~~~~~~~~
+
+`--all`::
+	Process the reflogs of all references.
+
+`--single-worktree`::
+	By default when `--all` is specified, reflogs from all working
+	trees are processed. This option limits the processing to reflogs
+	from the current working tree only.
+
+`--expire=<time>`::
+	Prune entries older than the specified time. If this option is
+	not specified, the expiration time is taken from the
+	configuration setting `gc.reflogExpire`, which in turn
+	defaults to 90 days. `--expire=all` prunes entries regardless
+	of their age; `--expire=never` turns off pruning of reachable
+	entries (but see `--expire-unreachable`).
+
+`--expire-unreachable=<time>`::
+	Prune entries older than `<time>` that are not reachable from
+	the current tip of the branch. If this option is not
+	specified, the expiration time is taken from the configuration
+	setting `gc.reflogExpireUnreachable`, which in turn defaults
+	to 30 days. `--expire-unreachable=all` prunes unreachable
+	entries regardless of their age; `--expire-unreachable=never`
+	turns off early pruning of unreachable entries (but see
+	`--expire`).
+
+`--updateref`::
+	Update the reference to the value of the top reflog entry (i.e.
+	<ref>@\{0\}) if the previous top entry was pruned.  (This
+	option is ignored for symbolic references.)
+
+`--rewrite`::
+	If a reflog entry's predecessor is pruned, adjust its "old"
+	SHA-1 to be equal to the "new" SHA-1 field of the entry that
+	now precedes it.
+
+`--stale-fix`::
+	Prune any reflog entries that point to "broken commits". A
+	broken commit is a commit that is not reachable from any of
+	the reference tips and that refers, directly or indirectly, to
+	a missing commit, tree, or blob object.
++
+This computation involves traversing all the reachable objects, i.e. it
+has the same cost as 'git prune'.  It is primarily intended to fix
+corruption caused by garbage collecting using older versions of Git,
+which didn't protect objects referred to by reflogs.
+
+`-n`::
+`--dry-run`::
+	Do not actually prune any entries; just show what would have
+	been pruned.
+
+`--verbose`::
+	Print extra information on screen.
+
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc
index 4d6dc99..fa33680 100644
--- a/Documentation/git-refs.adoc
+++ b/Documentation/git-refs.adoc
@@ -11,6 +11,15 @@
 [synopsis]
 git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]
 git refs verify [--strict] [--verbose]
+git refs list [--count=<count>] [--shell|--perl|--python|--tcl]
+		   [(--sort=<key>)...] [--format=<format>]
+		   [--include-root-refs] [--points-at=<object>]
+		   [--merged[=<object>]] [--no-merged[=<object>]]
+		   [--contains[=<object>]] [--no-contains[=<object>]]
+		   [(--exclude=<pattern>)...] [--start-after=<marker>]
+		   [ --stdin | (<pattern>...)]
+git refs exists <ref>
+git refs optimize [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]
 
 DESCRIPTION
 -----------
@@ -20,43 +29,67 @@
 COMMANDS
 --------
 
-migrate::
+`migrate`::
 	Migrate ref store between different formats.
 
-verify::
+`verify`::
 	Verify reference database consistency.
 
+list::
+	List references in the repository with support for filtering,
+	formatting, and sorting. This subcommand is an alias for
+	linkgit:git-for-each-ref[1] and offers identical functionality.
+
+exists::
+	Check whether the given reference exists. Returns an exit code of 0 if
+	it does, 2 if it is missing, and 1 in case looking up the reference
+	failed with an error other than the reference being missing. This does
+	not verify whether the reference resolves to an actual object.
+
+optimize::
+	Optimizes references to improve repository performance and reduce disk
+	usage. This subcommand is an alias for linkgit:git-pack-refs[1] and
+	offers identical functionality.
+
 OPTIONS
 -------
 
-The following options are specific to 'git refs migrate':
+The following options are specific to `git refs migrate`:
 
---ref-format=<format>::
+`--ref-format=<format>`::
 	The ref format to migrate the ref store to. Can be one of:
 +
 include::ref-storage-format.adoc[]
 
---dry-run::
+`--dry-run`::
 	Perform the migration, but do not modify the repository. The migrated
 	refs will be written into a separate directory that can be inspected
 	separately. The name of the directory will be reported on stdout. This
 	can be used to double check that the migration works as expected before
 	performing the actual migration.
 
---reflog::
---no-reflog::
+`--reflog`::
+`--no-reflog`::
 	Choose between migrating the reflog data to the new backend,
 	and discarding them.  The default is "--reflog", to migrate.
 
-The following options are specific to 'git refs verify':
+The following options are specific to `git refs verify`:
 
---strict::
+`--strict`::
 	Enable stricter error checking. This will cause warnings to be
 	reported as errors. See linkgit:git-fsck[1].
 
---verbose::
+`--verbose`::
 	When verifying the reference database consistency, be chatty.
 
+The following options are specific to 'git refs list':
+
+include::for-each-ref-options.adoc[]
+
+The following options are specific to 'git refs optimize':
+
+include::pack-refs-options.adoc[]
+
 KNOWN LIMITATIONS
 -----------------
 
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
new file mode 100644
index 0000000..209afd1
--- /dev/null
+++ b/Documentation/git-repo.adoc
@@ -0,0 +1,89 @@
+git-repo(1)
+===========
+
+NAME
+----
+git-repo - Retrieve information about the repository
+
+SYNOPSIS
+--------
+[synopsis]
+git repo info [--format=(keyvalue|nul)] [-z] [<key>...]
+
+DESCRIPTION
+-----------
+Retrieve information about the repository.
+
+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
+
+COMMANDS
+--------
+`info [--format=(keyvalue|nul)] [-z] [<key>...]`::
+	Retrieve metadata-related information about the current repository. Only
+	the requested data will be returned based on their keys (see "INFO KEYS"
+	section below).
++
+The values are returned in the same order in which their respective keys were
+requested.
++
+The output format can be chosen through the flag `--format`. Two formats are
+supported:
++
+`keyvalue`:::
+	output key-value pairs one per line using the `=` character as
+	the delimiter between the key and the value. Values containing "unusual"
+	characters are quoted as explained for the configuration variable
+	`core.quotePath` (see linkgit:git-config[1]). This is the default.
+
+`nul`:::
+	similar to `keyvalue`, but using a newline character as the delimiter
+	between the key and the value and using a NUL character after each value.
+	This format is better suited for being parsed by another applications than
+	`keyvalue`. Unlike in the `keyvalue` format, the values are never quoted.
++
+`-z` is an alias for `--format=nul`.
+
+INFO KEYS
+---------
+In order to obtain a set of values from `git repo info`, you should provide
+the keys that identify them. Here's a list of the available keys and the
+values that they return:
+
+`layout.bare`::
+	`true` if this is a bare repository, otherwise `false`.
+
+`layout.shallow`::
+	`true` if this is a shallow repository, otherwise `false`.
+
+`object.format`::
+	The object format (hash algorithm) used in the repository.
+
+`references.format`::
+	The reference storage format. The valid values are:
++
+include::ref-storage-format.adoc[]
+
+EXAMPLES
+--------
+
+* Retrieves the reference format of the current repository:
++
+------------
+git repo info references.format
+------------
++
+
+* Retrieves whether the current repository is bare and whether it is shallow
+using the `nul` format:
++
+------------
+git repo info --format=nul layout.bare layout.shallow
+------------
+
+SEE ALSO
+--------
+linkgit:git-rev-parse[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-reset.adoc b/Documentation/git-reset.adoc
index 50e8a0b..3b9ba9a 100644
--- a/Documentation/git-reset.adoc
+++ b/Documentation/git-reset.adoc
@@ -90,7 +90,8 @@
 	If a file that is different between _<commit>_ and `HEAD` has local
 	changes, reset is aborted.
 
-`--[no-]recurse-submodules`::
+`--recurse-submodules`::
+`--no-recurse-submodules`::
 	When the working tree is updated, using `--recurse-submodules` will
 	also recursively reset the working tree of all active submodules
 	according to the commit recorded in the superproject, also setting
diff --git a/Documentation/git-send-email.adoc b/Documentation/git-send-email.adoc
index 5335502..263b977 100644
--- a/Documentation/git-send-email.adoc
+++ b/Documentation/git-send-email.adoc
@@ -115,7 +115,8 @@
 Only necessary if `--compose` is also set.  If `--compose`
 is not set, this will be prompted for.
 
---[no-]outlook-id-fix::
+--outlook-id-fix::
+--no-outlook-id-fix::
 	Microsoft Outlook SMTP servers discard the Message-ID sent via email and
 	assign a new random Message-ID, thus breaking threads.
 +
@@ -299,6 +300,32 @@
 	commands and replies will be printed. Useful to debug TLS
 	connection and authentication problems.
 
+--imap-sent-folder=<folder>::
+	Some email providers (e.g. iCloud) do not send a copy of the emails sent
+	using SMTP to the `Sent` folder or similar in your mailbox. Use this option
+	to use `git imap-send` to send a copy of the emails to the folder specified
+	using this option. You can run `git imap-send --list` to get a list of
+	valid folder names, including the correct name of the `Sent` folder in
+	your mailbox. You can also use this option to send emails to a dedicated
+	IMAP folder of your choice.
++
+This feature requires setting up `git imap-send`. See linkgit:git-imap-send[1]
+for instructions.
+
+--use-imap-only::
+--no-use-imap-only::
+	If this is set, all emails will only be copied to the IMAP folder specified
+	with `--imap-sent-folder` or `sendemail.imapSentFolder` and will not be sent
+	to the recipients. Useful if you just want to create a draft of the emails
+	and use another email client to send them.
+	If disabled with `--no-use-imap-only`, the emails will be sent like usual.
+	Disabled by default, but the `sendemail.useImapOnly` configuration
+	variable can be used to enable it.
+
++
+This feature requires setting up `git imap-send`. See linkgit:git-imap-send[1]
+for instructions.
+
 --batch-size=<num>::
 	Some email servers (e.g. 'smtp.163.com') limit the number of emails to be
 	sent per session (connection) and this will lead to a failure when
@@ -350,7 +377,8 @@
 --no-header-cmd::
 	Disable any header command in use.
 
---[no-]chain-reply-to::
+--chain-reply-to::
+--no-chain-reply-to::
 	If this is set, each email will be sent as a reply to the previous
 	email sent.  If disabled with `--no-chain-reply-to`, all emails after
 	the first will be sent as replies to the first email sent.  When using
@@ -364,19 +392,22 @@
 	values in the `sendemail` section. The default identity is
 	the value of `sendemail.identity`.
 
---[no-]signed-off-by-cc::
+--signed-off-by-cc::
+--no-signed-off-by-cc::
 	If this is set, add emails found in the `Signed-off-by` trailer or `Cc:`
 	lines to the cc list. Default is the value of `sendemail.signedOffByCc`
 	configuration value; if that is unspecified, default to
 	`--signed-off-by-cc`.
 
---[no-]cc-cover::
+--cc-cover::
+--no-cc-cover::
 	If this is set, emails found in `Cc:` headers in the first patch of
 	the series (typically the cover letter) are added to the cc list
 	for each email set. Default is the value of `sendemail.ccCover`
 	configuration value; if that is unspecified, default to `--no-cc-cover`.
 
---[no-]to-cover::
+--to-cover::
+--no-to-cover::
 	If this is set, emails found in `To:` headers in the first patch of
 	the series (typically the cover letter) are added to the to list
 	for each email set. Default is the value of `sendemail.toCover`
@@ -407,12 +438,14 @@
 that is unspecified, default to `self` if `--suppress-from` is
 specified, as well as `body` if `--no-signed-off-cc` is specified.
 
---[no-]suppress-from::
+--suppress-from::
+--no-suppress-from::
 	If this is set, do not add the `From:` address to the `Cc:` list.
 	Default is the value of `sendemail.suppressFrom` configuration
 	value; if that is unspecified, default to `--no-suppress-from`.
 
---[no-]thread::
+--thread::
+--no-thread::
 	If this is set, the `In-Reply-To` and `References` headers will be
 	added to each email sent.  Whether each mail refers to the
 	previous email (`deep` threading per `git format-patch`
@@ -430,7 +463,8 @@
 Failure to do so may not produce the expected result in the
 recipient's MUA.
 
---[no-]mailmap::
+--mailmap::
+--no-mailmap::
 	Use the mailmap file (see linkgit:gitmailmap[5]) to map all
 	addresses to their canonical real name and email address. Additional
 	mailmap data specific to `git send-email` may be provided using the
@@ -459,7 +493,8 @@
 --dry-run::
 	Do everything except actually send the emails.
 
---[no-]format-patch::
+--format-patch::
+--no-format-patch::
 	When an argument may be understood either as a reference or as a file name,
 	choose to understand it as a format-patch argument (`--format-patch`)
 	or as a file name (`--no-format-patch`). By default, when such a conflict
@@ -469,7 +504,8 @@
 	Make `git send-email` less verbose.  One line per email should be
 	all that is output.
 
---[no-]validate::
+--validate::
+--no-validate::
 	Perform sanity checks on patches.
 	Currently, validation means the following:
 +
@@ -521,10 +557,10 @@
 
 ----
 [sendemail]
-	smtpEncryption = tls
+	smtpEncryption = ssl
 	smtpServer = smtp.gmail.com
 	smtpUser = yourname@gmail.com
-	smtpServerPort = 587
+	smtpServerPort = 465
 ----
 
 Gmail does not allow using your regular password for `git send-email`.
@@ -542,10 +578,10 @@
 
 ----
 [sendemail]
-	smtpEncryption = tls
+	smtpEncryption = ssl
 	smtpServer = smtp.gmail.com
 	smtpUser = yourname@gmail.com
-	smtpServerPort = 587
+	smtpServerPort = 465
 	smtpAuth = OAUTHBEARER
 ----
 
diff --git a/Documentation/git-send-pack.adoc b/Documentation/git-send-pack.adoc
index b9e73f2..811193f 100644
--- a/Documentation/git-send-pack.adoc
+++ b/Documentation/git-send-pack.adoc
@@ -71,7 +71,8 @@
 	fails to update then the entire push will fail without changing any
 	refs.
 
---[no-]signed::
+--signed::
+--no-signed::
 --signed=(true|false|if-asked)::
 	GPG-sign the push request to update refs on the receiving
 	side, to allow it to be checked by the hooks and/or be
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-submodule.adoc b/Documentation/git-submodule.adoc
index 503c84a..95beaee 100644
--- a/Documentation/git-submodule.adoc
+++ b/Documentation/git-submodule.adoc
@@ -442,7 +442,8 @@
 	clone with a history truncated to the specified number of revisions.
 	See linkgit:git-clone[1]
 
---[no-]recommend-shallow::
+--recommend-shallow::
+--no-recommend-shallow::
 	This option is only valid for the update command.
 	The initial clone of a submodule will use the recommended
 	`submodule.<name>.shallow` as provided by the `.gitmodules` file
@@ -454,7 +455,8 @@
 	Clone new submodules in parallel with as many jobs.
 	Defaults to the `submodule.fetchJobs` option.
 
---[no-]single-branch::
+--single-branch::
+--no-single-branch::
 	This option is only valid for the update command.
 	Clone only one branch during update: HEAD or one specified by --branch.
 
diff --git a/Documentation/git-svn.adoc b/Documentation/git-svn.adoc
index bcf7d84..c26c12b 100644
--- a/Documentation/git-svn.adoc
+++ b/Documentation/git-svn.adoc
@@ -1012,9 +1012,11 @@
 
 If you do merge, note the following rule: 'git svn dcommit' will
 attempt to commit on top of the SVN commit named in
+
 ------------------------------------------------------------------------
 git log --grep=^git-svn-id: --first-parent -1
 ------------------------------------------------------------------------
+
 You 'must' therefore ensure that the most recent commit of the branch
 you want to dcommit to is the 'first' parent of the merge.  Chaos will
 ensue otherwise, especially if the first parent is an older commit on
diff --git a/Documentation/git-tag.adoc b/Documentation/git-tag.adoc
index a4b1c0e..0f7badc 100644
--- a/Documentation/git-tag.adoc
+++ b/Documentation/git-tag.adoc
@@ -8,21 +8,21 @@
 
 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.
@@ -58,129 +58,129 @@
 
 OPTIONS
 -------
--a::
---annotate::
+`-a`::
+`--annotate`::
 	Make an unsigned, annotated tag object
 
--s::
---sign::
+`-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].
 
---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>::
+`-u <key-id>`::
+`--local-user=<key-id>`::
 	Make a GPG-signed tag, using the given key.
 
--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::
+`-v`::
+`--verify`::
 	Verify the GPG signature of the given tag names.
 
--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,46 +190,45 @@
 	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:
@@ -252,7 +251,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 +267,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 +324,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 +402,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-update-index.adoc b/Documentation/git-update-index.adoc
index 7128aed..9bea9fa 100644
--- a/Documentation/git-update-index.adoc
+++ b/Documentation/git-update-index.adoc
@@ -86,7 +86,8 @@
 --chmod=(+|-)x::
         Set the execute permissions on the updated files.
 
---[no-]assume-unchanged::
+--assume-unchanged::
+--no-assume-unchanged::
 	When this flag is specified, the object names recorded
 	for the paths are not updated.  Instead, this option
 	sets/unsets the "assume unchanged" bit for the
@@ -108,18 +109,21 @@
 	Like `--refresh`, but checks stat information unconditionally,
 	without regard to the "assume unchanged" setting.
 
---[no-]skip-worktree::
+--skip-worktree::
+--no-skip-worktree::
 	When one of these flags is specified, the object names recorded
 	for the paths are not updated. Instead, these options
 	set and unset the "skip-worktree" bit for the paths. See
 	section "Skip-worktree bit" below for more information.
 
 
---[no-]ignore-skip-worktree-entries::
+--ignore-skip-worktree-entries::
+--no-ignore-skip-worktree-entries::
 	Do not remove skip-worktree (AKA "index-only") entries even when
 	the `--remove` option was specified.
 
---[no-]fsmonitor-valid::
+--fsmonitor-valid::
+--no-fsmonitor-valid::
 	When one of these flags is specified, the object names recorded
 	for the paths are not updated. Instead, these options
 	set and unset the "fsmonitor valid" bit for the paths. See
diff --git a/Documentation/git-upload-pack.adoc b/Documentation/git-upload-pack.adoc
index 516d163..9167a32 100644
--- a/Documentation/git-upload-pack.adoc
+++ b/Documentation/git-upload-pack.adoc
@@ -25,7 +25,8 @@
 OPTIONS
 -------
 
---[no-]strict::
+--strict::
+--no-strict::
 	Do not try <directory>/.git/ if <directory> is not a Git directory.
 
 --timeout=<n>::
diff --git a/Documentation/git-whatchanged.adoc b/Documentation/git-whatchanged.adoc
index d214840..436e219 100644
--- a/Documentation/git-whatchanged.adoc
+++ b/Documentation/git-whatchanged.adoc
@@ -15,7 +15,7 @@
 -------
 `git whatchanged` has been deprecated and is scheduled for removal in
 a future version of Git, as it is merely `git log` with different
-default; `whatchanged` is not even shorter to type than `log --raw`.
+defaults.
 
 DESCRIPTION
 -----------
@@ -24,7 +24,11 @@
 
 New users are encouraged to use linkgit:git-log[1] instead.  The
 `whatchanged` command is essentially the same as linkgit:git-log[1]
-but defaults to showing the raw format diff output and skipping merges.
+but defaults to showing the raw format diff output and skipping merges:
+
+----
+git log --raw --no-merges
+----
 
 The command is primarily kept for historical reasons; fingers of
 many people who learned Git long before `git log` was invented by
diff --git a/Documentation/git-worktree.adoc b/Documentation/git-worktree.adoc
index 8340b7f..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,69 +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].
 
---[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].
 
---[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.
 
---[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].
@@ -224,59 +227,60 @@
 With `repair`, the linking files will be updated if there's an absolute/relative
 mismatch, even if the links are correct.
 
---[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
@@ -518,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/git.adoc b/Documentation/git.adoc
index 743b7b0..ce099e7 100644
--- a/Documentation/git.adoc
+++ b/Documentation/git.adoc
@@ -219,7 +219,8 @@
 	List commands by group. This is an internal/experimental
 	option and may change or be removed in the future. Supported
 	groups are: builtins, parseopt (builtin commands that use
-	parse-options), main (all commands in libexec directory),
+	parse-options), deprecated (deprecated builtins),
+	main (all commands in libexec directory),
 	others (all other commands in `$PATH` that have git- prefix),
 	list-<category> (see categories in command-list.txt),
 	nohelpers (exclude helper commands), alias and config
@@ -684,7 +685,7 @@
 
 `GIT_PROGRESS_DELAY`::
 	A number controlling how many seconds to delay before showing
-	optional progress indicators. Defaults to 2.
+	optional progress indicators. Defaults to 1.
 
 `GIT_EDITOR`::
 	This environment variable overrides `$EDITOR` and `$VISUAL`.
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/gitcredentials.adoc b/Documentation/gitcredentials.adoc
index 3337bb4..60c2cc4 100644
--- a/Documentation/gitcredentials.adoc
+++ b/Documentation/gitcredentials.adoc
@@ -150,9 +150,8 @@
 	username = foo
 --------------------------------------
 
-then we will match: both protocols are the same, both hosts are the same, and
-the "pattern" URL does not care about the path component at all. However, this
-context would not match:
+then we will match: both protocols are the same and both hosts are the same.
+However, this context would not match:
 
 --------------------------------------
 [credential "https://kernel.org"]
@@ -166,11 +165,11 @@
 the domain name and other pattern matching techniques as with the `http.<URL>.*`
 options.
 
-If the "pattern" URL does include a path component, then this too must match
-exactly: the context `https://example.com/bar/baz.git` will match a config
-entry for `https://example.com/bar/baz.git` (in addition to matching the config
-entry for `https://example.com`) but will not match a config entry for
-`https://example.com/bar`.
+If the "pattern" URL does include a path component, then this must match
+as a prefix path: the context `https://example.com/bar` will match a config
+entry for `https://example.com/bar/baz.git` but will not match a config entry for
+`https://example.com/other/repo.git` or `https://example.com/barry/repo.git`
+(even though it is a string prefix).
 
 
 CONFIGURATION OPTIONS
diff --git a/Documentation/gitk.adoc b/Documentation/gitk.adoc
index 58ce40d..5b34dcd 100644
--- a/Documentation/gitk.adoc
+++ b/Documentation/gitk.adoc
@@ -163,16 +163,16 @@
 
 History
 -------
-Gitk was the first graphical repository browser. It's written in
-tcl/tk.
+Gitk was the first graphical repository browser, written by
+Paul Mackerras in Tcl/Tk.
 
 'gitk' is actually maintained as an independent project, but stable
 versions are distributed as part of the Git suite for the convenience
 of end users.
 
-gitk-git/ comes from Paul Mackerras's gitk project:
+`gitk-git/` comes from Johannes Sixt's gitk project:
 
-	git://ozlabs.org/~paulus/gitk
+	https://github.com/j6t/gitk
 
 SEE ALSO
 --------
diff --git a/Documentation/gitprotocol-http.adoc b/Documentation/gitprotocol-http.adoc
index ec40a55..d024010 100644
--- a/Documentation/gitprotocol-http.adoc
+++ b/Documentation/gitprotocol-http.adoc
@@ -318,7 +318,7 @@
 
 
 Smart Service git-upload-pack
-------------------------------
+-----------------------------
 This service reads from the repository pointed to by `$GIT_URL`.
 
 Clients MUST first perform ref discovery with
diff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc
index 9a57005..c7db103 100644
--- a/Documentation/gitprotocol-v2.adoc
+++ b/Documentation/gitprotocol-v2.adoc
@@ -785,33 +785,64 @@
 save themselves and the server(s) the request(s) needed to inspect the
 headers of that bundle or bundles.
 
-promisor-remote=<pr-infos>
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+promisor-remote=<pr-info>
+~~~~~~~~~~~~~~~~~~~~~~~~~
 
 The server may advertise some promisor remotes it is using or knows
 about to a client which may want to use them as its promisor remotes,
-instead of this repository. In this case <pr-infos> should be of the
+instead of this repository. In this case <pr-info> should be of the
 form:
 
-	pr-infos = pr-info | pr-infos ";" pr-info
+	pr-info = pr-fields | pr-info ";" pr-fields
 
-	pr-info = "name=" pr-name | "name=" pr-name "," "url=" pr-url
+	pr-fields = pr-field | pr-fields "," pr-field
 
-where `pr-name` is the urlencoded name of a promisor remote, and
-`pr-url` the urlencoded URL of that promisor remote.
+	pr-field = field-name "=" field-value
 
-In this case, if the client decides to use one or more promisor
-remotes the server advertised, it can reply with
-"promisor-remote=<pr-names>" where <pr-names> should be of the form:
+where all the `field-name` and `field-value` in a given `pr-fields`
+are field names and values related to a single promisor remote. A
+given `field-name` MUST NOT appear more than once in given
+`pr-fields`.
+
+The server MUST advertise at least the "name" and "url" field names
+along with the associated field values, which are the name of a valid
+remote and its URL, in each `pr-fields`. The "name" and "url" fields
+MUST appear first in each pr-fields, in that order.
+
+After these mandatory fields, the server MAY advertise the following
+optional fields in any order:
+
+`partialCloneFilter`:: The filter specification used by the remote.
+Clients can use this to determine if the remote's filtering strategy
+is compatible with their needs (e.g., checking if both use "blob:none").
+It corresponds to the "remote.<name>.partialCloneFilter" config setting.
+
+`token`:: An authentication token that clients can use when
+connecting to the remote. It corresponds to the "remote.<name>.token"
+config setting.
+
+No other fields are defined by the protocol at this time. Field names
+are case-sensitive and MUST be transmitted exactly as specified
+above. Clients MUST ignore fields they don't recognize to allow for
+future protocol extensions.
+
+For now, the client can only use information transmitted through these
+fields to decide if it accepts the advertised promisor remote. In the
+future that information might be used for other purposes though.
+
+Field values MUST be urlencoded.
+
+If the client decides to use one or more promisor remotes the server
+advertised, it can reply with "promisor-remote=<pr-names>" where
+<pr-names> should be of the form:
 
 	pr-names = pr-name | pr-names ";" pr-name
 
 where `pr-name` is the urlencoded name of a promisor remote the server
 advertised and the client accepts.
 
-Note that, everywhere in this document, `pr-name` MUST be a valid
-remote name, and the ';' and ',' characters MUST be encoded if they
-appear in `pr-name` or `pr-url`.
+Note that, everywhere in this document, the ';' and ',' characters
+MUST be encoded if they appear in `pr-name` or `field-value`.
 
 If the server doesn't know any promisor remote that could be good for
 a client to use, or prefers a client not to use any promisor remote it
@@ -822,9 +853,10 @@
 the server advertised, the client shouldn't advertise the
 "promisor-remote" capability at all in its reply.
 
-The "promisor.advertise" and "promisor.acceptFromServer" configuration
-options can be used on the server and client side to control what they
-advertise or accept respectively. See the documentation of these
+On the server side, the "promisor.advertise" and "promisor.sendFields"
+configuration options can be used to control what it advertises. On
+the client side, the "promisor.acceptFromServer" configuration option
+can be used to control what it accepts. See the documentation of these
 configuration options for more information.
 
 Note that in the future it would be nice if the "promisor-remote"
diff --git a/Documentation/gitsubmodules.adoc b/Documentation/gitsubmodules.adoc
index f7b5a25..2082296 100644
--- a/Documentation/gitsubmodules.adoc
+++ b/Documentation/gitsubmodules.adoc
@@ -8,6 +8,7 @@
 SYNOPSIS
 --------
  .gitmodules, $GIT_DIR/config
+
 ------------------
 git submodule
 git <command> --recurse-submodules
@@ -240,7 +241,7 @@
 
 
 Workflow for an artificially split repo
---------------------------------------
+---------------------------------------
 
   # Enable recursion for relevant commands, such that
   # regular commands recurse into submodules by default
diff --git a/Documentation/gitweb.conf.adoc b/Documentation/gitweb.conf.adoc
index 1348e9b..64bebb8 100644
--- a/Documentation/gitweb.conf.adoc
+++ b/Documentation/gitweb.conf.adoc
@@ -178,7 +178,7 @@
 	Show repository only if this file exists (in repository).  Only
 	effective if this variable evaluates to true.  Can be set when
 	building gitweb by setting `GITWEB_EXPORT_OK`.  This path is
-	relative to `GIT_DIR`.  git-daemon[1] uses 'git-daemon-export-ok',
+	relative to `GIT_DIR`.  linkgit:git-daemon[1] uses 'git-daemon-export-ok',
 	unless started with `--export-all`.  By default this variable is
 	not set, which means that this feature is turned off.
 
diff --git a/Documentation/howto/meson.build b/Documentation/howto/meson.build
index 8100002..ece2024 100644
--- a/Documentation/howto/meson.build
+++ b/Documentation/howto/meson.build
@@ -29,7 +29,7 @@
   output: 'howto-index.adoc',
 )
 
-custom_target(
+doc_targets += custom_target(
   command: asciidoc_html_options,
   input: howto_index,
   output: 'howto-index.html',
@@ -51,7 +51,7 @@
     capture: true,
   )
 
-  custom_target(
+  doc_targets += custom_target(
     command: asciidoc_html_options,
     input: howto_stripped,
     output: fs.stem(howto_stripped.full_path()) + '.html',
diff --git a/Documentation/lint-delimited-sections.perl b/Documentation/lint-delimited-sections.perl
new file mode 100755
index 0000000..140b852
--- /dev/null
+++ b/Documentation/lint-delimited-sections.perl
@@ -0,0 +1,48 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+my $exit_code = 0;
+sub report {
+	my ($msg) = @_;
+	print STDERR "$ARGV:$.: $msg\n";
+	$exit_code = 1;
+}
+
+my $line_length = 0;
+my $in_section = 0;
+my $section_header = "";
+
+
+while (my $line = <>) {
+	if (($line =~ /^\+?$/) ||
+	    ($line =~ /^\[.*\]$/) ||
+	    ($line =~ /^ifdef::/)) {
+		$line_length = 0;
+	} elsif ($line =~ /^[^-.]/) {
+		$line_length = length($line);
+	} elsif (($line =~ /^-{3,}$/) || ($line =~ /^\.{3,}$/)) {
+		if ($in_section) {
+			if ($line eq $section_header) {
+				$in_section = 0;
+			}
+		next;
+		}
+		if ($line_length == 0) {
+			$in_section = 1;
+			$section_header = $line;
+			next;
+		}
+		if (($line_length != 0) && (length($line) != $line_length)) {
+			report("section delimiter not preceded by an empty line");
+		}
+		$line_length = 0;
+	}
+}
+
+if ($in_section) {
+	report("section not finished");
+}
+
+exit $exit_code;
diff --git a/Documentation/lint-documentation-style.perl b/Documentation/lint-documentation-style.perl
new file mode 100755
index 0000000..d7ab732
--- /dev/null
+++ b/Documentation/lint-documentation-style.perl
@@ -0,0 +1,33 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+my $exit_code = 0;
+sub report {
+	my ($line, $msg) = @_;
+	chomp $line;
+	print STDERR "$ARGV:$.: '$line' $msg\n";
+	$exit_code = 1;
+}
+
+my $synopsis_style = 0;
+
+while (my $line = <>) {
+	if ($line =~ /^[ \t]*`?[-a-z0-9.]+`?(, `?[-a-z0-9.]+`?)+(::|;;)$/) {
+
+		report($line, "multiple parameters in a definition list item");
+	}
+	if ($line =~ /^`?--\[no-\][a-z0-9-]+.*(::|;;)$/) {
+		report($line, "definition list item with a `--[no-]` parameter");
+	}
+	if ($line =~ /^\[synopsis\]$/) {
+		$synopsis_style = 1;
+	}
+	if (($line =~ /^(-[-a-z].*|<[-a-z0-9]+>(\.{3})?)(::|;;)$/) && ($synopsis_style)) {
+			report($line, "synopsis style and definition list item not backquoted");
+	}
+}
+
+
+exit $exit_code;
diff --git a/Documentation/lint-gitlink.perl b/Documentation/lint-gitlink.perl
index aea564d..f183a18 100755
--- a/Documentation/lint-gitlink.perl
+++ b/Documentation/lint-gitlink.perl
@@ -41,6 +41,13 @@
 @ARGV = $to_check;
 while (<>) {
 	my $line = $_;
+	while ($line =~ m/(.{,8})((git[-a-z]+|scalar)\[(\d)*\])/g) {
+	    my $pos = pos $line;
+	    my ($macro, $target, $page, $section) = ($1, $2, $3, $4);
+		if ( $macro ne "linkgit:" && $macro !~ "ifn?def::" && $macro ne "endif::" ) {
+			report($pos, $line, $target, "linkgit: macro expected");
+		}
+	}
 	while ($line =~ m/linkgit:((.*?)\[(\d)\])/g) {
 		my $pos = pos $line;
 		my ($target, $page, $section) = ($1, $2, $3);
diff --git a/Documentation/merge-options.adoc b/Documentation/merge-options.adoc
index 95ef491..9d43326 100644
--- a/Documentation/merge-options.adoc
+++ b/Documentation/merge-options.adoc
@@ -135,7 +135,8 @@
 Only useful when merging.
 endif::git-pull[]
 
-`--[no-]verify`::
+`--verify`::
+`--no-verify`::
 	By default, the pre-merge and commit-msg hooks are run.
 	When `--no-verify` is given, these are bypassed.
 	See also linkgit:githooks[5].
diff --git a/Documentation/mergetools/vimdiff.adoc b/Documentation/mergetools/vimdiff.adoc
index abfd426..b4ab83a 100644
--- a/Documentation/mergetools/vimdiff.adoc
+++ b/Documentation/mergetools/vimdiff.adoc
@@ -3,6 +3,7 @@
 
 When specifying `--tool=vimdiff` in `git mergetool` Git will open Vim with a 4
 windows layout distributed in the following way:
+
 ....
 ------------------------------------------
 |             |           |              |
@@ -56,6 +57,7 @@
 +
 --
 If, for some reason, we are not interested in the `BASE` buffer.
+
 ....
 ------------------------------------------
 |             |           |              |
@@ -72,6 +74,7 @@
 Only the `MERGED` buffer will be shown. Note, however, that all the other
 ones are still loaded in vim, and you can access them with the "buffers"
 command.
+
 ....
 ------------------------------------------
 |                                        |
@@ -88,6 +91,7 @@
 When `MERGED` is not present in the layout, you must "mark" one of the
 buffers with an arobase (`@`). That will become the buffer you need to edit and
 save after resolving the conflicts.
+
 ....
 ------------------------------------------
 |                   |                    |
@@ -106,6 +110,7 @@
 Three tabs will open: the first one is a copy of the default layout, while
 the other two only show the differences between (`BASE` and `LOCAL`) and
 (`BASE` and `REMOTE`) respectively.
+
 ....
 ------------------------------------------
 | <TAB #1> |  TAB #2  |  TAB #3  |       |
@@ -119,6 +124,7 @@
 |                                        |
 ------------------------------------------
 ....
+
 ....
 ------------------------------------------
 |  TAB #1  | <TAB #2> |  TAB #3  |       |
@@ -132,6 +138,7 @@
 |                   |                    |
 ------------------------------------------
 ....
+
 ....
 ------------------------------------------
 |  TAB #1  |  TAB #2  | <TAB #3> |       |
@@ -151,6 +158,7 @@
 --
 Same as the previous example, but adds a fourth tab with the same
 information as the first tab, with a different layout.
+
 ....
 ---------------------------------------------
 |  TAB #1  |  TAB #2  |  TAB #3  | <TAB #4> |
diff --git a/Documentation/meson.build b/Documentation/meson.build
index 4404c62..44f94cd 100644
--- a/Documentation/meson.build
+++ b/Documentation/meson.build
@@ -74,6 +74,7 @@
   'git-init.adoc' : 1,
   'git-instaweb.adoc' : 1,
   'git-interpret-trailers.adoc' : 1,
+  'git-last-modified.adoc' : 1,
   'git-log.adoc' : 1,
   'git-ls-files.adoc' : 1,
   'git-ls-remote.adoc' : 1,
@@ -116,6 +117,7 @@
   'git-repack.adoc' : 1,
   'git-replace.adoc' : 1,
   'git-replay.adoc' : 1,
+  'git-repo.adoc' : 1,
   'git-request-pull.adoc' : 1,
   'git-rerere.adoc' : 1,
   'git-reset.adoc' : 1,
@@ -375,7 +377,7 @@
       output: fs.stem(manpage) + '.xml',
     )
 
-    custom_target(
+    doc_targets += custom_target(
       command: [
         xmlto,
         '-m', '@INPUT0@',
@@ -398,7 +400,7 @@
   endif
 
   if get_option('docs').contains('html')
-    custom_target(
+    doc_targets += custom_target(
       command: asciidoc_common_options + [
         '--backend=' + asciidoc_html,
         '--doctype=manpage',
@@ -450,7 +452,7 @@
     depends: documentation_deps,
   )
 
-  custom_target(
+  doc_targets += custom_target(
     command: [
       xsltproc,
       '--xinclude',
@@ -479,7 +481,7 @@
   ]
 
   foreach article : articles
-    custom_target(
+    doc_targets += custom_target(
       command: asciidoc_common_options + [
         '--backend=' + asciidoc_html,
         '--out-file=@OUTPUT@',
diff --git a/Documentation/pack-refs-options.adoc b/Documentation/pack-refs-options.adoc
new file mode 100644
index 0000000..0b11282
--- /dev/null
+++ b/Documentation/pack-refs-options.adoc
@@ -0,0 +1,52 @@
+--all::
+
+The command by default packs all tags and refs that are already
+packed, and leaves other refs
+alone.  This is because branches are expected to be actively
+developed and packing their tips does not help performance.
+This option causes all refs to be packed as well, with the exception
+of hidden refs, broken refs, and symbolic refs. Useful for a repository
+with many branches of historical interests.
+
+--no-prune::
+
+The command usually removes loose refs under `$GIT_DIR/refs`
+hierarchy after packing them.  This option tells it not to.
+
+--auto::
+
+Pack refs as needed depending on the current state of the ref database. The
+behavior depends on the ref format used by the repository and may change in the
+future.
++
+	- "files": Loose references are packed into the `packed-refs` file
+	  based on the ratio of loose references to the size of the
+	  `packed-refs` file. The bigger the `packed-refs` file, the more loose
+	  references need to exist before we repack.
++
+	- "reftable": Tables are compacted such that they form a geometric
+	  sequence. For two tables N and N+1, where N+1 is newer, this
+	  maintains the property that N is at least twice as big as N+1. Only
+	  tables that violate this property are compacted.
+
+--include <pattern>::
+
+Pack refs based on a `glob(7)` pattern. Repetitions of this option
+accumulate inclusion patterns. If a ref is both included in `--include` and
+`--exclude`, `--exclude` takes precedence. Using `--include` will preclude all
+tags from being included by default. Symbolic refs and broken refs will never
+be packed. When used with `--all`, it will be a noop. Use `--no-include` to clear
+and reset the list of patterns.
+
+--exclude <pattern>::
+
+Do not pack refs matching the given `glob(7)` pattern. Repetitions of this option
+accumulate exclusion patterns. Use `--no-exclude` to clear and reset the list of
+patterns. If a ref is already packed, including it with `--exclude` will not
+unpack it.
++
+When used with `--all`, pack only loose refs which do not match any of
+the provided `--exclude` patterns.
++
+When used with `--include`, refs provided to `--include`, minus refs that are
+provided to `--exclude` will be packed.
diff --git a/Documentation/pretty-formats.adoc b/Documentation/pretty-formats.adoc
index 9ed0417..2121e8e 100644
--- a/Documentation/pretty-formats.adoc
+++ b/Documentation/pretty-formats.adoc
@@ -232,19 +232,21 @@
 colon and zero or more comma-separated options. Option values may contain
 literal formatting codes. These must be used for commas (`%x2C`) and closing
 parentheses (`%x29`), due to their role in the option syntax.
-+
-** `prefix=<value>`: Shown before the list of ref names.  Defaults to "{nbsp}+(+".
+
+** `prefix=<value>`: Shown before the list of ref names.  Defaults to "{nbsp}++(++".
 ** `suffix=<value>`: Shown after the list of ref names.  Defaults to "+)+".
 ** `separator=<value>`: Shown between ref names.  Defaults to "+,+{nbsp}".
 ** `pointer=<value>`: Shown between HEAD and the branch it points to, if any.
-		      Defaults to "{nbsp}+->+{nbsp}".
+		      Defaults to "{nbsp}++->++{nbsp}".
 ** `tag=<value>`: Shown before tag names. Defaults to "`tag:`{nbsp}".
 
 +
+--
 For example, to produce decorations with no wrapping
 or tag annotations, and spaces as separators:
-+
+
 ++%(decorate:prefix=,suffix=,tag=,separator= )++
+--
 
 ++%(describe++`[:<option>,...]`++)++::
 human-readable name, like linkgit:git-describe[1]; empty string for
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/scalar.adoc b/Documentation/scalar.adoc
index 4bd5b15..f81b283 100644
--- a/Documentation/scalar.adoc
+++ b/Documentation/scalar.adoc
@@ -71,7 +71,8 @@
 	Instead of checking out the branch pointed to by the cloned
 	repository's HEAD, check out the `<name>` branch instead.
 
---[no-]single-branch::
+--single-branch::
+--no-single-branch::
 	Clone only the history leading to the tip of a single branch, either
 	specified by the `--branch` option or the primary branch remote's
 	`HEAD` points at.
@@ -81,23 +82,27 @@
 cloning. If the HEAD at the remote did not point at any branch when
 `--single-branch` clone was made, no remote-tracking branch is created.
 
---[no-]src::
+--src::
+--no-src::
 	By default, `scalar clone` places the cloned repository within a
 	`<entlistment>/src` directory. Use `--no-src` to place the cloned
 	repository directly in the `<enlistment>` directory.
 
---[no-]tags::
+--tags::
+--no-tags::
 	By default, `scalar clone` will fetch the tag objects advertised by
 	the remote and future `git fetch` commands will do the same. Use
 	`--no-tags` to avoid fetching tags in `scalar clone` and to configure
 	the repository to avoid fetching tags in the future. To fetch tags after
 	cloning with `--no-tags`, run `git fetch --tags`.
 
---[no-]full-clone::
+--full-clone::
+--no-full-clone::
 	A sparse-checkout is initialized by default. This behavior can be
 	turned off via `--full-clone`.
 
---[no-]maintenance::
+--maintenance::
+--no-maintenance::
 	By default, `scalar clone` configures the enlistment to use Git's
 	background maintenance feature. Use the `--no-maintenance` to skip
 	this configuration.
@@ -122,7 +127,8 @@
 parent directory is considered to be the Scalar enlistment. If the worktree is
 _not_ called `src/`, it itself will be considered to be the Scalar enlistment.
 
---[no-]maintenance::
+--maintenance::
+--no-maintenance::
 	By default, `scalar register` configures the enlistment to use Git's
 	background maintenance feature. Use the `--no-maintenance` to skip
 	this configuration. This does not disable any maintenance that may
diff --git a/Documentation/technical/api-path-walk.adoc b/Documentation/technical/api-path-walk.adoc
index 34c905e..a67de1b 100644
--- a/Documentation/technical/api-path-walk.adoc
+++ b/Documentation/technical/api-path-walk.adoc
@@ -39,7 +39,10 @@
 the objects will be walked in a separate way based on those starting
 commits.
 
-`commits`, `blobs`, `trees`, `tags`::
+`commits`::
+`blobs`::
+`trees`::
+`tags`::
 	By default, these members are enabled and signal that the path-walk
 	API should call the `path_fn` on objects of these types. Specialized
 	applications could disable some options to make it simpler to walk
diff --git a/Documentation/technical/long-running-process-protocol.adoc b/Documentation/technical/long-running-process-protocol.adoc
index 6f33654..39bd89d 100644
--- a/Documentation/technical/long-running-process-protocol.adoc
+++ b/Documentation/technical/long-running-process-protocol.adoc
@@ -24,6 +24,7 @@
 it supports and a flush packet. Git expects to read a list of desired
 capabilities, which must be a subset of the supported capabilities list,
 and a flush packet as response:
+
 ------------------------
 packet:          git> git-filter-client
 packet:          git> version=2
diff --git a/Documentation/technical/meson.build b/Documentation/technical/meson.build
index a13aafc..858af81 100644
--- a/Documentation/technical/meson.build
+++ b/Documentation/technical/meson.build
@@ -46,7 +46,7 @@
   output: 'api-index.adoc',
 )
 
-custom_target(
+doc_targets += custom_target(
   command: asciidoc_html_options,
   input: api_index,
   output: 'api-index.html',
@@ -56,7 +56,7 @@
 )
 
 foreach article : api_docs + articles
-  custom_target(
+  doc_targets += custom_target(
     command: asciidoc_html_options,
     input: article,
     output: fs.stem(article) + '.html',
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/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 64cbc58..b16db85 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-DEF_VER=v2.51.0
+DEF_VER=v2.51.GIT
 
 LF='
 '
diff --git a/Makefile b/Makefile
index e11340c..1919d35 100644
--- a/Makefile
+++ b/Makefile
@@ -483,6 +483,14 @@
 # Define LIBPCREDIR=/foo/bar if your PCRE header and library files are
 # in /foo/bar/include and /foo/bar/lib directories.
 #
+# == Optional Rust support ==
+#
+# Define WITH_RUST if you want to include features and subsystems written in
+# Rust into Git. For now, Rust is still an optional feature of the build
+# process. With Git 3.0 though, Rust will always be enabled.
+#
+# Building Rust code requires Cargo.
+#
 # == SHA-1 and SHA-256 defines ==
 #
 # === SHA-1 backend ===
@@ -683,6 +691,7 @@
 OTHER_PROGRAMS =
 PROGRAM_OBJS =
 PROGRAMS =
+RUST_SOURCES =
 EXCLUDED_PROGRAMS =
 SCRIPT_PERL =
 SCRIPT_PYTHON =
@@ -883,7 +892,9 @@
 BUILT_INS += git-status$X
 BUILT_INS += git-switch$X
 BUILT_INS += git-version$X
+ifndef WITH_BREAKING_CHANGES
 BUILT_INS += git-whatchanged$X
+endif
 
 # what 'all' will build but not install in gitexecdir
 OTHER_PROGRAMS += git$X
@@ -916,8 +927,107 @@
 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
+else
+RUST_LIB = target/release/libgitcore.a
+endif
+
+GITLIBS = common-main.o $(LIB_FILE)
+EXTLIBS =
+
+GIT_USER_AGENT = git/$(GIT_VERSION)
+
+ifeq ($(wildcard sha1collisiondetection/lib/sha1.h),sha1collisiondetection/lib/sha1.h)
+DC_SHA1_SUBMODULE = auto
+endif
+
+# Set CFLAGS, LDFLAGS and other *FLAGS variables. These might be
+# tweaked by config.* below as well as the command-line, both of
+# which'll override these defaults.
+# Older versions of GCC may require adding "-std=gnu99" at the end.
+CFLAGS = -g -O2 -Wall
+LDFLAGS =
+CC_LD_DYNPATH = -Wl,-rpath,
+BASIC_CFLAGS = -I.
+BASIC_LDFLAGS =
+
+# library flags
+ARFLAGS = rcs
+PTHREAD_CFLAGS =
+
+# Rust flags
+CARGO_ARGS =
+ifndef V
+CARGO_ARGS += --quiet
+endif
+ifndef DEBUG
+CARGO_ARGS += --release
+endif
+
+# For the 'sparse' target
+SPARSE_FLAGS ?= -std=gnu99 -D__STDC_NO_VLA__
+SP_EXTRA_FLAGS =
+
+# For informing GIT-BUILD-OPTIONS of the SANITIZE=leak,address targets
+SANITIZE_LEAK =
+SANITIZE_ADDRESS =
+
+# For the 'coccicheck' target
+SPATCH_INCLUDE_FLAGS = --all-includes
+SPATCH_FLAGS =
+SPATCH_TEST_FLAGS =
+
+# If *.o files are present, have "coccicheck" depend on them, with
+# COMPUTE_HEADER_DEPENDENCIES this will speed up the common-case of
+# only needing to re-generate coccicheck results for the users of a
+# given API if it's changed, and not all files in the project. If
+# COMPUTE_HEADER_DEPENDENCIES=no this will be unset too.
+SPATCH_USE_O_DEPENDENCIES = YesPlease
+
+# Set SPATCH_CONCAT_COCCI to concatenate the contrib/cocci/*.cocci
+# files into a single contrib/cocci/ALL.cocci before running
+# "coccicheck".
+#
+# Pros:
+#
+# - Speeds up a one-shot run of "make coccicheck", as we won't have to
+#   parse *.[ch] files N times for the N *.cocci rules
+#
+# Cons:
+#
+# - Will make incremental development of *.cocci slower, as
+#   e.g. changing strbuf.cocci will re-run all *.cocci.
+#
+# - Makes error and performance analysis harder, as rules will be
+#   applied from a monolithic ALL.cocci, rather than
+#   e.g. strbuf.cocci. To work around this either undefine this, or
+#   generate a specific patch, e.g. this will always use strbuf.cocci,
+#   not ALL.cocci:
+#
+#	make contrib/coccinelle/strbuf.cocci.patch
+SPATCH_CONCAT_COCCI = YesPlease
+
+# Rebuild 'coccicheck' if $(SPATCH), its flags etc. change
+TRACK_SPATCH_DEFINES =
+TRACK_SPATCH_DEFINES += $(SPATCH)
+TRACK_SPATCH_DEFINES += $(SPATCH_INCLUDE_FLAGS)
+TRACK_SPATCH_DEFINES += $(SPATCH_FLAGS)
+TRACK_SPATCH_DEFINES += $(SPATCH_TEST_FLAGS)
+GIT-SPATCH-DEFINES: FORCE
+	@FLAGS='$(TRACK_SPATCH_DEFINES)'; \
+	    if test x"$$FLAGS" != x"`cat GIT-SPATCH-DEFINES 2>/dev/null`" ; then \
+		echo >&2 "    * new spatch flags"; \
+		echo "$$FLAGS" >GIT-SPATCH-DEFINES; \
+            fi
+
+include config.mak.uname
+-include config.mak.autogen
+-include config.mak
+
+ifdef DEVELOPER
+include config.mak.dev
+endif
 
 GENERATED_H += command-list.h
 GENERATED_H += config-list.h
@@ -974,7 +1084,6 @@
 LIB_OBJS += blob.o
 LIB_OBJS += bloom.o
 LIB_OBJS += branch.o
-LIB_OBJS += bulk-checkin.o
 LIB_OBJS += bundle-uri.o
 LIB_OBJS += bundle.o
 LIB_OBJS += cache-tree.o
@@ -1094,6 +1203,7 @@
 LIB_OBJS += pack-check.o
 LIB_OBJS += pack-mtimes.o
 LIB_OBJS += pack-objects.o
+LIB_OBJS += pack-refs.o
 LIB_OBJS += pack-revindex.o
 LIB_OBJS += pack-write.o
 LIB_OBJS += packfile.o
@@ -1135,6 +1245,20 @@
 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 += replace-object.o
 LIB_OBJS += repo-settings.o
@@ -1196,7 +1320,9 @@
 LIB_OBJS += usage.o
 LIB_OBJS += userdiff.o
 LIB_OBJS += utf8.o
+ifndef WITH_RUST
 LIB_OBJS += varint.o
+endif
 LIB_OBJS += version.o
 LIB_OBJS += versioncmp.o
 LIB_OBJS += walker.o
@@ -1207,6 +1333,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
@@ -1265,6 +1398,7 @@
 BUILTIN_OBJS += builtin/index-pack.o
 BUILTIN_OBJS += builtin/init-db.o
 BUILTIN_OBJS += builtin/interpret-trailers.o
+BUILTIN_OBJS += builtin/last-modified.o
 BUILTIN_OBJS += builtin/log.o
 BUILTIN_OBJS += builtin/ls-files.o
 BUILTIN_OBJS += builtin/ls-remote.o
@@ -1306,6 +1440,7 @@
 BUILTIN_OBJS += builtin/repack.o
 BUILTIN_OBJS += builtin/replace.o
 BUILTIN_OBJS += builtin/replay.o
+BUILTIN_OBJS += builtin/repo.o
 BUILTIN_OBJS += builtin/rerere.o
 BUILTIN_OBJS += builtin/reset.o
 BUILTIN_OBJS += builtin/rev-list.o
@@ -1354,6 +1489,7 @@
 THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
 
 CLAR_TEST_SUITES += u-ctype
+CLAR_TEST_SUITES += u-dir
 CLAR_TEST_SUITES += u-example-decorate
 CLAR_TEST_SUITES += u-hash
 CLAR_TEST_SUITES += u-hashmap
@@ -1385,93 +1521,8 @@
 
 UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
 
-# 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)
-EXTLIBS =
-
-GIT_USER_AGENT = git/$(GIT_VERSION)
-
-ifeq ($(wildcard sha1collisiondetection/lib/sha1.h),sha1collisiondetection/lib/sha1.h)
-DC_SHA1_SUBMODULE = auto
-endif
-
-# Set CFLAGS, LDFLAGS and other *FLAGS variables. These might be
-# tweaked by config.* below as well as the command-line, both of
-# which'll override these defaults.
-# Older versions of GCC may require adding "-std=gnu99" at the end.
-CFLAGS = -g -O2 -Wall
-LDFLAGS =
-CC_LD_DYNPATH = -Wl,-rpath,
-BASIC_CFLAGS = -I.
-BASIC_LDFLAGS =
-
-# library flags
-ARFLAGS = rcs
-PTHREAD_CFLAGS =
-
-# For the 'sparse' target
-SPARSE_FLAGS ?= -std=gnu99 -D__STDC_NO_VLA__
-SP_EXTRA_FLAGS =
-
-# For informing GIT-BUILD-OPTIONS of the SANITIZE=leak,address targets
-SANITIZE_LEAK =
-SANITIZE_ADDRESS =
-
-# For the 'coccicheck' target
-SPATCH_INCLUDE_FLAGS = --all-includes
-SPATCH_FLAGS =
-SPATCH_TEST_FLAGS =
-
-# If *.o files are present, have "coccicheck" depend on them, with
-# COMPUTE_HEADER_DEPENDENCIES this will speed up the common-case of
-# only needing to re-generate coccicheck results for the users of a
-# given API if it's changed, and not all files in the project. If
-# COMPUTE_HEADER_DEPENDENCIES=no this will be unset too.
-SPATCH_USE_O_DEPENDENCIES = YesPlease
-
-# Set SPATCH_CONCAT_COCCI to concatenate the contrib/cocci/*.cocci
-# files into a single contrib/cocci/ALL.cocci before running
-# "coccicheck".
-#
-# Pros:
-#
-# - Speeds up a one-shot run of "make coccicheck", as we won't have to
-#   parse *.[ch] files N times for the N *.cocci rules
-#
-# Cons:
-#
-# - Will make incremental development of *.cocci slower, as
-#   e.g. changing strbuf.cocci will re-run all *.cocci.
-#
-# - Makes error and performance analysis harder, as rules will be
-#   applied from a monolithic ALL.cocci, rather than
-#   e.g. strbuf.cocci. To work around this either undefine this, or
-#   generate a specific patch, e.g. this will always use strbuf.cocci,
-#   not ALL.cocci:
-#
-#	make contrib/coccinelle/strbuf.cocci.patch
-SPATCH_CONCAT_COCCI = YesPlease
-
-# Rebuild 'coccicheck' if $(SPATCH), its flags etc. change
-TRACK_SPATCH_DEFINES =
-TRACK_SPATCH_DEFINES += $(SPATCH)
-TRACK_SPATCH_DEFINES += $(SPATCH_INCLUDE_FLAGS)
-TRACK_SPATCH_DEFINES += $(SPATCH_FLAGS)
-TRACK_SPATCH_DEFINES += $(SPATCH_TEST_FLAGS)
-GIT-SPATCH-DEFINES: FORCE
-	@FLAGS='$(TRACK_SPATCH_DEFINES)'; \
-	    if test x"$$FLAGS" != x"`cat GIT-SPATCH-DEFINES 2>/dev/null`" ; then \
-		echo >&2 "    * new spatch flags"; \
-		echo "$$FLAGS" >GIT-SPATCH-DEFINES; \
-            fi
-
-include config.mak.uname
--include config.mak.autogen
--include config.mak
-
-ifdef DEVELOPER
-include config.mak.dev
-endif
+RUST_SOURCES += src/lib.rs
+RUST_SOURCES += src/varint.rs
 
 GIT-VERSION-FILE: FORCE
 	@OLD=$$(cat $@ 2>/dev/null || :) && \
@@ -1502,6 +1553,11 @@
 ALL_CFLAGS = $(DEVELOPER_CFLAGS) $(CPPFLAGS) $(CFLAGS) $(CFLAGS_APPEND)
 ALL_LDFLAGS = $(LDFLAGS) $(LDFLAGS_APPEND)
 
+ifdef WITH_RUST
+BASIC_CFLAGS += -DWITH_RUST
+GITLIBS += $(RUST_LIB)
+endif
+
 ifdef SANITIZE
 SANITIZERS := $(foreach flag,$(subst $(comma),$(space),$(SANITIZE)),$(flag))
 BASIC_CFLAGS += -fsanitize=$(SANITIZE) -fno-sanitize-recover=$(SANITIZE)
@@ -2718,30 +2774,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/error.o
-REFTABLE_OBJS += reftable/block.o
-REFTABLE_OBJS += reftable/blocksource.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
@@ -2762,9 +2794,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))
@@ -2916,11 +2947,11 @@
 $(LIB_FILE): $(LIB_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
-$(XDIFF_LIB): $(XDIFF_OBJS)
-	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
+$(RUST_LIB): Cargo.toml $(RUST_SOURCES)
+	$(QUIET_CARGO)cargo build $(CARGO_ARGS)
 
-$(REFTABLE_LIB): $(REFTABLE_OBJS)
-	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
+.PHONY: rust
+rust: $(RUST_LIB)
 
 export DEFAULT_EDITOR DEFAULT_PAGER
 
@@ -3760,12 +3791,13 @@
 	$(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)
 	$(RM) $(SP_OBJ)
 	$(RM) $(HCC)
+	$(RM) -r Cargo.lock target/
 	$(RM) version-def.h
 	$(RM) -r $(dep_dirs) $(compdb_dir) compile_commands.json
 	$(RM) $(test_bindir_programs)
@@ -3943,19 +3975,16 @@
 	$(MAKE) -C t/ unit-tests
 
 .PHONY: libgit-sys libgit-rs
-libgit-sys libgit-rs:
-	$(QUIET)(\
-		cd contrib/$@ && \
-		cargo build \
-	)
+libgit-sys:
+	$(QUIET)cargo build --manifest-path contrib/libgit-sys/Cargo.toml
+libgit-rs: libgit-sys
+	$(QUIET)cargo build --manifest-path contrib/libgit-rs/Cargo.toml
 ifdef INCLUDE_LIBGIT_RS
-all:: libgit-sys libgit-rs
+all:: libgit-rs
 endif
 
 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/RelNotes b/RelNotes
index 48f1577..6d16c00 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.51.0.adoc
\ No newline at end of file
+Documentation/RelNotes/2.52.0.adoc
\ No newline at end of file
diff --git a/add-interactive.c b/add-interactive.c
index 3e692b4..68fc095 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -20,14 +20,14 @@
 #include "prompt.h"
 #include "tree.h"
 
-static void init_color(struct repository *r, struct add_i_state *s,
+static void init_color(struct repository *r, enum git_colorbool use_color,
 		       const char *section_and_slot, char *dst,
 		       const char *default_color)
 {
 	char *key = xstrfmt("color.%s", section_and_slot);
 	const char *value;
 
-	if (!s->use_color)
+	if (!want_color(use_color))
 		dst[0] = '\0';
 	else if (repo_config_get_value(r, key, &value) ||
 		 color_parse(value, dst))
@@ -36,42 +36,64 @@ static void init_color(struct repository *r, struct add_i_state *s,
 	free(key);
 }
 
+static enum git_colorbool check_color_config(struct repository *r, const char *var)
+{
+	const char *value;
+	enum git_colorbool ret;
+
+	if (repo_config_get_value(r, var, &value))
+		ret = GIT_COLOR_UNKNOWN;
+	else
+		ret = git_config_colorbool(var, value);
+
+	/*
+	 * Do not rely on want_color() to fall back to color.ui for us. It uses
+	 * the value parsed by git_color_config(), which may not have been
+	 * called by the main command.
+	 */
+	if (ret == GIT_COLOR_UNKNOWN &&
+	    !repo_config_get_value(r, "color.ui", &value))
+		ret = git_config_colorbool("color.ui", value);
+
+	return ret;
+}
+
 void init_add_i_state(struct add_i_state *s, struct repository *r,
 		      struct add_p_opt *add_p_opt)
 {
-	const char *value;
-
 	s->r = r;
 	s->context = -1;
 	s->interhunkcontext = -1;
 
-	if (repo_config_get_value(r, "color.interactive", &value))
-		s->use_color = -1;
-	else
-		s->use_color =
-			git_config_colorbool("color.interactive", value);
-	s->use_color = want_color(s->use_color);
+	s->use_color_interactive = check_color_config(r, "color.interactive");
 
-	init_color(r, s, "interactive.header", s->header_color, GIT_COLOR_BOLD);
-	init_color(r, s, "interactive.help", s->help_color, GIT_COLOR_BOLD_RED);
-	init_color(r, s, "interactive.prompt", s->prompt_color,
-		   GIT_COLOR_BOLD_BLUE);
-	init_color(r, s, "interactive.error", s->error_color,
-		   GIT_COLOR_BOLD_RED);
+	init_color(r, s->use_color_interactive, "interactive.header",
+		   s->header_color, GIT_COLOR_BOLD);
+	init_color(r, s->use_color_interactive, "interactive.help",
+		   s->help_color, GIT_COLOR_BOLD_RED);
+	init_color(r, s->use_color_interactive, "interactive.prompt",
+		   s->prompt_color, GIT_COLOR_BOLD_BLUE);
+	init_color(r, s->use_color_interactive, "interactive.error",
+		   s->error_color, GIT_COLOR_BOLD_RED);
+	strlcpy(s->reset_color_interactive,
+		want_color(s->use_color_interactive) ? GIT_COLOR_RESET : "", COLOR_MAXLEN);
 
-	init_color(r, s, "diff.frag", s->fraginfo_color,
-		   diff_get_color(s->use_color, DIFF_FRAGINFO));
-	init_color(r, s, "diff.context", s->context_color, "fall back");
+	s->use_color_diff = check_color_config(r, "color.diff");
+
+	init_color(r, s->use_color_diff, "diff.frag", s->fraginfo_color,
+		   diff_get_color(s->use_color_diff, DIFF_FRAGINFO));
+	init_color(r, s->use_color_diff, "diff.context", s->context_color,
+		   "fall back");
 	if (!strcmp(s->context_color, "fall back"))
-		init_color(r, s, "diff.plain", s->context_color,
-			   diff_get_color(s->use_color, DIFF_CONTEXT));
-	init_color(r, s, "diff.old", s->file_old_color,
-		diff_get_color(s->use_color, DIFF_FILE_OLD));
-	init_color(r, s, "diff.new", s->file_new_color,
-		diff_get_color(s->use_color, DIFF_FILE_NEW));
-
-	strlcpy(s->reset_color,
-		s->use_color ? GIT_COLOR_RESET : "", COLOR_MAXLEN);
+		init_color(r, s->use_color_diff, "diff.plain",
+			   s->context_color,
+			   diff_get_color(s->use_color_diff, DIFF_CONTEXT));
+	init_color(r, s->use_color_diff, "diff.old", s->file_old_color,
+		   diff_get_color(s->use_color_diff, DIFF_FILE_OLD));
+	init_color(r, s->use_color_diff, "diff.new", s->file_new_color,
+		   diff_get_color(s->use_color_diff, DIFF_FILE_NEW));
+	strlcpy(s->reset_color_diff,
+		want_color(s->use_color_diff) ? GIT_COLOR_RESET : "", COLOR_MAXLEN);
 
 	FREE_AND_NULL(s->interactive_diff_filter);
 	repo_config_get_string(r, "interactive.difffilter",
@@ -109,7 +131,8 @@ void clear_add_i_state(struct add_i_state *s)
 	FREE_AND_NULL(s->interactive_diff_filter);
 	FREE_AND_NULL(s->interactive_diff_algorithm);
 	memset(s, 0, sizeof(*s));
-	s->use_color = -1;
+	s->use_color_interactive = GIT_COLOR_UNKNOWN;
+	s->use_color_diff = GIT_COLOR_UNKNOWN;
 }
 
 /*
@@ -221,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)
@@ -229,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;
@@ -1188,9 +1212,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps,
 	 * When color was asked for, use the prompt color for
 	 * highlighting, otherwise use square brackets.
 	 */
-	if (s.use_color) {
+	if (want_color(s.use_color_interactive)) {
 		data.color = s.prompt_color;
-		data.reset = s.reset_color;
+		data.reset = s.reset_color_interactive;
 	}
 	print_file_item_data.color = data.color;
 	print_file_item_data.reset = data.reset;
diff --git a/add-interactive.h b/add-interactive.h
index 4213dcd..da49502 100644
--- a/add-interactive.h
+++ b/add-interactive.h
@@ -12,16 +12,19 @@ struct add_p_opt {
 
 struct add_i_state {
 	struct repository *r;
-	int use_color;
+	enum git_colorbool use_color_interactive;
+	enum git_colorbool use_color_diff;
 	char header_color[COLOR_MAXLEN];
 	char help_color[COLOR_MAXLEN];
 	char prompt_color[COLOR_MAXLEN];
 	char error_color[COLOR_MAXLEN];
-	char reset_color[COLOR_MAXLEN];
+	char reset_color_interactive[COLOR_MAXLEN];
+
 	char fraginfo_color[COLOR_MAXLEN];
 	char context_color[COLOR_MAXLEN];
 	char file_old_color[COLOR_MAXLEN];
 	char file_new_color[COLOR_MAXLEN];
+	char reset_color_diff[COLOR_MAXLEN];
 
 	int use_single_key;
 	char *interactive_diff_filter, *interactive_diff_algorithm;
diff --git a/add-patch.c b/add-patch.c
index 302e6ba..9402dc7 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -300,7 +300,7 @@ static void err(struct add_p_state *s, const char *fmt, ...)
 	va_start(args, fmt);
 	fputs(s->s.error_color, stdout);
 	vprintf(fmt, args);
-	puts(s->s.reset_color);
+	puts(s->s.reset_color_interactive);
 	va_end(args);
 }
 
@@ -457,7 +457,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
 	}
 	strbuf_complete_line(plain);
 
-	if (want_color_fd(1, -1)) {
+	if (want_color_fd(1, s->s.use_color_diff)) {
 		struct child_process colored_cp = CHILD_PROCESS_INIT;
 		const char *diff_filter = s->s.interactive_diff_filter;
 
@@ -714,7 +714,7 @@ static void render_hunk(struct add_p_state *s, struct hunk *hunk,
 		if (len)
 			strbuf_add(out, p, len);
 		else if (colored)
-			strbuf_addf(out, "%s\n", s->s.reset_color);
+			strbuf_addf(out, "%s\n", s->s.reset_color_diff);
 		else
 			strbuf_addch(out, '\n');
 	}
@@ -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;
@@ -1107,7 +1108,7 @@ static void recolor_hunk(struct add_p_state *s, struct hunk *hunk)
 			      s->s.file_new_color :
 			      s->s.context_color);
 		strbuf_add(&s->colored, plain + current, eol - current);
-		strbuf_addstr(&s->colored, s->s.reset_color);
+		strbuf_addstr(&s->colored, s->s.reset_color_diff);
 		if (next > eol)
 			strbuf_add(&s->colored, plain + eol, next - eol);
 		current = next;
@@ -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,10 +1408,10 @@ 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"
@@ -1408,6 +1419,27 @@ N_("j - leave this hunk undecided, see next undecided hunk\n"
    "p - print the current hunk, 'P' to use 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 +1450,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 +1459,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 +1478,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 +1522,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 +1530,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");
 			}
@@ -1528,8 +1565,8 @@ static int patch_update_file(struct add_p_state *s,
 						: 1));
 		printf(_(s->mode->prompt_mode[prompt_mode_type]),
 		       s->buf.buf);
-		if (*s->s.reset_color)
-			fputs(s->s.reset_color, stdout);
+		if (*s->s.reset_color_interactive)
+			fputs(s->s.reset_color_interactive, stdout);
 		fflush(stdout);
 		if (read_single_character(s) == EOF)
 			break;
@@ -1558,6 +1595,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 +1607,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 +1618,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/advice.c b/advice.c
index e5f0ff8..0018501 100644
--- a/advice.c
+++ b/advice.c
@@ -7,7 +7,7 @@
 #include "help.h"
 #include "string-list.h"
 
-static int advice_use_color = -1;
+static enum git_colorbool advice_use_color = GIT_COLOR_UNKNOWN;
 static char advice_colors[][COLOR_MAXLEN] = {
 	GIT_COLOR_RESET,
 	GIT_COLOR_YELLOW,	/* HINT */
diff --git a/advice.h b/advice.h
index 727dcec..8def280 100644
--- a/advice.h
+++ b/advice.h
@@ -18,7 +18,7 @@ enum advice_type {
 	ADVICE_AM_WORK_DIR,
 	ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,
 	ADVICE_COMMIT_BEFORE_MERGE,
-	ADVICE_DEFAULT_BRANCH_NAME,
+	ADVICE_DEFAULT_BRANCH_NAME, /* To be retired sometime after Git 3.0 */
 	ADVICE_DETACHED_HEAD,
 	ADVICE_DIVERGING,
 	ADVICE_FETCH_SET_HEAD_WARN,
diff --git a/alloc.c b/alloc.c
index 377e80f..533a045 100644
--- a/alloc.c
+++ b/alloc.c
@@ -36,19 +36,25 @@ struct alloc_state {
 	int slab_nr, slab_alloc;
 };
 
-struct alloc_state *allocate_alloc_state(void)
+struct alloc_state *alloc_state_alloc(void)
 {
 	return xcalloc(1, sizeof(struct alloc_state));
 }
 
-void clear_alloc_state(struct alloc_state *s)
+void alloc_state_free_and_null(struct alloc_state **s_)
 {
+	struct alloc_state *s = *s_;
+
+	if (!s)
+		return;
+
 	while (s->slab_nr > 0) {
 		s->slab_nr--;
 		free(s->slabs[s->slab_nr]);
 	}
 
 	FREE_AND_NULL(s->slabs);
+	FREE_AND_NULL(*s_);
 }
 
 static inline void *alloc_node(struct alloc_state *s, size_t node_size)
diff --git a/alloc.h b/alloc.h
index 3f4a0ad..87a47a9 100644
--- a/alloc.h
+++ b/alloc.h
@@ -14,7 +14,7 @@ void *alloc_commit_node(struct repository *r);
 void *alloc_tag_node(struct repository *r);
 void *alloc_object_node(struct repository *r);
 
-struct alloc_state *allocate_alloc_state(void);
-void clear_alloc_state(struct alloc_state *s);
+struct alloc_state *alloc_state_alloc(void);
+void alloc_state_free_and_null(struct alloc_state **s_);
 
 #endif
diff --git a/bisect.c b/bisect.c
index f244745..a6dc76b 100644
--- a/bisect.c
+++ b/bisect.c
@@ -674,9 +674,6 @@ static void bisect_rev_setup(struct repository *r, struct rev_info *revs,
 			     const char *bad_format, const char *good_format,
 			     int read_paths)
 {
-	struct setup_revision_opt opt = {
-		.free_removed_argv_elements = 1,
-	};
 	int i;
 
 	repo_init_revisions(r, revs, prefix);
@@ -693,7 +690,7 @@ static void bisect_rev_setup(struct repository *r, struct rev_info *revs,
 	if (read_paths)
 		read_bisect_paths(rev_argv);
 
-	setup_revisions(rev_argv->nr, rev_argv->v, revs, &opt);
+	setup_revisions_from_strvec(rev_argv, revs, NULL);
 }
 
 static void bisect_common(struct rev_info *revs)
diff --git a/blame.c b/blame.c
index f1c0670..cb0b083 100644
--- a/blame.c
+++ b/blame.c
@@ -2909,9 +2909,6 @@ void setup_blame_bloom_data(struct blame_scoreboard *sb)
 	struct blame_bloom_data *bd;
 	struct bloom_filter_settings *bs;
 
-	if (!sb->repo->objects->commit_graph)
-		return;
-
 	bs = get_bloom_filter_settings(sb->repo);
 	if (!bs)
 		return;
diff --git a/bloom.c b/bloom.c
index b86015f..2d7b951 100644
--- a/bloom.c
+++ b/bloom.c
@@ -452,10 +452,12 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,
 	filter = bloom_filter_slab_at(&bloom_filters, c);
 
 	if (!filter->data) {
+		struct commit_graph *g;
 		uint32_t graph_pos;
-		if (repo_find_commit_pos_in_graph(r, c, &graph_pos))
-			load_bloom_filter_from_graph(r->objects->commit_graph,
-						     filter, graph_pos);
+
+		g = repo_find_commit_pos_in_graph(r, c, &graph_pos);
+		if (g)
+			load_bloom_filter_from_graph(g, filter, graph_pos);
 	}
 
 	if (filter->data && filter->len) {
diff --git a/builtin.h b/builtin.h
index bff13e3..1b35565 100644
--- a/builtin.h
+++ b/builtin.h
@@ -176,6 +176,7 @@ int cmd_hook(int argc, const char **argv, const char *prefix, struct repository
 int cmd_index_pack(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_init_db(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_interpret_trailers(int argc, const char **argv, const char *prefix, struct repository *repo);
+int cmd_last_modified(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_log_reflog(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_log(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_ls_files(int argc, const char **argv, const char *prefix, struct repository *repo);
@@ -216,6 +217,7 @@ int cmd_remote_ext(int argc, const char **argv, const char *prefix, struct repos
 int cmd_remote_fd(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_repack(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_replay(int argc, const char **argv, const char *prefix, struct repository *repo);
+int cmd_repo(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_rerere(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_reset(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_restore(int argc, const char **argv, const char *prefix, struct repository *repo);
diff --git a/builtin/add.c b/builtin/add.c
index 0235854..3270979 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -14,13 +14,14 @@
 #include "gettext.h"
 #include "pathspec.h"
 #include "run-command.h"
+#include "object-file.h"
+#include "odb.h"
 #include "parse-options.h"
 #include "path.h"
 #include "preload-index.h"
 #include "diff.h"
 #include "read-cache.h"
 #include "revision.h"
-#include "bulk-checkin.h"
 #include "strvec.h"
 #include "submodule.h"
 #include "add-interactive.h"
@@ -200,7 +201,7 @@ static int edit_patch(struct repository *repo,
 
 	argc = setup_revisions(argc, argv, &rev, NULL);
 	rev.diffopt.output_format = DIFF_FORMAT_PATCH;
-	rev.diffopt.use_color = 0;
+	rev.diffopt.use_color = GIT_COLOR_NEVER;
 	rev.diffopt.flags.ignore_dirty_submodules = 1;
 	out = xopen(file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
 	rev.diffopt.file = xfdopen(out, "w");
@@ -389,6 +390,7 @@ int cmd_add(int argc,
 	char *seen = NULL;
 	char *ps_matched = NULL;
 	struct lock_file lock_file = LOCK_INIT;
+	struct odb_transaction *transaction;
 
 	repo_config(repo, add_config, NULL);
 
@@ -574,7 +576,7 @@ int cmd_add(int argc,
 		string_list_clear(&only_match_skip_worktree, 0);
 	}
 
-	begin_odb_transaction();
+	transaction = odb_transaction_begin(repo->objects);
 
 	ps_matched = xcalloc(pathspec.nr, 1);
 	if (add_renormalize)
@@ -593,7 +595,7 @@ int cmd_add(int argc,
 
 	if (chmod_arg && pathspec.nr)
 		exit_status |= chmod_pathspec(repo, &pathspec, chmod_arg[0], show_only);
-	end_odb_transaction();
+	odb_transaction_commit(transaction);
 
 finish:
 	if (write_locked_index(repo->index, &lock_file,
diff --git a/builtin/am.c b/builtin/am.c
index 6073d64..277c2e7 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1408,7 +1408,7 @@ static void write_commit_patch(const struct am_state *state, struct commit *comm
 	rev_info.no_commit_id = 1;
 	rev_info.diffopt.flags.binary = 1;
 	rev_info.diffopt.flags.full_index = 1;
-	rev_info.diffopt.use_color = 0;
+	rev_info.diffopt.use_color = GIT_COLOR_NEVER;
 	rev_info.diffopt.file = fp;
 	rev_info.diffopt.close_file = 1;
 	add_pending_object(&rev_info, &commit->object, "");
@@ -1441,7 +1441,7 @@ static void write_index_patch(const struct am_state *state)
 	rev_info.disable_stdin = 1;
 	rev_info.no_commit_id = 1;
 	rev_info.diffopt.output_format = DIFF_FORMAT_PATCH;
-	rev_info.diffopt.use_color = 0;
+	rev_info.diffopt.use_color = GIT_COLOR_NEVER;
 	rev_info.diffopt.file = fp;
 	rev_info.diffopt.close_file = 1;
 	add_pending_object(&rev_info, &tree->object, "");
diff --git a/builtin/backfill.c b/builtin/backfill.c
index 80056ab..e80fc1b 100644
--- a/builtin/backfill.c
+++ b/builtin/backfill.c
@@ -53,7 +53,7 @@ static void download_batch(struct backfill_context *ctx)
 	 * We likely have a new packfile. Add it to the packed list to
 	 * avoid possible duplicate downloads of the same objects.
 	 */
-	reprepare_packed_git(ctx->repo);
+	odb_reprepare(ctx->repo->objects);
 }
 
 static int fill_missing_blobs(const char *path UNUSED,
diff --git a/builtin/blame.c b/builtin/blame.c
index 5b10e84..2703820 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -413,7 +413,7 @@ static void parse_color_fields(const char *s)
 	colorfield_nr = 0;
 
 	/* Ideally this would be stripped and split at the same time? */
-	string_list_split(&l, s, ',', -1);
+	string_list_split(&l, s, ",", -1);
 	ALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);
 
 	for_each_string_list_item(item, &l) {
diff --git a/builtin/branch.c b/builtin/branch.c
index fa5ced4..9fcf04b 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -46,7 +46,7 @@ static struct object_id head_oid;
 static int recurse_submodules = 0;
 static int submodule_propagate_branches = 0;
 
-static int branch_use_color = -1;
+static enum git_colorbool branch_use_color = GIT_COLOR_UNKNOWN;
 static char branch_colors[][COLOR_MAXLEN] = {
 	GIT_COLOR_RESET,
 	GIT_COLOR_NORMAL,       /* PLAIN */
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index fce0b06..ee6715f 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -852,9 +852,10 @@ static void batch_each_object(struct batch_options *opt,
 
 	if (bitmap && !for_each_bitmapped_object(bitmap, &opt->objects_filter,
 						 batch_one_object_bitmapped, &payload)) {
+		struct packfile_store *packs = the_repository->objects->packfiles;
 		struct packed_git *pack;
 
-		for (pack = get_all_packs(the_repository); pack; pack = pack->next) {
+		for (pack = packfile_store_get_all_packs(packs); pack; pack = pack->next) {
 			if (bitmap_index_contains_pack(bitmap, pack) ||
 			    open_pack_index(pack))
 				continue;
diff --git a/builtin/clean.c b/builtin/clean.c
index a1977b9..1d5e7e5 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -64,7 +64,7 @@ static const char *color_interactive_slots[] = {
 	[CLEAN_COLOR_RESET]  = "reset",
 };
 
-static int clean_use_color = -1;
+static enum git_colorbool clean_use_color = GIT_COLOR_UNKNOWN;
 static char clean_colors[][COLOR_MAXLEN] = {
 	[CLEAN_COLOR_ERROR] = GIT_COLOR_BOLD_RED,
 	[CLEAN_COLOR_HEADER] = GIT_COLOR_BOLD,
@@ -478,43 +478,39 @@ static int find_unique(const char *choice, struct menu_stuff *menu_stuff)
  */
 static int parse_choice(struct menu_stuff *menu_stuff,
 			int is_single,
-			struct strbuf input,
+			char *input,
 			int **chosen)
 {
-	struct strbuf **choice_list, **ptr;
+	struct string_list choice = STRING_LIST_INIT_NODUP;
+	struct string_list_item *item;
 	int nr = 0;
 	int i;
 
-	if (is_single) {
-		choice_list = strbuf_split_max(&input, '\n', 0);
-	} else {
-		char *p = input.buf;
-		do {
-			if (*p == ',')
-				*p = ' ';
-		} while (*p++);
-		choice_list = strbuf_split_max(&input, ' ', 0);
-	}
+	string_list_split_in_place_f(&choice, input,
+				     is_single ? "\n" : ", ", -1,
+				     STRING_LIST_SPLIT_TRIM);
 
-	for (ptr = choice_list; *ptr; ptr++) {
-		char *p;
-		int choose = 1;
+	for_each_string_list_item(item, &choice) {
+		const char *string;
+		int choose;
 		int bottom = 0, top = 0;
 		int is_range, is_number;
 
-		strbuf_trim(*ptr);
-		if (!(*ptr)->len)
+		string = item->string;
+		if (!*string)
 			continue;
 
 		/* Input that begins with '-'; unchoose */
-		if (*(*ptr)->buf == '-') {
+		if (string[0] == '-') {
 			choose = 0;
-			strbuf_remove((*ptr), 0, 1);
+			string++;
+		} else {
+			choose = 1;
 		}
 
 		is_range = 0;
 		is_number = 1;
-		for (p = (*ptr)->buf; *p; p++) {
+		for (const char *p = string; *p; p++) {
 			if ('-' == *p) {
 				if (!is_range) {
 					is_range = 1;
@@ -532,27 +528,27 @@ static int parse_choice(struct menu_stuff *menu_stuff,
 		}
 
 		if (is_number) {
-			bottom = atoi((*ptr)->buf);
+			bottom = atoi(string);
 			top = bottom;
 		} else if (is_range) {
-			bottom = atoi((*ptr)->buf);
+			bottom = atoi(string);
 			/* a range can be specified like 5-7 or 5- */
-			if (!*(strchr((*ptr)->buf, '-') + 1))
+			if (!*(strchr(string, '-') + 1))
 				top = menu_stuff->nr;
 			else
-				top = atoi(strchr((*ptr)->buf, '-') + 1);
-		} else if (!strcmp((*ptr)->buf, "*")) {
+				top = atoi(strchr(string, '-') + 1);
+		} else if (!strcmp(string, "*")) {
 			bottom = 1;
 			top = menu_stuff->nr;
 		} else {
-			bottom = find_unique((*ptr)->buf, menu_stuff);
+			bottom = find_unique(string, menu_stuff);
 			top = bottom;
 		}
 
 		if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top ||
 		    (is_single && bottom != top)) {
 			clean_print_color(CLEAN_COLOR_ERROR);
-			printf(_("Huh (%s)?\n"), (*ptr)->buf);
+			printf(_("Huh (%s)?\n"), string);
 			clean_print_color(CLEAN_COLOR_RESET);
 			continue;
 		}
@@ -561,7 +557,7 @@ static int parse_choice(struct menu_stuff *menu_stuff,
 			(*chosen)[i-1] = choose;
 	}
 
-	strbuf_list_free(choice_list);
+	string_list_clear(&choice, 0);
 
 	for (i = 0; i < menu_stuff->nr; i++)
 		nr += (*chosen)[i];
@@ -631,7 +627,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
 
 		nr = parse_choice(stuff,
 				  opts->flags & MENU_OPTS_SINGLETON,
-				  choice,
+				  choice.buf,
 				  &chosen);
 
 		if (opts->flags & MENU_OPTS_SINGLETON) {
@@ -679,12 +675,13 @@ static int filter_by_patterns_cmd(void)
 {
 	struct dir_struct dir = DIR_INIT;
 	struct strbuf confirm = STRBUF_INIT;
-	struct strbuf **ignore_list;
-	struct string_list_item *item;
 	struct pattern_list *pl;
 	int changed = -1, i;
 
 	for (;;) {
+		struct string_list ignore_list = STRING_LIST_INIT_NODUP;
+		struct string_list_item *item;
+
 		if (!del_list.nr)
 			break;
 
@@ -702,14 +699,15 @@ static int filter_by_patterns_cmd(void)
 			break;
 
 		pl = add_pattern_list(&dir, EXC_CMDL, "manual exclude");
-		ignore_list = strbuf_split_max(&confirm, ' ', 0);
 
-		for (i = 0; ignore_list[i]; i++) {
-			strbuf_trim(ignore_list[i]);
-			if (!ignore_list[i]->len)
+		string_list_split_in_place_f(&ignore_list, confirm.buf, " ", -1,
+					     STRING_LIST_SPLIT_TRIM);
+
+		for (i = 0; i < ignore_list.nr; i++) {
+			item = &ignore_list.items[i];
+			if (!*item->string)
 				continue;
-
-			add_pattern(ignore_list[i]->buf, "", 0, pl, -(i+1));
+			add_pattern(item->string, "", 0, pl, -(i+1));
 		}
 
 		changed = 0;
@@ -730,7 +728,7 @@ static int filter_by_patterns_cmd(void)
 			clean_print_color(CLEAN_COLOR_RESET);
 		}
 
-		strbuf_list_free(ignore_list);
+		string_list_clear(&ignore_list, 0);
 		dir_clear(&dir);
 	}
 
diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c
index 4992ac1..fe3ebaa 100644
--- a/builtin/commit-graph.c
+++ b/builtin/commit-graph.c
@@ -102,14 +102,15 @@ static int graph_verify(int argc, const char **argv, const char *prefix,
 	if (opts.progress)
 		flags |= COMMIT_GRAPH_WRITE_PROGRESS;
 
-	source = odb_find_source(the_repository->objects, opts.obj_dir);
+	source = odb_find_source_or_die(the_repository->objects, opts.obj_dir);
 	graph_name = get_commit_graph_filename(source);
 	chain_name = get_commit_graph_chain_filename(source);
 	if (open_commit_graph(graph_name, &fd, &st))
 		opened = OPENED_GRAPH;
 	else if (errno != ENOENT)
 		die_errno(_("Could not open commit-graph '%s'"), graph_name);
-	else if (open_commit_graph_chain(chain_name, &fd, &st))
+	else if (open_commit_graph_chain(chain_name, &fd, &st,
+					 the_repository->hash_algo))
 		opened = OPENED_CHAIN;
 	else if (errno != ENOENT)
 		die_errno(_("could not open commit-graph chain '%s'"), chain_name);
@@ -121,15 +122,15 @@ static int graph_verify(int argc, const char **argv, const char *prefix,
 	if (opened == OPENED_NONE)
 		return 0;
 	else if (opened == OPENED_GRAPH)
-		graph = load_commit_graph_one_fd_st(the_repository, fd, &st, source);
+		graph = load_commit_graph_one_fd_st(source, fd, &st);
 	else
-		graph = load_commit_graph_chain_fd_st(the_repository, fd, &st,
+		graph = load_commit_graph_chain_fd_st(the_repository->objects, fd, &st,
 						      &incomplete_chain);
 
 	if (!graph)
 		return 1;
 
-	ret = verify_commit_graph(the_repository, graph, flags);
+	ret = verify_commit_graph(graph, flags);
 	free_commit_graph(graph);
 
 	if (incomplete_chain) {
@@ -290,7 +291,7 @@ static int graph_write(int argc, const char **argv, const char *prefix,
 	    git_env_bool(GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS, 0))
 		flags |= COMMIT_GRAPH_WRITE_BLOOM_FILTERS;
 
-	source = odb_find_source(the_repository->objects, opts.obj_dir);
+	source = odb_find_source_or_die(the_repository->objects, opts.obj_dir);
 
 	if (opts.reachable) {
 		if (write_commit_graph_reachable(source, flags, &write_opts))
diff --git a/builtin/commit.c b/builtin/commit.c
index b5b9608..0243f17 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -695,6 +695,7 @@ static int author_date_is_interesting(void)
 	return author_message || force_date;
 }
 
+#ifndef WITH_BREAKING_CHANGES
 static void adjust_comment_line_char(const struct strbuf *sb)
 {
 	char candidates[] = "#;@!$%^&|:";
@@ -732,6 +733,7 @@ static void adjust_comment_line_char(const struct strbuf *sb)
 	free(comment_line_str_to_free);
 	comment_line_str = comment_line_str_to_free = xstrfmt("%c", *p);
 }
+#endif /* !WITH_BREAKING_CHANGES */
 
 static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,
 				struct pretty_print_context *ctx)
@@ -928,15 +930,17 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 	if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
 		die_errno(_("could not write commit template"));
 
+#ifndef WITH_BREAKING_CHANGES
 	if (auto_comment_line_char)
 		adjust_comment_line_char(&sb);
+#endif /* !WITH_BREAKING_CHANGES */
 	strbuf_release(&sb);
 
 	/* This checks if committer ident is explicitly given */
 	strbuf_addstr(&committer_ident, git_committer_info(IDENT_STRICT));
 	if (use_editor && include_status) {
 		int ident_shown = 0;
-		int saved_color_setting;
+		enum git_colorbool saved_color_setting;
 		struct ident_split ci, ai;
 		const char *hint_cleanup_all = allow_empty_message ?
 			_("Please enter the commit message for your changes."
@@ -1016,7 +1020,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 		status_printf_ln(s, GIT_COLOR_NORMAL, "%s", ""); /* Add new line for clarity */
 
 		saved_color_setting = s->use_color;
-		s->use_color = 0;
+		s->use_color = GIT_COLOR_NEVER;
 		committable = run_status(s->fp, index_file, prefix, 1, s);
 		s->use_color = saved_color_setting;
 		string_list_clear_func(&s->change, change_data_free);
@@ -1793,6 +1797,9 @@ int cmd_commit(int argc,
 	show_usage_with_options_if_asked(argc, argv,
 					 builtin_commit_usage, builtin_commit_options);
 
+#ifndef WITH_BREAKING_CHANGES
+	warn_on_auto_comment_char = true;
+#endif /* !WITH_BREAKING_CHANGES */
 	prepare_repo_settings(the_repository);
 	the_repository->settings.command_requires_full_index = 0;
 
@@ -1947,7 +1954,7 @@ int cmd_commit(int argc,
 		      "new index file. Check that disk is not full and quota is\n"
 		      "not exceeded, and then \"git restore --staged :/\" to recover."));
 
-	git_test_write_commit_graph_or_die();
+	git_test_write_commit_graph_or_die(the_repository->objects->sources);
 
 	repo_rerere(the_repository, 0);
 	run_auto_maintenance(quiet);
diff --git a/builtin/config.c b/builtin/config.c
index 59fb113..75852bd 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -547,30 +547,37 @@ static int git_get_color_config(const char *var, const char *value,
 	return 0;
 }
 
-static void get_color(const struct config_location_options *opts,
+static int get_color(const struct config_location_options *opts,
 		      const char *var, const char *def_color)
 {
 	struct get_color_config_data data = {
 		.get_color_slot = var,
 		.parsed_color[0] = '\0',
 	};
+	int ret;
 
 	config_with_options(git_get_color_config, &data,
 			    &opts->source, the_repository,
 			    &opts->options);
 
 	if (!data.get_color_found && def_color) {
-		if (color_parse(def_color, data.parsed_color) < 0)
-			die(_("unable to parse default color value"));
+		if (color_parse(def_color, data.parsed_color) < 0) {
+			ret = error(_("unable to parse default color value"));
+			goto out;
+		}
 	}
 
+	ret = 0;
+
+out:
 	fputs(data.parsed_color, stdout);
+	return ret;
 }
 
 struct get_colorbool_config_data {
-	int get_colorbool_found;
-	int get_diff_color_found;
-	int get_color_ui_found;
+	enum git_colorbool get_colorbool_found;
+	enum git_colorbool get_diff_color_found;
+	enum git_colorbool get_color_ui_found;
 	const char *get_colorbool_slot;
 };
 
@@ -594,33 +601,34 @@ static int get_colorbool(const struct config_location_options *opts,
 {
 	struct get_colorbool_config_data data = {
 		.get_colorbool_slot = var,
-		.get_colorbool_found = -1,
-		.get_diff_color_found = -1,
-		.get_color_ui_found = -1,
+		.get_colorbool_found = GIT_COLOR_UNKNOWN,
+		.get_diff_color_found = GIT_COLOR_UNKNOWN,
+		.get_color_ui_found = GIT_COLOR_UNKNOWN,
 	};
+	bool result;
 
 	config_with_options(git_get_colorbool_config, &data,
 			    &opts->source, the_repository,
 			    &opts->options);
 
-	if (data.get_colorbool_found < 0) {
+	if (data.get_colorbool_found == GIT_COLOR_UNKNOWN) {
 		if (!strcmp(data.get_colorbool_slot, "color.diff"))
 			data.get_colorbool_found = data.get_diff_color_found;
-		if (data.get_colorbool_found < 0)
+		if (data.get_colorbool_found == GIT_COLOR_UNKNOWN)
 			data.get_colorbool_found = data.get_color_ui_found;
 	}
 
-	if (data.get_colorbool_found < 0)
+	if (data.get_colorbool_found == GIT_COLOR_UNKNOWN)
 		/* default value if none found in config */
 		data.get_colorbool_found = GIT_COLOR_AUTO;
 
-	data.get_colorbool_found = want_color(data.get_colorbool_found);
+	result = want_color(data.get_colorbool_found);
 
 	if (print) {
-		printf("%s\n", data.get_colorbool_found ? "true" : "false");
+		printf("%s\n", result ? "true" : "false");
 		return 0;
 	} else
-		return data.get_colorbool_found ? 0 : 1;
+		return result ? 0 : 1;
 }
 
 static void check_write(const struct git_config_source *source)
@@ -912,10 +920,13 @@ static int cmd_config_get(int argc, const char **argv, const char *prefix,
 	location_options_init(&location_opts, prefix);
 	display_options_init(&display_opts);
 
-	setup_auto_pager("config", 1);
+	if (display_opts.type != TYPE_COLOR)
+		setup_auto_pager("config", 1);
 
 	if (url)
 		ret = get_urlmatch(&location_opts, &display_opts, argv[0], url);
+	else if (display_opts.type == TYPE_COLOR && !strlen(argv[0]) && display_opts.default_value)
+		ret = get_color(&location_opts, "", display_opts.default_value);
 	else
 		ret = get_value(&location_opts, &display_opts, argv[0], value_pattern,
 				get_value_flags, flags);
@@ -1390,7 +1401,7 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix)
 	}
 	else if (actions == ACTION_GET_COLOR) {
 		check_argc(argc, 1, 2);
-		get_color(&location_opts, argv[0], argv[1]);
+		ret = get_color(&location_opts, argv[0], argv[1]);
 	}
 	else if (actions == ACTION_GET_COLORBOOL) {
 		check_argc(argc, 1, 2);
diff --git a/builtin/count-objects.c b/builtin/count-objects.c
index a61d3b4..f2f407c 100644
--- a/builtin/count-objects.c
+++ b/builtin/count-objects.c
@@ -122,6 +122,7 @@ int cmd_count_objects(int argc,
 				      count_loose, count_cruft, NULL, NULL);
 
 	if (verbose) {
+		struct packfile_store *packs = the_repository->objects->packfiles;
 		struct packed_git *p;
 		unsigned long num_pack = 0;
 		off_t size_pack = 0;
@@ -129,7 +130,7 @@ int cmd_count_objects(int argc,
 		struct strbuf pack_buf = STRBUF_INIT;
 		struct strbuf garbage_buf = STRBUF_INIT;
 
-		for (p = get_all_packs(the_repository); p; p = p->next) {
+		for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
 			if (!p->pack_local)
 				continue;
 			if (open_pack_index(p))
diff --git a/builtin/describe.c b/builtin/describe.c
index d7dd813..ffaf8d9 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -23,6 +23,8 @@
 #include "list-objects.h"
 #include "commit-slab.h"
 #include "wildmatch.h"
+#include "prio-queue.h"
+#include "oidset.h"
 
 #define MAX_TAGS	(FLAG_BITS - 1)
 #define DEFAULT_CANDIDATES 10
@@ -249,36 +251,83 @@ static int compare_pt(const void *a_, const void *b_)
 	return 0;
 }
 
-static unsigned long finish_depth_computation(
-	struct commit_list **list,
-	struct possible_tag *best)
+struct lazy_queue {
+	struct prio_queue queue;
+	bool get_pending;
+};
+
+#define LAZY_QUEUE_INIT { { compare_commits_by_commit_date }, false }
+
+static void *lazy_queue_get(struct lazy_queue *queue)
+{
+	if (queue->get_pending)
+		prio_queue_get(&queue->queue);
+	else
+		queue->get_pending = true;
+	return prio_queue_peek(&queue->queue);
+}
+
+static void lazy_queue_put(struct lazy_queue *queue, void *thing)
+{
+	if (queue->get_pending)
+		prio_queue_replace(&queue->queue, thing);
+	else
+		prio_queue_put(&queue->queue, thing);
+	queue->get_pending = false;
+}
+
+static bool lazy_queue_empty(const struct lazy_queue *queue)
+{
+	return queue->queue.nr == (queue->get_pending ? 1 : 0);
+}
+
+static void lazy_queue_clear(struct lazy_queue *queue)
+{
+	clear_prio_queue(&queue->queue);
+	queue->get_pending = false;
+}
+
+static unsigned long finish_depth_computation(struct lazy_queue *queue,
+					      struct possible_tag *best)
 {
 	unsigned long seen_commits = 0;
-	while (*list) {
-		struct commit *c = pop_commit(list);
+	struct oidset unflagged = OIDSET_INIT;
+
+	for (size_t i = queue->get_pending ? 1 : 0; i < queue->queue.nr; i++) {
+		struct commit *commit = queue->queue.array[i].data;
+		if (!(commit->object.flags & best->flag_within))
+			oidset_insert(&unflagged, &commit->object.oid);
+	}
+
+	while (!lazy_queue_empty(queue)) {
+		struct commit *c = lazy_queue_get(queue);
 		struct commit_list *parents = c->parents;
 		seen_commits++;
 		if (c->object.flags & best->flag_within) {
-			struct commit_list *a = *list;
-			while (a) {
-				struct commit *i = a->item;
-				if (!(i->object.flags & best->flag_within))
-					break;
-				a = a->next;
-			}
-			if (!a)
+			if (!oidset_size(&unflagged))
 				break;
-		} else
+		} else {
+			oidset_remove(&unflagged, &c->object.oid);
 			best->depth++;
+		}
 		while (parents) {
+			unsigned seen, flag_before, flag_after;
 			struct commit *p = parents->item;
 			repo_parse_commit(the_repository, p);
-			if (!(p->object.flags & SEEN))
-				commit_list_insert_by_date(p, list);
+			seen = p->object.flags & SEEN;
+			if (!seen)
+				lazy_queue_put(queue, p);
+			flag_before = p->object.flags & best->flag_within;
 			p->object.flags |= c->object.flags;
+			flag_after = p->object.flags & best->flag_within;
+			if (!seen && !flag_after)
+				oidset_insert(&unflagged, &p->object.oid);
+			if (seen && !flag_before && flag_after)
+				oidset_remove(&unflagged, &p->object.oid);
 			parents = parents->next;
 		}
 	}
+	oidset_clear(&unflagged);
 	return seen_commits;
 }
 
@@ -313,18 +362,16 @@ static void append_suffix(int depth, const struct object_id *oid, struct strbuf
 		    repo_find_unique_abbrev(the_repository, oid, abbrev));
 }
 
-static void describe_commit(struct object_id *oid, struct strbuf *dst)
+static void describe_commit(struct commit *cmit, struct strbuf *dst)
 {
-	struct commit *cmit, *gave_up_on = NULL;
-	struct commit_list *list;
+	struct commit *gave_up_on = NULL;
+	struct lazy_queue queue = LAZY_QUEUE_INIT;
 	struct commit_name *n;
 	struct possible_tag all_matches[MAX_TAGS];
 	unsigned int match_cnt = 0, annotated_cnt = 0, cur_match;
 	unsigned long seen_commits = 0;
 	unsigned int unannotated_cnt = 0;
 
-	cmit = lookup_commit_reference(the_repository, oid);
-
 	n = find_commit_name(&cmit->object.oid);
 	if (n && (tags || all || n->prio == 2)) {
 		/*
@@ -332,7 +379,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
 		 */
 		append_name(n, dst);
 		if (n->misnamed || longformat)
-			append_suffix(0, n->tag ? get_tagged_oid(n->tag) : oid, dst);
+			append_suffix(0, n->tag ? get_tagged_oid(n->tag) : &cmit->object.oid, dst);
 		if (suffix)
 			strbuf_addstr(dst, suffix);
 		return;
@@ -359,11 +406,10 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
 		have_util = 1;
 	}
 
-	list = NULL;
 	cmit->object.flags = SEEN;
-	commit_list_insert(cmit, &list);
-	while (list) {
-		struct commit *c = pop_commit(&list);
+	lazy_queue_put(&queue, cmit);
+	while (!lazy_queue_empty(&queue)) {
+		struct commit *c = lazy_queue_get(&queue);
 		struct commit_list *parents = c->parents;
 		struct commit_name **slot;
 
@@ -397,7 +443,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
 				t->depth++;
 		}
 		/* Stop if last remaining path already covered by best candidate(s) */
-		if (annotated_cnt && !list) {
+		if (annotated_cnt && lazy_queue_empty(&queue)) {
 			int best_depth = INT_MAX;
 			unsigned best_within = 0;
 			for (cur_match = 0; cur_match < match_cnt; cur_match++) {
@@ -420,7 +466,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
 			struct commit *p = parents->item;
 			repo_parse_commit(the_repository, p);
 			if (!(p->object.flags & SEEN))
-				commit_list_insert_by_date(p, &list);
+				lazy_queue_put(&queue, p);
 			p->object.flags |= c->object.flags;
 			parents = parents->next;
 
@@ -435,6 +481,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
 			strbuf_add_unique_abbrev(dst, cmit_oid, abbrev);
 			if (suffix)
 				strbuf_addstr(dst, suffix);
+			lazy_queue_clear(&queue);
 			return;
 		}
 		if (unannotated_cnt)
@@ -450,11 +497,11 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
 	QSORT(all_matches, match_cnt, compare_pt);
 
 	if (gave_up_on) {
-		commit_list_insert_by_date(gave_up_on, &list);
+		lazy_queue_put(&queue, gave_up_on);
 		seen_commits--;
 	}
-	seen_commits += finish_depth_computation(&list, &all_matches[0]);
-	free_commit_list(list);
+	seen_commits += finish_depth_computation(&queue, &all_matches[0]);
+	lazy_queue_clear(&queue);
 
 	if (debug) {
 		static int label_width = -1;
@@ -489,8 +536,8 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
 }
 
 struct process_commit_data {
-	struct object_id current_commit;
-	struct object_id looking_for;
+	struct commit *current_commit;
+	const struct object_id *looking_for;
 	struct strbuf *dst;
 	struct rev_info *revs;
 };
@@ -498,34 +545,43 @@ struct process_commit_data {
 static void process_commit(struct commit *commit, void *data)
 {
 	struct process_commit_data *pcd = data;
-	pcd->current_commit = commit->object.oid;
+	pcd->current_commit = commit;
 }
 
 static void process_object(struct object *obj, const char *path, void *data)
 {
 	struct process_commit_data *pcd = data;
 
-	if (oideq(&pcd->looking_for, &obj->oid) && !pcd->dst->len) {
+	if (oideq(pcd->looking_for, &obj->oid) && !pcd->dst->len) {
 		reset_revision_walk();
-		describe_commit(&pcd->current_commit, pcd->dst);
-		strbuf_addf(pcd->dst, ":%s", path);
+		if (pcd->current_commit) {
+			describe_commit(pcd->current_commit, pcd->dst);
+			strbuf_addf(pcd->dst, ":%s", path);
+		}
 		free_commit_list(pcd->revs->commits);
 		pcd->revs->commits = NULL;
 	}
 }
 
-static void describe_blob(struct object_id oid, struct strbuf *dst)
+static void describe_blob(const struct object_id *oid, struct strbuf *dst)
 {
 	struct rev_info revs;
 	struct strvec args = STRVEC_INIT;
-	struct process_commit_data pcd = { *null_oid(the_hash_algo), oid, dst, &revs};
+	struct object_id head_oid;
+	struct process_commit_data pcd = { NULL, oid, dst, &revs};
+
+	if (repo_get_oid(the_repository, "HEAD", &head_oid))
+		die(_("cannot search for blob '%s' on an unborn branch"),
+		    oid_to_hex(oid));
 
 	strvec_pushl(&args, "internal: The first arg is not parsed",
-		     "--objects", "--in-commit-order", "--reverse", "HEAD",
+		     "--objects", "--in-commit-order", "--reverse",
+		     oid_to_hex(&head_oid),
 		     NULL);
 
 	repo_init_revisions(the_repository, &revs, NULL);
-	if (setup_revisions(args.nr, args.v, &revs, NULL) > 1)
+	setup_revisions_from_strvec(&args, &revs, NULL);
+	if (args.nr > 1)
 		BUG("setup_revisions could not handle all args?");
 
 	if (prepare_revision_walk(&revs))
@@ -535,6 +591,9 @@ static void describe_blob(struct object_id oid, struct strbuf *dst)
 	reset_revision_walk();
 	release_revisions(&revs);
 	strvec_clear(&args);
+
+	if (!dst->len)
+		die(_("blob '%s' not reachable from HEAD"), oid_to_hex(oid));
 }
 
 static void describe(const char *arg, int last_one)
@@ -551,10 +610,10 @@ static void describe(const char *arg, int last_one)
 	cmit = lookup_commit_reference_gently(the_repository, &oid, 1);
 
 	if (cmit)
-		describe_commit(&oid, &sb);
+		describe_commit(cmit, &sb);
 	else if (odb_read_object_info(the_repository->objects,
 				      &oid, NULL) == OBJ_BLOB)
-		describe_blob(oid, &sb);
+		describe_blob(&oid, &sb);
 	else
 		die(_("%s is neither a commit nor blob"), arg);
 
diff --git a/builtin/diff.c b/builtin/diff.c
index 9a89e25..0b23c41 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -487,6 +487,21 @@ int cmd_diff(int argc,
 
 	init_diff_ui_defaults();
 	repo_config(the_repository, git_diff_ui_config, NULL);
+
+	/*
+	 * If we are ignoring the fact that our current directory may
+	 * be part of a working tree controlled by a Git repository to
+	 * pretend to be a "better GNU diff", we should undo the
+	 * effect of the setup code that did a chdir() to the top of
+	 * the working tree.  Where we came from is recorded in the
+	 * prefix.
+	 */
+	if (no_index && prefix) {
+		if (chdir(prefix))
+			die(_("cannot come back to cwd"));
+		prefix = NULL;
+	}
+
 	prefix = precompose_argv_prefix(argc, argv, prefix);
 
 	repo_init_revisions(the_repository, &rev, prefix);
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index c06ee0b..dc2486f 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -37,8 +37,6 @@ static const char *const fast_export_usage[] = {
 	NULL
 };
 
-enum sign_mode { SIGN_ABORT, SIGN_VERBATIM, SIGN_STRIP, SIGN_WARN_VERBATIM, SIGN_WARN_STRIP };
-
 static int progress;
 static enum sign_mode signed_tag_mode = SIGN_ABORT;
 static enum sign_mode signed_commit_mode = SIGN_STRIP;
@@ -59,23 +57,16 @@ static struct hashmap anonymized_seeds;
 static struct revision_sources revision_sources;
 
 static int parse_opt_sign_mode(const struct option *opt,
-				     const char *arg, int unset)
+			       const char *arg, int unset)
 {
 	enum sign_mode *val = opt->value;
+
 	if (unset)
 		return 0;
-	else if (!strcmp(arg, "abort"))
-		*val = SIGN_ABORT;
-	else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore"))
-		*val = SIGN_VERBATIM;
-	else if (!strcmp(arg, "warn-verbatim") || !strcmp(arg, "warn"))
-		*val = SIGN_WARN_VERBATIM;
-	else if (!strcmp(arg, "warn-strip"))
-		*val = SIGN_WARN_STRIP;
-	else if (!strcmp(arg, "strip"))
-		*val = SIGN_STRIP;
-	else
+
+	if (parse_sign_mode(arg, val))
 		return error("Unknown %s mode: %s", opt->long_name, arg);
+
 	return 0;
 }
 
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index 2c35f93..606c6ae 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -188,6 +188,8 @@ static int global_argc;
 static const char **global_argv;
 static const char *global_prefix;
 
+static enum sign_mode signed_commit_mode = SIGN_VERBATIM;
+
 /* Memory pools */
 static struct mem_pool fi_mem_pool = {
 	.block_alloc = 2*1024*1024 - sizeof(struct mp_block),
@@ -897,11 +899,11 @@ static void end_packfile(void)
 		idx_name = keep_pack(create_index());
 
 		/* Register the packfile with core git's machinery. */
-		new_p = add_packed_git(pack_data->repo, idx_name, strlen(idx_name), 1);
+		new_p = packfile_store_load_pack(pack_data->repo->objects->packfiles,
+						 idx_name, 1);
 		if (!new_p)
 			die("core git rejected index %s", idx_name);
 		all_packs[pack_id] = new_p;
-		install_packed_git(the_repository, new_p);
 		free(idx_name);
 
 		/* Print the boundary */
@@ -952,6 +954,7 @@ static int store_object(
 	struct object_id *oidout,
 	uintmax_t mark)
 {
+	struct packfile_store *packs = the_repository->objects->packfiles;
 	void *out, *delta;
 	struct object_entry *e;
 	unsigned char hdr[96];
@@ -975,7 +978,7 @@ static int store_object(
 	if (e->idx.offset) {
 		duplicate_count_by_type[type]++;
 		return 1;
-	} else if (find_oid_pack(&oid, get_all_packs(the_repository))) {
+	} else if (find_oid_pack(&oid, packfile_store_get_all_packs(packs))) {
 		e->type = type;
 		e->pack_id = MAX_PACK_ID;
 		e->idx.offset = 1; /* just not zero! */
@@ -1092,6 +1095,7 @@ static void truncate_pack(struct hashfile_checkpoint *checkpoint)
 
 static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)
 {
+	struct packfile_store *packs = the_repository->objects->packfiles;
 	size_t in_sz = 64 * 1024, out_sz = 64 * 1024;
 	unsigned char *in_buf = xmalloc(in_sz);
 	unsigned char *out_buf = xmalloc(out_sz);
@@ -1175,7 +1179,7 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)
 		duplicate_count_by_type[OBJ_BLOB]++;
 		truncate_pack(&checkpoint);
 
-	} else if (find_oid_pack(&oid, get_all_packs(the_repository))) {
+	} else if (find_oid_pack(&oid, packfile_store_get_all_packs(packs))) {
 		e->type = OBJ_BLOB;
 		e->pack_id = MAX_PACK_ID;
 		e->idx.offset = 1; /* just not zero! */
@@ -2752,6 +2756,15 @@ static void parse_one_signature(struct signature_data *sig, const char *v)
 	parse_data(&sig->data, 0, NULL);
 }
 
+static void discard_one_signature(void)
+{
+	struct strbuf data = STRBUF_INIT;
+
+	read_next_command();
+	parse_data(&data, 0, NULL);
+	strbuf_release(&data);
+}
+
 static void add_gpgsig_to_commit(struct strbuf *commit_data,
 				 const char *header,
 				 struct signature_data *sig)
@@ -2785,6 +2798,22 @@ static void store_signature(struct signature_data *stored_sig,
 	}
 }
 
+static void import_one_signature(struct signature_data *sig_sha1,
+				 struct signature_data *sig_sha256,
+				 const char *v)
+{
+	struct signature_data sig = { NULL, NULL, STRBUF_INIT };
+
+	parse_one_signature(&sig, v);
+
+	if (!strcmp(sig.hash_algo, "sha1"))
+		store_signature(sig_sha1, &sig, "SHA-1");
+	else if (!strcmp(sig.hash_algo, "sha256"))
+		store_signature(sig_sha256, &sig, "SHA-256");
+	else
+		die(_("parse_one_signature() returned unknown hash algo"));
+}
+
 static void parse_new_commit(const char *arg)
 {
 	static struct strbuf msg = STRBUF_INIT;
@@ -2817,19 +2846,32 @@ static void parse_new_commit(const char *arg)
 	if (!committer)
 		die("Expected committer but didn't get one");
 
-	/* Process signatures (up to 2: one "sha1" and one "sha256") */
 	while (skip_prefix(command_buf.buf, "gpgsig ", &v)) {
-		struct signature_data sig = { NULL, NULL, STRBUF_INIT };
+		switch (signed_commit_mode) {
 
-		parse_one_signature(&sig, v);
+		/* First, modes that don't need the signature to be parsed */
+		case SIGN_ABORT:
+			die("encountered signed commit; use "
+			    "--signed-commits=<mode> to handle it");
+		case SIGN_WARN_STRIP:
+			warning(_("stripping a commit signature"));
+			/* fallthru */
+		case SIGN_STRIP:
+			discard_one_signature();
+			break;
 
-		if (!strcmp(sig.hash_algo, "sha1"))
-			store_signature(&sig_sha1, &sig, "SHA-1");
-		else if (!strcmp(sig.hash_algo, "sha256"))
-			store_signature(&sig_sha256, &sig, "SHA-256");
-		else
-			BUG("parse_one_signature() returned unknown hash algo");
+		/* Second, modes that parse the signature */
+		case SIGN_WARN_VERBATIM:
+			warning(_("importing a commit signature verbatim"));
+			/* fallthru */
+		case SIGN_VERBATIM:
+			import_one_signature(&sig_sha1, &sig_sha256, v);
+			break;
 
+		/* Third, BUG */
+		default:
+			BUG("invalid signed_commit_mode value %d", signed_commit_mode);
+		}
 		read_next_command();
 	}
 
@@ -3501,6 +3543,9 @@ static int parse_one_option(const char *option)
 		option_active_branches(option);
 	} else if (skip_prefix(option, "export-pack-edges=", &option)) {
 		option_export_pack_edges(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 (!strcmp(option, "quiet")) {
 		show_stats = 0;
 		quiet = 1;
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 24645c4..c7ff348 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1643,7 +1643,8 @@ static int set_head(const struct ref *remote_refs, struct remote *remote)
 
 struct ref_rejection_data {
 	int *retcode;
-	int conflict_msg_shown;
+	bool conflict_msg_shown;
+	bool case_sensitive_msg_shown;
 	const char *remote_name;
 };
 
@@ -1657,11 +1658,25 @@ static void ref_transaction_rejection_handler(const char *refname,
 {
 	struct ref_rejection_data *data = cb_data;
 
-	if (err == REF_TRANSACTION_ERROR_NAME_CONFLICT && !data->conflict_msg_shown) {
+	if (err == REF_TRANSACTION_ERROR_CASE_CONFLICT && ignore_case &&
+	    !data->case_sensitive_msg_shown) {
+		error(_("You're on a case-insensitive filesystem, and the remote you are\n"
+			"trying to fetch from has references that only differ in casing. It\n"
+			"is impossible to store such references with the 'files' backend. You\n"
+			"can either accept this as-is, in which case you won't be able to\n"
+			"store all remote references on disk. Or you can alternatively\n"
+			"migrate your repository to use the 'reftable' backend with the\n"
+			"following command:\n\n    git refs migrate --ref-format=reftable\n\n"
+			"Please keep in mind that not all implementations of Git support this\n"
+			"new format yet. So if you use tools other than Git to access this\n"
+			"repository it may not be an option to migrate to reftables.\n"));
+		data->case_sensitive_msg_shown = true;
+	} else if (err == REF_TRANSACTION_ERROR_NAME_CONFLICT &&
+		   !data->conflict_msg_shown) {
 		error(_("some local refs could not be updated; try running\n"
 			" 'git remote prune %s' to remove any old, conflicting "
 			"branches"), data->remote_name);
-		data->conflict_msg_shown = 1;
+		data->conflict_msg_shown = true;
 	} else {
 		const char *reason = ref_transaction_error_msg(err);
 
diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c
index edb93c0..cf4273a 100644
--- a/builtin/fmt-merge-msg.c
+++ b/builtin/fmt-merge-msg.c
@@ -1,4 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
 #include "builtin.h"
 #include "config.h"
 #include "fmt-merge-msg.h"
@@ -13,12 +12,13 @@ static const char * const fmt_merge_msg_usage[] = {
 int cmd_fmt_merge_msg(int argc,
 		      const char **argv,
 		      const char *prefix,
-		      struct repository *repo UNUSED)
+		      struct repository *repo)
 {
 	char *inpath = NULL;
 	const char *message = NULL;
 	char *into_name = NULL;
 	int shortlog_len = -1;
+	int merge_log_config = -1;
 	struct option options[] = {
 		{
 			.type = OPTION_INTEGER,
@@ -53,7 +53,7 @@ int cmd_fmt_merge_msg(int argc,
 	int ret;
 	struct fmt_merge_msg_opts opts;
 
-	repo_config(the_repository, fmt_merge_msg_config, NULL);
+	repo_config(repo, fmt_merge_msg_config, &merge_log_config);
 	argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage,
 			     0);
 	if (argc > 0)
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 222637a..4a2fc42 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -2,6 +2,7 @@
 #include "commit.h"
 #include "config.h"
 #include "environment.h"
+#include "for-each-ref.h"
 #include "gettext.h"
 #include "object.h"
 #include "parse-options.h"
@@ -9,19 +10,7 @@
 #include "strbuf.h"
 #include "strvec.h"
 
-static char const * const for_each_ref_usage[] = {
-	N_("git for-each-ref [<options>] [<pattern>]"),
-	N_("git for-each-ref [--points-at <object>]"),
-	N_("git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]"),
-	N_("git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"),
-	N_("git for-each-ref [--start-after <marker>]"),
-	NULL
-};
-
-int cmd_for_each_ref(int argc,
-		     const char **argv,
-		     const char *prefix,
-		     struct repository *repo)
+int for_each_ref_core(int argc, const char **argv, const char *prefix, struct repository *repo, const char *const *usage)
 {
 	struct ref_sorting *sorting;
 	struct string_list sorting_options = STRING_LIST_INIT_DUP;
@@ -70,17 +59,17 @@ int cmd_for_each_ref(int argc,
 	/* Set default (refname) sorting */
 	string_list_append(&sorting_options, "refname");
 
-	parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
+	parse_options(argc, argv, prefix, opts, usage, 0);
 	if (format.array_opts.max_count < 0) {
 		error("invalid --count argument: `%d'", format.array_opts.max_count);
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(usage, opts);
 	}
 	if (HAS_MULTI_BITS(format.quote_style)) {
 		error("more than one quoting style?");
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(usage, opts);
 	}
 	if (verify_ref_format(&format))
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(usage, opts);
 
 	if (filter.start_after && sorting_options.nr > 1)
 		die(_("cannot use --start-after with custom sort options"));
@@ -120,3 +109,16 @@ int cmd_for_each_ref(int argc,
 	strvec_clear(&vec);
 	return 0;
 }
+
+int cmd_for_each_ref(int argc,
+		     const char **argv,
+		     const char *prefix,
+		     struct repository *repo)
+{
+	static char const * const for_each_ref_usage[] = {
+		N_("git for-each-ref " COMMON_USAGE_FOR_EACH_REF),
+		NULL
+	};
+
+	return for_each_ref_core(argc, argv, prefix, repo, for_each_ref_usage);
+}
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 543a2cd..8ee95e0 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -503,13 +503,12 @@ static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid,
 	}
 }
 
-static int fsck_handle_reflog_ent(struct object_id *ooid, struct object_id *noid,
+static int fsck_handle_reflog_ent(const char *refname,
+				  struct object_id *ooid, struct object_id *noid,
 				  const char *email UNUSED,
 				  timestamp_t timestamp, int tz UNUSED,
-				  const char *message UNUSED, void *cb_data)
+				  const char *message UNUSED, void *cb_data UNUSED)
 {
-	const char *refname = cb_data;
-
 	if (verbose)
 		fprintf_ln(stderr, _("Checking reflog %s->%s"),
 			   oid_to_hex(ooid), oid_to_hex(noid));
@@ -526,7 +525,7 @@ static int fsck_handle_reflog(const char *logname, void *cb_data)
 	strbuf_worktree_ref(cb_data, &refname, logname);
 	refs_for_each_reflog_ent(get_main_ref_store(the_repository),
 				 refname.buf, fsck_handle_reflog_ent,
-				 refname.buf);
+				 NULL);
 	strbuf_release(&refname);
 	return 0;
 }
@@ -868,19 +867,20 @@ static int mark_packed_for_connectivity(const struct object_id *oid,
 
 static int check_pack_rev_indexes(struct repository *r, int show_progress)
 {
+	struct packfile_store *packs = r->objects->packfiles;
 	struct progress *progress = NULL;
 	uint32_t pack_count = 0;
 	int res = 0;
 
 	if (show_progress) {
-		for (struct packed_git *p = get_all_packs(r); p; p = p->next)
+		for (struct packed_git *p = packfile_store_get_all_packs(packs); p; p = p->next)
 			pack_count++;
 		progress = start_delayed_progress(the_repository,
 						  "Verifying reverse pack-indexes", pack_count);
 		pack_count = 0;
 	}
 
-	for (struct packed_git *p = get_all_packs(r); p; p = p->next) {
+	for (struct packed_git *p = packfile_store_get_all_packs(packs); p; p = p->next) {
 		int load_error = load_pack_revindex_from_disk(p);
 
 		if (load_error < 0) {
@@ -1000,6 +1000,8 @@ int cmd_fsck(int argc,
 		for_each_packed_object(the_repository,
 				       mark_packed_for_connectivity, NULL, 0);
 	} else {
+		struct packfile_store *packs = the_repository->objects->packfiles;
+
 		odb_prepare_alternates(the_repository->objects);
 		for (source = the_repository->objects->sources; source; source = source->next)
 			fsck_source(source);
@@ -1010,7 +1012,7 @@ int cmd_fsck(int argc,
 			struct progress *progress = NULL;
 
 			if (show_progress) {
-				for (p = get_all_packs(the_repository); p;
+				for (p = packfile_store_get_all_packs(packs); p;
 				     p = p->next) {
 					if (open_pack_index(p))
 						continue;
@@ -1020,7 +1022,7 @@ int cmd_fsck(int argc,
 				progress = start_progress(the_repository,
 							  _("Checking objects"), total);
 			}
-			for (p = get_all_packs(the_repository); p;
+			for (p = packfile_store_get_all_packs(packs); p;
 			     p = p->next) {
 				/* verify gives error messages itself */
 				if (verify_pack(the_repository,
diff --git a/builtin/gc.c b/builtin/gc.c
index 0edd94a..e19e13d 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -312,7 +312,8 @@ struct count_reflog_entries_data {
 	size_t limit;
 };
 
-static int count_reflog_entries(struct object_id *old_oid, struct object_id *new_oid,
+static int count_reflog_entries(const char *refname UNUSED,
+				struct object_id *old_oid, struct object_id *new_oid,
 				const char *committer, timestamp_t timestamp,
 				int tz, const char *msg, void *cb_data)
 {
@@ -486,9 +487,10 @@ static int too_many_loose_objects(struct gc_config *cfg)
 static struct packed_git *find_base_packs(struct string_list *packs,
 					  unsigned long limit)
 {
+	struct packfile_store *packfiles = the_repository->objects->packfiles;
 	struct packed_git *p, *base = NULL;
 
-	for (p = get_all_packs(the_repository); p; p = p->next) {
+	for (p = packfile_store_get_all_packs(packfiles); p; p = p->next) {
 		if (!p->pack_local || p->is_cruft)
 			continue;
 		if (limit) {
@@ -507,13 +509,14 @@ static struct packed_git *find_base_packs(struct string_list *packs,
 
 static int too_many_packs(struct gc_config *cfg)
 {
+	struct packfile_store *packs = the_repository->objects->packfiles;
 	struct packed_git *p;
 	int cnt;
 
 	if (cfg->gc_auto_pack_limit <= 0)
 		return 0;
 
-	for (cnt = 0, p = get_all_packs(the_repository); p; p = p->next) {
+	for (cnt = 0, p = packfile_store_get_all_packs(packs); p; p = p->next) {
 		if (!p->pack_local)
 			continue;
 		if (p->pack_keep)
@@ -1041,7 +1044,7 @@ int cmd_gc(int argc,
 		die(FAILED_RUN, "rerere");
 
 	report_garbage = report_pack_garbage;
-	reprepare_packed_git(the_repository);
+	odb_reprepare(the_repository->objects);
 	if (pack_garbage.nr > 0) {
 		close_object_store(the_repository->objects);
 		clean_pack_garbage();
@@ -1422,7 +1425,7 @@ static int incremental_repack_auto_condition(struct gc_config *cfg UNUSED)
 	if (incremental_repack_auto_limit < 0)
 		return 1;
 
-	for (p = get_packed_git(the_repository);
+	for (p = packfile_store_get_packs(the_repository->objects->packfiles);
 	     count < incremental_repack_auto_limit && p;
 	     p = p->next) {
 		if (!p->multi_pack_index)
@@ -1490,8 +1493,8 @@ static off_t get_auto_pack_size(void)
 	struct packed_git *p;
 	struct repository *r = the_repository;
 
-	reprepare_packed_git(r);
-	for (p = get_all_packs(r); p; p = p->next) {
+	odb_reprepare(r->objects);
+	for (p = packfile_store_get_all_packs(r->objects->packfiles); p; p = p->next) {
 		if (p->pack_size > max_size) {
 			second_largest_size = max_size;
 			max_size = p->pack_size;
diff --git a/builtin/grep.c b/builtin/grep.c
index 5df6537..13841fb 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -1091,7 +1091,7 @@ int cmd_grep(int argc,
 	if (show_in_pager == default_pager)
 		show_in_pager = git_pager(the_repository, 1);
 	if (show_in_pager) {
-		opt.color = 0;
+		opt.color = GIT_COLOR_NEVER;
 		opt.name_only = 1;
 		opt.null_following_name = 1;
 		opt.output_priv = &path_list;
@@ -1214,7 +1214,7 @@ int cmd_grep(int argc,
 		if (recurse_submodules)
 			repo_read_gitmodules(the_repository, 1);
 		if (startup_info->have_repository)
-			(void)get_packed_git(the_repository);
+			(void)packfile_store_get_packs(the_repository->objects->packfiles);
 
 		start_threads(&opt);
 	} else {
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index f91c301..2b78ba7 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -1640,13 +1640,9 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
 	rename_tmp_packfile(&final_index_name, curr_index_name, &index_name,
 			    hash, "idx", 1);
 
-	if (do_fsck_object) {
-		struct packed_git *p;
-		p = add_packed_git(the_repository, final_index_name,
-				   strlen(final_index_name), 0);
-		if (p)
-			install_packed_git(the_repository, p);
-	}
+	if (do_fsck_object)
+		packfile_store_load_pack(the_repository->objects->packfiles,
+					 final_index_name, 0);
 
 	if (!from_stdin) {
 		printf("%s\n", hash_to_hex(hash));
diff --git a/builtin/last-modified.c b/builtin/last-modified.c
new file mode 100644
index 0000000..ae8b36a
--- /dev/null
+++ b/builtin/last-modified.c
@@ -0,0 +1,327 @@
+#include "git-compat-util.h"
+#include "bloom.h"
+#include "builtin.h"
+#include "commit-graph.h"
+#include "commit.h"
+#include "config.h"
+#include "environment.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "environment.h"
+#include "hashmap.h"
+#include "hex.h"
+#include "log-tree.h"
+#include "object-name.h"
+#include "object.h"
+#include "parse-options.h"
+#include "quote.h"
+#include "repository.h"
+#include "revision.h"
+
+struct last_modified_entry {
+	struct hashmap_entry hashent;
+	struct object_id oid;
+	struct bloom_key key;
+	const char path[FLEX_ARRAY];
+};
+
+static int last_modified_entry_hashcmp(const void *unused UNUSED,
+				       const struct hashmap_entry *hent1,
+				       const struct hashmap_entry *hent2,
+				       const void *path)
+{
+	const struct last_modified_entry *ent1 =
+		container_of(hent1, const struct last_modified_entry, hashent);
+	const struct last_modified_entry *ent2 =
+		container_of(hent2, const struct last_modified_entry, hashent);
+	return strcmp(ent1->path, path ? path : ent2->path);
+}
+
+struct last_modified {
+	struct hashmap paths;
+	struct rev_info rev;
+	bool recursive;
+	bool show_trees;
+};
+
+static void last_modified_release(struct last_modified *lm)
+{
+	struct hashmap_iter iter;
+	struct last_modified_entry *ent;
+
+	hashmap_for_each_entry(&lm->paths, &iter, ent, hashent)
+		bloom_key_clear(&ent->key);
+
+	hashmap_clear_and_free(&lm->paths, struct last_modified_entry, hashent);
+	release_revisions(&lm->rev);
+}
+
+struct last_modified_callback_data {
+	struct last_modified *lm;
+	struct commit *commit;
+};
+
+static void add_path_from_diff(struct diff_queue_struct *q,
+			       struct diff_options *opt UNUSED, void *data)
+{
+	struct last_modified *lm = data;
+
+	for (int i = 0; i < q->nr; i++) {
+		struct diff_filepair *p = q->queue[i];
+		struct last_modified_entry *ent;
+		const char *path = p->two->path;
+
+		FLEX_ALLOC_STR(ent, path, path);
+		oidcpy(&ent->oid, &p->two->oid);
+		if (lm->rev.bloom_filter_settings)
+			bloom_key_fill(&ent->key, path, strlen(path),
+				       lm->rev.bloom_filter_settings);
+		hashmap_entry_init(&ent->hashent, strhash(ent->path));
+		hashmap_add(&lm->paths, &ent->hashent);
+	}
+}
+
+static int populate_paths_from_revs(struct last_modified *lm)
+{
+	int num_interesting = 0;
+	struct diff_options diffopt;
+
+	/*
+	 * Create a copy of `struct diff_options`. In this copy a callback is
+	 * set that when called adds entries to `paths` in `struct last_modified`.
+	 * This copy is used to diff the tree of the target revision against an
+	 * empty tree. This results in all paths in the target revision being
+	 * listed. After `paths` is populated, we don't need this copy no more.
+	 */
+	memcpy(&diffopt, &lm->rev.diffopt, sizeof(diffopt));
+	copy_pathspec(&diffopt.pathspec, &lm->rev.diffopt.pathspec);
+	diffopt.output_format = DIFF_FORMAT_CALLBACK;
+	diffopt.format_callback = add_path_from_diff;
+	diffopt.format_callback_data = lm;
+
+	for (size_t i = 0; i < lm->rev.pending.nr; i++) {
+		struct object_array_entry *obj = lm->rev.pending.objects + i;
+
+		if (obj->item->flags & UNINTERESTING)
+			continue;
+
+		if (num_interesting++)
+			return error(_("last-modified can only operate on one tree at a time"));
+
+		diff_tree_oid(lm->rev.repo->hash_algo->empty_tree,
+			      &obj->item->oid, "", &diffopt);
+		diff_flush(&diffopt);
+	}
+	clear_pathspec(&diffopt.pathspec);
+
+	return 0;
+}
+
+static void last_modified_emit(struct last_modified *lm,
+			       const char *path, const struct commit *commit)
+
+{
+	if (commit->object.flags & BOUNDARY)
+		putchar('^');
+	printf("%s\t", oid_to_hex(&commit->object.oid));
+
+	if (lm->rev.diffopt.line_termination)
+		write_name_quoted(path, stdout, '\n');
+	else
+		printf("%s%c", path, '\0');
+}
+
+static void mark_path(const char *path, const struct object_id *oid,
+		      struct last_modified_callback_data *data)
+{
+	struct last_modified_entry *ent;
+
+	/* Is it even a path that we are interested in? */
+	ent = hashmap_get_entry_from_hash(&data->lm->paths, strhash(path), path,
+					  struct last_modified_entry, hashent);
+	if (!ent)
+		return;
+
+	/*
+	 * Is it arriving at a version of interest, or is it from a side branch
+	 * which did not contribute to the final state?
+	 */
+	if (!oideq(oid, &ent->oid))
+		return;
+
+	last_modified_emit(data->lm, path, data->commit);
+
+	hashmap_remove(&data->lm->paths, &ent->hashent, path);
+	bloom_key_clear(&ent->key);
+	free(ent);
+}
+
+static void last_modified_diff(struct diff_queue_struct *q,
+			       struct diff_options *opt UNUSED, void *cbdata)
+{
+	struct last_modified_callback_data *data = cbdata;
+
+	for (int i = 0; i < q->nr; i++) {
+		struct diff_filepair *p = q->queue[i];
+		switch (p->status) {
+		case DIFF_STATUS_DELETED:
+			/*
+			 * There's no point in feeding a deletion, as it could
+			 * not have resulted in our current state, which
+			 * actually has the file.
+			 */
+			break;
+
+		default:
+			/*
+			 * Otherwise, we care only that we somehow arrived at
+			 * a final oid state. Note that this covers some
+			 * potentially controversial areas, including:
+			 *
+			 *  1. A rename or copy will be found, as it is the
+			 *     first time the content has arrived at the given
+			 *     path.
+			 *
+			 *  2. Even a non-content modification like a mode or
+			 *     type change will trigger it.
+			 *
+			 * We take the inclusive approach for now, and find
+			 * anything which impacts the path. Options to tweak
+			 * the behavior (e.g., to "--follow" the content across
+			 * renames) can come later.
+			 */
+			mark_path(p->two->path, &p->two->oid, data);
+			break;
+		}
+	}
+}
+
+static bool maybe_changed_path(struct last_modified *lm, struct commit *origin)
+{
+	struct bloom_filter *filter;
+	struct last_modified_entry *ent;
+	struct hashmap_iter iter;
+
+	if (!lm->rev.bloom_filter_settings)
+		return true;
+
+	if (commit_graph_generation(origin) == GENERATION_NUMBER_INFINITY)
+		return true;
+
+	filter = get_bloom_filter(lm->rev.repo, origin);
+	if (!filter)
+		return true;
+
+	hashmap_for_each_entry(&lm->paths, &iter, ent, hashent) {
+		if (bloom_filter_contains(filter, &ent->key,
+					  lm->rev.bloom_filter_settings))
+			return true;
+	}
+	return false;
+}
+
+static int last_modified_run(struct last_modified *lm)
+{
+	struct last_modified_callback_data data = { .lm = lm };
+
+	lm->rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+	lm->rev.diffopt.format_callback = last_modified_diff;
+	lm->rev.diffopt.format_callback_data = &data;
+
+	prepare_revision_walk(&lm->rev);
+
+	while (hashmap_get_size(&lm->paths)) {
+		data.commit = get_revision(&lm->rev);
+		if (!data.commit)
+			BUG("paths remaining beyond boundary in last-modified");
+
+		if (data.commit->object.flags & BOUNDARY) {
+			diff_tree_oid(lm->rev.repo->hash_algo->empty_tree,
+				      &data.commit->object.oid, "",
+				      &lm->rev.diffopt);
+			diff_flush(&lm->rev.diffopt);
+
+			break;
+		}
+
+		if (!maybe_changed_path(lm, data.commit))
+			continue;
+
+		log_tree_commit(&lm->rev, data.commit);
+	}
+
+	return 0;
+}
+
+static int last_modified_init(struct last_modified *lm, struct repository *r,
+			      const char *prefix, int argc, const char **argv)
+{
+	hashmap_init(&lm->paths, last_modified_entry_hashcmp, NULL, 0);
+
+	repo_init_revisions(r, &lm->rev, prefix);
+	lm->rev.def = "HEAD";
+	lm->rev.combine_merges = 1;
+	lm->rev.show_root_diff = 1;
+	lm->rev.boundary = 1;
+	lm->rev.no_commit_id = 1;
+	lm->rev.diff = 1;
+	lm->rev.diffopt.flags.no_recursive_diff_tree_combined = 1;
+	lm->rev.diffopt.flags.recursive = lm->recursive;
+	lm->rev.diffopt.flags.tree_in_recursive = lm->show_trees;
+
+	argc = setup_revisions(argc, argv, &lm->rev, NULL);
+	if (argc > 1) {
+		error(_("unknown last-modified argument: %s"), argv[1]);
+		return argc;
+	}
+
+	lm->rev.bloom_filter_settings = get_bloom_filter_settings(lm->rev.repo);
+
+	if (populate_paths_from_revs(lm) < 0)
+		return error(_("unable to setup last-modified"));
+
+	return 0;
+}
+
+int cmd_last_modified(int argc, const char **argv, const char *prefix,
+		      struct repository *repo)
+{
+	int ret;
+	struct last_modified lm = { 0 };
+
+	const char * const last_modified_usage[] = {
+		N_("git last-modified [--recursive] [--show-trees] "
+		   "[<revision-range>] [[--] <path>...]"),
+		NULL
+	};
+
+	struct option last_modified_options[] = {
+		OPT_BOOL('r', "recursive", &lm.recursive,
+			 N_("recurse into subtrees")),
+		OPT_BOOL('t', "show-trees", &lm.show_trees,
+			 N_("show tree entries when recursing into subtrees")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, last_modified_options,
+			     last_modified_usage,
+			     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
+
+	repo_config(repo, git_default_config, NULL);
+
+	ret = last_modified_init(&lm, repo, prefix, argc, argv);
+	if (ret > 0)
+		usage_with_options(last_modified_usage,
+				   last_modified_options);
+	if (ret)
+		goto out;
+
+	ret = last_modified_run(&lm);
+	if (ret)
+		goto out;
+
+out:
+	last_modified_release(&lm);
+
+	return ret;
+}
diff --git a/builtin/log.c b/builtin/log.c
index c2f8bbf..c8319b8 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -543,7 +543,13 @@ int cmd_whatchanged(int argc,
 	cmd_log_init(argc, argv, prefix, &rev, &opt, &cfg);
 
 	if (!cfg.i_still_use_this)
-		you_still_use_that("git whatchanged");
+		you_still_use_that("git whatchanged",
+				   _("\n"
+				     "hint: You can replace 'git whatchanged <opts>' with:\n"
+				     "hint:\tgit log <opts> --raw --no-merges\n"
+				     "hint: Or make an alias:\n"
+				     "hint:\tgit config set --global alias.whatchanged 'log --raw --no-merges'\n"
+				     "\n"));
 
 	if (!rev.diffopt.output_format)
 		rev.diffopt.output_format = DIFF_FORMAT_RAW;
@@ -1400,12 +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);
@@ -1413,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);
 	}
 }
 
@@ -2327,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);
 	}
 
 	/*
@@ -2486,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/ls-files.c b/builtin/ls-files.c
index c06a6f3..b148607 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -414,14 +414,21 @@ static void show_files(struct repository *repo, struct dir_struct *dir)
 	if (!(show_cached || show_stage || show_deleted || show_modified))
 		return;
 
-	if (!show_sparse_dirs)
-		ensure_full_index(repo->index);
-
 	for (i = 0; i < repo->index->cache_nr; i++) {
 		const struct cache_entry *ce = repo->index->cache[i];
 		struct stat st;
 		int stat_err;
 
+		if (S_ISSPARSEDIR(ce->ce_mode) && !show_sparse_dirs) {
+			/*
+			 * This is the first time we've hit a sparse dir,
+			 * so expansion will leave the first 'i' entries
+			 * alone.
+			 */
+			ensure_full_index(repo->index);
+			ce = repo->index->cache[i];
+		}
+
 		construct_fullname(&fullname, repo, ce);
 
 		if ((dir->flags & DIR_SHOW_IGNORED) &&
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 5d55731..ec6940f 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -373,7 +373,6 @@ int cmd_ls_tree(int argc,
 		OPT_END()
 	};
 	struct ls_tree_cmdmode_to_fmt *m2f = ls_tree_cmdmode_format;
-	struct object_context obj_context = {0};
 	int ret;
 
 	repo_config(the_repository, git_default_config, NULL);
@@ -405,9 +404,8 @@ int cmd_ls_tree(int argc,
 			ls_tree_usage, ls_tree_options);
 	if (argc < 1)
 		usage_with_options(ls_tree_usage, ls_tree_options);
-	if (get_oid_with_context(the_repository, argv[0],
-				 GET_OID_HASH_ANY, &oid,
-				 &obj_context))
+	if (repo_get_oid_with_flags(the_repository, argv[0], &oid,
+				    GET_OID_HASH_ANY))
 		die("Not a valid object name %s", argv[0]);
 
 	/*
@@ -447,6 +445,5 @@ int cmd_ls_tree(int argc,
 
 	ret = !!read_tree(the_repository, tree, &options.pathspec, fn, &options);
 	clear_pathspec(&options.pathspec);
-	object_context_release(&obj_context);
 	return ret;
 }
diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c
index 03b5100..17aa4db 100644
--- a/builtin/merge-recursive.c
+++ b/builtin/merge-recursive.c
@@ -38,7 +38,8 @@ int cmd_merge_recursive(int argc,
 	if (argv[0] && ends_with(argv[0], "-subtree"))
 		o.subtree_shift = "";
 
-	if (argc == 2 && !strcmp(argv[1], "-h")) {
+	if (argc == 2 && (!strcmp(argv[1], "-h") ||
+			  !strcmp(argv[1], "--help-all"))) {
 		struct strbuf msg = STRBUF_INIT;
 		strbuf_addf(&msg, builtin_merge_recursive_usage, argv[0]);
 		show_usage_if_asked(argc, argv, msg.buf);
diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c
index 203f0e6..1c063d9 100644
--- a/builtin/merge-tree.c
+++ b/builtin/merge-tree.c
@@ -619,32 +619,34 @@ int cmd_merge_tree(int argc,
 			    "--merge-base", "--stdin");
 		line_termination = '\0';
 		while (strbuf_getline_lf(&buf, stdin) != EOF) {
-			struct strbuf **split;
+			struct string_list split = STRING_LIST_INIT_NODUP;
 			const char *input_merge_base = NULL;
 
-			split = strbuf_split(&buf, ' ');
-			if (!split[0] || !split[1])
+			string_list_split_in_place_f(&split, buf.buf, " ", -1,
+						     STRING_LIST_SPLIT_TRIM);
+
+			if (split.nr < 2)
 				die(_("malformed input line: '%s'."), buf.buf);
-			strbuf_rtrim(split[0]);
-			strbuf_rtrim(split[1]);
 
 			/* parse the merge-base */
-			if (!strcmp(split[1]->buf, "--")) {
-				input_merge_base = split[0]->buf;
+			if (!strcmp(split.items[1].string, "--")) {
+				input_merge_base = split.items[0].string;
 			}
 
-			if (input_merge_base && split[2] && split[3] && !split[4]) {
-				strbuf_rtrim(split[2]);
-				strbuf_rtrim(split[3]);
-				real_merge(&o, input_merge_base, split[2]->buf, split[3]->buf, prefix);
-			} else if (!input_merge_base && !split[2]) {
-				real_merge(&o, NULL, split[0]->buf, split[1]->buf, prefix);
+			if (input_merge_base && split.nr == 4) {
+				real_merge(&o, input_merge_base,
+					   split.items[2].string, split.items[3].string,
+					   prefix);
+			} else if (!input_merge_base && split.nr == 2) {
+				real_merge(&o, NULL,
+					   split.items[0].string, split.items[1].string,
+					   prefix);
 			} else {
 				die(_("malformed input line: '%s'."), buf.buf);
 			}
 			maybe_flush_or_die(stdout, "stdout");
 
-			strbuf_list_free(split);
+			string_list_clear(&split, 0);
 		}
 		strbuf_release(&buf);
 
diff --git a/builtin/merge.c b/builtin/merge.c
index ce880e6..c421a11 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -875,7 +875,7 @@ static void add_strategies(const char *string, unsigned attr)
 	if (string) {
 		struct string_list list = STRING_LIST_INIT_DUP;
 		struct string_list_item *item;
-		string_list_split(&list, string, ' ', -1);
+		string_list_split(&list, string, " ", -1);
 		for_each_string_list_item(item, &list)
 			append_strategy(get_strategy(item->string));
 		string_list_clear(&list, 0);
@@ -1374,10 +1374,14 @@ int cmd_merge(int argc,
 	struct commit_list *remoteheads = NULL, *p;
 	void *branch_to_free;
 	int orig_argc = argc;
+	int merge_log_config = -1;
 
 	show_usage_with_options_if_asked(argc, argv,
 					 builtin_merge_usage, builtin_merge_options);
 
+#ifndef WITH_BREAKING_CHANGES
+	warn_on_auto_comment_char = true;
+#endif /* !WITH_BREAKING_CHANGES */
 	prepare_repo_settings(the_repository);
 	the_repository->settings.command_requires_full_index = 0;
 
@@ -1392,7 +1396,7 @@ int cmd_merge(int argc,
 		skip_prefix(branch, "refs/heads/", &branch);
 
 	init_diff_ui_defaults();
-	repo_config(the_repository, git_merge_config, NULL);
+	repo_config(the_repository, git_merge_config, &merge_log_config);
 
 	if (!branch || is_null_oid(&head_oid))
 		head_commit = NULL;
@@ -1862,7 +1866,7 @@ int cmd_merge(int argc,
 	if (squash) {
 		finish(head_commit, remoteheads, NULL, NULL);
 
-		git_test_write_commit_graph_or_die();
+		git_test_write_commit_graph_or_die(the_repository->objects->sources);
 	} else
 		write_merge_state(remoteheads);
 
diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c
index d3b9e98..5f364aa 100644
--- a/builtin/multi-pack-index.c
+++ b/builtin/multi-pack-index.c
@@ -65,12 +65,20 @@ static int parse_object_dir(const struct option *opt, const char *arg,
 	char **value = opt->value;
 	free(*value);
 	if (unset)
-		*value = xstrdup(repo_get_object_directory(the_repository));
+		*value = xstrdup(the_repository->objects->sources->path);
 	else
 		*value = real_pathdup(arg, 1);
 	return 0;
 }
 
+static struct odb_source *handle_object_dir_option(struct repository *repo)
+{
+	struct odb_source *source = odb_find_source(repo->objects, opts.object_dir);
+	if (!source)
+		source = odb_add_to_alternates_memory(repo->objects, opts.object_dir);
+	return source;
+}
+
 static struct option common_opts[] = {
 	OPT_CALLBACK(0, "object-dir", &opts.object_dir,
 	  N_("directory"),
@@ -140,6 +148,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,
 			     N_("refs snapshot for selecting bitmap commits")),
 		OPT_END(),
 	};
+	struct odb_source *source;
 	int ret;
 
 	opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE;
@@ -158,6 +167,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,
 	if (argc)
 		usage_with_options(builtin_multi_pack_index_write_usage,
 				   options);
+	source = handle_object_dir_option(repo);
 
 	FREE_AND_NULL(options);
 
@@ -166,7 +176,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,
 
 		read_packs_from_stdin(&packs);
 
-		ret = write_midx_file_only(repo, opts.object_dir, &packs,
+		ret = write_midx_file_only(source, &packs,
 					   opts.preferred_pack,
 					   opts.refs_snapshot, opts.flags);
 
@@ -177,7 +187,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,
 
 	}
 
-	ret = write_midx_file(repo, opts.object_dir, opts.preferred_pack,
+	ret = write_midx_file(source, opts.preferred_pack,
 			      opts.refs_snapshot, opts.flags);
 
 	free(opts.refs_snapshot);
@@ -194,6 +204,8 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv,
 			N_("force progress reporting"), MIDX_PROGRESS),
 		OPT_END(),
 	};
+	struct odb_source *source;
+
 	options = add_common_options(builtin_multi_pack_index_verify_options);
 
 	trace2_cmd_mode(argv[0]);
@@ -206,10 +218,11 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv,
 	if (argc)
 		usage_with_options(builtin_multi_pack_index_verify_usage,
 				   options);
+	source = handle_object_dir_option(the_repository);
 
 	FREE_AND_NULL(options);
 
-	return verify_midx_file(the_repository, opts.object_dir, opts.flags);
+	return verify_midx_file(source, opts.flags);
 }
 
 static int cmd_multi_pack_index_expire(int argc, const char **argv,
@@ -222,6 +235,8 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv,
 			N_("force progress reporting"), MIDX_PROGRESS),
 		OPT_END(),
 	};
+	struct odb_source *source;
+
 	options = add_common_options(builtin_multi_pack_index_expire_options);
 
 	trace2_cmd_mode(argv[0]);
@@ -234,10 +249,11 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv,
 	if (argc)
 		usage_with_options(builtin_multi_pack_index_expire_usage,
 				   options);
+	source = handle_object_dir_option(the_repository);
 
 	FREE_AND_NULL(options);
 
-	return expire_midx_packs(the_repository, opts.object_dir, opts.flags);
+	return expire_midx_packs(source, opts.flags);
 }
 
 static int cmd_multi_pack_index_repack(int argc, const char **argv,
@@ -252,6 +268,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv,
 		  N_("force progress reporting"), MIDX_PROGRESS),
 		OPT_END(),
 	};
+	struct odb_source *source;
 
 	options = add_common_options(builtin_multi_pack_index_repack_options);
 
@@ -266,11 +283,11 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv,
 	if (argc)
 		usage_with_options(builtin_multi_pack_index_repack_usage,
 				   options);
+	source = handle_object_dir_option(the_repository);
 
 	FREE_AND_NULL(options);
 
-	return midx_repack(the_repository, opts.object_dir,
-			   (size_t)opts.batch_size, opts.flags);
+	return midx_repack(source, (size_t)opts.batch_size, opts.flags);
 }
 
 int cmd_multi_pack_index(int argc,
diff --git a/builtin/notes.c b/builtin/notes.c
index 6fb4144..9af602b 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -376,18 +376,19 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 
 	while (strbuf_getline_lf(&buf, stdin) != EOF) {
 		struct object_id from_obj, to_obj;
-		struct strbuf **split;
+		struct string_list split = STRING_LIST_INIT_NODUP;
 		int err;
 
-		split = strbuf_split(&buf, ' ');
-		if (!split[0] || !split[1])
+		string_list_split_in_place_f(&split, buf.buf, " ", -1,
+					     STRING_LIST_SPLIT_TRIM);
+		if (split.nr < 2)
 			die(_("malformed input line: '%s'."), buf.buf);
-		strbuf_rtrim(split[0]);
-		strbuf_rtrim(split[1]);
-		if (repo_get_oid(the_repository, split[0]->buf, &from_obj))
-			die(_("failed to resolve '%s' as a valid ref."), split[0]->buf);
-		if (repo_get_oid(the_repository, split[1]->buf, &to_obj))
-			die(_("failed to resolve '%s' as a valid ref."), split[1]->buf);
+		if (repo_get_oid(the_repository, split.items[0].string, &from_obj))
+			die(_("failed to resolve '%s' as a valid ref."),
+			    split.items[0].string);
+		if (repo_get_oid(the_repository, split.items[1].string, &to_obj))
+			die(_("failed to resolve '%s' as a valid ref."),
+			    split.items[1].string);
 
 		if (rewrite_cmd)
 			err = copy_note_for_rewrite(c, &from_obj, &to_obj);
@@ -397,11 +398,11 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 
 		if (err) {
 			error(_("failed to copy notes from '%s' to '%s'"),
-			      split[0]->buf, split[1]->buf);
+			      split.items[0].string, split.items[1].string);
 			ret = 1;
 		}
 
-		strbuf_list_free(split);
+		string_list_clear(&split, 0);
 	}
 
 	if (!rewrite_cmd) {
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 53a2256..5bdc44f 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -1741,19 +1741,19 @@ static int want_object_in_pack_mtime(const struct object_id *oid,
 		struct multi_pack_index *m = get_multi_pack_index(source);
 		struct pack_entry e;
 
-		if (m && fill_midx_entry(the_repository, oid, &e, m)) {
+		if (m && fill_midx_entry(m, oid, &e)) {
 			want = want_object_in_pack_one(e.p, oid, exclude, found_pack, found_offset, found_mtime);
 			if (want != -1)
 				return want;
 		}
 	}
 
-	list_for_each(pos, get_packed_git_mru(the_repository)) {
+	list_for_each(pos, packfile_store_get_packs_mru(the_repository->objects->packfiles)) {
 		struct packed_git *p = list_entry(pos, struct packed_git, mru);
 		want = want_object_in_pack_one(p, oid, exclude, found_pack, found_offset, found_mtime);
 		if (!exclude && want > 0)
 			list_move(&p->mru,
-				  get_packed_git_mru(the_repository));
+				  packfile_store_get_packs_mru(the_repository->objects->packfiles));
 		if (want != -1)
 			return want;
 	}
@@ -3774,7 +3774,7 @@ static void show_object_pack_hint(struct object *object, const char *name,
 	enum stdin_packs_mode mode = *(enum stdin_packs_mode *)data;
 	if (mode == STDIN_PACKS_MODE_FOLLOW) {
 		if (object->type == OBJ_BLOB &&
-		    !has_object(the_repository, &object->oid, 0))
+		    !odb_has_object(the_repository->objects, &object->oid, 0))
 			return;
 		add_object_entry(&object->oid, object->type, name, 0);
 	} else {
@@ -3831,6 +3831,7 @@ static int pack_mtime_cmp(const void *_a, const void *_b)
 
 static void read_packs_list_from_stdin(struct rev_info *revs)
 {
+	struct packfile_store *packs = the_repository->objects->packfiles;
 	struct strbuf buf = STRBUF_INIT;
 	struct string_list include_packs = STRING_LIST_INIT_DUP;
 	struct string_list exclude_packs = STRING_LIST_INIT_DUP;
@@ -3855,7 +3856,7 @@ static void read_packs_list_from_stdin(struct rev_info *revs)
 	string_list_sort(&exclude_packs);
 	string_list_remove_duplicates(&exclude_packs, 0);
 
-	for (p = get_all_packs(the_repository); p; p = p->next) {
+	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
 		const char *pack_name = pack_basename(p);
 
 		if ((item = string_list_lookup(&include_packs, pack_name)))
@@ -4076,6 +4077,7 @@ static void enumerate_cruft_objects(void)
 
 static void enumerate_and_traverse_cruft_objects(struct string_list *fresh_packs)
 {
+	struct packfile_store *packs = the_repository->objects->packfiles;
 	struct packed_git *p;
 	struct rev_info revs;
 	int ret;
@@ -4105,7 +4107,7 @@ static void enumerate_and_traverse_cruft_objects(struct string_list *fresh_packs
 	 * Re-mark only the fresh packs as kept so that objects in
 	 * unknown packs do not halt the reachability traversal early.
 	 */
-	for (p = get_all_packs(the_repository); p; p = p->next)
+	for (p = packfile_store_get_all_packs(packs); p; p = p->next)
 		p->pack_keep_in_core = 0;
 	mark_pack_kept_in_core(fresh_packs, 1);
 
@@ -4122,6 +4124,7 @@ static void enumerate_and_traverse_cruft_objects(struct string_list *fresh_packs
 
 static void read_cruft_objects(void)
 {
+	struct packfile_store *packs = the_repository->objects->packfiles;
 	struct strbuf buf = STRBUF_INIT;
 	struct string_list discard_packs = STRING_LIST_INIT_DUP;
 	struct string_list fresh_packs = STRING_LIST_INIT_DUP;
@@ -4142,7 +4145,7 @@ static void read_cruft_objects(void)
 	string_list_sort(&discard_packs);
 	string_list_sort(&fresh_packs);
 
-	for (p = get_all_packs(the_repository); p; p = p->next) {
+	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
 		const char *pack_name = pack_basename(p);
 		struct string_list_item *item;
 
@@ -4390,11 +4393,12 @@ static void add_unreachable_loose_objects(struct rev_info *revs)
 
 static int has_sha1_pack_kept_or_nonlocal(const struct object_id *oid)
 {
+	struct packfile_store *packs = the_repository->objects->packfiles;
 	static struct packed_git *last_found = (void *)1;
 	struct packed_git *p;
 
 	p = (last_found != (void *)1) ? last_found :
-					get_all_packs(the_repository);
+					packfile_store_get_all_packs(packs);
 
 	while (p) {
 		if ((!p->pack_local || p->pack_keep ||
@@ -4404,7 +4408,7 @@ static int has_sha1_pack_kept_or_nonlocal(const struct object_id *oid)
 			return 1;
 		}
 		if (p == last_found)
-			p = get_all_packs(the_repository);
+			p = packfile_store_get_all_packs(packs);
 		else
 			p = p->next;
 		if (p == last_found)
@@ -4436,12 +4440,13 @@ static int loosened_object_can_be_discarded(const struct object_id *oid,
 
 static void loosen_unused_packed_objects(void)
 {
+	struct packfile_store *packs = the_repository->objects->packfiles;
 	struct packed_git *p;
 	uint32_t i;
 	uint32_t loosened_objects_nr = 0;
 	struct object_id oid;
 
-	for (p = get_all_packs(the_repository); p; p = p->next) {
+	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
 		if (!p->pack_local || p->pack_keep || p->pack_keep_in_core)
 			continue;
 
@@ -4591,8 +4596,8 @@ static int add_objects_by_path(const char *path,
 
 		/* Skip objects that do not exist locally. */
 		if ((exclude_promisor_objects || arg_missing_action != MA_ERROR) &&
-		    oid_object_info_extended(the_repository, oid, &oi,
-					     OBJECT_INFO_FOR_PREFETCH) < 0)
+		    odb_read_object_info_extended(the_repository->objects, oid, &oi,
+						  OBJECT_INFO_FOR_PREFETCH) < 0)
 			continue;
 
 		exclude = is_oid_uninteresting(the_repository, oid);
@@ -4650,7 +4655,7 @@ static void get_object_list_path_walk(struct rev_info *revs)
 		die(_("failed to pack objects via path-walk"));
 }
 
-static void get_object_list(struct rev_info *revs, int ac, const char **av)
+static void get_object_list(struct rev_info *revs, struct strvec *argv)
 {
 	struct setup_revision_opt s_r_opt = {
 		.allow_exclude_promisor_objects = 1,
@@ -4660,7 +4665,7 @@ static void get_object_list(struct rev_info *revs, int ac, const char **av)
 	int save_warning;
 
 	save_commit_buffer = 0;
-	setup_revisions(ac, av, revs, &s_r_opt);
+	setup_revisions_from_strvec(argv, revs, &s_r_opt);
 
 	/* make sure shallows are read */
 	is_repository_shallow(the_repository);
@@ -4742,12 +4747,13 @@ static void get_object_list(struct rev_info *revs, int ac, const char **av)
 
 static void add_extra_kept_packs(const struct string_list *names)
 {
+	struct packfile_store *packs = the_repository->objects->packfiles;
 	struct packed_git *p;
 
 	if (!names->nr)
 		return;
 
-	for (p = get_all_packs(the_repository); p; p = p->next) {
+	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
 		const char *name = basename(p->pack_name);
 		int i;
 
@@ -5185,8 +5191,10 @@ int cmd_pack_objects(int argc,
 
 	add_extra_kept_packs(&keep_pack_list);
 	if (ignore_packed_keep_on_disk) {
+		struct packfile_store *packs = the_repository->objects->packfiles;
 		struct packed_git *p;
-		for (p = get_all_packs(the_repository); p; p = p->next)
+
+		for (p = packfile_store_get_all_packs(packs); p; p = p->next)
 			if (p->pack_local && p->pack_keep)
 				break;
 		if (!p) /* no keep-able packs found */
@@ -5198,8 +5206,10 @@ int cmd_pack_objects(int argc,
 		 * want to unset "local" based on looking at packs, as
 		 * it also covers non-local objects
 		 */
+		struct packfile_store *packs = the_repository->objects->packfiles;
 		struct packed_git *p;
-		for (p = get_all_packs(the_repository); p; p = p->next) {
+
+		for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
 			if (!p->pack_local) {
 				have_non_local_packs = 1;
 				break;
@@ -5229,7 +5239,7 @@ int cmd_pack_objects(int argc,
 			revs.include_check = is_not_in_promisor_pack;
 			revs.include_check_obj = is_not_in_promisor_pack_obj;
 		}
-		get_object_list(&revs, rp.nr, rp.v);
+		get_object_list(&revs, &rp);
 		release_revisions(&revs);
 	}
 	cleanup_preferred_base();
diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c
index fe81c29..80743d8 100644
--- a/builtin/pack-redundant.c
+++ b/builtin/pack-redundant.c
@@ -566,7 +566,8 @@ static struct pack_list * add_pack(struct packed_git *p)
 
 static struct pack_list * add_pack_file(const char *filename)
 {
-	struct packed_git *p = get_all_packs(the_repository);
+	struct packfile_store *packs = the_repository->objects->packfiles;
+	struct packed_git *p = packfile_store_get_all_packs(packs);
 
 	if (strlen(filename) < 40)
 		die("Bad pack filename: %s", filename);
@@ -581,7 +582,8 @@ static struct pack_list * add_pack_file(const char *filename)
 
 static void load_all(void)
 {
-	struct packed_git *p = get_all_packs(the_repository);
+	struct packfile_store *packs = the_repository->objects->packfiles;
+	struct packed_git *p = packfile_store_get_all_packs(packs);
 
 	while (p) {
 		add_pack(p);
@@ -626,7 +628,7 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix UNUSED, s
 	}
 
 	if (!i_still_use_this)
-		you_still_use_that("git pack-redundant");
+		you_still_use_that("git pack-redundant", NULL);
 
 	if (load_all_packs)
 		load_all();
diff --git a/builtin/pack-refs.c b/builtin/pack-refs.c
index 5e28d0f..3446b84 100644
--- a/builtin/pack-refs.c
+++ b/builtin/pack-refs.c
@@ -1,60 +1,16 @@
 #include "builtin.h"
-#include "config.h"
-#include "environment.h"
 #include "gettext.h"
-#include "parse-options.h"
-#include "refs.h"
-#include "revision.h"
-
-static char const * const pack_refs_usage[] = {
-	N_("git pack-refs [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]"),
-	NULL
-};
+#include "pack-refs.h"
 
 int cmd_pack_refs(int argc,
 		  const char **argv,
 		  const char *prefix,
 		  struct repository *repo)
 {
-	struct ref_exclusions excludes = REF_EXCLUSIONS_INIT;
-	struct string_list included_refs = STRING_LIST_INIT_NODUP;
-	struct pack_refs_opts pack_refs_opts = {
-		.exclusions = &excludes,
-		.includes = &included_refs,
-		.flags = PACK_REFS_PRUNE,
+	static char const * const pack_refs_usage[] = {
+		N_("git pack-refs " PACK_REFS_OPTS),
+		NULL
 	};
-	struct string_list option_excluded_refs = STRING_LIST_INIT_NODUP;
-	struct string_list_item *item;
-	int pack_all = 0;
-	int ret;
 
-	struct option opts[] = {
-		OPT_BOOL(0, "all",   &pack_all, N_("pack everything")),
-		OPT_BIT(0, "prune", &pack_refs_opts.flags, N_("prune loose refs (default)"), PACK_REFS_PRUNE),
-		OPT_BIT(0, "auto", &pack_refs_opts.flags, N_("auto-pack refs as needed"), PACK_REFS_AUTO),
-		OPT_STRING_LIST(0, "include", pack_refs_opts.includes, N_("pattern"),
-			N_("references to include")),
-		OPT_STRING_LIST(0, "exclude", &option_excluded_refs, N_("pattern"),
-			N_("references to exclude")),
-		OPT_END(),
-	};
-	repo_config(repo, git_default_config, NULL);
-	if (parse_options(argc, argv, prefix, opts, pack_refs_usage, 0))
-		usage_with_options(pack_refs_usage, opts);
-
-	for_each_string_list_item(item, &option_excluded_refs)
-		add_ref_exclusion(pack_refs_opts.exclusions, item->string);
-
-	if (pack_all)
-		string_list_append(pack_refs_opts.includes, "*");
-
-	if (!pack_refs_opts.includes->nr)
-		string_list_append(pack_refs_opts.includes, "refs/tags/*");
-
-	ret = refs_pack_refs(get_main_ref_store(repo), &pack_refs_opts);
-
-	clear_ref_exclusions(&excludes);
-	string_list_clear(&included_refs, 0);
-	string_list_clear(&option_excluded_refs, 0);
-	return ret;
+	return pack_refs_core(argc, argv, prefix, repo, pack_refs_usage);
 }
diff --git a/builtin/push.c b/builtin/push.c
index d0794b7..5b6cebb 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -27,7 +27,7 @@ static const char * const push_usage[] = {
 	NULL,
 };
 
-static int push_use_color = -1;
+static enum git_colorbool push_use_color = GIT_COLOR_UNKNOWN;
 static char push_colors[][COLOR_MAXLEN] = {
 	GIT_COLOR_RESET,
 	GIT_COLOR_RED,	/* ERROR */
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index a563abf..e54c0f7 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -6,6 +6,8 @@
 #include "parse-options.h"
 #include "range-diff.h"
 #include "config.h"
+#include "parse.h"
+#include "color.h"
 
 
 static const char * const builtin_range_diff_usage[] = {
@@ -15,18 +17,34 @@ N_("git range-diff [<options>] <base> <old-tip> <new-tip>"),
 NULL
 };
 
+static int parse_max_memory(const struct option *opt, const char *arg, int unset)
+{
+	size_t *max_memory = opt->value;
+	uintmax_t val;
+
+	if (unset)
+		return 0;
+
+	if (!git_parse_unsigned(arg, &val, SIZE_MAX))
+		return error(_("invalid max-memory value: %s"), arg);
+
+	*max_memory = (size_t)val;
+	return 0;
+}
+
 int cmd_range_diff(int argc,
 		   const char **argv,
 		   const char *prefix,
 		   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[] = {
@@ -35,11 +53,15 @@ 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,
 				  N_("style"), N_("passed to 'git log'"), 0),
+		OPT_CALLBACK(0, "max-memory", &range_diff_opts.max_memory,
+			     N_("size"),
+			     N_("maximum memory for cost matrix (default 4G)"),
+			     parse_max_memory),
 		OPT_PASSTHRU_ARGV(0, "remerge-diff", &diff_merges_arg, NULL,
 				  N_("passed to 'git log'"), PARSE_OPT_NOARG),
 		OPT_BOOL(0, "left-only", &left_only,
@@ -66,12 +88,12 @@ int cmd_range_diff(int argc,
 
 	/* force color when --dual-color was used */
 	if (!simple_color)
-		diffopt.use_color = 1;
+		diffopt.use_color = GIT_COLOR_ALWAYS;
 
 	/* 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++)
@@ -103,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 &&
@@ -123,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 &&
@@ -154,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"),
@@ -166,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/rebase.c b/builtin/rebase.c
index 3c85768..c468828 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -299,8 +299,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
 			     oid_to_hex(&opts->restrict_revision->object.oid));
 
 	ret = sequencer_make_script(the_repository, &todo_list.buf,
-				    make_script_args.nr, make_script_args.v,
-				    flags);
+				    &make_script_args, flags);
 	if (ret) {
 		error(_("could not generate todo list"));
 		goto cleanup;
@@ -1242,6 +1241,9 @@ int cmd_rebase(int argc,
 					 builtin_rebase_usage,
 					 builtin_rebase_options);
 
+#ifndef WITH_BREAKING_CHANGES
+	warn_on_auto_comment_char = true;
+#endif /* !WITH_BREAKING_CHANGES */
 	prepare_repo_settings(the_repository);
 	the_repository->settings.command_requires_full_index = 0;
 
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 1113137..c9288a9 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -2389,7 +2389,7 @@ static const char *unpack(int err_fd, struct shallow_info *si)
 		status = finish_command(&child);
 		if (status)
 			return "index-pack abnormal exit";
-		reprepare_packed_git(the_repository);
+		odb_reprepare(the_repository->objects);
 	}
 	return NULL;
 }
diff --git a/builtin/reflog.c b/builtin/reflog.c
index 1db26aa..dcbfe89 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -3,6 +3,8 @@
 #include "builtin.h"
 #include "config.h"
 #include "gettext.h"
+#include "hex.h"
+#include "odb.h"
 #include "revision.h"
 #include "reachable.h"
 #include "wildmatch.h"
@@ -17,21 +19,24 @@
 #define BUILTIN_REFLOG_LIST_USAGE \
 	N_("git reflog list")
 
-#define BUILTIN_REFLOG_EXPIRE_USAGE \
-	N_("git reflog expire [--expire=<time>] [--expire-unreachable=<time>]\n" \
-	   "                  [--rewrite] [--updateref] [--stale-fix]\n" \
-	   "                  [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]")
+#define BUILTIN_REFLOG_EXISTS_USAGE \
+	N_("git reflog exists <ref>")
+
+#define BUILTIN_REFLOG_WRITE_USAGE \
+	N_("git reflog write <ref> <old-oid> <new-oid> <message>")
 
 #define BUILTIN_REFLOG_DELETE_USAGE \
 	N_("git reflog delete [--rewrite] [--updateref]\n" \
 	   "                  [--dry-run | -n] [--verbose] <ref>@{<specifier>}...")
 
-#define BUILTIN_REFLOG_EXISTS_USAGE \
-	N_("git reflog exists <ref>")
-
 #define BUILTIN_REFLOG_DROP_USAGE \
 	N_("git reflog drop [--all [--single-worktree] | <refs>...]")
 
+#define BUILTIN_REFLOG_EXPIRE_USAGE \
+	N_("git reflog expire [--expire=<time>] [--expire-unreachable=<time>]\n" \
+	   "                  [--rewrite] [--updateref] [--stale-fix]\n" \
+	   "                  [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]")
+
 static const char *const reflog_show_usage[] = {
 	BUILTIN_REFLOG_SHOW_USAGE,
 	NULL,
@@ -42,9 +47,14 @@ static const char *const reflog_list_usage[] = {
 	NULL,
 };
 
-static const char *const reflog_expire_usage[] = {
-	BUILTIN_REFLOG_EXPIRE_USAGE,
-	NULL
+static const char *const reflog_exists_usage[] = {
+	BUILTIN_REFLOG_EXISTS_USAGE,
+	NULL,
+};
+
+static const char *const reflog_write_usage[] = {
+	BUILTIN_REFLOG_WRITE_USAGE,
+	NULL,
 };
 
 static const char *const reflog_delete_usage[] = {
@@ -52,23 +62,24 @@ static const char *const reflog_delete_usage[] = {
 	NULL
 };
 
-static const char *const reflog_exists_usage[] = {
-	BUILTIN_REFLOG_EXISTS_USAGE,
-	NULL,
-};
-
 static const char *const reflog_drop_usage[] = {
 	BUILTIN_REFLOG_DROP_USAGE,
 	NULL,
 };
 
+static const char *const reflog_expire_usage[] = {
+	BUILTIN_REFLOG_EXPIRE_USAGE,
+	NULL
+};
+
 static const char *const reflog_usage[] = {
 	BUILTIN_REFLOG_SHOW_USAGE,
 	BUILTIN_REFLOG_LIST_USAGE,
-	BUILTIN_REFLOG_EXPIRE_USAGE,
+	BUILTIN_REFLOG_EXISTS_USAGE,
+	BUILTIN_REFLOG_WRITE_USAGE,
 	BUILTIN_REFLOG_DELETE_USAGE,
 	BUILTIN_REFLOG_DROP_USAGE,
-	BUILTIN_REFLOG_EXISTS_USAGE,
+	BUILTIN_REFLOG_EXPIRE_USAGE,
 	NULL
 };
 
@@ -395,6 +406,61 @@ static int cmd_reflog_drop(int argc, const char **argv, const char *prefix,
 	return ret;
 }
 
+static int cmd_reflog_write(int argc, const char **argv, const char *prefix,
+			    struct repository *repo)
+{
+	const struct option options[] = {
+		OPT_END()
+	};
+	struct object_id old_oid, new_oid;
+	struct strbuf err = STRBUF_INIT;
+	struct ref_transaction *tx;
+	const char *ref, *message;
+	int ret;
+
+	repo_config(repo, git_ident_config, NULL);
+
+	argc = parse_options(argc, argv, prefix, options, reflog_write_usage, 0);
+	if (argc != 4)
+		usage_with_options(reflog_write_usage, options);
+
+	ref = argv[0];
+	if (!is_root_ref(ref) && check_refname_format(ref, 0))
+		die(_("invalid reference name: %s"), ref);
+
+	ret = get_oid_hex_algop(argv[1], &old_oid, repo->hash_algo);
+	if (ret)
+		die(_("invalid old object ID: '%s'"), argv[1]);
+	if (!is_null_oid(&old_oid) && !odb_has_object(repo->objects, &old_oid, 0))
+		die(_("old object '%s' does not exist"), argv[1]);
+
+	ret = get_oid_hex_algop(argv[2], &new_oid, repo->hash_algo);
+	if (ret)
+		die(_("invalid new object ID: '%s'"), argv[2]);
+	if (!is_null_oid(&new_oid) && !odb_has_object(repo->objects, &new_oid, 0))
+		die(_("new object '%s' does not exist"), argv[2]);
+
+	message = argv[3];
+
+	tx = ref_store_transaction_begin(get_main_ref_store(repo), 0, &err);
+	if (!tx)
+		die(_("cannot start transaction: %s"), err.buf);
+
+	ret = ref_transaction_update_reflog(tx, ref, &new_oid, &old_oid,
+					    git_committer_info(0),
+					    message, 0, &err);
+	if (ret)
+		die(_("cannot queue reflog update: %s"), err.buf);
+
+	ret = ref_transaction_commit(tx, &err);
+	if (ret)
+		die(_("cannot commit reflog update: %s"), err.buf);
+
+	ref_transaction_free(tx);
+	strbuf_release(&err);
+	return 0;
+}
+
 /*
  * main "reflog"
  */
@@ -407,10 +473,11 @@ int cmd_reflog(int argc,
 	struct option options[] = {
 		OPT_SUBCOMMAND("show", &fn, cmd_reflog_show),
 		OPT_SUBCOMMAND("list", &fn, cmd_reflog_list),
-		OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire),
-		OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete),
 		OPT_SUBCOMMAND("exists", &fn, cmd_reflog_exists),
+		OPT_SUBCOMMAND("write", &fn, cmd_reflog_write),
+		OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete),
 		OPT_SUBCOMMAND("drop", &fn, cmd_reflog_drop),
+		OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire),
 		OPT_END()
 	};
 
diff --git a/builtin/refs.c b/builtin/refs.c
index c7ad0a2..3064f88 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -2,10 +2,13 @@
 #include "builtin.h"
 #include "config.h"
 #include "fsck.h"
+#include "pack-refs.h"
 #include "parse-options.h"
 #include "refs.h"
 #include "strbuf.h"
 #include "worktree.h"
+#include "for-each-ref.h"
+#include "refs/refs-internal.h"
 
 #define REFS_MIGRATE_USAGE \
 	N_("git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]")
@@ -13,6 +16,12 @@
 #define REFS_VERIFY_USAGE \
 	N_("git refs verify [--strict] [--verbose]")
 
+#define REFS_EXISTS_USAGE \
+	N_("git refs exists <ref>")
+
+#define REFS_OPTIMIZE_USAGE \
+	N_("git refs optimize " PACK_REFS_OPTS)
+
 static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,
 			    struct repository *repo UNUSED)
 {
@@ -101,6 +110,70 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
 	return ret;
 }
 
+static int cmd_refs_list(int argc, const char **argv, const char *prefix,
+			   struct repository *repo)
+{
+	static char const * const refs_list_usage[] = {
+		N_("git refs list " COMMON_USAGE_FOR_EACH_REF),
+		NULL
+	};
+
+	return for_each_ref_core(argc, argv, prefix, repo, refs_list_usage);
+}
+
+static int cmd_refs_exists(int argc, const char **argv, const char *prefix,
+			   struct repository *repo UNUSED)
+{
+	struct strbuf unused_referent = STRBUF_INIT;
+	struct object_id unused_oid;
+	unsigned int unused_type;
+	int failure_errno = 0;
+	const char *ref;
+	int ret = 0;
+	const char * const exists_usage[] = {
+		REFS_EXISTS_USAGE,
+		NULL,
+	};
+	struct option options[] = {
+		OPT_END(),
+	};
+
+	argc = parse_options(argc, argv, prefix, options, exists_usage, 0);
+	if (argc != 1)
+		die(_("'git refs exists' requires a reference"));
+
+	ref = *argv++;
+	if (refs_read_raw_ref(get_main_ref_store(the_repository), ref,
+			      &unused_oid, &unused_referent, &unused_type,
+			      &failure_errno)) {
+		if (failure_errno == ENOENT || failure_errno == EISDIR) {
+			error(_("reference does not exist"));
+			ret = 2;
+		} else {
+			errno = failure_errno;
+			error_errno(_("failed to look up reference"));
+			ret = 1;
+		}
+
+		goto out;
+	}
+
+out:
+	strbuf_release(&unused_referent);
+	return ret;
+}
+
+static int cmd_refs_optimize(int argc, const char **argv, const char *prefix,
+			     struct repository *repo)
+{
+	static char const * const refs_optimize_usage[] = {
+		REFS_OPTIMIZE_USAGE,
+		NULL
+	};
+
+	return pack_refs_core(argc, argv, prefix, repo, refs_optimize_usage);
+}
+
 int cmd_refs(int argc,
 	     const char **argv,
 	     const char *prefix,
@@ -109,12 +182,18 @@ int cmd_refs(int argc,
 	const char * const refs_usage[] = {
 		REFS_MIGRATE_USAGE,
 		REFS_VERIFY_USAGE,
+		"git refs list " COMMON_USAGE_FOR_EACH_REF,
+		REFS_EXISTS_USAGE,
+		REFS_OPTIMIZE_USAGE,
 		NULL,
 	};
 	parse_opt_subcommand_fn *fn = NULL;
 	struct option opts[] = {
 		OPT_SUBCOMMAND("migrate", &fn, cmd_refs_migrate),
 		OPT_SUBCOMMAND("verify", &fn, cmd_refs_verify),
+		OPT_SUBCOMMAND("list", &fn, cmd_refs_list),
+		OPT_SUBCOMMAND("exists", &fn, cmd_refs_exists),
+		OPT_SUBCOMMAND("optimize", &fn, cmd_refs_optimize),
 		OPT_END(),
 	};
 
diff --git a/builtin/remote.c b/builtin/remote.c
index 8961ae6..8a7ed42 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1,9 +1,11 @@
 #define USE_THE_REPOSITORY_VARIABLE
-#define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "builtin.h"
+#include "advice.h"
 #include "config.h"
+#include "date.h"
 #include "gettext.h"
+#include "ident.h"
 #include "parse-options.h"
 #include "path.h"
 #include "transport.h"
@@ -182,7 +184,6 @@ static int add(int argc, const char **argv, const char *prefix,
 	struct remote *remote;
 	struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
 	const char *name, *url;
-	int i;
 	int result = 0;
 
 	struct option options[] = {
@@ -233,7 +234,7 @@ static int add(int argc, const char **argv, const char *prefix,
 		strbuf_addf(&buf, "remote.%s.fetch", name);
 		if (track.nr == 0)
 			string_list_append(&track, "*");
-		for (i = 0; i < track.nr; i++) {
+		for (size_t i = 0; i < track.nr; i++) {
 			add_branch(buf.buf, track.items[i].string,
 				   name, mirror, &buf2);
 		}
@@ -612,53 +613,169 @@ static int add_branch_for_removal(const char *refname,
 struct rename_info {
 	const char *old_name;
 	const char *new_name;
-	struct string_list *remote_branches;
-	uint32_t symrefs_nr;
+	struct ref_transaction *transaction;
+	struct progress *progress;
+	struct strbuf *err;
+	uint32_t progress_nr;
+	uint64_t index;
 };
 
-static int read_remote_branches(const char *refname, const char *referent UNUSED,
-				const struct object_id *oid UNUSED,
-				int flags UNUSED, void *cb_data)
+static void compute_renamed_ref(struct rename_info *rename,
+				const char *refname,
+				struct strbuf *out)
+{
+	strbuf_reset(out);
+	strbuf_addstr(out, refname);
+	strbuf_splice(out, strlen("refs/remotes/"), strlen(rename->old_name),
+		      rename->new_name, strlen(rename->new_name));
+}
+
+static int rename_one_reflog_entry(const char *old_refname,
+				   struct object_id *old_oid,
+				   struct object_id *new_oid,
+				   const char *committer,
+				   timestamp_t timestamp, int tz,
+				   const char *msg, void *cb_data)
 {
 	struct rename_info *rename = cb_data;
-	struct strbuf buf = STRBUF_INIT;
-	struct string_list_item *item;
-	int flag;
-	const char *symref;
+	struct strbuf new_refname = STRBUF_INIT;
+	struct strbuf identity = STRBUF_INIT;
+	struct strbuf name = STRBUF_INIT;
+	struct strbuf mail = STRBUF_INIT;
+	struct ident_split ident;
+	const char *date;
+	int error;
 
-	strbuf_addf(&buf, "refs/remotes/%s/", rename->old_name);
-	if (starts_with(refname, buf.buf)) {
-		item = string_list_append(rename->remote_branches, refname);
-		symref = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
-						 refname, RESOLVE_REF_READING,
-						 NULL, &flag);
-		if (symref && (flag & REF_ISSYMREF)) {
-			item->util = xstrdup(symref);
-			rename->symrefs_nr++;
-		} else {
-			item->util = NULL;
-		}
+	compute_renamed_ref(rename, old_refname, &new_refname);
+
+	if (split_ident_line(&ident, committer, strlen(committer)) < 0) {
+		error = -1;
+		goto out;
 	}
-	strbuf_release(&buf);
 
-	return 0;
+	strbuf_add(&name, ident.name_begin, ident.name_end - ident.name_begin);
+	strbuf_add(&mail, ident.mail_begin, ident.mail_end - ident.mail_begin);
+
+	date = show_date(timestamp, tz, DATE_MODE(NORMAL));
+	strbuf_addstr(&identity, fmt_ident(name.buf, mail.buf,
+					  WANT_BLANK_IDENT, date, 0));
+
+	error = ref_transaction_update_reflog(rename->transaction, new_refname.buf,
+					      new_oid, old_oid, identity.buf, msg,
+					      rename->index++, rename->err);
+
+out:
+	strbuf_release(&new_refname);
+	strbuf_release(&identity);
+	strbuf_release(&name);
+	strbuf_release(&mail);
+	return error;
+}
+
+static int rename_one_reflog(const char *old_refname,
+			     const struct object_id *old_oid,
+			     struct rename_info *rename)
+{
+	struct strbuf new_refname = STRBUF_INIT;
+	struct strbuf message = STRBUF_INIT;
+	int error;
+
+	if (!refs_reflog_exists(get_main_ref_store(the_repository), old_refname))
+		return 0;
+
+	error = refs_for_each_reflog_ent(get_main_ref_store(the_repository),
+					 old_refname, rename_one_reflog_entry, rename);
+	if (error < 0)
+		goto out;
+
+	compute_renamed_ref(rename, old_refname, &new_refname);
+
+	/*
+	 * Manually write the reflog entry for the now-renamed ref. We cannot
+	 * rely on `rename_one_ref()` to do this for us as that would screw
+	 * over order in which reflog entries are being written.
+	 *
+	 * Furthermore, we only append the entry in case the reference
+	 * resolves. Missing references shouldn't have reflogs anyway.
+	 */
+	strbuf_addf(&message, "remote: renamed %s to %s", old_refname,
+		    new_refname.buf);
+
+	error = ref_transaction_update_reflog(rename->transaction, new_refname.buf,
+					      old_oid, old_oid, git_committer_info(0),
+					      message.buf, rename->index++, rename->err);
+	if (error < 0)
+		return error;
+
+out:
+	strbuf_release(&new_refname);
+	strbuf_release(&message);
+	return error;
+}
+
+static int rename_one_ref(const char *old_refname, const char *referent,
+			  const struct object_id *oid,
+			  int flags, void *cb_data)
+{
+	struct strbuf new_referent = STRBUF_INIT;
+	struct strbuf new_refname = STRBUF_INIT;
+	struct rename_info *rename = cb_data;
+	int error;
+
+	compute_renamed_ref(rename, old_refname, &new_refname);
+
+	if (flags & REF_ISSYMREF) {
+		/*
+		 * Stupidly enough `referent` is not pointing to the immediate
+		 * target of a symref, but it's the recursively resolved value.
+		 * So symrefs pointing to symrefs would be misresolved, and
+		 * unborn symrefs don't have any value for the `referent` at all.
+		 */
+		referent = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+						   old_refname, RESOLVE_REF_NO_RECURSE,
+						   NULL, NULL);
+		compute_renamed_ref(rename, referent, &new_referent);
+		oid = NULL;
+	}
+
+	error = ref_transaction_delete(rename->transaction, old_refname,
+				       oid, referent, REF_NO_DEREF, NULL, rename->err);
+	if (error < 0)
+		goto out;
+
+	error = ref_transaction_update(rename->transaction, new_refname.buf, oid, null_oid(the_hash_algo),
+				       (flags & REF_ISSYMREF) ? new_referent.buf : NULL, NULL,
+				       REF_SKIP_CREATE_REFLOG | REF_NO_DEREF | REF_SKIP_OID_VERIFICATION,
+				       NULL, rename->err);
+	if (error < 0)
+		goto out;
+
+	error = rename_one_reflog(old_refname, oid, rename);
+	if (error < 0)
+		goto out;
+
+	display_progress(rename->progress, ++rename->progress_nr);
+
+out:
+	strbuf_release(&new_referent);
+	strbuf_release(&new_refname);
+	return error;
 }
 
 static int migrate_file(struct remote *remote)
 {
 	struct strbuf buf = STRBUF_INIT;
-	int i;
 
 	strbuf_addf(&buf, "remote.%s.url", remote->name);
-	for (i = 0; i < remote->url.nr; i++)
+	for (size_t i = 0; i < remote->url.nr; i++)
 		repo_config_set_multivar(the_repository, buf.buf, remote->url.v[i], "^$", 0);
 	strbuf_reset(&buf);
 	strbuf_addf(&buf, "remote.%s.push", remote->name);
-	for (i = 0; i < remote->push.nr; i++)
+	for (int i = 0; i < remote->push.nr; i++)
 		repo_config_set_multivar(the_repository, buf.buf, remote->push.items[i].raw, "^$", 0);
 	strbuf_reset(&buf);
 	strbuf_addf(&buf, "remote.%s.fetch", remote->name);
-	for (i = 0; i < remote->fetch.nr; i++)
+	for (int i = 0; i < remote->fetch.nr; i++)
 		repo_config_set_multivar(the_repository, buf.buf, remote->fetch.items[i].raw, "^$", 0);
 #ifndef WITH_BREAKING_CHANGES
 	if (remote->origin == REMOTE_REMOTES)
@@ -730,6 +847,14 @@ static void handle_push_default(const char* old_name, const char* new_name)
 	strbuf_release(&push_default.origin);
 }
 
+static const char conflicting_remote_refs_advice[] = N_(
+	"The remote you are trying to rename has conflicting references in the\n"
+	"new target refspec. This is most likely caused by you trying to nest\n"
+	"a remote into itself, e.g. by renaming 'parent' into 'parent/child'\n"
+	"or by unnesting a remote, e.g. the other way round.\n"
+	"\n"
+	"If that is the case, you can address this by first renaming the\n"
+	"remote to a different name.\n");
 
 static int mv(int argc, const char **argv, const char *prefix,
 	      struct repository *repo UNUSED)
@@ -741,11 +866,11 @@ static int mv(int argc, const char **argv, const char *prefix,
 	};
 	struct remote *oldremote, *newremote;
 	struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT,
-		old_remote_context = STRBUF_INIT;
-	struct string_list remote_branches = STRING_LIST_INIT_DUP;
-	struct rename_info rename;
-	int i, refs_renamed_nr = 0, refspec_updated = 0;
-	struct progress *progress = NULL;
+		old_remote_context = STRBUF_INIT, err = STRBUF_INIT;
+	struct rename_info rename = {
+		.err = &err,
+	};
+	int refspecs_need_update = 0;
 	int result = 0;
 
 	argc = parse_options(argc, argv, prefix, options,
@@ -756,8 +881,6 @@ static int mv(int argc, const char **argv, const char *prefix,
 
 	rename.old_name = argv[0];
 	rename.new_name = argv[1];
-	rename.remote_branches = &remote_branches;
-	rename.symrefs_nr = 0;
 
 	oldremote = remote_get(rename.old_name);
 	if (!remote_is_configured(oldremote, 1)) {
@@ -785,19 +908,50 @@ static int mv(int argc, const char **argv, const char *prefix,
 		goto out;
 	}
 
+	strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old_name);
+
+	for (int i = 0; i < oldremote->fetch.nr && !refspecs_need_update; i++)
+		refspecs_need_update = !!strstr(oldremote->fetch.items[i].raw,
+						old_remote_context.buf);
+
+	if (refspecs_need_update) {
+		rename.transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
+							       0, &err);
+		if (!rename.transaction)
+			goto out;
+
+		if (show_progress)
+			rename.progress = start_delayed_progress(the_repository,
+								 _("Renaming remote references"), 0);
+
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "refs/remotes/%s/", rename.old_name);
+
+		result = refs_for_each_rawref_in(get_main_ref_store(the_repository), buf.buf,
+				rename_one_ref, &rename);
+		if (result < 0)
+			die(_("queueing remote ref renames failed: %s"), rename.err->buf);
+
+		result = ref_transaction_prepare(rename.transaction, &err);
+		if (result < 0) {
+			error("renaming remote references failed: %s", err.buf);
+			if (result == REF_TRANSACTION_ERROR_NAME_CONFLICT)
+				advise(conflicting_remote_refs_advice);
+			die(NULL);
+		}
+	}
+
 	if (oldremote->fetch.nr) {
 		strbuf_reset(&buf);
 		strbuf_addf(&buf, "remote.%s.fetch", rename.new_name);
 		repo_config_set_multivar(the_repository, buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);
-		strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old_name);
-		for (i = 0; i < oldremote->fetch.nr; i++) {
+		for (int i = 0; i < oldremote->fetch.nr; i++) {
 			char *ptr;
 
 			strbuf_reset(&buf2);
 			strbuf_addstr(&buf2, oldremote->fetch.items[i].raw);
 			ptr = strstr(buf2.buf, old_remote_context.buf);
 			if (ptr) {
-				refspec_updated = 1;
 				strbuf_splice(&buf2,
 					      ptr-buf2.buf + strlen(":refs/remotes/"),
 					      strlen(rename.old_name), rename.new_name,
@@ -813,7 +967,7 @@ static int mv(int argc, const char **argv, const char *prefix,
 	}
 
 	read_branches();
-	for (i = 0; i < branch_list.nr; i++) {
+	for (size_t i = 0; i < branch_list.nr; i++) {
 		struct string_list_item *item = branch_list.items + i;
 		struct branch_info *info = item->util;
 		if (info->remote_name && !strcmp(info->remote_name, rename.old_name)) {
@@ -828,83 +982,23 @@ static int mv(int argc, const char **argv, const char *prefix,
 		}
 	}
 
-	if (!refspec_updated)
-		goto out;
+	if (refspecs_need_update) {
+		result = ref_transaction_commit(rename.transaction, &err);
+		if (result < 0)
+			die(_("renaming remote refs failed: %s"), rename.err->buf);
 
-	/*
-	 * First remove symrefs, then rename the rest, finally create
-	 * the new symrefs.
-	 */
-	refs_for_each_ref(get_main_ref_store(the_repository),
-			  read_remote_branches, &rename);
-	if (show_progress) {
-		/*
-		 * Count symrefs twice, since "renaming" them is done by
-		 * deleting and recreating them in two separate passes.
-		 */
-		progress = start_progress(the_repository,
-					  _("Renaming remote references"),
-					  rename.remote_branches->nr + rename.symrefs_nr);
+		stop_progress(&rename.progress);
+
+		handle_push_default(rename.old_name, rename.new_name);
 	}
-	for (i = 0; i < remote_branches.nr; i++) {
-		struct string_list_item *item = remote_branches.items + i;
-		struct strbuf referent = STRBUF_INIT;
-
-		if (refs_read_symbolic_ref(get_main_ref_store(the_repository), item->string,
-					   &referent))
-			continue;
-		if (refs_delete_ref(get_main_ref_store(the_repository), NULL, item->string, NULL, REF_NO_DEREF))
-			die(_("deleting '%s' failed"), item->string);
-
-		strbuf_release(&referent);
-		display_progress(progress, ++refs_renamed_nr);
-	}
-	for (i = 0; i < remote_branches.nr; i++) {
-		struct string_list_item *item = remote_branches.items + i;
-
-		if (item->util)
-			continue;
-		strbuf_reset(&buf);
-		strbuf_addstr(&buf, item->string);
-		strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old_name),
-				rename.new_name, strlen(rename.new_name));
-		strbuf_reset(&buf2);
-		strbuf_addf(&buf2, "remote: renamed %s to %s",
-				item->string, buf.buf);
-		if (refs_rename_ref(get_main_ref_store(the_repository), item->string, buf.buf, buf2.buf))
-			die(_("renaming '%s' failed"), item->string);
-		display_progress(progress, ++refs_renamed_nr);
-	}
-	for (i = 0; i < remote_branches.nr; i++) {
-		struct string_list_item *item = remote_branches.items + i;
-
-		if (!item->util)
-			continue;
-		strbuf_reset(&buf);
-		strbuf_addstr(&buf, item->string);
-		strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old_name),
-				rename.new_name, strlen(rename.new_name));
-		strbuf_reset(&buf2);
-		strbuf_addstr(&buf2, item->util);
-		strbuf_splice(&buf2, strlen("refs/remotes/"), strlen(rename.old_name),
-				rename.new_name, strlen(rename.new_name));
-		strbuf_reset(&buf3);
-		strbuf_addf(&buf3, "remote: renamed %s to %s",
-				item->string, buf.buf);
-		if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf))
-			die(_("creating '%s' failed"), buf.buf);
-		display_progress(progress, ++refs_renamed_nr);
-	}
-	stop_progress(&progress);
-
-	handle_push_default(rename.old_name, rename.new_name);
 
 out:
-	string_list_clear(&remote_branches, 1);
+	ref_transaction_free(rename.transaction);
 	strbuf_release(&old_remote_context);
 	strbuf_release(&buf);
 	strbuf_release(&buf2);
 	strbuf_release(&buf3);
+	strbuf_release(&err);
 	return result;
 }
 
@@ -920,7 +1014,7 @@ static int rm(int argc, const char **argv, const char *prefix,
 	struct string_list branches = STRING_LIST_INIT_DUP;
 	struct string_list skipped = STRING_LIST_INIT_DUP;
 	struct branches_for_remote cb_data;
-	int i, result;
+	int result;
 
 	memset(&cb_data, 0, sizeof(cb_data));
 	cb_data.branches = &branches;
@@ -942,7 +1036,7 @@ static int rm(int argc, const char **argv, const char *prefix,
 	for_each_remote(add_known_remote, &known_remotes);
 
 	read_branches();
-	for (i = 0; i < branch_list.nr; i++) {
+	for (size_t i = 0; i < branch_list.nr; i++) {
 		struct string_list_item *item = branch_list.items + i;
 		struct branch_info *info = item->util;
 		if (info->remote_name && !strcmp(info->remote_name, remote->name)) {
@@ -988,7 +1082,7 @@ static int rm(int argc, const char **argv, const char *prefix,
 			      "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
 			      "to delete them, use:",
 			      skipped.nr));
-		for (i = 0; i < skipped.nr; i++)
+		for (size_t i = 0; i < skipped.nr; i++)
 			fprintf(stderr, "  git branch -d %s\n",
 				skipped.items[i].string);
 	}
@@ -1166,7 +1260,6 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)
 	struct branch_info *branch_info = item->util;
 	struct string_list *merge = &branch_info->merge;
 	int width = show_info->width + 4;
-	int i;
 
 	if (branch_info->rebase >= REBASE_TRUE && branch_info->merge.nr > 1) {
 		error(_("invalid branch.%s.merge; cannot rebase onto > 1 branch"),
@@ -1192,7 +1285,7 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)
 	} else {
 		printf_ln(_("merges with remote %s"), merge->items[0].string);
 	}
-	for (i = 1; i < merge->nr; i++)
+	for (size_t i = 1; i < merge->nr; i++)
 		printf(_("%-*s    and with remote %s\n"), width, "",
 		       merge->items[i].string);
 
@@ -1277,7 +1370,6 @@ static int get_one_entry(struct remote *remote, void *priv)
 	struct string_list *list = priv;
 	struct strbuf remote_info_buf = STRBUF_INIT;
 	struct strvec *url;
-	int i;
 
 	if (remote->url.nr > 0) {
 		struct strbuf promisor_config = STRBUF_INIT;
@@ -1294,8 +1386,7 @@ static int get_one_entry(struct remote *remote, void *priv)
 	} else
 		string_list_append(list, remote->name)->util = NULL;
 	url = push_url_of_remote(remote);
-	for (i = 0; i < url->nr; i++)
-	{
+	for (size_t i = 0; i < url->nr; i++) {
 		strbuf_addf(&remote_info_buf, "%s (push)", url->v[i]);
 		string_list_append(list, remote->name)->util =
 				strbuf_detach(&remote_info_buf, NULL);
@@ -1312,10 +1403,8 @@ static int show_all(void)
 	result = for_each_remote(get_one_entry, &list);
 
 	if (!result) {
-		int i;
-
 		string_list_sort(&list);
-		for (i = 0; i < list.nr; i++) {
+		for (size_t i = 0; i < list.nr; i++) {
 			struct string_list_item *item = list.items + i;
 			if (verbose)
 				printf("%s\t%s\n", item->string,
@@ -1352,7 +1441,7 @@ static int show(int argc, const char **argv, const char *prefix,
 		query_flag = (GET_REF_STATES | GET_HEAD_NAMES | GET_PUSH_REF_STATES);
 
 	for (; argc; argc--, argv++) {
-		int i;
+		size_t i;
 		struct strvec *url;
 
 		get_remote_ref_states(*argv, &info.states, query_flag);
@@ -1458,7 +1547,7 @@ static void report_set_head_auto(const char *remote, const char *head_name,
 static int set_head(int argc, const char **argv, const char *prefix,
 		    struct repository *repo UNUSED)
 {
-	int i, opt_a = 0, opt_d = 0, result = 0, was_detached;
+	int opt_a = 0, opt_d = 0, result = 0, was_detached;
 	struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
 		b_local_head = STRBUF_INIT;
 	char *head_name = NULL;
@@ -1492,7 +1581,7 @@ static int set_head(int argc, const char **argv, const char *prefix,
 		else if (states.heads.nr > 1) {
 			result |= error(_("Multiple remote HEAD branches. "
 					  "Please choose one explicitly with:"));
-			for (i = 0; i < states.heads.nr; i++)
+			for (size_t i = 0; i < states.heads.nr; i++)
 				fprintf(stderr, "  git remote set-head %s %s\n",
 					argv[0], states.heads.items[i].string);
 		} else
@@ -1717,7 +1806,7 @@ static int set_branches(int argc, const char **argv, const char *prefix,
 static int get_url(int argc, const char **argv, const char *prefix,
 		   struct repository *repo UNUSED)
 {
-	int i, push_mode = 0, all_mode = 0;
+	int push_mode = 0, all_mode = 0;
 	const char *remotename = NULL;
 	struct remote *remote;
 	struct strvec *url;
@@ -1745,7 +1834,7 @@ static int get_url(int argc, const char **argv, const char *prefix,
 	url = push_mode ? push_url_of_remote(remote) : &remote->url;
 
 	if (all_mode) {
-		for (i = 0; i < url->nr; i++)
+		for (size_t i = 0; i < url->nr; i++)
 			printf_ln("%s", url->v[i]);
 	} else {
 		printf_ln("%s", url->v[0]);
@@ -1757,7 +1846,7 @@ static int get_url(int argc, const char **argv, const char *prefix,
 static int set_url(int argc, const char **argv, const char *prefix,
 		   struct repository *repo UNUSED)
 {
-	int i, push_mode = 0, add_mode = 0, delete_mode = 0;
+	int push_mode = 0, add_mode = 0, delete_mode = 0;
 	int matches = 0, negative_matches = 0;
 	const char *remotename = NULL;
 	const char *newurl = NULL;
@@ -1821,7 +1910,7 @@ static int set_url(int argc, const char **argv, const char *prefix,
 	if (regcomp(&old_regex, oldurl, REG_EXTENDED))
 		die(_("Invalid old URL pattern: %s"), oldurl);
 
-	for (i = 0; i < urlset->nr; i++)
+	for (size_t i = 0; i < urlset->nr; i++)
 		if (!regexec(&old_regex, urlset->v[i], 0, NULL, 0))
 			matches++;
 		else
diff --git a/builtin/repack.c b/builtin/repack.c
index a4def39..e873080 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -223,9 +223,10 @@ static void mark_packs_for_deletion(struct existing_packs *existing,
 static void remove_redundant_pack(const char *dir_name, const char *base_name)
 {
 	struct strbuf buf = STRBUF_INIT;
-	struct multi_pack_index *m = get_multi_pack_index(the_repository->objects->sources);
+	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 && m->local && midx_contains_pack(m, buf.buf))
+	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);
@@ -264,10 +265,11 @@ static void existing_packs_release(struct existing_packs *existing)
 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 = get_all_packs(the_repository); p; p = p->next) {
+	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
 		int i;
 		const char *base;
 
@@ -496,10 +498,11 @@ 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 = get_all_packs(the_repository); p; p = p->next) {
+	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
@@ -1136,11 +1139,12 @@ static int write_filtered_pack(const struct pack_objects_args *args,
 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 = get_all_packs(the_repository); p; p = p->next) {
+	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
 		if (!(p->is_cruft && p->pack_local))
 			continue;
 
@@ -1684,7 +1688,7 @@ int cmd_repack(int argc,
 			goto cleanup;
 	}
 
-	reprepare_packed_git(the_repository);
+	odb_reprepare(the_repository->objects);
 
 	if (delete_redundant) {
 		int opts = 0;
@@ -1711,7 +1715,7 @@ int cmd_repack(int argc,
 		unsigned flags = 0;
 		if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL, 0))
 			flags |= MIDX_WRITE_INCREMENTAL;
-		write_midx_file(the_repository, repo_get_object_directory(the_repository),
+		write_midx_file(the_repository->objects->sources,
 				NULL, NULL, flags);
 	}
 
diff --git a/builtin/repo.c b/builtin/repo.c
new file mode 100644
index 0000000..bbb0966
--- /dev/null
+++ b/builtin/repo.c
@@ -0,0 +1,171 @@
+#define USE_THE_REPOSITORY_VARIABLE
+
+#include "builtin.h"
+#include "environment.h"
+#include "parse-options.h"
+#include "quote.h"
+#include "refs.h"
+#include "strbuf.h"
+#include "shallow.h"
+
+static const char *const repo_usage[] = {
+	"git repo info [--format=(keyvalue|nul)] [-z] [<key>...]",
+	NULL
+};
+
+typedef int get_value_fn(struct repository *repo, struct strbuf *buf);
+
+enum output_format {
+	FORMAT_KEYVALUE,
+	FORMAT_NUL_TERMINATED,
+};
+
+struct field {
+	const char *key;
+	get_value_fn *get_value;
+};
+
+static int get_layout_bare(struct repository *repo UNUSED, struct strbuf *buf)
+{
+	strbuf_addstr(buf, is_bare_repository() ? "true" : "false");
+	return 0;
+}
+
+static int get_layout_shallow(struct repository *repo, struct strbuf *buf)
+{
+	strbuf_addstr(buf,
+		      is_repository_shallow(repo) ? "true" : "false");
+	return 0;
+}
+
+static int get_object_format(struct repository *repo, struct strbuf *buf)
+{
+	strbuf_addstr(buf, repo->hash_algo->name);
+	return 0;
+}
+
+static int get_references_format(struct repository *repo, struct strbuf *buf)
+{
+	strbuf_addstr(buf,
+		      ref_storage_format_to_name(repo->ref_storage_format));
+	return 0;
+}
+
+/* repo_info_fields keys must be in lexicographical order */
+static const struct field repo_info_fields[] = {
+	{ "layout.bare", get_layout_bare },
+	{ "layout.shallow", get_layout_shallow },
+	{ "object.format", get_object_format },
+	{ "references.format", get_references_format },
+};
+
+static int repo_info_fields_cmp(const void *va, const void *vb)
+{
+	const struct field *a = va;
+	const struct field *b = vb;
+
+	return strcmp(a->key, b->key);
+}
+
+static get_value_fn *get_value_fn_for_key(const char *key)
+{
+	const struct field search_key = { key, NULL };
+	const struct field *found = bsearch(&search_key, repo_info_fields,
+					    ARRAY_SIZE(repo_info_fields),
+					    sizeof(*found),
+					    repo_info_fields_cmp);
+	return found ? found->get_value : NULL;
+}
+
+static int print_fields(int argc, const char **argv,
+			struct repository *repo,
+			enum output_format format)
+{
+	int ret = 0;
+	struct strbuf valbuf = STRBUF_INIT;
+	struct strbuf quotbuf = STRBUF_INIT;
+
+	for (int i = 0; i < argc; i++) {
+		get_value_fn *get_value;
+		const char *key = argv[i];
+
+		get_value = get_value_fn_for_key(key);
+
+		if (!get_value) {
+			ret = error(_("key '%s' not found"), key);
+			continue;
+		}
+
+		strbuf_reset(&valbuf);
+		strbuf_reset(&quotbuf);
+
+		get_value(repo, &valbuf);
+
+		switch (format) {
+		case FORMAT_KEYVALUE:
+			quote_c_style(valbuf.buf, &quotbuf, NULL, 0);
+			printf("%s=%s\n", key, quotbuf.buf);
+			break;
+		case FORMAT_NUL_TERMINATED:
+			printf("%s\n%s%c", key, valbuf.buf, '\0');
+			break;
+		default:
+			BUG("not a valid output format: %d", format);
+		}
+	}
+
+	strbuf_release(&valbuf);
+	strbuf_release(&quotbuf);
+	return ret;
+}
+
+static int parse_format_cb(const struct option *opt,
+			   const char *arg, int unset UNUSED)
+{
+	enum output_format *format = opt->value;
+
+	if (opt->short_name == 'z')
+		*format = FORMAT_NUL_TERMINATED;
+	else if (!strcmp(arg, "nul"))
+		*format = FORMAT_NUL_TERMINATED;
+	else if (!strcmp(arg, "keyvalue"))
+		*format = FORMAT_KEYVALUE;
+	else
+		die(_("invalid format '%s'"), arg);
+
+	return 0;
+}
+
+static int repo_info(int argc, const char **argv, const char *prefix,
+		     struct repository *repo)
+{
+	enum output_format format = FORMAT_KEYVALUE;
+	struct option options[] = {
+		OPT_CALLBACK_F(0, "format", &format, N_("format"),
+			       N_("output format"),
+			       PARSE_OPT_NONEG, parse_format_cb),
+		OPT_CALLBACK_F('z', NULL, &format, NULL,
+			       N_("synonym for --format=nul"),
+			       PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+			       parse_format_cb),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options, repo_usage, 0);
+
+	return print_fields(argc, argv, repo, format);
+}
+
+int cmd_repo(int argc, const char **argv, const char *prefix,
+	     struct repository *repo)
+{
+	parse_opt_subcommand_fn *fn = NULL;
+	struct option options[] = {
+		OPT_SUBCOMMAND("info", &fn, repo_info),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options, repo_usage, 0);
+
+	return fn(argc, argv, prefix, repo);
+}
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 44ff1b8..9da92b9 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -708,7 +708,6 @@ int cmd_rev_parse(int argc,
 	struct object_id oid;
 	unsigned int flags = 0;
 	const char *name = NULL;
-	struct object_context unused;
 	struct strbuf buf = STRBUF_INIT;
 	int seen_end_of_options = 0;
 	enum format_type format = FORMAT_DEFAULT;
@@ -1141,9 +1140,8 @@ int cmd_rev_parse(int argc,
 			name++;
 			type = REVERSED;
 		}
-		if (!get_oid_with_context(the_repository, name,
-					  flags, &oid, &unused)) {
-			object_context_release(&unused);
+		if (!repo_get_oid_with_flags(the_repository, name, &oid,
+					     flags)) {
 			if (output_algo)
 				repo_oid_to_algop(the_repository, &oid,
 						  output_algo, &oid);
@@ -1153,7 +1151,6 @@ int cmd_rev_parse(int argc,
 				show_rev(type, &oid, name);
 			continue;
 		}
-		object_context_release(&unused);
 		if (verify)
 			die_no_single_rev(quiet);
 		if (has_dashdash)
diff --git a/builtin/revert.c b/builtin/revert.c
index c3f92b5..bedc40f 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -4,6 +4,7 @@
 #include "builtin.h"
 #include "parse-options.h"
 #include "diff.h"
+#include "environment.h"
 #include "gettext.h"
 #include "revision.h"
 #include "rerere.h"
@@ -285,6 +286,9 @@ int cmd_revert(int argc,
 	struct replay_opts opts = REPLAY_OPTS_INIT;
 	int res;
 
+#ifndef WITH_BREAKING_CHANGES
+	warn_on_auto_comment_char = true;
+#endif /* !WITH_BREAKING_CHANGES */
 	opts.action = REPLAY_REVERT;
 	sequencer_init_config(&opts);
 	res = run_sequencer(argc, argv, prefix, &opts);
@@ -302,6 +306,9 @@ struct repository *repo UNUSED)
 	struct replay_opts opts = REPLAY_OPTS_INIT;
 	int res;
 
+#ifndef WITH_BREAKING_CHANGES
+	warn_on_auto_comment_char = true;
+#endif /* !WITH_BREAKING_CHANGES */
 	opts.action = REPLAY_PICK;
 	sequencer_init_config(&opts);
 	res = run_sequencer(argc, argv, prefix, &opts);
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index 1ab7db9..441babf 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -29,7 +29,7 @@ static const char*const show_branch_usage[] = {
     NULL
 };
 
-static int showbranch_use_color = -1;
+static enum git_colorbool showbranch_use_color = GIT_COLOR_UNKNOWN;
 
 static struct strvec default_args = STRVEC_INIT;
 
diff --git a/builtin/stash.c b/builtin/stash.c
index 1977e50..948eba0 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -146,6 +146,11 @@ static const char * const git_stash_import_usage[] = {
 static const char ref_stash[] = "refs/stash";
 static struct strbuf stash_index_path = STRBUF_INIT;
 
+static int show_stat = 1;
+static int show_patch;
+static int show_include_untracked;
+static int use_index;
+
 /*
  * w_commit is set to the commit containing the working tree
  * b_commit is set to the base commit
@@ -377,7 +382,7 @@ static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit)
 	 * however it should be done together with apply_cached.
 	 */
 	cp.git_cmd = 1;
-	strvec_pushl(&cp.args, "diff-tree", "--binary", NULL);
+	strvec_pushl(&cp.args, "diff-tree", "--binary", "--no-color", NULL);
 	strvec_pushf(&cp.args, "%s^2^..%s^2", w_commit_hex, w_commit_hex);
 
 	return pipe_command(&cp, NULL, 0, out, 0, NULL, 0);
@@ -717,7 +722,7 @@ static int apply_stash(int argc, const char **argv, const char *prefix,
 {
 	int ret = -1;
 	int quiet = 0;
-	int index = 0;
+	int index = use_index;
 	struct stash_info info = STASH_INFO_INIT;
 	struct option options[] = {
 		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
@@ -738,7 +743,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,
 	return ret;
 }
 
-static int reject_reflog_ent(struct object_id *ooid UNUSED,
+static int reject_reflog_ent(const char *refname UNUSED,
+			     struct object_id *ooid UNUSED,
 			     struct object_id *noid UNUSED,
 			     const char *email UNUSED,
 			     timestamp_t timestamp UNUSED,
@@ -814,7 +820,7 @@ static int pop_stash(int argc, const char **argv, const char *prefix,
 		     struct repository *repo UNUSED)
 {
 	int ret = -1;
-	int index = 0;
+	int index = use_index;
 	int quiet = 0;
 	struct stash_info info = STASH_INFO_INIT;
 	struct option options[] = {
@@ -904,10 +910,6 @@ static int list_stash(int argc, const char **argv, const char *prefix,
 	return run_command(&cp);
 }
 
-static int show_stat = 1;
-static int show_patch;
-static int show_include_untracked;
-
 static int git_stash_config(const char *var, const char *value,
 			    const struct config_context *ctx, void *cb)
 {
@@ -923,6 +925,10 @@ static int git_stash_config(const char *var, const char *value,
 		show_include_untracked = git_config_bool(var, value);
 		return 0;
 	}
+	if (!strcmp(var, "stash.index")) {
+		use_index = git_config_bool(var, value);
+		return 0;
+	}
 	return git_diff_basic_config(var, value, ctx, cb);
 }
 
@@ -1014,8 +1020,8 @@ static int show_stash(int argc, const char **argv, const char *prefix,
 		}
 	}
 
-	argc = setup_revisions(revision_args.nr, revision_args.v, &rev, NULL);
-	if (argc > 1)
+	setup_revisions_from_strvec(&revision_args, &rev, NULL);
+	if (revision_args.nr > 1)
 		goto usage;
 	if (!rev.diffopt.output_format) {
 		rev.diffopt.output_format = DIFF_FORMAT_PATCH;
@@ -1088,7 +1094,6 @@ static int store_stash(int argc, const char **argv, const char *prefix,
 	int quiet = 0;
 	const char *stash_msg = NULL;
 	struct object_id obj;
-	struct object_context dummy = {0};
 	struct option options[] = {
 		OPT__QUIET(&quiet, N_("be quiet")),
 		OPT_STRING('m', "message", &stash_msg, "message",
@@ -1108,9 +1113,8 @@ static int store_stash(int argc, const char **argv, const char *prefix,
 		return -1;
 	}
 
-	if (get_oid_with_context(the_repository,
-				 argv[0], quiet ? GET_OID_QUIETLY : 0, &obj,
-				 &dummy)) {
+	if (repo_get_oid_with_flags(the_repository, argv[0], &obj,
+				    quiet ? GET_OID_QUIETLY : 0)) {
 		if (!quiet)
 			fprintf_ln(stderr, _("Cannot update %s with %s"),
 					     ref_stash, argv[0]);
@@ -1121,7 +1125,6 @@ static int store_stash(int argc, const char **argv, const char *prefix,
 	ret = do_store_stash(&obj, stash_msg, quiet);
 
 out:
-	object_context_release(&dummy);
 	return ret;
 }
 
@@ -1283,6 +1286,7 @@ static int stash_staged(struct stash_info *info, struct strbuf *out_patch,
 
 	cp_diff_tree.git_cmd = 1;
 	strvec_pushl(&cp_diff_tree.args, "diff-tree", "-p", "--binary",
+		     "--no-color",
 		     "-U1", "HEAD", oid_to_hex(&info->w_tree), "--", NULL);
 	if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {
 		ret = -1;
@@ -1345,6 +1349,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,
 
 	cp_diff_tree.git_cmd = 1;
 	strvec_pushl(&cp_diff_tree.args, "diff-tree", "-p", "-U1", "HEAD",
+		     "--no-color",
 		     oid_to_hex(&info->w_tree), "--", NULL);
 	if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {
 		ret = -1;
@@ -1719,6 +1724,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
 
 			cp_diff.git_cmd = 1;
 			strvec_pushl(&cp_diff.args, "diff-index", "-p",
+				     "--no-color",
 				     "--cached", "--binary", "HEAD", "--",
 				     NULL);
 			add_pathspecs(&cp_diff.args, ps);
@@ -2207,7 +2213,8 @@ struct stash_entry_data {
 	size_t count;
 };
 
-static int collect_stash_entries(struct object_id *old_oid UNUSED,
+static int collect_stash_entries(const char *refname UNUSED,
+				 struct object_id *old_oid UNUSED,
 				 struct object_id *new_oid,
 				 const char *committer UNUSED,
 				 timestamp_t timestamp UNUSED,
@@ -2233,7 +2240,6 @@ static int do_export_stash(struct repository *r,
 			   const char **argv)
 {
 	struct object_id base;
-	struct object_context unused;
 	struct commit *prev;
 	struct commit_list *items = NULL, **iter = &items, *cur;
 	int res = 0;
@@ -2267,9 +2273,9 @@ static int do_export_stash(struct repository *r,
 			struct commit *stash;
 
 			if (parse_stash_revision(&revision, argv[i], 1) ||
-			    get_oid_with_context(r, revision.buf,
-						 GET_OID_QUIETLY | GET_OID_GENTLY,
-						 &oid, &unused)) {
+			    repo_get_oid_with_flags(r, revision.buf, &oid,
+						    GET_OID_QUIETLY |
+						    GET_OID_GENTLY)) {
 				res = error(_("unable to find stash entry %s"), argv[i]);
 				goto out;
 			}
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 07a1935..fcd73ab 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -616,9 +616,6 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
 	struct rev_info rev = REV_INFO_INIT;
 	struct strbuf buf = STRBUF_INIT;
 	const char *git_dir;
-	struct setup_revision_opt opt = {
-		.free_removed_argv_elements = 1,
-	};
 
 	if (validate_submodule_path(path) < 0)
 		die(NULL);
@@ -655,7 +652,7 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
 
 	repo_init_revisions(the_repository, &rev, NULL);
 	rev.abbrev = 0;
-	setup_revisions(diff_files_args.nr, diff_files_args.v, &rev, &opt);
+	setup_revisions_from_strvec(&diff_files_args, &rev, NULL);
 	run_diff_files(&rev, 0);
 
 	if (!diff_result_code(&rev)) {
@@ -1094,9 +1091,6 @@ static int compute_summary_module_list(struct object_id *head_oid,
 {
 	struct strvec diff_args = STRVEC_INIT;
 	struct rev_info rev;
-	struct setup_revision_opt opt = {
-		.free_removed_argv_elements = 1,
-	};
 	struct module_cb_list list = MODULE_CB_LIST_INIT;
 	int ret = 0;
 
@@ -1114,7 +1108,7 @@ static int compute_summary_module_list(struct object_id *head_oid,
 	repo_init_revisions(the_repository, &rev, info->prefix);
 	rev.abbrev = 0;
 	precompose_argv_prefix(diff_args.nr, diff_args.v, NULL);
-	setup_revisions(diff_args.nr, diff_args.v, &rev, &opt);
+	setup_revisions_from_strvec(&diff_args, &rev, NULL);
 	rev.diffopt.output_format = DIFF_FORMAT_NO_OUTPUT | DIFF_FORMAT_CALLBACK;
 	rev.diffopt.format_callback = submodule_summary_callback;
 	rev.diffopt.format_callback_data = &list;
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index 7ae7c82..ef79e43 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -2,7 +2,6 @@
 #define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "builtin.h"
-#include "bulk-checkin.h"
 #include "config.h"
 #include "environment.h"
 #include "gettext.h"
@@ -584,6 +583,7 @@ static void unpack_all(void)
 {
 	int i;
 	unsigned char *hdr = fill(sizeof(struct pack_header));
+	struct odb_transaction *transaction;
 
 	if (get_be32(hdr) != PACK_SIGNATURE)
 		die("bad pack file");
@@ -599,12 +599,12 @@ static void unpack_all(void)
 		progress = start_progress(the_repository,
 					  _("Unpacking objects"), nr_objects);
 	CALLOC_ARRAY(obj_list, nr_objects);
-	begin_odb_transaction();
+	transaction = odb_transaction_begin(the_repository->objects);
 	for (i = 0; i < nr_objects; i++) {
 		unpack_one(i);
 		display_progress(progress, i + 1);
 	}
-	end_odb_transaction();
+	odb_transaction_commit(transaction);
 	stop_progress(&progress);
 
 	if (delta_list)
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 2380f3c..8a59077 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -8,7 +8,6 @@
 #define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "builtin.h"
-#include "bulk-checkin.h"
 #include "config.h"
 #include "environment.h"
 #include "gettext.h"
@@ -19,6 +18,7 @@
 #include "cache-tree.h"
 #include "tree-walk.h"
 #include "object-file.h"
+#include "odb.h"
 #include "refs.h"
 #include "resolve-undo.h"
 #include "parse-options.h"
@@ -70,14 +70,6 @@ static void report(const char *fmt, ...)
 	if (!verbose)
 		return;
 
-	/*
-	 * It is possible, though unlikely, that a caller could use the verbose
-	 * output to synchronize with addition of objects to the object
-	 * database. The current implementation of ODB transactions leaves
-	 * objects invisible while a transaction is active, so flush the
-	 * transaction here before reporting a change made by update-index.
-	 */
-	flush_odb_transaction();
 	va_start(vp, fmt);
 	vprintf(fmt, vp);
 	putchar('\n');
@@ -940,6 +932,7 @@ int cmd_update_index(int argc,
 	strbuf_getline_fn getline_fn;
 	int parseopt_state = PARSE_OPT_UNKNOWN;
 	struct repository *r = the_repository;
+	struct odb_transaction *transaction;
 	struct option options[] = {
 		OPT_BIT('q', NULL, &refresh_args.flags,
 			N_("continue refresh even when index needs update"),
@@ -1130,7 +1123,7 @@ int cmd_update_index(int argc,
 	 * Allow the object layer to optimize adding multiple objects in
 	 * a batch.
 	 */
-	begin_odb_transaction();
+	transaction = odb_transaction_begin(the_repository->objects);
 	while (ctx.argc) {
 		if (parseopt_state != PARSE_OPT_DONE)
 			parseopt_state = parse_options_step(&ctx, options,
@@ -1149,6 +1142,21 @@ int cmd_update_index(int argc,
 			const char *path = ctx.argv[0];
 			char *p;
 
+			/*
+			 * It is possible, though unlikely, that a caller could
+			 * use the verbose output to synchronize with addition
+			 * of objects to the object database. The current
+			 * implementation of ODB transactions leaves objects
+			 * invisible while a transaction is active, so end the
+			 * transaction here early before processing the next
+			 * update. All further updates are performed outside of
+			 * a transaction.
+			 */
+			if (transaction && verbose) {
+				odb_transaction_commit(transaction);
+				transaction = NULL;
+			}
+
 			setup_work_tree();
 			p = prefix_path(prefix, prefix_length, path);
 			update_one(p);
@@ -1213,7 +1221,7 @@ int cmd_update_index(int argc,
 	/*
 	 * By now we have added all of the new objects
 	 */
-	end_odb_transaction();
+	odb_transaction_commit(transaction);
 
 	if (split_index > 0) {
 		if (repo_config_get_split_index(the_repository) == 0)
diff --git a/builtin/var.c b/builtin/var.c
index a2d790d..cc3a43c 100644
--- a/builtin/var.c
+++ b/builtin/var.c
@@ -182,7 +182,7 @@ static void list_vars(void)
 			if (ptr->multivalued && *val) {
 				struct string_list list = STRING_LIST_INIT_DUP;
 
-				string_list_split(&list, val, '\n', -1);
+				string_list_split(&list, val, "\n", -1);
 				for (size_t i = 0; i < list.nr; i++)
 					printf("%s=%s\n", ptr->name, list.items[i].string);
 				string_list_clear(&list, 0);
diff --git a/bulk-checkin.c b/bulk-checkin.c
deleted file mode 100644
index b2809ab..0000000
--- a/bulk-checkin.c
+++ /dev/null
@@ -1,391 +0,0 @@
-/*
- * Copyright (c) 2011, Google Inc.
- */
-
-#define USE_THE_REPOSITORY_VARIABLE
-
-#include "git-compat-util.h"
-#include "bulk-checkin.h"
-#include "environment.h"
-#include "gettext.h"
-#include "hex.h"
-#include "lockfile.h"
-#include "repository.h"
-#include "csum-file.h"
-#include "pack.h"
-#include "strbuf.h"
-#include "tmp-objdir.h"
-#include "packfile.h"
-#include "object-file.h"
-#include "odb.h"
-
-static int odb_transaction_nesting;
-
-static struct tmp_objdir *bulk_fsync_objdir;
-
-static struct bulk_checkin_packfile {
-	char *pack_tmp_name;
-	struct hashfile *f;
-	off_t offset;
-	struct pack_idx_option pack_idx_opts;
-
-	struct pack_idx_entry **written;
-	uint32_t alloc_written;
-	uint32_t nr_written;
-} bulk_checkin_packfile;
-
-static void finish_tmp_packfile(struct strbuf *basename,
-				const char *pack_tmp_name,
-				struct pack_idx_entry **written_list,
-				uint32_t nr_written,
-				struct pack_idx_option *pack_idx_opts,
-				unsigned char hash[])
-{
-	char *idx_tmp_name = NULL;
-
-	stage_tmp_packfiles(the_repository, basename, pack_tmp_name,
-			    written_list, nr_written, NULL, pack_idx_opts, hash,
-			    &idx_tmp_name);
-	rename_tmp_packfile_idx(the_repository, basename, &idx_tmp_name);
-
-	free(idx_tmp_name);
-}
-
-static void flush_bulk_checkin_packfile(struct bulk_checkin_packfile *state)
-{
-	unsigned char hash[GIT_MAX_RAWSZ];
-	struct strbuf packname = STRBUF_INIT;
-
-	if (!state->f)
-		return;
-
-	if (state->nr_written == 0) {
-		close(state->f->fd);
-		free_hashfile(state->f);
-		unlink(state->pack_tmp_name);
-		goto clear_exit;
-	} else if (state->nr_written == 1) {
-		finalize_hashfile(state->f, hash, FSYNC_COMPONENT_PACK,
-				  CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE);
-	} else {
-		int fd = finalize_hashfile(state->f, hash, FSYNC_COMPONENT_PACK, 0);
-		fixup_pack_header_footer(the_hash_algo, fd, hash, state->pack_tmp_name,
-					 state->nr_written, hash,
-					 state->offset);
-		close(fd);
-	}
-
-	strbuf_addf(&packname, "%s/pack/pack-%s.", repo_get_object_directory(the_repository),
-		    hash_to_hex(hash));
-	finish_tmp_packfile(&packname, state->pack_tmp_name,
-			    state->written, state->nr_written,
-			    &state->pack_idx_opts, hash);
-	for (uint32_t i = 0; i < state->nr_written; i++)
-		free(state->written[i]);
-
-clear_exit:
-	free(state->pack_tmp_name);
-	free(state->written);
-	memset(state, 0, sizeof(*state));
-
-	strbuf_release(&packname);
-	/* Make objects we just wrote available to ourselves */
-	reprepare_packed_git(the_repository);
-}
-
-/*
- * Cleanup after batch-mode fsync_object_files.
- */
-static void flush_batch_fsync(void)
-{
-	struct strbuf temp_path = STRBUF_INIT;
-	struct tempfile *temp;
-
-	if (!bulk_fsync_objdir)
-		return;
-
-	/*
-	 * Issue a full hardware flush against a temporary file to ensure
-	 * that all objects are durable before any renames occur. The code in
-	 * fsync_loose_object_bulk_checkin has already issued a writeout
-	 * request, but it has not flushed any writeback cache in the storage
-	 * hardware or any filesystem logs. This fsync call acts as a barrier
-	 * to ensure that the data in each new object file is durable before
-	 * the final name is visible.
-	 */
-	strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX", repo_get_object_directory(the_repository));
-	temp = xmks_tempfile(temp_path.buf);
-	fsync_or_die(get_tempfile_fd(temp), get_tempfile_path(temp));
-	delete_tempfile(&temp);
-	strbuf_release(&temp_path);
-
-	/*
-	 * Make the object files visible in the primary ODB after their data is
-	 * fully durable.
-	 */
-	tmp_objdir_migrate(bulk_fsync_objdir);
-	bulk_fsync_objdir = NULL;
-}
-
-static int already_written(struct bulk_checkin_packfile *state, struct object_id *oid)
-{
-	/* The object may already exist in the repository */
-	if (odb_has_object(the_repository->objects, oid,
-			   HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR))
-		return 1;
-
-	/* Might want to keep the list sorted */
-	for (uint32_t i = 0; i < state->nr_written; i++)
-		if (oideq(&state->written[i]->oid, oid))
-			return 1;
-
-	/* This is a new object we need to keep */
-	return 0;
-}
-
-/*
- * Read the contents from fd for size bytes, streaming it to the
- * packfile in state while updating the hash in ctx. Signal a failure
- * by returning a negative value when the resulting pack would exceed
- * the pack size limit and this is not the first object in the pack,
- * so that the caller can discard what we wrote from the current pack
- * by truncating it and opening a new one. The caller will then call
- * us again after rewinding the input fd.
- *
- * The already_hashed_to pointer is kept untouched by the caller to
- * make sure we do not hash the same byte when we are called
- * again. This way, the caller does not have to checkpoint its hash
- * status before calling us just in case we ask it to call us again
- * with a new pack.
- */
-static int stream_blob_to_pack(struct bulk_checkin_packfile *state,
-			       struct git_hash_ctx *ctx, off_t *already_hashed_to,
-			       int fd, size_t size, const char *path,
-			       unsigned flags)
-{
-	git_zstream s;
-	unsigned char ibuf[16384];
-	unsigned char obuf[16384];
-	unsigned hdrlen;
-	int status = Z_OK;
-	int write_object = (flags & INDEX_WRITE_OBJECT);
-	off_t offset = 0;
-
-	git_deflate_init(&s, pack_compression_level);
-
-	hdrlen = encode_in_pack_object_header(obuf, sizeof(obuf), OBJ_BLOB, size);
-	s.next_out = obuf + hdrlen;
-	s.avail_out = sizeof(obuf) - hdrlen;
-
-	while (status != Z_STREAM_END) {
-		if (size && !s.avail_in) {
-			size_t rsize = size < sizeof(ibuf) ? size : sizeof(ibuf);
-			ssize_t read_result = read_in_full(fd, ibuf, rsize);
-			if (read_result < 0)
-				die_errno("failed to read from '%s'", path);
-			if ((size_t)read_result != rsize)
-				die("failed to read %u bytes from '%s'",
-				    (unsigned)rsize, path);
-			offset += rsize;
-			if (*already_hashed_to < offset) {
-				size_t hsize = offset - *already_hashed_to;
-				if (rsize < hsize)
-					hsize = rsize;
-				if (hsize)
-					git_hash_update(ctx, ibuf, hsize);
-				*already_hashed_to = offset;
-			}
-			s.next_in = ibuf;
-			s.avail_in = rsize;
-			size -= rsize;
-		}
-
-		status = git_deflate(&s, size ? 0 : Z_FINISH);
-
-		if (!s.avail_out || status == Z_STREAM_END) {
-			if (write_object) {
-				size_t written = s.next_out - obuf;
-
-				/* would we bust the size limit? */
-				if (state->nr_written &&
-				    pack_size_limit_cfg &&
-				    pack_size_limit_cfg < state->offset + written) {
-					git_deflate_abort(&s);
-					return -1;
-				}
-
-				hashwrite(state->f, obuf, written);
-				state->offset += written;
-			}
-			s.next_out = obuf;
-			s.avail_out = sizeof(obuf);
-		}
-
-		switch (status) {
-		case Z_OK:
-		case Z_BUF_ERROR:
-		case Z_STREAM_END:
-			continue;
-		default:
-			die("unexpected deflate failure: %d", status);
-		}
-	}
-	git_deflate_end(&s);
-	return 0;
-}
-
-/* Lazily create backing packfile for the state */
-static void prepare_to_stream(struct bulk_checkin_packfile *state,
-			      unsigned flags)
-{
-	if (!(flags & INDEX_WRITE_OBJECT) || state->f)
-		return;
-
-	state->f = create_tmp_packfile(the_repository, &state->pack_tmp_name);
-	reset_pack_idx_option(&state->pack_idx_opts);
-
-	/* Pretend we are going to write only one object */
-	state->offset = write_pack_header(state->f, 1);
-	if (!state->offset)
-		die_errno("unable to write pack header");
-}
-
-static int deflate_blob_to_pack(struct bulk_checkin_packfile *state,
-				struct object_id *result_oid,
-				int fd, size_t size,
-				const char *path, unsigned flags)
-{
-	off_t seekback, already_hashed_to;
-	struct git_hash_ctx ctx;
-	unsigned char obuf[16384];
-	unsigned header_len;
-	struct hashfile_checkpoint checkpoint;
-	struct pack_idx_entry *idx = NULL;
-
-	seekback = lseek(fd, 0, SEEK_CUR);
-	if (seekback == (off_t) -1)
-		return error("cannot find the current offset");
-
-	header_len = format_object_header((char *)obuf, sizeof(obuf),
-					  OBJ_BLOB, size);
-	the_hash_algo->init_fn(&ctx);
-	git_hash_update(&ctx, obuf, header_len);
-
-	/* Note: idx is non-NULL when we are writing */
-	if ((flags & INDEX_WRITE_OBJECT) != 0) {
-		CALLOC_ARRAY(idx, 1);
-
-		prepare_to_stream(state, flags);
-		hashfile_checkpoint_init(state->f, &checkpoint);
-	}
-
-	already_hashed_to = 0;
-
-	while (1) {
-		prepare_to_stream(state, flags);
-		if (idx) {
-			hashfile_checkpoint(state->f, &checkpoint);
-			idx->offset = state->offset;
-			crc32_begin(state->f);
-		}
-		if (!stream_blob_to_pack(state, &ctx, &already_hashed_to,
-					 fd, size, path, flags))
-			break;
-		/*
-		 * Writing this object to the current pack will make
-		 * it too big; we need to truncate it, start a new
-		 * pack, and write into it.
-		 */
-		if (!idx)
-			BUG("should not happen");
-		hashfile_truncate(state->f, &checkpoint);
-		state->offset = checkpoint.offset;
-		flush_bulk_checkin_packfile(state);
-		if (lseek(fd, seekback, SEEK_SET) == (off_t) -1)
-			return error("cannot seek back");
-	}
-	git_hash_final_oid(result_oid, &ctx);
-	if (!idx)
-		return 0;
-
-	idx->crc32 = crc32_end(state->f);
-	if (already_written(state, result_oid)) {
-		hashfile_truncate(state->f, &checkpoint);
-		state->offset = checkpoint.offset;
-		free(idx);
-	} else {
-		oidcpy(&idx->oid, result_oid);
-		ALLOC_GROW(state->written,
-			   state->nr_written + 1,
-			   state->alloc_written);
-		state->written[state->nr_written++] = idx;
-	}
-	return 0;
-}
-
-void prepare_loose_object_bulk_checkin(void)
-{
-	/*
-	 * We lazily create the temporary object directory
-	 * the first time an object might be added, since
-	 * callers may not know whether any objects will be
-	 * added at the time they call begin_odb_transaction.
-	 */
-	if (!odb_transaction_nesting || bulk_fsync_objdir)
-		return;
-
-	bulk_fsync_objdir = tmp_objdir_create(the_repository, "bulk-fsync");
-	if (bulk_fsync_objdir)
-		tmp_objdir_replace_primary_odb(bulk_fsync_objdir, 0);
-}
-
-void fsync_loose_object_bulk_checkin(int fd, const char *filename)
-{
-	/*
-	 * If we have an active ODB transaction, we issue a call that
-	 * cleans the filesystem page cache but avoids a hardware flush
-	 * command. Later on we will issue a single hardware flush
-	 * before renaming the objects to their final names as part of
-	 * flush_batch_fsync.
-	 */
-	if (!bulk_fsync_objdir ||
-	    git_fsync(fd, FSYNC_WRITEOUT_ONLY) < 0) {
-		if (errno == ENOSYS)
-			warning(_("core.fsyncMethod = batch is unsupported on this platform"));
-		fsync_or_die(fd, filename);
-	}
-}
-
-int index_blob_bulk_checkin(struct object_id *oid,
-			    int fd, size_t size,
-			    const char *path, unsigned flags)
-{
-	int status = deflate_blob_to_pack(&bulk_checkin_packfile, oid, fd, size,
-					  path, flags);
-	if (!odb_transaction_nesting)
-		flush_bulk_checkin_packfile(&bulk_checkin_packfile);
-	return status;
-}
-
-void begin_odb_transaction(void)
-{
-	odb_transaction_nesting += 1;
-}
-
-void flush_odb_transaction(void)
-{
-	flush_batch_fsync();
-	flush_bulk_checkin_packfile(&bulk_checkin_packfile);
-}
-
-void end_odb_transaction(void)
-{
-	odb_transaction_nesting -= 1;
-	if (odb_transaction_nesting < 0)
-		BUG("Unbalanced ODB transaction nesting");
-
-	if (odb_transaction_nesting)
-		return;
-
-	flush_odb_transaction();
-}
diff --git a/bulk-checkin.h b/bulk-checkin.h
deleted file mode 100644
index 7246ea5..0000000
--- a/bulk-checkin.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (c) 2011, Google Inc.
- */
-#ifndef BULK_CHECKIN_H
-#define BULK_CHECKIN_H
-
-#include "object.h"
-
-void prepare_loose_object_bulk_checkin(void);
-void fsync_loose_object_bulk_checkin(int fd, const char *filename);
-
-/*
- * This creates one packfile per large blob unless bulk-checkin
- * machinery is "plugged".
- *
- * This also bypasses the usual "convert-to-git" dance, and that is on
- * purpose. We could write a streaming version of the converting
- * functions and insert that before feeding the data to fast-import
- * (or equivalent in-core API described above). However, that is
- * somewhat complicated, as we do not know the size of the filter
- * result, which we need to know beforehand when writing a git object.
- * Since the primary motivation for trying to stream from the working
- * tree file and to avoid mmaping it in core is to deal with large
- * binary blobs, they generally do not want to get any conversion, and
- * callers should avoid this code path when filters are requested.
- */
-int index_blob_bulk_checkin(struct object_id *oid,
-			    int fd, size_t size,
-			    const char *path, unsigned flags);
-
-/*
- * Tell the object database to optimize for adding
- * multiple objects. end_odb_transaction must be called
- * to make new objects visible. Transactions can be nested,
- * and objects are only visible after the outermost transaction
- * is complete or the transaction is flushed.
- */
-void begin_odb_transaction(void);
-
-/*
- * Make any objects that are currently part of a pending object
- * database transaction visible. It is valid to call this function
- * even if no transaction is active.
- */
-void flush_odb_transaction(void);
-
-/*
- * Tell the object database to make any objects from the
- * current transaction visible if this is the final nested
- * transaction.
- */
-void end_odb_transaction(void);
-
-#endif
diff --git a/cache-tree.c b/cache-tree.c
index 66ef2be..2aba470 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -8,7 +8,6 @@
 #include "tree.h"
 #include "tree-walk.h"
 #include "cache-tree.h"
-#include "bulk-checkin.h"
 #include "object-file.h"
 #include "odb.h"
 #include "read-cache-ll.h"
@@ -474,6 +473,7 @@ static int update_one(struct cache_tree *it,
 
 int cache_tree_update(struct index_state *istate, int flags)
 {
+	struct odb_transaction *transaction;
 	int skip, i;
 
 	i = verify_cache(istate, flags);
@@ -489,10 +489,10 @@ int cache_tree_update(struct index_state *istate, int flags)
 
 	trace_performance_enter();
 	trace2_region_enter("cache_tree", "update", the_repository);
-	begin_odb_transaction();
+	transaction = odb_transaction_begin(the_repository->objects);
 	i = update_one(istate->cache_tree, istate->cache, istate->cache_nr,
 		       "", 0, &skip, flags);
-	end_odb_transaction();
+	odb_transaction_commit(transaction);
 	trace2_region_leave("cache_tree", "update", the_repository);
 	trace_performance_leave("cache_tree_update");
 	if (i < 0)
diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh
index d061a47..a8dcd9b 100755
--- a/ci/install-dependencies.sh
+++ b/ci/install-dependencies.sh
@@ -30,8 +30,12 @@
 		bash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null
 	;;
 fedora-*|almalinux-*)
+	case "$jobname" in
+	*-meson)
+		MESON_DEPS="meson ninja";;
+	esac
 	dnf -yq update >/dev/null &&
-	dnf -yq install shadow-utils sudo make gcc findutils diffutils perl python3 gawk gettext zlib-devel expat-devel openssl-devel curl-devel pcre2-devel >/dev/null
+	dnf -yq install shadow-utils sudo make pkg-config gcc findutils diffutils perl python3 gawk gettext zlib-devel expat-devel openssl-devel curl-devel pcre2-devel $MESON_DEPS cargo >/dev/null
 	;;
 ubuntu-*|i386/ubuntu-*|debian-*)
 	# Required so that apt doesn't wait for user input on certain packages.
@@ -58,9 +62,18 @@
 		make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \
 		tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \
 		libemail-valid-perl libio-pty-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl \
-		libsecret-1-dev libpcre2-dev meson ninja-build pkg-config \
+		libsecret-1-dev libpcre2-dev meson ninja-build pkg-config cargo \
 		${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE
 
+	# Starting with Ubuntu 25.10, sudo can now be provided via either
+	# sudo(1) or sudo-rs(1), with the latter being the default. The problem
+	# is that it does not support `--preserve-env` though, which we rely on
+	# in our CI. We thus revert back to the C implementation.
+	if test -f /etc/alternatives/sudo
+	then
+		sudo update-alternatives --set sudo /usr/bin/sudo.ws
+	fi
+
 	case "$distro" in
 	ubuntu-*)
 		mkdir --parents "$CUSTOM_PATH"
diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
index 01823fd..8bda62b 100755
--- a/ci/run-build-and-tests.sh
+++ b/ci/run-build-and-tests.sh
@@ -5,12 +5,12 @@
 
 . ${0%/*}/lib.sh
 
-run_tests=t
-
 case "$jobname" in
-linux-breaking-changes)
-	export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+fedora-breaking-changes-musl|linux-breaking-changes)
 	export WITH_BREAKING_CHANGES=YesPlease
+	export WITH_RUST=YesPlease
+	MESONFLAGS="$MESONFLAGS -Dbreaking_changes=true"
+	MESONFLAGS="$MESONFLAGS -Drust=enabled"
 	;;
 linux-TEST-vars)
 	export OPENSSL_SHA1_UNSAFE=YesPlease
@@ -36,12 +36,6 @@
 linux-reftable|linux-reftable-leaks|osx-reftable)
 	export GIT_TEST_DEFAULT_REF_FORMAT=reftable
 	;;
-pedantic)
-	# Don't run the tests; we only care about whether Git can be
-	# built.
-	export DEVOPTS=pedantic
-	run_tests=
-	;;
 esac
 
 case "$jobname" in
@@ -54,21 +48,15 @@
 		-Dtest_output_directory="${TEST_OUTPUT_DIRECTORY:-$(pwd)/t}" \
 		$MESONFLAGS
 	group "Build" meson compile -C build --
-	if test -n "$run_tests"
-	then
-		group "Run tests" meson test -C build --print-errorlogs --test-args="$GIT_TEST_OPTS" || (
-			./t/aggregate-results.sh "${TEST_OUTPUT_DIRECTORY:-t}/test-results"
-			handle_failed_tests
-		)
-	fi
+	group "Run tests" meson test -C build --print-errorlogs --test-args="$GIT_TEST_OPTS" || (
+		./t/aggregate-results.sh "${TEST_OUTPUT_DIRECTORY:-t}/test-results"
+		handle_failed_tests
+	)
 	;;
 *)
 	group Build make
-	if test -n "$run_tests"
-	then
-		group "Run tests" make test ||
-		handle_failed_tests
-	fi
+	group "Run tests" make test ||
+	handle_failed_tests
 	;;
 esac
 
diff --git a/ci/test-documentation.sh b/ci/test-documentation.sh
index 49f87f5..5e4fd8f 100755
--- a/ci/test-documentation.sh
+++ b/ci/test-documentation.sh
@@ -48,13 +48,13 @@
 
 # Build docs with Meson and AsciiDoc
 meson setup build-asciidoc -Ddocs=html,man -Ddocs_backend=asciidoc
-meson compile -C build-asciidoc
+meson compile -C build-asciidoc docs
 check_docs build-asciidoc AsciiDoc
 rm -rf build-asciidoc
 
 # Build docs with Meson and AsciiDoctor
 meson setup build-asciidoctor -Ddocs=html,man -Ddocs_backend=asciidoctor
-meson compile -C build-asciidoctor
+meson compile -C build-asciidoctor docs
 check_docs build-asciidoctor Asciidoctor
 rm -rf build-asciidoctor
 
diff --git a/color.c b/color.c
index 7df8862..07ac8c9 100644
--- a/color.c
+++ b/color.c
@@ -9,7 +9,7 @@
 #include "pager.h"
 #include "strbuf.h"
 
-static int git_use_color_default = GIT_COLOR_AUTO;
+static enum git_colorbool git_use_color_default = GIT_COLOR_AUTO;
 int color_stdout_is_tty = -1;
 
 /*
@@ -369,29 +369,29 @@ int color_parse_mem(const char *value, int value_len, char *dst)
 #undef OUT
 }
 
-int git_config_colorbool(const char *var, const char *value)
+enum git_colorbool git_config_colorbool(const char *var, const char *value)
 {
 	if (value) {
 		if (!strcasecmp(value, "never"))
-			return 0;
+			return GIT_COLOR_NEVER;
 		if (!strcasecmp(value, "always"))
-			return 1;
+			return GIT_COLOR_ALWAYS;
 		if (!strcasecmp(value, "auto"))
 			return GIT_COLOR_AUTO;
 	}
 
 	if (!var)
-		return -1;
+		return GIT_COLOR_UNKNOWN;
 
 	/* Missing or explicit false to turn off colorization */
 	if (!git_config_bool(var, value))
-		return 0;
+		return GIT_COLOR_NEVER;
 
 	/* any normal truth value defaults to 'auto' */
 	return GIT_COLOR_AUTO;
 }
 
-static int check_auto_color(int fd)
+static bool check_auto_color(int fd)
 {
 	static int color_stderr_is_tty = -1;
 	int *is_tty_p = fd == 1 ? &color_stdout_is_tty : &color_stderr_is_tty;
@@ -399,12 +399,12 @@ static int check_auto_color(int fd)
 		*is_tty_p = isatty(fd);
 	if (*is_tty_p || (fd == 1 && pager_in_use() && pager_use_color)) {
 		if (!is_terminal_dumb())
-			return 1;
+			return true;
 	}
-	return 0;
+	return false;
 }
 
-int want_color_fd(int fd, int var)
+bool want_color_fd(int fd, enum git_colorbool var)
 {
 	/*
 	 * NEEDSWORK: This function is sometimes used from multiple threads, and
@@ -418,7 +418,7 @@ int want_color_fd(int fd, int var)
 	if (fd < 1 || fd >= ARRAY_SIZE(want_auto))
 		BUG("file descriptor out of range: %d", fd);
 
-	if (var < 0)
+	if (var == GIT_COLOR_UNKNOWN)
 		var = git_use_color_default;
 
 	if (var == GIT_COLOR_AUTO) {
@@ -426,7 +426,7 @@ int want_color_fd(int fd, int var)
 			want_auto[fd] = check_auto_color(fd);
 		return want_auto[fd];
 	}
-	return var;
+	return var == GIT_COLOR_ALWAYS;
 }
 
 int git_color_config(const char *var, const char *value, void *cb UNUSED)
diff --git a/color.h b/color.h
index 7ed259a..43e6c9a 100644
--- a/color.h
+++ b/color.h
@@ -73,10 +73,12 @@ struct strbuf;
  * returned from git_config_colorbool. The "auto" value can be returned from
  * config_colorbool, and will be converted by want_color() into either 0 or 1.
  */
-#define GIT_COLOR_UNKNOWN -1
-#define GIT_COLOR_NEVER  0
-#define GIT_COLOR_ALWAYS 1
-#define GIT_COLOR_AUTO   2
+enum git_colorbool {
+	GIT_COLOR_UNKNOWN = -1,
+	GIT_COLOR_NEVER = 0,
+	GIT_COLOR_ALWAYS = 1,
+	GIT_COLOR_AUTO = 2,
+};
 
 /* A default list of colors to use for commit graphs and show-branch output */
 extern const char *column_colors_ansi[];
@@ -98,13 +100,13 @@ int git_color_config(const char *var, const char *value, void *cb);
  * GIT_COLOR_ALWAYS for "always" or a positive boolean,
  * and GIT_COLOR_AUTO for "auto".
  */
-int git_config_colorbool(const char *var, const char *value);
+enum git_colorbool git_config_colorbool(const char *var, const char *value);
 
 /*
  * Return a boolean whether to use color, where the argument 'var' is
  * one of GIT_COLOR_UNKNOWN, GIT_COLOR_NEVER, GIT_COLOR_ALWAYS, GIT_COLOR_AUTO.
  */
-int want_color_fd(int fd, int var);
+bool want_color_fd(int fd, enum git_colorbool var);
 #define want_color(colorbool) want_color_fd(1, (colorbool))
 #define want_color_stderr(colorbool) want_color_fd(2, (colorbool))
 
diff --git a/combine-diff.c b/combine-diff.c
index 4ea2dc9..b799862 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -749,7 +749,7 @@ static void show_line_to_eol(const char *line, int len, const char *reset)
 
 static void dump_sline(struct sline *sline, const char *line_prefix,
 		       unsigned long cnt, int num_parent,
-		       int use_color, int result_deleted)
+		       enum git_colorbool use_color, int result_deleted)
 {
 	unsigned long mark = (1UL<<num_parent);
 	unsigned long no_pre_delete = (2UL<<num_parent);
@@ -1315,7 +1315,7 @@ static struct diff_filepair *combined_pair(struct combine_diff_path *p,
 	struct diff_filepair *pair;
 	struct diff_filespec *pool;
 
-	pair = xmalloc(sizeof(*pair));
+	CALLOC_ARRAY(pair, 1);
 	CALLOC_ARRAY(pool, st_add(num_parent, 1));
 	pair->one = pool + 1;
 	pair->two = pool;
@@ -1515,8 +1515,9 @@ void diff_tree_combined(const struct object_id *oid,
 
 	diffopts = *opt;
 	copy_pathspec(&diffopts.pathspec, &opt->pathspec);
-	diffopts.flags.recursive = 1;
 	diffopts.flags.allow_external = 0;
+	if (!opt->flags.no_recursive_diff_tree_combined)
+		diffopts.flags.recursive = 1;
 
 	/* find set of paths that everybody touches
 	 *
diff --git a/command-list.txt b/command-list.txt
index b7ade3a..accd3d0 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -124,6 +124,7 @@
 git-init                                mainporcelain           init
 git-instaweb                            ancillaryinterrogators          complete
 git-interpret-trailers                  purehelpers
+git-last-modified                       plumbinginterrogators
 git-log                                 mainporcelain           info
 git-ls-files                            plumbinginterrogators
 git-ls-remote                           plumbinginterrogators
@@ -164,6 +165,7 @@
 git-repack                              ancillarymanipulators           complete
 git-replace                             ancillarymanipulators           complete
 git-replay                              plumbingmanipulators
+git-repo                                plumbinginterrogators
 git-request-pull                        foreignscminterface             complete
 git-rerere                              ancillaryinterrogators
 git-reset                               mainporcelain           history
diff --git a/commit-graph.c b/commit-graph.c
index e0d92b8..474454d 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -1,4 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
 #define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "git-compat-util.h"
@@ -29,7 +28,7 @@
 #include "tree.h"
 #include "chunk-format.h"
 
-void git_test_write_commit_graph_or_die(void)
+void git_test_write_commit_graph_or_die(struct odb_source *source)
 {
 	int flags = 0;
 	if (!git_env_bool(GIT_TEST_COMMIT_GRAPH, 0))
@@ -38,8 +37,7 @@ void git_test_write_commit_graph_or_die(void)
 	if (git_env_bool(GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS, 0))
 		flags = COMMIT_GRAPH_WRITE_BLOOM_FILTERS;
 
-	if (write_commit_graph_reachable(the_repository->objects->sources,
-					 flags, NULL))
+	if (write_commit_graph_reachable(source, flags, NULL))
 		die("failed to write commit-graph under GIT_TEST_COMMIT_GRAPH");
 }
 
@@ -54,8 +52,6 @@ void git_test_write_commit_graph_or_die(void)
 #define GRAPH_CHUNKID_BLOOMDATA 0x42444154 /* "BDAT" */
 #define GRAPH_CHUNKID_BASE 0x42415345 /* "BASE" */
 
-#define GRAPH_DATA_WIDTH (the_hash_algo->rawsz + 16)
-
 #define GRAPH_VERSION_1 0x1
 #define GRAPH_VERSION GRAPH_VERSION_1
 
@@ -67,8 +63,6 @@ void git_test_write_commit_graph_or_die(void)
 
 #define GRAPH_HEADER_SIZE 8
 #define GRAPH_FANOUT_SIZE (4 * 256)
-#define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * CHUNK_TOC_ENTRY_SIZE \
-			+ GRAPH_FANOUT_SIZE + the_hash_algo->rawsz)
 
 #define CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW (1ULL << 31)
 
@@ -81,6 +75,16 @@ define_commit_slab(topo_level_slab, uint32_t);
 define_commit_slab(commit_pos, int);
 static struct commit_pos commit_pos = COMMIT_SLAB_INIT(1, commit_pos);
 
+static size_t graph_data_width(const struct git_hash_algo *algop)
+{
+	return algop->rawsz + 16;
+}
+
+static size_t graph_min_size(const struct git_hash_algo *algop)
+{
+	return GRAPH_HEADER_SIZE + 4 * CHUNK_TOC_ENTRY_SIZE + GRAPH_FANOUT_SIZE + algop->rawsz;
+}
+
 static void set_commit_pos(struct repository *r, const struct object_id *oid)
 {
 	static int32_t max_pos;
@@ -249,9 +253,8 @@ int open_commit_graph(const char *graph_file, int *fd, struct stat *st)
 	return 1;
 }
 
-struct commit_graph *load_commit_graph_one_fd_st(struct repository *r,
-						 int fd, struct stat *st,
-						 struct odb_source *source)
+struct commit_graph *load_commit_graph_one_fd_st(struct odb_source *source,
+						 int fd, struct stat *st)
 {
 	void *graph_map;
 	size_t graph_size;
@@ -259,16 +262,15 @@ struct commit_graph *load_commit_graph_one_fd_st(struct repository *r,
 
 	graph_size = xsize_t(st->st_size);
 
-	if (graph_size < GRAPH_MIN_SIZE) {
+	if (graph_size < graph_min_size(source->odb->repo->hash_algo)) {
 		close(fd);
 		error(_("commit-graph file is too small"));
 		return NULL;
 	}
 	graph_map = xmmap(NULL, graph_size, PROT_READ, MAP_PRIVATE, fd, 0);
 	close(fd);
-	prepare_repo_settings(r);
-	ret = parse_commit_graph(&r->settings, graph_map, graph_size);
 
+	ret = parse_commit_graph(source->odb->repo, graph_map, graph_size);
 	if (ret)
 		ret->odb_source = source;
 	else
@@ -306,7 +308,7 @@ static int graph_read_oid_lookup(const unsigned char *chunk_start,
 {
 	struct commit_graph *g = data;
 	g->chunk_oid_lookup = chunk_start;
-	if (chunk_size / g->hash_len != g->num_commits)
+	if (chunk_size / g->hash_algo->rawsz != g->num_commits)
 		return error(_("commit-graph OID lookup chunk is the wrong size"));
 	return 0;
 }
@@ -315,7 +317,7 @@ static int graph_read_commit_data(const unsigned char *chunk_start,
 				  size_t chunk_size, void *data)
 {
 	struct commit_graph *g = data;
-	if (chunk_size / GRAPH_DATA_WIDTH != g->num_commits)
+	if (chunk_size / graph_data_width(g->hash_algo) != g->num_commits)
 		return error(_("commit-graph commit data chunk is wrong size"));
 	g->chunk_commit_data = chunk_start;
 	return 0;
@@ -368,7 +370,7 @@ static int graph_read_bloom_data(const unsigned char *chunk_start,
 	return 0;
 }
 
-struct commit_graph *parse_commit_graph(struct repo_settings *s,
+struct commit_graph *parse_commit_graph(struct repository *r,
 					void *graph_map, size_t graph_size)
 {
 	const unsigned char *data;
@@ -380,7 +382,7 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s,
 	if (!graph_map)
 		return NULL;
 
-	if (graph_size < GRAPH_MIN_SIZE)
+	if (graph_size < graph_min_size(r->hash_algo))
 		return NULL;
 
 	data = (const unsigned char *)graph_map;
@@ -400,22 +402,22 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s,
 	}
 
 	hash_version = *(unsigned char*)(data + 5);
-	if (hash_version != oid_version(the_hash_algo)) {
+	if (hash_version != oid_version(r->hash_algo)) {
 		error(_("commit-graph hash version %X does not match version %X"),
-		      hash_version, oid_version(the_hash_algo));
+		      hash_version, oid_version(r->hash_algo));
 		return NULL;
 	}
 
 	graph = alloc_commit_graph();
 
-	graph->hash_len = the_hash_algo->rawsz;
+	graph->hash_algo = r->hash_algo;
 	graph->num_chunks = *(unsigned char*)(data + 6);
 	graph->data = graph_map;
 	graph->data_len = graph_size;
 
 	if (graph_size < GRAPH_HEADER_SIZE +
 			 (graph->num_chunks + 1) * CHUNK_TOC_ENTRY_SIZE +
-			 GRAPH_FANOUT_SIZE + the_hash_algo->rawsz) {
+			 GRAPH_FANOUT_SIZE + r->hash_algo->rawsz) {
 		error(_("commit-graph file is too small to hold %u chunks"),
 		      graph->num_chunks);
 		free(graph);
@@ -446,7 +448,9 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s,
 	pair_chunk(cf, GRAPH_CHUNKID_BASE, &graph->chunk_base_graphs,
 		   &graph->chunk_base_graphs_size);
 
-	if (s->commit_graph_generation_version >= 2) {
+	prepare_repo_settings(r);
+
+	if (r->settings.commit_graph_generation_version >= 2) {
 		read_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA,
 			   graph_read_generation_data, graph);
 		pair_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW,
@@ -457,7 +461,7 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s,
 			graph->read_generation_data = 1;
 	}
 
-	if (s->commit_graph_changed_paths_version) {
+	if (r->settings.commit_graph_changed_paths_version) {
 		read_chunk(cf, GRAPH_CHUNKID_BLOOMINDEXES,
 			   graph_read_bloom_index, graph);
 		read_chunk(cf, GRAPH_CHUNKID_BLOOMDATA,
@@ -473,8 +477,8 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s,
 		FREE_AND_NULL(graph->bloom_filter_settings);
 	}
 
-	oidread(&graph->oid, graph->data + graph->data_len - graph->hash_len,
-		the_repository->hash_algo);
+	oidread(&graph->oid, graph->data + graph->data_len - graph->hash_algo->rawsz,
+		r->hash_algo);
 
 	free_chunkfile(cf);
 	return graph;
@@ -486,11 +490,9 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s,
 	return NULL;
 }
 
-static struct commit_graph *load_commit_graph_one(struct repository *r,
-						  const char *graph_file,
-						  struct odb_source *source)
+static struct commit_graph *load_commit_graph_one(struct odb_source *source,
+						  const char *graph_file)
 {
-
 	struct stat st;
 	int fd;
 	struct commit_graph *g;
@@ -499,19 +501,17 @@ static struct commit_graph *load_commit_graph_one(struct repository *r,
 	if (!open_ok)
 		return NULL;
 
-	g = load_commit_graph_one_fd_st(r, fd, &st, source);
-
+	g = load_commit_graph_one_fd_st(source, fd, &st);
 	if (g)
 		g->filename = xstrdup(graph_file);
 
 	return g;
 }
 
-static struct commit_graph *load_commit_graph_v1(struct repository *r,
-						 struct odb_source *source)
+static struct commit_graph *load_commit_graph_v1(struct odb_source *source)
 {
 	char *graph_name = get_commit_graph_filename(source);
-	struct commit_graph *g = load_commit_graph_one(r, graph_name, source);
+	struct commit_graph *g = load_commit_graph_one(source, graph_name);
 	free(graph_name);
 
 	return g;
@@ -579,7 +579,7 @@ static int add_graph_to_chain(struct commit_graph *g,
 		return 0;
 	}
 
-	if (g->chunk_base_graphs_size / g->hash_len < n) {
+	if (g->chunk_base_graphs_size / g->hash_algo->rawsz < n) {
 		warning(_("commit-graph base graphs chunk is too small"));
 		return 0;
 	}
@@ -589,8 +589,8 @@ static int add_graph_to_chain(struct commit_graph *g,
 
 		if (!cur_g ||
 		    !oideq(&oids[n], &cur_g->oid) ||
-		    !hasheq(oids[n].hash, g->chunk_base_graphs + st_mult(g->hash_len, n),
-			    the_repository->hash_algo)) {
+		    !hasheq(oids[n].hash, g->chunk_base_graphs + st_mult(g->hash_algo->rawsz, n),
+			    g->hash_algo)) {
 			warning(_("commit-graph chain does not match"));
 			return 0;
 		}
@@ -614,7 +614,8 @@ static int add_graph_to_chain(struct commit_graph *g,
 }
 
 int open_commit_graph_chain(const char *chain_file,
-			    int *fd, struct stat *st)
+			    int *fd, struct stat *st,
+			    const struct git_hash_algo *hash_algo)
 {
 	*fd = git_open(chain_file);
 	if (*fd < 0)
@@ -623,7 +624,7 @@ int open_commit_graph_chain(const char *chain_file,
 		close(*fd);
 		return 0;
 	}
-	if (st->st_size < the_hash_algo->hexsz) {
+	if (st->st_size < hash_algo->hexsz) {
 		close(*fd);
 		if (!st->st_size) {
 			/* treat empty files the same as missing */
@@ -637,7 +638,7 @@ int open_commit_graph_chain(const char *chain_file,
 	return 1;
 }
 
-struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r,
+struct commit_graph *load_commit_graph_chain_fd_st(struct object_database *odb,
 						   int fd, struct stat *st,
 						   int *incomplete_chain)
 {
@@ -647,10 +648,10 @@ struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r,
 	int i = 0, valid = 1, count;
 	FILE *fp = xfdopen(fd, "r");
 
-	count = st->st_size / (the_hash_algo->hexsz + 1);
+	count = st->st_size / (odb->repo->hash_algo->hexsz + 1);
 	CALLOC_ARRAY(oids, count);
 
-	odb_prepare_alternates(r->objects);
+	odb_prepare_alternates(odb);
 
 	for (i = 0; i < count; i++) {
 		struct odb_source *source;
@@ -658,7 +659,7 @@ struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r,
 		if (strbuf_getline_lf(&line, fp) == EOF)
 			break;
 
-		if (get_oid_hex(line.buf, &oids[i])) {
+		if (get_oid_hex_algop(line.buf, &oids[i], odb->repo->hash_algo)) {
 			warning(_("invalid commit-graph chain: line '%s' not a hash"),
 				line.buf);
 			valid = 0;
@@ -666,9 +667,9 @@ struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r,
 		}
 
 		valid = 0;
-		for (source = r->objects->sources; source; source = source->next) {
+		for (source = odb->sources; source; source = source->next) {
 			char *graph_name = get_split_graph_filename(source, line.buf);
-			struct commit_graph *g = load_commit_graph_one(r, graph_name, source);
+			struct commit_graph *g = load_commit_graph_one(source, graph_name);
 
 			free(graph_name);
 
@@ -701,52 +702,40 @@ struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r,
 	return graph_chain;
 }
 
-static struct commit_graph *load_commit_graph_chain(struct repository *r,
-						    struct odb_source *source)
+static struct commit_graph *load_commit_graph_chain(struct odb_source *source)
 {
 	char *chain_file = get_commit_graph_chain_filename(source);
 	struct stat st;
 	int fd;
 	struct commit_graph *g = NULL;
 
-	if (open_commit_graph_chain(chain_file, &fd, &st)) {
+	if (open_commit_graph_chain(chain_file, &fd, &st, source->odb->repo->hash_algo)) {
 		int incomplete;
 		/* ownership of fd is taken over by load function */
-		g = load_commit_graph_chain_fd_st(r, fd, &st, &incomplete);
+		g = load_commit_graph_chain_fd_st(source->odb, fd, &st, &incomplete);
 	}
 
 	free(chain_file);
 	return g;
 }
 
-struct commit_graph *read_commit_graph_one(struct repository *r,
-					   struct odb_source *source)
+struct commit_graph *read_commit_graph_one(struct odb_source *source)
 {
-	struct commit_graph *g = load_commit_graph_v1(r, source);
+	struct commit_graph *g = load_commit_graph_v1(source);
 
 	if (!g)
-		g = load_commit_graph_chain(r, source);
+		g = load_commit_graph_chain(source);
 
 	return g;
 }
 
-static void prepare_commit_graph_one(struct repository *r,
-				     struct odb_source *source)
-{
-
-	if (r->objects->commit_graph)
-		return;
-
-	r->objects->commit_graph = read_commit_graph_one(r, source);
-}
-
 /*
  * Return 1 if commit_graph is non-NULL, and 0 otherwise.
  *
  * On the first invocation, this function attempts to load the commit
- * graph if the_repository is configured to have one.
+ * graph if the repository is configured to have one.
  */
-static int prepare_commit_graph(struct repository *r)
+static struct commit_graph *prepare_commit_graph(struct repository *r)
 {
 	struct odb_source *source;
 
@@ -758,10 +747,10 @@ static int prepare_commit_graph(struct repository *r)
 	 * we want to disable even an already-loaded graph file.
 	 */
 	if (!r->gitdir || r->commit_graph_disabled)
-		return 0;
+		return NULL;
 
 	if (r->objects->commit_graph_attempted)
-		return !!r->objects->commit_graph;
+		return r->objects->commit_graph;
 	r->objects->commit_graph_attempted = 1;
 
 	prepare_repo_settings(r);
@@ -774,33 +763,32 @@ static int prepare_commit_graph(struct repository *r)
 		 * so that commit graph loading is not attempted again for this
 		 * repository.)
 		 */
-		return 0;
+		return NULL;
 
 	if (!commit_graph_compatible(r))
-		return 0;
+		return NULL;
 
 	odb_prepare_alternates(r->objects);
-	for (source = r->objects->sources;
-	     !r->objects->commit_graph && source;
-	     source = source->next)
-		prepare_commit_graph_one(r, source);
-	return !!r->objects->commit_graph;
+	for (source = r->objects->sources; source; source = source->next) {
+		r->objects->commit_graph = read_commit_graph_one(source);
+		if (r->objects->commit_graph)
+			break;
+	}
+
+	return r->objects->commit_graph;
 }
 
 int generation_numbers_enabled(struct repository *r)
 {
 	uint32_t first_generation;
 	struct commit_graph *g;
-	if (!prepare_commit_graph(r))
+
+	g = prepare_commit_graph(r);
+	if (!g || !g->num_commits)
 	       return 0;
 
-	g = r->objects->commit_graph;
-
-	if (!g->num_commits)
-		return 0;
-
 	first_generation = get_be32(g->chunk_commit_data +
-				    g->hash_len + 8) >> 2;
+				    g->hash_algo->rawsz + 8) >> 2;
 
 	return !!first_generation;
 }
@@ -808,12 +796,9 @@ int generation_numbers_enabled(struct repository *r)
 int corrected_commit_dates_enabled(struct repository *r)
 {
 	struct commit_graph *g;
-	if (!prepare_commit_graph(r))
-		return 0;
 
-	g = r->objects->commit_graph;
-
-	if (!g->num_commits)
+	g = prepare_commit_graph(r);
+	if (!g || !g->num_commits)
 		return 0;
 
 	return g->read_generation_data;
@@ -821,7 +806,12 @@ int corrected_commit_dates_enabled(struct repository *r)
 
 struct bloom_filter_settings *get_bloom_filter_settings(struct repository *r)
 {
-	struct commit_graph *g = r->objects->commit_graph;
+	struct commit_graph *g;
+
+	if (!prepare_commit_graph(r))
+	       return NULL;
+
+	g = r->objects->commit_graph;
 	while (g) {
 		if (g->bloom_filter_settings)
 			return g->bloom_filter_settings;
@@ -844,7 +834,7 @@ void close_commit_graph(struct object_database *o)
 static int bsearch_graph(struct commit_graph *g, const struct object_id *oid, uint32_t *pos)
 {
 	return bsearch_hash(oid->hash, g->chunk_oid_fanout,
-			    g->chunk_oid_lookup, g->hash_len, pos);
+			    g->chunk_oid_lookup, g->hash_algo->rawsz, pos);
 }
 
 static void load_oid_from_graph(struct commit_graph *g,
@@ -864,12 +854,11 @@ static void load_oid_from_graph(struct commit_graph *g,
 
 	lex_index = pos - g->num_commits_in_base;
 
-	oidread(oid, g->chunk_oid_lookup + st_mult(g->hash_len, lex_index),
-		the_repository->hash_algo);
+	oidread(oid, g->chunk_oid_lookup + st_mult(g->hash_algo->rawsz, lex_index),
+		g->hash_algo);
 }
 
-static struct commit_list **insert_parent_or_die(struct repository *r,
-						 struct commit_graph *g,
+static struct commit_list **insert_parent_or_die(struct commit_graph *g,
 						 uint32_t pos,
 						 struct commit_list **pptr)
 {
@@ -880,7 +869,7 @@ static struct commit_list **insert_parent_or_die(struct repository *r,
 		die("invalid parent position %"PRIu32, pos);
 
 	load_oid_from_graph(g, pos, &oid);
-	c = lookup_commit(r, &oid);
+	c = lookup_commit(g->odb_source->odb->repo, &oid);
 	if (!c)
 		die(_("could not find commit %s"), oid_to_hex(&oid));
 	commit_graph_data_at(c)->graph_pos = pos;
@@ -901,13 +890,13 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g,
 		die(_("invalid commit position. commit-graph is likely corrupt"));
 
 	lex_index = pos - g->num_commits_in_base;
-	commit_data = g->chunk_commit_data + st_mult(GRAPH_DATA_WIDTH, lex_index);
+	commit_data = g->chunk_commit_data + st_mult(graph_data_width(g->hash_algo), lex_index);
 
 	graph_data = commit_graph_data_at(item);
 	graph_data->graph_pos = pos;
 
-	date_high = get_be32(commit_data + g->hash_len + 8) & 0x3;
-	date_low = get_be32(commit_data + g->hash_len + 12);
+	date_high = get_be32(commit_data + g->hash_algo->rawsz + 8) & 0x3;
+	date_low = get_be32(commit_data + g->hash_algo->rawsz + 12);
 	item->date = (timestamp_t)((date_high << 32) | date_low);
 
 	if (g->read_generation_data) {
@@ -925,10 +914,10 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g,
 		} else
 			graph_data->generation = item->date + offset;
 	} else
-		graph_data->generation = get_be32(commit_data + g->hash_len + 8) >> 2;
+		graph_data->generation = get_be32(commit_data + g->hash_algo->rawsz + 8) >> 2;
 
 	if (g->topo_levels)
-		*topo_level_slab_at(g->topo_levels, item) = get_be32(commit_data + g->hash_len + 8) >> 2;
+		*topo_level_slab_at(g->topo_levels, item) = get_be32(commit_data + g->hash_algo->rawsz + 8) >> 2;
 }
 
 static inline void set_commit_tree(struct commit *c, struct tree *t)
@@ -936,8 +925,7 @@ static inline void set_commit_tree(struct commit *c, struct tree *t)
 	c->maybe_tree = t;
 }
 
-static int fill_commit_in_graph(struct repository *r,
-				struct commit *item,
+static int fill_commit_in_graph(struct commit *item,
 				struct commit_graph *g, uint32_t pos)
 {
 	uint32_t edge_value;
@@ -952,7 +940,7 @@ static int fill_commit_in_graph(struct repository *r,
 	fill_commit_graph_info(item, g, pos);
 
 	lex_index = pos - g->num_commits_in_base;
-	commit_data = g->chunk_commit_data + st_mult(g->hash_len + 16, lex_index);
+	commit_data = g->chunk_commit_data + st_mult(g->hash_algo->rawsz + 16, lex_index);
 
 	item->object.parsed = 1;
 
@@ -960,16 +948,16 @@ static int fill_commit_in_graph(struct repository *r,
 
 	pptr = &item->parents;
 
-	edge_value = get_be32(commit_data + g->hash_len);
+	edge_value = get_be32(commit_data + g->hash_algo->rawsz);
 	if (edge_value == GRAPH_PARENT_NONE)
 		return 1;
-	pptr = insert_parent_or_die(r, g, edge_value, pptr);
+	pptr = insert_parent_or_die(g, edge_value, pptr);
 
-	edge_value = get_be32(commit_data + g->hash_len + 4);
+	edge_value = get_be32(commit_data + g->hash_algo->rawsz + 4);
 	if (edge_value == GRAPH_PARENT_NONE)
 		return 1;
 	if (!(edge_value & GRAPH_EXTRA_EDGES_NEEDED)) {
-		pptr = insert_parent_or_die(r, g, edge_value, pptr);
+		pptr = insert_parent_or_die(g, edge_value, pptr);
 		return 1;
 	}
 
@@ -984,7 +972,7 @@ static int fill_commit_in_graph(struct repository *r,
 		}
 		edge_value = get_be32(g->chunk_extra_edges +
 				      sizeof(uint32_t) * parent_data_pos);
-		pptr = insert_parent_or_die(r, g,
+		pptr = insert_parent_or_die(g,
 					    edge_value & GRAPH_EDGE_LAST_MASK,
 					    pptr);
 		parent_data_pos++;
@@ -1020,26 +1008,32 @@ static int find_commit_pos_in_graph(struct commit *item, struct commit_graph *g,
 	}
 }
 
-int repo_find_commit_pos_in_graph(struct repository *r, struct commit *c,
-				  uint32_t *pos)
+struct commit_graph *repo_find_commit_pos_in_graph(struct repository *r,
+						   struct commit *c,
+						   uint32_t *pos)
 {
-	if (!prepare_commit_graph(r))
-		return 0;
-	return find_commit_pos_in_graph(c, r->objects->commit_graph, pos);
+	struct commit_graph *g = prepare_commit_graph(r);
+	if (!g)
+		return NULL;
+	if (!find_commit_pos_in_graph(c, g, pos))
+		return NULL;
+	return g;
 }
 
 struct commit *lookup_commit_in_graph(struct repository *repo, const struct object_id *id)
 {
 	static int commit_graph_paranoia = -1;
+	struct commit_graph *g;
 	struct commit *commit;
 	uint32_t pos;
 
 	if (commit_graph_paranoia == -1)
 		commit_graph_paranoia = git_env_bool(GIT_COMMIT_GRAPH_PARANOIA, 0);
 
-	if (!prepare_commit_graph(repo))
+	g = prepare_commit_graph(repo);
+	if (!g)
 		return NULL;
-	if (!search_commit_pos_in_graph(id, repo->objects->commit_graph, &pos))
+	if (!search_commit_pos_in_graph(id, g, &pos))
 		return NULL;
 	if (commit_graph_paranoia && !odb_has_object(repo->objects, id, 0))
 		return NULL;
@@ -1050,14 +1044,13 @@ struct commit *lookup_commit_in_graph(struct repository *repo, const struct obje
 	if (commit->object.parsed)
 		return commit;
 
-	if (!fill_commit_in_graph(repo, commit, repo->objects->commit_graph, pos))
+	if (!fill_commit_in_graph(commit, g, pos))
 		return NULL;
 
 	return commit;
 }
 
-static int parse_commit_in_graph_one(struct repository *r,
-				     struct commit_graph *g,
+static int parse_commit_in_graph_one(struct commit_graph *g,
 				     struct commit *item)
 {
 	uint32_t pos;
@@ -1066,7 +1059,7 @@ static int parse_commit_in_graph_one(struct repository *r,
 		return 1;
 
 	if (find_commit_pos_in_graph(item, g, &pos))
-		return fill_commit_in_graph(r, item, g, pos);
+		return fill_commit_in_graph(item, g, pos);
 
 	return 0;
 }
@@ -1074,6 +1067,7 @@ static int parse_commit_in_graph_one(struct repository *r,
 int parse_commit_in_graph(struct repository *r, struct commit *item)
 {
 	static int checked_env = 0;
+	struct commit_graph *g;
 
 	if (!checked_env &&
 	    git_env_bool(GIT_TEST_COMMIT_GRAPH_DIE_ON_PARSE, 0))
@@ -1081,20 +1075,23 @@ int parse_commit_in_graph(struct repository *r, struct commit *item)
 		    GIT_TEST_COMMIT_GRAPH_DIE_ON_PARSE);
 	checked_env = 1;
 
-	if (!prepare_commit_graph(r))
+	g = prepare_commit_graph(r);
+	if (!g)
 		return 0;
-	return parse_commit_in_graph_one(r, r->objects->commit_graph, item);
+	return parse_commit_in_graph_one(g, item);
 }
 
 void load_commit_graph_info(struct repository *r, struct commit *item)
 {
+	struct commit_graph *g;
 	uint32_t pos;
-	if (repo_find_commit_pos_in_graph(r, item, &pos))
-		fill_commit_graph_info(item, r->objects->commit_graph, pos);
+
+	g = repo_find_commit_pos_in_graph(r, item, &pos);
+	if (g)
+		fill_commit_graph_info(item, g, pos);
 }
 
-static struct tree *load_tree_for_commit(struct repository *r,
-					 struct commit_graph *g,
+static struct tree *load_tree_for_commit(struct commit_graph *g,
 					 struct commit *c)
 {
 	struct object_id oid;
@@ -1105,16 +1102,16 @@ static struct tree *load_tree_for_commit(struct repository *r,
 		g = g->base_graph;
 
 	commit_data = g->chunk_commit_data +
-			st_mult(GRAPH_DATA_WIDTH, graph_pos - g->num_commits_in_base);
+			st_mult(graph_data_width(g->hash_algo),
+				graph_pos - g->num_commits_in_base);
 
-	oidread(&oid, commit_data, the_repository->hash_algo);
-	set_commit_tree(c, lookup_tree(r, &oid));
+	oidread(&oid, commit_data, g->hash_algo);
+	set_commit_tree(c, lookup_tree(g->odb_source->odb->repo, &oid));
 
 	return c->maybe_tree;
 }
 
-static struct tree *get_commit_tree_in_graph_one(struct repository *r,
-						 struct commit_graph *g,
+static struct tree *get_commit_tree_in_graph_one(struct commit_graph *g,
 						 const struct commit *c)
 {
 	if (c->maybe_tree)
@@ -1122,12 +1119,12 @@ static struct tree *get_commit_tree_in_graph_one(struct repository *r,
 	if (commit_graph_position(c) == COMMIT_NOT_FROM_GRAPH)
 		BUG("get_commit_tree_in_graph_one called from non-commit-graph commit");
 
-	return load_tree_for_commit(r, g, (struct commit *)c);
+	return load_tree_for_commit(g, (struct commit *)c);
 }
 
 struct tree *get_commit_tree_in_graph(struct repository *r, const struct commit *c)
 {
-	return get_commit_tree_in_graph_one(r, r->objects->commit_graph, c);
+	return get_commit_tree_in_graph_one(r->objects->commit_graph, c);
 }
 
 struct packed_commit_list {
@@ -1213,7 +1210,7 @@ static int write_graph_chunk_oids(struct hashfile *f,
 	int count;
 	for (count = 0; count < ctx->commits.nr; count++, list++) {
 		display_progress(ctx->progress, ++ctx->progress_cnt);
-		hashwrite(f, (*list)->object.oid.hash, the_hash_algo->rawsz);
+		hashwrite(f, (*list)->object.oid.hash, f->algop->rawsz);
 	}
 
 	return 0;
@@ -1244,7 +1241,7 @@ static int write_graph_chunk_data(struct hashfile *f,
 			die(_("unable to parse commit %s"),
 				oid_to_hex(&(*list)->object.oid));
 		tree = get_commit_tree_oid(*list);
-		hashwrite(f, tree->hash, the_hash_algo->rawsz);
+		hashwrite(f, tree->hash, ctx->r->hash_algo->rawsz);
 
 		parent = (*list)->parents;
 
@@ -1534,7 +1531,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)
 
 	if (ctx->report_progress)
 		ctx->progress = start_delayed_progress(
-					the_repository,
+					ctx->r,
 					_("Loading known commits in commit graph"),
 					ctx->oids.nr);
 	for (i = 0; i < ctx->oids.nr; i++) {
@@ -1552,7 +1549,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)
 	 */
 	if (ctx->report_progress)
 		ctx->progress = start_delayed_progress(
-					the_repository,
+					ctx->r,
 					_("Expanding reachable commits in commit graph"),
 					0);
 	for (i = 0; i < ctx->oids.nr; i++) {
@@ -1573,7 +1570,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)
 
 	if (ctx->report_progress)
 		ctx->progress = start_delayed_progress(
-					the_repository,
+					ctx->r,
 					_("Clearing commit marks in commit graph"),
 					ctx->oids.nr);
 	for (i = 0; i < ctx->oids.nr; i++) {
@@ -1691,7 +1688,7 @@ static void compute_topological_levels(struct write_commit_graph_context *ctx)
 	if (ctx->report_progress)
 		info.progress = ctx->progress
 			      = start_delayed_progress(
-					the_repository,
+					ctx->r,
 					_("Computing commit graph topological levels"),
 					ctx->commits.nr);
 
@@ -1726,7 +1723,7 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)
 	if (ctx->report_progress)
 		info.progress = ctx->progress
 			      = start_delayed_progress(
-					the_repository,
+					ctx->r,
 					_("Computing commit graph generation numbers"),
 					ctx->commits.nr);
 
@@ -1803,7 +1800,7 @@ static void compute_bloom_filters(struct write_commit_graph_context *ctx)
 
 	if (ctx->report_progress)
 		progress = start_delayed_progress(
-			the_repository,
+			ctx->r,
 			_("Computing commit changed paths Bloom filters"),
 			ctx->commits.nr);
 
@@ -1849,6 +1846,7 @@ static void compute_bloom_filters(struct write_commit_graph_context *ctx)
 }
 
 struct refs_cb_data {
+	struct repository *repo;
 	struct oidset *commits;
 	struct progress *progress;
 };
@@ -1861,9 +1859,9 @@ static int add_ref_to_set(const char *refname UNUSED,
 	struct object_id peeled;
 	struct refs_cb_data *data = (struct refs_cb_data *)cb_data;
 
-	if (!peel_iterated_oid(the_repository, oid, &peeled))
+	if (!peel_iterated_oid(data->repo, oid, &peeled))
 		oid = &peeled;
-	if (odb_read_object_info(the_repository->objects, oid, NULL) == OBJ_COMMIT)
+	if (odb_read_object_info(data->repo->objects, oid, NULL) == OBJ_COMMIT)
 		oidset_insert(data->commits, oid);
 
 	display_progress(data->progress, oidset_size(data->commits));
@@ -1880,13 +1878,15 @@ int write_commit_graph_reachable(struct odb_source *source,
 	int result;
 
 	memset(&data, 0, sizeof(data));
+	data.repo = source->odb->repo;
 	data.commits = &commits;
+
 	if (flags & COMMIT_GRAPH_WRITE_PROGRESS)
 		data.progress = start_delayed_progress(
-			the_repository,
+			source->odb->repo,
 			_("Collecting referenced commits"), 0);
 
-	refs_for_each_ref(get_main_ref_store(the_repository), add_ref_to_set,
+	refs_for_each_ref(get_main_ref_store(source->odb->repo), add_ref_to_set,
 			  &data);
 
 	stop_progress(&data.progress);
@@ -1915,7 +1915,7 @@ static int fill_oids_from_packs(struct write_commit_graph_context *ctx,
 			       "Finding commits for commit graph in %"PRIuMAX" packs",
 			       pack_indexes->nr),
 			    (uintmax_t)pack_indexes->nr);
-		ctx->progress = start_delayed_progress(the_repository,
+		ctx->progress = start_delayed_progress(ctx->r,
 						       progress_title.buf, 0);
 		ctx->progress_done = 0;
 	}
@@ -1969,7 +1969,7 @@ static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx)
 {
 	if (ctx->report_progress)
 		ctx->progress = start_delayed_progress(
-			the_repository,
+			ctx->r,
 			_("Finding commits for commit graph among packed objects"),
 			ctx->approx_nr_objects);
 	for_each_packed_object(ctx->r, add_packed_commits, ctx,
@@ -1988,7 +1988,7 @@ static void copy_oids_to_commits(struct write_commit_graph_context *ctx)
 	ctx->num_extra_edges = 0;
 	if (ctx->report_progress)
 		ctx->progress = start_delayed_progress(
-			the_repository,
+			ctx->r,
 			_("Finding extra edges in commit graph"),
 			ctx->oids.nr);
 	oid_array_sort(&ctx->oids);
@@ -2027,7 +2027,7 @@ static int write_graph_chunk_base_1(struct hashfile *f,
 		return 0;
 
 	num = write_graph_chunk_base_1(f, g->base_graph);
-	hashwrite(f, g->oid.hash, the_hash_algo->rawsz);
+	hashwrite(f, g->oid.hash, g->hash_algo->rawsz);
 	return num + 1;
 }
 
@@ -2051,7 +2051,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
 	struct hashfile *f;
 	struct tempfile *graph_layer; /* when ctx->split is non-zero */
 	struct lock_file lk = LOCK_INIT;
-	const unsigned hashsz = the_hash_algo->rawsz;
+	const unsigned hashsz = ctx->r->hash_algo->rawsz;
 	struct strbuf progress_title = STRBUF_INIT;
 	struct chunkfile *cf;
 	unsigned char file_hash[GIT_MAX_RAWSZ];
@@ -2067,7 +2067,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
 		ctx->graph_name = get_commit_graph_filename(ctx->odb_source);
 	}
 
-	if (safe_create_leading_directories(the_repository, ctx->graph_name)) {
+	if (safe_create_leading_directories(ctx->r, ctx->graph_name)) {
 		error(_("unable to create leading directories of %s"),
 			ctx->graph_name);
 		return -1;
@@ -2086,18 +2086,18 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
 			return -1;
 		}
 
-		if (adjust_shared_perm(the_repository, get_tempfile_path(graph_layer))) {
+		if (adjust_shared_perm(ctx->r, get_tempfile_path(graph_layer))) {
 			error(_("unable to adjust shared permissions for '%s'"),
 			      get_tempfile_path(graph_layer));
 			return -1;
 		}
 
-		f = hashfd(the_repository->hash_algo,
+		f = hashfd(ctx->r->hash_algo,
 			   get_tempfile_fd(graph_layer), get_tempfile_path(graph_layer));
 	} else {
 		hold_lock_file_for_update_mode(&lk, ctx->graph_name,
 					       LOCK_DIE_ON_ERROR, 0444);
-		f = hashfd(the_repository->hash_algo,
+		f = hashfd(ctx->r->hash_algo,
 			   get_lock_file_fd(&lk), get_lock_file_path(&lk));
 	}
 
@@ -2139,7 +2139,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
 	hashwrite_be32(f, GRAPH_SIGNATURE);
 
 	hashwrite_u8(f, GRAPH_VERSION);
-	hashwrite_u8(f, oid_version(the_hash_algo));
+	hashwrite_u8(f, oid_version(ctx->r->hash_algo));
 	hashwrite_u8(f, get_num_chunks(cf));
 	hashwrite_u8(f, ctx->num_commit_graphs_after - 1);
 
@@ -2150,7 +2150,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
 			       get_num_chunks(cf)),
 			    get_num_chunks(cf));
 		ctx->progress = start_delayed_progress(
-			the_repository,
+			ctx->r,
 			progress_title.buf,
 			st_mult(get_num_chunks(cf), ctx->commits.nr));
 	}
@@ -2208,7 +2208,8 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
 		}
 
 		free(ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1]);
-		ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1] = xstrdup(hash_to_hex(file_hash));
+		ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1] =
+			xstrdup(hash_to_hex_algop(file_hash, ctx->r->hash_algo));
 		final_graph_name = get_split_graph_filename(ctx->odb_source,
 					ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1]);
 		free(ctx->commit_graph_filenames_after[ctx->num_commit_graphs_after - 1]);
@@ -2230,7 +2231,8 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
 	return 0;
 }
 
-static void split_graph_merge_strategy(struct write_commit_graph_context *ctx)
+static void split_graph_merge_strategy(struct write_commit_graph_context *ctx,
+				       struct commit_graph *graph_to_merge)
 {
 	struct commit_graph *g;
 	uint32_t num_commits;
@@ -2249,7 +2251,7 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx)
 		flags = ctx->opts->split_flags;
 	}
 
-	g = ctx->r->objects->commit_graph;
+	g = graph_to_merge;
 	num_commits = ctx->commits.nr;
 	if (flags == COMMIT_GRAPH_SPLIT_REPLACE)
 		ctx->num_commit_graphs_after = 1;
@@ -2301,7 +2303,7 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx)
 		ctx->commit_graph_filenames_after[i] = xstrdup(ctx->commit_graph_filenames_before[i]);
 
 	i = ctx->num_commit_graphs_before - 1;
-	g = ctx->r->objects->commit_graph;
+	g = graph_to_merge;
 
 	while (g) {
 		if (i < ctx->num_commit_graphs_after)
@@ -2363,7 +2365,7 @@ static void sort_and_scan_merged_commits(struct write_commit_graph_context *ctx)
 
 	if (ctx->report_progress)
 		ctx->progress = start_delayed_progress(
-					the_repository,
+					ctx->r,
 					_("Scanning merged commits"),
 					ctx->commits.nr);
 
@@ -2399,16 +2401,16 @@ static void sort_and_scan_merged_commits(struct write_commit_graph_context *ctx)
 	stop_progress(&ctx->progress);
 }
 
-static void merge_commit_graphs(struct write_commit_graph_context *ctx)
+static void merge_commit_graphs(struct write_commit_graph_context *ctx,
+				struct commit_graph *g)
 {
-	struct commit_graph *g = ctx->r->objects->commit_graph;
 	uint32_t current_graph_number = ctx->num_commit_graphs_before;
 
 	while (g && current_graph_number >= ctx->num_commit_graphs_after) {
 		current_graph_number--;
 
 		if (ctx->report_progress)
-			ctx->progress = start_delayed_progress(the_repository,
+			ctx->progress = start_delayed_progress(ctx->r,
 							       _("Merging commit-graph"), 0);
 
 		merge_commit_graph(ctx, g);
@@ -2511,7 +2513,7 @@ int write_commit_graph(struct odb_source *source,
 		       enum commit_graph_write_flags flags,
 		       const struct commit_graph_opts *opts)
 {
-	struct repository *r = the_repository;
+	struct repository *r = source->odb->repo;
 	struct write_commit_graph_context ctx = {
 		.r = r,
 		.odb_source = source,
@@ -2528,6 +2530,7 @@ int write_commit_graph(struct odb_source *source,
 	int replace = 0;
 	struct bloom_filter_settings bloom_settings = DEFAULT_BLOOM_FILTER_SETTINGS;
 	struct topo_level_slab topo_levels;
+	struct commit_graph *g;
 
 	prepare_repo_settings(r);
 	if (!r->settings.core_commit_graph) {
@@ -2556,23 +2559,13 @@ int write_commit_graph(struct odb_source *source,
 	init_topo_level_slab(&topo_levels);
 	ctx.topo_levels = &topo_levels;
 
-	prepare_commit_graph(ctx.r);
-	if (ctx.r->objects->commit_graph) {
-		struct commit_graph *g = ctx.r->objects->commit_graph;
-
-		while (g) {
-			g->topo_levels = &topo_levels;
-			g = g->base_graph;
-		}
-	}
+	g = prepare_commit_graph(ctx.r);
+	for (struct commit_graph *chain = g; chain; chain = chain->base_graph)
+		g->topo_levels = &topo_levels;
 
 	if (flags & COMMIT_GRAPH_WRITE_BLOOM_FILTERS)
 		ctx.changed_paths = 1;
 	if (!(flags & COMMIT_GRAPH_NO_WRITE_BLOOM_FILTERS)) {
-		struct commit_graph *g;
-
-		g = ctx.r->objects->commit_graph;
-
 		/* We have changed-paths already. Keep them in the next graph */
 		if (g && g->bloom_filter_settings) {
 			ctx.changed_paths = 1;
@@ -2589,36 +2582,28 @@ int write_commit_graph(struct odb_source *source,
 	bloom_settings.hash_version = bloom_settings.hash_version == 2 ? 2 : 1;
 
 	if (ctx.split) {
-		struct commit_graph *g = ctx.r->objects->commit_graph;
-
-		while (g) {
+		for (struct commit_graph *chain = g; chain; chain = chain->base_graph)
 			ctx.num_commit_graphs_before++;
-			g = g->base_graph;
-		}
 
 		if (ctx.num_commit_graphs_before) {
 			ALLOC_ARRAY(ctx.commit_graph_filenames_before, ctx.num_commit_graphs_before);
 			i = ctx.num_commit_graphs_before;
-			g = ctx.r->objects->commit_graph;
 
-			while (g) {
-				ctx.commit_graph_filenames_before[--i] = xstrdup(g->filename);
-				g = g->base_graph;
-			}
+			for (struct commit_graph *chain = g; chain; chain = chain->base_graph)
+				ctx.commit_graph_filenames_before[--i] = xstrdup(chain->filename);
 		}
 
 		if (ctx.opts)
 			replace = ctx.opts->split_flags & COMMIT_GRAPH_SPLIT_REPLACE;
 	}
 
-	ctx.approx_nr_objects = repo_approximate_object_count(the_repository);
+	ctx.approx_nr_objects = repo_approximate_object_count(r);
 
-	if (ctx.append && ctx.r->objects->commit_graph) {
-		struct commit_graph *g = ctx.r->objects->commit_graph;
+	if (ctx.append && g) {
 		for (i = 0; i < g->num_commits; i++) {
 			struct object_id oid;
-			oidread(&oid, g->chunk_oid_lookup + st_mult(g->hash_len, i),
-				the_repository->hash_algo);
+			oidread(&oid, g->chunk_oid_lookup + st_mult(g->hash_algo->rawsz, i),
+				r->hash_algo);
 			oid_array_append(&ctx.oids, &oid);
 		}
 	}
@@ -2653,14 +2638,15 @@ int write_commit_graph(struct odb_source *source,
 		goto cleanup;
 
 	if (ctx.split) {
-		split_graph_merge_strategy(&ctx);
+		split_graph_merge_strategy(&ctx, g);
 
 		if (!replace)
-			merge_commit_graphs(&ctx);
-	} else
+			merge_commit_graphs(&ctx, g);
+	} else {
 		ctx.num_commit_graphs_after = 1;
+	}
 
-	ctx.trust_generation_numbers = validate_mixed_generation_chain(ctx.r->objects->commit_graph);
+	ctx.trust_generation_numbers = validate_mixed_generation_chain(g);
 
 	compute_topological_levels(&ctx);
 	if (ctx.write_generation_data)
@@ -2726,15 +2712,15 @@ static void graph_report(const char *fmt, ...)
 
 static int commit_graph_checksum_valid(struct commit_graph *g)
 {
-	return hashfile_checksum_valid(the_repository->hash_algo,
+	return hashfile_checksum_valid(g->hash_algo,
 				       g->data, g->data_len);
 }
 
-static int verify_one_commit_graph(struct repository *r,
-				   struct commit_graph *g,
+static int verify_one_commit_graph(struct commit_graph *g,
 				   struct progress *progress,
 				   uint64_t *seen)
 {
+	struct repository *r = g->odb_source->odb->repo;
 	uint32_t i, cur_fanout_pos = 0;
 	struct object_id prev_oid, cur_oid;
 	struct commit *seen_gen_zero = NULL;
@@ -2748,8 +2734,8 @@ static int verify_one_commit_graph(struct repository *r,
 	for (i = 0; i < g->num_commits; i++) {
 		struct commit *graph_commit;
 
-		oidread(&cur_oid, g->chunk_oid_lookup + st_mult(g->hash_len, i),
-			the_repository->hash_algo);
+		oidread(&cur_oid, g->chunk_oid_lookup + st_mult(g->hash_algo->rawsz, i),
+			g->hash_algo);
 
 		if (i && oidcmp(&prev_oid, &cur_oid) >= 0)
 			graph_report(_("commit-graph has incorrect OID order: %s then %s"),
@@ -2768,7 +2754,7 @@ static int verify_one_commit_graph(struct repository *r,
 		}
 
 		graph_commit = lookup_commit(r, &cur_oid);
-		if (!parse_commit_in_graph_one(r, g, graph_commit))
+		if (!parse_commit_in_graph_one(g, graph_commit))
 			graph_report(_("failed to parse commit %s from commit-graph"),
 				     oid_to_hex(&cur_oid));
 	}
@@ -2793,8 +2779,8 @@ static int verify_one_commit_graph(struct repository *r,
 		timestamp_t generation;
 
 		display_progress(progress, ++(*seen));
-		oidread(&cur_oid, g->chunk_oid_lookup + st_mult(g->hash_len, i),
-			the_repository->hash_algo);
+		oidread(&cur_oid, g->chunk_oid_lookup + st_mult(g->hash_algo->rawsz, i),
+			g->hash_algo);
 
 		graph_commit = lookup_commit(r, &cur_oid);
 		odb_commit = (struct commit *)create_object(r, &cur_oid, alloc_commit_node(r));
@@ -2804,7 +2790,7 @@ static int verify_one_commit_graph(struct repository *r,
 			continue;
 		}
 
-		if (!oideq(&get_commit_tree_in_graph_one(r, g, graph_commit)->object.oid,
+		if (!oideq(&get_commit_tree_in_graph_one(g, graph_commit)->object.oid,
 			   get_commit_tree_oid(odb_commit)))
 			graph_report(_("root tree OID for commit %s in commit-graph is %s != %s"),
 				     oid_to_hex(&cur_oid),
@@ -2822,7 +2808,7 @@ static int verify_one_commit_graph(struct repository *r,
 			}
 
 			/* parse parent in case it is in a base graph */
-			parse_commit_in_graph_one(r, g, graph_parents->item);
+			parse_commit_in_graph_one(g, graph_parents->item);
 
 			if (!oideq(&graph_parents->item->object.oid, &odb_parents->item->object.oid))
 				graph_report(_("commit-graph parent for %s is %s != %s"),
@@ -2882,7 +2868,7 @@ static int verify_one_commit_graph(struct repository *r,
 	return verify_commit_graph_error;
 }
 
-int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)
+int verify_commit_graph(struct commit_graph *g, int flags)
 {
 	struct progress *progress = NULL;
 	int local_error = 0;
@@ -2898,13 +2884,13 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)
 		if (!(flags & COMMIT_GRAPH_VERIFY_SHALLOW))
 			total += g->num_commits_in_base;
 
-		progress = start_progress(the_repository,
+		progress = start_progress(g->odb_source->odb->repo,
 					  _("Verifying commits in commit graph"),
 					  total);
 	}
 
 	for (; g; g = g->base_graph) {
-		local_error |= verify_one_commit_graph(r, g, progress, &seen);
+		local_error |= verify_one_commit_graph(g, progress, &seen);
 		if (flags & COMMIT_GRAPH_VERIFY_SHALLOW)
 			break;
 	}
diff --git a/commit-graph.h b/commit-graph.h
index 78ab7b8..f6a5433 100644
--- a/commit-graph.h
+++ b/commit-graph.h
@@ -21,7 +21,7 @@
  * call this method oustide of a builtin, and only if you know what
  * you are doing!
  */
-void git_test_write_commit_graph_or_die(void);
+void git_test_write_commit_graph_or_die(struct odb_source *source);
 
 struct commit;
 struct bloom_filter_settings;
@@ -32,7 +32,8 @@ struct string_list;
 char *get_commit_graph_filename(struct odb_source *source);
 char *get_commit_graph_chain_filename(struct odb_source *source);
 int open_commit_graph(const char *graph_file, int *fd, struct stat *st);
-int open_commit_graph_chain(const char *chain_file, int *fd, struct stat *st);
+int open_commit_graph_chain(const char *chain_file, int *fd, struct stat *st,
+			    const struct git_hash_algo *hash_algo);
 
 /*
  * Given a commit struct, try to fill the commit struct info, including:
@@ -47,10 +48,9 @@ int open_commit_graph_chain(const char *chain_file, int *fd, struct stat *st);
 int parse_commit_in_graph(struct repository *r, struct commit *item);
 
 /*
- * Fills `*pos` with the graph position of `c`, and returns 1 if `c` is
- * found in the commit-graph belonging to `r`, or 0 otherwise.
- * Initializes the commit-graph belonging to `r` if it hasn't been
- * already.
+ * Fills `*pos` with the graph position of `c`, and returns the graph `c` is
+ * found in, or NULL otherwise. Initializes the commit-graphs belonging to
+ * `r` if it hasn't been already.
  *
  * Note: this is a low-level helper that does not alter any slab data
  * associated with `c`. Useful in circumstances where the slab data is
@@ -58,8 +58,9 @@ int parse_commit_in_graph(struct repository *r, struct commit *item);
  *
  * In most cases, callers should use `parse_commit_in_graph()` instead.
  */
-int repo_find_commit_pos_in_graph(struct repository *r, struct commit *c,
-				  uint32_t *pos);
+struct commit_graph *repo_find_commit_pos_in_graph(struct repository *r,
+						   struct commit *c,
+						   uint32_t *pos);
 
 /*
  * Look up the given commit ID in the commit-graph. This will only return a
@@ -84,7 +85,7 @@ struct commit_graph {
 	const unsigned char *data;
 	size_t data_len;
 
-	unsigned char hash_len;
+	const struct git_hash_algo *hash_algo;
 	unsigned char num_chunks;
 	uint32_t num_commits;
 	struct object_id oid;
@@ -113,14 +114,12 @@ struct commit_graph {
 	struct bloom_filter_settings *bloom_filter_settings;
 };
 
-struct commit_graph *load_commit_graph_one_fd_st(struct repository *r,
-						 int fd, struct stat *st,
-						 struct odb_source *source);
-struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r,
+struct commit_graph *load_commit_graph_one_fd_st(struct odb_source *source,
+						 int fd, struct stat *st);
+struct commit_graph *load_commit_graph_chain_fd_st(struct object_database *odb,
 						   int fd, struct stat *st,
 						   int *incomplete_chain);
-struct commit_graph *read_commit_graph_one(struct repository *r,
-					   struct odb_source *source);
+struct commit_graph *read_commit_graph_one(struct odb_source *source);
 
 struct repo_settings;
 
@@ -128,7 +127,7 @@ struct repo_settings;
  * Callers should initialize the repo_settings with prepare_repo_settings()
  * prior to calling parse_commit_graph().
  */
-struct commit_graph *parse_commit_graph(struct repo_settings *s,
+struct commit_graph *parse_commit_graph(struct repository *r,
 					void *graph_map, size_t graph_size);
 
 /*
@@ -184,7 +183,7 @@ int write_commit_graph(struct odb_source *source,
 
 #define COMMIT_GRAPH_VERIFY_SHALLOW	(1 << 0)
 
-int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags);
+int verify_commit_graph(struct commit_graph *g, int flags);
 
 void close_commit_graph(struct object_database *);
 void free_commit_graph(struct commit_graph *);
diff --git a/commit.c b/commit.c
index d1e1061..16d91b2 100644
--- a/commit.c
+++ b/commit.c
@@ -1039,7 +1039,8 @@ static void add_one_commit(struct object_id *oid, struct rev_collect *revs)
 	commit->object.flags |= TMP_MARK;
 }
 
-static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid,
+static int collect_one_reflog_ent(const char *refname UNUSED,
+				  struct object_id *ooid, struct object_id *noid,
 				  const char *ident UNUSED,
 				  timestamp_t timestamp UNUSED, int tz UNUSED,
 				  const char *message UNUSED, void *cbdata)
diff --git a/config.c b/config.c
index 97ffef4..71b136b 100644
--- a/config.c
+++ b/config.c
@@ -8,9 +8,11 @@
 
 #include "git-compat-util.h"
 #include "abspath.h"
+#include "advice.h"
 #include "date.h"
 #include "branch.h"
 #include "config.h"
+#include "dir.h"
 #include "parse.h"
 #include "convert.h"
 #include "environment.h"
@@ -629,31 +631,28 @@ int git_config_parse_parameter(const char *text,
 			       config_fn_t fn, void *data)
 {
 	const char *value;
-	struct strbuf **pair;
+	struct string_list pair = STRING_LIST_INIT_DUP;
 	int ret;
 	struct key_value_info kvi = KVI_INIT;
 
 	kvi_from_param(&kvi);
 
-	pair = strbuf_split_str(text, '=', 2);
-	if (!pair[0])
+	string_list_split(&pair, text, "=", 1);
+	if (!pair.nr)
 		return error(_("bogus config parameter: %s"), text);
 
-	if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=') {
-		strbuf_setlen(pair[0], pair[0]->len - 1);
-		value = pair[1] ? pair[1]->buf : "";
-	} else {
+	if (pair.nr == 1)
 		value = NULL;
-	}
+	else
+		value = pair.items[1].string;
 
-	strbuf_trim(pair[0]);
-	if (!pair[0]->len) {
-		strbuf_list_free(pair);
+	if (!*pair.items[0].string) {
+		string_list_clear(&pair, 0);
 		return error(_("bogus config parameter: %s"), text);
 	}
 
-	ret = config_parse_pair(pair[0]->buf, value, &kvi, fn, data);
-	strbuf_list_free(pair);
+	ret = config_parse_pair(pair.items[0].string, value, &kvi, fn, data);
+	string_list_clear(&pair, 0);
 	return ret;
 }
 
@@ -1279,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;
 }
 
@@ -1951,10 +1962,290 @@ int git_configset_get_pathname(struct config_set *set, const char *key, char **d
 		return 1;
 }
 
+struct comment_char_config {
+	unsigned last_key_id;
+	bool auto_set;
+	bool auto_set_in_file;
+	struct strintmap key_flags;
+	size_t alloc, nr;
+	struct comment_char_config_item {
+		unsigned key_id;
+		char *path;
+		enum config_scope scope;
+	} *item;
+};
+
+#define COMMENT_CHAR_CFG_INIT {			\
+		.key_flags = STRINTMAP_INIT,	\
+	}
+
+static void comment_char_config_release(struct comment_char_config *config)
+{
+	strintmap_clear(&config->key_flags);
+	for (size_t i = 0; i < config->nr; i++)
+		free(config->item[i].path);
+	free(config->item);
+}
+
+/* Used to track whether the key occurs more than once in a given file */
+#define KEY_SEEN_ONCE 1u
+#define KEY_SEEN_TWICE 2u
+#define COMMENT_KEY_SHIFT(id) (2 * (id))
+#define COMMENT_KEY_MASK(id) (3u << COMMENT_KEY_SHIFT(id))
+
+static void set_comment_key_flags(struct comment_char_config *config,
+				  const char *path, unsigned id, unsigned value)
+{
+	unsigned old = strintmap_get(&config->key_flags, path);
+	unsigned new = (old & ~COMMENT_KEY_MASK(id)) |
+				value << COMMENT_KEY_SHIFT(id);
+
+	strintmap_set(&config->key_flags, path, new);
+}
+
+static unsigned get_comment_key_flags(struct comment_char_config *config,
+				      const char *path, unsigned id)
+{
+	unsigned value = strintmap_get(&config->key_flags, path);
+
+	return (value & COMMENT_KEY_MASK(id)) >> COMMENT_KEY_SHIFT(id);
+}
+
+static const char *comment_key_name(unsigned id)
+{
+	static const char *name[] = {
+		"core.commentChar",
+		"core.commentString",
+	};
+
+	if (id >= ARRAY_SIZE(name))
+		BUG("invalid comment key id");
+
+	return name[id];
+}
+
+static void comment_char_callback(const char *key, const char *value,
+				  const struct config_context *ctx, void *data)
+{
+	struct comment_char_config *config = data;
+	const struct key_value_info *kvi = ctx->kvi;
+	unsigned key_id;
+
+	if (!strcmp(key, "core.commentchar"))
+		key_id = 0;
+	else if (!strcmp(key, "core.commentstring"))
+		key_id = 1;
+	else
+		return;
+
+	config->last_key_id = key_id;
+	config->auto_set = value && !strcmp(value, "auto");
+	if (kvi->origin_type != CONFIG_ORIGIN_FILE) {
+		return;
+	} else if (get_comment_key_flags(config, kvi->filename, key_id)) {
+		set_comment_key_flags(config, kvi->filename, key_id,
+				      KEY_SEEN_TWICE);
+	} else {
+		struct comment_char_config_item *item;
+
+		ALLOC_GROW_BY(config->item, config->nr, 1, config->alloc);
+		item = &config->item[config->nr - 1];
+		item->key_id = key_id;
+		item->scope = kvi->scope;
+		item->path = xstrdup(kvi->filename);
+		set_comment_key_flags(config, kvi->filename, key_id,
+				      KEY_SEEN_ONCE);
+	}
+	config->auto_set_in_file = config->auto_set;
+}
+
+static void add_config_scope_arg(struct repository *repo, struct strbuf *buf,
+				 struct comment_char_config_item *item)
+{
+	char *global_config = git_global_config();
+	char *system_config = git_system_config();
+
+	if (item->scope == CONFIG_SCOPE_SYSTEM && access(item->path, W_OK)) {
+		/*
+		 * If the user cannot write to the system config recommend
+		 * setting the global config instead.
+		 */
+		strbuf_addstr(buf, "--global ");
+	} else if (fspatheq(item->path, system_config)) {
+		strbuf_addstr(buf, "--system ");
+	} else if (fspatheq(item->path, global_config)) {
+		strbuf_addstr(buf, "--global ");
+	} else if (fspatheq(item->path,
+			    mkpath("%s/config",
+				   repo_get_git_dir(repo)))) {
+		; /* --local is the default */
+	} else if (fspatheq(item->path,
+			    mkpath("%s/config.worktree",
+				   repo_get_common_dir(repo)))) {
+		strbuf_addstr(buf, "--worktree ");
+	} else {
+		const char *path = item->path;
+		const char *home = getenv("HOME");
+
+		strbuf_addstr(buf, "--file ");
+		if (home && !fspathncmp(path, home, strlen(home))) {
+			path += strlen(home);
+			if (!fspathncmp(path, "/", 1))
+				path++;
+			strbuf_addstr(buf, "~/");
+		}
+		sq_quote_buf_pretty(buf, path);
+		strbuf_addch(buf, ' ');
+	}
+
+	free(global_config);
+	free(system_config);
+}
+
+static bool can_unset_comment_char_config(struct comment_char_config *config)
+{
+	for (size_t i = 0; i < config->nr; i++) {
+		struct comment_char_config_item *item = &config->item[i];
+
+		if (item->scope == CONFIG_SCOPE_SYSTEM &&
+		    access(item->path, W_OK))
+			return false;
+	}
+
+	return true;
+}
+
+static void add_unset_auto_comment_char_advice(struct repository *repo,
+					       struct comment_char_config *config)
+{
+	struct strbuf buf = STRBUF_INIT;
+
+	if (!can_unset_comment_char_config(config))
+		return;
+
+	for (size_t i = 0; i < config->nr; i++) {
+		struct comment_char_config_item *item = &config->item[i];
+
+		strbuf_addstr(&buf, "    git config unset ");
+		add_config_scope_arg(repo, &buf, item);
+		if (get_comment_key_flags(config, item->path, item->key_id) == KEY_SEEN_TWICE)
+			strbuf_addstr(&buf, "--all ");
+		strbuf_addf(&buf, "%s\n", comment_key_name(item->key_id));
+	}
+	advise(_("\nTo use the default comment string (#) please run\n\n%s"),
+	       buf.buf);
+	strbuf_release(&buf);
+}
+
+static void add_comment_char_advice(struct repository *repo,
+				    struct comment_char_config *config)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct comment_char_config_item *item;
+	/* TRANSLATORS this is a place holder for the value of core.commentString */
+	const char *placeholder = _("<comment string>");
+
+	/*
+	 * If auto is set in the last file that we saw advise the user how to
+	 * update their config.
+	 */
+	if (!config->auto_set_in_file)
+		return;
+
+	add_unset_auto_comment_char_advice(repo, config);
+	item = &config->item[config->nr - 1];
+	strbuf_reset(&buf);
+	strbuf_addstr(&buf, "    git config set ");
+	add_config_scope_arg(repo, &buf, item);
+	strbuf_addf(&buf, "%s %s\n", comment_key_name(item->key_id),
+		    placeholder);
+	advise(_("\nTo set a custom comment string please run\n\n"
+		 "%s\nwhere '%s' is the string you wish to use.\n"),
+	       buf.buf, placeholder);
+	strbuf_release(&buf);
+}
+
+#undef KEY_SEEN_ONCE
+#undef KEY_SEEN_TWICE
+#undef COMMENT_KEY_SHIFT
+#undef COMMENT_KEY_MASK
+
+struct repo_config {
+	struct repository *repo;
+	struct comment_char_config comment_char_config;
+};
+
+#define REPO_CONFIG_INIT(repo_) {				\
+		.comment_char_config = COMMENT_CHAR_CFG_INIT,	\
+		.repo = repo_,					\
+	};
+
+static void repo_config_release(struct repo_config *config)
+{
+	comment_char_config_release(&config->comment_char_config);
+}
+
+#ifdef WITH_BREAKING_CHANGES
+static void check_auto_comment_char_config(struct repository *repo,
+					   struct comment_char_config *config)
+{
+	if (!config->auto_set)
+		return;
+
+	die_message(_("Support for '%s=auto' has been removed in Git 3.0"),
+		    comment_key_name(config->last_key_id));
+	add_comment_char_advice(repo, config);
+	die(NULL);
+}
+#else
+static void check_auto_comment_char_config(struct repository *repo,
+					   struct comment_char_config *config)
+{
+	extern bool warn_on_auto_comment_char;
+	const char *DEPRECATED_CONFIG_ENV =
+				"GIT_AUTO_COMMENT_CHAR_CONFIG_WARNING_GIVEN";
+
+	if (!config->auto_set || !warn_on_auto_comment_char)
+		return;
+
+	/*
+	 * Use an environment variable to ensure that subprocesses do not repeat
+	 * the warning.
+	 */
+	if (git_env_bool(DEPRECATED_CONFIG_ENV, false))
+		return;
+
+	setenv(DEPRECATED_CONFIG_ENV, "true", true);
+
+	warning(_("Support for '%s=auto' is deprecated and will be removed in "
+		  "Git 3.0"), comment_key_name(config->last_key_id));
+	add_comment_char_advice(repo, config);
+}
+#endif /* WITH_BREAKING_CHANGES */
+
+static void check_deprecated_config(struct repo_config *config)
+{
+	if (!config->repo->check_deprecated_config)
+			return;
+
+	check_auto_comment_char_config(config->repo,
+				       &config->comment_char_config);
+}
+
+static int repo_config_callback(const char *key, const char *value,
+				const struct config_context *ctx, void *data)
+{
+	struct repo_config *config = data;
+
+	comment_char_callback(key, value, ctx, &config->comment_char_config);
+	return config_set_callback(key, value, ctx, config->repo->config);
+}
+
 /* Functions use to read configuration from a repository */
 static void repo_read_config(struct repository *repo)
 {
 	struct config_options opts = { 0 };
+	struct repo_config config = REPO_CONFIG_INIT(repo);
 
 	opts.respect_includes = 1;
 	opts.commondir = repo->commondir;
@@ -1966,8 +2257,8 @@ static void repo_read_config(struct repository *repo)
 		git_configset_clear(repo->config);
 
 	git_configset_init(repo->config);
-	if (config_with_options(config_set_callback, repo->config, NULL,
-				repo, &opts) < 0)
+	if (config_with_options(repo_config_callback, &config, NULL, repo,
+				&opts) < 0)
 		/*
 		 * config_with_options() normally returns only
 		 * zero, as most errors are fatal, and
@@ -1980,6 +2271,8 @@ static void repo_read_config(struct repository *repo)
 		 * immediately.
 		 */
 		die(_("unknown error occurred while reading the configuration files"));
+	check_deprecated_config(&config);
+	repo_config_release(&config);
 }
 
 static void git_config_check_init(struct repository *repo)
@@ -2667,6 +2960,14 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,
 	char *contents = NULL;
 	size_t contents_sz;
 	struct config_store_data store = CONFIG_STORE_INIT;
+	bool saved_check_deprecated_config = r->check_deprecated_config;
+
+	/*
+	 * Do not warn or die if there are deprecated config settings as
+	 * we want the user to be able to change those settings by running
+	 * "git config".
+	 */
+	r->check_deprecated_config = false;
 
 	validate_comment_string(comment);
 
@@ -2898,6 +3199,7 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,
 	if (in_fd >= 0)
 		close(in_fd);
 	config_store_data_clear(&store);
+	r->check_deprecated_config = saved_check_deprecated_config;
 	return ret;
 
 write_err_out:
diff --git a/connect.c b/connect.c
index 34cac24..8352b71 100644
--- a/connect.c
+++ b/connect.c
@@ -407,7 +407,7 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list,
 	 * name.  Subsequent fields (symref-target and peeled) are optional and
 	 * don't have a particular order.
 	 */
-	if (string_list_split(&line_sections, line, ' ', -1) < 2) {
+	if (string_list_split(&line_sections, line, " ", -1) < 2) {
 		ret = 0;
 		goto out;
 	}
diff --git a/connected.c b/connected.c
index 18c1324..b288a18 100644
--- a/connected.c
+++ b/connected.c
@@ -72,11 +72,12 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
 		 * Before checking for promisor packs, be sure we have the
 		 * latest pack-files loaded into memory.
 		 */
-		reprepare_packed_git(the_repository);
+		odb_reprepare(the_repository->objects);
 		do {
+			struct packfile_store *packs = the_repository->objects->packfiles;
 			struct packed_git *p;
 
-			for (p = get_all_packs(the_repository); p; p = p->next) {
+			for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
 				if (!p->pack_promisor)
 					continue;
 				if (find_pack_entry_one(oid, p))
diff --git a/contrib/contacts/meson.build b/contrib/contacts/meson.build
index 73d82df..c8fdb35 100644
--- a/contrib/contacts/meson.build
+++ b/contrib/contacts/meson.build
@@ -20,7 +20,7 @@
     output: 'git-contacts.xml',
   )
 
-  custom_target(
+  doc_targets += custom_target(
     command: [
       xmlto,
       '-m', '@INPUT@',
@@ -39,7 +39,7 @@
 endif
 
 if get_option('docs').contains('html')
-  custom_target(
+  doc_targets += custom_target(
     command: asciidoc_common_options + [
       '--backend=' + asciidoc_html,
       '--doctype=manpage',
diff --git a/contrib/diff-highlight/README b/contrib/diff-highlight/README
index d4c2343..1db4440 100644
--- a/contrib/diff-highlight/README
+++ b/contrib/diff-highlight/README
@@ -58,6 +58,14 @@
 	diff = diff-highlight | less
 ---------------------------------------------
 
+If you use the interactive patch mode of `git add -p`, `git checkout
+-p`, etc, you may also want to configure it to be used there:
+
+---------------------------------------------
+[interactive]
+        diffFilter = diff-highlight
+---------------------------------------------
+
 
 Color Config
 ------------
diff --git a/contrib/git-jump/git-jump b/contrib/git-jump/git-jump
index 3f69675..8d1d5d7 100755
--- a/contrib/git-jump/git-jump
+++ b/contrib/git-jump/git-jump
@@ -44,7 +44,7 @@
 mode_diff() {
 	git diff --no-prefix --relative "$@" |
 	perl -ne '
-	if (m{^\+\+\+ (.*)}) { $file = $1 eq "/dev/null" ? undef : $1; next }
+	if (m{^\+\+\+ (.*?)\t?$}) { $file = $1 eq "/dev/null" ? undef : $1; next }
 	defined($file) or next;
 	if (m/^@@ .*?\+(\d+)/) { $line = $1; next }
 	defined($line) or next;
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index 3fddba7..17106d1 100755
--- a/contrib/subtree/git-subtree.sh
+++ b/contrib/subtree/git-subtree.sh
@@ -785,20 +785,40 @@
 		die "fatal: '$1' does not look like a ref"
 }
 
-# Usage: check if a commit from another subtree should be
+# Usage: should_ignore_subtree_split_commit REV
+#
+# Check if REV is a commit from another subtree and should be
 # ignored from processing for splits
 should_ignore_subtree_split_commit () {
 	assert test $# = 1
-	local rev="$1"
-	if test -n "$(git log -1 --grep="git-subtree-dir:" $rev)"
+
+	git show \
+		--no-patch \
+		--no-show-signature \
+		--format='%(trailers:key=git-subtree-dir,key=git-subtree-mainline)' \
+		"$1" |
+	(
+	have_mainline=
+	subtree_dir=
+
+	while read -r trailer val
+	do
+		case "$trailer" in
+		git-subtree-dir:)
+			subtree_dir="${val%/}" ;;
+		git-subtree-mainline:)
+			have_mainline=y ;;
+		esac
+	done
+
+	if test -n "${subtree_dir}" &&
+		test -z "${have_mainline}" &&
+		test "${subtree_dir}" != "$arg_prefix"
 	then
-		if test -z "$(git log -1 --grep="git-subtree-mainline:" $rev)" &&
-			test -z "$(git log -1 --grep="git-subtree-dir: $arg_prefix$" $rev)"
-		then
-			return 0
-		fi
+		return 0
 	fi
 	return 1
+	)
 }
 
 # Usage: process_split_commit REV PARENTS
diff --git a/contrib/subtree/meson.build b/contrib/subtree/meson.build
index 98dd8e0..46cdbcc 100644
--- a/contrib/subtree/meson.build
+++ b/contrib/subtree/meson.build
@@ -38,7 +38,7 @@
     output: 'git-subtree.xml',
   )
 
-  custom_target(
+  doc_targets += custom_target(
     command: [
       xmlto,
       '-m', '@INPUT@',
@@ -57,7 +57,7 @@
 endif
 
 if get_option('docs').contains('html')
-  custom_target(
+  doc_targets += custom_target(
     command: asciidoc_common_options + [
       '--backend=' + asciidoc_html,
       '--doctype=manpage',
diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh
index 3edbb33..316dc52 100755
--- a/contrib/subtree/t/t7900-subtree.sh
+++ b/contrib/subtree/t/t7900-subtree.sh
@@ -9,6 +9,9 @@
 and push subcommands of git subtree.
 '
 
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
 TEST_DIRECTORY=$(pwd)/../../../t
 . "$TEST_DIRECTORY"/test-lib.sh
 . "$TEST_DIRECTORY"/lib-gpg.sh
@@ -68,6 +71,33 @@
 	git -C "$1-clone" replace HEAD^2 $new_commit
 }
 
+# test_create_subtree_add REPO ORPHAN PREFIX FILENAME ...
+#
+# Create a simple subtree on a new branch named ORPHAN in REPO.
+# The subtree is then merged into the current branch of REPO,
+# under PREFIX. The generated subtree has has one commit
+# with subject and tag FILENAME with a single file "FILENAME.t"
+#
+# When this method returns:
+# - the current branch of REPO will have file PREFIX/FILENAME.t
+# - REPO will have a branch named ORPHAN with subtree history
+#
+# additional arguments are forwarded to "subtree add"
+test_create_subtree_add () {
+	(
+		cd "$1" &&
+		orphan="$2" &&
+		prefix="$3" &&
+		filename="$4" &&
+		shift 4 &&
+		last="$(git branch --show-current)" &&
+		git switch --orphan "$orphan" &&
+		test_commit "$filename" &&
+		git checkout "$last" &&
+		git subtree add --prefix="$prefix" "$@" "$orphan"
+	)
+}
+
 test_expect_success 'shows short help text for -h' '
 	test_expect_code 129 git subtree -h >out 2>err &&
 	test_must_be_empty err &&
@@ -426,6 +456,47 @@
 		--squash --rejoin -d -m "Sub B Split 1" 2>&1 | grep -w "\[1\]")" = ""
 '
 
+# When subtree split-ing a directory that has other subtree
+# *merges* underneath it, the split must include those subtrees.
+# This test creates a nested subtree, `subA/subB`, and tests
+# that the tree is correct after a subtree split of `subA/`.
+# The test covers:
+# - An initial `subtree add`; and
+# - A follow-up `subtree merge`
+# both with and without `--squashed`.
+for is_squashed in '' 'y'
+do
+	test_expect_success "split keeps nested ${is_squashed:+--squash }subtrees that are part of the split" '
+		subtree_test_create_repo "$test_count" &&
+		(
+			cd "$test_count" &&
+			mkdir subA &&
+			test_commit subA/file1 &&
+			test_create_subtree_add \
+				. mksubtree subA/subB file2 ${is_squashed:+--squash} &&
+			test_path_is_file subA/file1.t &&
+			test_path_is_file subA/subB/file2.t &&
+			git subtree split --prefix=subA --branch=bsplit &&
+			git checkout bsplit &&
+			test_path_is_file file1.t &&
+			test_path_is_file subB/file2.t &&
+			git checkout mksubtree &&
+			git branch -D bsplit &&
+			test_commit file3 &&
+			git checkout main &&
+			git subtree merge \
+				${is_squashed:+--squash} \
+				--prefix=subA/subB mksubtree &&
+			test_path_is_file subA/subB/file3.t &&
+			git subtree split --prefix=subA --branch=bsplit &&
+			git checkout bsplit &&
+			test_path_is_file file1.t &&
+			test_path_is_file subB/file2.t &&
+			test_path_is_file subB/file3.t
+		)
+	'
+done
+
 test_expect_success 'split sub dir/ with --rejoin from scratch' '
 	subtree_test_create_repo "$test_count" &&
 	test_create_commit "$test_count" main1 &&
diff --git a/diff-lib.c b/diff-lib.c
index 244468d..b8f8f3b 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -115,6 +115,9 @@ void run_diff_files(struct rev_info *revs, unsigned int option)
 	uint64_t start = getnanotime();
 	struct index_state *istate = revs->diffopt.repo->index;
 
+	if (revs->diffopt.max_depth_valid)
+		die(_("max-depth is not supported for worktree diffs"));
+
 	diff_set_mnemonic_prefix(&revs->diffopt, "i/", "w/");
 
 	refresh_fsmonitor(istate);
@@ -560,6 +563,8 @@ static int diff_cache(struct rev_info *revs,
 	opts.dst_index = NULL;
 	opts.pathspec = &revs->diffopt.pathspec;
 	opts.pathspec->recursive = 1;
+	if (revs->diffopt.max_depth_valid)
+		die(_("max-depth is not supported for index diffs"));
 
 	init_tree_desc(&t, &tree->object.oid, tree->buffer, tree->size);
 	return unpack_trees(1, &t, &opts);
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 dca87e1..87fa16b 100644
--- a/diff.c
+++ b/diff.c
@@ -57,7 +57,7 @@ static int diff_detect_rename_default;
 static int diff_indent_heuristic = 1;
 static int diff_rename_limit_default = 1000;
 static int diff_suppress_blank_empty;
-static int diff_use_color_default = -1;
+static enum git_colorbool diff_use_color_default = GIT_COLOR_UNKNOWN;
 static int diff_color_moved_default;
 static int diff_color_moved_ws_default;
 static int diff_context_default = 3;
@@ -327,29 +327,23 @@ static unsigned parse_color_moved_ws(const char *arg)
 	struct string_list l = STRING_LIST_INIT_DUP;
 	struct string_list_item *i;
 
-	string_list_split(&l, arg, ',', -1);
+	string_list_split_f(&l, arg, ",", -1, STRING_LIST_SPLIT_TRIM);
 
 	for_each_string_list_item(i, &l) {
-		struct strbuf sb = STRBUF_INIT;
-		strbuf_addstr(&sb, i->string);
-		strbuf_trim(&sb);
-
-		if (!strcmp(sb.buf, "no"))
+		if (!strcmp(i->string, "no"))
 			ret = 0;
-		else if (!strcmp(sb.buf, "ignore-space-change"))
+		else if (!strcmp(i->string, "ignore-space-change"))
 			ret |= XDF_IGNORE_WHITESPACE_CHANGE;
-		else if (!strcmp(sb.buf, "ignore-space-at-eol"))
+		else if (!strcmp(i->string, "ignore-space-at-eol"))
 			ret |= XDF_IGNORE_WHITESPACE_AT_EOL;
-		else if (!strcmp(sb.buf, "ignore-all-space"))
+		else if (!strcmp(i->string, "ignore-all-space"))
 			ret |= XDF_IGNORE_WHITESPACE;
-		else if (!strcmp(sb.buf, "allow-indentation-change"))
+		else if (!strcmp(i->string, "allow-indentation-change"))
 			ret |= COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE;
 		else {
 			ret |= COLOR_MOVED_WS_ERROR;
-			error(_("unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'"), sb.buf);
+			error(_("unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'"), i->string);
 		}
-
-		strbuf_release(&sb);
 	}
 
 	if ((ret & COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) &&
@@ -1678,7 +1672,7 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
 	const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
 	const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
 	const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
-	const char *reverse = ecbdata->color_diff ? GIT_COLOR_REVERSE : "";
+	const char *reverse = want_color(ecbdata->color_diff) ? GIT_COLOR_REVERSE : "";
 	static const char atat[2] = { '@', '@' };
 	const char *cp, *ep;
 	struct strbuf msgbuf = STRBUF_INIT;
@@ -1832,7 +1826,7 @@ static void emit_rewrite_diff(const char *name_a,
 	size_two = fill_textconv(o->repo, textconv_two, two, &data_two);
 
 	memset(&ecbdata, 0, sizeof(ecbdata));
-	ecbdata.color_diff = want_color(o->use_color);
+	ecbdata.color_diff = o->use_color;
 	ecbdata.ws_rule = whitespace_rule(o->repo->index, name_b);
 	ecbdata.opt = o;
 	if (ecbdata.ws_rule & WS_BLANK_AT_EOF) {
@@ -2309,7 +2303,7 @@ static void free_diff_words_data(struct emit_callback *ecbdata)
 	}
 }
 
-const char *diff_get_color(int diff_use_color, enum color_diff ix)
+const char *diff_get_color(enum git_colorbool diff_use_color, enum color_diff ix)
 {
 	if (want_color(diff_use_color))
 		return diff_colors[ix];
@@ -2444,6 +2438,15 @@ static int fn_out_consume(void *priv, char *line, unsigned long len)
 	return 0;
 }
 
+static int quick_consume(void *priv, char *line UNUSED, unsigned long len UNUSED)
+{
+	struct emit_callback *ecbdata = priv;
+	struct diff_options *o = ecbdata->opt;
+
+	o->found_changes = 1;
+	return 1;
+}
+
 static void pprint_rename(struct strbuf *name, const char *a, const char *b)
 {
 	const char *old_name = a;
@@ -3729,7 +3732,7 @@ static void builtin_diff(const char *name_a,
 		if (o->flags.suppress_diff_headers)
 			lbl[0] = NULL;
 		ecbdata.label_path = lbl;
-		ecbdata.color_diff = want_color(o->use_color);
+		ecbdata.color_diff = o->use_color;
 		ecbdata.ws_rule = whitespace_rule(o->repo->index, name_b);
 		if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
 			check_blank_at_eof(&mf1, &mf2, &ecbdata);
@@ -3759,8 +3762,21 @@ static void builtin_diff(const char *name_a,
 
 		if (o->word_diff)
 			init_diff_words_data(&ecbdata, o, one, two);
-		if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume,
-				  &ecbdata, &xpp, &xecfg))
+		if (o->dry_run) {
+			/*
+			 * Unlike the !dry_run case, we need to ignore the
+			 * return value from xdi_diff_outf() here, because
+			 * xdi_diff_outf() takes non-zero return from its
+			 * callback function as a sign of error and returns
+			 * early (which is why we return non-zero from our
+			 * callback, quick_consume()).  Unfortunately,
+			 * xdi_diff_outf() signals an error by returning
+			 * non-zero.
+			 */
+			xdi_diff_outf(&mf1, &mf2, NULL, quick_consume,
+				      &ecbdata, &xpp, &xecfg);
+		} else if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume,
+					 &ecbdata, &xpp, &xecfg))
 			die("unable to generate diff for %s", one->path);
 		if (o->word_diff)
 			free_diff_words_data(&ecbdata);
@@ -4481,7 +4497,7 @@ static void fill_metainfo(struct strbuf *msg,
 			  struct diff_options *o,
 			  struct diff_filepair *p,
 			  int *must_show_header,
-			  int use_color)
+			  enum git_colorbool use_color)
 {
 	const char *set = diff_get_color(use_color, DIFF_METAINFO);
 	const char *reset = diff_get_color(use_color, DIFF_RESET);
@@ -4580,7 +4596,7 @@ static void run_diff_cmd(const struct external_diff *pgm,
 		 */
 		fill_metainfo(msg, name, other, one, two, o, p,
 			      &must_show_header,
-			      want_color(o->use_color) && !pgm);
+			      pgm ? GIT_COLOR_NEVER : o->use_color);
 		xfrm_msg = msg->len ? msg->buf : NULL;
 	}
 
@@ -4979,8 +4995,7 @@ void diff_setup_done(struct diff_options *options)
 	if (options->flags.follow_renames)
 		diff_check_follow_pathspec(&options->pathspec, 1);
 
-	if (!options->use_color ||
-	    (options->flags.allow_external && external_diff()))
+	if (options->flags.allow_external && external_diff())
 		options->color_moved = 0;
 
 	if (options->filter_not) {
@@ -4988,6 +5003,9 @@ void diff_setup_done(struct diff_options *options)
 			options->filter = ~filter_bit[DIFF_STATUS_FILTER_AON];
 		options->filter &= ~options->filter_not;
 	}
+
+	if (options->pathspec.has_wildcard && options->max_depth_valid)
+		die("max-depth cannot be used with wildcard pathspecs");
 }
 
 int parse_long_opt(const char *opt, const char **argv,
@@ -5259,7 +5277,7 @@ static int diff_opt_color_words(const struct option *opt,
 	struct diff_options *options = opt->value;
 
 	BUG_ON_OPT_NEG(unset);
-	options->use_color = 1;
+	options->use_color = GIT_COLOR_ALWAYS;
 	options->word_diff = DIFF_WORDS_COLOR;
 	options->word_regex = arg;
 	return 0;
@@ -5581,7 +5599,7 @@ static int diff_opt_word_diff(const struct option *opt,
 		if (!strcmp(arg, "plain"))
 			options->word_diff = DIFF_WORDS_PLAIN;
 		else if (!strcmp(arg, "color")) {
-			options->use_color = 1;
+			options->use_color = GIT_COLOR_ALWAYS;
 			options->word_diff = DIFF_WORDS_COLOR;
 		}
 		else if (!strcmp(arg, "porcelain"))
@@ -5622,6 +5640,23 @@ static int diff_opt_rotate_to(const struct option *opt, const char *arg, int uns
 	return 0;
 }
 
+static int diff_opt_max_depth(const struct option *opt,
+			      const char *arg, int unset)
+{
+	struct diff_options *options = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+
+	if (!git_parse_int(arg, &options->max_depth))
+		return error(_("invalid value for '%s': '%s'"),
+			     "--max-depth", arg);
+
+	options->flags.recursive = 1;
+	options->max_depth_valid = options->max_depth >= 0;
+
+	return 0;
+}
+
 /*
  * Consider adding new flags to __git_diff_common_options
  * in contrib/completion/git-completion.bash
@@ -5894,6 +5929,10 @@ struct option *add_diff_options(const struct option *opts,
 		OPT_CALLBACK_F(0, "diff-filter", options, N_("[(A|C|D|M|R|T|U|X|B)...[*]]"),
 			       N_("select files by diff type"),
 			       PARSE_OPT_NONEG, diff_opt_diff_filter),
+		OPT_CALLBACK_F(0, "max-depth", options, N_("<depth>"),
+			       N_("maximum tree depth to recurse"),
+			       PARSE_OPT_NONEG, diff_opt_max_depth),
+
 		{
 			.type = OPTION_CALLBACK,
 			.long_name = "output",
@@ -6150,6 +6189,22 @@ static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
 	run_diff(p, o);
 }
 
+/* return 1 if any change is found; otherwise, return 0 */
+static int diff_flush_patch_quietly(struct diff_filepair *p, struct diff_options *o)
+{
+	int saved_dry_run = o->dry_run;
+	int saved_found_changes = o->found_changes;
+	int ret;
+
+	o->dry_run = 1;
+	o->found_changes = 0;
+	diff_flush_patch(p, o);
+	ret = o->found_changes;
+	o->dry_run = saved_dry_run;
+	o->found_changes |= saved_found_changes;
+	return ret;
+}
+
 static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o,
 			    struct diffstat_t *diffstat)
 {
@@ -6677,7 +6732,7 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
 	if (WSEH_NEW & WS_RULE_MASK)
 		BUG("WS rules bit mask overlaps with diff symbol flags");
 
-	if (o->color_moved)
+	if (o->color_moved && want_color(o->use_color))
 		o->emitted_symbols = &esm;
 
 	if (o->additional_path_headers)
@@ -6690,20 +6745,17 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
 	}
 
 	if (o->emitted_symbols) {
-		if (o->color_moved) {
-			struct mem_pool entry_pool;
-			struct moved_entry_list *entry_list;
+		struct mem_pool entry_pool;
+		struct moved_entry_list *entry_list;
 
-			mem_pool_init(&entry_pool, 1024 * 1024);
-			entry_list = add_lines_to_move_detection(o,
-								 &entry_pool);
-			mark_color_as_moved(o, entry_list);
-			if (o->color_moved == COLOR_MOVED_ZEBRA_DIM)
-				dim_moved_lines(o);
+		mem_pool_init(&entry_pool, 1024 * 1024);
+		entry_list = add_lines_to_move_detection(o, &entry_pool);
+		mark_color_as_moved(o, entry_list);
+		if (o->color_moved == COLOR_MOVED_ZEBRA_DIM)
+			dim_moved_lines(o);
 
-			mem_pool_discard(&entry_pool, 0);
-			free(entry_list);
-		}
+		mem_pool_discard(&entry_pool, 0);
+		free(entry_list);
 
 		for (i = 0; i < esm.nr; i++)
 			emit_diff_symbol_from_struct(o, &esm.buf[i]);
@@ -6778,8 +6830,15 @@ void diff_flush(struct diff_options *options)
 			     DIFF_FORMAT_CHECKDIFF)) {
 		for (i = 0; i < q->nr; i++) {
 			struct diff_filepair *p = q->queue[i];
-			if (check_pair_status(p))
-				flush_one_pair(p, options);
+
+			if (!check_pair_status(p))
+				continue;
+
+			if (options->flags.diff_from_contents &&
+			    !diff_flush_patch_quietly(p, options))
+				continue;
+
+			flush_one_pair(p, options);
 		}
 		separator++;
 	}
@@ -6831,19 +6890,10 @@ 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_flush_patch(p, options);
+				diff_flush_patch_quietly(p, options);
 			if (options->found_changes)
 				break;
 		}
diff --git a/diff.h b/diff.h
index 62e5768..2fa256c 100644
--- a/diff.h
+++ b/diff.h
@@ -7,6 +7,7 @@
 #include "hash.h"
 #include "pathspec.h"
 #include "strbuf.h"
+#include "color.h"
 
 struct oidset;
 
@@ -126,6 +127,13 @@ struct diff_flags {
 	unsigned recursive;
 	unsigned tree_in_recursive;
 
+	/*
+	 * Historically diff_tree_combined() overrides recursive to 1. To
+	 * suppress this behavior, set the flag below.
+	 * It has no effect if recursive is already set to 1.
+	 */
+	unsigned no_recursive_diff_tree_combined;
+
 	/* Affects the way how a file that is seemingly binary is treated. */
 	unsigned binary;
 	unsigned text;
@@ -283,7 +291,7 @@ struct diff_options {
 	/* diff-filter bits */
 	unsigned int filter, filter_not;
 
-	int use_color;
+	enum git_colorbool use_color;
 
 	/* Number of context lines to generate in patch output. */
 	int context;
@@ -400,10 +408,20 @@ struct diff_options {
 	#define COLOR_MOVED_WS_ERROR (1<<0)
 	unsigned color_moved_ws_handling;
 
+	bool dry_run;
+
 	struct repository *repo;
 	struct strmap *additional_path_headers;
 
 	int no_free;
+
+	/*
+	 * The value '0' is a valid max-depth (for no recursion), and value '-1'
+	 * also (for unlimited recursion), so the extra "valid" flag is used to
+	 * determined whether the user specified option --max-depth.
+	 */
+	int max_depth;
+	int max_depth_valid;
 };
 
 unsigned diff_filter_bit(char status);
@@ -459,7 +477,7 @@ enum color_diff {
 	DIFF_FILE_NEW_BOLD = 22,
 };
 
-const char *diff_get_color(int diff_use_color, enum color_diff ix);
+const char *diff_get_color(enum git_colorbool diff_use_color, enum color_diff ix);
 #define diff_get_color_opt(o, ix) \
 	diff_get_color((o)->use_color, ix)
 
diff --git a/dir.c b/dir.c
index dfb4d40..0a67a99 100644
--- a/dir.c
+++ b/dir.c
@@ -277,7 +277,7 @@ int within_depth(const char *name, int namelen,
 		if (depth > max_depth)
 			return 0;
 	}
-	return 1;
+	return depth <= max_depth;
 }
 
 /*
@@ -3579,7 +3579,8 @@ static void write_one_dir(struct untracked_cache_dir *untracked,
 	struct stat_data stat_data;
 	struct strbuf *out = &wd->out;
 	unsigned char intbuf[16];
-	unsigned int intlen, value;
+	unsigned int value;
+	uint8_t intlen;
 	int i = wd->index++;
 
 	/*
@@ -3632,7 +3633,7 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra
 	struct ondisk_untracked_cache *ouc;
 	struct write_data wd;
 	unsigned char varbuf[16];
-	int varint_len;
+	uint8_t varint_len;
 	const unsigned hashsz = the_hash_algo->rawsz;
 
 	CALLOC_ARRAY(ouc, 1);
@@ -3738,7 +3739,7 @@ static int read_one_dir(struct untracked_cache_dir **untracked_,
 	struct untracked_cache_dir ud, *untracked;
 	const unsigned char *data = rd->data, *end = rd->end;
 	const unsigned char *eos;
-	unsigned int value;
+	uint64_t value;
 	int i;
 
 	memset(&ud, 0, sizeof(ud));
@@ -3830,7 +3831,8 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long
 	struct read_data rd;
 	const unsigned char *next = data, *end = (const unsigned char *)data + sz;
 	const char *ident;
-	int ident_len;
+	uint64_t ident_len;
+	uint64_t varint_len;
 	ssize_t len;
 	const char *exclude_per_dir;
 	const unsigned hashsz = the_hash_algo->rawsz;
@@ -3867,8 +3869,8 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long
 	if (next >= end)
 		goto done2;
 
-	len = decode_varint(&next);
-	if (next > end || len == 0)
+	varint_len = decode_varint(&next);
+	if (next > end || varint_len == 0)
 		goto done2;
 
 	rd.valid      = ewah_new();
@@ -3877,9 +3879,9 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long
 	rd.data	      = next;
 	rd.end	      = end;
 	rd.index      = 0;
-	ALLOC_ARRAY(rd.ucd, len);
+	ALLOC_ARRAY(rd.ucd, varint_len);
 
-	if (read_one_dir(&uc->root, &rd) || rd.index != len)
+	if (read_one_dir(&uc->root, &rd) || rd.index != varint_len)
 		goto done;
 
 	next = rd.data;
diff --git a/environment.c b/environment.c
index ae1427b..a770b59 100644
--- a/environment.c
+++ b/environment.c
@@ -78,7 +78,6 @@ int grafts_keep_true_parents;
 int core_apply_sparse_checkout;
 int core_sparse_checkout_cone;
 int sparse_expect_files_outside_of_patterns;
-int merge_log_config = -1;
 int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
 unsigned long pack_size_limit_cfg;
 int max_allowed_tree_depth =
@@ -122,7 +121,10 @@ int protect_ntfs = PROTECT_NTFS_DEFAULT;
  */
 const char *comment_line_str = "#";
 char *comment_line_str_to_free;
+#ifndef WITH_BREAKING_CHANGES
 int auto_comment_line_char;
+bool warn_on_auto_comment_char;
+#endif /* !WITH_BREAKING_CHANGES */
 
 /* This is set by setup_git_directory_gently() and/or git_default_config() */
 char *git_work_tree_cfg;
@@ -175,10 +177,10 @@ int have_git_dir(void)
 const char *get_git_namespace(void)
 {
 	static const char *namespace;
-
 	struct strbuf buf = STRBUF_INIT;
-	struct strbuf **components, **c;
 	const char *raw_namespace;
+	struct string_list components = STRING_LIST_INIT_DUP;
+	struct string_list_item *item;
 
 	if (namespace)
 		return namespace;
@@ -190,12 +192,17 @@ const char *get_git_namespace(void)
 	}
 
 	strbuf_addstr(&buf, raw_namespace);
-	components = strbuf_split(&buf, '/');
+
+	string_list_split(&components, buf.buf, "/", -1);
 	strbuf_reset(&buf);
-	for (c = components; *c; c++)
-		if (strcmp((*c)->buf, "/") != 0)
-			strbuf_addf(&buf, "refs/namespaces/%s", (*c)->buf);
-	strbuf_list_free(components);
+
+	for_each_string_list_item(item, &components) {
+		if (item->string[0])
+			strbuf_addf(&buf, "refs/namespaces/%s/", item->string);
+	}
+	string_list_clear(&components, 0);
+
+	strbuf_trim_trailing_dir_sep(&buf);
 	if (check_refname_format(buf.buf, 0))
 		die(_("bad git namespace path \"%s\""), raw_namespace);
 	strbuf_addch(&buf, '/');
@@ -459,16 +466,22 @@ static int git_default_core_config(const char *var, const char *value,
 
 	if (!strcmp(var, "core.commentchar") ||
 	    !strcmp(var, "core.commentstring")) {
-		if (!value)
+		if (!value) {
 			return config_error_nonbool(var);
-		else if (!strcasecmp(value, "auto"))
+#ifndef WITH_BREAKING_CHANGES
+		} else if (!strcasecmp(value, "auto")) {
 			auto_comment_line_char = 1;
-		else if (value[0]) {
+			FREE_AND_NULL(comment_line_str_to_free);
+			comment_line_str = "#";
+#endif /* !WITH_BREAKING_CHANGES */
+		} else if (value[0]) {
 			if (strchr(value, '\n'))
 				return error(_("%s cannot contain newline"), var);
 			comment_line_str = value;
 			FREE_AND_NULL(comment_line_str_to_free);
+#ifndef WITH_BREAKING_CHANGES
 			auto_comment_line_char = 0;
+#endif /* !WITH_BREAKING_CHANGES */
 		} else
 			return error(_("%s must have at least one character"), var);
 		return 0;
diff --git a/environment.h b/environment.h
index 8cfce41..51898c9 100644
--- a/environment.h
+++ b/environment.h
@@ -208,7 +208,10 @@ extern char *excludes_file;
  */
 extern const char *comment_line_str;
 extern char *comment_line_str_to_free;
+#ifndef WITH_BREAKING_CHANGES
 extern int auto_comment_line_char;
+extern bool warn_on_auto_comment_char;
+#endif /* !WITH_BREAKING_CHANGES */
 
 # endif /* USE_THE_REPOSITORY_VARIABLE */
 #endif /* ENVIRONMENT_H */
diff --git a/fetch-pack.c b/fetch-pack.c
index 46c39f8..fe7a84b 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -143,7 +143,8 @@ static struct commit *deref_without_lazy_fetch(const struct object_id *oid,
 	commit = lookup_commit_in_graph(the_repository, oid);
 	if (commit) {
 		if (mark_tags_complete_and_check_obj_db) {
-			if (!odb_has_object(the_repository->objects, oid, 0))
+			if (!odb_has_object(the_repository->objects, oid,
+					    HAS_OBJECT_RECHECK_PACKED))
 				die_in_commit_graph_only(oid);
 		}
 		return commit;
@@ -1914,7 +1915,7 @@ static void fetch_pack_config(void)
 		char *str;
 
 		if (!repo_config_get_string(the_repository, "fetch.uriprotocols", &str) && str) {
-			string_list_split(&uri_protocols, str, ',', -1);
+			string_list_split(&uri_protocols, str, ",", -1);
 			free(str);
 		}
 	}
@@ -1982,7 +1983,7 @@ static void update_shallow(struct fetch_pack_args *args,
 		 * remote is shallow, but this is a clone, there are
 		 * no objects in repo to worry about. Accept any
 		 * shallow points that exist in the pack (iow in repo
-		 * after get_pack() and reprepare_packed_git())
+		 * after get_pack() and odb_reprepare())
 		 */
 		struct oid_array extra = OID_ARRAY_INIT;
 		struct object_id *oid = si->shallow->oid;
@@ -2107,7 +2108,7 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
 		ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought,
 					&si, pack_lockfiles);
 	}
-	reprepare_packed_git(the_repository);
+	odb_reprepare(the_repository->objects);
 
 	if (!args->cloning && args->deepen) {
 		struct check_connected_options opt = CHECK_CONNECTED_INIT;
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c
index 40174ef..c9085ed 100644
--- a/fmt-merge-msg.c
+++ b/fmt-merge-msg.c
@@ -26,13 +26,15 @@ static struct string_list suppress_dest_patterns = STRING_LIST_INIT_DUP;
 int fmt_merge_msg_config(const char *key, const char *value,
 			 const struct config_context *ctx, void *cb)
 {
+	int *merge_log_config = cb;
+
 	if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
 		int is_bool;
-		merge_log_config = git_config_bool_or_int(key, value, ctx->kvi, &is_bool);
-		if (!is_bool && merge_log_config < 0)
+		*merge_log_config = git_config_bool_or_int(key, value, ctx->kvi, &is_bool);
+		if (!is_bool && *merge_log_config < 0)
 			return error("%s: negative length %s", key, value);
-		if (is_bool && merge_log_config)
-			merge_log_config = DEFAULT_MERGE_LOG_LEN;
+		if (is_bool && *merge_log_config)
+			*merge_log_config = DEFAULT_MERGE_LOG_LEN;
 	} else if (!strcmp(key, "merge.branchdesc")) {
 		use_branch_desc = git_config_bool(key, value);
 	} else if (!strcmp(key, "merge.suppressdest")) {
diff --git a/fmt-merge-msg.h b/fmt-merge-msg.h
index 73ca3e4..c066d83 100644
--- a/fmt-merge-msg.h
+++ b/fmt-merge-msg.h
@@ -12,7 +12,6 @@ struct fmt_merge_msg_opts {
 	const char *into_name;
 };
 
-extern int merge_log_config;
 int fmt_merge_msg_config(const char *key, const char *value,
 			 const struct config_context *ctx, void *cb);
 int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
diff --git a/for-each-ref.h b/for-each-ref.h
new file mode 100644
index 0000000..c8d0219
--- /dev/null
+++ b/for-each-ref.h
@@ -0,0 +1,26 @@
+#ifndef FOR_EACH_REF_H
+#define FOR_EACH_REF_H
+
+struct repository;
+
+/*
+ * Shared usage string for options common to git-for-each-ref(1)
+ * and git-refs-list(1). The command-specific part (e.g., "git refs list ")
+ * must be prepended by the caller.
+ */
+#define COMMON_USAGE_FOR_EACH_REF \
+	"[--count=<count>] [--shell|--perl|--python|--tcl]\n" \
+	"                         [(--sort=<key>)...] [--format=<format>]\n" \
+	"                         [--include-root-refs] [--points-at=<object>]\n" \
+	"                         [--merged[=<object>]] [--no-merged[=<object>]]\n" \
+	"                         [--contains[=<object>]] [--no-contains[=<object>]]\n" \
+	"                         [(--exclude=<pattern>)...] [--start-after=<marker>]\n" \
+	"                         [ --stdin | (<pattern>...)]"
+
+/*
+ * The core logic for for-each-ref and its clones.
+ */
+int for_each_ref_core(int argc, const char **argv, const char *prefix,
+		      struct repository *repo, const char *const *usage);
+
+#endif /* FOR_EACH_REF_H */
diff --git a/fsck.h b/fsck.h
index dd7df3d..759df97 100644
--- a/fsck.h
+++ b/fsck.h
@@ -33,15 +33,27 @@ enum fsck_msg_type {
 	FUNC(BAD_PACKED_REF_ENTRY, ERROR) \
 	FUNC(BAD_PACKED_REF_HEADER, ERROR) \
 	FUNC(BAD_PARENT_SHA1, ERROR) \
+	FUNC(BAD_REFERENT_NAME, ERROR) \
 	FUNC(BAD_REF_CONTENT, ERROR) \
 	FUNC(BAD_REF_FILETYPE, ERROR) \
 	FUNC(BAD_REF_NAME, ERROR) \
-	FUNC(BAD_REFERENT_NAME, ERROR) \
 	FUNC(BAD_TIMEZONE, ERROR) \
 	FUNC(BAD_TREE, ERROR) \
 	FUNC(BAD_TREE_SHA1, ERROR) \
 	FUNC(BAD_TYPE, ERROR) \
 	FUNC(DUPLICATE_ENTRIES, ERROR) \
+	FUNC(GITATTRIBUTES_BLOB, ERROR) \
+	FUNC(GITATTRIBUTES_LARGE, ERROR) \
+	FUNC(GITATTRIBUTES_LINE_LENGTH, ERROR) \
+	FUNC(GITATTRIBUTES_MISSING, ERROR) \
+	FUNC(GITMODULES_BLOB, ERROR) \
+	FUNC(GITMODULES_LARGE, ERROR) \
+	FUNC(GITMODULES_MISSING, ERROR) \
+	FUNC(GITMODULES_NAME, ERROR) \
+	FUNC(GITMODULES_PATH, ERROR) \
+	FUNC(GITMODULES_SYMLINK, ERROR) \
+	FUNC(GITMODULES_UPDATE, ERROR) \
+	FUNC(GITMODULES_URL, ERROR) \
 	FUNC(MISSING_AUTHOR, ERROR) \
 	FUNC(MISSING_COMMITTER, ERROR) \
 	FUNC(MISSING_EMAIL, ERROR) \
@@ -60,39 +72,28 @@ enum fsck_msg_type {
 	FUNC(TREE_NOT_SORTED, ERROR) \
 	FUNC(UNKNOWN_TYPE, ERROR) \
 	FUNC(ZERO_PADDED_DATE, ERROR) \
-	FUNC(GITMODULES_MISSING, ERROR) \
-	FUNC(GITMODULES_BLOB, ERROR) \
-	FUNC(GITMODULES_LARGE, ERROR) \
-	FUNC(GITMODULES_NAME, ERROR) \
-	FUNC(GITMODULES_SYMLINK, ERROR) \
-	FUNC(GITMODULES_URL, ERROR) \
-	FUNC(GITMODULES_PATH, ERROR) \
-	FUNC(GITMODULES_UPDATE, ERROR) \
-	FUNC(GITATTRIBUTES_MISSING, ERROR) \
-	FUNC(GITATTRIBUTES_LARGE, ERROR) \
-	FUNC(GITATTRIBUTES_LINE_LENGTH, ERROR) \
-	FUNC(GITATTRIBUTES_BLOB, ERROR) \
 	/* warnings */ \
+	FUNC(BAD_REFTABLE_TABLE_NAME, WARN) \
 	FUNC(EMPTY_NAME, WARN) \
 	FUNC(FULL_PATHNAME, WARN) \
 	FUNC(HAS_DOT, WARN) \
 	FUNC(HAS_DOTDOT, WARN) \
 	FUNC(HAS_DOTGIT, WARN) \
-	FUNC(NULL_SHA1, WARN) \
-	FUNC(ZERO_PADDED_FILEMODE, WARN) \
-	FUNC(NUL_IN_COMMIT, WARN) \
 	FUNC(LARGE_PATHNAME, WARN) \
+	FUNC(NULL_SHA1, WARN) \
+	FUNC(NUL_IN_COMMIT, WARN) \
+	FUNC(ZERO_PADDED_FILEMODE, WARN) \
 	/* infos (reported as warnings, but ignored by default) */ \
 	FUNC(BAD_FILEMODE, INFO) \
-	FUNC(EMPTY_PACKED_REFS_FILE, INFO) \
-	FUNC(GITMODULES_PARSE, INFO) \
-	FUNC(GITIGNORE_SYMLINK, INFO) \
-	FUNC(GITATTRIBUTES_SYMLINK, INFO) \
-	FUNC(MAILMAP_SYMLINK, INFO) \
 	FUNC(BAD_TAG_NAME, INFO) \
+	FUNC(EMPTY_PACKED_REFS_FILE, INFO) \
+	FUNC(GITATTRIBUTES_SYMLINK, INFO) \
+	FUNC(GITIGNORE_SYMLINK, INFO) \
+	FUNC(GITMODULES_PARSE, INFO) \
+	FUNC(MAILMAP_SYMLINK, INFO) \
 	FUNC(MISSING_TAGGER_ENTRY, INFO) \
-	FUNC(SYMLINK_REF, INFO) \
 	FUNC(REF_MISSING_NEWLINE, INFO) \
+	FUNC(SYMLINK_REF, INFO) \
 	FUNC(SYMREF_TARGET_IS_NOT_A_REF, INFO) \
 	FUNC(TRAILING_REF_CONTENT, INFO) \
 	/* ignored (elevated when requested) */ \
diff --git a/git-compat-util.h b/git-compat-util.h
index 9408f46..398e0fa 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -460,7 +460,7 @@ void warning_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));
 
 void show_usage_if_asked(int ac, const char **av, const char *err);
 
-NORETURN void you_still_use_that(const char *command_name);
+NORETURN void you_still_use_that(const char *command_name, const char *hint);
 
 #ifndef NO_OPENSSL
 #ifdef APPLE_COMMON_CRYPTO
diff --git a/git-curl-compat.h b/git-curl-compat.h
index aa8eed7..659e5a3 100644
--- a/git-curl-compat.h
+++ b/git-curl-compat.h
@@ -46,6 +46,13 @@
 #endif
 
 /**
+ * curl_global_trace() was added in 8.3.0, released September 2023.
+ */
+#if LIBCURL_VERSION_NUM >= 0x080300
+#define GIT_CURL_HAVE_GLOBAL_TRACE 1
+#endif
+
+/**
  * CURLOPT_TCP_KEEPCNT was added in 8.9.0, released in July, 2024.
  */
 #if LIBCURL_VERSION_NUM >= 0x080900
diff --git a/git-gui/Makefile b/git-gui/Makefile
index 27bbe05..69b0b84 100644
--- a/git-gui/Makefile
+++ b/git-gui/Makefile
@@ -186,6 +186,7 @@
 	$(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL_D1)
 	$(QUIET)$(INSTALL_X0)git-gui $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
 	$(QUIET)$(INSTALL_X0)git-gui--askpass $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
+	$(QUIET)$(INSTALL_X0)git-gui--askyesno $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
 	$(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(INSTALL_L0)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L1)'$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' $(INSTALL_L2)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L3) &&) true
 ifdef GITGUI_WINDOWS_WRAPPER
 	$(QUIET)$(INSTALL_R0)git-gui.tcl $(INSTALL_R1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
@@ -200,6 +201,7 @@
 	$(QUIET)$(CLEAN_DST) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
 	$(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui $(REMOVE_F1)
 	$(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui--askpass $(REMOVE_F1)
+	$(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui--askyesno $(REMOVE_F1)
 	$(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/$p $(REMOVE_F1) &&) true
 ifdef GITGUI_WINDOWS_WRAPPER
 	$(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui.tcl $(REMOVE_F1)
diff --git a/git-gui/git-gui--askyesno b/git-gui/git-gui--askyesno
new file mode 100755
index 0000000..142d1bc
--- /dev/null
+++ b/git-gui/git-gui--askyesno
@@ -0,0 +1,63 @@
+#!/bin/sh
+# Tcl ignores the next line -*- tcl -*- \
+exec wish "$0" -- "$@"
+
+# This is an implementation of a simple yes no dialog
+# which is injected into the git commandline by git gui
+# in case a yesno question needs to be answered.
+#
+# The window title, which defaults to "Question?", can be
+# overridden via the optional `--title` command-line
+# option.
+
+set NS {}
+set use_ttk [package vsatisfies [package provide Tk] 8.5]
+if {$use_ttk} {
+	set NS ttk
+}
+
+set title "Question?"
+if {$argc < 1} {
+	puts stderr "Usage: $argv0 <question>"
+	exit 1
+} else {
+	if {$argc > 2 && [lindex $argv 0] == "--title"} {
+		set title [lindex $argv 1]
+		set argv [lreplace $argv 0 1]
+	}
+	set prompt [join $argv " "]
+}
+
+${NS}::frame .t
+${NS}::label .t.m -text $prompt -justify center -width 40
+.t.m configure -wraplength 400
+pack .t.m -side top -fill x -padx 20 -pady 20 -expand 1
+pack .t -side top -fill x -ipadx 20 -ipady 20 -expand 1
+
+${NS}::frame .b
+${NS}::frame .b.left -width 200
+${NS}::button .b.yes -text Yes -command {exit 0}
+${NS}::button .b.no  -text No  -command {exit 1}
+
+pack .b.left -side left -expand 1 -fill x
+pack .b.yes -side left -expand 1
+pack .b.no -side right -expand 1 -ipadx 5
+pack .b -side bottom -fill x -ipadx 20 -ipady 15
+
+bind . <Key-Return> {exit 0}
+bind . <Key-Escape> {exit 1}
+
+if {$::tcl_platform(platform) eq {windows}} {
+	set icopath [file dirname [file normalize $argv0]]
+	if {[file tail $icopath] eq {git-core}} {
+		set icopath [file dirname $icopath]
+	}
+	set icopath [file dirname $icopath]
+	set icopath [file join $icopath share git git-for-windows.ico]
+	if {[file exists $icopath]} {
+		wm iconbitmap . -default $icopath
+	}
+}
+
+wm title . $title
+tk::PlaceWindow .
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index a931d7f..d3d3aa1 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -103,7 +103,6 @@
 	set _path_sep {:}
 }
 
-set _search_path {}
 set _path_seen [dict create]
 foreach p [split $env(PATH) $_path_sep] {
 	# Keep only absolute paths, getting rid of ., empty, etc.
@@ -112,12 +111,9 @@
 	}
 	# Keep only the first occurence of any duplicates.
 	set norm_p [file normalize $p]
-	if {[dict exists $_path_seen $norm_p]} {
-		continue
-	}
 	dict set _path_seen $norm_p 1
-	lappend _search_path $norm_p
 }
+set _search_path [dict keys $_path_seen]
 unset _path_seen
 
 set env(PATH) [join $_search_path $_path_sep]
@@ -583,21 +579,6 @@
 	return [open |$run r]
 }
 
-proc _lappend_nice {cmd_var} {
-	global _nice
-	upvar $cmd_var cmd
-
-	if {![info exists _nice]} {
-		set _nice [_which nice]
-		if {[catch {safe_exec [list $_nice git version]}]} {
-			set _nice {}
-		}
-	}
-	if {$_nice ne {}} {
-		lappend cmd $_nice
-	}
-}
-
 proc git {args} {
 	git_redir $args {}
 }
@@ -631,15 +612,14 @@
 	return [safe_open_command $cmdp $redir]
 }
 
+set _nice [list [_which nice]]
+if {[catch {safe_exec [list {*}$_nice git version]}]} {
+	set _nice {}
+}
+
 proc git_read_nice {cmd} {
-	global _git
-	set opt [list]
-
-	_lappend_nice opt
-
-	set cmdp [concat [list $_git] $cmd]
-
-	return [safe_open_command [concat $opt $cmdp]]
+	set cmdp [list {*}$::_nice $::_git {*}$cmd]
+	return [safe_open_command $cmdp]
 }
 
 proc git_write {cmd} {
@@ -1130,6 +1110,12 @@
 if {![info exists env(SSH_ASKPASS)]} {
 	set env(SSH_ASKPASS) [file join $argv0dir git-gui--askpass]
 }
+if {![info exists env(GIT_ASKPASS)]} {
+	set env(GIT_ASKPASS) [file join $argv0dir git-gui--askpass]
+}
+if {![info exists env(GIT_ASK_YESNO)]} {
+	set env(GIT_ASK_YESNO) [file join $argv0dir git-gui--askyesno]
+}
 unset argv0dir
 
 ######################################################################
diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl
index 7aa09c7..e1d38e5 100644
--- a/git-gui/lib/index.tcl
+++ b/git-gui/lib/index.tcl
@@ -425,6 +425,11 @@
 
 	if {![lock_index begin-update]} return
 
+	# Workaround for Tcl < 9.0: chord namespaces are not obeyed and
+	# operated in the global namespace. This clears an error that could
+	# have been left over from a previous operation.
+	set ::err {}
+
 	# Common "after" functionality that waits until multiple asynchronous
 	# operations are complete (by waiting for them to activate their notes
 	# on the chord).
@@ -432,7 +437,7 @@
 	# The asynchronous operations are each indicated below by a comment
 	# before the code block that starts the async operation.
 	set after_chord [SimpleChord::new {
-		if {[string trim $err] != ""} {
+		if {[info exists err] && [string trim $err] ne ""} {
 			rescan_on_error $err
 		} else {
 			unlock_index
diff --git a/git-send-email.perl b/git-send-email.perl
index 437f8ac..cd4b316 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -62,7 +62,7 @@
     --smtp-user             <str>  * Username for SMTP-AUTH.
     --smtp-pass             <str>  * Password for SMTP-AUTH; not necessary.
     --smtp-encryption       <str>  * tls or ssl; anything else disables.
-    --smtp-ssl                     * Deprecated. Use '--smtp-encryption ssl'.
+    --smtp-ssl                     * Deprecated. Use `--smtp-encryption ssl`.
     --smtp-ssl-cert-path    <str>  * Path to ca-certificates (either directory or file).
                                      Pass an empty string to disable certificate
                                      verification.
@@ -73,6 +73,10 @@
     --no-smtp-auth                 * Disable SMTP authentication. Shorthand for
                                      `--smtp-auth=none`
     --smtp-debug            <0|1>  * Disable, enable Net::SMTP debug.
+    --imap-sent-folder      <str>  * IMAP folder where a copy of the emails should be sent.
+                                     Make sure `git imap-send` is set up to use this feature.
+    --[no-]use-imap-only           * Only copy emails to the IMAP folder specified by
+                                     `--imap-sent-folder` instead of actually sending them.
 
     --batch-size            <int>  * send max <int> message per connection.
     --relogin-delay         <int>  * delay <int> seconds between two successive login.
@@ -200,7 +204,7 @@
 
 # Variables we fill in automatically, or via prompting:
 my (@to,@cc,@xh,$envelope_sender,
-	$initial_in_reply_to,$reply_to,$initial_subject,@files,
+	$initial_in_reply_to,$reply_to,$initial_subject,@files,@imap_copy,
 	$author,$sender,$smtp_authpass,$annotate,$compose,$time);
 # Things we either get from config, *or* are overridden on the
 # command-line.
@@ -277,6 +281,7 @@
 my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path);
 my ($batch_size, $relogin_delay);
 my ($identity, $aliasfiletype, @alias_files, $smtp_domain, $smtp_auth);
+my ($imap_sent_folder);
 my ($confirm);
 my (@suppress_cc);
 my ($auto_8bit_encoding);
@@ -293,6 +298,7 @@
 my $target_xfer_encoding = 'auto';
 my $forbid_sendmail_variables = 1;
 my $outlook_id_fix = 'auto';
+my $use_imap_only = 0;
 
 my %config_bool_settings = (
     "thread" => \$thread,
@@ -309,6 +315,7 @@
     "forbidsendmailvariables" => \$forbid_sendmail_variables,
     "mailmap" => \$mailmap,
     "outlookidfix" => \$outlook_id_fix,
+    "useimaponly" => \$use_imap_only,
 );
 
 my %config_settings = (
@@ -322,6 +329,7 @@
     "smtpauth" => \$smtp_auth,
     "smtpbatchsize" => \$batch_size,
     "smtprelogindelay" => \$relogin_delay,
+    "imapsentfolder" => \$imap_sent_folder,
     "to" => \@config_to,
     "tocmd" => \$to_cmd,
     "cc" => \@config_cc,
@@ -527,6 +535,8 @@
 		    "smtp-domain:s" => \$smtp_domain,
 		    "smtp-auth=s" => \$smtp_auth,
 		    "no-smtp-auth" => sub {$smtp_auth = 'none'},
+		    "imap-sent-folder=s" => \$imap_sent_folder,
+		    "use-imap-only!" => \$use_imap_only,
 		    "annotate!" => \$annotate,
 		    "compose" => \$compose,
 		    "quiet" => \$quiet,
@@ -1678,6 +1688,8 @@
 
 	if ($dry_run) {
 		# We don't want to send the email.
+	} elsif ($use_imap_only) {
+		die __("The destination IMAP folder is not properly defined.") if !defined $imap_sent_folder;
 	} elsif (defined $sendmail_cmd || file_name_is_absolute($smtp_server)) {
 		my $pid = open my $sm, '|-';
 		defined $pid or die $!;
@@ -1829,6 +1841,17 @@
 		print "\n";
 	}
 
+	if ($imap_sent_folder && !$dry_run) {
+		my $imap_header = $header;
+		if (@initial_bcc) {
+			# Bcc is not a part of $header, so we add it here.
+			# This is only for the IMAP copy, not for the actual email
+			# sent to the recipients.
+			$imap_header .= "Bcc: " . join(", ", @initial_bcc) . "\n";
+		}
+		push @imap_copy, "From git-send-email\n$imap_header\n$message";
+	}
+
 	return 1;
 }
 
@@ -1931,6 +1954,9 @@
 					$in_reply_to = $1;
 				}
 			}
+			elsif (/^Reply-To: (.*)/i) {
+				$reply_to = $1;
+			}
 			elsif (/^References: (.*)/i) {
 				if (!$initial_in_reply_to || $thread) {
 					$references = $1;
@@ -2223,6 +2249,19 @@
 
 $smtp->quit if $smtp;
 
+if ($imap_sent_folder && @imap_copy && !$dry_run) {
+	my $imap_input = join("\n", @imap_copy);
+	eval {
+		print "\nStarting git imap-send...\n";
+		my ($fh, $ctx) = Git::command_input_pipe(['imap-send', '-f', $imap_sent_folder]);
+		print $fh $imap_input;
+		Git::command_close_pipe($fh, $ctx);
+		1;
+	} or do {
+		warn "Warning: failed to send messages to IMAP folder $imap_sent_folder: $@";
+	};
+}
+
 sub apply_transfer_encoding {
 	my $message = shift;
 	my $from = shift;
diff --git a/git.c b/git.c
index 83eac0a..c5fad56 100644
--- a/git.c
+++ b/git.c
@@ -28,6 +28,7 @@
 #define NEED_WORK_TREE		(1<<3)
 #define DELAY_PAGER_CONFIG	(1<<4)
 #define NO_PARSEOPT		(1<<5) /* parse-options is not used */
+#define DEPRECATED		(1<<6)
 
 struct cmd_struct {
 	const char *cmd;
@@ -51,7 +52,9 @@ const char git_more_info_string[] =
 
 static int use_pager = -1;
 
-static void list_builtins(struct string_list *list, unsigned int exclude_option);
+static void list_builtins(struct string_list *list,
+			  unsigned int include_option,
+			  unsigned int exclude_option);
 
 static void exclude_helpers_from_list(struct string_list *list)
 {
@@ -88,7 +91,7 @@ static int list_cmds(const char *spec)
 		int len = sep - spec;
 
 		if (match_token(spec, len, "builtins"))
-			list_builtins(&list, 0);
+			list_builtins(&list, 0, 0);
 		else if (match_token(spec, len, "main"))
 			list_all_main_cmds(&list);
 		else if (match_token(spec, len, "others"))
@@ -99,6 +102,8 @@ static int list_cmds(const char *spec)
 			list_aliases(&list);
 		else if (match_token(spec, len, "config"))
 			list_cmds_by_config(&list);
+		else if (match_token(spec, len, "deprecated"))
+			list_builtins(&list, DEPRECATED, 0);
 		else if (len > 5 && !strncmp(spec, "list-", 5)) {
 			struct strbuf sb = STRBUF_INIT;
 
@@ -322,7 +327,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
 			if (!strcmp(cmd, "parseopt")) {
 				struct string_list list = STRING_LIST_INIT_DUP;
 
-				list_builtins(&list, NO_PARSEOPT);
+				list_builtins(&list, 0, NO_PARSEOPT);
 				for (size_t i = 0; i < list.nr; i++)
 					printf("%s ", list.items[i].string);
 				string_list_clear(&list, 0);
@@ -360,7 +365,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
 	return (*argv) - orig_argv;
 }
 
-static int handle_alias(struct strvec *args)
+static int handle_alias(struct strvec *args, struct string_list *expanded_aliases)
 {
 	int envchanged = 0, ret = 0, saved_errno = errno;
 	int count, option_count;
@@ -371,6 +376,8 @@ static int handle_alias(struct strvec *args)
 	alias_command = args->v[0];
 	alias_string = alias_lookup(alias_command);
 	if (alias_string) {
+		struct string_list_item *seen;
+
 		if (args->nr == 2 && !strcmp(args->v[1], "-h"))
 			fprintf_ln(stderr, _("'%s' is aliased to '%s'"),
 				   alias_command, alias_string);
@@ -418,6 +425,25 @@ static int handle_alias(struct strvec *args)
 		if (!strcmp(alias_command, new_argv[0]))
 			die(_("recursive alias: %s"), alias_command);
 
+		string_list_append(expanded_aliases, alias_command);
+		seen = unsorted_string_list_lookup(expanded_aliases,
+						   new_argv[0]);
+
+		if (seen) {
+			struct strbuf sb = STRBUF_INIT;
+			for (size_t i = 0; i < expanded_aliases->nr; i++) {
+				struct string_list_item *item = &expanded_aliases->items[i];
+
+				strbuf_addf(&sb, "\n  %s", item->string);
+				if (item == seen)
+					strbuf_addstr(&sb, " <==");
+				else if (i == expanded_aliases->nr - 1)
+					strbuf_addstr(&sb, " ==>");
+			}
+			die(_("alias loop detected: expansion of '%s' does"
+			      " not terminate:%s"), expanded_aliases->items[0].string, sb.buf);
+		}
+
 		trace_argv_printf(new_argv,
 				  "trace: alias expansion: %s =>",
 				  alias_command);
@@ -445,7 +471,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv, struct
 	const char *prefix;
 	int run_setup = (p->option & (RUN_SETUP | RUN_SETUP_GENTLY));
 
-	help = argc == 2 && !strcmp(argv[1], "-h");
+	help = argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help-all"));
 	if (help && (run_setup & RUN_SETUP))
 		/* demote to GENTLY to allow 'git cmd -h' outside repo */
 		run_setup = RUN_SETUP_GENTLY;
@@ -565,6 +591,7 @@ static struct cmd_struct commands[] = {
 	{ "init", cmd_init_db },
 	{ "init-db", cmd_init_db },
 	{ "interpret-trailers", cmd_interpret_trailers, RUN_SETUP_GENTLY },
+	{ "last-modified", cmd_last_modified, RUN_SETUP },
 	{ "log", cmd_log, RUN_SETUP },
 	{ "ls-files", cmd_ls_files, RUN_SETUP },
 	{ "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
@@ -590,7 +617,7 @@ static struct cmd_struct commands[] = {
 	{ "notes", cmd_notes, RUN_SETUP },
 	{ "pack-objects", cmd_pack_objects, RUN_SETUP },
 #ifndef WITH_BREAKING_CHANGES
-	{ "pack-redundant", cmd_pack_redundant, RUN_SETUP | NO_PARSEOPT },
+	{ "pack-redundant", cmd_pack_redundant, RUN_SETUP | NO_PARSEOPT | DEPRECATED },
 #endif
 	{ "pack-refs", cmd_pack_refs, RUN_SETUP },
 	{ "patch-id", cmd_patch_id, RUN_SETUP_GENTLY | NO_PARSEOPT },
@@ -611,6 +638,7 @@ static struct cmd_struct commands[] = {
 	{ "repack", cmd_repack, RUN_SETUP },
 	{ "replace", cmd_replace, RUN_SETUP },
 	{ "replay", cmd_replay, RUN_SETUP },
+	{ "repo", cmd_repo, RUN_SETUP },
 	{ "rerere", cmd_rerere, RUN_SETUP },
 	{ "reset", cmd_reset, RUN_SETUP },
 	{ "restore", cmd_restore, RUN_SETUP | NEED_WORK_TREE },
@@ -647,7 +675,7 @@ static struct cmd_struct commands[] = {
 	{ "verify-tag", cmd_verify_tag, RUN_SETUP },
 	{ "version", cmd_version },
 #ifndef WITH_BREAKING_CHANGES
-	{ "whatchanged", cmd_whatchanged, RUN_SETUP },
+	{ "whatchanged", cmd_whatchanged, RUN_SETUP | DEPRECATED },
 #endif
 	{ "worktree", cmd_worktree, RUN_SETUP },
 	{ "write-tree", cmd_write_tree, RUN_SETUP },
@@ -668,11 +696,16 @@ int is_builtin(const char *s)
 	return !!get_builtin(s);
 }
 
-static void list_builtins(struct string_list *out, unsigned int exclude_option)
+static void list_builtins(struct string_list *out,
+			  unsigned int include_option,
+			  unsigned int exclude_option)
 {
+	if (include_option && exclude_option)
+		BUG("'include_option' and 'exclude_option' are mutually exclusive");
 	for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
-		if (exclude_option &&
-		    (commands[i].option & exclude_option))
+		if (include_option && !(commands[i].option & include_option))
+			continue;
+		if (exclude_option && (commands[i].option & exclude_option))
 			continue;
 		string_list_append(out, commands[i].cmd);
 	}
@@ -793,14 +826,30 @@ static void execv_dashed_external(const char **argv)
 		exit(128);
 }
 
+static int is_deprecated_command(const char *cmd)
+{
+	struct cmd_struct *builtin = get_builtin(cmd);
+	return builtin && (builtin->option & DEPRECATED);
+}
+
 static int run_argv(struct strvec *args)
 {
 	int done_alias = 0;
-	struct string_list cmd_list = STRING_LIST_INIT_DUP;
-	struct string_list_item *seen;
+	struct string_list expanded_aliases = STRING_LIST_INIT_DUP;
 
 	while (1) {
 		/*
+		 * Allow deprecated commands to be overridden by aliases. This
+		 * creates a seamless path forward for people who want to keep
+		 * using the name after it is gone, but want to skip the
+		 * deprecation complaint in the meantime.
+		 */
+		if (is_deprecated_command(args->v[0]) &&
+		    handle_alias(args, &expanded_aliases)) {
+			done_alias = 1;
+			continue;
+		}
+		/*
 		 * If we tried alias and futzed with our environment,
 		 * it no longer is safe to invoke builtins directly in
 		 * general.  We have to spawn them as dashed externals.
@@ -849,35 +898,17 @@ static int run_argv(struct strvec *args)
 		/* .. then try the external ones */
 		execv_dashed_external(args->v);
 
-		seen = unsorted_string_list_lookup(&cmd_list, args->v[0]);
-		if (seen) {
-			struct strbuf sb = STRBUF_INIT;
-			for (size_t i = 0; i < cmd_list.nr; i++) {
-				struct string_list_item *item = &cmd_list.items[i];
-
-				strbuf_addf(&sb, "\n  %s", item->string);
-				if (item == seen)
-					strbuf_addstr(&sb, " <==");
-				else if (i == cmd_list.nr - 1)
-					strbuf_addstr(&sb, " ==>");
-			}
-			die(_("alias loop detected: expansion of '%s' does"
-			      " not terminate:%s"), cmd_list.items[0].string, sb.buf);
-		}
-
-		string_list_append(&cmd_list, args->v[0]);
-
 		/*
 		 * It could be an alias -- this works around the insanity
 		 * of overriding "git log" with "git show" by having
 		 * alias.log = show
 		 */
-		if (!handle_alias(args))
+		if (!handle_alias(args, &expanded_aliases))
 			break;
 		done_alias = 1;
 	}
 
-	string_list_clear(&cmd_list, 0);
+	string_list_clear(&expanded_aliases, 0);
 
 	return done_alias;
 }
diff --git a/gitk-git/README.md b/gitk-git/README.md
new file mode 100644
index 0000000..2e30746
--- /dev/null
+++ b/gitk-git/README.md
@@ -0,0 +1,93 @@
+Gitk - The Git Repository Browser
+=================================
+
+Gitk is a graphical Git repository browser. It displays the commit
+history of a Git repository as a graph, showing the relationships
+between commits, branches, and tags.
+
+Usage
+=====
+
+To view the history of the current repository:
+```bash
+gitk
+```
+
+To view the history of specific files or directories:
+```bash
+gitk path/to/file
+gitk path/to/directory
+```
+
+To view a specific branch or range of commits:
+```bash
+gitk branch-name
+gitk v1.0..v2.0
+```
+
+For more usage examples and options, see the [gitk manual](https://git-scm.com/docs/gitk).
+
+Building
+========
+
+Gitk is a Tcl/Tk application. It requires Tcl/Tk to be installed on
+your system.
+
+Running directly
+----------------
+
+Gitk can be run from the source directory without installation:
+
+```bash
+./gitk
+```
+
+This allows for quick testing of changes.
+
+Installation
+------------
+
+To install system-wide, you can use either `make` or `meson`:
+
+```bash
+# Install to default location ($HOME/bin)
+make install
+
+# Install to system-wide location
+sudo make install prefix=/usr/local
+
+# Install to custom location
+make install prefix=/opt/gitk
+
+# Using Meson
+meson setup builddir
+meson compile -C builddir
+meson install -C builddir
+```
+
+Both build systems will handle setting the correct Tcl/Tk interpreter
+path and installing translation files.
+
+Contributing
+============
+
+Contributions are welcome! The preferred method for submitting patches
+is via email to the Git mailing list, as this allows for more thorough
+review and broader community feedback. However, GitHub pull requests
+are also accepted.
+
+All commits must be signed off (use `git commit --signoff`) and should
+have commit messages prefixed with `gitk:`.
+
+Email Patches
+-------------
+
+Send patches to git@vger.kernel.org and CC j6t@kdbg.org. See the Git
+project's [patch submission guidelines](https://git-scm.com/docs/SubmittingPatches)
+for detailed instructions on creating and sending patches.
+
+License
+=======
+
+Gitk is distributed under the GNU General Public License, either
+version 2, or (at your option) any later version.
diff --git a/gitk-git/gitk b/gitk-git/gitk
index 3b6acfc..c02db01 100755
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -2215,6 +2215,7 @@
 }
 
 proc setttkstyle {} {
+    global theme
     eval font configure TkDefaultFont [fontflags mainfont]
     eval font configure TkTextFont [fontflags textfont]
     eval font configure TkHeadingFont [fontflags mainfont]
@@ -2224,6 +2225,10 @@
     eval font configure TkIconFont    [fontflags uifont]
     eval font configure TkMenuFont    [fontflags uifont]
     eval font configure TkSmallCaptionFont [fontflags uifont]
+
+    if {[catch {ttk::style theme use $theme} err]} {
+        set theme [ttk::style theme use]
+    }
 }
 
 # Make a menu and submenus.
@@ -2301,6 +2306,11 @@
     return [expr int(-($D / $scroll_D0) * max(1, $kscroll-$koff))]
 }
 
+proc precisescrollval {D {koff 0}} {
+    global kscroll
+    return [expr (-($D / 10.0) * max(1, $kscroll-$koff))]
+}
+
 proc bind_mousewheel {} {
     global canv cflist ctext
     bindall <MouseWheel> {allcanvs yview scroll [scrollval %D] units}
@@ -2319,6 +2329,25 @@
         bind $cflist <Alt-MouseWheel> {$cflist yview scroll [scrollval 5*%D 2] units}
         bind $cflist <Alt-Shift-MouseWheel> break
         bind $canv <Alt-Shift-MouseWheel> {$canv xview scroll [scrollval 5*%D] units}
+
+        bindall <TouchpadScroll> {
+            lassign [tk::PreciseScrollDeltas %D] deltaX deltaY
+            allcanvs yview scroll [precisescrollval $deltaY] units
+        }
+        bind $ctext <TouchpadScroll> {
+            lassign [tk::PreciseScrollDeltas %D] deltaX deltaY
+            $ctext yview scroll [precisescrollval $deltaY 2] units
+            $ctext xview scroll [precisescrollval $deltaX 2] units
+        }
+        bind $cflist <TouchpadScroll> {
+            lassign [tk::PreciseScrollDeltas %D] deltaX deltaY
+            $cflist yview scroll [precisescrollval $deltaY 2] units
+        }
+        bind $canv <TouchpadScroll> {
+            lassign [tk::PreciseScrollDeltas %D] deltaX deltaY
+            $canv xview scroll [precisescrollval $deltaX] units
+            allcanvs yview scroll [precisescrollval $deltaY] units
+        }
     }
 }
 
@@ -2352,7 +2381,6 @@
     global highlight_files gdttype
     global searchstring sstring
     global bgcolor fgcolor bglist fglist diffcolors diffbgcolors selectbgcolor
-    global uifgcolor uifgdisabledcolor
     global filesepbgcolor filesepfgcolor
     global mergecolors foundbgcolor currentsearchhitbgcolor
     global headctxmenu progresscanv progressitem progresscoords statusw
@@ -2471,40 +2499,18 @@
     set sha1entry .tf.bar.sha1
     set entries $sha1entry
     set sha1but .tf.bar.sha1label
-    button $sha1but -text "[mc "Commit ID:"] " -state disabled -relief flat \
+    ttk::button $sha1but -text "[mc "Commit ID:"] " -state disabled \
         -command gotocommit -width 8
-    $sha1but conf -disabledforeground [$sha1but cget -foreground]
     pack .tf.bar.sha1label -side left
     ttk::entry $sha1entry -width $hashlength -font textfont -textvariable sha1string
     trace add variable sha1string write sha1change
     pack $sha1entry -side left -pady 2
 
-    set bm_left_data {
-        #define left_width 16
-        #define left_height 16
-        static unsigned char left_bits[] = {
-        0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
-        0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
-        0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
-    }
-    set bm_right_data {
-        #define right_width 16
-        #define right_height 16
-        static unsigned char right_bits[] = {
-        0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
-        0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
-        0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
-    }
-    image create bitmap bm-left -data $bm_left_data -foreground $uifgcolor
-    image create bitmap bm-left-gray -data $bm_left_data -foreground $uifgdisabledcolor
-    image create bitmap bm-right -data $bm_right_data -foreground $uifgcolor
-    image create bitmap bm-right-gray -data $bm_right_data -foreground $uifgdisabledcolor
-
-    ttk::button .tf.bar.leftbut -command goback -state disabled -width 26
-    .tf.bar.leftbut configure -image [list bm-left disabled bm-left-gray]
+    ttk::button .tf.bar.leftbut -command goback -state disabled 
+    .tf.bar.leftbut configure -text \u2190 -width 3
     pack .tf.bar.leftbut -side left -fill y
-    ttk::button .tf.bar.rightbut -command goforw -state disabled -width 26
-    .tf.bar.rightbut configure -image [list bm-right disabled bm-right-gray]
+    ttk::button .tf.bar.rightbut -command goforw -state disabled 
+    .tf.bar.rightbut configure -text \u2192 -width 3
     pack .tf.bar.rightbut -side left -fill y
 
     ttk::label .tf.bar.rowlabel -text [mc "Row"]
@@ -2535,31 +2541,8 @@
     # build up the bottom bar of upper window
     ttk::label .tf.lbar.flabel -text "[mc "Find"] "
 
-    set bm_down_data {
-        #define down_width 16
-        #define down_height 16
-        static unsigned char down_bits[] = {
-        0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01,
-        0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01,
-        0x87, 0xe1, 0x8e, 0x71, 0x9c, 0x39, 0xb8, 0x1d,
-        0xf0, 0x0f, 0xe0, 0x07, 0xc0, 0x03, 0x80, 0x01};
-    }
-    image create bitmap bm-down -data $bm_down_data -foreground $uifgcolor
-    ttk::button .tf.lbar.fnext -width 26 -command {dofind 1 1}
-    .tf.lbar.fnext configure -image bm-down
-
-    set bm_up_data {
-        #define up_width 16
-        #define up_height 16
-        static unsigned char up_bits[] = {
-        0x80, 0x01, 0xc0, 0x03, 0xe0, 0x07, 0xf0, 0x0f,
-        0xb8, 0x1d, 0x9c, 0x39, 0x8e, 0x71, 0x87, 0xe1,
-        0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01,
-        0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01};
-    }
-    image create bitmap bm-up -data $bm_up_data -foreground $uifgcolor
-    ttk::button .tf.lbar.fprev -width 26 -command {dofind -1 1}
-    .tf.lbar.fprev configure -image bm-up
+    ttk::button .tf.lbar.fnext -command {dofind 1 1} -text \u2193 -width 3
+    ttk::button .tf.lbar.fprev -command {dofind -1 1} -text \u2191 -width 3
 
     ttk::label .tf.lbar.flab2 -text " [mc "commit"] "
 
@@ -2632,7 +2615,7 @@
 
     ttk::label .bleft.mid.labeldiffcontext -text "      [mc "Lines of context"]: "
     pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left -ipadx $wgap
-    spinbox .bleft.mid.diffcontext -width 5 \
+    ttk::spinbox .bleft.mid.diffcontext -width 5 \
         -from 0 -increment 1 -to 10000000 \
         -validate all -validatecommand "diffcontextvalidate %P" \
         -textvariable diffcontextstring
@@ -8886,9 +8869,9 @@
     }
     if {[$sha1but cget -state] == $state} return
     if {$state == "normal"} {
-        $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
+        $sha1but conf -state normal -text "[mc "Goto:"] "
     } else {
-        $sha1but conf -state disabled -relief flat -text "[mc "Commit ID:"] "
+        $sha1but conf -state disabled -text "[mc "Commit ID:"] "
     }
 }
 
@@ -10270,7 +10253,9 @@
         if {![string match "remotes/*" $n] && [string match $reflistfilter $n]} {
             if {[commitinview $headids($n) $curview]} {
                 lappend localrefs [list $n H]
-                if {[info exists upstreamofref($n)] && [commitinview $headids($upstreamofref($n)) $curview]} {
+                if {[info exists upstreamofref($n)] && \
+                        [info exists headids($upstreamofref($n))] && \
+                        [commitinview $headids($upstreamofref($n)) $curview]} {
                     lappend trackedremoterefs [list $upstreamofref($n) R]
                 }
             } else {
@@ -11584,9 +11569,10 @@
     set fontpref($font) [set $font]
     ttk::button $top.${font}but -text $which \
         -command [list choosefont $font $which]
-    ttk::label $top.$font -relief flat -font $font \
-        -text $fontattr($font,family) -justify left
+    ttk::label $top.$font -font $font \
+        -text $fontattr($font,family)
     grid x $top.${font}but $top.$font -sticky w
+    grid configure $top.$font -sticky ew
 }
 
 proc centertext {w} {
@@ -11666,48 +11652,52 @@
 
     ttk::label $page.ldisp -text [mc "Commit list display options"] -font mainfontbold
     grid $page.ldisp - -sticky w -pady 10
+
     ttk::label $page.spacer -text " "
     ttk::label $page.maxwidthl -text [mc "Maximum graph width (lines)"]
-    spinbox $page.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
+    ttk::spinbox $page.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
     grid $page.spacer $page.maxwidthl $page.maxwidth -sticky w
                                          #xgettext:no-tcl-format
     ttk::label $page.maxpctl -text [mc "Maximum graph width (% of pane)"]
-    spinbox $page.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
+    ttk::spinbox $page.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
     grid x $page.maxpctl $page.maxpct -sticky w
+
     ttk::checkbutton $page.showlocal -text [mc "Show local changes"] \
         -variable showlocalchanges
     grid x $page.showlocal -sticky w
+
     ttk::checkbutton $page.hideremotes -text [mc "Hide remote refs"] \
         -variable hideremotes
     grid x $page.hideremotes -sticky w
 
     ttk::entry $page.refstohide -textvariable refstohide
-    ttk::frame $page.refstohidef
-    ttk::label $page.refstohidef.l -text [mc "Refs to hide (space-separated globs)" ]
-    pack $page.refstohidef.l -side left
-    pack configure $page.refstohidef.l -padx 10
-    grid x $page.refstohidef $page.refstohide -sticky ew
+    ttk::label $page.refstohidel -text [mc "Refs to hide (space-separated globs)"]
+    grid x $page.refstohidel $page.refstohide -sticky ew
+    grid configure $page.refstohide -padx {0 5}
 
     ttk::checkbutton $page.autocopy -text [mc "Copy commit ID to clipboard"] \
         -variable autocopy
     grid x $page.autocopy -sticky w
+
     if {[haveselectionclipboard]} {
         ttk::checkbutton $page.autoselect -text [mc "Copy commit ID to X11 selection"] \
             -variable autoselect
         grid x $page.autoselect -sticky w
     }
 
-    spinbox $page.autosellen -from 1 -to $hashlength -width 4 -textvariable autosellen
+    ttk::spinbox $page.autosellen -from 1 -to $hashlength -width 4 -textvariable autosellen
     ttk::label $page.autosellenl -text [mc "Length of commit ID to copy"]
     grid x $page.autosellenl $page.autosellen -sticky w
+
     ttk::label $page.kscroll1 -text [mc "Wheel scrolling multiplier"]
-    spinbox $page.kscroll -from 1 -to 20 -width 4 -textvariable kscroll
+    ttk::spinbox $page.kscroll -from 1 -to 20 -width 4 -textvariable kscroll
     grid x $page.kscroll1 $page.kscroll -sticky w
 
     ttk::label $page.ddisp -text [mc "Diff display options"] -font mainfontbold
     grid $page.ddisp - -sticky w -pady 10
+
     ttk::label $page.tabstopl -text [mc "Tab spacing"]
-    spinbox $page.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
+    ttk::spinbox $page.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
     grid x $page.tabstopl $page.tabstop -sticky w
 
     ttk::label $page.wrapcommentl -text [mc "Wrap comment text"]
@@ -11721,12 +11711,15 @@
     ttk::checkbutton $page.ntag -text [mc "Display nearby tags/heads"] \
         -variable showneartags
     grid x $page.ntag -sticky w
+
     ttk::label $page.maxrefsl -text [mc "Maximum # tags/heads to show"]
-    spinbox $page.maxrefs -from 1 -to 1000 -width 4 -textvariable maxrefs
+    ttk::spinbox $page.maxrefs -from 1 -to 1000 -width 4 -textvariable maxrefs
     grid x $page.maxrefsl $page.maxrefs -sticky w
+
     ttk::checkbutton $page.ldiff -text [mc "Limit diffs to listed paths"] \
         -variable limitdiffs
     grid x $page.ldiff -sticky w
+
     ttk::checkbutton $page.lattr -text [mc "Support per-file encodings"] \
         -variable perfile_attrs
     grid x $page.lattr -sticky w
@@ -11735,76 +11728,109 @@
     ttk::frame $page.extdifff
     ttk::label $page.extdifff.l -text [mc "External diff tool" ]
     ttk::button $page.extdifff.b -text [mc "Choose..."] -command choose_extdiff
-    pack $page.extdifff.l $page.extdifff.b -side left
-    pack configure $page.extdifff.l -padx 10
+    pack $page.extdifff.l -side left
+    pack $page.extdifff.b -side right -padx {0 5}
     grid x $page.extdifff $page.extdifft -sticky ew
+    grid configure $page.extdifft -padx {0 5}
 
     ttk::entry $page.webbrowser -textvariable web_browser
-    ttk::frame $page.webbrowserf
-    ttk::label $page.webbrowserf.l -text [mc "Web browser" ]
-    pack $page.webbrowserf.l -side left
-    pack configure $page.webbrowserf.l -padx 10
-    grid x $page.webbrowserf $page.webbrowser -sticky ew
+    ttk::label $page.webbrowserl -text [mc "Web browser" ]
+    grid x $page.webbrowserl $page.webbrowser -sticky ew
+    grid configure $page.webbrowser -padx {0 5}
+
+    grid columnconfigure $page 2 -weight 1
 
     return $page
 }
 
 proc prefspage_colors {notebook} {
-    global uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
+    global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
     global diffbgcolors
+    global themeloader
 
     set page [create_prefs_page $notebook.colors]
 
+    ttk::label $page.themesel -font mainfontbold \
+        -text [mc "Themes - change requires restart"]
+    grid $page.themesel - -sticky w -pady 10
+
+    ttk::label $page.themelabel -text [mc "Theme to use after restart"]
+    makedroplist $page.theme theme {*}[lsort [ttk::style theme names]]
+    grid x $page.themelabel $page.theme -sticky w
+
+    ttk::entry $page.tloadvar -textvariable themeloader
+    ttk::frame $page.tloadframe
+    ttk::label $page.tloadframe.l -text [mc "Theme definition file"]
+    ttk::button $page.tloadframe.b -text [mc "Choose..."] \
+        -command [list choose_themeloader $page]
+    pack $page.tloadframe.l -side left
+    pack $page.tloadframe.b -side right -padx {0 5}
+    pack configure $page.tloadframe.l -padx 0
+    grid x $page.tloadframe $page.tloadvar -sticky ew
+    grid configure $page.tloadvar -padx {0 5}
+
+    ttk::label $page.themelabel2 -text \
+        [mc "The theme definition file may affect all themes."]
+    ttk::button $page.themebut2 -text [mc "Apply theme"] \
+        -command [list updatetheme $page]
+    grid x $page.themebut2 $page.themelabel2 -sticky w
+
     ttk::label $page.cdisp -text [mc "Colors: press to choose"] -font mainfontbold
     grid $page.cdisp - -sticky w -pady 10
-    label $page.ui -padx 40 -relief sunk -background $uicolor
-    ttk::button $page.uibut -text [mc "Interface"] \
-       -command [list choosecolor uicolor {} $page [mc "interface"]]
-    grid x $page.uibut $page.ui -sticky w
     label $page.bg -padx 40 -relief sunk -background $bgcolor
     ttk::button $page.bgbut -text [mc "Background"] \
         -command [list choosecolor bgcolor {} $page [mc "background"]]
     grid x $page.bgbut $page.bg -sticky w
+
     label $page.fg -padx 40 -relief sunk -background $fgcolor
     ttk::button $page.fgbut -text [mc "Foreground"] \
         -command [list choosecolor fgcolor {} $page [mc "foreground"]]
     grid x $page.fgbut $page.fg -sticky w
+
     label $page.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
     ttk::button $page.diffoldbut -text [mc "Diff: old lines"] \
         -command [list choosecolor diffcolors 0 $page [mc "diff old lines"]]
     grid x $page.diffoldbut $page.diffold -sticky w
+
     label $page.diffoldbg -padx 40 -relief sunk -background [lindex $diffbgcolors 0]
     ttk::button $page.diffoldbgbut -text [mc "Diff: old lines bg"] \
         -command [list choosecolor diffbgcolors 0 $page [mc "diff old lines bg"]]
     grid x $page.diffoldbgbut $page.diffoldbg -sticky w
+
     label $page.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
     ttk::button $page.diffnewbut -text [mc "Diff: new lines"] \
         -command [list choosecolor diffcolors 1 $page [mc "diff new lines"]]
     grid x $page.diffnewbut $page.diffnew -sticky w
+
     label $page.diffnewbg -padx 40 -relief sunk -background [lindex $diffbgcolors 1]
     ttk::button $page.diffnewbgbut -text [mc "Diff: new lines bg"] \
         -command [list choosecolor diffbgcolors 1 $page [mc "diff new lines bg"]]
     grid x $page.diffnewbgbut $page.diffnewbg -sticky w
+
     label $page.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
     ttk::button $page.hunksepbut -text [mc "Diff: hunk header"] \
         -command [list choosecolor diffcolors 2 $page [mc "diff hunk header"]]
     grid x $page.hunksepbut $page.hunksep -sticky w
+
     label $page.markbgsep -padx 40 -relief sunk -background $markbgcolor
     ttk::button $page.markbgbut -text [mc "Marked line bg"] \
         -command [list choosecolor markbgcolor {} $page [mc "marked line background"]]
     grid x $page.markbgbut $page.markbgsep -sticky w
+
     label $page.selbgsep -padx 40 -relief sunk -background $selectbgcolor
     ttk::button $page.selbgbut -text [mc "Select bg"] \
         -command [list choosecolor selectbgcolor {} $page [mc "background"]]
     grid x $page.selbgbut $page.selbgsep -sticky w
+
+    grid columnconfigure $page 2 -weight 1
+
     return $page
 }
 
 proc prefspage_set_colorswatches {page} {
-    global uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
+    global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
     global diffbgcolors
 
-    $page.ui configure -background $uicolor
     $page.bg configure -background $bgcolor
     $page.fg configure -background $fgcolor
     $page.diffold configure -background [lindex $diffcolors 0]
@@ -11823,6 +11849,7 @@
     mkfontdisp mainfont $page [mc "Main font"]
     mkfontdisp textfont $page [mc "Diff display font"]
     mkfontdisp uifont $page [mc "User interface font"]
+    grid columnconfigure $page 2 -weight 1
     return $page
 }
 
@@ -11857,7 +11884,7 @@
     grid rowconfigure $notebook 1 -weight 1
     raise [lindex $pages 0]
 
-    grid $notebook -sticky news -padx 2 -pady 2
+    grid $notebook -sticky news -padx 3 -pady 3
     grid rowconfigure $top 0 -weight 1
     grid columnconfigure $top 0 -weight 1
 
@@ -11866,12 +11893,13 @@
     ttk::button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
     bind $top <Key-Return> prefsok
     bind $top <Key-Escape> prefscan
-    grid $top.buts.ok $top.buts.can
-    grid columnconfigure $top.buts 0 -weight 1 -uniform a
-    grid columnconfigure $top.buts 1 -weight 1 -uniform a
-    grid $top.buts - - -pady 10 -sticky ew
-    grid columnconfigure $top 2 -weight 1
+    grid $top.buts.ok $top.buts.can -padx 20
+    grid $top.buts -sticky w -pady 10
     bind $top <Visibility> [list focus $top.buts.ok]
+
+    # let geometry manager determine run, set minimum size
+    update idletasks
+    wm minsize $top [winfo reqwidth $top] [winfo reqheight $top]
 }
 
 proc choose_extdiff {} {
@@ -11883,6 +11911,51 @@
     }
 }
 
+proc run_themeloader {f} {
+    if {![info exists ::_themefiles_seen]} {
+        set ::_themefiles_seen [dict create]
+    }
+
+    set fn [file normalize $f]
+    if {![dict exists $::_themefiles_seen $fn]} {
+        if {[catch {source $fn} err]} {
+            error_popup "could not interpret: $fn\n$err"
+            dict set ::_themefiles_seen $fn 0
+        } else {
+            dict set ::_themefiles_seen $fn 1
+        }
+    }
+    return [dict get $::_themefiles_seen $fn]
+}
+
+proc updatetheme {prefspage {dotheme 1}} {
+    global theme
+    global themeloader
+    if {$themeloader ne {}} {
+        if {![run_themeloader $themeloader]} {
+            set themeloader {}
+            return
+        } else {
+            $prefspage.theme configure -values \
+                [lsort [ttk::style theme names]]
+        }
+    }
+    if {$dotheme} {
+        ttk::style theme use $theme
+        set_gui_colors
+        prefspage_set_colorswatches $prefspage
+    }
+}
+
+proc choose_themeloader {prefspage} {
+    global themeloader
+    set tfile [tk_getOpenFile -title [mc "Gitk: select theme definition"] -multiple false]
+    if {$tfile ne {}} {
+        set themeloader $tfile
+        updatetheme $prefspage 0
+    }
+}
+
 proc choosecolor {v vi prefspage x} {
     global $v
 
@@ -11906,21 +11979,6 @@
     allcanvs itemconf secsel -fill $c
 }
 
-# This sets the background color and the color scheme for the whole UI.
-# For some reason, tk_setPalette chooses a nasty dark red for selectColor
-# if we don't specify one ourselves, which makes the checkbuttons and
-# radiobuttons look bad.  This chooses white for selectColor if the
-# background color is light, or black if it is dark.
-proc setui {c} {
-    if {[tk windowingsystem] eq "win32"} { return }
-    set bg [winfo rgb . $c]
-    set selc black
-    if {[lindex $bg 0] + 1.5 * [lindex $bg 1] + 0.5 * [lindex $bg 2] > 100000} {
-        set selc white
-    }
-    tk_setPalette background $c selectColor $selc
-}
-
 proc setbg {c} {
     global bglist
 
@@ -11945,10 +12003,9 @@
 }
 
 proc set_gui_colors {} {
-    global uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
+    global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
     global diffbgcolors
 
-    setui $uicolor
     setbg $bgcolor
     setfg $fgcolor
     $ctext tag conf d0 -foreground [lindex $diffcolors   0]
@@ -11970,6 +12027,7 @@
     catch {destroy $prefstop}
     unset prefstop
     fontcan
+    setttkstyle
     set_gui_colors
 }
 
@@ -12436,11 +12494,13 @@
 
 # on OSX bring the current Wish process window to front
 if {[tk windowingsystem] eq "aqua"} {
-    safe_exec [list osascript -e [format {
-        tell application "System Events"
-            set frontmost of processes whose unix id is %d to true
-        end tell
-    } [pid] ]]
+    catch {
+        safe_exec [list osascript -e [format {
+            tell application "System Events"
+                set frontmost of processes whose unix id is %d to true
+            end tell
+        } [pid] ]]
+    }
 }
 
 # Unset GIT_TRACE var if set
@@ -12545,17 +12605,11 @@
 
 set colors {"#00ff00" red blue magenta darkgrey brown orange}
 if {[tk windowingsystem] eq "win32"} {
-    set uicolor SystemButtonFace
-    set uifgcolor SystemButtonText
-    set uifgdisabledcolor SystemDisabledText
     set bgcolor SystemWindow
     set fgcolor SystemWindowText
     set selectbgcolor SystemHighlight
     set web_browser "cmd /c start"
 } else {
-    set uicolor grey85
-    set uifgcolor black
-    set uifgdisabledcolor "#999"
     set bgcolor white
     set fgcolor black
     set selectbgcolor gray85
@@ -12595,8 +12649,14 @@
 set foundbgcolor yellow
 set currentsearchhitbgcolor orange
 
+set theme [ttk::style theme use]
+set themeloader {}
+set uicolor {}
+set uifgcolor {}
+set uifgdisabledcolor {}
+
 # button for popping up context menus
-if {[tk windowingsystem] eq "aqua"} {
+if {[tk windowingsystem] eq "aqua" && [package vcompare $::tcl_version 8.7] < 0} {
     set ctxbut <Button-2>
 } else {
     set ctxbut <Button-3>
@@ -12678,6 +12738,8 @@
     tagfgcolor
     tagoutlinecolor
     textfont
+    theme
+    themeloader
     uicolor
     uifgcolor
     uifgdisabledcolor
@@ -12777,7 +12839,13 @@
 set nullid2 "0000000000000000000000000000000000000001"
 set nullfile "/dev/null"
 
-setttkstyle
+if {[file exists $themeloader]} {
+    if {![run_themeloader $themeloader]} {
+        puts stderr "Could not interpret themeloader: $themeloader"
+        exit 1
+    }
+}
+
 set appname "gitk"
 
 set runq {}
@@ -12893,6 +12961,7 @@
     focus -force .
 }
 
+setttkstyle
 set_gui_colors
 
 getcommits {}
diff --git a/gpg-interface.c b/gpg-interface.c
index 06e7fb5..2f4f0e3 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -1125,3 +1125,20 @@ static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature,
 	FREE_AND_NULL(ssh_signing_key_file);
 	return ret;
 }
+
+int parse_sign_mode(const char *arg, enum sign_mode *mode)
+{
+	if (!strcmp(arg, "abort"))
+		*mode = SIGN_ABORT;
+	else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore"))
+		*mode = SIGN_VERBATIM;
+	else if (!strcmp(arg, "warn-verbatim") || !strcmp(arg, "warn"))
+		*mode = SIGN_WARN_VERBATIM;
+	else if (!strcmp(arg, "warn-strip"))
+		*mode = SIGN_WARN_STRIP;
+	else if (!strcmp(arg, "strip"))
+		*mode = SIGN_STRIP;
+	else
+		return -1;
+	return 0;
+}
diff --git a/gpg-interface.h b/gpg-interface.h
index 60ddf8b..50487aa 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -104,4 +104,19 @@ int check_signature(struct signature_check *sigc,
 void print_signature_buffer(const struct signature_check *sigc,
 			    unsigned flags);
 
+/* Modes for --signed-tags=<mode> and --signed-commits=<mode> options. */
+enum sign_mode {
+	SIGN_ABORT,
+	SIGN_WARN_VERBATIM,
+	SIGN_VERBATIM,
+	SIGN_WARN_STRIP,
+	SIGN_STRIP,
+};
+
+/*
+ * Return 0 if `arg` can be parsed into an `enum sign_mode`. Return -1
+ * otherwise.
+ */
+int parse_sign_mode(const char *arg, enum sign_mode *mode);
+
 #endif
diff --git a/grep.c b/grep.c
index 932647e..c7e1dc1 100644
--- a/grep.c
+++ b/grep.c
@@ -1263,12 +1263,12 @@ static void show_line(struct grep_opt *opt,
 		 */
 		show_line_header(opt, name, lno, cno, sign);
 	}
-	if (opt->color || opt->only_matching) {
+	if (want_color(opt->color) || opt->only_matching) {
 		regmatch_t match;
 		enum grep_context ctx = GREP_CONTEXT_BODY;
 		int eflags = 0;
 
-		if (opt->color) {
+		if (want_color(opt->color)) {
 			if (sign == ':')
 				match_color = opt->colors[GREP_COLOR_MATCH_SELECTED];
 			else
diff --git a/grep.h b/grep.h
index 926c087..13e26a9 100644
--- a/grep.h
+++ b/grep.h
@@ -159,7 +159,7 @@ struct grep_opt {
 	int pathname;
 	int null_following_name;
 	int only_matching;
-	int color;
+	enum git_colorbool color;
 	int max_depth;
 	int funcname;
 	int funcbody;
@@ -198,7 +198,7 @@ struct grep_opt {
 		[GREP_COLOR_SEP] = GIT_COLOR_CYAN, \
 	}, \
 	.only_matching = 0, \
-	.color = -1, \
+	.color = GIT_COLOR_UNKNOWN, \
 	.output = std_output, \
 }
 
diff --git a/help.c b/help.c
index bb20498..5854dd4 100644
--- a/help.c
+++ b/help.c
@@ -791,6 +791,12 @@ void get_version_info(struct strbuf *buf, int show_build_options)
 		strbuf_addf(buf, "shell-path: %s\n", SHELL_PATH);
 		/* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */
 
+#if defined WITH_RUST
+		strbuf_addstr(buf, "rust: enabled\n");
+#else
+		strbuf_addstr(buf, "rust: disabled\n");
+#endif
+
 		if (fsmonitor_ipc__is_supported())
 			strbuf_addstr(buf, "feature: fsmonitor--daemon\n");
 #if defined LIBCURL_VERSION
diff --git a/http-backend.c b/http-backend.c
index d5dfe76..9084058 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -603,18 +603,19 @@ static void get_head(struct strbuf *hdr, char *arg UNUSED)
 static void get_info_packs(struct strbuf *hdr, char *arg UNUSED)
 {
 	size_t objdirlen = strlen(repo_get_object_directory(the_repository));
+	struct packfile_store *packs = the_repository->objects->packfiles;
 	struct strbuf buf = STRBUF_INIT;
 	struct packed_git *p;
 	size_t cnt = 0;
 
 	select_getanyfile(hdr);
-	for (p = get_all_packs(the_repository); p; p = p->next) {
+	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
 		if (p->pack_local)
 			cnt++;
 	}
 
 	strbuf_grow(&buf, cnt * 53 + 2);
-	for (p = get_all_packs(the_repository); p; p = p->next) {
+	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
 		if (p->pack_local)
 			strbuf_addf(&buf, "P %s\n", p->pack_name + objdirlen + 6);
 	}
diff --git a/http-push.c b/http-push.c
index 91a5465..a1c01e3 100644
--- a/http-push.c
+++ b/http-push.c
@@ -208,7 +208,8 @@ static void curl_setup_http(CURL *curl, const char *url,
 	curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
 	curl_easy_setopt(curl, CURLOPT_URL, url);
 	curl_easy_setopt(curl, CURLOPT_INFILE, buffer);
-	curl_easy_setopt(curl, CURLOPT_INFILESIZE, buffer->buf.len);
+	curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE,
+			 cast_size_t_to_curl_off_t(buffer->buf.len));
 	curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);
 	curl_easy_setopt(curl, CURLOPT_SEEKFUNCTION, seek_buffer);
 	curl_easy_setopt(curl, CURLOPT_SEEKDATA, buffer);
@@ -1941,7 +1942,7 @@ int cmd_main(int argc, const char **argv)
 			strvec_pushf(&commit_argv, "^%s",
 				     oid_to_hex(&ref->old_oid));
 		repo_init_revisions(the_repository, &revs, setup_git_directory());
-		setup_revisions(commit_argv.nr, commit_argv.v, &revs, NULL);
+		setup_revisions_from_strvec(&commit_argv, &revs, NULL);
 		revs.edge_hint = 0; /* just in case */
 
 		/* Generate a list of objects that need to be pushed */
diff --git a/http.c b/http.c
index 98853d6..7e3af1e 100644
--- a/http.c
+++ b/http.c
@@ -1348,6 +1348,14 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
 	if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
 		die("curl_global_init failed");
 
+#ifdef GIT_CURL_HAVE_GLOBAL_TRACE
+	{
+		const char *comp = getenv("GIT_TRACE_CURL_COMPONENTS");
+		if (comp)
+			curl_global_trace(comp);
+	}
+#endif
+
 	if (proactive_auth && http_proactive_auth == PROACTIVE_AUTH_NONE)
 		http_proactive_auth = PROACTIVE_AUTH_IF_CREDENTIALS;
 
@@ -2408,6 +2416,7 @@ static char *fetch_pack_index(unsigned char *hash, const char *base_url)
 static int fetch_and_setup_pack_index(struct packed_git **packs_head,
 	unsigned char *sha1, const char *base_url)
 {
+	struct packfile_store *packs = the_repository->objects->packfiles;
 	struct packed_git *new_pack, *p;
 	char *tmp_idx = NULL;
 	int ret;
@@ -2416,7 +2425,7 @@ static int fetch_and_setup_pack_index(struct packed_git **packs_head,
 	 * If we already have the pack locally, no need to fetch its index or
 	 * even add it to list; we already have all of its objects.
 	 */
-	for (p = get_all_packs(the_repository); p; p = p->next) {
+	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
 		if (hasheq(p->hash, sha1, the_repository->hash_algo))
 			return 0;
 	}
@@ -2541,7 +2550,7 @@ void http_install_packfile(struct packed_git *p,
 		lst = &((*lst)->next);
 	*lst = (*lst)->next;
 
-	install_packed_git(the_repository, p);
+	packfile_store_add_pack(the_repository->objects->packfiles, p);
 }
 
 struct http_pack_request *new_http_pack_request(
diff --git a/http.h b/http.h
index 3620213..553e162 100644
--- a/http.h
+++ b/http.h
@@ -8,6 +8,7 @@ struct packed_git;
 #include <curl/curl.h>
 #include <curl/easy.h>
 
+#include "gettext.h"
 #include "strbuf.h"
 #include "remote.h"
 
@@ -95,6 +96,15 @@ static inline int missing__target(int code, int result)
 
 #define missing_target(a) missing__target((a)->http_code, (a)->curl_result)
 
+static inline curl_off_t cast_size_t_to_curl_off_t(size_t a)
+{
+	uintmax_t size = a;
+	if (size > maximum_signed_value_of_type(curl_off_t))
+		die(_("number too large to represent as curl_off_t "
+		      "on this platform: %"PRIuMAX), (uintmax_t)a);
+	return (curl_off_t)a;
+}
+
 /*
  * Normalize curl results to handle CURL_FAILONERROR (or lack thereof). Failing
  * http codes have their "result" converted to CURLE_HTTP_RETURNED_ERROR, and
@@ -210,7 +220,7 @@ int finish_http_pack_request(struct http_pack_request *preq);
 void release_http_pack_request(struct http_pack_request *preq);
 
 /*
- * Remove p from the given list, and invoke install_packed_git() on it.
+ * Remove p from the given list, and invoke packfile_store_add_pack() on it.
  *
  * This is a convenience function for users that have obtained a list of packs
  * from http_get_info_packs() and have chosen a specific pack to fetch.
diff --git a/ident.c b/ident.c
index 281e830..0b7aace 100644
--- a/ident.c
+++ b/ident.c
@@ -272,7 +272,7 @@ static void strbuf_addstr_without_crud(struct strbuf *sb, const char *src)
  * can still be NULL if the input line only has the name/email part
  * (e.g. reading from a reflog entry).
  */
-int split_ident_line(struct ident_split *split, const char *line, int len)
+int split_ident_line(struct ident_split *split, const char *line, size_t len)
 {
 	const char *cp;
 	size_t span;
diff --git a/ident.h b/ident.h
index 6a79feb..3c03403 100644
--- a/ident.h
+++ b/ident.h
@@ -35,7 +35,7 @@ void reset_ident_date(void);
  * Signals an success with 0, but time part of the result may be NULL
  * if the input lacks timestamp and zone
  */
-int split_ident_line(struct ident_split *, const char *, int);
+int split_ident_line(struct ident_split *, const char *, size_t);
 
 /*
  * Given a commit or tag object buffer and the commit or tag headers, replaces
diff --git a/imap-send.c b/imap-send.c
index 254ec83..26dda7f 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -1442,14 +1442,24 @@ static int count_messages(struct strbuf *all_msgs)
 
 	while (1) {
 		if (starts_with(p, "From ")) {
-			p = strstr(p+5, "\nFrom: ");
-			if (!p) break;
-			p = strstr(p+7, "\nDate: ");
-			if (!p) break;
-			p = strstr(p+7, "\nSubject: ");
-			if (!p) break;
-			p += 10;
-			count++;
+			if (starts_with(p, "From git-send-email")) {
+				p = strstr(p+5, "\nFrom: ");
+				if (!p) break;
+				p += 7;
+				p = strstr(p, "\nTo: ");
+				if (!p) break;
+				p += 5;
+				count++;
+			} else {
+				p = strstr(p+5, "\nFrom: ");
+				if (!p) break;
+				p = strstr(p+7, "\nDate: ");
+				if (!p) break;
+				p = strstr(p+7, "\nSubject: ");
+				if (!p) break;
+				p += 10;
+				count++;
+			}
 		}
 		p = strstr(p+5, "\nFrom ");
 		if (!p)
@@ -1711,7 +1721,7 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,
 		lf_to_crlf(&msgbuf.buf);
 
 		curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE,
-				 (curl_off_t)(msgbuf.buf.len-prev_len));
+				 cast_size_t_to_curl_off_t(msgbuf.buf.len-prev_len));
 
 		res = curl_easy_perform(curl);
 
diff --git a/line-log.c b/line-log.c
index 07f2154..8bd4221 100644
--- a/line-log.c
+++ b/line-log.c
@@ -201,7 +201,7 @@ static void range_set_difference(struct range_set *out,
 				 * b: ------|
 				 */
 				j++;
-			if (j >= b->nr || end < b->ranges[j].start) {
+			if (j >= b->nr || end <= b->ranges[j].start) {
 				/*
 				 * b exhausted, or
 				 * a:  ----|
@@ -408,7 +408,7 @@ static void diff_ranges_filter_touched(struct diff_ranges *out,
 	assert(out->target.nr == 0);
 
 	for (i = 0; i < diff->target.nr; i++) {
-		while (diff->target.ranges[i].start > rs->ranges[j].end) {
+		while (diff->target.ranges[i].start >= rs->ranges[j].end) {
 			j++;
 			if (j == rs->nr)
 				return;
@@ -939,9 +939,18 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
 		long t_cur = t_start;
 		unsigned int j_last;
 
+		/*
+		 * If a diff range touches multiple line ranges, then all
+		 * those line ranges should be shown, so take a step back if
+		 * the current line range is still in the previous diff range
+		 * (even if only partially).
+		 */
+		if (j > 0 && diff->target.ranges[j-1].end > t_start)
+			j--;
+
 		while (j < diff->target.nr && diff->target.ranges[j].end < t_start)
 			j++;
-		if (j == diff->target.nr || diff->target.ranges[j].start > t_end)
+		if (j == diff->target.nr || diff->target.ranges[j].start >= t_end)
 			continue;
 
 		/* Scan ahead to determine the last diff that falls in this range */
@@ -1087,13 +1096,6 @@ static struct diff_filepair *diff_filepair_dup(struct diff_filepair *pair)
 	return new_filepair;
 }
 
-static void free_diffqueues(int n, struct diff_queue_struct *dq)
-{
-	for (int i = 0; i < n; i++)
-		diff_queue_clear(&dq[i]);
-	free(dq);
-}
-
 static int process_all_files(struct line_log_data **range_out,
 			     struct rev_info *rev,
 			     struct diff_queue_struct *queue,
@@ -1189,7 +1191,7 @@ static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *c
 					  struct line_log_data *range)
 {
 	struct commit *parent = NULL;
-	struct diff_queue_struct queue;
+	struct diff_queue_struct queue = DIFF_QUEUE_INIT;
 	struct line_log_data *parent_range;
 	int changed;
 
@@ -1209,9 +1211,7 @@ static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *c
 static int process_ranges_merge_commit(struct rev_info *rev, struct commit *commit,
 				       struct line_log_data *range)
 {
-	struct diff_queue_struct *diffqueues;
 	struct line_log_data **cand;
-	struct commit **parents;
 	struct commit_list *p;
 	int i;
 	int nparents = commit_list_count(commit->parents);
@@ -1220,28 +1220,27 @@ static int process_ranges_merge_commit(struct rev_info *rev, struct commit *comm
 	if (nparents > 1 && rev->first_parent_only)
 		nparents = 1;
 
-	ALLOC_ARRAY(diffqueues, nparents);
 	CALLOC_ARRAY(cand, nparents);
-	ALLOC_ARRAY(parents, nparents);
 
-	p = commit->parents;
-	for (i = 0; i < nparents; i++) {
-		parents[i] = p->item;
-		p = p->next;
-		queue_diffs(range, &rev->diffopt, &diffqueues[i], commit, parents[i]);
-	}
-
-	for (i = 0; i < nparents; i++) {
+	for (p = commit->parents, i = 0;
+	     p && i < nparents;
+	     p = p->next, i++) {
+		struct commit *parent = p->item;
+		struct diff_queue_struct diffqueue = DIFF_QUEUE_INIT;
 		int changed;
-		changed = process_all_files(&cand[i], rev, &diffqueues[i], range);
+
+		queue_diffs(range, &rev->diffopt, &diffqueue, commit, parent);
+
+		changed = process_all_files(&cand[i], rev, &diffqueue, range);
+		diff_queue_clear(&diffqueue);
 		if (!changed) {
 			/*
 			 * This parent can take all the blame, so we
 			 * don't follow any other path in history
 			 */
-			add_line_range(rev, parents[i], cand[i]);
+			add_line_range(rev, parent, cand[i]);
 			free_commit_list(commit->parents);
-			commit_list_append(parents[i], &commit->parents);
+			commit_list_append(parent, &commit->parents);
 
 			ret = 0;
 			goto out;
@@ -1252,14 +1251,15 @@ static int process_ranges_merge_commit(struct rev_info *rev, struct commit *comm
 	 * No single parent took the blame.  We add the candidates
 	 * from the above loop to the parents.
 	 */
-	for (i = 0; i < nparents; i++)
-		add_line_range(rev, parents[i], cand[i]);
+	for (p = commit->parents, i = 0;
+	     p && i < nparents;
+	     p = p->next, i++)
+		add_line_range(rev, p->item, cand[i]);
 
 	ret = 1;
 
 out:
 	clear_commit_line_range(rev, commit);
-	free(parents);
 	for (i = 0; i < nparents; i++) {
 		if (!cand[i])
 			continue;
@@ -1267,7 +1267,6 @@ static int process_ranges_merge_commit(struct rev_info *rev, struct commit *comm
 		free(cand[i]);
 	}
 	free(cand);
-	free_diffqueues(nparents, diffqueues);
 	return ret;
 
 	/* NEEDSWORK evil merge detection stuff */
@@ -1283,10 +1282,10 @@ int line_log_process_ranges_arbitrary_commit(struct rev_info *rev, struct commit
 			struct line_log_data *prange = line_log_data_copy(range);
 			add_line_range(rev, commit->parents->item, prange);
 			clear_commit_line_range(rev, commit);
-		} else if (!commit->parents || !commit->parents->next)
-			changed = process_ranges_ordinary_commit(rev, commit, range);
-		else
+		} else if (commit->parents && commit->parents->next)
 			changed = process_ranges_merge_commit(rev, commit, range);
+		else
+			changed = process_ranges_ordinary_commit(rev, commit, range);
 	}
 
 	if (!changed)
diff --git a/list-objects-filter.c b/list-objects-filter.c
index 7ecd4d9..acd65eb 100644
--- a/list-objects-filter.c
+++ b/list-objects-filter.c
@@ -524,12 +524,11 @@ static void filter_sparse_oid__init(
 	struct filter *filter)
 {
 	struct filter_sparse_data *d = xcalloc(1, sizeof(*d));
-	struct object_context oc;
 	struct object_id sparse_oid;
 
-	if (get_oid_with_context(the_repository,
-				 filter_options->sparse_oid_name,
-				 GET_OID_BLOB, &sparse_oid, &oc))
+	if (repo_get_oid_with_flags(the_repository,
+				    filter_options->sparse_oid_name,
+				    &sparse_oid, GET_OID_BLOB))
 		die(_("unable to access sparse blob in '%s'"),
 		    filter_options->sparse_oid_name);
 	if (add_patterns_from_blob_to_list(&sparse_oid, "", 0, &d->pl) < 0)
@@ -544,8 +543,6 @@ static void filter_sparse_oid__init(
 	filter->filter_data = d;
 	filter->filter_object_fn = filter_sparse;
 	filter->free_fn = filter_sparse_free;
-
-	object_context_release(&oc);
 }
 
 /*
diff --git a/log-tree.c b/log-tree.c
index 233bf9f..7d917f2 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -57,7 +57,7 @@ static const char *color_decorate_slots[] = {
 	[DECORATION_GRAFTED]	= "grafted",
 };
 
-static const char *decorate_get_color(int decorate_use_color, enum decoration_type ix)
+static const char *decorate_get_color(enum git_colorbool decorate_use_color, enum decoration_type ix)
 {
 	if (want_color(decorate_use_color))
 		return decoration_colors[ix];
@@ -341,7 +341,7 @@ static void show_name(struct strbuf *sb, const struct name_decoration *decoratio
  */
 void format_decorations(struct strbuf *sb,
 			const struct commit *commit,
-			int use_color,
+			enum git_colorbool use_color,
 			const struct decoration_options *opts)
 {
 	const struct name_decoration *decoration;
@@ -717,7 +717,9 @@ static void show_diff_of_diff(struct rev_info *opt)
 		struct range_diff_options range_diff_opts = {
 			.creation_factor = opt->creation_factor,
 			.dual_color = 1,
-			.diffopt = &opts
+			.max_memory = RANGE_DIFF_MAX_MEMORY_DEFAULT,
+			.diffopt = &opts,
+			.log_arg = &opt->rdiff_log_arg
 		};
 
 		memcpy(&dq, &diff_queued_diff, sizeof(diff_queued_diff));
diff --git a/log-tree.h b/log-tree.h
index ebe491c..07924be 100644
--- a/log-tree.h
+++ b/log-tree.h
@@ -1,6 +1,8 @@
 #ifndef LOG_TREE_H
 #define LOG_TREE_H
 
+#include "color.h"
+
 struct rev_info;
 
 struct log_info {
@@ -26,7 +28,7 @@ int log_tree_diff_flush(struct rev_info *);
 int log_tree_commit(struct rev_info *, struct commit *);
 void show_log(struct rev_info *opt);
 void format_decorations(struct strbuf *sb, const struct commit *commit,
-			int use_color, const struct decoration_options *opts);
+			enum git_colorbool use_color, const struct decoration_options *opts);
 void show_decorations(struct rev_info *opt, struct commit *commit);
 void log_write_email_headers(struct rev_info *opt, struct commit *commit,
 			     char **extra_headers_p,
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/merge-ort.c b/merge-ort.c
index cd0cb4d..2985807 100644
--- a/merge-ort.c
+++ b/merge-ort.c
@@ -316,9 +316,14 @@ struct merge_options_internal {
 	 *     (e.g. "drivers/firmware/raspberrypi.c").
 	 *   * store all relevant paths in the repo, both directories and
 	 *     files (e.g. drivers, drivers/firmware would also be included)
-	 *   * these keys serve to intern all the path strings, which allows
-	 *     us to do pointer comparison on directory names instead of
-	 *     strcmp; we just have to be careful to use the interned strings.
+	 *   * these keys serve to intern *all* path strings, which allows us
+	 *     to do pointer comparisons on file & directory names instead of
+	 *     using strcmp; however, for this pointer-comparison optimization
+	 *     to work, any code path that independently computes a path needs
+	 *     to check for it existing in this strmap, and if so, point to
+	 *     the path in this strmap instead of their computed copy.  See
+	 *     the "reuse known pointer" comment in
+	 *     apply_directory_rename_modifications() for an example.
 	 *
 	 * The values of paths:
 	 *   * either a pointer to a merged_info, or a conflict_info struct
@@ -2163,7 +2168,7 @@ static int handle_content_merge(struct merge_options *opt,
 		/*
 		 * FIXME: If opt->priv->call_depth && !clean, then we really
 		 * should not make result->mode match either a->mode or
-		 * b->mode; that causes t6036 "check conflicting mode for
+		 * b->mode; that causes t6416 "check conflicting mode for
 		 * regular file" to fail.  It would be best to use some other
 		 * mode, but we'll confuse all kinds of stuff if we use one
 		 * where S_ISREG(result->mode) isn't true, and if we use
@@ -2313,14 +2318,20 @@ static char *apply_dir_rename(struct strmap_entry *rename_info,
 	return strbuf_detach(&new_path, NULL);
 }
 
-static int path_in_way(struct strmap *paths, const char *path, unsigned side_mask)
+static int path_in_way(struct strmap *paths,
+		       const char *path,
+		       unsigned side_mask,
+		       struct diff_filepair *p)
 {
 	struct merged_info *mi = strmap_get(paths, path);
 	struct conflict_info *ci;
 	if (!mi)
 		return 0;
 	INITIALIZE_CI(ci, mi);
-	return mi->clean || (side_mask & (ci->filemask | ci->dirmask));
+	return mi->clean || (side_mask & (ci->filemask | ci->dirmask))
+	  /* See testcases 12[npq] of t6423 for this next condition */
+			 || ((ci->filemask & 0x01) &&
+			     strcmp(p->one->path, path));
 }
 
 /*
@@ -2332,6 +2343,7 @@ static int path_in_way(struct strmap *paths, const char *path, unsigned side_mas
 static char *handle_path_level_conflicts(struct merge_options *opt,
 					 const char *path,
 					 unsigned side_index,
+					 struct diff_filepair *p,
 					 struct strmap_entry *rename_info,
 					 struct strmap *collisions)
 {
@@ -2366,7 +2378,7 @@ static char *handle_path_level_conflicts(struct merge_options *opt,
 	 */
 	if (c_info->reported_already) {
 		clean = 0;
-	} else if (path_in_way(&opt->priv->paths, new_path, 1 << side_index)) {
+	} else if (path_in_way(&opt->priv->paths, new_path, 1 << side_index, p)) {
 		c_info->reported_already = 1;
 		strbuf_add_separated_string_list(&collision_paths, ", ",
 						 &c_info->source_files);
@@ -2520,7 +2532,7 @@ static void compute_collisions(struct strmap *collisions,
 	 * happening, and fall back to no-directory-rename detection
 	 * behavior for those paths.
 	 *
-	 * See testcases 9e and all of section 5 from t6043 for examples.
+	 * See testcases 9e and all of section 5 from t6423 for examples.
 	 */
 	for (i = 0; i < pairs->nr; ++i) {
 		struct strmap_entry *rename_info;
@@ -2573,6 +2585,7 @@ static void free_collisions(struct strmap *collisions)
 static char *check_for_directory_rename(struct merge_options *opt,
 					const char *path,
 					unsigned side_index,
+					struct diff_filepair *p,
 					struct strmap *dir_renames,
 					struct strmap *dir_rename_exclusions,
 					struct strmap *collisions,
@@ -2580,7 +2593,6 @@ static char *check_for_directory_rename(struct merge_options *opt,
 {
 	char *new_path;
 	struct strmap_entry *rename_info;
-	struct strmap_entry *otherinfo;
 	const char *new_dir;
 	int other_side = 3 - side_index;
 
@@ -2615,14 +2627,13 @@ static char *check_for_directory_rename(struct merge_options *opt,
 	 * to not let Side1 do the rename to dumbdir, since we know that is
 	 * the source of one of our directory renames.
 	 *
-	 * That's why otherinfo and dir_rename_exclusions is here.
+	 * That's why dir_rename_exclusions is here.
 	 *
 	 * As it turns out, this also prevents N-way transient rename
-	 * confusion; See testcases 9c and 9d of t6043.
+	 * confusion; See testcases 9c and 9d of t6423.
 	 */
 	new_dir = rename_info->value; /* old_dir = rename_info->key; */
-	otherinfo = strmap_get_entry(dir_rename_exclusions, new_dir);
-	if (otherinfo) {
+	if (strmap_contains(dir_rename_exclusions, new_dir)) {
 		path_msg(opt, INFO_DIR_RENAME_SKIPPED_DUE_TO_RERENAME, 1,
 			 rename_info->key, path, new_dir, NULL,
 			 _("WARNING: Avoiding applying %s -> %s rename "
@@ -2631,7 +2642,7 @@ static char *check_for_directory_rename(struct merge_options *opt,
 		return NULL;
 	}
 
-	new_path = handle_path_level_conflicts(opt, path, side_index,
+	new_path = handle_path_level_conflicts(opt, path, side_index, p,
 					       rename_info,
 					       &collisions[side_index]);
 	*clean_merge &= (new_path != NULL);
@@ -2876,6 +2887,20 @@ static int process_renames(struct merge_options *opt,
 		}
 
 		/*
+		 * Directory renames can result in rename-to-self; the code
+		 * below assumes we have A->B with different A & B, and tries
+		 * to move all entries to path B.  If A & B are the same path,
+		 * the logic can get confused, so skip further processing when
+		 * A & B are already the same path.
+		 *
+		 * As a reminder, we can avoid strcmp here because all paths
+		 * are interned in opt->priv->paths; see the comment above
+		 * "paths" in struct merge_options_internal.
+		 */
+		if (oldpath == newpath)
+			continue;
+
+		/*
 		 * If pair->one->path isn't in opt->priv->paths, that means
 		 * that either directory rename detection removed that
 		 * path, or a parent directory of oldpath was resolved and
@@ -3419,7 +3444,7 @@ static int collect_renames(struct merge_options *opt,
 		}
 
 		new_path = check_for_directory_rename(opt, p->two->path,
-						      side_index,
+						      side_index, p,
 						      dir_renames_for_side,
 						      rename_exclusions,
 						      collisions,
diff --git a/meson.build b/meson.build
index 5dd299b..cee9424 100644
--- a/meson.build
+++ b/meson.build
@@ -220,7 +220,7 @@
   # learned to define __STDC_VERSION__ with C11 and later. We thus require
   # GNU C99 and fall back to C11. Meson only learned to handle the fallback
   # with version 1.3.0, so on older versions we use GNU C99 unconditionally.
-  default_options: meson.version().version_compare('>=1.3.0') ? ['c_std=gnu99,c11'] : ['c_std=gnu99'],
+  default_options: meson.version().version_compare('>=1.3.0') ? ['rust_std=2018', 'c_std=gnu99,c11'] : ['rust_std=2018', 'c_std=gnu99'],
 )
 
 fs = import('fs')
@@ -287,7 +287,6 @@
   'blob.c',
   'bloom.c',
   'branch.c',
-  'bulk-checkin.c',
   'bundle-uri.c',
   'bundle.c',
   'cache-tree.c',
@@ -407,6 +406,7 @@
   'pack-check.c',
   'pack-mtimes.c',
   'pack-objects.c',
+  'pack-refs.c',
   'pack-revindex.c',
   'pack-write.c',
   'packfile.c',
@@ -452,6 +452,7 @@
   'reftable/error.c',
   'reftable/block.c',
   'reftable/blocksource.c',
+  'reftable/fsck.c',
   'reftable/iter.c',
   'reftable/merged.c',
   'reftable/pq.c',
@@ -522,7 +523,6 @@
   'usage.c',
   'userdiff.c',
   'utf8.c',
-  'varint.c',
   'version.c',
   'versioncmp.c',
   'walker.c',
@@ -607,6 +607,7 @@
   'builtin/index-pack.c',
   'builtin/init-db.c',
   'builtin/interpret-trailers.c',
+  'builtin/last-modified.c',
   'builtin/log.c',
   'builtin/ls-files.c',
   'builtin/ls-remote.c',
@@ -645,6 +646,7 @@
   'builtin/repack.c',
   'builtin/replace.c',
   'builtin/replay.c',
+  'builtin/repo.c',
   'builtin/rerere.c',
   'builtin/reset.c',
   'builtin/rev-list.c',
@@ -1701,6 +1703,17 @@
 )
 libgit_sources += version_def_h
 
+cargo = find_program('cargo', dirs: program_path, native: true, required: get_option('rust'))
+rust_option = get_option('rust').disable_auto_if(not cargo.found())
+if rust_option.allowed()
+  subdir('src')
+  libgit_c_args += '-DWITH_RUST'
+else
+  libgit_sources += [
+    'varint.c',
+  ]
+endif
+
 libgit = declare_dependency(
   link_with: static_library('git',
     sources: libgit_sources,
@@ -2099,11 +2112,20 @@
 
 subdir('bin-wrappers')
 if get_option('docs') != []
+  doc_targets = []
   subdir('Documentation')
+else
+  docs_backend = 'none'
 endif
 
 subdir('contrib')
 
+# Note that the target is intentionally configured after including the
+# 'contrib' directory, as some tool there also have their own manpages.
+if get_option('docs') != []
+  alias_target('docs', doc_targets)
+endif
+
 exclude_from_check_headers = [
   'compat/',
   'unicode-width.h',
@@ -2238,10 +2260,12 @@
   'pcre2': pcre2,
   'perl': perl_features_enabled,
   'python': target_python.found(),
+  'rust': rust_option.allowed(),
 }, section: 'Auto-detected features', bool_yn: true)
 
 summary({
   'csprng': csprng_backend,
+  'docs': docs_backend,
   'https': https_backend,
   'sha1': sha1_backend,
   'sha1_unsafe': sha1_unsafe_backend,
diff --git a/meson_options.txt b/meson_options.txt
index 1668f26..143dee9 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -71,6 +71,8 @@
 # Build tweaks.
 option('breaking_changes', type: 'boolean', value: false,
   description: 'Enable upcoming breaking changes.')
+option('rust', type: 'feature', value: 'auto',
+  description: 'Enable building with Rust.')
 option('macos_use_homebrew_gettext', type: 'boolean', value: true,
   description: 'Use gettext from Homebrew instead of the slightly-broken system-provided one.')
 
diff --git a/midx-write.c b/midx-write.c
index a0aceab..c73010d 100644
--- a/midx-write.c
+++ b/midx-write.c
@@ -1,5 +1,3 @@
-#define DISABLE_SIGN_COMPARE_WARNINGS
-
 #include "git-compat-util.h"
 #include "abspath.h"
 #include "config.h"
@@ -24,11 +22,12 @@
 #define BITMAP_POS_UNKNOWN (~((uint32_t)0))
 #define MIDX_CHUNK_FANOUT_SIZE (sizeof(uint32_t) * 256)
 #define MIDX_CHUNK_LARGE_OFFSET_WIDTH (sizeof(uint64_t))
+#define NO_PREFERRED_PACK (~((uint32_t)0))
 
 extern int midx_checksum_valid(struct multi_pack_index *m);
-extern void clear_midx_files_ext(const char *object_dir, const char *ext,
+extern void clear_midx_files_ext(struct odb_source *source, const char *ext,
 				 const char *keep_hash);
-extern void clear_incremental_midx_files_ext(const char *object_dir,
+extern void clear_incremental_midx_files_ext(struct odb_source *source,
 					     const char *ext,
 					     const char **keep_hashes,
 					     uint32_t hashes_nr);
@@ -104,7 +103,7 @@ struct write_midx_context {
 	unsigned large_offsets_needed:1;
 	uint32_t num_large_offsets;
 
-	int preferred_pack_idx;
+	uint32_t preferred_pack_idx;
 
 	int incremental;
 	uint32_t num_multi_pack_indexes_before;
@@ -112,6 +111,7 @@ struct write_midx_context {
 	struct string_list *to_include;
 
 	struct repository *repo;
+	struct odb_source *source;
 };
 
 static int should_include_pack(const struct write_midx_context *ctx,
@@ -260,7 +260,7 @@ static void midx_fanout_sort(struct midx_fanout *fanout)
 static void midx_fanout_add_midx_fanout(struct midx_fanout *fanout,
 					struct multi_pack_index *m,
 					uint32_t cur_fanout,
-					int preferred_pack)
+					uint32_t preferred_pack)
 {
 	uint32_t start = m->num_objects_in_base, end;
 	uint32_t cur_object;
@@ -274,7 +274,7 @@ static void midx_fanout_add_midx_fanout(struct midx_fanout *fanout,
 	end = m->num_objects_in_base + ntohl(m->chunk_oid_fanout[cur_fanout]);
 
 	for (cur_object = start; cur_object < end; cur_object++) {
-		if ((preferred_pack > -1) &&
+		if ((preferred_pack != NO_PREFERRED_PACK) &&
 		    (preferred_pack == nth_midxed_pack_int_id(m, cur_object))) {
 			/*
 			 * Objects from preferred packs are added
@@ -364,7 +364,8 @@ static void compute_sorted_entries(struct write_midx_context *ctx,
 						    preferred, cur_fanout);
 		}
 
-		if (-1 < ctx->preferred_pack_idx && ctx->preferred_pack_idx < start_pack)
+		if (ctx->preferred_pack_idx != NO_PREFERRED_PACK &&
+		    ctx->preferred_pack_idx < start_pack)
 			midx_fanout_add_pack_fanout(&fanout, ctx->info,
 						    ctx->preferred_pack_idx, 1,
 						    cur_fanout);
@@ -648,7 +649,6 @@ static uint32_t *midx_pack_order(struct write_midx_context *ctx)
 }
 
 static void write_midx_reverse_index(struct write_midx_context *ctx,
-				     const char *object_dir,
 				     unsigned char *midx_hash)
 {
 	struct strbuf buf = STRBUF_INIT;
@@ -657,11 +657,10 @@ static void write_midx_reverse_index(struct write_midx_context *ctx,
 	trace2_region_enter("midx", "write_midx_reverse_index", ctx->repo);
 
 	if (ctx->incremental)
-		get_split_midx_filename_ext(ctx->repo->hash_algo, &buf,
-					    object_dir, midx_hash,
-					    MIDX_EXT_REV);
+		get_split_midx_filename_ext(ctx->source, &buf,
+					    midx_hash, MIDX_EXT_REV);
 	else
-		get_midx_filename_ext(ctx->repo->hash_algo, &buf, object_dir,
+		get_midx_filename_ext(ctx->source, &buf,
 				      midx_hash, MIDX_EXT_REV);
 
 	tmp_file = write_rev_file_order(ctx->repo, NULL, ctx->pack_order,
@@ -836,14 +835,13 @@ static struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr
 }
 
 static int write_midx_bitmap(struct write_midx_context *ctx,
-			     const char *object_dir,
 			     const unsigned char *midx_hash,
 			     struct packing_data *pdata,
 			     struct commit **commits,
 			     uint32_t commits_nr,
 			     unsigned flags)
 {
-	int ret, i;
+	int ret;
 	uint16_t options = 0;
 	struct bitmap_writer writer;
 	struct pack_idx_entry **index;
@@ -852,12 +850,11 @@ static int write_midx_bitmap(struct write_midx_context *ctx,
 	trace2_region_enter("midx", "write_midx_bitmap", ctx->repo);
 
 	if (ctx->incremental)
-		get_split_midx_filename_ext(ctx->repo->hash_algo, &bitmap_name,
-					    object_dir, midx_hash,
-					    MIDX_EXT_BITMAP);
+		get_split_midx_filename_ext(ctx->source, &bitmap_name,
+					    midx_hash, MIDX_EXT_BITMAP);
 	else
-		get_midx_filename_ext(ctx->repo->hash_algo, &bitmap_name,
-				      object_dir, midx_hash, MIDX_EXT_BITMAP);
+		get_midx_filename_ext(ctx->source, &bitmap_name,
+				      midx_hash, MIDX_EXT_BITMAP);
 
 	if (flags & MIDX_WRITE_BITMAP_HASH_CACHE)
 		options |= BITMAP_OPT_HASH_CACHE;
@@ -871,7 +868,7 @@ static int write_midx_bitmap(struct write_midx_context *ctx,
 	 * this order).
 	 */
 	ALLOC_ARRAY(index, pdata->nr_objects);
-	for (i = 0; i < pdata->nr_objects; i++)
+	for (uint32_t i = 0; i < pdata->nr_objects; i++)
 		index[i] = &pdata->objects[i].idx;
 
 	bitmap_writer_init(&writer, ctx->repo, pdata,
@@ -892,7 +889,7 @@ static int write_midx_bitmap(struct write_midx_context *ctx,
 	 * happens between bitmap_writer_build_type_index() and
 	 * bitmap_writer_finish().
 	 */
-	for (i = 0; i < pdata->nr_objects; i++)
+	for (uint32_t i = 0; i < pdata->nr_objects; i++)
 		index[ctx->pack_order[i]] = &pdata->objects[i].idx;
 
 	bitmap_writer_select_commits(&writer, commits, commits_nr);
@@ -913,15 +910,7 @@ static int write_midx_bitmap(struct write_midx_context *ctx,
 	return ret;
 }
 
-static struct multi_pack_index *lookup_multi_pack_index(struct repository *r,
-							const char *object_dir)
-{
-	struct odb_source *source = odb_find_source(r->objects, object_dir);
-	return get_multi_pack_index(source);
-}
-
-static int fill_packs_from_midx(struct write_midx_context *ctx,
-				const char *preferred_pack_name, uint32_t flags)
+static int fill_packs_from_midx(struct write_midx_context *ctx)
 {
 	struct multi_pack_index *m;
 
@@ -929,30 +918,10 @@ static int fill_packs_from_midx(struct write_midx_context *ctx,
 		uint32_t i;
 
 		for (i = 0; i < m->num_packs; i++) {
+			if (prepare_midx_pack(m, m->num_packs_in_base + i))
+				return error(_("could not load pack"));
+
 			ALLOC_GROW(ctx->info, ctx->nr + 1, ctx->alloc);
-
-			/*
-			 * If generating a reverse index, need to have
-			 * packed_git's loaded to compare their
-			 * mtimes and object count.
-			 *
-			 * If a preferred pack is specified, need to
-			 * have packed_git's loaded to ensure the chosen
-			 * preferred pack has a non-zero object count.
-			 */
-			if (flags & MIDX_WRITE_REV_INDEX ||
-			    preferred_pack_name) {
-				if (prepare_midx_pack(ctx->repo, m,
-						      m->num_packs_in_base + i)) {
-					error(_("could not load pack"));
-					return 1;
-				}
-
-				if (open_pack_index(m->packs[i]))
-					die(_("could not open index for %s"),
-					    m->packs[i]->pack_name);
-			}
-
 			fill_pack_info(&ctx->info[ctx->nr++], m->packs[i],
 				       m->pack_names[i],
 				       m->num_packs_in_base + i);
@@ -989,10 +958,9 @@ static int link_midx_to_chain(struct multi_pack_index *m)
 	for (i = 0; i < ARRAY_SIZE(midx_exts); i++) {
 		const unsigned char *hash = get_midx_checksum(m);
 
-		get_midx_filename_ext(m->repo->hash_algo, &from, m->object_dir,
+		get_midx_filename_ext(m->source, &from,
 				      hash, midx_exts[i].non_split);
-		get_split_midx_filename_ext(m->repo->hash_algo, &to,
-					    m->object_dir, hash,
+		get_split_midx_filename_ext(m->source, &to, hash,
 					    midx_exts[i].split);
 
 		if (link(from.buf, to.buf) < 0 && errno != ENOENT) {
@@ -1011,7 +979,7 @@ static int link_midx_to_chain(struct multi_pack_index *m)
 	return ret;
 }
 
-static void clear_midx_files(struct repository *r, const char *object_dir,
+static void clear_midx_files(struct odb_source *source,
 			     const char **hashes, uint32_t hashes_nr,
 			     unsigned incremental)
 {
@@ -1030,16 +998,16 @@ static void clear_midx_files(struct repository *r, const char *object_dir,
 	uint32_t i, j;
 
 	for (i = 0; i < ARRAY_SIZE(exts); i++) {
-		clear_incremental_midx_files_ext(object_dir, exts[i],
+		clear_incremental_midx_files_ext(source, exts[i],
 						 hashes, hashes_nr);
 		for (j = 0; j < hashes_nr; j++)
-			clear_midx_files_ext(object_dir, exts[i], hashes[j]);
+			clear_midx_files_ext(source, exts[i], hashes[j]);
 	}
 
 	if (incremental)
-		get_midx_filename(r->hash_algo, &buf, object_dir);
+		get_midx_filename(source, &buf);
 	else
-		get_midx_chain_filename(&buf, object_dir);
+		get_midx_chain_filename(source, &buf);
 
 	if (unlink(buf.buf) && errno != ENOENT)
 		die_errno(_("failed to clear multi-pack-index at %s"), buf.buf);
@@ -1047,45 +1015,49 @@ static void clear_midx_files(struct repository *r, const char *object_dir,
 	strbuf_release(&buf);
 }
 
-static int write_midx_internal(struct repository *r, const char *object_dir,
+static int write_midx_internal(struct odb_source *source,
 			       struct string_list *packs_to_include,
 			       struct string_list *packs_to_drop,
 			       const char *preferred_pack_name,
 			       const char *refs_snapshot,
 			       unsigned flags)
 {
+	struct repository *r = source->odb->repo;
 	struct strbuf midx_name = STRBUF_INIT;
 	unsigned char midx_hash[GIT_MAX_RAWSZ];
-	uint32_t i, start_pack;
+	uint32_t start_pack;
 	struct hashfile *f = NULL;
 	struct lock_file lk;
 	struct tempfile *incr;
-	struct write_midx_context ctx = { 0 };
+	struct write_midx_context ctx = {
+		.preferred_pack_idx = NO_PREFERRED_PACK,
+	 };
 	int bitmapped_packs_concat_len = 0;
 	int pack_name_concat_len = 0;
 	int dropped_packs = 0;
-	int result = 0;
+	int result = -1;
 	const char **keep_hashes = NULL;
 	struct chunkfile *cf;
 
 	trace2_region_enter("midx", "write_midx_internal", r);
 
 	ctx.repo = r;
+	ctx.source = source;
 
 	ctx.incremental = !!(flags & MIDX_WRITE_INCREMENTAL);
 
 	if (ctx.incremental)
 		strbuf_addf(&midx_name,
 			    "%s/pack/multi-pack-index.d/tmp_midx_XXXXXX",
-			    object_dir);
+			    source->path);
 	else
-		get_midx_filename(r->hash_algo, &midx_name, object_dir);
+		get_midx_filename(source, &midx_name);
 	if (safe_create_leading_directories(r, midx_name.buf))
 		die_errno(_("unable to create leading directories of %s"),
 			  midx_name.buf);
 
 	if (!packs_to_include || ctx.incremental) {
-		struct multi_pack_index *m = lookup_multi_pack_index(r, object_dir);
+		struct multi_pack_index *m = get_multi_pack_index(source);
 		if (m && !midx_checksum_valid(m)) {
 			warning(_("ignoring existing multi-pack-index; checksum mismatch"));
 			m = NULL;
@@ -1116,15 +1088,13 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 			if (flags & MIDX_WRITE_BITMAP && load_midx_revindex(m)) {
 				error(_("could not load reverse index for MIDX %s"),
 				      hash_to_hex_algop(get_midx_checksum(m),
-							m->repo->hash_algo));
-				result = 1;
+							m->source->odb->repo->hash_algo));
 				goto cleanup;
 			}
 			ctx.num_multi_pack_indexes_before++;
 			m = m->base_midx;
 		}
-	} else if (ctx.m && fill_packs_from_midx(&ctx, preferred_pack_name,
-						 flags) < 0) {
+	} else if (ctx.m && fill_packs_from_midx(&ctx)) {
 		goto cleanup;
 	}
 
@@ -1139,7 +1109,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 
 	ctx.to_include = packs_to_include;
 
-	for_each_file_in_pack_dir(object_dir, add_pack_to_midx, &ctx);
+	for_each_file_in_pack_dir(source->path, add_pack_to_midx, &ctx);
 	stop_progress(&ctx.progress);
 
 	if ((ctx.m && ctx.nr == ctx.m->num_packs + ctx.m->num_packs_in_base) &&
@@ -1159,18 +1129,21 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 			 * corresponding bitmap (or one wasn't requested).
 			 */
 			if (!want_bitmap)
-				clear_midx_files_ext(object_dir, "bitmap", NULL);
+				clear_midx_files_ext(source, "bitmap", NULL);
+			result = 0;
 			goto cleanup;
 		}
 	}
 
-	if (ctx.incremental && !ctx.nr)
+	if (ctx.incremental && !ctx.nr) {
+		result = 0;
 		goto cleanup; /* nothing to do */
+	}
 
 	if (preferred_pack_name) {
-		ctx.preferred_pack_idx = -1;
+		ctx.preferred_pack_idx = NO_PREFERRED_PACK;
 
-		for (i = 0; i < ctx.nr; i++) {
+		for (size_t i = 0; i < ctx.nr; i++) {
 			if (!cmp_idx_or_pack_name(preferred_pack_name,
 						  ctx.info[i].pack_name)) {
 				ctx.preferred_pack_idx = i;
@@ -1178,14 +1151,21 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 			}
 		}
 
-		if (ctx.preferred_pack_idx == -1)
+		if (ctx.preferred_pack_idx == NO_PREFERRED_PACK)
 			warning(_("unknown preferred pack: '%s'"),
 				preferred_pack_name);
 	} else if (ctx.nr &&
 		   (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP))) {
-		struct packed_git *oldest = ctx.info[ctx.preferred_pack_idx].p;
+		struct packed_git *oldest = ctx.info[0].p;
 		ctx.preferred_pack_idx = 0;
 
+		/*
+		 * Attempt opening the pack index to populate num_objects.
+		 * Ignore failiures as they can be expected and are not
+		 * fatal during this selection time.
+		 */
+		open_pack_index(oldest);
+
 		if (packs_to_drop && packs_to_drop->nr)
 			BUG("cannot write a MIDX bitmap during expiration");
 
@@ -1195,11 +1175,12 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 		 * pack-order has all of its objects selected from that pack
 		 * (and not another pack containing a duplicate)
 		 */
-		for (i = 1; i < ctx.nr; i++) {
+		for (size_t i = 1; i < ctx.nr; i++) {
 			struct packed_git *p = ctx.info[i].p;
 
 			if (!oldest->num_objects || p->mtime < oldest->mtime) {
 				oldest = p;
+				open_pack_index(oldest);
 				ctx.preferred_pack_idx = i;
 			}
 		}
@@ -1211,22 +1192,26 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 			 * objects to resolve, so the preferred value doesn't
 			 * matter.
 			 */
-			ctx.preferred_pack_idx = -1;
+			ctx.preferred_pack_idx = NO_PREFERRED_PACK;
 		}
 	} else {
 		/*
 		 * otherwise don't mark any pack as preferred to avoid
 		 * interfering with expiration logic below
 		 */
-		ctx.preferred_pack_idx = -1;
+		ctx.preferred_pack_idx = NO_PREFERRED_PACK;
 	}
 
-	if (ctx.preferred_pack_idx > -1) {
+	if (ctx.preferred_pack_idx != NO_PREFERRED_PACK) {
 		struct packed_git *preferred = ctx.info[ctx.preferred_pack_idx].p;
+
+		if (open_pack_index(preferred))
+			die(_("failed to open preferred pack %s"),
+			    ctx.info[ctx.preferred_pack_idx].pack_name);
+
 		if (!preferred->num_objects) {
 			error(_("cannot select preferred pack %s with no objects"),
 			      preferred->pack_name);
-			result = 1;
 			goto cleanup;
 		}
 	}
@@ -1234,7 +1219,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 	compute_sorted_entries(&ctx, start_pack);
 
 	ctx.large_offsets_needed = 0;
-	for (i = 0; i < ctx.entries_nr; i++) {
+	for (size_t i = 0; i < ctx.entries_nr; i++) {
 		if (ctx.entries[i].offset > 0x7fffffff)
 			ctx.num_large_offsets++;
 		if (ctx.entries[i].offset > 0xffffffff)
@@ -1244,10 +1229,10 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 	QSORT(ctx.info, ctx.nr, pack_info_compare);
 
 	if (packs_to_drop && packs_to_drop->nr) {
-		int drop_index = 0;
+		size_t drop_index = 0;
 		int missing_drops = 0;
 
-		for (i = 0; i < ctx.nr && drop_index < packs_to_drop->nr; i++) {
+		for (size_t i = 0; i < ctx.nr && drop_index < packs_to_drop->nr; i++) {
 			int cmp = strcmp(ctx.info[i].pack_name,
 					 packs_to_drop->items[drop_index].string);
 
@@ -1265,10 +1250,8 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 			}
 		}
 
-		if (missing_drops) {
-			result = 1;
+		if (missing_drops)
 			goto cleanup;
-		}
 	}
 
 	/*
@@ -1278,7 +1261,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 	 * pack_perm[old_id] = new_id
 	 */
 	ALLOC_ARRAY(ctx.pack_perm, ctx.nr);
-	for (i = 0; i < ctx.nr; i++) {
+	for (size_t i = 0; i < ctx.nr; i++) {
 		if (ctx.info[i].expired) {
 			dropped_packs++;
 			ctx.pack_perm[ctx.info[i].orig_pack_int_id] = PACK_EXPIRED;
@@ -1287,7 +1270,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 		}
 	}
 
-	for (i = 0; i < ctx.nr; i++) {
+	for (size_t i = 0; i < ctx.nr; i++) {
 		if (ctx.info[i].expired)
 			continue;
 		pack_name_concat_len += strlen(ctx.info[i].pack_name) + 1;
@@ -1314,7 +1297,6 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 
 	if (ctx.nr - dropped_packs == 0) {
 		error(_("no pack files to index."));
-		result = 1;
 		goto cleanup;
 	}
 
@@ -1327,20 +1309,20 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 	if (ctx.incremental) {
 		struct strbuf lock_name = STRBUF_INIT;
 
-		get_midx_chain_filename(&lock_name, object_dir);
+		get_midx_chain_filename(source, &lock_name);
 		hold_lock_file_for_update(&lk, lock_name.buf, LOCK_DIE_ON_ERROR);
 		strbuf_release(&lock_name);
 
 		incr = mks_tempfile_m(midx_name.buf, 0444);
 		if (!incr) {
 			error(_("unable to create temporary MIDX layer"));
-			return -1;
+			goto cleanup;
 		}
 
 		if (adjust_shared_perm(r, get_tempfile_path(incr))) {
 			error(_("unable to adjust shared permissions for '%s'"),
 			      get_tempfile_path(incr));
-			return -1;
+			goto cleanup;
 		}
 
 		f = hashfd(r->hash_algo, get_tempfile_fd(incr),
@@ -1390,7 +1372,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 
 	if (flags & MIDX_WRITE_REV_INDEX &&
 	    git_env_bool("GIT_TEST_MIDX_WRITE_REV", 0))
-		write_midx_reverse_index(&ctx, object_dir, midx_hash);
+		write_midx_reverse_index(&ctx, midx_hash);
 
 	if (flags & MIDX_WRITE_BITMAP) {
 		struct packing_data pdata;
@@ -1413,11 +1395,10 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 		FREE_AND_NULL(ctx.entries);
 		ctx.entries_nr = 0;
 
-		if (write_midx_bitmap(&ctx, object_dir,
+		if (write_midx_bitmap(&ctx,
 				      midx_hash, &pdata, commits, commits_nr,
 				      flags) < 0) {
 			error(_("could not write multi-pack bitmap"));
-			result = 1;
 			clear_packing_data(&pdata);
 			free(commits);
 			goto cleanup;
@@ -1431,6 +1412,9 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 	 * have been freed in the previous if block.
 	 */
 
+	if (ctx.num_multi_pack_indexes_before == UINT32_MAX)
+		die(_("too many multi-pack-indexes"));
+
 	CALLOC_ARRAY(keep_hashes, ctx.num_multi_pack_indexes_before + 1);
 
 	if (ctx.incremental) {
@@ -1440,18 +1424,18 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 
 		if (!chainf) {
 			error_errno(_("unable to open multi-pack-index chain file"));
-			return -1;
+			goto cleanup;
 		}
 
 		if (link_midx_to_chain(ctx.base_midx) < 0)
-			return -1;
+			goto cleanup;
 
-		get_split_midx_filename_ext(r->hash_algo, &final_midx_name,
-					    object_dir, midx_hash, MIDX_EXT_MIDX);
+		get_split_midx_filename_ext(source, &final_midx_name,
+					    midx_hash, MIDX_EXT_MIDX);
 
 		if (rename_tempfile(&incr, final_midx_name.buf) < 0) {
 			error_errno(_("unable to rename new multi-pack-index layer"));
-			return -1;
+			goto cleanup;
 		}
 
 		strbuf_release(&final_midx_name);
@@ -1459,7 +1443,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 		keep_hashes[ctx.num_multi_pack_indexes_before] =
 			xstrdup(hash_to_hex_algop(midx_hash, r->hash_algo));
 
-		for (i = 0; i < ctx.num_multi_pack_indexes_before; i++) {
+		for (uint32_t i = 0; i < ctx.num_multi_pack_indexes_before; i++) {
 			uint32_t j = ctx.num_multi_pack_indexes_before - i - 1;
 
 			keep_hashes[j] = xstrdup(hash_to_hex_algop(get_midx_checksum(m),
@@ -1467,7 +1451,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 			m = m->base_midx;
 		}
 
-		for (i = 0; i < ctx.num_multi_pack_indexes_before + 1; i++)
+		for (uint32_t i = 0; i <= ctx.num_multi_pack_indexes_before; i++)
 			fprintf(get_lock_file_fp(&lk), "%s\n", keep_hashes[i]);
 	} else {
 		keep_hashes[ctx.num_multi_pack_indexes_before] =
@@ -1480,12 +1464,13 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 	if (commit_lock_file(&lk) < 0)
 		die_errno(_("could not write multi-pack-index"));
 
-	clear_midx_files(r, object_dir, keep_hashes,
+	clear_midx_files(source, keep_hashes,
 			 ctx.num_multi_pack_indexes_before + 1,
 			 ctx.incremental);
+	result = 0;
 
 cleanup:
-	for (i = 0; i < ctx.nr; i++) {
+	for (size_t i = 0; i < ctx.nr; i++) {
 		if (ctx.info[i].p) {
 			close_pack(ctx.info[i].p);
 			free(ctx.info[i].p);
@@ -1498,7 +1483,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 	free(ctx.pack_perm);
 	free(ctx.pack_order);
 	if (keep_hashes) {
-		for (i = 0; i < ctx.num_multi_pack_indexes_before + 1; i++)
+		for (uint32_t i = 0; i <= ctx.num_multi_pack_indexes_before; i++)
 			free((char *)keep_hashes[i]);
 		free(keep_hashes);
 	}
@@ -1509,29 +1494,29 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
 	return result;
 }
 
-int write_midx_file(struct repository *r, const char *object_dir,
+int write_midx_file(struct odb_source *source,
 		    const char *preferred_pack_name,
 		    const char *refs_snapshot, unsigned flags)
 {
-	return write_midx_internal(r, object_dir, NULL, NULL,
+	return write_midx_internal(source, NULL, NULL,
 				   preferred_pack_name, refs_snapshot,
 				   flags);
 }
 
-int write_midx_file_only(struct repository *r, const char *object_dir,
+int write_midx_file_only(struct odb_source *source,
 			 struct string_list *packs_to_include,
 			 const char *preferred_pack_name,
 			 const char *refs_snapshot, unsigned flags)
 {
-	return write_midx_internal(r, object_dir, packs_to_include, NULL,
+	return write_midx_internal(source, packs_to_include, NULL,
 				   preferred_pack_name, refs_snapshot, flags);
 }
 
-int expire_midx_packs(struct repository *r, const char *object_dir, unsigned flags)
+int expire_midx_packs(struct odb_source *source, unsigned flags)
 {
 	uint32_t i, *count, result = 0;
 	struct string_list packs_to_drop = STRING_LIST_INIT_DUP;
-	struct multi_pack_index *m = lookup_multi_pack_index(r, object_dir);
+	struct multi_pack_index *m = get_multi_pack_index(source);
 	struct progress *progress = NULL;
 
 	if (!m)
@@ -1544,7 +1529,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla
 
 	if (flags & MIDX_PROGRESS)
 		progress = start_delayed_progress(
-					  r,
+					  source->odb->repo,
 					  _("Counting referenced objects"),
 					  m->num_objects);
 	for (i = 0; i < m->num_objects; i++) {
@@ -1556,7 +1541,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla
 
 	if (flags & MIDX_PROGRESS)
 		progress = start_delayed_progress(
-					  r,
+					  source->odb->repo,
 					  _("Finding and deleting unreferenced packfiles"),
 					  m->num_packs);
 	for (i = 0; i < m->num_packs; i++) {
@@ -1566,7 +1551,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla
 		if (count[i])
 			continue;
 
-		if (prepare_midx_pack(r, m, i))
+		if (prepare_midx_pack(m, i))
 			continue;
 
 		if (m->packs[i]->pack_keep || m->packs[i]->is_cruft)
@@ -1584,7 +1569,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla
 	free(count);
 
 	if (packs_to_drop.nr)
-		result = write_midx_internal(r, object_dir, NULL,
+		result = write_midx_internal(source, NULL,
 					     &packs_to_drop, NULL, NULL, flags);
 
 	string_list_clear(&packs_to_drop, 0);
@@ -1612,13 +1597,12 @@ static int compare_by_mtime(const void *a_, const void *b_)
 	return 0;
 }
 
-static int want_included_pack(struct repository *r,
-			      struct multi_pack_index *m,
+static int want_included_pack(struct multi_pack_index *m,
 			      int pack_kept_objects,
 			      uint32_t pack_int_id)
 {
 	struct packed_git *p;
-	if (prepare_midx_pack(r, m, pack_int_id))
+	if (prepare_midx_pack(m, pack_int_id))
 		return 0;
 	p = m->packs[pack_int_id];
 	if (!pack_kept_objects && p->pack_keep)
@@ -1640,7 +1624,7 @@ static void fill_included_packs_all(struct repository *r,
 	repo_config_get_bool(r, "repack.packkeptobjects", &pack_kept_objects);
 
 	for (i = 0; i < m->num_packs; i++) {
-		if (!want_included_pack(r, m, pack_kept_objects, i))
+		if (!want_included_pack(m, pack_kept_objects, i))
 			continue;
 
 		include_pack[i] = 1;
@@ -1664,7 +1648,7 @@ static void fill_included_packs_batch(struct repository *r,
 	for (i = 0; i < m->num_packs; i++) {
 		pack_info[i].pack_int_id = i;
 
-		if (prepare_midx_pack(r, m, i))
+		if (prepare_midx_pack(m, i))
 			continue;
 
 		pack_info[i].mtime = m->packs[i]->mtime;
@@ -1683,7 +1667,7 @@ static void fill_included_packs_batch(struct repository *r,
 		struct packed_git *p = m->packs[pack_int_id];
 		uint64_t expected_size;
 
-		if (!want_included_pack(r, m, pack_kept_objects, pack_int_id))
+		if (!want_included_pack(m, pack_kept_objects, pack_int_id))
 			continue;
 
 		/*
@@ -1710,14 +1694,15 @@ static void fill_included_packs_batch(struct repository *r,
 	free(pack_info);
 }
 
-int midx_repack(struct repository *r, const char *object_dir, size_t batch_size, unsigned flags)
+int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags)
 {
+	struct repository *r = source->odb->repo;
 	int result = 0;
 	uint32_t i, packs_to_repack = 0;
 	unsigned char *include_pack;
 	struct child_process cmd = CHILD_PROCESS_INIT;
 	FILE *cmd_in;
-	struct multi_pack_index *m = lookup_multi_pack_index(r, object_dir);
+	struct multi_pack_index *m = get_multi_pack_index(source);
 
 	/*
 	 * When updating the default for these configuration
@@ -1751,7 +1736,7 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size,
 
 	strvec_push(&cmd.args, "pack-objects");
 
-	strvec_pushf(&cmd.args, "%s/pack/pack", object_dir);
+	strvec_pushf(&cmd.args, "%s/pack/pack", source->path);
 
 	if (delta_base_offset)
 		strvec_push(&cmd.args, "--delta-base-offset");
@@ -1792,7 +1777,7 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size,
 		goto cleanup;
 	}
 
-	result = write_midx_internal(r, object_dir, NULL, NULL, NULL, NULL,
+	result = write_midx_internal(source, NULL, NULL, NULL, NULL,
 				     flags);
 
 cleanup:
diff --git a/midx.c b/midx.c
index 7d40768..1d6269f 100644
--- a/midx.c
+++ b/midx.c
@@ -16,9 +16,9 @@
 #define MIDX_PACK_ERROR ((void *)(intptr_t)-1)
 
 int midx_checksum_valid(struct multi_pack_index *m);
-void clear_midx_files_ext(const char *object_dir, const char *ext,
+void clear_midx_files_ext(struct odb_source *source, const char *ext,
 			  const char *keep_hash);
-void clear_incremental_midx_files_ext(const char *object_dir, const char *ext,
+void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext,
 				      char **keep_hashes,
 				      uint32_t hashes_nr);
 int cmp_idx_or_pack_name(const char *idx_or_pack_name,
@@ -26,22 +26,20 @@ int cmp_idx_or_pack_name(const char *idx_or_pack_name,
 
 const unsigned char *get_midx_checksum(struct multi_pack_index *m)
 {
-	return m->data + m->data_len - m->repo->hash_algo->rawsz;
+	return m->data + m->data_len - m->source->odb->repo->hash_algo->rawsz;
 }
 
-void get_midx_filename(const struct git_hash_algo *hash_algo,
-		       struct strbuf *out, const char *object_dir)
+void get_midx_filename(struct odb_source *source, struct strbuf *out)
 {
-	get_midx_filename_ext(hash_algo, out, object_dir, NULL, NULL);
+	get_midx_filename_ext(source, out, NULL, NULL);
 }
 
-void get_midx_filename_ext(const struct git_hash_algo *hash_algo,
-			   struct strbuf *out, const char *object_dir,
+void get_midx_filename_ext(struct odb_source *source, struct strbuf *out,
 			   const unsigned char *hash, const char *ext)
 {
-	strbuf_addf(out, "%s/pack/multi-pack-index", object_dir);
+	strbuf_addf(out, "%s/pack/multi-pack-index", source->path);
 	if (ext)
-		strbuf_addf(out, "-%s.%s", hash_to_hex_algop(hash, hash_algo), ext);
+		strbuf_addf(out, "-%s.%s", hash_to_hex_algop(hash, source->odb->repo->hash_algo), ext);
 }
 
 static int midx_read_oid_fanout(const unsigned char *chunk_start,
@@ -95,11 +93,16 @@ static int midx_read_object_offsets(const unsigned char *chunk_start,
 	return 0;
 }
 
-static struct multi_pack_index *load_multi_pack_index_one(struct repository *r,
-							  const char *object_dir,
-							  const char *midx_name,
-							  int local)
+struct multi_pack_index *get_multi_pack_index(struct odb_source *source)
 {
+	packfile_store_prepare(source->odb->packfiles);
+	return source->midx;
+}
+
+static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *source,
+							  const char *midx_name)
+{
+	struct repository *r = source->odb->repo;
 	struct multi_pack_index *m = NULL;
 	int fd;
 	struct stat st;
@@ -129,11 +132,10 @@ static struct multi_pack_index *load_multi_pack_index_one(struct repository *r,
 	midx_map = xmmap(NULL, midx_size, PROT_READ, MAP_PRIVATE, fd, 0);
 	close(fd);
 
-	FLEX_ALLOC_STR(m, object_dir, object_dir);
+	CALLOC_ARRAY(m, 1);
 	m->data = midx_map;
 	m->data_len = midx_size;
-	m->local = local;
-	m->repo = r;
+	m->source = source;
 
 	m->signature = get_be32(m->data);
 	if (m->signature != MIDX_SIGNATURE)
@@ -224,24 +226,23 @@ static struct multi_pack_index *load_multi_pack_index_one(struct repository *r,
 	return NULL;
 }
 
-void get_midx_chain_dirname(struct strbuf *buf, const char *object_dir)
+void get_midx_chain_dirname(struct odb_source *source, struct strbuf *buf)
 {
-	strbuf_addf(buf, "%s/pack/multi-pack-index.d", object_dir);
+	strbuf_addf(buf, "%s/pack/multi-pack-index.d", source->path);
 }
 
-void get_midx_chain_filename(struct strbuf *buf, const char *object_dir)
+void get_midx_chain_filename(struct odb_source *source, struct strbuf *buf)
 {
-	get_midx_chain_dirname(buf, object_dir);
+	get_midx_chain_dirname(source, buf);
 	strbuf_addstr(buf, "/multi-pack-index-chain");
 }
 
-void get_split_midx_filename_ext(const struct git_hash_algo *hash_algo,
-				 struct strbuf *buf, const char *object_dir,
+void get_split_midx_filename_ext(struct odb_source *source, struct strbuf *buf,
 				 const unsigned char *hash, const char *ext)
 {
-	get_midx_chain_dirname(buf, object_dir);
+	get_midx_chain_dirname(source, buf);
 	strbuf_addf(buf, "/multi-pack-index-%s.%s",
-		    hash_to_hex_algop(hash, hash_algo), ext);
+		    hash_to_hex_algop(hash, source->odb->repo->hash_algo), ext);
 }
 
 static int open_multi_pack_index_chain(const struct git_hash_algo *hash_algo,
@@ -297,19 +298,18 @@ static int add_midx_to_chain(struct multi_pack_index *midx,
 	return 1;
 }
 
-static struct multi_pack_index *load_midx_chain_fd_st(struct repository *r,
-						      const char *object_dir,
-						      int local,
+static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source *source,
 						      int fd, struct stat *st,
 						      int *incomplete_chain)
 {
+	const struct git_hash_algo *hash_algo = source->odb->repo->hash_algo;
 	struct multi_pack_index *midx_chain = NULL;
 	struct strbuf buf = STRBUF_INIT;
 	int valid = 1;
 	uint32_t i, count;
 	FILE *fp = xfdopen(fd, "r");
 
-	count = st->st_size / (r->hash_algo->hexsz + 1);
+	count = st->st_size / (hash_algo->hexsz + 1);
 
 	for (i = 0; i < count; i++) {
 		struct multi_pack_index *m;
@@ -318,7 +318,7 @@ static struct multi_pack_index *load_midx_chain_fd_st(struct repository *r,
 		if (strbuf_getline_lf(&buf, fp) == EOF)
 			break;
 
-		if (get_oid_hex_algop(buf.buf, &layer, r->hash_algo)) {
+		if (get_oid_hex_algop(buf.buf, &layer, hash_algo)) {
 			warning(_("invalid multi-pack-index chain: line '%s' "
 				  "not a hash"),
 				buf.buf);
@@ -329,9 +329,9 @@ static struct multi_pack_index *load_midx_chain_fd_st(struct repository *r,
 		valid = 0;
 
 		strbuf_reset(&buf);
-		get_split_midx_filename_ext(r->hash_algo, &buf, object_dir,
+		get_split_midx_filename_ext(source, &buf,
 					    layer.hash, MIDX_EXT_MIDX);
-		m = load_multi_pack_index_one(r, object_dir, buf.buf, local);
+		m = load_multi_pack_index_one(source, buf.buf);
 
 		if (m) {
 			if (add_midx_to_chain(m, midx_chain)) {
@@ -354,40 +354,34 @@ static struct multi_pack_index *load_midx_chain_fd_st(struct repository *r,
 	return midx_chain;
 }
 
-static struct multi_pack_index *load_multi_pack_index_chain(struct repository *r,
-							    const char *object_dir,
-							    int local)
+static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *source)
 {
 	struct strbuf chain_file = STRBUF_INIT;
 	struct stat st;
 	int fd;
 	struct multi_pack_index *m = NULL;
 
-	get_midx_chain_filename(&chain_file, object_dir);
-	if (open_multi_pack_index_chain(r->hash_algo, chain_file.buf, &fd, &st)) {
+	get_midx_chain_filename(source, &chain_file);
+	if (open_multi_pack_index_chain(source->odb->repo->hash_algo, chain_file.buf, &fd, &st)) {
 		int incomplete;
 		/* ownership of fd is taken over by load function */
-		m = load_midx_chain_fd_st(r, object_dir, local, fd, &st,
-					  &incomplete);
+		m = load_midx_chain_fd_st(source, fd, &st, &incomplete);
 	}
 
 	strbuf_release(&chain_file);
 	return m;
 }
 
-struct multi_pack_index *load_multi_pack_index(struct repository *r,
-					       const char *object_dir,
-					       int local)
+struct multi_pack_index *load_multi_pack_index(struct odb_source *source)
 {
 	struct strbuf midx_name = STRBUF_INIT;
 	struct multi_pack_index *m;
 
-	get_midx_filename(r->hash_algo, &midx_name, object_dir);
+	get_midx_filename(source, &midx_name);
 
-	m = load_multi_pack_index_one(r, object_dir,
-				      midx_name.buf, local);
+	m = load_multi_pack_index_one(source, midx_name.buf);
 	if (!m)
-		m = load_multi_pack_index_chain(r, object_dir, local);
+		m = load_multi_pack_index_chain(source);
 
 	strbuf_release(&midx_name);
 
@@ -450,11 +444,11 @@ static uint32_t midx_for_pack(struct multi_pack_index **_m,
 	return pack_int_id - m->num_packs_in_base;
 }
 
-int prepare_midx_pack(struct repository *r, struct multi_pack_index *m,
+int prepare_midx_pack(struct multi_pack_index *m,
 		      uint32_t pack_int_id)
 {
+	struct repository *r = m->source->odb->repo;
 	struct strbuf pack_name = STRBUF_INIT;
-	struct strbuf key = STRBUF_INIT;
 	struct packed_git *p;
 
 	pack_int_id = midx_for_pack(&m, pack_int_id);
@@ -464,26 +458,13 @@ int prepare_midx_pack(struct repository *r, struct multi_pack_index *m,
 	if (m->packs[pack_int_id])
 		return 0;
 
-	strbuf_addf(&pack_name, "%s/pack/%s", m->object_dir,
+	strbuf_addf(&pack_name, "%s/pack/%s", m->source->path,
 		    m->pack_names[pack_int_id]);
-
-	/* pack_map holds the ".pack" name, but we have the .idx */
-	strbuf_addbuf(&key, &pack_name);
-	strbuf_strip_suffix(&key, ".idx");
-	strbuf_addstr(&key, ".pack");
-	p = hashmap_get_entry_from_hash(&r->objects->pack_map,
-					strhash(key.buf), key.buf,
-					struct packed_git, packmap_ent);
-	if (!p) {
-		p = add_packed_git(r, pack_name.buf, pack_name.len, m->local);
-		if (p) {
-			install_packed_git(r, p);
-			list_add_tail(&p->mru, &r->objects->packed_git_mru);
-		}
-	}
-
+	p = packfile_store_load_pack(r->objects->packfiles,
+				     pack_name.buf, m->source->local);
+	if (p)
+		list_add_tail(&p->mru, &r->objects->packfiles->mru);
 	strbuf_release(&pack_name);
-	strbuf_release(&key);
 
 	if (!p) {
 		m->packs[pack_int_id] = MIDX_PACK_ERROR;
@@ -507,7 +488,7 @@ struct packed_git *nth_midxed_pack(struct multi_pack_index *m,
 
 #define MIDX_CHUNK_BITMAPPED_PACKS_WIDTH (2 * sizeof(uint32_t))
 
-int nth_bitmapped_pack(struct repository *r, struct multi_pack_index *m,
+int nth_bitmapped_pack(struct multi_pack_index *m,
 		       struct bitmapped_pack *bp, uint32_t pack_int_id)
 {
 	uint32_t local_pack_int_id = midx_for_pack(&m, pack_int_id);
@@ -515,7 +496,7 @@ int nth_bitmapped_pack(struct repository *r, struct multi_pack_index *m,
 	if (!m->chunk_bitmapped_packs)
 		return error(_("MIDX does not contain the BTMP chunk"));
 
-	if (prepare_midx_pack(r, m, pack_int_id))
+	if (prepare_midx_pack(m, pack_int_id))
 		return error(_("could not load bitmapped pack %"PRIu32), pack_int_id);
 
 	bp->p = m->packs[local_pack_int_id];
@@ -534,7 +515,8 @@ int bsearch_one_midx(const struct object_id *oid, struct multi_pack_index *m,
 		     uint32_t *result)
 {
 	int ret = bsearch_hash(oid->hash, m->chunk_oid_fanout,
-			       m->chunk_oid_lookup, m->repo->hash_algo->rawsz,
+			       m->chunk_oid_lookup,
+			       m->source->odb->repo->hash_algo->rawsz,
 			       result);
 	if (result)
 		*result += m->num_objects_in_base;
@@ -565,7 +547,7 @@ struct object_id *nth_midxed_object_oid(struct object_id *oid,
 	n = midx_for_object(&m, n);
 
 	oidread(oid, m->chunk_oid_lookup + st_mult(m->hash_len, n),
-		m->repo->hash_algo);
+		m->source->odb->repo->hash_algo);
 	return oid;
 }
 
@@ -600,10 +582,9 @@ uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos)
 					       (off_t)pos * MIDX_CHUNK_OFFSET_WIDTH);
 }
 
-int fill_midx_entry(struct repository *r,
+int fill_midx_entry(struct multi_pack_index *m,
 		    const struct object_id *oid,
-		    struct pack_entry *e,
-		    struct multi_pack_index *m)
+		    struct pack_entry *e)
 {
 	uint32_t pos;
 	uint32_t pack_int_id;
@@ -615,7 +596,7 @@ int fill_midx_entry(struct repository *r,
 	midx_for_object(&m, pos);
 	pack_int_id = nth_midxed_pack_int_id(m, pos);
 
-	if (prepare_midx_pack(r, m, pack_int_id))
+	if (prepare_midx_pack(m, pack_int_id))
 		return 0;
 	p = m->packs[pack_int_id - m->num_packs_in_base];
 
@@ -723,7 +704,7 @@ int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id)
 	return 0;
 }
 
-int prepare_multi_pack_index_one(struct odb_source *source, int local)
+int prepare_multi_pack_index_one(struct odb_source *source)
 {
 	struct repository *r = source->odb->repo;
 
@@ -734,14 +715,14 @@ int prepare_multi_pack_index_one(struct odb_source *source, int local)
 	if (source->midx)
 		return 1;
 
-	source->midx = load_multi_pack_index(r, source->path, local);
+	source->midx = load_multi_pack_index(source);
 
 	return !!source->midx;
 }
 
 int midx_checksum_valid(struct multi_pack_index *m)
 {
-	return hashfile_checksum_valid(m->repo->hash_algo,
+	return hashfile_checksum_valid(m->source->odb->repo->hash_algo,
 				       m->data, m->data_len);
 }
 
@@ -768,7 +749,7 @@ static void clear_midx_file_ext(const char *full_path, size_t full_path_len UNUS
 		die_errno(_("failed to remove %s"), full_path);
 }
 
-void clear_midx_files_ext(const char *object_dir, const char *ext,
+void clear_midx_files_ext(struct odb_source *source, const char *ext,
 			  const char *keep_hash)
 {
 	struct clear_midx_data data;
@@ -782,7 +763,7 @@ void clear_midx_files_ext(const char *object_dir, const char *ext,
 	}
 	data.ext = ext;
 
-	for_each_file_in_pack_dir(object_dir,
+	for_each_file_in_pack_dir(source->path,
 				  clear_midx_file_ext,
 				  &data);
 
@@ -791,7 +772,7 @@ void clear_midx_files_ext(const char *object_dir, const char *ext,
 	free(data.keep);
 }
 
-void clear_incremental_midx_files_ext(const char *object_dir, const char *ext,
+void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext,
 				      char **keep_hashes,
 				      uint32_t hashes_nr)
 {
@@ -807,7 +788,7 @@ void clear_incremental_midx_files_ext(const char *object_dir, const char *ext,
 	data.keep_nr = hashes_nr;
 	data.ext = ext;
 
-	for_each_file_in_pack_subdir(object_dir, "multi-pack-index.d",
+	for_each_file_in_pack_subdir(source->path, "multi-pack-index.d",
 				     clear_midx_file_ext, &data);
 
 	for (i = 0; i < hashes_nr; i++)
@@ -819,7 +800,7 @@ void clear_midx_file(struct repository *r)
 {
 	struct strbuf midx = STRBUF_INIT;
 
-	get_midx_filename(r->hash_algo, &midx, r->objects->sources->path);
+	get_midx_filename(r->objects->sources, &midx);
 
 	if (r->objects) {
 		struct odb_source *source;
@@ -834,8 +815,8 @@ void clear_midx_file(struct repository *r)
 	if (remove_path(midx.buf))
 		die(_("failed to clear multi-pack-index at %s"), midx.buf);
 
-	clear_midx_files_ext(r->objects->sources->path, MIDX_EXT_BITMAP, NULL);
-	clear_midx_files_ext(r->objects->sources->path, MIDX_EXT_REV, NULL);
+	clear_midx_files_ext(r->objects->sources, MIDX_EXT_BITMAP, NULL);
+	clear_midx_files_ext(r->objects->sources, MIDX_EXT_REV, NULL);
 
 	strbuf_release(&midx);
 }
@@ -879,12 +860,13 @@ static int compare_pair_pos_vs_id(const void *_a, const void *_b)
 			display_progress(progress, _n); \
 	} while (0)
 
-int verify_midx_file(struct repository *r, const char *object_dir, unsigned flags)
+int verify_midx_file(struct odb_source *source, unsigned flags)
 {
+	struct repository *r = source->odb->repo;
 	struct pair_pos_vs_id *pairs = NULL;
 	uint32_t i;
 	struct progress *progress = NULL;
-	struct multi_pack_index *m = load_multi_pack_index(r, object_dir, 1);
+	struct multi_pack_index *m = load_multi_pack_index(source);
 	struct multi_pack_index *curr;
 	verify_midx_error = 0;
 
@@ -893,7 +875,7 @@ int verify_midx_file(struct repository *r, const char *object_dir, unsigned flag
 		struct stat sb;
 		struct strbuf filename = STRBUF_INIT;
 
-		get_midx_filename(r->hash_algo, &filename, object_dir);
+		get_midx_filename(source, &filename);
 
 		if (!stat(filename.buf, &sb)) {
 			error(_("multi-pack-index file exists, but failed to parse"));
@@ -911,7 +893,7 @@ int verify_midx_file(struct repository *r, const char *object_dir, unsigned flag
 						  _("Looking for referenced packfiles"),
 						  m->num_packs + m->num_packs_in_base);
 	for (i = 0; i < m->num_packs + m->num_packs_in_base; i++) {
-		if (prepare_midx_pack(r, m, i))
+		if (prepare_midx_pack(m, i))
 			midx_report("failed to load pack in position %d", i);
 
 		display_progress(progress, i + 1);
@@ -988,7 +970,7 @@ int verify_midx_file(struct repository *r, const char *object_dir, unsigned flag
 
 		nth_midxed_object_oid(&oid, m, pairs[i].pos);
 
-		if (!fill_midx_entry(r, &oid, &e, m)) {
+		if (!fill_midx_entry(m, &oid, &e)) {
 			midx_report(_("failed to load pack entry for oid[%d] = %s"),
 				    pairs[i].pos, oid_to_hex(&oid));
 			continue;
diff --git a/midx.h b/midx.h
index 076382d..6e54d73 100644
--- a/midx.h
+++ b/midx.h
@@ -35,6 +35,8 @@ struct odb_source;
 	"GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL"
 
 struct multi_pack_index {
+	struct odb_source *source;
+
 	const unsigned char *data;
 	size_t data_len;
 
@@ -50,7 +52,6 @@ struct multi_pack_index {
 	uint32_t num_objects;
 	int preferred_pack_idx;
 
-	int local;
 	int has_chain;
 
 	const unsigned char *chunk_pack_names;
@@ -71,10 +72,6 @@ struct multi_pack_index {
 
 	const char **pack_names;
 	struct packed_git **packs;
-
-	struct repository *repo;
-
-	char object_dir[FLEX_ARRAY];
 };
 
 #define MIDX_PROGRESS     (1 << 0)
@@ -89,24 +86,20 @@ struct multi_pack_index {
 #define MIDX_EXT_MIDX "midx"
 
 const unsigned char *get_midx_checksum(struct multi_pack_index *m);
-void get_midx_filename(const struct git_hash_algo *hash_algo,
-		       struct strbuf *out, const char *object_dir);
-void get_midx_filename_ext(const struct git_hash_algo *hash_algo,
-			   struct strbuf *out, const char *object_dir,
+void get_midx_filename(struct odb_source *source, struct strbuf *out);
+void get_midx_filename_ext(struct odb_source *source, struct strbuf *out,
 			   const unsigned char *hash, const char *ext);
-void get_midx_chain_dirname(struct strbuf *buf, const char *object_dir);
-void get_midx_chain_filename(struct strbuf *buf, const char *object_dir);
-void get_split_midx_filename_ext(const struct git_hash_algo *hash_algo,
-				 struct strbuf *buf, const char *object_dir,
+void get_midx_chain_dirname(struct odb_source *source, struct strbuf *out);
+void get_midx_chain_filename(struct odb_source *source, struct strbuf *out);
+void get_split_midx_filename_ext(struct odb_source *source, struct strbuf *buf,
 				 const unsigned char *hash, const char *ext);
 
-struct multi_pack_index *load_multi_pack_index(struct repository *r,
-					       const char *object_dir,
-					       int local);
-int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, uint32_t pack_int_id);
+struct multi_pack_index *get_multi_pack_index(struct odb_source *source);
+struct multi_pack_index *load_multi_pack_index(struct odb_source *source);
+int prepare_midx_pack(struct multi_pack_index *m, uint32_t pack_int_id);
 struct packed_git *nth_midxed_pack(struct multi_pack_index *m,
 				   uint32_t pack_int_id);
-int nth_bitmapped_pack(struct repository *r, struct multi_pack_index *m,
+int nth_bitmapped_pack(struct multi_pack_index *m,
 		       struct bitmapped_pack *bp, uint32_t pack_int_id);
 int bsearch_one_midx(const struct object_id *oid, struct multi_pack_index *m,
 		     uint32_t *result);
@@ -118,27 +111,27 @@ uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos);
 struct object_id *nth_midxed_object_oid(struct object_id *oid,
 					struct multi_pack_index *m,
 					uint32_t n);
-int fill_midx_entry(struct repository *r, const struct object_id *oid, struct pack_entry *e, struct multi_pack_index *m);
+int fill_midx_entry(struct multi_pack_index *m, const struct object_id *oid, struct pack_entry *e);
 int midx_contains_pack(struct multi_pack_index *m,
 		       const char *idx_or_pack_name);
 int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id);
-int prepare_multi_pack_index_one(struct odb_source *source, int local);
+int prepare_multi_pack_index_one(struct odb_source *source);
 
 /*
  * Variant of write_midx_file which writes a MIDX containing only the packs
  * specified in packs_to_include.
  */
-int write_midx_file(struct repository *r, const char *object_dir,
+int write_midx_file(struct odb_source *source,
 		    const char *preferred_pack_name, const char *refs_snapshot,
 		    unsigned flags);
-int write_midx_file_only(struct repository *r, const char *object_dir,
+int write_midx_file_only(struct odb_source *source,
 			 struct string_list *packs_to_include,
 			 const char *preferred_pack_name,
 			 const char *refs_snapshot, unsigned flags);
 void clear_midx_file(struct repository *r);
-int verify_midx_file(struct repository *r, const char *object_dir, unsigned flags);
-int expire_midx_packs(struct repository *r, const char *object_dir, unsigned flags);
-int midx_repack(struct repository *r, const char *object_dir, size_t batch_size, unsigned flags);
+int verify_midx_file(struct odb_source *source, unsigned flags);
+int expire_midx_packs(struct odb_source *source, unsigned flags);
+int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags);
 
 void close_midx(struct multi_pack_index *m);
 
diff --git a/notes.c b/notes.c
index 9736276..9a2e918 100644
--- a/notes.c
+++ b/notes.c
@@ -894,7 +894,7 @@ static int string_list_add_note_lines(struct string_list *list,
 	 * later, along with any empty strings that came from empty
 	 * lines within the file.
 	 */
-	string_list_split(list, data, '\n', -1);
+	string_list_split(list, data, "\n", -1);
 	free(data);
 	return 0;
 }
@@ -973,8 +973,8 @@ void string_list_add_refs_from_colon_sep(struct string_list *list,
 	char *globs_copy = xstrdup(globs);
 	int i;
 
-	string_list_split_in_place(&split, globs_copy, ":", -1);
-	string_list_remove_empty_items(&split, 0);
+	string_list_split_in_place_f(&split, globs_copy, ":", -1,
+				     STRING_LIST_SPLIT_NONEMPTY);
 
 	for (i = 0; i < split.nr; i++)
 		string_list_add_refs_by_glob(list, split.items[i].string);
diff --git a/object-file.c b/object-file.c
index 2bc36ab..4675c8e 100644
--- a/object-file.c
+++ b/object-file.c
@@ -10,7 +10,6 @@
 #define USE_THE_REPOSITORY_VARIABLE
 
 #include "git-compat-util.h"
-#include "bulk-checkin.h"
 #include "convert.h"
 #include "dir.h"
 #include "environment.h"
@@ -28,6 +27,8 @@
 #include "read-cache-ll.h"
 #include "setup.h"
 #include "streaming.h"
+#include "tempfile.h"
+#include "tmp-objdir.h"
 
 /* The maximum size for an object header. */
 #define MAX_HEADER_LEN 32
@@ -666,6 +667,93 @@ void hash_object_file(const struct git_hash_algo *algo, const void *buf,
 	write_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen);
 }
 
+struct transaction_packfile {
+	char *pack_tmp_name;
+	struct hashfile *f;
+	off_t offset;
+	struct pack_idx_option pack_idx_opts;
+
+	struct pack_idx_entry **written;
+	uint32_t alloc_written;
+	uint32_t nr_written;
+};
+
+struct odb_transaction {
+	struct object_database *odb;
+
+	struct tmp_objdir *objdir;
+	struct transaction_packfile packfile;
+};
+
+static void prepare_loose_object_transaction(struct odb_transaction *transaction)
+{
+	/*
+	 * We lazily create the temporary object directory
+	 * the first time an object might be added, since
+	 * callers may not know whether any objects will be
+	 * added at the time they call object_file_transaction_begin.
+	 */
+	if (!transaction || transaction->objdir)
+		return;
+
+	transaction->objdir = tmp_objdir_create(transaction->odb->repo, "bulk-fsync");
+	if (transaction->objdir)
+		tmp_objdir_replace_primary_odb(transaction->objdir, 0);
+}
+
+static void fsync_loose_object_transaction(struct odb_transaction *transaction,
+					   int fd, const char *filename)
+{
+	/*
+	 * If we have an active ODB transaction, we issue a call that
+	 * cleans the filesystem page cache but avoids a hardware flush
+	 * command. Later on we will issue a single hardware flush
+	 * before renaming the objects to their final names as part of
+	 * flush_batch_fsync.
+	 */
+	if (!transaction || !transaction->objdir ||
+	    git_fsync(fd, FSYNC_WRITEOUT_ONLY) < 0) {
+		if (errno == ENOSYS)
+			warning(_("core.fsyncMethod = batch is unsupported on this platform"));
+		fsync_or_die(fd, filename);
+	}
+}
+
+/*
+ * Cleanup after batch-mode fsync_object_files.
+ */
+static void flush_loose_object_transaction(struct odb_transaction *transaction)
+{
+	struct strbuf temp_path = STRBUF_INIT;
+	struct tempfile *temp;
+
+	if (!transaction->objdir)
+		return;
+
+	/*
+	 * Issue a full hardware flush against a temporary file to ensure
+	 * that all objects are durable before any renames occur. The code in
+	 * fsync_loose_object_transaction has already issued a writeout
+	 * request, but it has not flushed any writeback cache in the storage
+	 * hardware or any filesystem logs. This fsync call acts as a barrier
+	 * to ensure that the data in each new object file is durable before
+	 * the final name is visible.
+	 */
+	strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX",
+		    repo_get_object_directory(transaction->odb->repo));
+	temp = xmks_tempfile(temp_path.buf);
+	fsync_or_die(get_tempfile_fd(temp), get_tempfile_path(temp));
+	delete_tempfile(&temp);
+	strbuf_release(&temp_path);
+
+	/*
+	 * Make the object files visible in the primary ODB after their data is
+	 * fully durable.
+	 */
+	tmp_objdir_migrate(transaction->objdir);
+	transaction->objdir = NULL;
+}
+
 /* Finalize a file on disk, and close it. */
 static void close_loose_object(struct odb_source *source,
 			       int fd, const char *filename)
@@ -674,7 +762,7 @@ static void close_loose_object(struct odb_source *source,
 		goto out;
 
 	if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
-		fsync_loose_object_bulk_checkin(fd, filename);
+		fsync_loose_object_transaction(source->odb->transaction, fd, filename);
 	else if (fsync_object_files > 0)
 		fsync_or_die(fd, filename);
 	else
@@ -852,7 +940,7 @@ static int write_loose_object(struct odb_source *source,
 	static struct strbuf filename = STRBUF_INIT;
 
 	if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
-		prepare_loose_object_bulk_checkin();
+		prepare_loose_object_transaction(source->odb->transaction);
 
 	odb_loose_path(source, &filename, oid);
 
@@ -941,7 +1029,7 @@ int stream_loose_object(struct odb_source *source,
 	int hdrlen;
 
 	if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
-		prepare_loose_object_bulk_checkin();
+		prepare_loose_object_transaction(source->odb->transaction);
 
 	/* Since oid is not determined, save tmp file to odb path. */
 	strbuf_addf(&filename, "%s/", source->path);
@@ -1243,6 +1331,274 @@ static int index_core(struct index_state *istate,
 	return ret;
 }
 
+static int already_written(struct odb_transaction *transaction,
+			   struct object_id *oid)
+{
+	/* The object may already exist in the repository */
+	if (odb_has_object(transaction->odb, oid,
+			   HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR))
+		return 1;
+
+	/* Might want to keep the list sorted */
+	for (uint32_t i = 0; i < transaction->packfile.nr_written; i++)
+		if (oideq(&transaction->packfile.written[i]->oid, oid))
+			return 1;
+
+	/* This is a new object we need to keep */
+	return 0;
+}
+
+/* Lazily create backing packfile for the state */
+static void prepare_packfile_transaction(struct odb_transaction *transaction,
+					 unsigned flags)
+{
+	struct transaction_packfile *state = &transaction->packfile;
+	if (!(flags & INDEX_WRITE_OBJECT) || state->f)
+		return;
+
+	state->f = create_tmp_packfile(transaction->odb->repo,
+				       &state->pack_tmp_name);
+	reset_pack_idx_option(&state->pack_idx_opts);
+
+	/* Pretend we are going to write only one object */
+	state->offset = write_pack_header(state->f, 1);
+	if (!state->offset)
+		die_errno("unable to write pack header");
+}
+
+/*
+ * Read the contents from fd for size bytes, streaming it to the
+ * packfile in state while updating the hash in ctx. Signal a failure
+ * by returning a negative value when the resulting pack would exceed
+ * the pack size limit and this is not the first object in the pack,
+ * so that the caller can discard what we wrote from the current pack
+ * by truncating it and opening a new one. The caller will then call
+ * us again after rewinding the input fd.
+ *
+ * The already_hashed_to pointer is kept untouched by the caller to
+ * make sure we do not hash the same byte when we are called
+ * again. This way, the caller does not have to checkpoint its hash
+ * status before calling us just in case we ask it to call us again
+ * with a new pack.
+ */
+static int stream_blob_to_pack(struct transaction_packfile *state,
+			       struct git_hash_ctx *ctx, off_t *already_hashed_to,
+			       int fd, size_t size, const char *path,
+			       unsigned flags)
+{
+	git_zstream s;
+	unsigned char ibuf[16384];
+	unsigned char obuf[16384];
+	unsigned hdrlen;
+	int status = Z_OK;
+	int write_object = (flags & INDEX_WRITE_OBJECT);
+	off_t offset = 0;
+
+	git_deflate_init(&s, pack_compression_level);
+
+	hdrlen = encode_in_pack_object_header(obuf, sizeof(obuf), OBJ_BLOB, size);
+	s.next_out = obuf + hdrlen;
+	s.avail_out = sizeof(obuf) - hdrlen;
+
+	while (status != Z_STREAM_END) {
+		if (size && !s.avail_in) {
+			size_t rsize = size < sizeof(ibuf) ? size : sizeof(ibuf);
+			ssize_t read_result = read_in_full(fd, ibuf, rsize);
+			if (read_result < 0)
+				die_errno("failed to read from '%s'", path);
+			if ((size_t)read_result != rsize)
+				die("failed to read %u bytes from '%s'",
+				    (unsigned)rsize, path);
+			offset += rsize;
+			if (*already_hashed_to < offset) {
+				size_t hsize = offset - *already_hashed_to;
+				if (rsize < hsize)
+					hsize = rsize;
+				if (hsize)
+					git_hash_update(ctx, ibuf, hsize);
+				*already_hashed_to = offset;
+			}
+			s.next_in = ibuf;
+			s.avail_in = rsize;
+			size -= rsize;
+		}
+
+		status = git_deflate(&s, size ? 0 : Z_FINISH);
+
+		if (!s.avail_out || status == Z_STREAM_END) {
+			if (write_object) {
+				size_t written = s.next_out - obuf;
+
+				/* would we bust the size limit? */
+				if (state->nr_written &&
+				    pack_size_limit_cfg &&
+				    pack_size_limit_cfg < state->offset + written) {
+					git_deflate_abort(&s);
+					return -1;
+				}
+
+				hashwrite(state->f, obuf, written);
+				state->offset += written;
+			}
+			s.next_out = obuf;
+			s.avail_out = sizeof(obuf);
+		}
+
+		switch (status) {
+		case Z_OK:
+		case Z_BUF_ERROR:
+		case Z_STREAM_END:
+			continue;
+		default:
+			die("unexpected deflate failure: %d", status);
+		}
+	}
+	git_deflate_end(&s);
+	return 0;
+}
+
+static void flush_packfile_transaction(struct odb_transaction *transaction)
+{
+	struct transaction_packfile *state = &transaction->packfile;
+	struct repository *repo = transaction->odb->repo;
+	unsigned char hash[GIT_MAX_RAWSZ];
+	struct strbuf packname = STRBUF_INIT;
+	char *idx_tmp_name = NULL;
+
+	if (!state->f)
+		return;
+
+	if (state->nr_written == 0) {
+		close(state->f->fd);
+		free_hashfile(state->f);
+		unlink(state->pack_tmp_name);
+		goto clear_exit;
+	} else if (state->nr_written == 1) {
+		finalize_hashfile(state->f, hash, FSYNC_COMPONENT_PACK,
+				  CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE);
+	} else {
+		int fd = finalize_hashfile(state->f, hash, FSYNC_COMPONENT_PACK, 0);
+		fixup_pack_header_footer(repo->hash_algo, fd, hash, state->pack_tmp_name,
+					 state->nr_written, hash,
+					 state->offset);
+		close(fd);
+	}
+
+	strbuf_addf(&packname, "%s/pack/pack-%s.",
+		    repo_get_object_directory(transaction->odb->repo),
+		    hash_to_hex_algop(hash, repo->hash_algo));
+
+	stage_tmp_packfiles(repo, &packname, state->pack_tmp_name,
+			    state->written, state->nr_written, NULL,
+			    &state->pack_idx_opts, hash, &idx_tmp_name);
+	rename_tmp_packfile_idx(repo, &packname, &idx_tmp_name);
+
+	for (uint32_t i = 0; i < state->nr_written; i++)
+		free(state->written[i]);
+
+clear_exit:
+	free(idx_tmp_name);
+	free(state->pack_tmp_name);
+	free(state->written);
+	memset(state, 0, sizeof(*state));
+
+	strbuf_release(&packname);
+	/* Make objects we just wrote available to ourselves */
+	odb_reprepare(repo->objects);
+}
+
+/*
+ * This writes the specified object to a packfile. Objects written here
+ * during the same transaction are written to the same packfile. The
+ * packfile is not flushed until the transaction is flushed. The caller
+ * is expected to ensure a valid transaction is setup for objects to be
+ * recorded to.
+ *
+ * This also bypasses the usual "convert-to-git" dance, and that is on
+ * purpose. We could write a streaming version of the converting
+ * functions and insert that before feeding the data to fast-import
+ * (or equivalent in-core API described above). However, that is
+ * somewhat complicated, as we do not know the size of the filter
+ * result, which we need to know beforehand when writing a git object.
+ * Since the primary motivation for trying to stream from the working
+ * tree file and to avoid mmaping it in core is to deal with large
+ * binary blobs, they generally do not want to get any conversion, and
+ * callers should avoid this code path when filters are requested.
+ */
+static int index_blob_packfile_transaction(struct odb_transaction *transaction,
+					   struct object_id *result_oid, int fd,
+					   size_t size, const char *path,
+					   unsigned flags)
+{
+	struct transaction_packfile *state = &transaction->packfile;
+	off_t seekback, already_hashed_to;
+	struct git_hash_ctx ctx;
+	unsigned char obuf[16384];
+	unsigned header_len;
+	struct hashfile_checkpoint checkpoint;
+	struct pack_idx_entry *idx = NULL;
+
+	seekback = lseek(fd, 0, SEEK_CUR);
+	if (seekback == (off_t)-1)
+		return error("cannot find the current offset");
+
+	header_len = format_object_header((char *)obuf, sizeof(obuf),
+					  OBJ_BLOB, size);
+	transaction->odb->repo->hash_algo->init_fn(&ctx);
+	git_hash_update(&ctx, obuf, header_len);
+
+	/* Note: idx is non-NULL when we are writing */
+	if ((flags & INDEX_WRITE_OBJECT) != 0) {
+		CALLOC_ARRAY(idx, 1);
+
+		prepare_packfile_transaction(transaction, flags);
+		hashfile_checkpoint_init(state->f, &checkpoint);
+	}
+
+	already_hashed_to = 0;
+
+	while (1) {
+		prepare_packfile_transaction(transaction, flags);
+		if (idx) {
+			hashfile_checkpoint(state->f, &checkpoint);
+			idx->offset = state->offset;
+			crc32_begin(state->f);
+		}
+		if (!stream_blob_to_pack(state, &ctx, &already_hashed_to,
+					 fd, size, path, flags))
+			break;
+		/*
+		 * Writing this object to the current pack will make
+		 * it too big; we need to truncate it, start a new
+		 * pack, and write into it.
+		 */
+		if (!idx)
+			BUG("should not happen");
+		hashfile_truncate(state->f, &checkpoint);
+		state->offset = checkpoint.offset;
+		flush_packfile_transaction(transaction);
+		if (lseek(fd, seekback, SEEK_SET) == (off_t)-1)
+			return error("cannot seek back");
+	}
+	git_hash_final_oid(result_oid, &ctx);
+	if (!idx)
+		return 0;
+
+	idx->crc32 = crc32_end(state->f);
+	if (already_written(transaction, result_oid)) {
+		hashfile_truncate(state->f, &checkpoint);
+		state->offset = checkpoint.offset;
+		free(idx);
+	} else {
+		oidcpy(&idx->oid, result_oid);
+		ALLOC_GROW(state->written,
+			   state->nr_written + 1,
+			   state->alloc_written);
+		state->written[state->nr_written++] = idx;
+	}
+	return 0;
+}
+
 int index_fd(struct index_state *istate, struct object_id *oid,
 	     int fd, struct stat *st,
 	     enum object_type type, const char *path, unsigned flags)
@@ -1253,18 +1609,27 @@ int index_fd(struct index_state *istate, struct object_id *oid,
 	 * Call xsize_t() only when needed to avoid potentially unnecessary
 	 * die() for large files.
 	 */
-	if (type == OBJ_BLOB && path && would_convert_to_git_filter_fd(istate, path))
+	if (type == OBJ_BLOB && path && would_convert_to_git_filter_fd(istate, path)) {
 		ret = index_stream_convert_blob(istate, oid, fd, path, flags);
-	else if (!S_ISREG(st->st_mode))
+	} else if (!S_ISREG(st->st_mode)) {
 		ret = index_pipe(istate, oid, fd, type, path, flags);
-	else if ((st->st_size >= 0 && (size_t) st->st_size <= repo_settings_get_big_file_threshold(istate->repo)) ||
-		 type != OBJ_BLOB ||
-		 (path && would_convert_to_git(istate, path)))
+	} else if ((st->st_size >= 0 &&
+		    (size_t)st->st_size <= repo_settings_get_big_file_threshold(istate->repo)) ||
+		   type != OBJ_BLOB ||
+		   (path && would_convert_to_git(istate, path))) {
 		ret = index_core(istate, oid, fd, xsize_t(st->st_size),
 				 type, path, flags);
-	else
-		ret = index_blob_bulk_checkin(oid, fd, xsize_t(st->st_size), path,
-					     flags);
+	} else {
+		struct odb_transaction *transaction;
+
+		transaction = odb_transaction_begin(the_repository->objects);
+		ret = index_blob_packfile_transaction(the_repository->objects->transaction,
+						      oid, fd,
+						      xsize_t(st->st_size),
+						      path, flags);
+		odb_transaction_commit(transaction);
+	}
+
 	close(fd);
 	return ret;
 }
@@ -1601,3 +1966,32 @@ int read_loose_object(struct repository *repo,
 		munmap(map, mapsize);
 	return ret;
 }
+
+struct odb_transaction *object_file_transaction_begin(struct odb_source *source)
+{
+	struct object_database *odb = source->odb;
+
+	if (odb->transaction)
+		return NULL;
+
+	CALLOC_ARRAY(odb->transaction, 1);
+	odb->transaction->odb = odb;
+
+	return odb->transaction;
+}
+
+void object_file_transaction_commit(struct odb_transaction *transaction)
+{
+	if (!transaction)
+		return;
+
+	/*
+	 * Ensure the transaction ending matches the pending transaction.
+	 */
+	ASSERT(transaction == transaction->odb->transaction);
+
+	flush_loose_object_transaction(transaction);
+	flush_packfile_transaction(transaction);
+	transaction->odb->transaction = NULL;
+	free(transaction);
+}
diff --git a/object-file.h b/object-file.h
index 15d9763..3fd48dc 100644
--- a/object-file.h
+++ b/object-file.h
@@ -218,4 +218,20 @@ int read_loose_object(struct repository *repo,
 		      void **contents,
 		      struct object_info *oi);
 
+struct odb_transaction;
+
+/*
+ * Tell the object database to optimize for adding
+ * multiple objects. object_file_transaction_commit must be called
+ * to make new objects visible. If a transaction is already
+ * pending, NULL is returned.
+ */
+struct odb_transaction *object_file_transaction_begin(struct odb_source *source);
+
+/*
+ * Tell the object database to make any objects from the
+ * current transaction visible.
+ */
+void object_file_transaction_commit(struct odb_transaction *transaction);
+
 #endif /* OBJECT_FILE_H */
diff --git a/object-name.c b/object-name.c
index 11aa0e6..f6902e1 100644
--- a/object-name.c
+++ b/object-name.c
@@ -213,7 +213,7 @@ static void find_short_packed_object(struct disambiguate_state *ds)
 			unique_in_midx(m, ds);
 	}
 
-	for (p = get_packed_git(ds->repo); p && !ds->ambiguous;
+	for (p = packfile_store_get_packs(ds->repo->objects->packfiles); p && !ds->ambiguous;
 	     p = p->next)
 		unique_in_pack(p, ds);
 }
@@ -596,7 +596,7 @@ static enum get_oid_result get_short_oid(struct repository *r,
 	 * or migrated from loose to packed.
 	 */
 	if (status == MISSING_OBJECT) {
-		reprepare_packed_git(r);
+		odb_reprepare(r->objects);
 		find_short_object_filename(&ds);
 		find_short_packed_object(&ds);
 		status = finish_object_disambiguation(&ds, oid);
@@ -696,15 +696,14 @@ static inline char get_hex_char_from_oid(const struct object_id *oid,
 		return hex[oid->hash[pos >> 1] & 0xf];
 }
 
-static int extend_abbrev_len(const struct object_id *oid, void *cb_data)
+static int extend_abbrev_len(const struct object_id *oid,
+			     struct min_abbrev_data *mad)
 {
-	struct min_abbrev_data *mad = cb_data;
-
 	unsigned int i = mad->init_len;
 	while (mad->hex[i] && mad->hex[i] == get_hex_char_from_oid(oid, i))
 		i++;
 
-	if (i < GIT_MAX_RAWSZ && i >= mad->cur_len)
+	if (mad->hex[i] && i >= mad->cur_len)
 		mad->cur_len = i + 1;
 
 	return 0;
@@ -806,7 +805,7 @@ static void find_abbrev_len_packed(struct min_abbrev_data *mad)
 			find_abbrev_len_for_midx(m, mad);
 	}
 
-	for (p = get_packed_git(mad->repo); p; p = p->next)
+	for (p = packfile_store_get_packs(mad->repo->objects->packfiles); p; p = p->next)
 		find_abbrev_len_for_pack(p, mad);
 }
 
@@ -1525,7 +1524,8 @@ struct grab_nth_branch_switch_cbdata {
 	struct strbuf *sb;
 };
 
-static int grab_nth_branch_switch(struct object_id *ooid UNUSED,
+static int grab_nth_branch_switch(const char *refname UNUSED,
+				  struct object_id *ooid UNUSED,
 				  struct object_id *noid UNUSED,
 				  const char *email UNUSED,
 				  timestamp_t timestamp UNUSED,
@@ -1857,55 +1857,35 @@ int repo_get_oid_committish(struct repository *r,
 			    const char *name,
 			    struct object_id *oid)
 {
-	struct object_context unused;
-	int ret = get_oid_with_context(r, name, GET_OID_COMMITTISH,
-				       oid, &unused);
-	object_context_release(&unused);
-	return ret;
+	return repo_get_oid_with_flags(r, name, oid, GET_OID_COMMITTISH);
 }
 
 int repo_get_oid_treeish(struct repository *r,
 			 const char *name,
 			 struct object_id *oid)
 {
-	struct object_context unused;
-	int ret = get_oid_with_context(r, name, GET_OID_TREEISH,
-				       oid, &unused);
-	object_context_release(&unused);
-	return ret;
+	return repo_get_oid_with_flags(r, name, oid, GET_OID_TREEISH);
 }
 
 int repo_get_oid_commit(struct repository *r,
 			const char *name,
 			struct object_id *oid)
 {
-	struct object_context unused;
-	int ret = get_oid_with_context(r, name, GET_OID_COMMIT,
-				       oid, &unused);
-	object_context_release(&unused);
-	return ret;
+	return repo_get_oid_with_flags(r, name, oid, GET_OID_COMMIT);
 }
 
 int repo_get_oid_tree(struct repository *r,
 		      const char *name,
 		      struct object_id *oid)
 {
-	struct object_context unused;
-	int ret = get_oid_with_context(r, name, GET_OID_TREE,
-				       oid, &unused);
-	object_context_release(&unused);
-	return ret;
+	return repo_get_oid_with_flags(r, name, oid, GET_OID_TREE);
 }
 
 int repo_get_oid_blob(struct repository *r,
 		      const char *name,
 		      struct object_id *oid)
 {
-	struct object_context unused;
-	int ret = get_oid_with_context(r, name, GET_OID_BLOB,
-				       oid, &unused);
-	object_context_release(&unused);
-	return ret;
+	return repo_get_oid_with_flags(r, name, oid, GET_OID_BLOB);
 }
 
 /* Must be called only when object_name:filename doesn't exist. */
diff --git a/object.c b/object.c
index c1553ee..986114a 100644
--- a/object.c
+++ b/object.c
@@ -517,12 +517,11 @@ struct parsed_object_pool *parsed_object_pool_new(struct repository *repo)
 	memset(o, 0, sizeof(*o));
 
 	o->repo = repo;
-	o->blob_state = allocate_alloc_state();
-	o->tree_state = allocate_alloc_state();
-	o->commit_state = allocate_alloc_state();
-	o->tag_state = allocate_alloc_state();
-	o->object_state = allocate_alloc_state();
-
+	o->blob_state = alloc_state_alloc();
+	o->tree_state = alloc_state_alloc();
+	o->commit_state = alloc_state_alloc();
+	o->tag_state = alloc_state_alloc();
+	o->object_state = alloc_state_alloc();
 	o->is_shallow = -1;
 	CALLOC_ARRAY(o->shallow_stat, 1);
 
@@ -573,16 +572,11 @@ void parsed_object_pool_clear(struct parsed_object_pool *o)
 	o->buffer_slab = NULL;
 
 	parsed_object_pool_reset_commit_grafts(o);
-	clear_alloc_state(o->blob_state);
-	clear_alloc_state(o->tree_state);
-	clear_alloc_state(o->commit_state);
-	clear_alloc_state(o->tag_state);
-	clear_alloc_state(o->object_state);
+	alloc_state_free_and_null(&o->blob_state);
+	alloc_state_free_and_null(&o->tree_state);
+	alloc_state_free_and_null(&o->commit_state);
+	alloc_state_free_and_null(&o->tag_state);
+	alloc_state_free_and_null(&o->object_state);
 	stat_validity_clear(o->shallow_stat);
-	FREE_AND_NULL(o->blob_state);
-	FREE_AND_NULL(o->tree_state);
-	FREE_AND_NULL(o->commit_state);
-	FREE_AND_NULL(o->tag_state);
-	FREE_AND_NULL(o->object_state);
 	FREE_AND_NULL(o->shallow_stat);
 }
diff --git a/odb.c b/odb.c
index 2a92a01..00a6e71 100644
--- a/odb.c
+++ b/odb.c
@@ -139,23 +139,21 @@ static void read_info_alternates(struct object_database *odb,
 				 const char *relative_base,
 				 int depth);
 
-static int link_alt_odb_entry(struct object_database *odb,
-			      const struct strbuf *entry,
-			      const char *relative_base,
-			      int depth,
-			      const char *normalized_objdir)
+static struct odb_source *link_alt_odb_entry(struct object_database *odb,
+					     const char *dir,
+					     const char *relative_base,
+					     int depth)
 {
-	struct odb_source *alternate;
+	struct odb_source *alternate = NULL;
 	struct strbuf pathbuf = STRBUF_INIT;
 	struct strbuf tmp = STRBUF_INIT;
 	khiter_t pos;
-	int ret = -1;
 
-	if (!is_absolute_path(entry->buf) && relative_base) {
+	if (!is_absolute_path(dir) && relative_base) {
 		strbuf_realpath(&pathbuf, relative_base, 1);
 		strbuf_addch(&pathbuf, '/');
 	}
-	strbuf_addbuf(&pathbuf, entry);
+	strbuf_addstr(&pathbuf, dir);
 
 	if (!strbuf_realpath(&tmp, pathbuf.buf, 0)) {
 		error(_("unable to normalize alternate object path: %s"),
@@ -171,11 +169,15 @@ static int link_alt_odb_entry(struct object_database *odb,
 	while (pathbuf.len && pathbuf.buf[pathbuf.len - 1] == '/')
 		strbuf_setlen(&pathbuf, pathbuf.len - 1);
 
-	if (!alt_odb_usable(odb, &pathbuf, normalized_objdir, &pos))
+	strbuf_reset(&tmp);
+	strbuf_realpath(&tmp, odb->sources->path, 1);
+
+	if (!alt_odb_usable(odb, &pathbuf, tmp.buf, &pos))
 		goto error;
 
 	CALLOC_ARRAY(alternate, 1);
 	alternate->odb = odb;
+	alternate->local = false;
 	/* pathbuf.buf is already in r->objects->source_by_path */
 	alternate->path = strbuf_detach(&pathbuf, NULL);
 
@@ -188,11 +190,11 @@ static int link_alt_odb_entry(struct object_database *odb,
 
 	/* recursively add alternates */
 	read_info_alternates(odb, alternate->path, depth + 1);
-	ret = 0;
+
  error:
 	strbuf_release(&tmp);
 	strbuf_release(&pathbuf);
-	return ret;
+	return alternate;
 }
 
 static const char *parse_alt_odb_entry(const char *string,
@@ -227,8 +229,7 @@ static const char *parse_alt_odb_entry(const char *string,
 static void link_alt_odb_entries(struct object_database *odb, const char *alt,
 				 int sep, const char *relative_base, int depth)
 {
-	struct strbuf objdirbuf = STRBUF_INIT;
-	struct strbuf entry = STRBUF_INIT;
+	struct strbuf dir = STRBUF_INIT;
 
 	if (!alt || !*alt)
 		return;
@@ -239,17 +240,13 @@ static void link_alt_odb_entries(struct object_database *odb, const char *alt,
 		return;
 	}
 
-	strbuf_realpath(&objdirbuf, odb->sources->path, 1);
-
 	while (*alt) {
-		alt = parse_alt_odb_entry(alt, sep, &entry);
-		if (!entry.len)
+		alt = parse_alt_odb_entry(alt, sep, &dir);
+		if (!dir.len)
 			continue;
-		link_alt_odb_entry(odb, &entry,
-				   relative_base, depth, objdirbuf.buf);
+		link_alt_odb_entry(odb, dir.buf, relative_base, depth);
 	}
-	strbuf_release(&entry);
-	strbuf_release(&objdirbuf);
+	strbuf_release(&dir);
 }
 
 static void read_info_alternates(struct object_database *odb,
@@ -272,7 +269,7 @@ static void read_info_alternates(struct object_database *odb,
 }
 
 void odb_add_to_alternates_file(struct object_database *odb,
-				const char *reference)
+				const char *dir)
 {
 	struct lock_file lock = LOCK_INIT;
 	char *alts = repo_git_path(odb->repo, "objects/info/alternates");
@@ -289,7 +286,7 @@ void odb_add_to_alternates_file(struct object_database *odb,
 		struct strbuf line = STRBUF_INIT;
 
 		while (strbuf_getline(&line, in) != EOF) {
-			if (!strcmp(reference, line.buf)) {
+			if (!strcmp(dir, line.buf)) {
 				found = 1;
 				break;
 			}
@@ -305,27 +302,24 @@ void odb_add_to_alternates_file(struct object_database *odb,
 	if (found) {
 		rollback_lock_file(&lock);
 	} else {
-		fprintf_or_die(out, "%s\n", reference);
+		fprintf_or_die(out, "%s\n", dir);
 		if (commit_lock_file(&lock))
 			die_errno(_("unable to move new alternates file into place"));
 		if (odb->loaded_alternates)
-			link_alt_odb_entries(odb, reference,
-					     '\n', NULL, 0);
+			link_alt_odb_entries(odb, dir, '\n', NULL, 0);
 	}
 	free(alts);
 }
 
-void odb_add_to_alternates_memory(struct object_database *odb,
-				  const char *reference)
+struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
+						const char *dir)
 {
 	/*
 	 * Make sure alternates are initialized, or else our entry may be
 	 * overwritten when they are.
 	 */
 	odb_prepare_alternates(odb);
-
-	link_alt_odb_entries(odb, reference,
-			     '\n', NULL, 0);
+	return link_alt_odb_entry(odb, dir, NULL, 0);
 }
 
 struct odb_source *odb_set_temporary_primary_source(struct object_database *odb,
@@ -463,6 +457,12 @@ struct odb_source *odb_find_source(struct object_database *odb, const char *obj_
 	free(obj_dir_real);
 	strbuf_release(&odb_path_real);
 
+	return source;
+}
+
+struct odb_source *odb_find_source_or_die(struct object_database *odb, const char *obj_dir)
+{
+	struct odb_source *source = odb_find_source(odb, obj_dir);
 	if (!source)
 		die(_("could not find object directory matching %s"), obj_dir);
 	return source;
@@ -694,7 +694,7 @@ static int do_oid_object_info_extended(struct object_database *odb,
 
 		/* Not a loose object; someone else may have just packed it. */
 		if (!(flags & OBJECT_INFO_QUICK)) {
-			reprepare_packed_git(odb->repo);
+			odb_reprepare(odb->repo->objects);
 			if (find_pack_entry(odb->repo, real, &e))
 				break;
 		}
@@ -996,8 +996,7 @@ struct object_database *odb_new(struct repository *repo)
 
 	memset(o, 0, sizeof(*o));
 	o->repo = repo;
-	INIT_LIST_HEAD(&o->packed_git_mru);
-	hashmap_init(&o->pack_map, pack_map_entry_cmp, NULL, 0);
+	o->packfiles = packfile_store_new(o);
 	pthread_mutex_init(&o->replace_mutex, NULL);
 	string_list_init_dup(&o->submodule_source_paths);
 	return o;
@@ -1035,19 +1034,44 @@ void odb_clear(struct object_database *o)
 		free((char *) o->cached_objects[i].value.buf);
 	FREE_AND_NULL(o->cached_objects);
 
-	INIT_LIST_HEAD(&o->packed_git_mru);
 	close_object_store(o);
+	packfile_store_free(o->packfiles);
+	o->packfiles = NULL;
+
+	string_list_clear(&o->submodule_source_paths, 0);
+}
+
+void odb_reprepare(struct object_database *o)
+{
+	struct odb_source *source;
+
+	obj_read_lock();
 
 	/*
-	 * `close_object_store()` only closes the packfiles, but doesn't free
-	 * them. We thus have to do this manually.
+	 * Reprepare alt odbs, in case the alternates file was modified
+	 * during the course of this process. This only _adds_ odbs to
+	 * the linked list, so existing odbs will continue to exist for
+	 * the lifetime of the process.
 	 */
-	for (struct packed_git *p = o->packed_git, *next; p; p = next) {
-		next = p->next;
-		free(p);
-	}
-	o->packed_git = NULL;
+	o->loaded_alternates = 0;
+	odb_prepare_alternates(o);
 
-	hashmap_clear(&o->pack_map);
-	string_list_clear(&o->submodule_source_paths, 0);
+	for (source = o->sources; source; source = source->next)
+		odb_clear_loose_cache(source);
+
+	o->approximate_object_count_valid = 0;
+
+	packfile_store_reprepare(o->packfiles);
+
+	obj_read_unlock();
+}
+
+struct odb_transaction *odb_transaction_begin(struct object_database *odb)
+{
+	return object_file_transaction_begin(odb->sources);
+}
+
+void odb_transaction_commit(struct odb_transaction *transaction)
+{
+	object_file_transaction_commit(transaction);
 }
diff --git a/odb.h b/odb.h
index 3dfc66d..e6602dd 100644
--- a/odb.h
+++ b/odb.h
@@ -3,7 +3,6 @@
 
 #include "hashmap.h"
 #include "object.h"
-#include "list.h"
 #include "oidset.h"
 #include "oidmap.h"
 #include "string-list.h"
@@ -64,6 +63,14 @@ struct odb_source {
 	struct multi_pack_index *midx;
 
 	/*
+	 * Figure out whether this is the local source of the owning
+	 * repository, which would typically be its ".git/objects" directory.
+	 * This local object directory is usually where objects would be
+	 * written to.
+	 */
+	bool local;
+
+	/*
 	 * This is a temporary object store created by the tmp_objdir
 	 * facility. Disable ref updates since the objects in the store
 	 * might be discarded on rollback.
@@ -83,7 +90,9 @@ struct odb_source {
 };
 
 struct packed_git;
+struct packfile_store;
 struct cached_object_entry;
+struct odb_transaction;
 
 /*
  * The object database encapsulates access to objects in a repository. It
@@ -95,6 +104,13 @@ struct object_database {
 	struct repository *repo;
 
 	/*
+	 * State of current current object database transaction. Only one
+	 * transaction may be pending at a time. Is NULL when no transaction is
+	 * configured.
+	 */
+	struct odb_transaction *transaction;
+
+	/*
 	 * Set of all object directories; the main directory is first (and
 	 * cannot be NULL after initialization). Subsequent directories are
 	 * alternates.
@@ -123,20 +139,8 @@ struct object_database {
 	struct commit_graph *commit_graph;
 	unsigned commit_graph_attempted : 1; /* if loading has been attempted */
 
-	/*
-	 * private data
-	 *
-	 * should only be accessed directly by packfile.c
-	 */
-
-	struct packed_git *packed_git;
-	/* A most-recently-used ordered version of the packed_git list. */
-	struct list_head packed_git_mru;
-
-	struct {
-		struct packed_git **packs;
-		unsigned flags;
-	} kept_pack_cache;
+	/* Should only be accessed directly by packfile.c and midx.c. */
+	struct packfile_store *packfiles;
 
 	/*
 	 * This is meant to hold a *small* number of objects that you would
@@ -148,12 +152,6 @@ struct object_database {
 	size_t cached_object_nr, cached_object_alloc;
 
 	/*
-	 * A map of packfiles to packed_git structs for tracking which
-	 * packs have been loaded already.
-	 */
-	struct hashmap pack_map;
-
-	/*
 	 * A fast, rough count of the number of objects in the repository.
 	 * These two fields are not meant for direct access. Use
 	 * repo_approximate_object_count() instead.
@@ -162,12 +160,6 @@ struct object_database {
 	unsigned approximate_object_count_valid : 1;
 
 	/*
-	 * Whether packed_git has already been populated with this repository's
-	 * packs.
-	 */
-	unsigned packed_git_initialized : 1;
-
-	/*
 	 * Submodule source paths that will be added as additional sources to
 	 * allow lookup of submodule objects via the main object database.
 	 */
@@ -178,11 +170,33 @@ struct object_database *odb_new(struct repository *repo);
 void odb_clear(struct object_database *o);
 
 /*
- * Find source by its object directory path. Dies in case the source couldn't
- * be found.
+ * Clear caches, reload alternates and then reload object sources so that new
+ * objects may become accessible.
+ */
+void odb_reprepare(struct object_database *o);
+
+/*
+ * Starts an ODB transaction. Subsequent objects are written to the transaction
+ * and not committed until odb_transaction_commit() is invoked on the
+ * transaction. If the ODB already has a pending transaction, NULL is returned.
+ */
+struct odb_transaction *odb_transaction_begin(struct object_database *odb);
+
+/*
+ * Commits an ODB transaction making the written objects visible. If the
+ * specified transaction is NULL, the function is a no-op.
+ */
+void odb_transaction_commit(struct odb_transaction *transaction);
+
+/*
+ * Find source by its object directory path. Returns a `NULL` pointer in case
+ * the source could not be found.
  */
 struct odb_source *odb_find_source(struct object_database *odb, const char *obj_dir);
 
+/* Same as `odb_find_source()`, but dies in case the source doesn't exist. */
+struct odb_source *odb_find_source_or_die(struct object_database *odb, const char *obj_dir);
+
 /*
  * Replace the current writable object directory with the specified temporary
  * object directory; returns the former primary source.
@@ -257,8 +271,8 @@ void odb_add_to_alternates_file(struct object_database *odb,
  * recursive alternates it points to), but do not modify the on-disk alternates
  * file.
  */
-void odb_add_to_alternates_memory(struct object_database *odb,
-				  const char *dir);
+struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
+						const char *dir);
 
 /*
  * Read an object from the database. Returns the object data and assigns object
@@ -475,37 +489,4 @@ static inline int odb_write_object(struct object_database *odb,
 	return odb_write_object_ext(odb, buf, len, type, oid, NULL, 0);
 }
 
-/* Compatibility wrappers, to be removed once Git 2.51 has been released. */
-#include "repository.h"
-
-static inline int oid_object_info_extended(struct repository *r,
-					   const struct object_id *oid,
-					   struct object_info *oi,
-					   unsigned flags)
-{
-	return odb_read_object_info_extended(r->objects, oid, oi, flags);
-}
-
-static inline int oid_object_info(struct repository *r,
-				  const struct object_id *oid,
-				  unsigned long *sizep)
-{
-	return odb_read_object_info(r->objects, oid, sizep);
-}
-
-static inline void *repo_read_object_file(struct repository *r,
-					  const struct object_id *oid,
-					  enum object_type *type,
-					  unsigned long *size)
-{
-	return odb_read_object(r->objects, oid, type, size);
-}
-
-static inline int has_object(struct repository *r,
-			     const struct object_id *oid,
-			     unsigned flags)
-{
-	return odb_has_object(r->objects, oid, flags);
-}
-
 #endif /* ODB_H */
diff --git a/oss-fuzz/fuzz-commit-graph.c b/oss-fuzz/fuzz-commit-graph.c
index fbb77fe..fb8b878 100644
--- a/oss-fuzz/fuzz-commit-graph.c
+++ b/oss-fuzz/fuzz-commit-graph.c
@@ -4,9 +4,6 @@
 #include "commit-graph.h"
 #include "repository.h"
 
-struct commit_graph *parse_commit_graph(struct repo_settings *s,
-					void *graph_map, size_t graph_size);
-
 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
 
 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
@@ -22,9 +19,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
 	 * possible.
 	 */
 	repo_set_hash_algo(the_repository, GIT_HASH_SHA1);
+	the_repository->settings.initialized = 1;
 	the_repository->settings.commit_graph_generation_version = 2;
 	the_repository->settings.commit_graph_changed_paths_version = 1;
-	g = parse_commit_graph(&the_repository->settings, (void *)data, size);
+	g = parse_commit_graph(the_repository, (void *)data, size);
 	repo_clear(the_repository);
 	free_commit_graph(g);
 
diff --git a/pack-bitmap.c b/pack-bitmap.c
index d14421e..ac71035 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -216,7 +216,7 @@ static uint32_t bitmap_num_objects(struct bitmap_index *index)
 static struct repository *bitmap_repo(struct bitmap_index *bitmap_git)
 {
 	if (bitmap_is_midx(bitmap_git))
-		return bitmap_git->midx->repo;
+		return bitmap_git->midx->source->odb->repo;
 	return bitmap_git->pack->repo;
 }
 
@@ -418,13 +418,12 @@ char *midx_bitmap_filename(struct multi_pack_index *midx)
 {
 	struct strbuf buf = STRBUF_INIT;
 	if (midx->has_chain)
-		get_split_midx_filename_ext(midx->repo->hash_algo, &buf,
-					    midx->object_dir,
+		get_split_midx_filename_ext(midx->source, &buf,
 					    get_midx_checksum(midx),
 					    MIDX_EXT_BITMAP);
 	else
-		get_midx_filename_ext(midx->repo->hash_algo, &buf,
-				      midx->object_dir, get_midx_checksum(midx),
+		get_midx_filename_ext(midx->source, &buf,
+				      get_midx_checksum(midx),
 				      MIDX_EXT_BITMAP);
 
 	return strbuf_detach(&buf, NULL);
@@ -463,7 +462,7 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git,
 
 	if (bitmap_git->pack || bitmap_git->midx) {
 		struct strbuf buf = STRBUF_INIT;
-		get_midx_filename(midx->repo->hash_algo, &buf, midx->object_dir);
+		get_midx_filename(midx->source, &buf);
 		trace2_data_string("bitmap", bitmap_repo(bitmap_git),
 				   "ignoring extra midx bitmap file", buf.buf);
 		close(fd);
@@ -493,7 +492,7 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git,
 	}
 
 	for (i = 0; i < bitmap_git->midx->num_packs + bitmap_git->midx->num_packs_in_base; i++) {
-		if (prepare_midx_pack(bitmap_repo(bitmap_git), bitmap_git->midx, i)) {
+		if (prepare_midx_pack(bitmap_git->midx, i)) {
 			warning(_("could not open pack %s"),
 				bitmap_git->midx->pack_names[i]);
 			goto cleanup;
@@ -665,7 +664,7 @@ static int open_pack_bitmap(struct repository *r,
 	struct packed_git *p;
 	int ret = -1;
 
-	for (p = get_all_packs(r); p; p = p->next) {
+	for (p = packfile_store_get_all_packs(r->objects->packfiles); p; p = p->next) {
 		if (open_pack_bitmap_1(bitmap_git, p) == 0) {
 			ret = 0;
 			/*
@@ -2466,7 +2465,7 @@ void reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
 		struct multi_pack_index *m = bitmap_git->midx;
 		for (i = 0; i < m->num_packs + m->num_packs_in_base; i++) {
 			struct bitmapped_pack pack;
-			if (nth_bitmapped_pack(r, bitmap_git->midx, &pack, i) < 0) {
+			if (nth_bitmapped_pack(bitmap_git->midx, &pack, i) < 0) {
 				warning(_("unable to load pack: '%s', disabling pack-reuse"),
 					bitmap_git->midx->pack_names[i]);
 				free(packs);
@@ -3363,7 +3362,7 @@ int verify_bitmap_files(struct repository *r)
 		free(midx_bitmap_name);
 	}
 
-	for (struct packed_git *p = get_all_packs(r);
+	for (struct packed_git *p = packfile_store_get_all_packs(r->objects->packfiles);
 	     p; p = p->next) {
 		char *pack_bitmap_name = pack_bitmap_filename(p);
 		res |= verify_bitmap_file(r->hash_algo, pack_bitmap_name);
diff --git a/pack-objects.c b/pack-objects.c
index a9d9855..9d6ee72 100644
--- a/pack-objects.c
+++ b/pack-objects.c
@@ -4,6 +4,7 @@
 #include "pack-objects.h"
 #include "packfile.h"
 #include "parse.h"
+#include "repository.h"
 
 static uint32_t locate_object_entry_hash(struct packing_data *pdata,
 					 const struct object_id *oid,
@@ -86,6 +87,7 @@ struct object_entry *packlist_find(struct packing_data *pdata,
 
 static void prepare_in_pack_by_idx(struct packing_data *pdata)
 {
+	struct packfile_store *packs = pdata->repo->objects->packfiles;
 	struct packed_git **mapping, *p;
 	int cnt = 0, nr = 1U << OE_IN_PACK_BITS;
 
@@ -95,7 +97,7 @@ static void prepare_in_pack_by_idx(struct packing_data *pdata)
 	 * (i.e. in_pack_idx also zero) should return NULL.
 	 */
 	mapping[cnt++] = NULL;
-	for (p = get_all_packs(pdata->repo); p; p = p->next, cnt++) {
+	for (p = packfile_store_get_all_packs(packs); p; p = p->next, cnt++) {
 		if (cnt == nr) {
 			free(mapping);
 			return;
diff --git a/pack-refs.c b/pack-refs.c
new file mode 100644
index 0000000..1a5e07d
--- /dev/null
+++ b/pack-refs.c
@@ -0,0 +1,56 @@
+#include "builtin.h"
+#include "config.h"
+#include "environment.h"
+#include "pack-refs.h"
+#include "parse-options.h"
+#include "refs.h"
+#include "revision.h"
+
+int pack_refs_core(int argc,
+		   const char **argv,
+		   const char *prefix,
+		   struct repository *repo,
+		   const char * const *usage_opts)
+{
+	struct ref_exclusions excludes = REF_EXCLUSIONS_INIT;
+	struct string_list included_refs = STRING_LIST_INIT_NODUP;
+	struct pack_refs_opts pack_refs_opts = {
+		.exclusions = &excludes,
+		.includes = &included_refs,
+		.flags = PACK_REFS_PRUNE,
+	};
+	struct string_list option_excluded_refs = STRING_LIST_INIT_NODUP;
+	struct string_list_item *item;
+	int pack_all = 0;
+	int ret;
+
+	struct option opts[] = {
+		OPT_BOOL(0, "all",   &pack_all, N_("pack everything")),
+		OPT_BIT(0, "prune", &pack_refs_opts.flags, N_("prune loose refs (default)"), PACK_REFS_PRUNE),
+		OPT_BIT(0, "auto", &pack_refs_opts.flags, N_("auto-pack refs as needed"), PACK_REFS_AUTO),
+		OPT_STRING_LIST(0, "include", pack_refs_opts.includes, N_("pattern"),
+			N_("references to include")),
+		OPT_STRING_LIST(0, "exclude", &option_excluded_refs, N_("pattern"),
+			N_("references to exclude")),
+		OPT_END(),
+	};
+	repo_config(repo, git_default_config, NULL);
+	if (parse_options(argc, argv, prefix, opts, usage_opts, 0))
+		usage_with_options(usage_opts, opts);
+
+	for_each_string_list_item(item, &option_excluded_refs)
+		add_ref_exclusion(pack_refs_opts.exclusions, item->string);
+
+	if (pack_all)
+		string_list_append(pack_refs_opts.includes, "*");
+
+	if (!pack_refs_opts.includes->nr)
+		string_list_append(pack_refs_opts.includes, "refs/tags/*");
+
+	ret = refs_optimize(get_main_ref_store(repo), &pack_refs_opts);
+
+	clear_ref_exclusions(&excludes);
+	string_list_clear(&included_refs, 0);
+	string_list_clear(&option_excluded_refs, 0);
+	return ret;
+}
diff --git a/pack-refs.h b/pack-refs.h
new file mode 100644
index 0000000..5de27e7
--- /dev/null
+++ b/pack-refs.h
@@ -0,0 +1,23 @@
+#ifndef PACK_REFS_H
+#define PACK_REFS_H
+
+struct repository;
+
+/*
+ * Shared usage string for options common to git-pack-refs(1)
+ * and git-refs-optimize(1). The command-specific part (e.g., "git refs optimize ")
+ * must be prepended by the caller.
+ */
+#define PACK_REFS_OPTS \
+	"[--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]"
+
+/*
+ * The core logic for pack-refs and its clones.
+ */
+int pack_refs_core(int argc,
+		   const char **argv,
+		   const char *prefix,
+		   struct repository *repo,
+		   const char * const *usage_opts);
+
+#endif /* PACK_REFS_H */
diff --git a/pack-revindex.c b/pack-revindex.c
index 0cc422a..d0791cc 100644
--- a/pack-revindex.c
+++ b/pack-revindex.c
@@ -379,25 +379,25 @@ int load_midx_revindex(struct multi_pack_index *m)
 		 * not want to accidentally call munmap() in the middle of the
 		 * MIDX.
 		 */
-		trace2_data_string("load_midx_revindex", m->repo,
+		trace2_data_string("load_midx_revindex", m->source->odb->repo,
 				   "source", "midx");
 		m->revindex_data = (const uint32_t *)m->chunk_revindex;
 		return 0;
 	}
 
-	trace2_data_string("load_midx_revindex", m->repo,
+	trace2_data_string("load_midx_revindex", m->source->odb->repo,
 			   "source", "rev");
 
 	if (m->has_chain)
-		get_split_midx_filename_ext(m->repo->hash_algo, &revindex_name,
-					    m->object_dir, get_midx_checksum(m),
+		get_split_midx_filename_ext(m->source, &revindex_name,
+					    get_midx_checksum(m),
 					    MIDX_EXT_REV);
 	else
-		get_midx_filename_ext(m->repo->hash_algo, &revindex_name,
-				      m->object_dir, get_midx_checksum(m),
+		get_midx_filename_ext(m->source, &revindex_name,
+				      get_midx_checksum(m),
 				      MIDX_EXT_REV);
 
-	ret = load_revindex_from_disk(m->repo->hash_algo,
+	ret = load_revindex_from_disk(m->source->odb->repo->hash_algo,
 				      revindex_name.buf,
 				      m->num_objects,
 				      &m->revindex_map,
diff --git a/packfile.c b/packfile.c
index 5d73932..5a7caec 100644
--- a/packfile.c
+++ b/packfile.c
@@ -278,7 +278,7 @@ static int unuse_one_window(struct packed_git *current)
 
 	if (current)
 		scan_windows(current, &lru_p, &lru_w, &lru_l);
-	for (p = current->repo->objects->packed_git; p; p = p->next)
+	for (p = current->repo->objects->packfiles->packs; p; p = p->next)
 		scan_windows(p, &lru_p, &lru_w, &lru_l);
 	if (lru_p) {
 		munmap(lru_w->base, lru_w->len);
@@ -362,13 +362,8 @@ void close_pack(struct packed_git *p)
 void close_object_store(struct object_database *o)
 {
 	struct odb_source *source;
-	struct packed_git *p;
 
-	for (p = o->packed_git; p; p = p->next)
-		if (p->do_not_close)
-			BUG("want to close pack marked 'do-not-close'");
-		else
-			close_pack(p);
+	packfile_store_close(o->packfiles);
 
 	for (source = o->sources; source; source = source->next) {
 		if (source->midx)
@@ -468,7 +463,7 @@ static int close_one_pack(struct repository *r)
 	struct pack_window *mru_w = NULL;
 	int accept_windows_inuse = 1;
 
-	for (p = r->objects->packed_git; p; p = p->next) {
+	for (p = r->objects->packfiles->packs; p; p = p->next) {
 		if (p->pack_fd == -1)
 			continue;
 		find_lru_pack(p, &lru_p, &mru_w, &accept_windows_inuse);
@@ -784,16 +779,44 @@ struct packed_git *add_packed_git(struct repository *r, const char *path,
 	return p;
 }
 
-void install_packed_git(struct repository *r, struct packed_git *pack)
+void packfile_store_add_pack(struct packfile_store *store,
+			     struct packed_git *pack)
 {
 	if (pack->pack_fd != -1)
 		pack_open_fds++;
 
-	pack->next = r->objects->packed_git;
-	r->objects->packed_git = pack;
+	pack->next = store->packs;
+	store->packs = pack;
 
 	hashmap_entry_init(&pack->packmap_ent, strhash(pack->pack_name));
-	hashmap_add(&r->objects->pack_map, &pack->packmap_ent);
+	hashmap_add(&store->map, &pack->packmap_ent);
+}
+
+struct packed_git *packfile_store_load_pack(struct packfile_store *store,
+					    const char *idx_path, int local)
+{
+	struct strbuf key = STRBUF_INIT;
+	struct packed_git *p;
+
+	/*
+	 * We're being called with the path to the index file, but `pack_map`
+	 * holds the path to the packfile itself.
+	 */
+	strbuf_addstr(&key, idx_path);
+	strbuf_strip_suffix(&key, ".idx");
+	strbuf_addstr(&key, ".pack");
+
+	p = hashmap_get_entry_from_hash(&store->map, strhash(key.buf), key.buf,
+					struct packed_git, packmap_ent);
+	if (!p) {
+		p = add_packed_git(store->odb->repo, idx_path,
+				   strlen(idx_path), local);
+		if (p)
+			packfile_store_add_pack(store, p);
+	}
+
+	strbuf_release(&key);
+	return p;
 }
 
 void (*report_garbage)(unsigned seen_bits, const char *path);
@@ -895,23 +918,14 @@ static void prepare_pack(const char *full_name, size_t full_name_len,
 			 const char *file_name, void *_data)
 {
 	struct prepare_pack_data *data = (struct prepare_pack_data *)_data;
-	struct packed_git *p;
 	size_t base_len = full_name_len;
 
 	if (strip_suffix_mem(full_name, &base_len, ".idx") &&
 	    !(data->m && midx_contains_pack(data->m, file_name))) {
-		struct hashmap_entry hent;
-		char *pack_name = xstrfmt("%.*s.pack", (int)base_len, full_name);
-		unsigned int hash = strhash(pack_name);
-		hashmap_entry_init(&hent, hash);
-
-		/* Don't reopen a pack we already have. */
-		if (!hashmap_get(&data->r->objects->pack_map, &hent, pack_name)) {
-			p = add_packed_git(data->r, full_name, full_name_len, data->local);
-			if (p)
-				install_packed_git(data->r, p);
-		}
-		free(pack_name);
+		char *trimmed_path = xstrndup(full_name, full_name_len);
+		packfile_store_load_pack(data->r->objects->packfiles,
+					 trimmed_path, data->local);
+		free(trimmed_path);
 	}
 
 	if (!report_garbage)
@@ -935,14 +949,14 @@ static void prepare_pack(const char *full_name, size_t full_name_len,
 		report_garbage(PACKDIR_FILE_GARBAGE, full_name);
 }
 
-static void prepare_packed_git_one(struct odb_source *source, int local)
+static void prepare_packed_git_one(struct odb_source *source)
 {
 	struct string_list garbage = STRING_LIST_INIT_DUP;
 	struct prepare_pack_data data = {
 		.m = source->midx,
 		.r = source->odb->repo,
 		.garbage = &garbage,
-		.local = local,
+		.local = source->local,
 	};
 
 	for_each_file_in_pack_dir(source->path, prepare_pack, &data);
@@ -951,40 +965,6 @@ static void prepare_packed_git_one(struct odb_source *source, int local)
 	string_list_clear(data.garbage, 0);
 }
 
-static void prepare_packed_git(struct repository *r);
-/*
- * Give a fast, rough count of the number of objects in the repository. This
- * ignores loose objects completely. If you have a lot of them, then either
- * you should repack because your performance will be awful, or they are
- * all unreachable objects about to be pruned, in which case they're not really
- * interesting as a measure of repo size in the first place.
- */
-unsigned long repo_approximate_object_count(struct repository *r)
-{
-	if (!r->objects->approximate_object_count_valid) {
-		struct odb_source *source;
-		unsigned long count = 0;
-		struct packed_git *p;
-
-		prepare_packed_git(r);
-
-		for (source = r->objects->sources; source; source = source->next) {
-			struct multi_pack_index *m = get_multi_pack_index(source);
-			if (m)
-				count += m->num_objects;
-		}
-
-		for (p = r->objects->packed_git; p; p = p->next) {
-			if (open_pack_index(p))
-				continue;
-			count += p->num_objects;
-		}
-		r->objects->approximate_object_count = count;
-		r->objects->approximate_object_count_valid = 1;
-	}
-	return r->objects->approximate_object_count;
-}
-
 DEFINE_LIST_SORT(static, sort_packs, struct packed_git, next);
 
 static int sort_pack(const struct packed_git *a, const struct packed_git *b)
@@ -1013,95 +993,98 @@ static int sort_pack(const struct packed_git *a, const struct packed_git *b)
 	return -1;
 }
 
-static void rearrange_packed_git(struct repository *r)
-{
-	sort_packs(&r->objects->packed_git, sort_pack);
-}
-
-static void prepare_packed_git_mru(struct repository *r)
+static void packfile_store_prepare_mru(struct packfile_store *store)
 {
 	struct packed_git *p;
 
-	INIT_LIST_HEAD(&r->objects->packed_git_mru);
+	INIT_LIST_HEAD(&store->mru);
 
-	for (p = r->objects->packed_git; p; p = p->next)
-		list_add_tail(&p->mru, &r->objects->packed_git_mru);
+	for (p = store->packs; p; p = p->next)
+		list_add_tail(&p->mru, &store->mru);
 }
 
-static void prepare_packed_git(struct repository *r)
+void packfile_store_prepare(struct packfile_store *store)
 {
 	struct odb_source *source;
 
-	if (r->objects->packed_git_initialized)
+	if (store->initialized)
 		return;
 
-	odb_prepare_alternates(r->objects);
-	for (source = r->objects->sources; source; source = source->next) {
-		int local = (source == r->objects->sources);
-		prepare_multi_pack_index_one(source, local);
-		prepare_packed_git_one(source, local);
+	odb_prepare_alternates(store->odb);
+	for (source = store->odb->sources; source; source = source->next) {
+		prepare_multi_pack_index_one(source);
+		prepare_packed_git_one(source);
 	}
-	rearrange_packed_git(r);
+	sort_packs(&store->packs, sort_pack);
 
-	prepare_packed_git_mru(r);
-	r->objects->packed_git_initialized = 1;
+	packfile_store_prepare_mru(store);
+	store->initialized = true;
 }
 
-void reprepare_packed_git(struct repository *r)
+void packfile_store_reprepare(struct packfile_store *store)
 {
-	struct odb_source *source;
-
-	obj_read_lock();
-
-	/*
-	 * Reprepare alt odbs, in case the alternates file was modified
-	 * during the course of this process. This only _adds_ odbs to
-	 * the linked list, so existing odbs will continue to exist for
-	 * the lifetime of the process.
-	 */
-	r->objects->loaded_alternates = 0;
-	odb_prepare_alternates(r->objects);
-
-	for (source = r->objects->sources; source; source = source->next)
-		odb_clear_loose_cache(source);
-
-	r->objects->approximate_object_count_valid = 0;
-	r->objects->packed_git_initialized = 0;
-	prepare_packed_git(r);
-	obj_read_unlock();
+	store->initialized = false;
+	packfile_store_prepare(store);
 }
 
-struct packed_git *get_packed_git(struct repository *r)
+struct packed_git *packfile_store_get_packs(struct packfile_store *store)
 {
-	prepare_packed_git(r);
-	return r->objects->packed_git;
+	packfile_store_prepare(store);
+	return store->packs;
 }
 
-struct multi_pack_index *get_multi_pack_index(struct odb_source *source)
+struct packed_git *packfile_store_get_all_packs(struct packfile_store *store)
 {
-	prepare_packed_git(source->odb->repo);
-	return source->midx;
-}
+	packfile_store_prepare(store);
 
-struct packed_git *get_all_packs(struct repository *r)
-{
-	prepare_packed_git(r);
-
-	for (struct odb_source *source = r->objects->sources; source; source = source->next) {
+	for (struct odb_source *source = store->odb->sources; source; source = source->next) {
 		struct multi_pack_index *m = source->midx;
 		if (!m)
 			continue;
 		for (uint32_t i = 0; i < m->num_packs + m->num_packs_in_base; i++)
-			prepare_midx_pack(r, m, i);
+			prepare_midx_pack(m, i);
 	}
 
-	return r->objects->packed_git;
+	return store->packs;
 }
 
-struct list_head *get_packed_git_mru(struct repository *r)
+struct list_head *packfile_store_get_packs_mru(struct packfile_store *store)
 {
-	prepare_packed_git(r);
-	return &r->objects->packed_git_mru;
+	packfile_store_prepare(store);
+	return &store->mru;
+}
+
+/*
+ * Give a fast, rough count of the number of objects in the repository. This
+ * ignores loose objects completely. If you have a lot of them, then either
+ * you should repack because your performance will be awful, or they are
+ * all unreachable objects about to be pruned, in which case they're not really
+ * interesting as a measure of repo size in the first place.
+ */
+unsigned long repo_approximate_object_count(struct repository *r)
+{
+	if (!r->objects->approximate_object_count_valid) {
+		struct odb_source *source;
+		unsigned long count = 0;
+		struct packed_git *p;
+
+		packfile_store_prepare(r->objects->packfiles);
+
+		for (source = r->objects->sources; source; source = source->next) {
+			struct multi_pack_index *m = get_multi_pack_index(source);
+			if (m)
+				count += m->num_objects;
+		}
+
+		for (p = r->objects->packfiles->packs; p; p = p->next) {
+			if (open_pack_index(p))
+				continue;
+			count += p->num_objects;
+		}
+		r->objects->approximate_object_count = count;
+		r->objects->approximate_object_count_valid = 1;
+	}
+	return r->objects->approximate_object_count;
 }
 
 unsigned long unpack_object_header_buffer(const unsigned char *buf,
@@ -1156,7 +1139,7 @@ unsigned long get_size_from_delta(struct packed_git *p,
 		 *
 		 * Other worrying sections could be the call to close_pack_fd(),
 		 * which can close packs even with in-use windows, and to
-		 * reprepare_packed_git(). Regarding the former, mmap doc says:
+		 * odb_reprepare(). Regarding the former, mmap doc says:
 		 * "closing the file descriptor does not unmap the region". And
 		 * for the latter, it won't re-open already available packs.
 		 */
@@ -1220,7 +1203,7 @@ const struct packed_git *has_packed_and_bad(struct repository *r,
 {
 	struct packed_git *p;
 
-	for (p = r->objects->packed_git; p; p = p->next)
+	for (p = r->objects->packfiles->packs; p; p = p->next)
 		if (oidset_contains(&p->bad_objects, oid))
 			return p;
 	return NULL;
@@ -2075,19 +2058,19 @@ int find_pack_entry(struct repository *r, const struct object_id *oid, struct pa
 {
 	struct list_head *pos;
 
-	prepare_packed_git(r);
+	packfile_store_prepare(r->objects->packfiles);
 
 	for (struct odb_source *source = r->objects->sources; source; source = source->next)
-		if (source->midx && fill_midx_entry(r, oid, e, source->midx))
+		if (source->midx && fill_midx_entry(source->midx, oid, e))
 			return 1;
 
-	if (!r->objects->packed_git)
+	if (!r->objects->packfiles->packs)
 		return 0;
 
-	list_for_each(pos, &r->objects->packed_git_mru) {
+	list_for_each(pos, &r->objects->packfiles->mru) {
 		struct packed_git *p = list_entry(pos, struct packed_git, mru);
 		if (!p->multi_pack_index && fill_pack_entry(oid, e, p)) {
-			list_move(&p->mru, &r->objects->packed_git_mru);
+			list_move(&p->mru, &r->objects->packfiles->mru);
 			return 1;
 		}
 	}
@@ -2097,19 +2080,19 @@ int find_pack_entry(struct repository *r, const struct object_id *oid, struct pa
 static void maybe_invalidate_kept_pack_cache(struct repository *r,
 					     unsigned flags)
 {
-	if (!r->objects->kept_pack_cache.packs)
+	if (!r->objects->packfiles->kept_cache.packs)
 		return;
-	if (r->objects->kept_pack_cache.flags == flags)
+	if (r->objects->packfiles->kept_cache.flags == flags)
 		return;
-	FREE_AND_NULL(r->objects->kept_pack_cache.packs);
-	r->objects->kept_pack_cache.flags = 0;
+	FREE_AND_NULL(r->objects->packfiles->kept_cache.packs);
+	r->objects->packfiles->kept_cache.flags = 0;
 }
 
 struct packed_git **kept_pack_cache(struct repository *r, unsigned flags)
 {
 	maybe_invalidate_kept_pack_cache(r, flags);
 
-	if (!r->objects->kept_pack_cache.packs) {
+	if (!r->objects->packfiles->kept_cache.packs) {
 		struct packed_git **packs = NULL;
 		size_t nr = 0, alloc = 0;
 		struct packed_git *p;
@@ -2122,7 +2105,7 @@ struct packed_git **kept_pack_cache(struct repository *r, unsigned flags)
 		 * covers, one kept and one not kept, but the midx returns only
 		 * the non-kept version.
 		 */
-		for (p = get_all_packs(r); p; p = p->next) {
+		for (p = packfile_store_get_all_packs(r->objects->packfiles); p; p = p->next) {
 			if ((p->pack_keep && (flags & ON_DISK_KEEP_PACKS)) ||
 			    (p->pack_keep_in_core && (flags & IN_CORE_KEEP_PACKS))) {
 				ALLOC_GROW(packs, nr + 1, alloc);
@@ -2132,11 +2115,11 @@ struct packed_git **kept_pack_cache(struct repository *r, unsigned flags)
 		ALLOC_GROW(packs, nr + 1, alloc);
 		packs[nr] = NULL;
 
-		r->objects->kept_pack_cache.packs = packs;
-		r->objects->kept_pack_cache.flags = flags;
+		r->objects->packfiles->kept_cache.packs = packs;
+		r->objects->packfiles->kept_cache.flags = flags;
 	}
 
-	return r->objects->kept_pack_cache.packs;
+	return r->objects->packfiles->kept_cache.packs;
 }
 
 int find_kept_pack_entry(struct repository *r,
@@ -2219,7 +2202,7 @@ int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,
 	int r = 0;
 	int pack_errors = 0;
 
-	for (p = get_all_packs(repo); p; p = p->next) {
+	for (p = packfile_store_get_all_packs(repo->objects->packfiles); p; p = p->next) {
 		if ((flags & FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)
 			continue;
 		if ((flags & FOR_EACH_OBJECT_PROMISOR_ONLY) &&
@@ -2333,3 +2316,46 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l
 	*len = hdr - out;
 	return 0;
 }
+
+static int pack_map_entry_cmp(const void *cmp_data UNUSED,
+			      const struct hashmap_entry *entry,
+			      const struct hashmap_entry *entry2,
+			      const void *keydata)
+{
+	const char *key = keydata;
+	const struct packed_git *pg1, *pg2;
+
+	pg1 = container_of(entry, const struct packed_git, packmap_ent);
+	pg2 = container_of(entry2, const struct packed_git, packmap_ent);
+
+	return strcmp(pg1->pack_name, key ? key : pg2->pack_name);
+}
+
+struct packfile_store *packfile_store_new(struct object_database *odb)
+{
+	struct packfile_store *store;
+	CALLOC_ARRAY(store, 1);
+	store->odb = odb;
+	INIT_LIST_HEAD(&store->mru);
+	hashmap_init(&store->map, pack_map_entry_cmp, NULL, 0);
+	return store;
+}
+
+void packfile_store_free(struct packfile_store *store)
+{
+	for (struct packed_git *p = store->packs, *next; p; p = next) {
+		next = p->next;
+		free(p);
+	}
+	hashmap_clear(&store->map);
+	free(store);
+}
+
+void packfile_store_close(struct packfile_store *store)
+{
+	for (struct packed_git *p = store->packs; p; p = p->next) {
+		if (p->do_not_close)
+			BUG("want to close pack marked 'do-not-close'");
+		close_pack(p);
+	}
+}
diff --git a/packfile.h b/packfile.h
index f16753f..e7a5792 100644
--- a/packfile.h
+++ b/packfile.h
@@ -52,19 +52,114 @@ struct packed_git {
 	char pack_name[FLEX_ARRAY]; /* more */
 };
 
-static inline int pack_map_entry_cmp(const void *cmp_data UNUSED,
-				     const struct hashmap_entry *entry,
-				     const struct hashmap_entry *entry2,
-				     const void *keydata)
-{
-	const char *key = keydata;
-	const struct packed_git *pg1, *pg2;
+/*
+ * A store that manages packfiles for a given object database.
+ */
+struct packfile_store {
+	struct object_database *odb;
 
-	pg1 = container_of(entry, const struct packed_git, packmap_ent);
-	pg2 = container_of(entry2, const struct packed_git, packmap_ent);
+	/*
+	 * The list of packfiles in the order in which they are being added to
+	 * the store.
+	 */
+	struct packed_git *packs;
 
-	return strcmp(pg1->pack_name, key ? key : pg2->pack_name);
-}
+	/*
+	 * Cache of packfiles which are marked as "kept", either because there
+	 * is an on-disk ".keep" file or because they are marked as "kept" in
+	 * memory.
+	 *
+	 * Should not be accessed directly, but via `kept_pack_cache()`. The
+	 * list of packs gets invalidated when the stored flags and the flags
+	 * passed to `kept_pack_cache()` mismatch.
+	 */
+	struct {
+		struct packed_git **packs;
+		unsigned flags;
+	} kept_cache;
+
+	/* A most-recently-used ordered version of the packs list. */
+	struct list_head mru;
+
+	/*
+	 * A map of packfile names to packed_git structs for tracking which
+	 * packs have been loaded already.
+	 */
+	struct hashmap map;
+
+	/*
+	 * Whether packfiles have already been populated with this store's
+	 * packs.
+	 */
+	bool initialized;
+};
+
+/*
+ * Allocate and initialize a new empty packfile store for the given object
+ * database.
+ */
+struct packfile_store *packfile_store_new(struct object_database *odb);
+
+/*
+ * Free the packfile store and all its associated state. All packfiles
+ * tracked by the store will be closed.
+ */
+void packfile_store_free(struct packfile_store *store);
+
+/*
+ * Close all packfiles associated with this store. The packfiles won't be
+ * free'd, so they can be re-opened at a later point in time.
+ */
+void packfile_store_close(struct packfile_store *store);
+
+/*
+ * Prepare the packfile store by loading packfiles and multi-pack indices for
+ * all alternates. This becomes a no-op if the store is already prepared.
+ *
+ * It shouldn't typically be necessary to call this function directly, as
+ * functions that access the store know to prepare it.
+ */
+void packfile_store_prepare(struct packfile_store *store);
+
+/*
+ * Clear the packfile caches and try to look up any new packfiles that have
+ * appeared since last preparing the packfiles store.
+ *
+ * This function must be called under the `odb_read_lock()`.
+ */
+void packfile_store_reprepare(struct packfile_store *store);
+
+/*
+ * Add the pack to the store so that contained objects become accessible via
+ * the store. This moves ownership into the store.
+ */
+void packfile_store_add_pack(struct packfile_store *store,
+			     struct packed_git *pack);
+
+/*
+ * Get packs managed by the given store. Does not load the MIDX or any packs
+ * referenced by it.
+ */
+struct packed_git *packfile_store_get_packs(struct packfile_store *store);
+
+/*
+ * Get all packs managed by the given store, including packfiles that are
+ * referenced by multi-pack indices.
+ */
+struct packed_git *packfile_store_get_all_packs(struct packfile_store *store);
+
+/*
+ * Get all packs in most-recently-used order.
+ */
+struct list_head *packfile_store_get_packs_mru(struct packfile_store *store);
+
+/*
+ * Open the packfile and add it to the store if it isn't yet known. Returns
+ * either the newly opened packfile or the preexisting packfile. Returns a
+ * `NULL` pointer in case the packfile could not be opened.
+ */
+struct packed_git *packfile_store_load_pack(struct packfile_store *store,
+					    const char *idx_path, int local);
 
 struct pack_window {
 	struct pack_window *next;
@@ -142,14 +237,6 @@ int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,
 #define PACKDIR_FILE_GARBAGE 4
 extern void (*report_garbage)(unsigned seen_bits, const char *path);
 
-void reprepare_packed_git(struct repository *r);
-void install_packed_git(struct repository *r, struct packed_git *pack);
-
-struct packed_git *get_packed_git(struct repository *r);
-struct list_head *get_packed_git_mru(struct repository *r);
-struct multi_pack_index *get_multi_pack_index(struct odb_source *source);
-struct packed_git *get_all_packs(struct repository *r);
-
 /*
  * Give a rough count of objects in the repository. This sacrifices accuracy
  * for speed.
diff --git a/parse-options-cb.c b/parse-options-cb.c
index 50c8afe..976cc86 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -50,12 +50,12 @@ int parse_opt_expiry_date_cb(const struct option *opt, const char *arg,
 int parse_opt_color_flag_cb(const struct option *opt, const char *arg,
 			    int unset)
 {
-	int value;
+	enum git_colorbool value;
 
 	if (!arg)
 		arg = unset ? "never" : (const char *)opt->defval;
 	value = git_config_colorbool(NULL, arg);
-	if (value < 0)
+	if (value == GIT_COLOR_UNKNOWN)
 		return error(_("option `%s' expects \"always\", \"auto\", or \"never\""),
 			     opt->long_name);
 	*(int *)opt->value = value;
diff --git a/parse-options.c b/parse-options.c
index 5224203..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:
 	{
@@ -953,10 +962,16 @@ static void free_preprocessed_options(struct option *options)
 	free(options);
 }
 
+#define USAGE_NORMAL 0
+#define USAGE_FULL 1
+#define USAGE_TO_STDOUT 0
+#define USAGE_TO_STDERR 1
+
 static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t *,
 							 const char * const *,
 							 const struct option *,
-							 int, int);
+							 int full_usage,
+							 int usage_to_stderr);
 
 enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
 					 const struct option *options,
@@ -1088,7 +1103,8 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
 		}
 
 		if (internal_help && !strcmp(arg + 2, "help-all"))
-			return usage_with_options_internal(ctx, usagestr, options, 1, 0);
+			return usage_with_options_internal(ctx, usagestr, options,
+							   USAGE_FULL, USAGE_TO_STDOUT);
 		if (internal_help && !strcmp(arg + 2, "help"))
 			goto show_usage;
 		switch (parse_long_opt(ctx, arg + 2, options)) {
@@ -1129,7 +1145,8 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
 	return PARSE_OPT_DONE;
 
  show_usage:
-	return usage_with_options_internal(ctx, usagestr, options, 0, 0);
+	return usage_with_options_internal(ctx, usagestr, options,
+					   USAGE_NORMAL, USAGE_TO_STDOUT);
 }
 
 int parse_options_end(struct parse_opt_ctx_t *ctx)
@@ -1338,7 +1355,7 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t
 		if (!saw_empty_line && !*str)
 			saw_empty_line = 1;
 
-		string_list_split(&list, str, '\n', -1);
+		string_list_split(&list, str, "\n", -1);
 		for (j = 0; j < list.nr; j++) {
 			const char *line = list.items[j].string;
 
@@ -1444,7 +1461,8 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t
 void NORETURN usage_with_options(const char * const *usagestr,
 			const struct option *opts)
 {
-	usage_with_options_internal(NULL, usagestr, opts, 0, 1);
+	usage_with_options_internal(NULL, usagestr, opts,
+				    USAGE_NORMAL, USAGE_TO_STDERR);
 	exit(129);
 }
 
@@ -1452,9 +1470,16 @@ void show_usage_with_options_if_asked(int ac, const char **av,
 				      const char * const *usagestr,
 				      const struct option *opts)
 {
-	if (ac == 2 && !strcmp(av[1], "-h")) {
-		usage_with_options_internal(NULL, usagestr, opts, 0, 0);
-		exit(129);
+	if (ac == 2) {
+		if (!strcmp(av[1], "-h")) {
+			usage_with_options_internal(NULL, usagestr, opts,
+						    USAGE_NORMAL, USAGE_TO_STDOUT);
+			exit(129);
+		} else if (!strcmp(av[1], "--help-all")) {
+			usage_with_options_internal(NULL, usagestr, opts,
+						    USAGE_FULL, USAGE_TO_STDOUT);
+			exit(129);
+		}
 	}
 }
 
diff --git a/path-walk.c b/path-walk.c
index 2d4ddba..f1ceed9 100644
--- a/path-walk.c
+++ b/path-walk.c
@@ -105,6 +105,24 @@ static void push_to_stack(struct path_walk_context *ctx,
 	prio_queue_put(&ctx->path_stack, xstrdup(path));
 }
 
+static void add_path_to_list(struct path_walk_context *ctx,
+			     const char *path,
+			     enum object_type type,
+			     struct object_id *oid,
+			     int interesting)
+{
+	struct type_and_oid_list *list = strmap_get(&ctx->paths_to_lists, path);
+
+	if (!list) {
+		CALLOC_ARRAY(list, 1);
+		list->type = type;
+		strmap_put(&ctx->paths_to_lists, path, list);
+	}
+
+	list->maybe_interesting |= interesting;
+	oid_array_append(&list->oids, oid);
+}
+
 static int add_tree_entries(struct path_walk_context *ctx,
 			    const char *base_path,
 			    struct object_id *oid)
@@ -129,7 +147,6 @@ static int add_tree_entries(struct path_walk_context *ctx,
 
 	init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size);
 	while (tree_entry(&desc, &entry)) {
-		struct type_and_oid_list *list;
 		struct object *o;
 		/* Not actually true, but we will ignore submodules later. */
 		enum object_type type = S_ISDIR(entry.mode) ? OBJ_TREE : OBJ_BLOB;
@@ -190,17 +207,10 @@ static int add_tree_entries(struct path_walk_context *ctx,
 				continue;
 		}
 
-		if (!(list = strmap_get(&ctx->paths_to_lists, path.buf))) {
-			CALLOC_ARRAY(list, 1);
-			list->type = type;
-			strmap_put(&ctx->paths_to_lists, path.buf, list);
-		}
+		add_path_to_list(ctx, path.buf, type, &entry.oid,
+				 !(o->flags & UNINTERESTING));
+
 		push_to_stack(ctx, path.buf);
-
-		if (!(o->flags & UNINTERESTING))
-			list->maybe_interesting = 1;
-
-		oid_array_append(&list->oids, &entry.oid);
 	}
 
 	free_tree_buffer(tree);
@@ -377,15 +387,9 @@ static int setup_pending_objects(struct path_walk_info *info,
 			if (!info->trees)
 				continue;
 			if (pending->path) {
-				struct type_and_oid_list *list;
 				char *path = *pending->path ? xstrfmt("%s/", pending->path)
 							    : xstrdup("");
-				if (!(list = strmap_get(&ctx->paths_to_lists, path))) {
-					CALLOC_ARRAY(list, 1);
-					list->type = OBJ_TREE;
-					strmap_put(&ctx->paths_to_lists, path, list);
-				}
-				oid_array_append(&list->oids, &obj->oid);
+				add_path_to_list(ctx, path, OBJ_TREE, &obj->oid, 1);
 				free(path);
 			} else {
 				/* assume a root tree, such as a lightweight tag. */
@@ -396,19 +400,10 @@ static int setup_pending_objects(struct path_walk_info *info,
 		case OBJ_BLOB:
 			if (!info->blobs)
 				continue;
-			if (pending->path) {
-				struct type_and_oid_list *list;
-				char *path = pending->path;
-				if (!(list = strmap_get(&ctx->paths_to_lists, path))) {
-					CALLOC_ARRAY(list, 1);
-					list->type = OBJ_BLOB;
-					strmap_put(&ctx->paths_to_lists, path, list);
-				}
-				oid_array_append(&list->oids, &obj->oid);
-			} else {
-				/* assume a root tree, such as a lightweight tag. */
+			if (pending->path)
+				add_path_to_list(ctx, pending->path, OBJ_BLOB, &obj->oid, 1);
+			else
 				oid_array_append(&tagged_blobs->oids, &obj->oid);
-			}
 			break;
 
 		case OBJ_COMMIT:
diff --git a/pathspec.c b/pathspec.c
index a3ddd70..5993c4a 100644
--- a/pathspec.c
+++ b/pathspec.c
@@ -201,8 +201,7 @@ static void parse_pathspec_attr_match(struct pathspec_item *item, const char *va
 	if (!value || !*value)
 		die(_("attr spec must not be empty"));
 
-	string_list_split(&list, value, ' ', -1);
-	string_list_remove_empty_items(&list, 0);
+	string_list_split_f(&list, value, " ", -1, STRING_LIST_SPLIT_NONEMPTY);
 
 	item->attr_check = attr_check_alloc();
 	CALLOC_ARRAY(item->attr_match, list.nr);
diff --git a/pretty.c b/pretty.c
index cee96b9..e0646bb 100644
--- a/pretty.c
+++ b/pretty.c
@@ -470,7 +470,7 @@ static inline void strbuf_add_with_color(struct strbuf *sb, const char *color,
 
 static void append_line_with_color(struct strbuf *sb, struct grep_opt *opt,
 				   const char *line, size_t linelen,
-				   int color, enum grep_context ctx,
+				   enum git_colorbool color, enum grep_context ctx,
 				   enum grep_header_field field)
 {
 	const char *buf, *eol, *line_color, *match_color;
@@ -899,7 +899,7 @@ struct format_commit_context {
 	const char *message;
 	char *commit_encoding;
 	size_t width, indent1, indent2;
-	int auto_color;
+	enum git_colorbool auto_color;
 	int padding;
 
 	/* These offsets are relative to the start of the commit message. */
@@ -1455,14 +1455,14 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 	switch (placeholder[0]) {
 	case 'C':
 		if (starts_with(placeholder + 1, "(auto)")) {
-			c->auto_color = want_color(c->pretty_ctx->color);
-			if (c->auto_color && sb->len)
+			c->auto_color = c->pretty_ctx->color;
+			if (want_color(c->auto_color) && sb->len)
 				strbuf_addstr(sb, GIT_COLOR_RESET);
 			return 7; /* consumed 7 bytes, "C(auto)" */
 		} else {
 			int ret = parse_color(sb, placeholder, c);
 			if (ret)
-				c->auto_color = 0;
+				c->auto_color = GIT_COLOR_NEVER;
 			/*
 			 * Otherwise, we decided to treat %C<unknown>
 			 * as a literal string, and the previous
@@ -2167,7 +2167,7 @@ static int pp_utf8_width(const char *start, const char *end)
 }
 
 static void strbuf_add_tabexpand(struct strbuf *sb, struct grep_opt *opt,
-				 int color, int tabwidth, const char *line,
+				 enum git_colorbool color, int tabwidth, const char *line,
 				 int linelen)
 {
 	const char *tab;
diff --git a/pretty.h b/pretty.h
index df267af..fac6990 100644
--- a/pretty.h
+++ b/pretty.h
@@ -3,6 +3,7 @@
 
 #include "date.h"
 #include "string-list.h"
+#include "color.h"
 
 struct commit;
 struct repository;
@@ -46,7 +47,7 @@ struct pretty_print_context {
 	struct rev_info *rev;
 	const char *output_encoding;
 	struct string_list *mailmap;
-	int color;
+	enum git_colorbool color;
 	struct ident_split *from_ident;
 	unsigned encode_email_headers:1;
 	struct pretty_print_describe_status *describe_status;
diff --git a/progress.c b/progress.c
index 8d5ae70..8315bdc 100644
--- a/progress.c
+++ b/progress.c
@@ -114,16 +114,19 @@ static void display(struct progress *progress, uint64_t n, const char *done)
 	const char *tp;
 	struct strbuf *counters_sb = &progress->counters_sb;
 	int show_update = 0;
+	int update = !!progress_update;
 	int last_count_len = counters_sb->len;
 
-	if (progress->delay && (!progress_update || --progress->delay))
+	progress_update = 0;
+
+	if (progress->delay && (!update || --progress->delay))
 		return;
 
 	progress->last_value = n;
 	tp = (progress->throughput) ? progress->throughput->display.buf : "";
 	if (progress->total) {
 		unsigned percent = n * 100 / progress->total;
-		if (percent != progress->last_percent || progress_update) {
+		if (percent != progress->last_percent || update) {
 			progress->last_percent = percent;
 
 			strbuf_reset(counters_sb);
@@ -133,7 +136,7 @@ static void display(struct progress *progress, uint64_t n, const char *done)
 				    tp);
 			show_update = 1;
 		}
-	} else if (progress_update) {
+	} else if (update) {
 		strbuf_reset(counters_sb);
 		strbuf_addf(counters_sb, "%"PRIuMAX"%s", (uintmax_t)n, tp);
 		show_update = 1;
@@ -166,7 +169,6 @@ static void display(struct progress *progress, uint64_t n, const char *done)
 			}
 			fflush(stderr);
 		}
-		progress_update = 0;
 	}
 }
 
@@ -281,7 +283,7 @@ static int get_default_delay(void)
 	static int delay_in_secs = -1;
 
 	if (delay_in_secs < 0)
-		delay_in_secs = git_env_ulong("GIT_PROGRESS_DELAY", 2);
+		delay_in_secs = git_env_ulong("GIT_PROGRESS_DELAY", 1);
 
 	return delay_in_secs;
 }
diff --git a/promisor-remote.c b/promisor-remote.c
index 08b0da8..77ebf53 100644
--- a/promisor-remote.c
+++ b/promisor-remote.c
@@ -314,9 +314,162 @@ static int allow_unsanitized(char ch)
 	return ch > 32 && ch < 127;
 }
 
-static void promisor_info_vecs(struct repository *repo,
-			       struct strvec *names,
-			       struct strvec *urls)
+/*
+ * All the fields used in "promisor-remote" protocol capability,
+ * including the mandatory "name" and "url" ones.
+ */
+static const char promisor_field_name[] = "name";
+static const char promisor_field_url[] = "url";
+static const char promisor_field_filter[] = "partialCloneFilter";
+static const char promisor_field_token[] = "token";
+
+/*
+ * List of optional field names that can be used in the
+ * "promisor-remote" protocol capability (others must be
+ * ignored). Each field should correspond to a configurable property
+ * of a remote that can be relevant for the client.
+ */
+static const char *known_fields[] = {
+	promisor_field_filter, /* Filter used for partial clone */
+	promisor_field_token,  /* Authentication token for the remote */
+	NULL
+};
+
+/*
+ * Check if 'field' is in the list of the known field names for the
+ * "promisor-remote" protocol capability.
+ */
+static int is_known_field(const char *field)
+{
+	const char **p;
+
+	for (p = known_fields; *p; p++)
+		if (!strcasecmp(*p, field))
+			return 1;
+	return 0;
+}
+
+static int is_valid_field(struct string_list_item *item, void *cb_data)
+{
+	const char *field = item->string;
+	const char *config_key = (const char *)cb_data;
+
+	if (!is_known_field(field)) {
+		warning(_("unsupported field '%s' in '%s' config"), field, config_key);
+		return 0;
+	}
+	return 1;
+}
+
+static char *fields_from_config(struct string_list *fields_list, const char *config_key)
+{
+	char *fields = NULL;
+
+	if (!repo_config_get_string(the_repository, config_key, &fields) && *fields) {
+		string_list_split_in_place_f(fields_list, fields, ",", -1,
+					     STRING_LIST_SPLIT_TRIM |
+					     STRING_LIST_SPLIT_NONEMPTY);
+		filter_string_list(fields_list, 0, is_valid_field, (void *)config_key);
+	}
+
+	return fields;
+}
+
+static struct string_list *fields_sent(void)
+{
+	static struct string_list fields_list = STRING_LIST_INIT_NODUP;
+	static int initialized;
+
+	if (!initialized) {
+		fields_list.cmp = strcasecmp;
+		fields_from_config(&fields_list, "promisor.sendFields");
+		initialized = 1;
+	}
+
+	return &fields_list;
+}
+
+static struct string_list *fields_checked(void)
+{
+	static struct string_list fields_list = STRING_LIST_INIT_NODUP;
+	static int initialized;
+
+	if (!initialized) {
+		fields_list.cmp = strcasecmp;
+		fields_from_config(&fields_list, "promisor.checkFields");
+		initialized = 1;
+	}
+
+	return &fields_list;
+}
+
+/*
+ * Struct for promisor remotes involved in the "promisor-remote"
+ * protocol capability.
+ *
+ * Except for "name", each <member> in this struct and its <value>
+ * should correspond (either on the client side or on the server side)
+ * to a "remote.<name>.<member>" config variable set to <value> where
+ * "<name>" is a promisor remote name.
+ */
+struct promisor_info {
+	const char *name;
+	const char *url;
+	const char *filter;
+	const char *token;
+};
+
+static void promisor_info_free(struct promisor_info *p)
+{
+	free((char *)p->name);
+	free((char *)p->url);
+	free((char *)p->filter);
+	free((char *)p->token);
+	free(p);
+}
+
+static void promisor_info_list_clear(struct string_list *list)
+{
+	for (size_t i = 0; i < list->nr; i++)
+		promisor_info_free(list->items[i].util);
+	string_list_clear(list, 0);
+}
+
+static void set_one_field(struct promisor_info *p,
+			  const char *field, const char *value)
+{
+	if (!strcasecmp(field, promisor_field_filter))
+		p->filter = xstrdup(value);
+	else if (!strcasecmp(field, promisor_field_token))
+		p->token = xstrdup(value);
+	else
+		BUG("invalid field '%s'", field);
+}
+
+static void set_fields(struct promisor_info *p,
+		       struct string_list *field_names)
+{
+	struct string_list_item *item;
+
+	for_each_string_list_item(item, field_names) {
+		char *key = xstrfmt("remote.%s.%s", p->name, item->string);
+		const char *val;
+		if (!repo_config_get_string_tmp(the_repository, key, &val) && *val)
+			set_one_field(p, item->string, val);
+		free(key);
+	}
+}
+
+/*
+ * Populate 'list' with promisor remote information from the config.
+ * The 'util' pointer of each list item will hold a 'struct
+ * promisor_info'. Except "name" and "url", only members of that
+ * struct specified by the 'field_names' list are set (using values
+ * from the configuration).
+ */
+static void promisor_config_info_list(struct repository *repo,
+				      struct string_list *list,
+				      struct string_list *field_names)
 {
 	struct promisor_remote *r;
 
@@ -328,8 +481,17 @@ static void promisor_info_vecs(struct repository *repo,
 
 		/* Only add remotes with a non empty URL */
 		if (!repo_config_get_string_tmp(the_repository, url_key, &url) && *url) {
-			strvec_push(names, r->name);
-			strvec_push(urls, url);
+			struct promisor_info *new_info = xcalloc(1, sizeof(*new_info));
+			struct string_list_item *item;
+
+			new_info->name = xstrdup(r->name);
+			new_info->url = xstrdup(url);
+
+			if (field_names)
+				set_fields(new_info, field_names);
+
+			item = string_list_append(list, new_info->name);
+			item->util = new_info;
 		}
 
 		free(url_key);
@@ -340,47 +502,45 @@ char *promisor_remote_info(struct repository *repo)
 {
 	struct strbuf sb = STRBUF_INIT;
 	int advertise_promisors = 0;
-	struct strvec names = STRVEC_INIT;
-	struct strvec urls = STRVEC_INIT;
+	struct string_list config_info = STRING_LIST_INIT_NODUP;
+	struct string_list_item *item;
 
 	repo_config_get_bool(the_repository, "promisor.advertise", &advertise_promisors);
 
 	if (!advertise_promisors)
 		return NULL;
 
-	promisor_info_vecs(repo, &names, &urls);
+	promisor_config_info_list(repo, &config_info, fields_sent());
 
-	if (!names.nr)
+	if (!config_info.nr)
 		return NULL;
 
-	for (size_t i = 0; i < names.nr; i++) {
-		if (i)
+	for_each_string_list_item(item, &config_info) {
+		struct promisor_info *p = item->util;
+
+		if (item != config_info.items)
 			strbuf_addch(&sb, ';');
-		strbuf_addstr(&sb, "name=");
-		strbuf_addstr_urlencode(&sb, names.v[i], allow_unsanitized);
-		strbuf_addstr(&sb, ",url=");
-		strbuf_addstr_urlencode(&sb, urls.v[i], allow_unsanitized);
+
+		strbuf_addf(&sb, "%s=", promisor_field_name);
+		strbuf_addstr_urlencode(&sb, p->name, allow_unsanitized);
+		strbuf_addf(&sb, ",%s=", promisor_field_url);
+		strbuf_addstr_urlencode(&sb, p->url, allow_unsanitized);
+
+		if (p->filter) {
+			strbuf_addf(&sb, ",%s=", promisor_field_filter);
+			strbuf_addstr_urlencode(&sb, p->filter, allow_unsanitized);
+		}
+		if (p->token) {
+			strbuf_addf(&sb, ",%s=", promisor_field_token);
+			strbuf_addstr_urlencode(&sb, p->token, allow_unsanitized);
+		}
 	}
 
-	strvec_clear(&names);
-	strvec_clear(&urls);
+	promisor_info_list_clear(&config_info);
 
 	return strbuf_detach(&sb, NULL);
 }
 
-/*
- * Find first index of 'nicks' where there is 'nick'. 'nick' is
- * compared case sensitively to the strings in 'nicks'. If not found
- * 'nicks->nr' is returned.
- */
-static size_t remote_nick_find(struct strvec *nicks, const char *nick)
-{
-	for (size_t i = 0; i < nicks->nr; i++)
-		if (!strcmp(nicks->v[i], nick))
-			return i;
-	return nicks->nr;
-}
-
 enum accept_promisor {
 	ACCEPT_NONE = 0,
 	ACCEPT_KNOWN_URL,
@@ -388,23 +548,84 @@ enum accept_promisor {
 	ACCEPT_ALL
 };
 
-static int should_accept_remote(enum accept_promisor accept,
-				const char *remote_name, const char *remote_url,
-				struct strvec *names, struct strvec *urls)
+static int match_field_against_config(const char *field, const char *value,
+				      struct promisor_info *config_info)
 {
-	size_t i;
+	if (config_info->filter && !strcasecmp(field, promisor_field_filter))
+		return !strcmp(config_info->filter, value);
+	else if (config_info->token && !strcasecmp(field, promisor_field_token))
+		return !strcmp(config_info->token, value);
+
+	return 0;
+}
+
+static int all_fields_match(struct promisor_info *advertised,
+			    struct string_list *config_info,
+			    int in_list)
+{
+	struct string_list *fields = fields_checked();
+	struct string_list_item *item_checked;
+
+	for_each_string_list_item(item_checked, fields) {
+		int match = 0;
+		const char *field = item_checked->string;
+		const char *value = NULL;
+		struct string_list_item *item;
+
+		if (!strcasecmp(field, promisor_field_filter))
+			value = advertised->filter;
+		else if (!strcasecmp(field, promisor_field_token))
+			value = advertised->token;
+
+		if (!value)
+			return 0;
+
+		if (in_list) {
+			for_each_string_list_item(item, config_info) {
+				struct promisor_info *p = item->util;
+				if (match_field_against_config(field, value, p)) {
+					match = 1;
+					break;
+				}
+			}
+		} else {
+			item = string_list_lookup(config_info, advertised->name);
+			if (item) {
+				struct promisor_info *p = item->util;
+				match = match_field_against_config(field, value, p);
+			}
+		}
+
+		if (!match)
+			return 0;
+	}
+
+	return 1;
+}
+
+static int should_accept_remote(enum accept_promisor accept,
+				struct promisor_info *advertised,
+				struct string_list *config_info)
+{
+	struct promisor_info *p;
+	struct string_list_item *item;
+	const char *remote_name = advertised->name;
+	const char *remote_url = advertised->url;
 
 	if (accept == ACCEPT_ALL)
-		return 1;
+		return all_fields_match(advertised, config_info, 1);
 
-	i = remote_nick_find(names, remote_name);
+	/* Get config info for that promisor remote */
+	item = string_list_lookup(config_info, remote_name);
 
-	if (i >= names->nr)
+	if (!item)
 		/* We don't know about that remote */
 		return 0;
 
+	p = item->util;
+
 	if (accept == ACCEPT_KNOWN_NAME)
-		return 1;
+		return all_fields_match(advertised, config_info, 0);
 
 	if (accept != ACCEPT_KNOWN_URL)
 		BUG("Unhandled 'enum accept_promisor' value '%d'", accept);
@@ -414,24 +635,72 @@ static int should_accept_remote(enum accept_promisor accept,
 		return 0;
 	}
 
-	if (!strcmp(urls->v[i], remote_url))
-		return 1;
+	if (!strcmp(p->url, remote_url))
+		return all_fields_match(advertised, config_info, 0);
 
 	warning(_("known remote named '%s' but with URL '%s' instead of '%s'"),
-		remote_name, urls->v[i], remote_url);
+		remote_name, p->url, remote_url);
 
 	return 0;
 }
 
+static int skip_field_name_prefix(const char *elem, const char *field_name, const char **value)
+{
+	const char *p;
+	if (!skip_prefix(elem, field_name, &p) || *p != '=')
+		return 0;
+	*value = p + 1;
+	return 1;
+}
+
+static struct promisor_info *parse_one_advertised_remote(const char *remote_info)
+{
+	struct promisor_info *info = xcalloc(1, sizeof(*info));
+	struct string_list elem_list = STRING_LIST_INIT_DUP;
+	struct string_list_item *item;
+
+	string_list_split(&elem_list, remote_info, ",", -1);
+
+	for_each_string_list_item(item, &elem_list) {
+		const char *elem = item->string;
+		const char *p = strchr(elem, '=');
+
+		if (!p) {
+			warning(_("invalid element '%s' from remote info"), elem);
+			continue;
+		}
+
+		if (skip_field_name_prefix(elem, promisor_field_name, &p))
+			info->name = url_percent_decode(p);
+		else if (skip_field_name_prefix(elem, promisor_field_url, &p))
+			info->url = url_percent_decode(p);
+		else if (skip_field_name_prefix(elem, promisor_field_filter, &p))
+			info->filter = url_percent_decode(p);
+		else if (skip_field_name_prefix(elem, promisor_field_token, &p))
+			info->token = url_percent_decode(p);
+	}
+
+	string_list_clear(&elem_list, 0);
+
+	if (!info->name || !info->url) {
+		warning(_("server advertised a promisor remote without a name or URL: %s"),
+			remote_info);
+		promisor_info_free(info);
+		return NULL;
+	}
+
+	return info;
+}
+
 static void filter_promisor_remote(struct repository *repo,
 				   struct strvec *accepted,
 				   const char *info)
 {
-	struct strbuf **remotes;
 	const char *accept_str;
 	enum accept_promisor accept = ACCEPT_NONE;
-	struct strvec names = STRVEC_INIT;
-	struct strvec urls = STRVEC_INIT;
+	struct string_list config_info = STRING_LIST_INIT_NODUP;
+	struct string_list remote_info = STRING_LIST_INIT_DUP;
+	struct string_list_item *item;
 
 	if (!repo_config_get_string_tmp(the_repository, "promisor.acceptfromserver", &accept_str)) {
 		if (!*accept_str || !strcasecmp("None", accept_str))
@@ -450,49 +719,31 @@ static void filter_promisor_remote(struct repository *repo,
 	if (accept == ACCEPT_NONE)
 		return;
 
-	if (accept != ACCEPT_ALL)
-		promisor_info_vecs(repo, &names, &urls);
-
 	/* Parse remote info received */
 
-	remotes = strbuf_split_str(info, ';', 0);
+	string_list_split(&remote_info, info, ";", -1);
 
-	for (size_t i = 0; remotes[i]; i++) {
-		struct strbuf **elems;
-		const char *remote_name = NULL;
-		const char *remote_url = NULL;
-		char *decoded_name = NULL;
-		char *decoded_url = NULL;
+	for_each_string_list_item(item, &remote_info) {
+		struct promisor_info *advertised;
 
-		strbuf_strip_suffix(remotes[i], ";");
-		elems = strbuf_split(remotes[i], ',');
+		advertised = parse_one_advertised_remote(item->string);
 
-		for (size_t j = 0; elems[j]; j++) {
-			int res;
-			strbuf_strip_suffix(elems[j], ",");
-			res = skip_prefix(elems[j]->buf, "name=", &remote_name) ||
-				skip_prefix(elems[j]->buf, "url=", &remote_url);
-			if (!res)
-				warning(_("unknown element '%s' from remote info"),
-					elems[j]->buf);
+		if (!advertised)
+			continue;
+
+		if (!config_info.nr) {
+			promisor_config_info_list(repo, &config_info, fields_checked());
+			string_list_sort(&config_info);
 		}
 
-		if (remote_name)
-			decoded_name = url_percent_decode(remote_name);
-		if (remote_url)
-			decoded_url = url_percent_decode(remote_url);
+		if (should_accept_remote(accept, advertised, &config_info))
+			strvec_push(accepted, advertised->name);
 
-		if (decoded_name && should_accept_remote(accept, decoded_name, decoded_url, &names, &urls))
-			strvec_push(accepted, decoded_name);
-
-		strbuf_list_free(elems);
-		free(decoded_name);
-		free(decoded_url);
+		promisor_info_free(advertised);
 	}
 
-	strvec_clear(&names);
-	strvec_clear(&urls);
-	strbuf_list_free(remotes);
+	promisor_info_list_clear(&config_info);
+	string_list_clear(&remote_info, 0);
 }
 
 char *promisor_remote_reply(const char *info)
@@ -518,16 +769,15 @@ char *promisor_remote_reply(const char *info)
 
 void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes)
 {
-	struct strbuf **accepted_remotes = strbuf_split_str(remotes, ';', 0);
+	struct string_list accepted_remotes = STRING_LIST_INIT_DUP;
+	struct string_list_item *item;
 
-	for (size_t i = 0; accepted_remotes[i]; i++) {
-		struct promisor_remote *p;
-		char *decoded_remote;
+	string_list_split(&accepted_remotes, remotes, ";", -1);
 
-		strbuf_strip_suffix(accepted_remotes[i], ";");
-		decoded_remote = url_percent_decode(accepted_remotes[i]->buf);
+	for_each_string_list_item(item, &accepted_remotes) {
+		char *decoded_remote = url_percent_decode(item->string);
+		struct promisor_remote *p = repo_promisor_remote_find(r, decoded_remote);
 
-		p = repo_promisor_remote_find(r, decoded_remote);
 		if (p)
 			p->accepted = 1;
 		else
@@ -537,5 +787,5 @@ void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes
 		free(decoded_remote);
 	}
 
-	strbuf_list_free(accepted_remotes);
+	string_list_clear(&accepted_remotes, 0);
 }
diff --git a/protocol.c b/protocol.c
index 65f6621..a3e26a8 100644
--- a/protocol.c
+++ b/protocol.c
@@ -61,7 +61,7 @@ enum protocol_version determine_protocol_version_server(void)
 	if (git_protocol) {
 		struct string_list list = STRING_LIST_INIT_DUP;
 		const struct string_list_item *item;
-		string_list_split(&list, git_protocol, ':', -1);
+		string_list_split(&list, git_protocol, ":", -1);
 
 		for_each_string_list_item(item, &list) {
 			const char *value;
diff --git a/range-diff.c b/range-diff.c
index 8a2dcbe..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;
@@ -325,13 +325,24 @@ static int diffsize(const char *a, const char *b)
 }
 
 static void get_correspondences(struct string_list *a, struct string_list *b,
-				int creation_factor)
+				int creation_factor, size_t max_memory)
 {
 	int n = a->nr + b->nr;
 	int *cost, c, *a2b, *b2a;
 	int i, j;
-
-	ALLOC_ARRAY(cost, st_mult(n, n));
+	size_t cost_size = st_mult(n, n);
+	size_t cost_bytes = st_mult(sizeof(int), cost_size);
+	if (cost_bytes >= max_memory) {
+		struct strbuf cost_str = STRBUF_INIT;
+		struct strbuf max_str = STRBUF_INIT;
+		strbuf_humanise_bytes(&cost_str, cost_bytes);
+		strbuf_humanise_bytes(&max_str, max_memory);
+		die(_("range-diff: unable to compute the range-diff, since it "
+		      "exceeds the maximum memory for the cost matrix: %s "
+		      "(%"PRIuMAX" bytes) needed, limited to %s (%"PRIuMAX" bytes)"),
+		    cost_str.buf, (uintmax_t)cost_bytes, max_str.buf, (uintmax_t)max_memory);
+	}
+	ALLOC_ARRAY(cost, cost_size);
 	ALLOC_ARRAY(a2b, n);
 	ALLOC_ARRAY(b2a, n);
 
@@ -583,15 +594,16 @@ 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) {
 		find_exact_matches(&branch1, &branch2);
 		get_correspondences(&branch1, &branch2,
-				    range_diff_opts->creation_factor);
+				    range_diff_opts->creation_factor,
+				    range_diff_opts->max_memory);
 		output(&branch1, &branch2, range_diff_opts);
 	}
 
diff --git a/range-diff.h b/range-diff.h
index cd85000..9b70a80 100644
--- a/range-diff.h
+++ b/range-diff.h
@@ -5,6 +5,10 @@
 #include "strvec.h"
 
 #define RANGE_DIFF_CREATION_FACTOR_DEFAULT 60
+#define RANGE_DIFF_MAX_MEMORY_DEFAULT \
+	(sizeof(void*) >= 8 ? \
+		((size_t)(1024L * 1024L) * (size_t)(4L * 1024L)) : /* 4GB on 64-bit */ \
+		((size_t)(1024L * 1024L) * (size_t)(2L * 1024L)))   /* 2GB on 32-bit */
 
 /*
  * A much higher value than the default, when we KNOW we are comparing
@@ -17,8 +21,9 @@ struct range_diff_options {
 	unsigned dual_color:1;
 	unsigned left_only:1, right_only:1;
 	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/read-cache.c b/read-cache.c
index 06ad74d..032480d 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -8,7 +8,6 @@
 #define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "git-compat-util.h"
-#include "bulk-checkin.h"
 #include "config.h"
 #include "date.h"
 #include "diff.h"
@@ -1807,7 +1806,7 @@ static struct cache_entry *create_from_disk(struct mem_pool *ce_mem_pool,
 
 	if (expand_name_field) {
 		const unsigned char *cp = (const unsigned char *)name;
-		size_t strip_len, previous_len;
+		uint64_t strip_len, previous_len;
 
 		/* If we're at the beginning of a block, ignore the previous name */
 		strip_len = decode_varint(&cp);
@@ -2655,8 +2654,10 @@ static int ce_write_entry(struct hashfile *f, struct cache_entry *ce,
 		hashwrite(f, ce->name, len);
 		hashwrite(f, padding, align_padding_size(size, len));
 	} else {
-		int common, to_remove, prefix_size;
+		int common, to_remove;
+		uint8_t prefix_size;
 		unsigned char to_remove_vi[16];
+
 		for (common = 0;
 		     (common < previous_name->len &&
 		      ce->name[common] &&
@@ -3947,6 +3948,7 @@ int add_files_to_cache(struct repository *repo, const char *prefix,
 		       const struct pathspec *pathspec, char *ps_matched,
 		       int include_sparse, int flags)
 {
+	struct odb_transaction *transaction;
 	struct update_callback_data data;
 	struct rev_info rev;
 
@@ -3972,9 +3974,9 @@ int add_files_to_cache(struct repository *repo, const char *prefix,
 	 * This function is invoked from commands other than 'add', which
 	 * may not have their own transaction active.
 	 */
-	begin_odb_transaction();
+	transaction = odb_transaction_begin(repo->objects);
 	run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
-	end_odb_transaction();
+	odb_transaction_commit(transaction);
 
 	release_revisions(&rev);
 	return !!data.add_errors;
diff --git a/ref-filter.c b/ref-filter.c
index 4edf0df..520d253 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -435,7 +435,7 @@ static int remote_ref_atom_parser(struct ref_format *format UNUSED,
 	}
 
 	atom->u.remote_ref.nobracket = 0;
-	string_list_split(&params, arg, ',', -1);
+	string_list_split(&params, arg, ",", -1);
 
 	for (i = 0; i < params.nr; i++) {
 		const char *s = params.items[i].string;
@@ -831,7 +831,7 @@ static int align_atom_parser(struct ref_format *format UNUSED,
 
 	align->position = ALIGN_LEFT;
 
-	string_list_split(&params, arg, ',', -1);
+	string_list_split(&params, arg, ",", -1);
 	for (i = 0; i < params.nr; i++) {
 		const char *s = params.items[i].string;
 		int position;
diff --git a/ref-filter.h b/ref-filter.h
index f22ca94..81f2c22 100644
--- a/ref-filter.h
+++ b/ref-filter.h
@@ -95,7 +95,7 @@ struct ref_format {
 	const char *format;
 	const char *rest;
 	int quote_style;
-	int use_color;
+	enum git_colorbool use_color;
 
 	/* Internal state to ref-filter */
 	int need_color_reset_at_eol;
@@ -111,7 +111,7 @@ struct ref_format {
 	.exclude = STRVEC_INIT, \
 }
 #define REF_FORMAT_INIT {             \
-	.use_color = -1,              \
+	.use_color = GIT_COLOR_UNKNOWN, \
 }
 
 /*  Macros for checking --merged and --no-merged options */
diff --git a/reflog-walk.c b/reflog-walk.c
index c7070b1..4f1ce04 100644
--- a/reflog-walk.c
+++ b/reflog-walk.c
@@ -22,9 +22,10 @@ struct complete_reflogs {
 	int nr, alloc;
 };
 
-static int read_one_reflog(struct object_id *ooid, struct object_id *noid,
-		const char *email, timestamp_t timestamp, int tz,
-		const char *message, void *cb_data)
+static int read_one_reflog(const char *refname UNUSED,
+			   struct object_id *ooid, struct object_id *noid,
+			   const char *email, timestamp_t timestamp, int tz,
+			   const char *message, void *cb_data)
 {
 	struct complete_reflogs *array = cb_data;
 	struct reflog_info *item;
diff --git a/reflog.c b/reflog.c
index b267369..65ef259 100644
--- a/reflog.c
+++ b/reflog.c
@@ -507,7 +507,8 @@ void reflog_expiry_cleanup(void *cb_data)
 	free_commit_list(cb->mark_list);
 }
 
-int count_reflog_ent(struct object_id *ooid UNUSED,
+int count_reflog_ent(const char *refname UNUSED,
+		     struct object_id *ooid UNUSED,
 		     struct object_id *noid UNUSED,
 		     const char *email UNUSED,
 		     timestamp_t timestamp, int tz UNUSED,
diff --git a/reflog.h b/reflog.h
index 74b3f3c..b996712 100644
--- a/reflog.h
+++ b/reflog.h
@@ -65,7 +65,8 @@ void reflog_expiry_prepare(const char *refname, const struct object_id *oid,
 int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,
 			     const char *email, timestamp_t timestamp, int tz,
 			     const char *message, void *cb_data);
-int count_reflog_ent(struct object_id *ooid, struct object_id *noid,
+int count_reflog_ent(const char *refname,
+		     struct object_id *ooid, struct object_id *noid,
 		     const char *email, timestamp_t timestamp, int tz,
 		     const char *message, void *cb_data);
 int should_expire_reflog_ent_verbose(struct object_id *ooid,
diff --git a/refs.c b/refs.c
index bfdbe71..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"
@@ -32,6 +31,7 @@
 #include "commit.h"
 #include "wildmatch.h"
 #include "ident.h"
+#include "fsck.h"
 
 /*
  * List of all available backends
@@ -323,6 +323,9 @@ int check_refname_format(const char *refname, int flags)
 int refs_fsck(struct ref_store *refs, struct fsck_options *o,
 	      struct worktree *wt)
 {
+	if (o->verbose)
+		fprintf_ln(stderr, _("Checking references consistency"));
+
 	return refs->be->fsck(refs, o, wt);
 }
 
@@ -627,10 +630,12 @@ void expand_ref_prefix(struct strvec *prefixes, const char *prefix)
 		strvec_pushf(prefixes, *p, len, prefix);
 }
 
+#ifndef WITH_BREAKING_CHANGES
 static const char default_branch_name_advice[] = N_(
 "Using '%s' as the name for the initial branch. This default branch name\n"
-"is subject to change. To configure the initial branch name to use in all\n"
-"of your new repositories, which will suppress this warning, call:\n"
+"will change to \"main\" in Git 3.0. To configure the initial branch name\n"
+"to use in all of your new repositories, which will suppress this warning,\n"
+"call:\n"
 "\n"
 "\tgit config --global init.defaultBranch <name>\n"
 "\n"
@@ -639,6 +644,15 @@ static const char default_branch_name_advice[] = N_(
 "\n"
 "\tgit branch -m <name>\n"
 );
+#else
+static const char default_branch_name_advice[] = N_(
+"Using '%s' as the name for the initial branch since Git 3.0.\n"
+"If you expected Git to create 'master', the just-created\n"
+"branch can be renamed via this command:\n"
+"\n"
+"\tgit branch -m master\n"
+);
+#endif /* WITH_BREAKING_CHANGES */
 
 char *repo_default_branch_name(struct repository *r, int quiet)
 {
@@ -649,11 +663,15 @@ char *repo_default_branch_name(struct repository *r, int quiet)
 
 	if (env && *env)
 		ret = xstrdup(env);
-	else if (repo_config_get_string(r, config_key, &ret) < 0)
+	if (!ret && repo_config_get_string(r, config_key, &ret) < 0)
 		die(_("could not retrieve `%s`"), config_display_key);
 
 	if (!ret) {
+#ifdef WITH_BREAKING_CHANGES
+		ret = xstrdup("main");
+#else
 		ret = xstrdup("master");
+#endif /* WITH_BREAKING_CHANGES */
 		if (!quiet)
 			advise_if_enabled(ADVICE_DEFAULT_BRANCH_NAME,
 					  _(default_branch_name_advice), ret);
@@ -1022,7 +1040,6 @@ int is_branch(const char *refname)
 }
 
 struct read_ref_at_cb {
-	const char *refname;
 	timestamp_t at_time;
 	int cnt;
 	int reccnt;
@@ -1052,7 +1069,8 @@ static void set_read_ref_cutoffs(struct read_ref_at_cb *cb,
 		*cb->cutoff_cnt = cb->reccnt;
 }
 
-static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid,
+static int read_ref_at_ent(const char *refname,
+			   struct object_id *ooid, struct object_id *noid,
 			   const char *email UNUSED,
 			   timestamp_t timestamp, int tz,
 			   const char *message, void *cb_data)
@@ -1072,14 +1090,13 @@ static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid,
 			oidcpy(cb->oid, noid);
 			if (!oideq(&cb->ooid, noid))
 				warning(_("log for ref %s has gap after %s"),
-					cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
+					refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
 		}
 		else if (cb->date == cb->at_time)
 			oidcpy(cb->oid, noid);
 		else if (!oideq(noid, cb->oid))
 			warning(_("log for ref %s unexpectedly ended on %s"),
-				cb->refname, show_date(cb->date, cb->tz,
-						       DATE_MODE(RFC2822)));
+				refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
 		cb->reccnt++;
 		oidcpy(&cb->ooid, ooid);
 		oidcpy(&cb->noid, noid);
@@ -1094,7 +1111,8 @@ static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid,
 	return 0;
 }
 
-static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid,
+static int read_ref_at_ent_oldest(const char *refname UNUSED,
+				  struct object_id *ooid, struct object_id *noid,
 				  const char *email UNUSED,
 				  timestamp_t timestamp, int tz,
 				  const char *message, void *cb_data)
@@ -1117,7 +1135,6 @@ int read_ref_at(struct ref_store *refs, const char *refname,
 	struct read_ref_at_cb cb;
 
 	memset(&cb, 0, sizeof(cb));
-	cb.refname = refname;
 	cb.at_time = at_time;
 	cb.cnt = cnt;
 	cb.msg = msg;
@@ -1223,7 +1240,7 @@ int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction,
 		return 0;
 
 	if (!transaction->rejections)
-		BUG("transaction not inititalized with failure support");
+		BUG("transaction not initialized with failure support");
 
 	/*
 	 * Don't accept generic errors, since these errors are not user
@@ -1232,6 +1249,13 @@ int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction,
 	if (err == REF_TRANSACTION_ERROR_GENERIC)
 		return 0;
 
+	/*
+	 * Rejected refnames shouldn't be considered in the availability
+	 * checks, so remove them from the list.
+	 */
+	string_list_remove(&transaction->refnames,
+			   transaction->updates[update_idx]->refname, 0);
+
 	transaction->updates[update_idx]->rejection_err = err;
 	ALLOC_GROW(transaction->rejections->update_indices,
 		   transaction->rejections->nr + 1,
@@ -1362,27 +1386,22 @@ int ref_transaction_update(struct ref_transaction *transaction,
 	return 0;
 }
 
-/*
- * Similar to`ref_transaction_update`, but this function is only for adding
- * a reflog update. Supports providing custom committer information. The index
- * field can be utiltized to order updates as desired. When not used, the
- * updates default to being ordered by refname.
- */
-static int ref_transaction_update_reflog(struct ref_transaction *transaction,
-					 const char *refname,
-					 const struct object_id *new_oid,
-					 const struct object_id *old_oid,
-					 const char *committer_info,
-					 unsigned int flags,
-					 const char *msg,
-					 uint64_t index,
-					 struct strbuf *err)
+int ref_transaction_update_reflog(struct ref_transaction *transaction,
+				  const char *refname,
+				  const struct object_id *new_oid,
+				  const struct object_id *old_oid,
+				  const char *committer_info,
+				  const char *msg,
+				  uint64_t index,
+				  struct strbuf *err)
 {
 	struct ref_update *update;
+	unsigned int flags;
 
 	assert(err);
 
-	flags |= REF_LOG_ONLY | REF_FORCE_CREATE_REFLOG | REF_NO_DEREF;
+	flags = REF_HAVE_OLD | REF_HAVE_NEW | REF_LOG_ONLY | REF_FORCE_CREATE_REFLOG | REF_NO_DEREF |
+		REF_LOG_USE_PROVIDED_OIDS;
 
 	if (!transaction_refname_valid(refname, new_oid, flags, err))
 		return -1;
@@ -1390,11 +1409,6 @@ static int ref_transaction_update_reflog(struct ref_transaction *transaction,
 	update = ref_transaction_add_update(transaction, refname, flags,
 					    new_oid, old_oid, NULL, NULL,
 					    committer_info, msg);
-	/*
-	 * While we do set the old_oid value, we unset the flag to skip
-	 * old_oid verification which only makes sense for refs.
-	 */
-	update->flags &= ~REF_HAVE_OLD;
 	update->index = index;
 
 	/*
@@ -1699,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;
 
@@ -1710,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;
 
@@ -1850,7 +1862,13 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
 
 int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 {
-	return do_for_each_ref(refs, "", NULL, fn, 0,
+	return refs_for_each_rawref_in(refs, "", fn, cb_data);
+}
+
+int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
+			    each_ref_fn fn, void *cb_data)
+{
+	return do_for_each_ref(refs, prefix, NULL, fn, 0,
 			       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
 }
 
@@ -2287,6 +2305,11 @@ int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts)
 	return refs->be->pack_refs(refs, opts);
 }
 
+int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts)
+{
+	return refs->be->optimize(refs, opts);
+}
+
 int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled)
 {
 	if (current_ref_iter &&
@@ -2388,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)
@@ -2405,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)
@@ -2798,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,
@@ -2951,7 +2972,8 @@ struct migration_data {
 	struct ref_store *old_refs;
 	struct ref_transaction *transaction;
 	struct strbuf *errbuf;
-	struct strbuf sb;
+	struct strbuf sb, name, mail;
+	uint64_t index;
 };
 
 static int migrate_one_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
@@ -2984,50 +3006,41 @@ static int migrate_one_ref(const char *refname, const char *referent UNUSED, con
 	return ret;
 }
 
-struct reflog_migration_data {
-	uint64_t index;
-	const char *refname;
-	struct ref_store *old_refs;
-	struct ref_transaction *transaction;
-	struct strbuf *errbuf;
-	struct strbuf *sb;
-};
-
-static int migrate_one_reflog_entry(struct object_id *old_oid,
+static int migrate_one_reflog_entry(const char *refname,
+				    struct object_id *old_oid,
 				    struct object_id *new_oid,
 				    const char *committer,
 				    timestamp_t timestamp, int tz,
 				    const char *msg, void *cb_data)
 {
-	struct reflog_migration_data *data = cb_data;
+	struct migration_data *data = cb_data;
+	struct ident_split ident;
 	const char *date;
 	int ret;
 
-	date = show_date(timestamp, tz, DATE_MODE(NORMAL));
-	strbuf_reset(data->sb);
-	/* committer contains name and email */
-	strbuf_addstr(data->sb, fmt_ident("", committer, WANT_BLANK_IDENT, date, 0));
+	if (split_ident_line(&ident, committer, strlen(committer)) < 0)
+		return -1;
 
-	ret = ref_transaction_update_reflog(data->transaction, data->refname,
-					    new_oid, old_oid, data->sb->buf,
-					    REF_HAVE_NEW | REF_HAVE_OLD, msg,
-					    data->index++, data->errbuf);
+	strbuf_reset(&data->name);
+	strbuf_add(&data->name, ident.name_begin, ident.name_end - ident.name_begin);
+	strbuf_reset(&data->mail);
+	strbuf_add(&data->mail, ident.mail_begin, ident.mail_end - ident.mail_begin);
+
+	date = show_date(timestamp, tz, DATE_MODE(NORMAL));
+	strbuf_reset(&data->sb);
+	strbuf_addstr(&data->sb, fmt_ident(data->name.buf, data->mail.buf, WANT_BLANK_IDENT, date, 0));
+
+	ret = ref_transaction_update_reflog(data->transaction, refname,
+					    new_oid, old_oid, data->sb.buf,
+					    msg, data->index++, data->errbuf);
 	return ret;
 }
 
 static int migrate_one_reflog(const char *refname, void *cb_data)
 {
 	struct migration_data *migration_data = cb_data;
-	struct reflog_migration_data data = {
-		.refname = refname,
-		.old_refs = migration_data->old_refs,
-		.transaction = migration_data->transaction,
-		.errbuf = migration_data->errbuf,
-		.sb = &migration_data->sb,
-	};
-
 	return refs_for_each_reflog_ent(migration_data->old_refs, refname,
-					migrate_one_reflog_entry, &data);
+					migrate_one_reflog_entry, migration_data);
 }
 
 static int move_files(const char *from_path, const char *to_path, struct strbuf *errbuf)
@@ -3122,6 +3135,8 @@ int repo_migrate_ref_storage_format(struct repository *repo,
 	struct strbuf new_gitdir = STRBUF_INIT;
 	struct migration_data data = {
 		.sb = STRBUF_INIT,
+		.name = STRBUF_INIT,
+		.mail = STRBUF_INIT,
 	};
 	int did_migrate_refs = 0;
 	int ret;
@@ -3297,11 +3312,16 @@ int repo_migrate_ref_storage_format(struct repository *repo,
 	ref_transaction_free(transaction);
 	strbuf_release(&new_gitdir);
 	strbuf_release(&data.sb);
+	strbuf_release(&data.name);
+	strbuf_release(&data.mail);
 	return ret;
 }
 
 int ref_update_expects_existing_old_ref(struct ref_update *update)
 {
+	if (update->flags & REF_LOG_ONLY)
+		return 0;
+
 	return (update->flags & REF_HAVE_OLD) &&
 		(!is_null_oid(&update->old_oid) || update->old_target);
 }
@@ -3321,6 +3341,8 @@ const char *ref_transaction_error_msg(enum ref_transaction_error err)
 		return "invalid new value provided";
 	case REF_TRANSACTION_ERROR_EXPECTED_SYMREF:
 		return "expected symref but found regular ref";
+	case REF_TRANSACTION_ERROR_CASE_CONFLICT:
+		return "reference conflict due to case-insensitive filesystem";
 	default:
 		return "unknown failure";
 	}
diff --git a/refs.h b/refs.h
index eedbb59..4e6bd63 100644
--- a/refs.h
+++ b/refs.h
@@ -31,6 +31,8 @@ enum ref_transaction_error {
 	REF_TRANSACTION_ERROR_INVALID_NEW_VALUE = -6,
 	/* Expected ref to be symref, but is a regular ref */
 	REF_TRANSACTION_ERROR_EXPECTED_SYMREF = -7,
+	/* Cannot create ref due to case-insensitive filesystem */
+	REF_TRANSACTION_ERROR_CASE_CONFLICT = -8,
 };
 
 /*
@@ -428,6 +430,8 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
 
 /* can be used to learn about broken ref and symref */
 int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data);
+int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
+			    each_ref_fn fn, void *cb_data);
 
 /*
  * Iterates over all refs including root refs, i.e. pseudorefs and HEAD.
@@ -479,6 +483,12 @@ struct pack_refs_opts {
 int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts);
 
 /*
+ * Optimize the ref store. The exact behavior is up to the backend.
+ * For the files backend, this is equivalent to packing refs.
+ */
+int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts);
+
+/*
  * Setup reflog before using. Fill in err and return -1 on failure.
  */
 int refs_create_reflog(struct ref_store *refs, const char *refname,
@@ -558,10 +568,13 @@ int refs_delete_reflog(struct ref_store *refs, const char *refname);
  * The cb_data is a caller-supplied pointer given to the iterator
  * functions.
  */
-typedef int each_reflog_ent_fn(
-		struct object_id *old_oid, struct object_id *new_oid,
-		const char *committer, timestamp_t timestamp,
-		int tz, const char *msg, void *cb_data);
+typedef int each_reflog_ent_fn(const char *refname,
+			       struct object_id *old_oid,
+			       struct object_id *new_oid,
+			       const char *committer,
+			       timestamp_t timestamp,
+			       int tz, const char *msg,
+			       void *cb_data);
 
 /* Iterate over reflog entries in the log for `refname`. */
 
@@ -760,12 +773,19 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
 #define REF_SKIP_CREATE_REFLOG (1 << 12)
 
 /*
+ * When writing a REF_LOG_ONLY record, use the old and new object IDs provided
+ * in the update instead of resolving the old object ID. The caller must also
+ * set both REF_HAVE_OLD and REF_HAVE_NEW.
+ */
+#define REF_LOG_USE_PROVIDED_OIDS (1 << 13)
+
+/*
  * Bitmask of all of the flags that are allowed to be passed in to
  * ref_transaction_update() and friends:
  */
 #define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS                                  \
 	(REF_NO_DEREF | REF_FORCE_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION | \
-	 REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_CREATE_REFLOG)
+	 REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_CREATE_REFLOG | REF_LOG_USE_PROVIDED_OIDS)
 
 /*
  * Add a reference update to transaction. `new_oid` is the value that
@@ -795,6 +815,21 @@ int ref_transaction_update(struct ref_transaction *transaction,
 			   struct strbuf *err);
 
 /*
+ * Similar to `ref_transaction_update`, but this function is only for adding
+ * a reflog update. Supports providing custom committer information. The index
+ * field can be utiltized to order updates as desired. When set to zero, the
+ * updates default to being ordered by refname.
+ */
+int ref_transaction_update_reflog(struct ref_transaction *transaction,
+				  const char *refname,
+				  const struct object_id *new_oid,
+				  const struct object_id *old_oid,
+				  const char *committer_info,
+				  const char *msg,
+				  uint64_t index,
+				  struct strbuf *err);
+
+/*
  * Add a reference creation to transaction. new_oid is the value that
  * the reference should have after the update; it must not be
  * null_oid. It is verified that the reference does not exist
diff --git a/refs/debug.c b/refs/debug.c
index da300ef..697adbd 100644
--- a/refs/debug.c
+++ b/refs/debug.c
@@ -1,7 +1,6 @@
 #include "git-compat-util.h"
 #include "hex.h"
 #include "refs-internal.h"
-#include "string-list.h"
 #include "trace.h"
 
 static struct trace_key trace_refs = TRACE_KEY_INIT(REFS);
@@ -277,7 +276,8 @@ struct debug_reflog {
 	void *cb_data;
 };
 
-static int debug_print_reflog_ent(struct object_id *old_oid,
+static int debug_print_reflog_ent(const char *refname,
+				  struct object_id *old_oid,
 				  struct object_id *new_oid,
 				  const char *committer, timestamp_t timestamp,
 				  int tz, const char *msg, void *cb_data)
@@ -292,7 +292,7 @@ static int debug_print_reflog_ent(struct object_id *old_oid,
 	if (new_oid)
 		oid_to_hex_r(n, new_oid);
 
-	ret = dbg->fn(old_oid, new_oid, committer, timestamp, tz, msg,
+	ret = dbg->fn(refname, old_oid, new_oid, committer, timestamp, tz, msg,
 		      dbg->cb_data);
 	trace_printf_key(&trace_refs,
 			 "reflog_ent %s (ret %d): %s -> %s, %s %ld \"%.*s\"\n",
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 088b52c..5ddf418 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -20,7 +20,6 @@
 #include "../dir-iterator.h"
 #include "../lockfile.h"
 #include "../object.h"
-#include "../object-file.h"
 #include "../path.h"
 #include "../dir.h"
 #include "../chdir-notify.h"
@@ -68,6 +67,12 @@
  */
 #define REF_DELETED_RMDIR (1 << 9)
 
+/*
+ * Used to indicate that the reflog-only update has been created via
+ * `split_head_update()`.
+ */
+#define REF_LOG_VIA_SPLIT (1 << 14)
+
 struct ref_lock {
 	char *ref_name;
 	struct lock_file lk;
@@ -648,6 +653,26 @@ static void unlock_ref(struct ref_lock *lock)
 }
 
 /*
+ * Check if the transaction has another update with a case-insensitive refname
+ * match.
+ *
+ * If the update is part of the transaction, we only check up to that index.
+ * Further updates are expected to call this function to match previous indices.
+ */
+static bool transaction_has_case_conflicting_update(struct ref_transaction *transaction,
+						    struct ref_update *update)
+{
+	for (size_t i = 0; i < transaction->nr; i++) {
+		if (transaction->updates[i] == update)
+			break;
+
+		if (!strcasecmp(transaction->updates[i]->refname, update->refname))
+			return true;
+	}
+	return false;
+}
+
+/*
  * Lock refname, without following symrefs, and set *lock_p to point
  * at a newly-allocated lock object. Fill in lock->old_oid, referent,
  * and type similarly to read_raw_ref().
@@ -677,16 +702,17 @@ static void unlock_ref(struct ref_lock *lock)
  * - Generate informative error messages in the case of failure
  */
 static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
-					       struct ref_update *update,
+					       struct ref_transaction *transaction,
 					       size_t update_idx,
 					       int mustexist,
 					       struct string_list *refnames_to_check,
-					       const struct string_list *extras,
 					       struct ref_lock **lock_p,
 					       struct strbuf *referent,
 					       struct strbuf *err)
 {
 	enum ref_transaction_error ret = REF_TRANSACTION_ERROR_GENERIC;
+	struct ref_update *update = transaction->updates[update_idx];
+	const struct string_list *extras = &transaction->refnames;
 	const char *refname = update->refname;
 	unsigned int *type = &update->type;
 	struct ref_lock *lock;
@@ -776,6 +802,24 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
 			goto retry;
 		} else {
 			unable_to_lock_message(ref_file.buf, myerr, err);
+			if (myerr == EEXIST) {
+				if (ignore_case &&
+				    transaction_has_case_conflicting_update(transaction, update)) {
+					/*
+					 * In case-insensitive filesystems, ensure that conflicts within a
+					 * given transaction are handled. Pre-existing refs on a
+					 * case-insensitive system will be overridden without any issue.
+					 */
+					ret = REF_TRANSACTION_ERROR_CASE_CONFLICT;
+				} else {
+					/*
+					 * Pre-existing case-conflicting reference locks should also be
+					 * specially categorized to avoid failing all batched updates.
+					 */
+					ret = REF_TRANSACTION_ERROR_CREATE_EXISTS;
+				}
+			}
+
 			goto error_return;
 		}
 	}
@@ -831,6 +875,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
 				goto error_return;
 			} else if (remove_dir_recursively(&ref_file,
 							  REMOVE_DIR_EMPTY_ONLY)) {
+				ret = REF_TRANSACTION_ERROR_NAME_CONFLICT;
 				if (refs_verify_refname_available(
 						    &refs->base, refname,
 						    extras, NULL, 0, err)) {
@@ -838,14 +883,14 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
 					 * The error message set by
 					 * verify_refname_available() is OK.
 					 */
-					ret = REF_TRANSACTION_ERROR_NAME_CONFLICT;
 					goto error_return;
 				} else {
 					/*
-					 * We can't delete the directory,
-					 * but we also don't know of any
-					 * references that it should
-					 * contain.
+					 * Directory conflicts can occur if there
+					 * is an existing lock file in the directory
+					 * or if the filesystem is case-insensitive
+					 * and the directory contains a valid reference
+					 * but conflicts with the update.
 					 */
 					strbuf_addf(err, "there is a non-empty directory '%s' "
 						    "blocking reference '%s'",
@@ -867,8 +912,23 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
 		 * If the ref did not exist and we are creating it, we have to
 		 * make sure there is no existing packed ref that conflicts
 		 * with refname. This check is deferred so that we can batch it.
+		 *
+		 * For case-insensitive filesystems, we should also check for F/D
+		 * conflicts between 'foo' and 'Foo/bar'. So let's lowercase
+		 * the refname.
 		 */
-		item = string_list_append(refnames_to_check, refname);
+		if (ignore_case) {
+			struct strbuf lower = STRBUF_INIT;
+
+			strbuf_addstr(&lower, refname);
+			strbuf_tolower(&lower);
+
+			item = string_list_append_nodup(refnames_to_check,
+							strbuf_detach(&lower, NULL));
+		} else {
+			item = string_list_append(refnames_to_check, refname);
+		}
+
 		item->util = xmalloc(sizeof(update_idx));
 		memcpy(item->util, &update_idx, sizeof(update_idx));
 	}
@@ -1467,6 +1527,15 @@ static int files_pack_refs(struct ref_store *ref_store,
 	return 0;
 }
 
+static int files_optimize(struct ref_store *ref_store, struct pack_refs_opts *opts)
+{
+	/*
+	 * For the "files" backend, "optimizing" is the same as "packing".
+	 * So, we just call the existing worker function for packing.
+	 */
+	return files_pack_refs(ref_store, opts);
+}
+
 /*
  * People using contrib's git-new-workdir have .git/logs/refs ->
  * /some/other/path/.git/logs/refs, and that may live on another device.
@@ -2109,7 +2178,9 @@ static int files_delete_reflog(struct ref_store *ref_store,
 	return ret;
 }
 
-static int show_one_reflog_ent(struct files_ref_store *refs, struct strbuf *sb,
+static int show_one_reflog_ent(struct files_ref_store *refs,
+			       const char *refname,
+			       struct strbuf *sb,
 			       each_reflog_ent_fn fn, void *cb_data)
 {
 	struct object_id ooid, noid;
@@ -2136,7 +2207,7 @@ static int show_one_reflog_ent(struct files_ref_store *refs, struct strbuf *sb,
 		message += 6;
 	else
 		message += 7;
-	return fn(&ooid, &noid, p, timestamp, tz, message, cb_data);
+	return fn(refname, &ooid, &noid, p, timestamp, tz, message, cb_data);
 }
 
 static char *find_beginning_of_line(char *bob, char *scan)
@@ -2220,7 +2291,7 @@ static int files_for_each_reflog_ent_reverse(struct ref_store *ref_store,
 				strbuf_splice(&sb, 0, 0, bp + 1, endp - (bp + 1));
 				scanp = bp;
 				endp = bp + 1;
-				ret = show_one_reflog_ent(refs, &sb, fn, cb_data);
+				ret = show_one_reflog_ent(refs, refname, &sb, fn, cb_data);
 				strbuf_reset(&sb);
 				if (ret)
 					break;
@@ -2232,7 +2303,7 @@ static int files_for_each_reflog_ent_reverse(struct ref_store *ref_store,
 				 * Process it, and we can end the loop.
 				 */
 				strbuf_splice(&sb, 0, 0, buf, endp - buf);
-				ret = show_one_reflog_ent(refs, &sb, fn, cb_data);
+				ret = show_one_reflog_ent(refs, refname, &sb, fn, cb_data);
 				strbuf_reset(&sb);
 				break;
 			}
@@ -2282,7 +2353,7 @@ static int files_for_each_reflog_ent(struct ref_store *ref_store,
 		return -1;
 
 	while (!ret && !strbuf_getwholeline(&sb, logfp, '\n'))
-		ret = show_one_reflog_ent(refs, &sb, fn, cb_data);
+		ret = show_one_reflog_ent(refs, refname, &sb, fn, cb_data);
 	fclose(logfp);
 	strbuf_release(&sb);
 	return ret;
@@ -2421,9 +2492,10 @@ static enum ref_transaction_error split_head_update(struct ref_update *update,
 
 	new_update = ref_transaction_add_update(
 			transaction, "HEAD",
-			update->flags | REF_LOG_ONLY | REF_NO_DEREF,
+			update->flags | REF_LOG_ONLY | REF_NO_DEREF | REF_LOG_VIA_SPLIT,
 			&update->new_oid, &update->old_oid,
 			NULL, NULL, update->committer_info, update->msg);
+	new_update->parent_update = update;
 
 	/*
 	 * Add "HEAD". This insertion is O(N) in the transaction
@@ -2494,7 +2566,6 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update,
 	 * done when new_update is processed.
 	 */
 	update->flags |= REF_LOG_ONLY | REF_NO_DEREF;
-	update->flags &= ~REF_HAVE_OLD;
 
 	return 0;
 }
@@ -2507,12 +2578,37 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update,
  */
 static enum ref_transaction_error check_old_oid(struct ref_update *update,
 						struct object_id *oid,
+						struct strbuf *referent,
 						struct strbuf *err)
 {
-	if (!(update->flags & REF_HAVE_OLD) ||
-		   oideq(oid, &update->old_oid))
+	if (update->flags & REF_LOG_ONLY ||
+	    !(update->flags & REF_HAVE_OLD))
 		return 0;
 
+	if (oideq(oid, &update->old_oid)) {
+		/*
+		 * Normally matching the expected old oid is enough. Either we
+		 * found the ref at the expected state, or we are creating and
+		 * expect the null oid (and likewise found nothing).
+		 *
+		 * But there is one exception for the null oid: if we found a
+		 * symref pointing to nothing we'll also get the null oid. In
+		 * regular recursive mode, that's good (we'll write to what the
+		 * symref points to, which doesn't exist). But in no-deref
+		 * mode, it means we'll clobber the symref, even though the
+		 * caller asked for this to be a creation event. So flag
+		 * that case to preserve the dangling symref.
+		 */
+		if ((update->flags & REF_NO_DEREF) && referent->len &&
+		    is_null_oid(oid)) {
+			strbuf_addf(err, "cannot lock ref '%s': "
+				    "dangling symref already exists",
+				    ref_update_original_update_refname(update));
+			return REF_TRANSACTION_ERROR_CREATE_EXISTS;
+		}
+		return 0;
+	}
+
 	if (is_null_oid(&update->old_oid)) {
 		strbuf_addf(err, "cannot lock ref '%s': "
 			    "reference already exists",
@@ -2583,9 +2679,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re
 	if (lock) {
 		lock->count++;
 	} else {
-		ret = lock_raw_ref(refs, update, update_idx, mustexist,
-				   refnames_to_check, &transaction->refnames,
-				   &lock, &referent, err);
+		ret = lock_raw_ref(refs, transaction, update_idx, mustexist,
+				   refnames_to_check, &lock, &referent, err);
 		if (ret) {
 			char *reason;
 
@@ -2601,7 +2696,36 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re
 
 	update->backend_data = lock;
 
-	if (update->type & REF_ISSYMREF) {
+	if (update->flags & REF_LOG_VIA_SPLIT) {
+		struct ref_lock *parent_lock;
+
+		if (!update->parent_update)
+			BUG("split update without a parent");
+
+		parent_lock = update->parent_update->backend_data;
+
+		/*
+		 * Check that "HEAD" didn't racily change since we have looked
+		 * it up. If it did we must refuse to write the reflog entry.
+		 *
+		 * Note that this does not catch all races: if "HEAD" was
+		 * racily changed to point to one of the refs part of the
+		 * transaction then we would miss writing the split reflog
+		 * entry for "HEAD".
+		 */
+		if (!(update->type & REF_ISSYMREF) ||
+		    strcmp(update->parent_update->refname, referent.buf)) {
+			strbuf_addstr(err, "HEAD has been racily updated");
+			ret = REF_TRANSACTION_ERROR_GENERIC;
+			goto out;
+		}
+
+		if (update->flags & REF_HAVE_OLD) {
+			oidcpy(&lock->old_oid, &update->old_oid);
+		} else {
+			oidcpy(&lock->old_oid, &parent_lock->old_oid);
+		}
+	} else if (update->type & REF_ISSYMREF) {
 		if (update->flags & REF_NO_DEREF) {
 			/*
 			 * We won't be reading the referent as part of
@@ -2623,7 +2747,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re
 			if (update->old_target)
 				ret = ref_update_check_old_target(referent.buf, update, err);
 			else
-				ret = check_old_oid(update, &lock->old_oid, err);
+				ret = check_old_oid(update, &lock->old_oid,
+						    &referent, err);
 			if (ret)
 				goto out;
 		} else {
@@ -2655,7 +2780,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re
 			ret = REF_TRANSACTION_ERROR_EXPECTED_SYMREF;
 			goto out;
 		} else {
-			ret = check_old_oid(update, &lock->old_oid, err);
+			ret = check_old_oid(update, &lock->old_oid,
+					    &referent, err);
 			if  (ret) {
 				goto out;
 			}
@@ -2794,7 +2920,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
 			       "ref_transaction_prepare");
 	size_t i;
 	int ret = 0;
-	struct string_list refnames_to_check = STRING_LIST_INIT_NODUP;
+	struct string_list refnames_to_check = STRING_LIST_INIT_DUP;
 	char *head_ref = NULL;
 	int head_type;
 	struct files_transaction_backend_data *backend_data;
@@ -2977,6 +3103,20 @@ static int parse_and_write_reflog(struct files_ref_store *refs,
 				  struct ref_lock *lock,
 				  struct strbuf *err)
 {
+	struct object_id *old_oid = &lock->old_oid;
+
+	if (update->flags & REF_LOG_USE_PROVIDED_OIDS) {
+		if (!(update->flags & REF_HAVE_OLD) ||
+		    !(update->flags & REF_HAVE_NEW) ||
+		    !(update->flags & REF_LOG_ONLY)) {
+			strbuf_addf(err, _("trying to write reflog for '%s'"
+					   "with incomplete values"), update->refname);
+			return REF_TRANSACTION_ERROR_GENERIC;
+		}
+
+		old_oid = &update->old_oid;
+	}
+
 	if (update->new_target) {
 		/*
 		 * We want to get the resolved OID for the target, to ensure
@@ -2994,7 +3134,7 @@ static int parse_and_write_reflog(struct files_ref_store *refs,
 		}
 	}
 
-	if (files_log_ref_write(refs, lock->ref_name, &lock->old_oid,
+	if (files_log_ref_write(refs, lock->ref_name, old_oid,
 				&update->new_oid, update->committer_info,
 				update->msg, update->flags, err)) {
 		char *old_msg = strbuf_detach(err, NULL);
@@ -3062,7 +3202,8 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
 	for (i = 0; i < transaction->nr; i++) {
 		struct ref_update *update = transaction->updates[i];
 
-		if ((update->flags & REF_HAVE_OLD) &&
+		if (!(update->flags & REF_LOG_ONLY) &&
+		    (update->flags & REF_HAVE_OLD) &&
 		    !is_null_oid(&update->old_oid))
 			BUG("initial ref transaction with old_sha1 set");
 
@@ -3309,7 +3450,8 @@ struct expire_reflog_cb {
 		     dry_run:1;
 };
 
-static int expire_reflog_ent(struct object_id *ooid, struct object_id *noid,
+static int expire_reflog_ent(const char *refname UNUSED,
+			     struct object_id *ooid, struct object_id *noid,
 			     const char *email, timestamp_t timestamp, int tz,
 			     const char *message, void *cb_data)
 {
@@ -3827,8 +3969,6 @@ static int files_fsck_refs(struct ref_store *ref_store,
 		NULL,
 	};
 
-	if (o->verbose)
-		fprintf_ln(stderr, _("Checking references consistency"));
 	return files_fsck_refs_dir(ref_store, o, "refs", wt, fsck_refs_fn);
 }
 
@@ -3855,6 +3995,7 @@ struct ref_storage_be refs_be_files = {
 	.transaction_abort = files_transaction_abort,
 
 	.pack_refs = files_pack_refs,
+	.optimize = files_optimize,
 	.rename_ref = files_rename_ref,
 	.copy_ref = files_copy_ref,
 
diff --git a/refs/ref-cache.c b/refs/ref-cache.c
index c180e0a..e5e5df1 100644
--- a/refs/ref-cache.c
+++ b/refs/ref-cache.c
@@ -539,7 +539,7 @@ static int cache_ref_iterator_seek(struct ref_iterator *ref_iterator,
 				 */
 				break;
 			}
-		} while (slash);
+		} while (slash && dir->nr);
 	}
 
 	return 0;
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 40c1c0f..4ef3bd7 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -447,6 +447,8 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs,
 
 typedef int pack_refs_fn(struct ref_store *ref_store,
 			 struct pack_refs_opts *opts);
+typedef int optimize_fn(struct ref_store *ref_store,
+			struct pack_refs_opts *opts);
 typedef int rename_ref_fn(struct ref_store *ref_store,
 			  const char *oldref, const char *newref,
 			  const char *logmsg);
@@ -572,6 +574,7 @@ struct ref_storage_be {
 	ref_transaction_abort_fn *transaction_abort;
 
 	pack_refs_fn *pack_refs;
+	optimize_fn *optimize;
 	rename_ref_fn *rename_ref;
 	copy_ref_fn *copy_ref;
 
@@ -662,7 +665,8 @@ enum ref_transaction_error ref_update_check_old_target(const char *referent,
 
 /*
  * Check if the ref must exist, this means that the old_oid or
- * old_target is non NULL.
+ * old_target is non NULL. Log-only updates never require the old state to
+ * match.
  */
 int ref_update_expects_existing_old_ref(struct ref_update *update);
 
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 8dae1e1..d4b7928 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -6,20 +6,21 @@
 #include "../config.h"
 #include "../dir.h"
 #include "../environment.h"
+#include "../fsck.h"
 #include "../gettext.h"
 #include "../hash.h"
 #include "../hex.h"
 #include "../iterator.h"
 #include "../ident.h"
-#include "../lockfile.h"
 #include "../object.h"
 #include "../path.h"
 #include "../refs.h"
 #include "../reftable/reftable-basics.h"
-#include "../reftable/reftable-stack.h"
-#include "../reftable/reftable-record.h"
 #include "../reftable/reftable-error.h"
+#include "../reftable/reftable-fsck.h"
 #include "../reftable/reftable-iterator.h"
+#include "../reftable/reftable-record.h"
+#include "../reftable/reftable-stack.h"
 #include "../repo-settings.h"
 #include "../setup.h"
 #include "../strmap.h"
@@ -1012,10 +1013,6 @@ static int prepare_transaction_update(struct write_transaction_table_arg **out,
 	if (!arg) {
 		struct reftable_addition *addition;
 
-		ret = reftable_stack_reload(be->stack);
-		if (ret)
-			return ret;
-
 		ret = reftable_stack_new_addition(&addition, be->stack,
 						  REFTABLE_STACK_NEW_ADDITION_RELOAD);
 		if (ret) {
@@ -1102,6 +1099,20 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
 	if (ret)
 		return REF_TRANSACTION_ERROR_GENERIC;
 
+	if (u->flags & REF_LOG_USE_PROVIDED_OIDS) {
+		if (!(u->flags & REF_HAVE_OLD) ||
+		    !(u->flags & REF_HAVE_NEW) ||
+		    !(u->flags & REF_LOG_ONLY)) {
+			strbuf_addf(err, _("trying to write reflog for '%s'"
+					   "with incomplete values"), u->refname);
+			return REF_TRANSACTION_ERROR_GENERIC;
+		}
+
+		if (queue_transaction_update(refs, tx_data, u, &u->old_oid, err))
+			return REF_TRANSACTION_ERROR_GENERIC;
+		return 0;
+	}
+
 	/* Verify that the new object ID is valid. */
 	if ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) &&
 	    !(u->flags & REF_SKIP_OID_VERIFICATION) &&
@@ -1186,8 +1197,6 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
 	if (ret > 0) {
 		/* The reference does not exist, but we expected it to. */
 		strbuf_addf(err, _("cannot lock ref '%s': "
-
-
 				   "unable to resolve reference '%s'"),
 			    ref_update_original_update_refname(u), u->refname);
 		return REF_TRANSACTION_ERROR_NONEXISTENT_REF;
@@ -1241,13 +1250,8 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
 
 			new_update->parent_update = u;
 
-			/*
-			 * Change the symbolic ref update to log only. Also, it
-			 * doesn't need to check its old OID value, as that will be
-			 * done when new_update is processed.
-			 */
+			/* Change the symbolic ref update to log only. */
 			u->flags |= REF_LOG_ONLY | REF_NO_DEREF;
-			u->flags &= ~REF_HAVE_OLD;
 		}
 	}
 
@@ -1271,8 +1275,33 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
 		ret = ref_update_check_old_target(referent->buf, u, err);
 		if (ret)
 			return ret;
-	} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {
-		if (is_null_oid(&u->old_oid)) {
+	} else if ((u->flags & (REF_LOG_ONLY | REF_HAVE_OLD)) == REF_HAVE_OLD) {
+		if (oideq(&current_oid, &u->old_oid)) {
+			/*
+			 * Normally matching the expected old oid is enough. Either we
+			 * found the ref at the expected state, or we are creating and
+			 * expect the null oid (and likewise found nothing).
+			 *
+			 * But there is one exception for the null oid: if we found a
+			 * symref pointing to nothing we'll also get the null oid. In
+			 * regular recursive mode, that's good (we'll write to what the
+			 * symref points to, which doesn't exist). But in no-deref
+			 * mode, it means we'll clobber the symref, even though the
+			 * caller asked for this to be a creation event. So flag
+			 * that case to preserve the dangling symref.
+			 *
+			 * Everything else is OK and we can fall through to the
+			 * end of the conditional chain.
+			 */
+			if ((u->flags & REF_NO_DEREF) &&
+			    referent->len &&
+			    is_null_oid(&u->old_oid)) {
+				strbuf_addf(err, _("cannot lock ref '%s': "
+					    "dangling symref already exists"),
+					    ref_update_original_update_refname(u));
+				return REF_TRANSACTION_ERROR_CREATE_EXISTS;
+			}
+		} else if (is_null_oid(&u->old_oid)) {
 			strbuf_addf(err, _("cannot lock ref '%s': "
 					   "reference already exists"),
 				    ref_update_original_update_refname(u));
@@ -1713,6 +1742,12 @@ static int reftable_be_pack_refs(struct ref_store *ref_store,
 	return ret;
 }
 
+static int reftable_be_optimize(struct ref_store *ref_store,
+				struct pack_refs_opts *opts)
+{
+	return reftable_be_pack_refs(ref_store, opts);
+}
+
 struct write_create_symref_arg {
 	struct reftable_ref_store *refs;
 	struct reftable_stack *stack;
@@ -1966,7 +2001,8 @@ static int reftable_be_rename_ref(struct ref_store *ref_store,
 	ret = backend_for(&arg.be, refs, newrefname, &newrefname, 1);
 	if (ret)
 		goto done;
-	ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg);
+	ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg,
+				 REFTABLE_STACK_NEW_ADDITION_RELOAD);
 
 done:
 	assert(ret != REFTABLE_API_ERROR);
@@ -1995,7 +2031,8 @@ static int reftable_be_copy_ref(struct ref_store *ref_store,
 	ret = backend_for(&arg.be, refs, newrefname, &newrefname, 1);
 	if (ret)
 		goto done;
-	ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg);
+	ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg,
+				 REFTABLE_STACK_NEW_ADDITION_RELOAD);
 
 done:
 	assert(ret != REFTABLE_API_ERROR);
@@ -2147,7 +2184,7 @@ static int yield_log_record(struct reftable_ref_store *refs,
 
 	full_committer = fmt_ident(log->value.update.name, log->value.update.email,
 				   WANT_COMMITTER_IDENT, NULL, IDENT_NO_DATE);
-	return fn(&old_oid, &new_oid, full_committer,
+	return fn(log->refname, &old_oid, &new_oid, full_committer,
 		  log->value.update.time, log->value.update.tz_offset,
 		  log->value.update.message, cb_data);
 }
@@ -2367,7 +2404,8 @@ static int reftable_be_create_reflog(struct ref_store *ref_store,
 		goto done;
 	arg.stack = be->stack;
 
-	ret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg);
+	ret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg,
+				 REFTABLE_STACK_NEW_ADDITION_RELOAD);
 
 done:
 	return ret;
@@ -2438,7 +2476,8 @@ static int reftable_be_delete_reflog(struct ref_store *ref_store,
 		return ret;
 	arg.stack = be->stack;
 
-	ret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg);
+	ret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg,
+				 REFTABLE_STACK_NEW_ADDITION_RELOAD);
 
 	assert(ret != REFTABLE_API_ERROR);
 	return ret;
@@ -2559,6 +2598,11 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store,
 	if (ret < 0)
 		goto done;
 
+	ret = reftable_stack_new_addition(&add, be->stack,
+					  REFTABLE_STACK_NEW_ADDITION_RELOAD);
+	if (ret < 0)
+		goto done;
+
 	ret = reftable_stack_init_log_iterator(be->stack, &it);
 	if (ret < 0)
 		goto done;
@@ -2567,10 +2611,6 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store,
 	if (ret < 0)
 		goto done;
 
-	ret = reftable_stack_new_addition(&add, be->stack, 0);
-	if (ret < 0)
-		goto done;
-
 	ret = reftable_backend_read_ref(be, refname, &oid, &referent, &type);
 	if (ret < 0)
 		goto done;
@@ -2675,11 +2715,56 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store,
 	return ret;
 }
 
-static int reftable_be_fsck(struct ref_store *ref_store UNUSED,
-			    struct fsck_options *o UNUSED,
+static void reftable_fsck_verbose_handler(const char *msg, void *cb_data)
+{
+	struct fsck_options *o = cb_data;
+
+	if (o->verbose)
+		fprintf_ln(stderr, "%s", msg);
+}
+
+static const enum fsck_msg_id fsck_msg_id_map[] = {
+	[REFTABLE_FSCK_ERROR_TABLE_NAME] = FSCK_MSG_BAD_REFTABLE_TABLE_NAME,
+};
+
+static int reftable_fsck_error_handler(struct reftable_fsck_info *info,
+				       void *cb_data)
+{
+	struct fsck_ref_report report = { .path = info->path };
+	struct fsck_options *o = cb_data;
+	enum fsck_msg_id msg_id;
+
+	if (info->error < 0 || info->error >= REFTABLE_FSCK_MAX_VALUE)
+		BUG("unknown fsck error: %d", (int)info->error);
+
+	msg_id = fsck_msg_id_map[info->error];
+
+	if (!msg_id)
+		BUG("fsck_msg_id value missing for reftable error: %d", (int)info->error);
+
+	return fsck_report_ref(o, &report, msg_id, "%s", info->msg);
+}
+
+static int reftable_be_fsck(struct ref_store *ref_store, struct fsck_options *o,
 			    struct worktree *wt UNUSED)
 {
-	return 0;
+	struct reftable_ref_store *refs;
+	struct strmap_entry *entry;
+	struct hashmap_iter iter;
+	int ret = 0;
+
+	refs = reftable_be_downcast(ref_store, REF_STORE_READ, "fsck");
+
+	ret |= reftable_fsck_check(refs->main_backend.stack, reftable_fsck_error_handler,
+				   reftable_fsck_verbose_handler, o);
+
+	strmap_for_each_entry(&refs->worktree_backends, &iter, entry) {
+		struct reftable_backend *b = (struct reftable_backend *)entry->value;
+		ret |= reftable_fsck_check(b->stack, reftable_fsck_error_handler,
+					   reftable_fsck_verbose_handler, o);
+	}
+
+	return ret;
 }
 
 struct ref_storage_be refs_be_reftable = {
@@ -2694,6 +2779,7 @@ struct ref_storage_be refs_be_reftable = {
 	.transaction_abort = reftable_be_transaction_abort,
 
 	.pack_refs = reftable_be_pack_refs,
+	.optimize = reftable_be_optimize,
 	.rename_ref = reftable_be_rename_ref,
 	.copy_ref = reftable_be_copy_ref,
 
diff --git a/reftable/basics.c b/reftable/basics.c
index 9988ebd..e969927 100644
--- a/reftable/basics.c
+++ b/reftable/basics.c
@@ -195,44 +195,55 @@ size_t names_length(const char **names)
 	return p - names;
 }
 
-char **parse_names(char *buf, int size)
+int parse_names(char *buf, int size, char ***out)
 {
 	char **names = NULL;
 	size_t names_cap = 0;
 	size_t names_len = 0;
 	char *p = buf;
 	char *end = buf + size;
+	int err = 0;
 
 	while (p < end) {
 		char *next = strchr(p, '\n');
-		if (next && next < end) {
-			*next = 0;
+		if (!next) {
+			err = REFTABLE_FORMAT_ERROR;
+			goto done;
+		} else if (next < end) {
+			*next = '\0';
 		} else {
 			next = end;
 		}
+
 		if (p < next) {
 			if (REFTABLE_ALLOC_GROW(names, names_len + 1,
-						names_cap))
-				goto err;
+						names_cap)) {
+				err = REFTABLE_OUT_OF_MEMORY_ERROR;
+				goto done;
+			}
 
 			names[names_len] = reftable_strdup(p);
-			if (!names[names_len++])
-				goto err;
+			if (!names[names_len++]) {
+				err = REFTABLE_OUT_OF_MEMORY_ERROR;
+				goto done;
+			}
 		}
 		p = next + 1;
 	}
 
-	if (REFTABLE_ALLOC_GROW(names, names_len + 1, names_cap))
-		goto err;
+	if (REFTABLE_ALLOC_GROW(names, names_len + 1, names_cap)) {
+		err = REFTABLE_OUT_OF_MEMORY_ERROR;
+		goto done;
+	}
 	names[names_len] = NULL;
 
-	return names;
-
-err:
+	*out = names;
+	return 0;
+done:
 	for (size_t i = 0; i < names_len; i++)
 		reftable_free(names[i]);
 	reftable_free(names);
-	return NULL;
+	return err;
 }
 
 int names_equal(const char **a, const char **b)
diff --git a/reftable/basics.h b/reftable/basics.h
index 7d22f96..e4b83b2 100644
--- a/reftable/basics.h
+++ b/reftable/basics.h
@@ -167,10 +167,11 @@ void free_names(char **a);
 
 /*
  * Parse a newline separated list of names. `size` is the length of the buffer,
- * without terminating '\0'. Empty names are discarded. Returns a `NULL`
- * pointer when allocations fail.
+ * without terminating '\0'. Empty names are discarded.
+ *
+ * Returns 0 on success, a reftable error code on error.
  */
-char **parse_names(char *buf, int size);
+int parse_names(char *buf, int size, char ***out);
 
 /* compares two NULL-terminated arrays of strings. */
 int names_equal(const char **a, const char **b);
diff --git a/reftable/fsck.c b/reftable/fsck.c
new file mode 100644
index 0000000..26b9115
--- /dev/null
+++ b/reftable/fsck.c
@@ -0,0 +1,100 @@
+#include "basics.h"
+#include "reftable-fsck.h"
+#include "reftable-table.h"
+#include "stack.h"
+
+static bool table_has_valid_name(const char *name)
+{
+	const char *ptr = name;
+	char *endptr;
+
+	/* strtoull doesn't set errno on success */
+	errno = 0;
+
+	strtoull(ptr, &endptr, 16);
+	if (errno)
+		return false;
+	ptr = endptr;
+
+	if (*ptr != '-')
+		return false;
+	ptr++;
+
+	strtoull(ptr, &endptr, 16);
+	if (errno)
+		return false;
+	ptr = endptr;
+
+	if (*ptr != '-')
+		return false;
+	ptr++;
+
+	strtoul(ptr, &endptr, 16);
+	if (errno)
+		return false;
+	ptr = endptr;
+
+	if (strcmp(ptr, ".ref") && strcmp(ptr, ".log"))
+		return false;
+
+	return true;
+}
+
+typedef int (*table_check_fn)(struct reftable_table *table,
+			      reftable_fsck_report_fn report_fn,
+			      void *cb_data);
+
+static int table_check_name(struct reftable_table *table,
+			    reftable_fsck_report_fn report_fn,
+			    void *cb_data)
+{
+	if (!table_has_valid_name(table->name)) {
+		struct reftable_fsck_info info;
+
+		info.error = REFTABLE_FSCK_ERROR_TABLE_NAME;
+		info.msg = "invalid reftable table name";
+		info.path = table->name;
+
+		return report_fn(&info, cb_data);
+	}
+
+	return 0;
+}
+
+static int table_checks(struct reftable_table *table,
+			reftable_fsck_report_fn report_fn,
+			reftable_fsck_verbose_fn verbose_fn UNUSED,
+			void *cb_data)
+{
+	table_check_fn table_check_fns[] = {
+		table_check_name,
+		NULL,
+	};
+	int err = 0;
+
+	for (size_t i = 0; table_check_fns[i]; i++)
+		err |= table_check_fns[i](table, report_fn, cb_data);
+
+	return err;
+}
+
+int reftable_fsck_check(struct reftable_stack *stack,
+			reftable_fsck_report_fn report_fn,
+			reftable_fsck_verbose_fn verbose_fn,
+			void *cb_data)
+{
+	struct reftable_buf msg = REFTABLE_BUF_INIT;
+	int err = 0;
+
+	for (size_t i = 0; i < stack->tables_len; i++) {
+		reftable_buf_reset(&msg);
+		reftable_buf_addstr(&msg, "Checking table: ");
+		reftable_buf_addstr(&msg, stack->tables[i]->name);
+		verbose_fn(msg.buf, cb_data);
+
+		err |= table_checks(stack->tables[i], report_fn, verbose_fn, cb_data);
+	}
+
+	reftable_buf_release(&msg);
+	return err;
+}
diff --git a/reftable/reftable-fsck.h b/reftable/reftable-fsck.h
new file mode 100644
index 0000000..007a392
--- /dev/null
+++ b/reftable/reftable-fsck.h
@@ -0,0 +1,40 @@
+#ifndef REFTABLE_FSCK_H
+#define REFTABLE_FSCK_H
+
+#include "reftable-stack.h"
+
+enum reftable_fsck_error {
+	/* Invalid table name */
+	REFTABLE_FSCK_ERROR_TABLE_NAME = 0,
+	/* Used for bounds checking, must be last */
+	REFTABLE_FSCK_MAX_VALUE,
+};
+
+/* Represents an individual error encountered during the FSCK checks. */
+struct reftable_fsck_info {
+	enum reftable_fsck_error error;
+	const char *msg;
+	const char *path;
+};
+
+typedef int reftable_fsck_report_fn(struct reftable_fsck_info *info,
+				    void *cb_data);
+typedef void reftable_fsck_verbose_fn(const char *msg, void *cb_data);
+
+/*
+ * Given a reftable stack, perform consistency checks on the stack.
+ *
+ * If an issue is encountered, the issue is reported to the callee via the
+ * provided 'report_fn'. If the issue is non-recoverable the flow will not
+ * continue. If it is recoverable, the flow will continue and further issues
+ * will be reported as identified.
+ *
+ * The 'verbose_fn' will be invoked to provide verbose information about
+ * the progress and state of the consistency checks.
+ */
+int reftable_fsck_check(struct reftable_stack *stack,
+			reftable_fsck_report_fn report_fn,
+			reftable_fsck_verbose_fn verbose_fn,
+			void *cb_data);
+
+#endif /* REFTABLE_FSCK_H */
diff --git a/reftable/reftable-stack.h b/reftable/reftable-stack.h
index 910ec6e..d70fcb7 100644
--- a/reftable/reftable-stack.h
+++ b/reftable/reftable-stack.h
@@ -68,12 +68,15 @@ int reftable_addition_commit(struct reftable_addition *add);
  * transaction. Releases the lock if held. */
 void reftable_addition_destroy(struct reftable_addition *add);
 
-/* add a new table to the stack. The write_table function must call
- * reftable_writer_set_limits, add refs and return an error value. */
+/*
+ * Add a new table to the stack. The write_table function must call
+ * reftable_writer_set_limits, add refs and return an error value.
+ * The flags are passed through to `reftable_stack_new_addition()`.
+ */
 int reftable_stack_add(struct reftable_stack *st,
 		       int (*write_table)(struct reftable_writer *wr,
 					  void *write_arg),
-		       void *write_arg);
+		       void *write_arg, unsigned flags);
 
 struct reftable_iterator;
 
diff --git a/reftable/reftable-writer.h b/reftable/reftable-writer.h
index 0fbeff1..1e7003c 100644
--- a/reftable/reftable-writer.h
+++ b/reftable/reftable-writer.h
@@ -156,7 +156,7 @@ int reftable_writer_add_ref(struct reftable_writer *w,
   the records before adding them, reordering the records array passed in.
 */
 int reftable_writer_add_refs(struct reftable_writer *w,
-			     struct reftable_ref_record *refs, int n);
+			     struct reftable_ref_record *refs, size_t n);
 
 /*
   adds reftable_log_records. Log records are keyed by (refname, decreasing
@@ -171,7 +171,7 @@ int reftable_writer_add_log(struct reftable_writer *w,
   the records before adding them, reordering records array passed in.
 */
 int reftable_writer_add_logs(struct reftable_writer *w,
-			     struct reftable_log_record *logs, int n);
+			     struct reftable_log_record *logs, size_t n);
 
 /* reftable_writer_close finalizes the reftable. The writer is retained so
  * statistics can be inspected. */
diff --git a/reftable/stack.c b/reftable/stack.c
index 4caf96a..65d8982 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -17,18 +17,6 @@
 #include "table.h"
 #include "writer.h"
 
-static int stack_try_add(struct reftable_stack *st,
-			 int (*write_table)(struct reftable_writer *wr,
-					    void *arg),
-			 void *arg);
-static int stack_write_compact(struct reftable_stack *st,
-			       struct reftable_writer *wr,
-			       size_t first, size_t last,
-			       struct reftable_log_expiry_config *config);
-static void reftable_addition_close(struct reftable_addition *add);
-static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
-					     int reuse_open);
-
 static int stack_filename(struct reftable_buf *dest, struct reftable_stack *st,
 			  const char *name)
 {
@@ -84,54 +72,6 @@ static int fd_writer_flush(void *arg)
 	return stack_fsync(writer->opts, writer->fd);
 }
 
-int reftable_new_stack(struct reftable_stack **dest, const char *dir,
-		       const struct reftable_write_options *_opts)
-{
-	struct reftable_buf list_file_name = REFTABLE_BUF_INIT;
-	struct reftable_write_options opts = { 0 };
-	struct reftable_stack *p;
-	int err;
-
-	p = reftable_calloc(1, sizeof(*p));
-	if (!p) {
-		err = REFTABLE_OUT_OF_MEMORY_ERROR;
-		goto out;
-	}
-
-	if (_opts)
-		opts = *_opts;
-	if (opts.hash_id == 0)
-		opts.hash_id = REFTABLE_HASH_SHA1;
-
-	*dest = NULL;
-
-	reftable_buf_reset(&list_file_name);
-	if ((err = reftable_buf_addstr(&list_file_name, dir)) < 0 ||
-	    (err = reftable_buf_addstr(&list_file_name, "/tables.list")) < 0)
-		goto out;
-
-	p->list_file = reftable_buf_detach(&list_file_name);
-	p->list_fd = -1;
-	p->opts = opts;
-	p->reftable_dir = reftable_strdup(dir);
-	if (!p->reftable_dir) {
-		err = REFTABLE_OUT_OF_MEMORY_ERROR;
-		goto out;
-	}
-
-	err = reftable_stack_reload_maybe_reuse(p, 1);
-	if (err < 0)
-		goto out;
-
-	*dest = p;
-	err = 0;
-
-out:
-	if (err < 0)
-		reftable_stack_destroy(p);
-	return err;
-}
-
 static int fd_read_lines(int fd, char ***namesp)
 {
 	char *buf = NULL;
@@ -169,12 +109,7 @@ static int fd_read_lines(int fd, char ***namesp)
 	}
 	buf[size] = 0;
 
-	*namesp = parse_names(buf, size);
-	if (!*namesp) {
-		err = REFTABLE_OUT_OF_MEMORY_ERROR;
-		goto done;
-	}
-
+	err = parse_names(buf, size, namesp);
 done:
 	reftable_free(buf);
 	return err;
@@ -591,9 +526,59 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
 	return err;
 }
 
-/* -1 = error
- 0 = up to date
- 1 = changed. */
+int reftable_new_stack(struct reftable_stack **dest, const char *dir,
+		       const struct reftable_write_options *_opts)
+{
+	struct reftable_buf list_file_name = REFTABLE_BUF_INIT;
+	struct reftable_write_options opts = { 0 };
+	struct reftable_stack *p;
+	int err;
+
+	p = reftable_calloc(1, sizeof(*p));
+	if (!p) {
+		err = REFTABLE_OUT_OF_MEMORY_ERROR;
+		goto out;
+	}
+
+	if (_opts)
+		opts = *_opts;
+	if (opts.hash_id == 0)
+		opts.hash_id = REFTABLE_HASH_SHA1;
+
+	*dest = NULL;
+
+	reftable_buf_reset(&list_file_name);
+	if ((err = reftable_buf_addstr(&list_file_name, dir)) < 0 ||
+	    (err = reftable_buf_addstr(&list_file_name, "/tables.list")) < 0)
+		goto out;
+
+	p->list_file = reftable_buf_detach(&list_file_name);
+	p->list_fd = -1;
+	p->opts = opts;
+	p->reftable_dir = reftable_strdup(dir);
+	if (!p->reftable_dir) {
+		err = REFTABLE_OUT_OF_MEMORY_ERROR;
+		goto out;
+	}
+
+	err = reftable_stack_reload_maybe_reuse(p, 1);
+	if (err < 0)
+		goto out;
+
+	*dest = p;
+	err = 0;
+
+out:
+	if (err < 0)
+		reftable_stack_destroy(p);
+	return err;
+}
+
+/*
+ * Check whether the given stack is up-to-date with what we have in memory.
+ * Returns 0 if so, 1 if the stack is out-of-date or a negative error code
+ * otherwise.
+ */
 static int stack_uptodate(struct reftable_stack *st)
 {
 	char **names = NULL;
@@ -667,34 +652,6 @@ int reftable_stack_reload(struct reftable_stack *st)
 	return err;
 }
 
-int reftable_stack_add(struct reftable_stack *st,
-		       int (*write)(struct reftable_writer *wr, void *arg),
-		       void *arg)
-{
-	int err = stack_try_add(st, write, arg);
-	if (err < 0) {
-		if (err == REFTABLE_OUTDATED_ERROR) {
-			/* Ignore error return, we want to propagate
-			   REFTABLE_OUTDATED_ERROR.
-			*/
-			reftable_stack_reload(st);
-		}
-		return err;
-	}
-
-	return 0;
-}
-
-static int format_name(struct reftable_buf *dest, uint64_t min, uint64_t max)
-{
-	char buf[100];
-	uint32_t rnd = reftable_rand();
-	snprintf(buf, sizeof(buf), "0x%012" PRIx64 "-0x%012" PRIx64 "-%08x",
-		 min, max, rnd);
-	reftable_buf_reset(dest);
-	return reftable_buf_addstr(dest, buf);
-}
-
 struct reftable_addition {
 	struct reftable_flock tables_list_lock;
 	struct reftable_stack *stack;
@@ -704,7 +661,25 @@ struct reftable_addition {
 	uint64_t next_update_index;
 };
 
-#define REFTABLE_ADDITION_INIT {0}
+static void reftable_addition_close(struct reftable_addition *add)
+{
+	struct reftable_buf nm = REFTABLE_BUF_INIT;
+	size_t i;
+
+	for (i = 0; i < add->new_tables_len; i++) {
+		if (!stack_filename(&nm, add->stack, add->new_tables[i]))
+			unlink(nm.buf);
+		reftable_free(add->new_tables[i]);
+		add->new_tables[i] = NULL;
+	}
+	reftable_free(add->new_tables);
+	add->new_tables = NULL;
+	add->new_tables_len = 0;
+	add->new_tables_cap = 0;
+
+	flock_release(&add->tables_list_lock);
+	reftable_buf_release(&nm);
+}
 
 static int reftable_stack_init_addition(struct reftable_addition *add,
 					struct reftable_stack *st,
@@ -713,18 +688,14 @@ static int reftable_stack_init_addition(struct reftable_addition *add,
 	struct reftable_buf lock_file_name = REFTABLE_BUF_INIT;
 	int err;
 
+	memset(add, 0, sizeof(*add));
 	add->stack = st;
 
 	err = flock_acquire(&add->tables_list_lock, st->list_file,
 			    st->opts.lock_timeout_ms);
-	if (err < 0) {
-		if (errno == EEXIST) {
-			err = REFTABLE_LOCK_ERROR;
-		} else {
-			err = REFTABLE_IO_ERROR;
-		}
+	if (err < 0)
 		goto done;
-	}
+
 	if (st->opts.default_permissions) {
 		if (chmod(add->tables_list_lock.path,
 			  st->opts.default_permissions) < 0) {
@@ -754,24 +725,54 @@ static int reftable_stack_init_addition(struct reftable_addition *add,
 	return err;
 }
 
-static void reftable_addition_close(struct reftable_addition *add)
+static int stack_try_add(struct reftable_stack *st,
+			 int (*write_table)(struct reftable_writer *wr,
+					    void *arg),
+			 void *arg, unsigned flags)
 {
-	struct reftable_buf nm = REFTABLE_BUF_INIT;
-	size_t i;
+	struct reftable_addition add;
+	int err;
 
-	for (i = 0; i < add->new_tables_len; i++) {
-		if (!stack_filename(&nm, add->stack, add->new_tables[i]))
-			unlink(nm.buf);
-		reftable_free(add->new_tables[i]);
-		add->new_tables[i] = NULL;
+	err = reftable_stack_init_addition(&add, st, flags);
+	if (err < 0)
+		goto done;
+
+	err = reftable_addition_add(&add, write_table, arg);
+	if (err < 0)
+		goto done;
+
+	err = reftable_addition_commit(&add);
+done:
+	reftable_addition_close(&add);
+	return err;
+}
+
+int reftable_stack_add(struct reftable_stack *st,
+		       int (*write)(struct reftable_writer *wr, void *arg),
+		       void *arg, unsigned flags)
+{
+	int err = stack_try_add(st, write, arg, flags);
+	if (err < 0) {
+		if (err == REFTABLE_OUTDATED_ERROR) {
+			/* Ignore error return, we want to propagate
+			   REFTABLE_OUTDATED_ERROR.
+			*/
+			reftable_stack_reload(st);
+		}
+		return err;
 	}
-	reftable_free(add->new_tables);
-	add->new_tables = NULL;
-	add->new_tables_len = 0;
-	add->new_tables_cap = 0;
 
-	flock_release(&add->tables_list_lock);
-	reftable_buf_release(&nm);
+	return 0;
+}
+
+static int format_name(struct reftable_buf *dest, uint64_t min, uint64_t max)
+{
+	char buf[100];
+	uint32_t rnd = reftable_rand();
+	snprintf(buf, sizeof(buf), "0x%012" PRIx64 "-0x%012" PRIx64 "-%08x",
+		 min, max, rnd);
+	reftable_buf_reset(dest);
+	return reftable_buf_addstr(dest, buf);
 }
 
 void reftable_addition_destroy(struct reftable_addition *add)
@@ -841,10 +842,13 @@ int reftable_addition_commit(struct reftable_addition *add)
 		 * control. It is possible that a concurrent writer is already
 		 * trying to compact parts of the stack, which would lead to a
 		 * `REFTABLE_LOCK_ERROR` because parts of the stack are locked
-		 * already. This is a benign error though, so we ignore it.
+		 * already. Similarly, the stack may have been rewritten by a
+		 * concurrent writer, which causes `REFTABLE_OUTDATED_ERROR`.
+		 * Both of these errors are benign, so we simply ignore them.
 		 */
 		err = reftable_stack_auto_compact(add->stack);
-		if (err < 0 && err != REFTABLE_LOCK_ERROR)
+		if (err < 0 && err != REFTABLE_LOCK_ERROR &&
+		    err != REFTABLE_OUTDATED_ERROR)
 			goto done;
 		err = 0;
 	}
@@ -858,39 +862,18 @@ int reftable_stack_new_addition(struct reftable_addition **dest,
 				struct reftable_stack *st,
 				unsigned int flags)
 {
-	int err = 0;
-	struct reftable_addition empty = REFTABLE_ADDITION_INIT;
+	int err;
 
 	REFTABLE_CALLOC_ARRAY(*dest, 1);
 	if (!*dest)
 		return REFTABLE_OUT_OF_MEMORY_ERROR;
 
-	**dest = empty;
 	err = reftable_stack_init_addition(*dest, st, flags);
 	if (err) {
 		reftable_free(*dest);
 		*dest = NULL;
 	}
-	return err;
-}
 
-static int stack_try_add(struct reftable_stack *st,
-			 int (*write_table)(struct reftable_writer *wr,
-					    void *arg),
-			 void *arg)
-{
-	struct reftable_addition add = REFTABLE_ADDITION_INIT;
-	int err = reftable_stack_init_addition(&add, st, 0);
-	if (err < 0)
-		goto done;
-
-	err = reftable_addition_add(&add, write_table, arg);
-	if (err < 0)
-		goto done;
-
-	err = reftable_addition_commit(&add);
-done:
-	reftable_addition_close(&add);
 	return err;
 }
 
@@ -1007,72 +990,6 @@ uint64_t reftable_stack_next_update_index(struct reftable_stack *st)
 	return 1;
 }
 
-static int stack_compact_locked(struct reftable_stack *st,
-				size_t first, size_t last,
-				struct reftable_log_expiry_config *config,
-				struct reftable_tmpfile *tab_file_out)
-{
-	struct reftable_buf next_name = REFTABLE_BUF_INIT;
-	struct reftable_buf tab_file_path = REFTABLE_BUF_INIT;
-	struct reftable_writer *wr = NULL;
-	struct fd_writer writer=  {
-		.opts = &st->opts,
-	};
-	struct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT;
-	int err = 0;
-
-	err = format_name(&next_name, reftable_table_min_update_index(st->tables[first]),
-			  reftable_table_max_update_index(st->tables[last]));
-	if (err < 0)
-		goto done;
-
-	err = stack_filename(&tab_file_path, st, next_name.buf);
-	if (err < 0)
-		goto done;
-
-	err = reftable_buf_addstr(&tab_file_path, ".temp.XXXXXX");
-	if (err < 0)
-		goto done;
-
-	err = tmpfile_from_pattern(&tab_file, tab_file_path.buf);
-	if (err < 0)
-		goto done;
-
-	if (st->opts.default_permissions &&
-	    chmod(tab_file.path, st->opts.default_permissions) < 0) {
-		err = REFTABLE_IO_ERROR;
-		goto done;
-	}
-
-	writer.fd = tab_file.fd;
-	err = reftable_writer_new(&wr, fd_writer_write, fd_writer_flush,
-				  &writer, &st->opts);
-	if (err < 0)
-		goto done;
-
-	err = stack_write_compact(st, wr, first, last, config);
-	if (err < 0)
-		goto done;
-
-	err = reftable_writer_close(wr);
-	if (err < 0)
-		goto done;
-
-	err = tmpfile_close(&tab_file);
-	if (err < 0)
-		goto done;
-
-	*tab_file_out = tab_file;
-	tab_file = REFTABLE_TMPFILE_INIT;
-
-done:
-	tmpfile_delete(&tab_file);
-	reftable_writer_free(wr);
-	reftable_buf_release(&next_name);
-	reftable_buf_release(&tab_file_path);
-	return err;
-}
-
 static int stack_write_compact(struct reftable_stack *st,
 			       struct reftable_writer *wr,
 			       size_t first, size_t last,
@@ -1172,6 +1089,72 @@ static int stack_write_compact(struct reftable_stack *st,
 	return err;
 }
 
+static int stack_compact_locked(struct reftable_stack *st,
+				size_t first, size_t last,
+				struct reftable_log_expiry_config *config,
+				struct reftable_tmpfile *tab_file_out)
+{
+	struct reftable_buf next_name = REFTABLE_BUF_INIT;
+	struct reftable_buf tab_file_path = REFTABLE_BUF_INIT;
+	struct reftable_writer *wr = NULL;
+	struct fd_writer writer=  {
+		.opts = &st->opts,
+	};
+	struct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT;
+	int err = 0;
+
+	err = format_name(&next_name, reftable_table_min_update_index(st->tables[first]),
+			  reftable_table_max_update_index(st->tables[last]));
+	if (err < 0)
+		goto done;
+
+	err = stack_filename(&tab_file_path, st, next_name.buf);
+	if (err < 0)
+		goto done;
+
+	err = reftable_buf_addstr(&tab_file_path, ".temp.XXXXXX");
+	if (err < 0)
+		goto done;
+
+	err = tmpfile_from_pattern(&tab_file, tab_file_path.buf);
+	if (err < 0)
+		goto done;
+
+	if (st->opts.default_permissions &&
+	    chmod(tab_file.path, st->opts.default_permissions) < 0) {
+		err = REFTABLE_IO_ERROR;
+		goto done;
+	}
+
+	writer.fd = tab_file.fd;
+	err = reftable_writer_new(&wr, fd_writer_write, fd_writer_flush,
+				  &writer, &st->opts);
+	if (err < 0)
+		goto done;
+
+	err = stack_write_compact(st, wr, first, last, config);
+	if (err < 0)
+		goto done;
+
+	err = reftable_writer_close(wr);
+	if (err < 0)
+		goto done;
+
+	err = tmpfile_close(&tab_file);
+	if (err < 0)
+		goto done;
+
+	*tab_file_out = tab_file;
+	tab_file = REFTABLE_TMPFILE_INIT;
+
+done:
+	tmpfile_delete(&tab_file);
+	reftable_writer_free(wr);
+	reftable_buf_release(&next_name);
+	reftable_buf_release(&tab_file_path);
+	return err;
+}
+
 enum stack_compact_range_flags {
 	/*
 	 * Perform a best-effort compaction. That is, even if we cannot lock
@@ -1219,18 +1202,28 @@ static int stack_compact_range(struct reftable_stack *st,
 	 * which are part of the user-specified range.
 	 */
 	err = flock_acquire(&tables_list_lock, st->list_file, st->opts.lock_timeout_ms);
-	if (err < 0) {
-		if (errno == EEXIST)
-			err = REFTABLE_LOCK_ERROR;
-		else
-			err = REFTABLE_IO_ERROR;
+	if (err < 0)
+		goto done;
+
+	/*
+	 * Check whether the stack is up-to-date. We unfortunately cannot
+	 * handle the situation gracefully in case it's _not_ up-to-date
+	 * because the range of tables that the user has requested us to
+	 * compact may have been changed. So instead we abort.
+	 *
+	 * We could in theory improve the situation by having the caller not
+	 * pass in a range, but instead the list of tables to compact. If so,
+	 * we could check that relevant tables still exist. But for now it's
+	 * good enough to just abort.
+	 */
+	err = stack_uptodate(st);
+	if (err < 0)
+		goto done;
+	if (err > 0) {
+		err = REFTABLE_OUTDATED_ERROR;
 		goto done;
 	}
 
-	err = stack_uptodate(st);
-	if (err)
-		goto done;
-
 	/*
 	 * Lock all tables in the user-provided range. This is the slice of our
 	 * stack which we'll compact.
@@ -1264,7 +1257,7 @@ static int stack_compact_range(struct reftable_stack *st,
 			 * tables, otherwise there would be nothing to compact.
 			 * In that case, we return a lock error to our caller.
 			 */
-			if (errno == EEXIST && last - (i - 1) >= 2 &&
+			if (err == REFTABLE_LOCK_ERROR && last - (i - 1) >= 2 &&
 			    flags & STACK_COMPACT_RANGE_BEST_EFFORT) {
 				err = 0;
 				/*
@@ -1276,13 +1269,9 @@ static int stack_compact_range(struct reftable_stack *st,
 				 */
 				first = (i - 1) + 1;
 				break;
-			} else if (errno == EEXIST) {
-				err = REFTABLE_LOCK_ERROR;
-				goto done;
-			} else {
-				err = REFTABLE_IO_ERROR;
-				goto done;
 			}
+
+			goto done;
 		}
 
 		/*
@@ -1291,10 +1280,8 @@ static int stack_compact_range(struct reftable_stack *st,
 		 * of tables.
 		 */
 		err = flock_close(&table_locks[nlocks++]);
-		if (err < 0) {
-			err = REFTABLE_IO_ERROR;
+		if (err < 0)
 			goto done;
-		}
 	}
 
 	/*
@@ -1326,13 +1313,8 @@ static int stack_compact_range(struct reftable_stack *st,
 	 * the new table.
 	 */
 	err = flock_acquire(&tables_list_lock, st->list_file, st->opts.lock_timeout_ms);
-	if (err < 0) {
-		if (errno == EEXIST)
-			err = REFTABLE_LOCK_ERROR;
-		else
-			err = REFTABLE_IO_ERROR;
+	if (err < 0)
 		goto done;
-	}
 
 	if (st->opts.default_permissions) {
 		if (chmod(tables_list_lock.path,
diff --git a/reftable/system.c b/reftable/system.c
index 1ee268b..725a258 100644
--- a/reftable/system.c
+++ b/reftable/system.c
@@ -72,7 +72,7 @@ int flock_acquire(struct reftable_flock *l, const char *target_path,
 		reftable_free(lockfile);
 		if (errno == EEXIST)
 			return REFTABLE_LOCK_ERROR;
-		return -1;
+		return REFTABLE_IO_ERROR;
 	}
 
 	l->fd = get_lock_file_fd(lockfile);
diff --git a/reftable/system.h b/reftable/system.h
index beb9d24..c54ed4c 100644
--- a/reftable/system.h
+++ b/reftable/system.h
@@ -81,7 +81,9 @@ struct reftable_flock {
  * to acquire the lock. If `timeout_ms` is 0 we don't wait, if it is negative
  * we block indefinitely.
  *
- * Retrun 0 on success, a reftable error code on error.
+ * Retrun 0 on success, a reftable error code on error. Specifically,
+ * `REFTABLE_LOCK_ERROR` should be returned in case the target path is already
+ * locked.
  */
 int flock_acquire(struct reftable_flock *l, const char *target_path,
 		  long timeout_ms);
diff --git a/reftable/writer.c b/reftable/writer.c
index 3b4ebdd..0133b64 100644
--- a/reftable/writer.c
+++ b/reftable/writer.c
@@ -395,14 +395,16 @@ int reftable_writer_add_ref(struct reftable_writer *w,
 }
 
 int reftable_writer_add_refs(struct reftable_writer *w,
-			     struct reftable_ref_record *refs, int n)
+			     struct reftable_ref_record *refs, size_t n)
 {
 	int err = 0;
-	int i = 0;
-	QSORT(refs, n, reftable_ref_record_compare_name);
-	for (i = 0; err == 0 && i < n; i++) {
+
+	if (n)
+		qsort(refs, n, sizeof(*refs), reftable_ref_record_compare_name);
+
+	for (size_t i = 0; err == 0 && i < n; i++)
 		err = reftable_writer_add_ref(w, &refs[i]);
-	}
+
 	return err;
 }
 
@@ -486,15 +488,16 @@ int reftable_writer_add_log(struct reftable_writer *w,
 }
 
 int reftable_writer_add_logs(struct reftable_writer *w,
-			     struct reftable_log_record *logs, int n)
+			     struct reftable_log_record *logs, size_t n)
 {
 	int err = 0;
-	int i = 0;
-	QSORT(logs, n, reftable_log_record_compare_key);
 
-	for (i = 0; err == 0 && i < n; i++) {
+	if (n)
+		qsort(logs, n, sizeof(*logs), reftable_log_record_compare_key);
+
+	for (size_t i = 0; err == 0 && i < n; i++)
 		err = reftable_writer_add_log(w, &logs[i]);
-	}
+
 	return err;
 }
 
diff --git a/remote-curl.c b/remote-curl.c
index 84f4694..69f9194 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -894,14 +894,6 @@ static int probe_rpc(struct rpc_state *rpc, struct slot_results *results)
 	return err;
 }
 
-static curl_off_t xcurl_off_t(size_t len)
-{
-	uintmax_t size = len;
-	if (size > maximum_signed_value_of_type(curl_off_t))
-		die(_("cannot handle pushes this big"));
-	return (curl_off_t)size;
-}
-
 /*
  * If flush_received is true, do not attempt to read any more; just use what's
  * in rpc->buf.
@@ -999,7 +991,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece
 		 * and we just need to send it.
 		 */
 		curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, gzip_body);
-		curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE_LARGE, xcurl_off_t(gzip_size));
+		curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE_LARGE, cast_size_t_to_curl_off_t(gzip_size));
 
 	} else if (use_gzip && 1024 < rpc->len) {
 		/* The client backend isn't giving us compressed data so
@@ -1030,7 +1022,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece
 
 		headers = curl_slist_append(headers, "Content-Encoding: gzip");
 		curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, gzip_body);
-		curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE_LARGE, xcurl_off_t(gzip_size));
+		curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE_LARGE, cast_size_t_to_curl_off_t(gzip_size));
 
 		if (options.verbosity > 1) {
 			fprintf(stderr, "POST %s (gzip %lu to %lu bytes)\n",
@@ -1043,7 +1035,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece
 		 * more normal Content-Length approach.
 		 */
 		curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, rpc->buf);
-		curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE_LARGE, xcurl_off_t(rpc->len));
+		curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE_LARGE, cast_size_t_to_curl_off_t(rpc->len));
 		if (options.verbosity > 1) {
 			fprintf(stderr, "POST %s (%lu bytes)\n",
 				rpc->service_name, (unsigned long)rpc->len);
diff --git a/remote.c b/remote.c
index 88f9917..df9675c 100644
--- a/remote.c
+++ b/remote.c
@@ -1171,7 +1171,6 @@ static void show_push_unqualified_ref_name_error(const char *dst_value,
 						 const char *matched_src_name)
 {
 	struct object_id oid;
-	enum object_type type;
 
 	/*
 	 * TRANSLATORS: "matches '%s'%" is the <dst> part of "git push
@@ -1196,30 +1195,37 @@ static void show_push_unqualified_ref_name_error(const char *dst_value,
 		BUG("'%s' is not a valid object, "
 		    "match_explicit_lhs() should catch this!",
 		    matched_src_name);
-	type = odb_read_object_info(the_repository->objects, &oid, NULL);
-	if (type == OBJ_COMMIT) {
+
+	switch (odb_read_object_info(the_repository->objects, &oid, NULL)) {
+	case OBJ_COMMIT:
 		advise(_("The <src> part of the refspec is a commit object.\n"
 			 "Did you mean to create a new branch by pushing to\n"
 			 "'%s:refs/heads/%s'?"),
 		       matched_src_name, dst_value);
-	} else if (type == OBJ_TAG) {
+		break;
+	case OBJ_TAG:
 		advise(_("The <src> part of the refspec is a tag object.\n"
 			 "Did you mean to create a new tag by pushing to\n"
 			 "'%s:refs/tags/%s'?"),
 		       matched_src_name, dst_value);
-	} else if (type == OBJ_TREE) {
+		break;
+	case OBJ_TREE:
 		advise(_("The <src> part of the refspec is a tree object.\n"
 			 "Did you mean to tag a new tree by pushing to\n"
 			 "'%s:refs/tags/%s'?"),
 		       matched_src_name, dst_value);
-	} else if (type == OBJ_BLOB) {
+		break;
+	case OBJ_BLOB:
 		advise(_("The <src> part of the refspec is a blob object.\n"
 			 "Did you mean to tag a new blob by pushing to\n"
 			 "'%s:refs/tags/%s'?"),
 		       matched_src_name, dst_value);
-	} else {
-		BUG("'%s' should be commit/tag/tree/blob, is '%d'",
-		    matched_src_name, type);
+		break;
+	default:
+		advise(_("The <src> part of the refspec ('%s') "
+			 "is an object ID that doesn't exist.\n"),
+		       matched_src_name);
+		break;
 	}
 }
 
@@ -2137,9 +2143,6 @@ static int stat_branch_pair(const char *branch_name, const char *base,
 	struct object_id oid;
 	struct commit *ours, *theirs;
 	struct rev_info revs;
-	struct setup_revision_opt opt = {
-		.free_removed_argv_elements = 1,
-	};
 	struct strvec argv = STRVEC_INIT;
 
 	/* Cannot stat if what we used to build on no longer exists */
@@ -2174,7 +2177,7 @@ static int stat_branch_pair(const char *branch_name, const char *base,
 	strvec_push(&argv, "--");
 
 	repo_init_revisions(the_repository, &revs, NULL);
-	setup_revisions(argv.nr, argv.v, &revs, &opt);
+	setup_revisions_from_strvec(&argv, &revs, NULL);
 	if (prepare_revision_walk(&revs))
 		die(_("revision walk setup failed"));
 
@@ -2578,7 +2581,8 @@ struct check_and_collect_until_cb_data {
 };
 
 /* Get the timestamp of the latest entry. */
-static int peek_reflog(struct object_id *o_oid UNUSED,
+static int peek_reflog(const char *refname UNUSED,
+		       struct object_id *o_oid UNUSED,
 		       struct object_id *n_oid UNUSED,
 		       const char *ident UNUSED,
 		       timestamp_t timestamp, int tz UNUSED,
@@ -2589,7 +2593,8 @@ static int peek_reflog(struct object_id *o_oid UNUSED,
 	return 1;
 }
 
-static int check_and_collect_until(struct object_id *o_oid UNUSED,
+static int check_and_collect_until(const char *refname UNUSED,
+				   struct object_id *o_oid UNUSED,
 				   struct object_id *n_oid,
 				   const char *ident UNUSED,
 				   timestamp_t timestamp, int tz UNUSED,
diff --git a/repository.c b/repository.c
index ecd6911..6faf5c7 100644
--- a/repository.c
+++ b/repository.c
@@ -57,6 +57,7 @@ void initialize_repository(struct repository *repo)
 	repo->parsed_objects = parsed_object_pool_new(repo);
 	ALLOC_ARRAY(repo->index, 1);
 	index_state_init(repo->index, repo);
+	repo->check_deprecated_config = true;
 
 	/*
 	 * When a command runs inside a repository, it learns what
@@ -168,6 +169,7 @@ void repo_set_gitdir(struct repository *repo,
 	if (!repo->objects->sources) {
 		CALLOC_ARRAY(repo->objects->sources, 1);
 		repo->objects->sources->odb = repo->objects;
+		repo->objects->sources->local = true;
 		repo->objects->sources_tail = &repo->objects->sources->next;
 	}
 	expand_base_dir(&repo->objects->sources->path, o->object_dir,
diff --git a/repository.h b/repository.h
index 042dc93..5808a5d 100644
--- a/repository.h
+++ b/repository.h
@@ -161,6 +161,9 @@ struct repository {
 
 	/* Indicate if a repository has a different 'commondir' from 'gitdir' */
 	unsigned different_commondir:1;
+
+	/* Should repo_config() check for deprecated settings */
+	bool check_deprecated_config;
 };
 
 #ifdef USE_THE_REPOSITORY_VARIABLE
diff --git a/revision.c b/revision.c
index 18f300d..cf5e6c1 100644
--- a/revision.c
+++ b/revision.c
@@ -671,12 +671,17 @@ static void trace2_bloom_filter_statistics_atexit(void)
 
 static int forbid_bloom_filters(struct pathspec *spec)
 {
-	if (spec->has_wildcard)
-		return 1;
-	if (spec->magic & ~PATHSPEC_LITERAL)
+	unsigned int allowed_magic =
+		PATHSPEC_FROMTOP |
+		PATHSPEC_MAXDEPTH |
+		PATHSPEC_LITERAL |
+		PATHSPEC_GLOB |
+		PATHSPEC_ATTR;
+
+	if (spec->magic & ~allowed_magic)
 		return 1;
 	for (size_t nr = 0; nr < spec->nr; nr++)
-		if (spec->items[nr].magic & ~PATHSPEC_LITERAL)
+		if (spec->items[nr].magic & ~allowed_magic)
 			return 1;
 
 	return 0;
@@ -691,23 +696,34 @@ static int convert_pathspec_to_bloom_keyvec(struct bloom_keyvec **out,
 	char *path_alloc = NULL;
 	const char *path;
 	size_t len;
-	int res = 0;
+	int res = -1;
 
+	len = pi->nowildcard_len;
+	if (len != pi->len) {
+		/*
+		 * for path like "dir/file*", nowildcard part would be
+		 * "dir/file", but only "dir" should be used for the
+		 * bloom filter.
+		 */
+		while (len > 0 && pi->match[len - 1] != '/')
+			len--;
+	}
 	/* remove single trailing slash from path, if needed */
-	if (pi->len > 0 && pi->match[pi->len - 1] == '/') {
-		path_alloc = xmemdupz(pi->match, pi->len - 1);
+	if (len > 0 && pi->match[len - 1] == '/')
+		len--;
+
+	if (!len)
+		goto cleanup;
+
+	if (len != pi->len) {
+		path_alloc = xmemdupz(pi->match, len);
 		path = path_alloc;
 	} else
 		path = pi->match;
 
-	len = strlen(path);
-	if (!len) {
-		res = -1;
-		goto cleanup;
-	}
-
 	*out = bloom_keyvec_new(path, len, settings);
 
+	res = 0;
 cleanup:
 	free(path_alloc);
 	return res;
@@ -758,9 +774,6 @@ static int check_maybe_different_in_bloom_filter(struct rev_info *revs,
 	struct bloom_filter *filter;
 	int result = 0;
 
-	if (!revs->repo->objects->commit_graph)
-		return -1;
-
 	if (commit_graph_generation(commit) == GENERATION_NUMBER_INFINITY)
 		return -1;
 
@@ -1689,7 +1702,8 @@ static void handle_one_reflog_commit(struct object_id *oid, void *cb_data)
 	}
 }
 
-static int handle_one_reflog_ent(struct object_id *ooid, struct object_id *noid,
+static int handle_one_reflog_ent(const char *refname UNUSED,
+				 struct object_id *ooid, struct object_id *noid,
 				 const char *email UNUSED,
 				 timestamp_t timestamp UNUSED,
 				 int tz UNUSED,
@@ -2304,6 +2318,24 @@ static timestamp_t parse_age(const char *arg)
 	return num;
 }
 
+static void overwrite_argv(int *argc, const char **argv,
+			   const char **value,
+			   const struct setup_revision_opt *opt)
+{
+	/*
+	 * Detect the case when we are overwriting ourselves. The assignment
+	 * itself would be a noop either way, but this lets us avoid corner
+	 * cases around the free() and NULL operations.
+	 */
+	if (*value != argv[*argc]) {
+		if (opt && opt->free_removed_argv_elements)
+			free((char *)argv[*argc]);
+		argv[*argc] = *value;
+		*value = NULL;
+	}
+	(*argc)++;
+}
+
 static int handle_revision_opt(struct rev_info *revs, int argc, const char **argv,
 			       int *unkc, const char **unkv,
 			       const struct setup_revision_opt* opt)
@@ -2325,7 +2357,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 	    starts_with(arg, "--branches=") || starts_with(arg, "--tags=") ||
 	    starts_with(arg, "--remotes=") || starts_with(arg, "--no-walk="))
 	{
-		unkv[(*unkc)++] = arg;
+		overwrite_argv(unkc, unkv, &argv[0], opt);
 		return 1;
 	}
 
@@ -2689,7 +2721,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 	} else {
 		int opts = diff_opt_parse(&revs->diffopt, argv, argc, revs->prefix);
 		if (!opts)
-			unkv[(*unkc)++] = arg;
+			overwrite_argv(unkc, unkv, &argv[0], opt);
 		return opts;
 	}
 
@@ -3001,7 +3033,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
 
 			if (!strcmp(arg, "--stdin")) {
 				if (revs->disable_stdin) {
-					argv[left++] = arg;
+					overwrite_argv(&left, argv, &argv[i], opt);
 					continue;
 				}
 				if (revs->read_from_stdin++)
@@ -3157,9 +3189,34 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
 		revs->show_notes_given = 1;
 	}
 
+	if (argv) {
+		if (opt && opt->free_removed_argv_elements)
+			free((char *)argv[left]);
+		argv[left] = NULL;
+	}
+
 	return left;
 }
 
+void setup_revisions_from_strvec(struct strvec *argv, struct rev_info *revs,
+				 struct setup_revision_opt *opt)
+{
+	struct setup_revision_opt fallback_opt;
+	int ret;
+
+	if (!opt) {
+		memset(&fallback_opt, 0, sizeof(fallback_opt));
+		opt = &fallback_opt;
+	}
+	opt->free_removed_argv_elements = 1;
+
+	ret = setup_revisions(argv->nr, argv->v, revs, opt);
+
+	for (size_t i = ret; i < argv->nr; i++)
+		free((char *)argv->v[i]);
+	argv->nr = ret;
+}
+
 static void release_revisions_cmdline(struct rev_cmdline_info *cmdline)
 {
 	unsigned int i;
diff --git a/revision.h b/revision.h
index 21e288c..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, \
 }
 
 /**
@@ -441,6 +443,8 @@ struct setup_revision_opt {
 };
 int setup_revisions(int argc, const char **argv, struct rev_info *revs,
 		    struct setup_revision_opt *);
+void setup_revisions_from_strvec(struct strvec *argv, struct rev_info *revs,
+				 struct setup_revision_opt *);
 
 /**
  * Free data allocated in a "struct rev_info" after it's been
diff --git a/sequencer.c b/sequencer.c
index aaf2e4d..5476d39 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1087,7 +1087,6 @@ N_("you have staged changes in your working tree\n"
 #define CLEANUP_MSG (1<<3)
 #define VERIFY_MSG  (1<<4)
 #define CREATE_ROOT_COMMIT (1<<5)
-#define VERBATIM_MSG (1<<6)
 
 static int run_command_silent_on_success(struct child_process *cmd)
 {
@@ -1125,9 +1124,6 @@ static int run_git_commit(const char *defmsg,
 {
 	struct child_process cmd = CHILD_PROCESS_INIT;
 
-	if ((flags & CLEANUP_MSG) && (flags & VERBATIM_MSG))
-		BUG("CLEANUP_MSG and VERBATIM_MSG are mutually exclusive");
-
 	cmd.git_cmd = 1;
 
 	if (is_rebase_i(opts) &&
@@ -1166,8 +1162,6 @@ static int run_git_commit(const char *defmsg,
 		strvec_pushl(&cmd.args, "-C", "HEAD", NULL);
 	if ((flags & CLEANUP_MSG))
 		strvec_push(&cmd.args, "--cleanup=strip");
-	if ((flags & VERBATIM_MSG))
-		strvec_push(&cmd.args, "--cleanup=verbatim");
 	if ((flags & EDIT_MSG))
 		strvec_push(&cmd.args, "-e");
 	else if (!(flags & CLEANUP_MSG) &&
@@ -1540,9 +1534,6 @@ static int try_to_commit(struct repository *r,
 	enum commit_msg_cleanup_mode cleanup;
 	int res = 0;
 
-	if ((flags & CLEANUP_MSG) && (flags & VERBATIM_MSG))
-		BUG("CLEANUP_MSG and VERBATIM_MSG are mutually exclusive");
-
 	if (parse_head(r, &current_head))
 		return -1;
 
@@ -1618,8 +1609,6 @@ static int try_to_commit(struct repository *r,
 
 	if (flags & CLEANUP_MSG)
 		cleanup = COMMIT_MSG_CLEANUP_ALL;
-	else if (flags & VERBATIM_MSG)
-		cleanup = COMMIT_MSG_CLEANUP_NONE;
 	else if ((opts->signoff || opts->record_origin) &&
 		 !opts->explicit_cleanup)
 		cleanup = COMMIT_MSG_CLEANUP_SPACE;
@@ -2436,7 +2425,6 @@ static int do_pick_commit(struct repository *r,
 		if (!final_fixup)
 			msg_file = rebase_path_squash_msg();
 		else if (file_exists(rebase_path_fixup_msg())) {
-			flags |= VERBATIM_MSG;
 			msg_file = rebase_path_fixup_msg();
 		} else {
 			const char *dest = git_path_squash_msg(r);
@@ -2721,6 +2709,7 @@ static int check_merge_commit_insn(enum todo_command command)
 		return error(_("cannot squash merge commit into another commit"));
 
 	case TODO_MERGE:
+	case TODO_DROP:
 		return 0;
 
 	default:
@@ -6063,8 +6052,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 	return 0;
 }
 
-int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
-			  const char **argv, unsigned flags)
+int sequencer_make_script(struct repository *r, struct strbuf *out,
+			  struct strvec *argv, unsigned flags)
 {
 	char *format = NULL;
 	struct pretty_print_context pp = {0};
@@ -6105,7 +6094,8 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
 	pp.fmt = revs.commit_format;
 	pp.output_encoding = get_log_output_encoding();
 
-	if (setup_revisions(argc, argv, &revs, NULL) > 1) {
+	setup_revisions_from_strvec(argv, &revs, NULL);
+	if (argv->nr > 1) {
 		ret = error(_("make_script: unhandled options"));
 		goto cleanup;
 	}
diff --git a/sequencer.h b/sequencer.h
index 304ba4b..719684c 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -186,8 +186,8 @@ int sequencer_remove_state(struct replay_opts *opts);
 #define TODO_LIST_REAPPLY_CHERRY_PICKS (1U << 7)
 #define TODO_LIST_WARN_SKIPPED_CHERRY_PICKS (1U << 8)
 
-int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
-			  const char **argv, unsigned flags);
+int sequencer_make_script(struct repository *r, struct strbuf *out,
+			  struct strvec *argv, unsigned flags);
 
 int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,
 		    const char *shortrevisions, const char *onto_name,
diff --git a/server-info.c b/server-info.c
index 9bb30d9..1d33de8 100644
--- a/server-info.c
+++ b/server-info.c
@@ -287,12 +287,13 @@ static int compare_info(const void *a_, const void *b_)
 
 static void init_pack_info(struct repository *r, const char *infofile, int force)
 {
+	struct packfile_store *packs = r->objects->packfiles;
 	struct packed_git *p;
 	int stale;
 	int i;
 	size_t alloc = 0;
 
-	for (p = get_all_packs(r); p; p = p->next) {
+	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
 		/* we ignore things on alternate path since they are
 		 * not available to the pullers in general.
 		 */
diff --git a/setup.c b/setup.c
index 98ddbf3..7086741 100644
--- a/setup.c
+++ b/setup.c
@@ -1460,8 +1460,9 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
 
 	if (env_ceiling_dirs) {
 		int empty_entry_found = 0;
+		static const char path_sep[] = { PATH_SEP, '\0' };
 
-		string_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);
+		string_list_split(&ceiling_dirs, env_ceiling_dirs, path_sep, -1);
 		filter_string_list(&ceiling_dirs, 0,
 				   canonicalize_ceiling_entry, &empty_entry_found);
 		ceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);
diff --git a/shallow.c b/shallow.c
index ef3adb6..d9cd4e2 100644
--- a/shallow.c
+++ b/shallow.c
@@ -213,7 +213,7 @@ static void show_commit(struct commit *commit, void *data)
  * are marked with shallow_flag. The list of border/shallow commits
  * are also returned.
  */
-struct commit_list *get_shallow_commits_by_rev_list(int ac, const char **av,
+struct commit_list *get_shallow_commits_by_rev_list(struct strvec *argv,
 						    int shallow_flag,
 						    int not_shallow_flag)
 {
@@ -232,7 +232,7 @@ struct commit_list *get_shallow_commits_by_rev_list(int ac, const char **av,
 
 	repo_init_revisions(the_repository, &revs, NULL);
 	save_commit_buffer = 0;
-	setup_revisions(ac, av, &revs, NULL);
+	setup_revisions_from_strvec(argv, &revs, NULL);
 
 	if (prepare_revision_walk(&revs))
 		die("revision walk setup failed");
diff --git a/shallow.h b/shallow.h
index 9bfeade..ad591bd 100644
--- a/shallow.h
+++ b/shallow.h
@@ -7,6 +7,7 @@
 #include "strbuf.h"
 
 struct oid_array;
+struct strvec;
 
 void set_alternate_shallow_file(struct repository *r, const char *path, int override);
 int register_shallow(struct repository *r, const struct object_id *oid);
@@ -36,8 +37,8 @@ void rollback_shallow_file(struct repository *r, struct shallow_lock *lk);
 
 struct commit_list *get_shallow_commits(struct object_array *heads,
 					int depth, int shallow_flag, int not_shallow_flag);
-struct commit_list *get_shallow_commits_by_rev_list(
-		int ac, const char **av, int shallow_flag, int not_shallow_flag);
+struct commit_list *get_shallow_commits_by_rev_list(struct strvec *argv,
+						    int shallow_flag, int not_shallow_flag);
 int write_shallow_commits(struct strbuf *out, int use_pack_protocol,
 			  const struct oid_array *extra);
 
diff --git a/shared.mak b/shared.mak
index 1a99848..0e74920 100644
--- a/shared.mak
+++ b/shared.mak
@@ -56,6 +56,7 @@
 	QUIET_MKDIR_P_PARENT  = @echo '   ' MKDIR -p $(@D);
 
 ## Used in "Makefile"
+	QUIET_CARGO    = @echo '   ' CARGO $@;
 	QUIET_CC       = @echo '   ' CC $@;
 	QUIET_AR       = @echo '   ' AR $@;
 	QUIET_LINK     = @echo '   ' LINK $@;
@@ -88,6 +89,8 @@
 
 	QUIET_LINT_GITLINK	= @echo '   ' LINT GITLINK $<;
 	QUIET_LINT_MANSEC	= @echo '   ' LINT MAN SEC $<;
+	QUIET_LINT_DELIMSEC	= @echo '   ' LINT DEL SEC $<;
+	QUIET_LINT_DOCSTYLE	= @echo '   ' LINT DOCSTYLE $<;
 	QUIET_LINT_MANEND	= @echo '   ' LINT MAN END $<;
 
 	export V
diff --git a/sideband.c b/sideband.c
index 8f15b98..ea7c252 100644
--- a/sideband.c
+++ b/sideband.c
@@ -27,16 +27,16 @@ static struct keyword_entry keywords[] = {
 };
 
 /* Returns a color setting (GIT_COLOR_NEVER, etc). */
-static int use_sideband_colors(void)
+static enum git_colorbool use_sideband_colors(void)
 {
-	static int use_sideband_colors_cached = -1;
+	static enum git_colorbool use_sideband_colors_cached = GIT_COLOR_UNKNOWN;
 
 	const char *key = "color.remote";
 	struct strbuf sb = STRBUF_INIT;
 	const char *value;
 	int i;
 
-	if (use_sideband_colors_cached >= 0)
+	if (use_sideband_colors_cached != GIT_COLOR_UNKNOWN)
 		return use_sideband_colors_cached;
 
 	if (!repo_config_get_string_tmp(the_repository, key, &value))
diff --git a/src/cargo-meson.sh b/src/cargo-meson.sh
new file mode 100755
index 0000000..9940098
--- /dev/null
+++ b/src/cargo-meson.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+if test "$#" -lt 2
+then
+	exit 1
+fi
+
+SOURCE_DIR="$1"
+BUILD_DIR="$2"
+BUILD_TYPE=debug
+
+shift 2
+
+for arg
+do
+	case "$arg" in
+	--release)
+		BUILD_TYPE=release;;
+	esac
+done
+
+cargo build --lib --quiet --manifest-path="$SOURCE_DIR/Cargo.toml" --target-dir="$BUILD_DIR" "$@"
+RET=$?
+if test $RET -ne 0
+then
+	exit $RET
+fi
+
+if ! cmp "$BUILD_DIR/$BUILD_TYPE/libgitcore.a" "$BUILD_DIR/libgitcore.a" >/dev/null 2>&1
+then
+	cp "$BUILD_DIR/$BUILD_TYPE/libgitcore.a" "$BUILD_DIR/libgitcore.a"
+fi
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..9da70d8
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1 @@
+pub mod varint;
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..25b9ad5
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,41 @@
+libgit_rs_sources = [
+  'lib.rs',
+  'varint.rs',
+]
+
+# Unfortunately we must use a wrapper command to move the output file into the
+# current build directory. This can fixed once `cargo build --artifact-dir`
+# stabilizes. See https://github.com/rust-lang/cargo/issues/6790 for that
+# effort.
+cargo_command = [
+  shell,
+  meson.current_source_dir() / 'cargo-meson.sh',
+  meson.project_source_root(),
+  meson.current_build_dir(),
+]
+if get_option('buildtype') == 'release'
+  cargo_command += '--release'
+endif
+
+libgit_rs = custom_target('git_rs',
+  input: libgit_rs_sources + [
+    meson.project_source_root() / 'Cargo.toml',
+  ],
+  output: 'libgitcore.a',
+  command: cargo_command,
+)
+libgit_dependencies += declare_dependency(link_with: libgit_rs)
+
+if get_option('tests')
+  test('rust', cargo,
+    args: [
+      'test',
+      '--manifest-path',
+      meson.project_source_root() / 'Cargo.toml',
+      '--target-dir',
+      meson.current_build_dir() / 'target',
+    ],
+    timeout: 0,
+    protocol: 'rust',
+  )
+endif
diff --git a/src/varint.rs b/src/varint.rs
new file mode 100644
index 0000000..6e610bd
--- /dev/null
+++ b/src/varint.rs
@@ -0,0 +1,92 @@
+#[no_mangle]
+pub unsafe extern "C" fn decode_varint(bufp: *mut *const u8) -> u64 {
+    let mut buf = *bufp;
+    let mut c = *buf;
+    let mut val = u64::from(c & 127);
+
+    buf = buf.add(1);
+
+    while (c & 128) != 0 {
+        val = val.wrapping_add(1);
+        if val == 0 || val.leading_zeros() < 7 {
+            return 0; // overflow
+        }
+
+        c = *buf;
+        buf = buf.add(1);
+
+        val = (val << 7) + u64::from(c & 127);
+    }
+
+    *bufp = buf;
+    val
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn encode_varint(value: u64, buf: *mut u8) -> u8 {
+    let mut varint: [u8; 16] = [0; 16];
+    let mut pos = varint.len() - 1;
+
+    varint[pos] = (value & 127) as u8;
+
+    let mut value = value >> 7;
+    while value != 0 {
+        pos -= 1;
+        value -= 1;
+        varint[pos] = 128 | (value & 127) as u8;
+        value >>= 7;
+    }
+
+    if !buf.is_null() {
+        std::ptr::copy_nonoverlapping(varint.as_ptr().add(pos), buf, varint.len() - pos);
+    }
+
+    (varint.len() - pos) as u8
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_decode_varint() {
+        unsafe {
+            assert_eq!(decode_varint(&mut [0x00].as_slice().as_ptr()), 0);
+            assert_eq!(decode_varint(&mut [0x01].as_slice().as_ptr()), 1);
+            assert_eq!(decode_varint(&mut [0x7f].as_slice().as_ptr()), 127);
+            assert_eq!(decode_varint(&mut [0x80, 0x00].as_slice().as_ptr()), 128);
+            assert_eq!(decode_varint(&mut [0x80, 0x01].as_slice().as_ptr()), 129);
+            assert_eq!(decode_varint(&mut [0x80, 0x7f].as_slice().as_ptr()), 255);
+
+            // Overflows are expected to return 0.
+            assert_eq!(decode_varint(&mut [0x88; 16].as_slice().as_ptr()), 0);
+        }
+    }
+
+    #[test]
+    fn test_encode_varint() {
+        unsafe {
+            let mut varint: [u8; 16] = [0; 16];
+
+            assert_eq!(encode_varint(0, std::ptr::null_mut()), 1);
+
+            assert_eq!(encode_varint(0, varint.as_mut_slice().as_mut_ptr()), 1);
+            assert_eq!(varint, [0; 16]);
+
+            assert_eq!(encode_varint(10, varint.as_mut_slice().as_mut_ptr()), 1);
+            assert_eq!(varint, [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+            assert_eq!(encode_varint(127, varint.as_mut_slice().as_mut_ptr()), 1);
+            assert_eq!(varint, [127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+            assert_eq!(encode_varint(128, varint.as_mut_slice().as_mut_ptr()), 2);
+            assert_eq!(varint, [128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+            assert_eq!(encode_varint(129, varint.as_mut_slice().as_mut_ptr()), 2);
+            assert_eq!(varint, [128, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+            assert_eq!(encode_varint(255, varint.as_mut_slice().as_mut_ptr()), 2);
+            assert_eq!(varint, [128, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+        }
+    }
+}
diff --git a/string-list.c b/string-list.c
index 53faaa8..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;
@@ -276,55 +275,99 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_
 	list->nr--;
 }
 
-int string_list_split(struct string_list *list, const char *string,
-		      int delim, int maxsplit)
+/*
+ * append a substring [p..end] to list; return number of things it
+ * appended to the list.
+ */
+static int append_one(struct string_list *list,
+		      const char *p, const char *end,
+		      int in_place, unsigned flags)
+{
+	if (!end)
+		end = p + strlen(p);
+
+	if ((flags & STRING_LIST_SPLIT_TRIM)) {
+		/* rtrim */
+		for (; p < end; end--)
+			if (!isspace(end[-1]))
+				break;
+	}
+
+	if ((flags & STRING_LIST_SPLIT_NONEMPTY) && (end <= p))
+		return 0;
+
+	if (in_place) {
+		*((char *)end) = '\0';
+		string_list_append(list, p);
+	} else {
+		string_list_append_nodup(list, xmemdupz(p, end - p));
+	}
+	return 1;
+}
+
+/*
+ * Unfortunately this cannot become a public interface, as _in_place()
+ * wants to have "const char *string" while the other variant wants to
+ * have "char *string" for type safety.
+ *
+ * This accepts "const char *string" to allow both wrappers to use it;
+ * it internally casts away the constness when in_place is true by
+ * taking advantage of strpbrk() that takes a "const char *" arg and
+ * returns "char *" pointer into that const string.  Yucky but works ;-).
+ */
+static int split_string(struct string_list *list, const char *string, const char *delim,
+			int maxsplit, int in_place, unsigned flags)
 {
 	int count = 0;
-	const char *p = string, *end;
+	const char *p = string;
 
-	if (!list->strdup_strings)
-		die("internal error in string_list_split(): "
-		    "list->strdup_strings must be set");
+	if (in_place && list->strdup_strings)
+		BUG("string_list_split_in_place() called with strdup_strings");
+	else if (!in_place && !list->strdup_strings)
+		BUG("string_list_split() called without strdup_strings");
+
 	for (;;) {
-		count++;
-		if (maxsplit >= 0 && count > maxsplit) {
-			string_list_append(list, p);
-			return count;
+		char *end;
+
+		if (flags & STRING_LIST_SPLIT_TRIM) {
+			/* ltrim */
+			while (*p && isspace(*p))
+				p++;
 		}
-		end = strchr(p, delim);
-		if (end) {
-			string_list_append_nodup(list, xmemdupz(p, end - p));
-			p = end + 1;
-		} else {
-			string_list_append(list, p);
+
+		if (0 <= maxsplit && maxsplit <= count)
+			end = NULL;
+		else
+			end = strpbrk(p, delim);
+
+		count += append_one(list, p, end, in_place, flags);
+
+		if (!end)
 			return count;
-		}
+		p = end + 1;
 	}
 }
 
+int string_list_split(struct string_list *list, const char *string,
+		      const char *delim, int maxsplit)
+{
+	return split_string(list, string, delim, maxsplit, 0, 0);
+}
+
 int string_list_split_in_place(struct string_list *list, char *string,
 			       const char *delim, int maxsplit)
 {
-	int count = 0;
-	char *p = string, *end;
+	return split_string(list, string, delim, maxsplit, 1, 0);
+}
 
-	if (list->strdup_strings)
-		die("internal error in string_list_split_in_place(): "
-		    "list->strdup_strings must not be set");
-	for (;;) {
-		count++;
-		if (maxsplit >= 0 && count > maxsplit) {
-			string_list_append(list, p);
-			return count;
-		}
-		end = strpbrk(p, delim);
-		if (end) {
-			*end = '\0';
-			string_list_append(list, p);
-			p = end + 1;
-		} else {
-			string_list_append(list, p);
-			return count;
-		}
-	}
+int string_list_split_f(struct string_list *list, const char *string,
+			const char *delim, int maxsplit, unsigned flags)
+{
+	return split_string(list, string, delim, maxsplit, 0, flags);
+}
+
+int string_list_split_in_place_f(struct string_list *list, char *string,
+			       const char *delim, int maxsplit, unsigned flags)
+{
+	return split_string(list, string, delim, maxsplit, 1, flags);
 }
diff --git a/string-list.h b/string-list.h
index 122b318..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
@@ -254,7 +260,7 @@ struct string_list_item *unsorted_string_list_lookup(struct string_list *list,
 void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util);
 
 /**
- * Split string into substrings on character `delim` and append the
+ * Split string into substrings on characters in `delim` and append the
  * substrings to `list`.  The input string is not modified.
  * list->strdup_strings must be set, as new memory needs to be
  * allocated to hold the substrings.  If maxsplit is non-negative,
@@ -262,15 +268,15 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_
  * appended to list.
  *
  * Examples:
- *   string_list_split(l, "foo:bar:baz", ':', -1) -> ["foo", "bar", "baz"]
- *   string_list_split(l, "foo:bar:baz", ':', 0) -> ["foo:bar:baz"]
- *   string_list_split(l, "foo:bar:baz", ':', 1) -> ["foo", "bar:baz"]
- *   string_list_split(l, "foo:bar:", ':', -1) -> ["foo", "bar", ""]
- *   string_list_split(l, "", ':', -1) -> [""]
- *   string_list_split(l, ":", ':', -1) -> ["", ""]
+ *   string_list_split(l, "foo:bar:baz", ":", -1) -> ["foo", "bar", "baz"]
+ *   string_list_split(l, "foo:bar:baz", ":", 0) -> ["foo:bar:baz"]
+ *   string_list_split(l, "foo:bar:baz", ":", 1) -> ["foo", "bar:baz"]
+ *   string_list_split(l, "foo:bar:", ":", -1) -> ["foo", "bar", ""]
+ *   string_list_split(l, "", ":", -1) -> [""]
+ *   string_list_split(l, ":", ":", -1) -> ["", ""]
  */
 int string_list_split(struct string_list *list, const char *string,
-		      int delim, int maxsplit);
+		      const char *delim, int maxsplit);
 
 /*
  * Like string_list_split(), except that string is split in-place: the
@@ -281,4 +287,21 @@ int string_list_split(struct string_list *list, const char *string,
  */
 int string_list_split_in_place(struct string_list *list, char *string,
 			       const char *delim, int maxsplit);
+
+/* Flag bits for split_f and split_in_place_f functions */
+enum {
+	/*
+	 * trim whitespaces around resulting string piece before adding
+	 * it to the list
+	 */
+	STRING_LIST_SPLIT_TRIM = (1 << 0),
+	/* omit adding empty string piece to the resulting list */
+	STRING_LIST_SPLIT_NONEMPTY = (1 << 1),
+};
+
+int string_list_split_f(struct string_list *, const char *string,
+			const char *delim, int maxsplit, unsigned flags);
+
+int string_list_split_in_place_f(struct string_list *, char *string,
+				 const char *delim, int maxsplit, unsigned flags);
 #endif /* STRING_LIST_H */
diff --git a/sub-process.c b/sub-process.c
index 1daf5a9..83bf0a0 100644
--- a/sub-process.c
+++ b/sub-process.c
@@ -30,23 +30,20 @@ struct subprocess_entry *subprocess_find_entry(struct hashmap *hashmap, const ch
 
 int subprocess_read_status(int fd, struct strbuf *status)
 {
-	struct strbuf **pair;
-	char *line;
 	int len;
 
 	for (;;) {
+		char *line;
+		const char *value;
+
 		len = packet_read_line_gently(fd, NULL, &line);
 		if ((len < 0) || !line)
 			break;
-		pair = strbuf_split_str(line, '=', 2);
-		if (pair[0] && pair[0]->len && pair[1]) {
+		if (skip_prefix(line, "status=", &value)) {
 			/* the last "status=<foo>" line wins */
-			if (!strcmp(pair[0]->buf, "status=")) {
-				strbuf_reset(status);
-				strbuf_addbuf(status, pair[1]);
-			}
+			strbuf_reset(status);
+			strbuf_addstr(status, value);
 		}
-		strbuf_list_free(pair);
 	}
 
 	return (len < 0) ? len : 0;
diff --git a/submodule.c b/submodule.c
index fff3c75..35c5515 100644
--- a/submodule.c
+++ b/submodule.c
@@ -900,7 +900,7 @@ static void collect_changed_submodules(struct repository *r,
 	save_warning = warn_on_object_refname_ambiguity;
 	warn_on_object_refname_ambiguity = 0;
 	repo_init_revisions(r, &rev, NULL);
-	setup_revisions(argv->nr, argv->v, &rev, &s_r_opt);
+	setup_revisions_from_strvec(argv, &rev, &s_r_opt);
 	warn_on_object_refname_ambiguity = save_warning;
 	if (prepare_revision_walk(&rev))
 		die(_("revision walk setup failed"));
diff --git a/t/Makefile b/t/Makefile
index 757674e..ab8a5b5 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -189,15 +189,9 @@
 
 .PHONY: libgit-sys-test libgit-rs-test
 libgit-sys-test:
-	$(QUIET)(\
-		cd ../contrib/libgit-sys && \
-		cargo test \
-	)
-libgit-rs-test:
-	$(QUIET)(\
-		cd ../contrib/libgit-rs && \
-		cargo test \
-	)
+	$(QUIET)cargo test --manifest-path ../contrib/libgit-sys/Cargo.toml
+libgit-rs-test: libgit-sys-test
+	$(QUIET)cargo test --manifest-path ../contrib/libgit-rs/Cargo.toml
 ifdef INCLUDE_LIBGIT_RS
-all:: libgit-sys-test libgit-rs-test
+all:: libgit-rs-test
 endif
diff --git a/t/for-each-ref-tests.sh b/t/for-each-ref-tests.sh
new file mode 100644
index 0000000..e3ad192
--- /dev/null
+++ b/t/for-each-ref-tests.sh
@@ -0,0 +1,2141 @@
+git_for_each_ref=${git_for_each_ref:-git for-each-ref}
+GNUPGHOME_NOT_USED=$GNUPGHOME
+. "$TEST_DIRECTORY"/lib-gpg.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
+
+# Mon Jul 3 23:18:43 2006 +0000
+datestamp=1151968723
+setdate_and_increment () {
+    GIT_COMMITTER_DATE="$datestamp +0200"
+    datestamp=$(expr "$datestamp" + 1)
+    GIT_AUTHOR_DATE="$datestamp +0200"
+    datestamp=$(expr "$datestamp" + 1)
+    export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
+}
+
+test_object_file_size () {
+	oid=$(git rev-parse "$1")
+	path=".git/objects/$(test_oid_to_path $oid)"
+	test_file_size "$path"
+}
+
+test_expect_success setup '
+	# setup .mailmap
+	cat >.mailmap <<-EOF &&
+	A Thor <athor@example.com> A U Thor <author@example.com>
+	C Mitter <cmitter@example.com> C O Mitter <committer@example.com>
+	EOF
+
+	setdate_and_increment &&
+	echo "Using $datestamp" > one &&
+	git add one &&
+	git commit -m "Initial" &&
+	git branch -M main &&
+	setdate_and_increment &&
+	git tag -a -m "Tagging at $datestamp" testtag &&
+	git update-ref refs/remotes/origin/main main &&
+	git remote add origin nowhere &&
+	git config branch.main.remote origin &&
+	git config branch.main.merge refs/heads/main &&
+	git remote add myfork elsewhere &&
+	git config remote.pushdefault myfork &&
+	git config push.default current
+'
+
+test_atom () {
+	case "$1" in
+		head) ref=refs/heads/main ;;
+		 tag) ref=refs/tags/testtag ;;
+		 sym) ref=refs/heads/sym ;;
+		   *) ref=$1 ;;
+	esac
+	format=$2
+	test_do=test_expect_${4:-success}
+
+	printf '%s\n' "$3" >expected
+	$test_do $PREREQ "basic atom: $ref $format" '
+		${git_for_each_ref} --format="%($format)" "$ref" >actual &&
+		sanitize_pgp <actual >actual.clean &&
+		test_cmp expected actual.clean
+	'
+
+	# Automatically test "contents:size" atom after testing "contents"
+	if test "$format" = "contents"
+	then
+		# for commit leg, $3 is changed there
+		expect=$(printf '%s' "$3" | wc -c)
+		$test_do $PREREQ "basic atom: $ref contents:size" '
+			type=$(git cat-file -t "$ref") &&
+			case $type in
+			tag)
+				# We cannot use $3 as it expects sanitize_pgp to run
+				git cat-file tag $ref >out &&
+				expect=$(tail -n +6 out | wc -c) &&
+				rm -f out ;;
+			tree | blob)
+				expect="" ;;
+			commit)
+				: "use the calculated expect" ;;
+			*)
+				BUG "unknown object type" ;;
+			esac &&
+			# Leave $expect unquoted to lose possible leading whitespaces
+			echo $expect >expected &&
+			${git_for_each_ref} --format="%(contents:size)" "$ref" >actual &&
+			test_cmp expected actual
+		'
+	fi
+}
+
+hexlen=$(test_oid hexsz)
+
+test_atom head refname refs/heads/main
+test_atom head refname: refs/heads/main
+test_atom head refname:short main
+test_atom head refname:lstrip=1 heads/main
+test_atom head refname:lstrip=2 main
+test_atom head refname:lstrip=-1 main
+test_atom head refname:lstrip=-2 heads/main
+test_atom head refname:rstrip=1 refs/heads
+test_atom head refname:rstrip=2 refs
+test_atom head refname:rstrip=-1 refs
+test_atom head refname:rstrip=-2 refs/heads
+test_atom head refname:strip=1 heads/main
+test_atom head refname:strip=2 main
+test_atom head refname:strip=-1 main
+test_atom head refname:strip=-2 heads/main
+test_atom head upstream refs/remotes/origin/main
+test_atom head upstream:short origin/main
+test_atom head upstream:lstrip=2 origin/main
+test_atom head upstream:lstrip=-2 origin/main
+test_atom head upstream:rstrip=2 refs/remotes
+test_atom head upstream:rstrip=-2 refs/remotes
+test_atom head upstream:strip=2 origin/main
+test_atom head upstream:strip=-2 origin/main
+test_atom head push refs/remotes/myfork/main
+test_atom head push:short myfork/main
+test_atom head push:lstrip=1 remotes/myfork/main
+test_atom head push:lstrip=-1 main
+test_atom head push:rstrip=1 refs/remotes/myfork
+test_atom head push:rstrip=-1 refs
+test_atom head push:strip=1 remotes/myfork/main
+test_atom head push:strip=-1 main
+test_atom head objecttype commit
+test_atom head objectsize $((131 + hexlen))
+test_atom head objectsize:disk $(test_object_file_size refs/heads/main)
+test_atom head deltabase $ZERO_OID
+test_atom head objectname $(git rev-parse refs/heads/main)
+test_atom head objectname:short $(git rev-parse --short refs/heads/main)
+test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
+test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
+test_atom head tree $(git rev-parse refs/heads/main^{tree})
+test_atom head tree:short $(git rev-parse --short refs/heads/main^{tree})
+test_atom head tree:short=1 $(git rev-parse --short=1 refs/heads/main^{tree})
+test_atom head tree:short=10 $(git rev-parse --short=10 refs/heads/main^{tree})
+test_atom head parent ''
+test_atom head parent:short ''
+test_atom head parent:short=1 ''
+test_atom head parent:short=10 ''
+test_atom head numparent 0
+test_atom head object ''
+test_atom head type ''
+test_atom head raw "$(git cat-file commit refs/heads/main)
+"
+test_atom head '*objectname' ''
+test_atom head '*objecttype' ''
+test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
+test_atom head authorname 'A U Thor'
+test_atom head authorname:mailmap 'A Thor'
+test_atom head authoremail '<author@example.com>'
+test_atom head authoremail:trim 'author@example.com'
+test_atom head authoremail:localpart 'author'
+test_atom head authoremail:trim,localpart 'author'
+test_atom head authoremail:mailmap '<athor@example.com>'
+test_atom head authoremail:mailmap,trim 'athor@example.com'
+test_atom head authoremail:trim,mailmap 'athor@example.com'
+test_atom head authoremail:mailmap,localpart 'athor'
+test_atom head authoremail:localpart,mailmap 'athor'
+test_atom head authoremail:mailmap,trim,localpart,mailmap,trim 'athor'
+test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
+test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
+test_atom head committername 'C O Mitter'
+test_atom head committername:mailmap 'C Mitter'
+test_atom head committeremail '<committer@example.com>'
+test_atom head committeremail:trim 'committer@example.com'
+test_atom head committeremail:localpart 'committer'
+test_atom head committeremail:localpart,trim 'committer'
+test_atom head committeremail:mailmap '<cmitter@example.com>'
+test_atom head committeremail:mailmap,trim 'cmitter@example.com'
+test_atom head committeremail:trim,mailmap 'cmitter@example.com'
+test_atom head committeremail:mailmap,localpart 'cmitter'
+test_atom head committeremail:localpart,mailmap 'cmitter'
+test_atom head committeremail:trim,mailmap,trim,trim,localpart 'cmitter'
+test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
+test_atom head tag ''
+test_atom head tagger ''
+test_atom head taggername ''
+test_atom head taggeremail ''
+test_atom head taggeremail:trim ''
+test_atom head taggeremail:localpart ''
+test_atom head taggerdate ''
+test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
+test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
+test_atom head subject 'Initial'
+test_atom head subject:sanitize 'Initial'
+test_atom head contents:subject 'Initial'
+test_atom head body ''
+test_atom head contents:body ''
+test_atom head contents:signature ''
+test_atom head contents 'Initial
+'
+test_atom head HEAD '*'
+
+test_atom tag refname refs/tags/testtag
+test_atom tag refname:short testtag
+test_atom tag upstream ''
+test_atom tag push ''
+test_atom tag objecttype tag
+test_atom tag objectsize $((114 + hexlen))
+test_atom tag objectsize:disk $(test_object_file_size refs/tags/testtag)
+test_atom tag '*objectsize:disk' $(test_object_file_size refs/heads/main)
+test_atom tag deltabase $ZERO_OID
+test_atom tag '*deltabase' $ZERO_OID
+test_atom tag objectname $(git rev-parse refs/tags/testtag)
+test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
+test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
+test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
+test_atom tag tree ''
+test_atom tag tree:short ''
+test_atom tag tree:short=1 ''
+test_atom tag tree:short=10 ''
+test_atom tag parent ''
+test_atom tag parent:short ''
+test_atom tag parent:short=1 ''
+test_atom tag parent:short=10 ''
+test_atom tag numparent ''
+test_atom tag object $(git rev-parse refs/tags/testtag^0)
+test_atom tag type 'commit'
+test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{})
+test_atom tag '*objecttype' 'commit'
+test_atom tag author ''
+test_atom tag authorname ''
+test_atom tag authorname:mailmap ''
+test_atom tag authoremail ''
+test_atom tag authoremail:trim ''
+test_atom tag authoremail:localpart ''
+test_atom tag authoremail:trim,localpart ''
+test_atom tag authoremail:mailmap ''
+test_atom tag authoremail:mailmap,trim ''
+test_atom tag authoremail:trim,mailmap ''
+test_atom tag authoremail:mailmap,localpart ''
+test_atom tag authoremail:localpart,mailmap ''
+test_atom tag authoremail:mailmap,trim,localpart,mailmap,trim ''
+test_atom tag authordate ''
+test_atom tag committer ''
+test_atom tag committername ''
+test_atom tag committername:mailmap ''
+test_atom tag committeremail ''
+test_atom tag committeremail:trim ''
+test_atom tag committeremail:localpart ''
+test_atom tag committeremail:localpart,trim ''
+test_atom tag committeremail:mailmap ''
+test_atom tag committeremail:mailmap,trim ''
+test_atom tag committeremail:trim,mailmap ''
+test_atom tag committeremail:mailmap,localpart ''
+test_atom tag committeremail:localpart,mailmap ''
+test_atom tag committeremail:trim,mailmap,trim,trim,localpart ''
+test_atom tag committerdate ''
+test_atom tag tag 'testtag'
+test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
+test_atom tag taggername 'C O Mitter'
+test_atom tag taggername:mailmap 'C Mitter'
+test_atom tag taggeremail '<committer@example.com>'
+test_atom tag taggeremail:trim 'committer@example.com'
+test_atom tag taggeremail:localpart 'committer'
+test_atom tag taggeremail:trim,localpart 'committer'
+test_atom tag taggeremail:mailmap '<cmitter@example.com>'
+test_atom tag taggeremail:mailmap,trim 'cmitter@example.com'
+test_atom tag taggeremail:trim,mailmap 'cmitter@example.com'
+test_atom tag taggeremail:mailmap,localpart 'cmitter'
+test_atom tag taggeremail:localpart,mailmap 'cmitter'
+test_atom tag taggeremail:trim,mailmap,trim,localpart,localpart 'cmitter'
+test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
+test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
+test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
+test_atom tag subject 'Tagging at 1151968727'
+test_atom tag subject:sanitize 'Tagging-at-1151968727'
+test_atom tag contents:subject 'Tagging at 1151968727'
+test_atom tag body ''
+test_atom tag contents:body ''
+test_atom tag contents:signature ''
+test_atom tag contents 'Tagging at 1151968727
+'
+test_atom tag HEAD ' '
+
+test_expect_success 'basic atom: refs/tags/testtag *raw' '
+	git cat-file commit refs/tags/testtag^{} >expected &&
+	${git_for_each_ref} --format="%(*raw)" refs/tags/testtag >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_expect_success 'Check invalid atoms names are errors' '
+	test_must_fail ${git_for_each_ref} --format="%(INVALID)" refs/heads
+'
+
+test_expect_success 'Check format specifiers are ignored in naming date atoms' '
+	${git_for_each_ref} --format="%(authordate)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:default) %(authordate)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate) %(authordate:default)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:default) %(authordate:default)" refs/heads
+'
+
+test_expect_success 'Check valid format specifiers for date fields' '
+	${git_for_each_ref} --format="%(authordate:default)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:relative)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:short)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:local)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:iso8601)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:rfc2822)" refs/heads
+'
+
+test_expect_success 'Check invalid format specifiers are errors' '
+	test_must_fail ${git_for_each_ref} --format="%(authordate:INVALID)" refs/heads
+'
+
+test_expect_success 'arguments to %(objectname:short=) must be positive integers' '
+	test_must_fail ${git_for_each_ref} --format="%(objectname:short=0)" &&
+	test_must_fail ${git_for_each_ref} --format="%(objectname:short=-1)" &&
+	test_must_fail ${git_for_each_ref} --format="%(objectname:short=foo)"
+'
+
+test_bad_atom () {
+	case "$1" in
+	head) ref=refs/heads/main ;;
+	 tag) ref=refs/tags/testtag ;;
+	 sym) ref=refs/heads/sym ;;
+	   *) ref=$1 ;;
+	esac
+	format=$2
+	test_do=test_expect_${4:-success}
+
+	printf '%s\n' "$3" >expect
+	$test_do $PREREQ "err basic atom: $ref $format" '
+		test_must_fail ${git_for_each_ref} \
+			--format="%($format)" "$ref" 2>error &&
+		test_cmp expect error
+	'
+}
+
+test_bad_atom head 'authoremail:foo' \
+	'fatal: unrecognized %(authoremail) argument: foo'
+
+test_bad_atom head 'authoremail:mailmap,trim,bar' \
+	'fatal: unrecognized %(authoremail) argument: bar'
+
+test_bad_atom head 'authoremail:trim,' \
+	'fatal: unrecognized %(authoremail) argument: '
+
+test_bad_atom head 'authoremail:mailmaptrim' \
+	'fatal: unrecognized %(authoremail) argument: trim'
+
+test_bad_atom head 'committeremail: ' \
+	'fatal: unrecognized %(committeremail) argument:  '
+
+test_bad_atom head 'committeremail: trim,foo' \
+	'fatal: unrecognized %(committeremail) argument:  trim,foo'
+
+test_bad_atom head 'committeremail:mailmap,localpart ' \
+	'fatal: unrecognized %(committeremail) argument:  '
+
+test_bad_atom head 'committeremail:trim_localpart' \
+	'fatal: unrecognized %(committeremail) argument: _localpart'
+
+test_bad_atom head 'committeremail:localpart,,,trim' \
+	'fatal: unrecognized %(committeremail) argument: ,,trim'
+
+test_bad_atom tag 'taggeremail:mailmap,trim, foo ' \
+	'fatal: unrecognized %(taggeremail) argument:  foo '
+
+test_bad_atom tag 'taggeremail:trim,localpart,' \
+	'fatal: unrecognized %(taggeremail) argument: '
+
+test_bad_atom tag 'taggeremail:mailmap;localpart trim' \
+	'fatal: unrecognized %(taggeremail) argument: ;localpart trim'
+
+test_bad_atom tag 'taggeremail:localpart trim' \
+	'fatal: unrecognized %(taggeremail) argument:  trim'
+
+test_bad_atom tag 'taggeremail:mailmap,mailmap,trim,qux,localpart,trim' \
+	'fatal: unrecognized %(taggeremail) argument: qux,localpart,trim'
+
+test_date () {
+	f=$1 &&
+	committer_date=$2 &&
+	author_date=$3 &&
+	tagger_date=$4 &&
+	cat >expected <<-EOF &&
+	'refs/heads/main' '$committer_date' '$author_date'
+	'refs/tags/testtag' '$tagger_date'
+	EOF
+	(
+		${git_for_each_ref} --shell \
+			--format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \
+			refs/heads &&
+		${git_for_each_ref} --shell \
+			--format="%(refname) %(taggerdate${f:+:$f})" \
+			refs/tags
+	) >actual &&
+	test_cmp expected actual
+}
+
+test_expect_success 'Check unformatted date fields output' '
+	test_date "" \
+		"Tue Jul 4 01:18:43 2006 +0200" \
+		"Tue Jul 4 01:18:44 2006 +0200" \
+		"Tue Jul 4 01:18:45 2006 +0200"
+'
+
+test_expect_success 'Check format "default" formatted date fields output' '
+	test_date default \
+		"Tue Jul 4 01:18:43 2006 +0200" \
+		"Tue Jul 4 01:18:44 2006 +0200" \
+		"Tue Jul 4 01:18:45 2006 +0200"
+'
+
+test_expect_success 'Check format "default-local" date fields output' '
+	test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006"
+'
+
+# Don't know how to do relative check because I can't know when this script
+# is going to be run and can't fake the current time to git, and hence can't
+# provide expected output.  Instead, I'll just make sure that "relative"
+# doesn't exit in error
+test_expect_success 'Check format "relative" date fields output' '
+	f=relative &&
+	(${git_for_each_ref} --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
+	${git_for_each_ref} --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
+'
+
+# We just check that this is the same as "relative" for now.
+test_expect_success 'Check format "relative-local" date fields output' '
+	test_date relative-local \
+		"$(${git_for_each_ref} --format="%(committerdate:relative)" refs/heads)" \
+		"$(${git_for_each_ref} --format="%(authordate:relative)" refs/heads)" \
+		"$(${git_for_each_ref} --format="%(taggerdate:relative)" refs/tags)"
+'
+
+test_expect_success 'Check format "short" date fields output' '
+	test_date short 2006-07-04 2006-07-04 2006-07-04
+'
+
+test_expect_success 'Check format "short-local" date fields output' '
+	test_date short-local 2006-07-03 2006-07-03 2006-07-03
+'
+
+test_expect_success 'Check format "local" date fields output' '
+	test_date local \
+		"Mon Jul 3 23:18:43 2006" \
+		"Mon Jul 3 23:18:44 2006" \
+		"Mon Jul 3 23:18:45 2006"
+'
+
+test_expect_success 'Check format "iso8601" date fields output' '
+	test_date iso8601 \
+		"2006-07-04 01:18:43 +0200" \
+		"2006-07-04 01:18:44 +0200" \
+		"2006-07-04 01:18:45 +0200"
+'
+
+test_expect_success 'Check format "iso8601-local" date fields output' '
+	test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000"
+'
+
+test_expect_success 'Check format "rfc2822" date fields output' '
+	test_date rfc2822 \
+		"Tue, 4 Jul 2006 01:18:43 +0200" \
+		"Tue, 4 Jul 2006 01:18:44 +0200" \
+		"Tue, 4 Jul 2006 01:18:45 +0200"
+'
+
+test_expect_success 'Check format "rfc2822-local" date fields output' '
+	test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000"
+'
+
+test_expect_success 'Check format "raw" date fields output' '
+	test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200"
+'
+
+test_expect_success 'Check format "raw-local" date fields output' '
+	test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000"
+'
+
+test_expect_success 'Check format of strftime date fields' '
+	echo "my date is 2006-07-04" >expected &&
+	${git_for_each_ref} \
+	  --format="%(authordate:format:my date is %Y-%m-%d)" \
+	  refs/heads >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Check format of strftime-local date fields' '
+	echo "my date is 2006-07-03" >expected &&
+	${git_for_each_ref} \
+	  --format="%(authordate:format-local:my date is %Y-%m-%d)" \
+	  refs/heads >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'exercise strftime with odd fields' '
+	echo >expected &&
+	${git_for_each_ref} --format="%(authordate:format:)" refs/heads >actual &&
+	test_cmp expected actual &&
+	long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" &&
+	echo $long >expected &&
+	${git_for_each_ref} --format="%(authordate:format:$long)" refs/heads >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/heads/main
+refs/remotes/origin/main
+refs/tags/testtag
+EOF
+
+test_expect_success 'Verify ascending sort' '
+	${git_for_each_ref} --format="%(refname)" --sort=refname >actual &&
+	test_cmp expected actual
+'
+
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/remotes/origin/main
+refs/heads/main
+EOF
+
+test_expect_success 'Verify descending sort' '
+	${git_for_each_ref} --format="%(refname)" --sort=-refname >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Give help even with invalid sort atoms' '
+	test_expect_code 129 ${git_for_each_ref} --sort=bogus -h >actual 2>&1 &&
+	grep "^usage: ${git_for_each_ref}" actual
+'
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/tags/testtag-2
+EOF
+
+test_expect_success 'exercise patterns with prefixes' '
+	git tag testtag-2 &&
+	test_when_finished "git tag -d testtag-2" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/testtag refs/tags/testtag-2 >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/tags/testtag-2
+EOF
+
+test_expect_success 'exercise glob patterns with prefixes' '
+	git tag testtag-2 &&
+	test_when_finished "git tag -d testtag-2" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/testtag "refs/tags/testtag-*" >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/tags/bar
+refs/tags/baz
+refs/tags/testtag
+EOF
+
+test_expect_success 'exercise patterns with prefix exclusions' '
+	for tag in foo/one foo/two foo/three bar baz
+	do
+		git tag "$tag" || return 1
+	done &&
+	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/ --exclude=refs/tags/foo >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/tags/bar
+refs/tags/baz
+refs/tags/foo/one
+refs/tags/testtag
+EOF
+
+test_expect_success 'exercise patterns with pattern exclusions' '
+	for tag in foo/one foo/two foo/three bar baz
+	do
+		git tag "$tag" || return 1
+	done &&
+	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/ --exclude="refs/tags/foo/t*" >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+'refs/heads/main'
+'refs/remotes/origin/main'
+'refs/tags/testtag'
+EOF
+
+test_expect_success 'Quoting style: shell' '
+	${git_for_each_ref} --shell --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Quoting style: perl' '
+	${git_for_each_ref} --perl --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Quoting style: python' '
+	${git_for_each_ref} --python --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+"refs/heads/main"
+"refs/remotes/origin/main"
+"refs/tags/testtag"
+EOF
+
+test_expect_success 'Quoting style: tcl' '
+	${git_for_each_ref} --tcl --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
+	test_expect_success "more than one quoting style: $i" "
+		test_must_fail ${git_for_each_ref} $i 2>err &&
+		grep '^error: more than one quoting style' err
+	"
+done
+
+test_expect_success 'setup for upstream:track[short]' '
+	test_commit two
+'
+
+test_atom head upstream:track '[ahead 1]'
+test_atom head upstream:trackshort '>'
+test_atom head upstream:track,nobracket 'ahead 1'
+test_atom head upstream:nobracket,track 'ahead 1'
+
+test_expect_success 'setup for push:track[short]' '
+	test_commit third &&
+	git update-ref refs/remotes/myfork/main main &&
+	git reset main~1
+'
+
+test_atom head push:track '[behind 1]'
+test_atom head push:trackshort '<'
+
+test_expect_success 'Check that :track[short] cannot be used with other atoms' '
+	test_must_fail ${git_for_each_ref} --format="%(refname:track)" 2>/dev/null &&
+	test_must_fail ${git_for_each_ref} --format="%(refname:trackshort)" 2>/dev/null
+'
+
+test_expect_success 'Check that :track[short] works when upstream is invalid' '
+	cat >expected <<-\EOF &&
+	[gone]
+
+	EOF
+	test_when_finished "git config branch.main.merge refs/heads/main" &&
+	git config branch.main.merge refs/heads/does-not-exist &&
+	${git_for_each_ref} \
+		--format="%(upstream:track)$LF%(upstream:trackshort)" \
+		refs/heads >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Check for invalid refname format' '
+	test_must_fail ${git_for_each_ref} --format="%(refname:INVALID)"
+'
+
+test_expect_success 'set up color tests' '
+	cat >expected.color <<-EOF &&
+	$(git rev-parse --short refs/heads/main) <GREEN>main<RESET>
+	$(git rev-parse --short refs/remotes/myfork/main) <GREEN>myfork/main<RESET>
+	$(git rev-parse --short refs/remotes/origin/main) <GREEN>origin/main<RESET>
+	$(git rev-parse --short refs/tags/testtag) <GREEN>testtag<RESET>
+	$(git rev-parse --short refs/tags/third) <GREEN>third<RESET>
+	$(git rev-parse --short refs/tags/two) <GREEN>two<RESET>
+	EOF
+	sed "s/<[^>]*>//g" <expected.color >expected.bare &&
+	color_format="%(objectname:short) %(color:green)%(refname:short)"
+'
+
+test_expect_success TTY '%(color) shows color with a tty' '
+	test_terminal ${git_for_each_ref} --format="$color_format" >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	test_cmp expected.color actual
+'
+
+test_expect_success '%(color) does not show color without tty' '
+	TERM=vt100 ${git_for_each_ref} --format="$color_format" >actual &&
+	test_cmp expected.bare actual
+'
+
+test_expect_success '--color can override tty check' '
+	${git_for_each_ref} --color --format="$color_format" >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	test_cmp expected.color actual
+'
+
+test_expect_success 'color.ui=always does not override tty check' '
+	git -c color.ui=always ${git_for_each_ref#git} --format="$color_format" >actual &&
+	test_cmp expected.bare actual
+'
+
+test_expect_success 'setup for describe atom tests' '
+	git init -b master describe-repo &&
+	(
+		cd describe-repo &&
+
+		test_commit --no-tag one &&
+		git tag tagone &&
+
+		test_commit --no-tag two &&
+		git tag -a -m "tag two" tagtwo
+	)
+'
+
+test_expect_success 'describe atom vs git describe' '
+	(
+		cd describe-repo &&
+
+		${git_for_each_ref} --format="%(objectname)" \
+			refs/tags/ >obj &&
+		while read hash
+		do
+			if desc=$(git describe $hash)
+			then
+				: >expect-contains-good
+			else
+				: >expect-contains-bad
+			fi &&
+			echo "$hash $desc" || return 1
+		done <obj >expect &&
+		test_path_exists expect-contains-good &&
+		test_path_exists expect-contains-bad &&
+
+		${git_for_each_ref} --format="%(objectname) %(describe)" \
+			refs/tags/ >actual 2>err &&
+		test_cmp expect actual &&
+		test_must_be_empty err
+	)
+'
+
+test_expect_success 'describe:tags vs describe --tags' '
+	(
+		cd describe-repo &&
+		git describe --tags >expect &&
+		${git_for_each_ref} --format="%(describe:tags)" \
+				refs/heads/master >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'describe:abbrev=... vs describe --abbrev=...' '
+	(
+		cd describe-repo &&
+
+		# Case 1: We have commits between HEAD and the most
+		#	  recent tag reachable from it
+		test_commit --no-tag file &&
+		git describe --abbrev=14 >expect &&
+		${git_for_each_ref} --format="%(describe:abbrev=14)" \
+			refs/heads/master >actual &&
+		test_cmp expect actual &&
+
+		# Make sure the hash used is at least 14 digits long
+		sed -e "s/^.*-g\([0-9a-f]*\)$/\1/" <actual >hexpart &&
+		test 15 -le $(wc -c <hexpart) &&
+
+		# Case 2: We have a tag at HEAD, describe directly gives
+		#	  the name of the tag
+		git tag -a -m tagged tagname &&
+		git describe --abbrev=14 >expect &&
+		${git_for_each_ref} --format="%(describe:abbrev=14)" \
+			refs/heads/master >actual &&
+		test_cmp expect actual &&
+		test tagname = $(cat actual)
+	)
+'
+
+test_expect_success 'describe:match=... vs describe --match ...' '
+	(
+		cd describe-repo &&
+		git tag -a -m "tag foo" tag-foo &&
+		git describe --match "*-foo" >expect &&
+		${git_for_each_ref} --format="%(describe:match="*-foo")" \
+			refs/heads/master >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'describe:exclude:... vs describe --exclude ...' '
+	(
+		cd describe-repo &&
+		git tag -a -m "tag bar" tag-bar &&
+		git describe --exclude "*-bar" >expect &&
+		${git_for_each_ref} --format="%(describe:exclude="*-bar")" \
+			refs/heads/master >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'deref with describe atom' '
+	(
+		cd describe-repo &&
+		cat >expect <<-\EOF &&
+
+		tagname
+		tagname
+		tagname
+
+		tagtwo
+		EOF
+		${git_for_each_ref} --format="%(*describe)" >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'err on bad describe atom arg' '
+	(
+		cd describe-repo &&
+
+		# The bad arg is the only arg passed to describe atom
+		cat >expect <<-\EOF &&
+		fatal: unrecognized %(describe) argument: baz
+		EOF
+		test_must_fail ${git_for_each_ref} --format="%(describe:baz)" \
+			refs/heads/master 2>actual &&
+		test_cmp expect actual &&
+
+		# The bad arg is in the middle of the option string
+		# passed to the describe atom
+		cat >expect <<-\EOF &&
+		fatal: unrecognized %(describe) argument: qux=1,abbrev=14
+		EOF
+		test_must_fail ${git_for_each_ref} \
+			--format="%(describe:tags,qux=1,abbrev=14)" \
+			ref/heads/master 2>actual &&
+		test_cmp expect actual
+	)
+'
+
+cat >expected <<\EOF
+heads/main
+tags/main
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs (strict)' '
+	git config --bool core.warnambiguousrefs true &&
+	git checkout -b newtag &&
+	echo "Using $datestamp" > one &&
+	git add one &&
+	git commit -m "Branch" &&
+	setdate_and_increment &&
+	git tag -m "Tagging at $datestamp" main &&
+	${git_for_each_ref} --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+heads/main
+main
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs (loose)' '
+	git config --bool core.warnambiguousrefs false &&
+	${git_for_each_ref} --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+heads/ambiguous
+ambiguous
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs II (loose)' '
+	git checkout main &&
+	git tag ambiguous testtag^0 &&
+	git branch ambiguous testtag^0 &&
+	${git_for_each_ref} --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'create tag without tagger' '
+	git tag -a -m "Broken tag" taggerless &&
+	git tag -f taggerless $(git cat-file tag taggerless |
+		sed -e "/^tagger /d" |
+		git hash-object --literally --stdin -w -t tag)
+'
+
+test_atom refs/tags/taggerless type 'commit'
+test_atom refs/tags/taggerless tag 'taggerless'
+test_atom refs/tags/taggerless tagger ''
+test_atom refs/tags/taggerless taggername ''
+test_atom refs/tags/taggerless taggeremail ''
+test_atom refs/tags/taggerless taggeremail:trim ''
+test_atom refs/tags/taggerless taggeremail:localpart ''
+test_atom refs/tags/taggerless taggerdate ''
+test_atom refs/tags/taggerless committer ''
+test_atom refs/tags/taggerless committername ''
+test_atom refs/tags/taggerless committeremail ''
+test_atom refs/tags/taggerless committeremail:trim ''
+test_atom refs/tags/taggerless committeremail:localpart ''
+test_atom refs/tags/taggerless committerdate ''
+test_atom refs/tags/taggerless subject 'Broken tag'
+
+test_expect_success 'an unusual tag with an incomplete line' '
+
+	git tag -m "bogo" bogo &&
+	bogo=$(git cat-file tag bogo) &&
+	bogo=$(printf "%s" "$bogo" | git mktag) &&
+	git tag -f bogo "$bogo" &&
+	${git_for_each_ref} --format "%(body)" refs/tags/bogo
+
+'
+
+test_expect_success 'create tag with subject and body content' '
+	cat >>msg <<-\EOF &&
+		the subject line
+
+		first body line
+		second body line
+	EOF
+	git tag -F msg subject-body
+'
+test_atom refs/tags/subject-body subject 'the subject line'
+test_atom refs/tags/subject-body subject:sanitize 'the-subject-line'
+test_atom refs/tags/subject-body body 'first body line
+second body line
+'
+test_atom refs/tags/subject-body contents 'the subject line
+
+first body line
+second body line
+'
+
+test_expect_success 'create tag with multiline subject' '
+	cat >msg <<-\EOF &&
+		first subject line
+		second subject line
+
+		first body line
+		second body line
+	EOF
+	git tag -F msg multiline
+'
+test_atom refs/tags/multiline subject 'first subject line second subject line'
+test_atom refs/tags/multiline subject:sanitize 'first-subject-line-second-subject-line'
+test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
+test_atom refs/tags/multiline body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:signature ''
+test_atom refs/tags/multiline contents 'first subject line
+second subject line
+
+first body line
+second body line
+'
+
+test_expect_success GPG 'create signed tags' '
+	git tag -s -m "" signed-empty &&
+	git tag -s -m "subject line" signed-short &&
+	cat >msg <<-\EOF &&
+	subject line
+
+	body contents
+	EOF
+	git tag -s -F msg signed-long
+'
+
+sig='-----BEGIN PGP SIGNATURE-----
+-----END PGP SIGNATURE-----
+'
+
+PREREQ=GPG
+test_atom refs/tags/signed-empty subject ''
+test_atom refs/tags/signed-empty subject:sanitize ''
+test_atom refs/tags/signed-empty contents:subject ''
+test_atom refs/tags/signed-empty body "$sig"
+test_atom refs/tags/signed-empty contents:body ''
+test_atom refs/tags/signed-empty contents:signature "$sig"
+test_atom refs/tags/signed-empty contents "$sig"
+
+test_expect_success GPG 'basic atom: refs/tags/signed-empty raw' '
+	git cat-file tag refs/tags/signed-empty >expected &&
+	${git_for_each_ref} --format="%(raw)" refs/tags/signed-empty >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_atom refs/tags/signed-short subject 'subject line'
+test_atom refs/tags/signed-short subject:sanitize 'subject-line'
+test_atom refs/tags/signed-short contents:subject 'subject line'
+test_atom refs/tags/signed-short body "$sig"
+test_atom refs/tags/signed-short contents:body ''
+test_atom refs/tags/signed-short contents:signature "$sig"
+test_atom refs/tags/signed-short contents "subject line
+$sig"
+
+test_expect_success GPG 'basic atom: refs/tags/signed-short raw' '
+	git cat-file tag refs/tags/signed-short >expected &&
+	${git_for_each_ref} --format="%(raw)" refs/tags/signed-short >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_atom refs/tags/signed-long subject 'subject line'
+test_atom refs/tags/signed-long subject:sanitize 'subject-line'
+test_atom refs/tags/signed-long contents:subject 'subject line'
+test_atom refs/tags/signed-long body "body contents
+$sig"
+test_atom refs/tags/signed-long contents:body 'body contents
+'
+test_atom refs/tags/signed-long contents:signature "$sig"
+test_atom refs/tags/signed-long contents "subject line
+
+body contents
+$sig"
+
+test_expect_success GPG 'basic atom: refs/tags/signed-long raw' '
+	git cat-file tag refs/tags/signed-long >expected &&
+	${git_for_each_ref} --format="%(raw)" refs/tags/signed-long >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_expect_success 'set up refs pointing to tree and blob' '
+	git update-ref refs/mytrees/first refs/heads/main^{tree} &&
+	git update-ref refs/myblobs/first refs/heads/main:one
+'
+
+test_atom refs/mytrees/first subject ""
+test_atom refs/mytrees/first contents:subject ""
+test_atom refs/mytrees/first body ""
+test_atom refs/mytrees/first contents:body ""
+test_atom refs/mytrees/first contents:signature ""
+test_atom refs/mytrees/first contents ""
+
+test_expect_success 'basic atom: refs/mytrees/first raw' '
+	git cat-file tree refs/mytrees/first >expected &&
+	echo >>expected &&
+	${git_for_each_ref} --format="%(raw)" refs/mytrees/first >actual &&
+	test_cmp expected actual &&
+	git cat-file -s refs/mytrees/first >expected &&
+	${git_for_each_ref} --format="%(raw:size)" refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_atom refs/myblobs/first subject ""
+test_atom refs/myblobs/first contents:subject ""
+test_atom refs/myblobs/first body ""
+test_atom refs/myblobs/first contents:body ""
+test_atom refs/myblobs/first contents:signature ""
+test_atom refs/myblobs/first contents ""
+
+test_expect_success 'basic atom: refs/myblobs/first raw' '
+	git cat-file blob refs/myblobs/first >expected &&
+	echo >>expected &&
+	${git_for_each_ref} --format="%(raw)" refs/myblobs/first >actual &&
+	test_cmp expected actual &&
+	git cat-file -s refs/myblobs/first >expected &&
+	${git_for_each_ref} --format="%(raw:size)" refs/myblobs/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'set up refs pointing to binary blob' '
+	printf "a\0b\0c" >blob1 &&
+	printf "a\0c\0b" >blob2 &&
+	printf "\0a\0b\0c" >blob3 &&
+	printf "abc" >blob4 &&
+	printf "\0 \0 \0 " >blob5 &&
+	printf "\0 \0a\0 " >blob6 &&
+	printf "  " >blob7 &&
+	>blob8 &&
+	obj=$(git hash-object -w blob1) &&
+	git update-ref refs/myblobs/blob1 "$obj" &&
+	obj=$(git hash-object -w blob2) &&
+	git update-ref refs/myblobs/blob2 "$obj" &&
+	obj=$(git hash-object -w blob3) &&
+	git update-ref refs/myblobs/blob3 "$obj" &&
+	obj=$(git hash-object -w blob4) &&
+	git update-ref refs/myblobs/blob4 "$obj" &&
+	obj=$(git hash-object -w blob5) &&
+	git update-ref refs/myblobs/blob5 "$obj" &&
+	obj=$(git hash-object -w blob6) &&
+	git update-ref refs/myblobs/blob6 "$obj" &&
+	obj=$(git hash-object -w blob7) &&
+	git update-ref refs/myblobs/blob7 "$obj" &&
+	obj=$(git hash-object -w blob8) &&
+	git update-ref refs/myblobs/blob8 "$obj"
+'
+
+test_expect_success 'Verify sorts with raw' '
+	cat >expected <<-EOF &&
+	refs/myblobs/blob8
+	refs/myblobs/blob5
+	refs/myblobs/blob6
+	refs/myblobs/blob3
+	refs/myblobs/blob7
+	refs/mytrees/first
+	refs/myblobs/first
+	refs/myblobs/blob1
+	refs/myblobs/blob2
+	refs/myblobs/blob4
+	refs/heads/main
+	EOF
+	${git_for_each_ref} --format="%(refname)" --sort=raw \
+		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Verify sorts with raw:size' '
+	cat >expected <<-EOF &&
+	refs/myblobs/blob8
+	refs/myblobs/blob7
+	refs/myblobs/blob4
+	refs/myblobs/blob1
+	refs/myblobs/blob2
+	refs/myblobs/blob3
+	refs/myblobs/blob5
+	refs/myblobs/blob6
+	refs/myblobs/first
+	refs/mytrees/first
+	refs/heads/main
+	EOF
+	${git_for_each_ref} --format="%(refname)" --sort=raw:size \
+		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'validate raw atom with %(if:equals)' '
+	cat >expected <<-EOF &&
+	not equals
+	not equals
+	not equals
+	not equals
+	not equals
+	not equals
+	refs/myblobs/blob4
+	not equals
+	not equals
+	not equals
+	not equals
+	not equals
+	EOF
+	${git_for_each_ref} --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
+		refs/myblobs/ refs/heads/ >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'validate raw atom with %(if:notequals)' '
+	cat >expected <<-EOF &&
+	refs/heads/ambiguous
+	refs/heads/main
+	refs/heads/newtag
+	refs/myblobs/blob1
+	refs/myblobs/blob2
+	refs/myblobs/blob3
+	equals
+	refs/myblobs/blob5
+	refs/myblobs/blob6
+	refs/myblobs/blob7
+	refs/myblobs/blob8
+	refs/myblobs/first
+	EOF
+	${git_for_each_ref} --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
+		refs/myblobs/ refs/heads/ >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'empty raw refs with %(if)' '
+	cat >expected <<-EOF &&
+	refs/myblobs/blob1 not empty
+	refs/myblobs/blob2 not empty
+	refs/myblobs/blob3 not empty
+	refs/myblobs/blob4 not empty
+	refs/myblobs/blob5 not empty
+	refs/myblobs/blob6 not empty
+	refs/myblobs/blob7 empty
+	refs/myblobs/blob8 empty
+	refs/myblobs/first not empty
+	EOF
+	${git_for_each_ref} --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
+		refs/myblobs/ >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '%(raw) with --python must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --python
+'
+
+test_expect_success '%(raw) with --tcl must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --tcl
+'
+
+test_expect_success PERL_TEST_HELPERS '%(raw) with --perl' '
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual &&
+	cmp blob1 actual &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob3 --perl | perl >actual &&
+	cmp blob3 actual &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob8 --perl | perl >actual &&
+	cmp blob8 actual &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/first --perl | perl >actual &&
+	cmp one actual &&
+	git cat-file tree refs/mytrees/first > expected &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/mytrees/first --perl | perl >actual &&
+	cmp expected actual
+'
+
+test_expect_success '%(raw) with --shell must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --shell
+'
+
+test_expect_success '%(raw) with --shell and --sort=raw must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --sort=raw --shell
+'
+
+test_expect_success '%(raw:size) with --shell' '
+	${git_for_each_ref} --format="%(raw:size)" | sed "s/^/$SQ/;s/$/$SQ/" >expect &&
+	${git_for_each_ref} --format="%(raw:size)" --shell >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --format compare with cat-file --batch" '
+	git rev-parse refs/mytrees/first | git cat-file --batch >expected &&
+	${git_for_each_ref} --format="%(objectname) %(objecttype) %(objectsize)
+%(raw)" refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'verify sorts with contents:size' '
+	cat >expect <<-\EOF &&
+	refs/heads/main
+	refs/heads/newtag
+	refs/heads/ambiguous
+	EOF
+	${git_for_each_ref} --format="%(refname)" \
+		--sort=contents:size refs/heads/ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'set up multiple-sort tags' '
+	for when in 100000 200000
+	do
+		for email in user1 user2
+		do
+			for ref in ref1 ref2
+			do
+				GIT_COMMITTER_DATE="@$when +0000" \
+				GIT_COMMITTER_EMAIL="$email@example.com" \
+				git tag -m "tag $ref-$when-$email" \
+				multi-$ref-$when-$email || return 1
+			done
+		done
+	done
+'
+
+test_expect_success 'Verify sort with multiple keys' '
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	EOF
+	${git_for_each_ref} \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=-refname \
+		--sort=taggeremail \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'equivalent sorts fall back on refname' '
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	EOF
+	${git_for_each_ref} \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '--no-sort cancels the previous sort keys' '
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	EOF
+	${git_for_each_ref} \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=-refname \
+		--sort=taggeremail \
+		--no-sort \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '--no-sort without subsequent --sort prints expected refs' '
+	cat >expected <<-\EOF &&
+	refs/tags/multi-ref1-100000-user1
+	refs/tags/multi-ref1-100000-user2
+	refs/tags/multi-ref1-200000-user1
+	refs/tags/multi-ref1-200000-user2
+	refs/tags/multi-ref2-100000-user1
+	refs/tags/multi-ref2-100000-user2
+	refs/tags/multi-ref2-200000-user1
+	refs/tags/multi-ref2-200000-user2
+	EOF
+
+	# Sort the results with `sort` for a consistent comparison against
+	# expected
+	${git_for_each_ref} \
+		--format="%(refname)" \
+		--no-sort \
+		"refs/tags/multi-*" | sort >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'set up custom date sorting' '
+	# Dates:
+	# - Wed Feb 07 2024 21:34:20 +0000
+	# - Tue Dec 14 1999 00:05:22 +0000
+	# - Fri Jun 04 2021 11:26:51 +0000
+	# - Mon Jan 22 2007 16:44:01 GMT+0000
+	i=1 &&
+	for when in 1707341660 945129922 1622806011 1169484241
+	do
+		GIT_COMMITTER_DATE="@$when +0000" \
+		GIT_COMMITTER_EMAIL="user@example.com" \
+		git tag -m "tag $when" custom-dates-$i &&
+		i=$(($i+1)) || return 1
+	done
+'
+
+test_expect_success 'sort by date defaults to full timestamp' '
+	cat >expected <<-\EOF &&
+	945129922 refs/tags/custom-dates-2
+	1169484241 refs/tags/custom-dates-4
+	1622806011 refs/tags/custom-dates-3
+	1707341660 refs/tags/custom-dates-1
+	EOF
+
+	${git_for_each_ref} \
+		--format="%(creatordate:unix) %(refname)" \
+		--sort=creatordate \
+		"refs/tags/custom-dates-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'sort by custom date format' '
+	cat >expected <<-\EOF &&
+	00:05:22 refs/tags/custom-dates-2
+	11:26:51 refs/tags/custom-dates-3
+	16:44:01 refs/tags/custom-dates-4
+	21:34:20 refs/tags/custom-dates-1
+	EOF
+
+	${git_for_each_ref} \
+		--format="%(creatordate:format:%H:%M:%S) %(refname)" \
+		--sort="creatordate:format:%H:%M:%S" \
+		"refs/tags/custom-dates-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
+	test_when_finished "git checkout main" &&
+	${git_for_each_ref} --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
+	sed -e "s/^\* /  /" actual >expect &&
+	git checkout --orphan orphaned-branch &&
+	${git_for_each_ref} --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
+	test_cmp expect actual
+'
+
+cat >trailers <<EOF
+Reviewed-by: A U Thor <author@example.com>
+Signed-off-by: A U Thor <author@example.com>
+[ v2 updated patch description ]
+Acked-by: A U Thor
+  <author@example.com>
+EOF
+
+unfold () {
+	perl -0pe 's/\n\s+/ /g'
+}
+
+test_expect_success 'set up trailers for next test' '
+	echo "Some contents" > two &&
+	git add two &&
+	git commit -F - <<-EOF
+	trailers: this commit message has trailers
+
+	Some message contents
+
+	$(cat trailers)
+	EOF
+'
+
+test_trailer_option () {
+	if test "$#" -eq 3
+	then
+		prereq="$1"
+		shift
+	fi &&
+	title=$1 option=$2
+	cat >expect
+	test_expect_success $prereq "$title" '
+		${git_for_each_ref} --format="%($option)" refs/heads/main >actual &&
+		test_cmp expect actual &&
+		${git_for_each_ref} --format="%(contents:$option)" refs/heads/main >actual &&
+		test_cmp expect actual
+	'
+}
+
+test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) unfolds trailers' \
+	'trailers:unfold' <<-EOF
+	$(unfold <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only) shows only "key: value" trailers' \
+	'trailers:only' <<-EOF
+	$(grep -v patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only=no,only=true) shows only "key: value" trailers' \
+	'trailers:only=no,only=true' <<-EOF
+	$(grep -v patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only=yes) shows only "key: value" trailers' \
+	'trailers:only=yes' <<-EOF
+	$(grep -v patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only=no) shows all trailers' \
+	'trailers:only=no' <<-EOF
+	$(cat trailers)
+
+	EOF
+
+test_trailer_option PERL_TEST_HELPERS '%(trailers:only) and %(trailers:unfold) work together' \
+	'trailers:only,unfold' <<-EOF
+	$(grep -v patch.description <trailers | unfold)
+
+	EOF
+
+test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) and %(trailers:only) work together' \
+	'trailers:unfold,only' <<-EOF
+	$(grep -v patch.description <trailers | unfold)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) shows that trailer' \
+	'trailers:key=Signed-off-by' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) is case insensitive' \
+	'trailers:key=SiGned-oFf-bY' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo:) trailing colon also works' \
+	'trailers:key=Signed-off-by:' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) multiple keys' \
+	'trailers:key=Reviewed-by:,key=Signed-off-by' <<-EOF
+	Reviewed-by: A U Thor <author@example.com>
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=nonexistent) becomes empty' \
+	'trailers:key=Shined-off-by:' <<-EOF
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) handles multiple lines even if folded' \
+	'trailers:key=Acked-by' <<-EOF
+	$(grep -v patch.description <trailers | grep -v Signed-off-by | grep -v Reviewed-by)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo,unfold) properly unfolds' \
+	'trailers:key=Signed-Off-by,unfold' <<-EOF
+	$(unfold <trailers | grep Signed-off-by)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo,only=no) also includes nontrailer lines' \
+	'trailers:key=Signed-off-by,only=no' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+	$(grep patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo,valueonly) shows only value' \
+	'trailers:key=Signed-off-by,valueonly' <<-EOF
+	A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:separator) changes separator' \
+	'trailers:separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
+	Reviewed-by: A U Thor <author@example.com>,Signed-off-by: A U Thor <author@example.com>
+	EOF
+
+test_trailer_option '%(trailers:key_value_separator) changes key-value separator' \
+	'trailers:key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
+	Reviewed-by,A U Thor <author@example.com>
+	Signed-off-by,A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:separator,key_value_separator) changes both separators' \
+	'trailers:separator=%x2C,key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
+	Reviewed-by,A U Thor <author@example.com>,Signed-off-by,A U Thor <author@example.com>
+	EOF
+
+test_expect_success 'multiple %(trailers) use their own options' '
+	git tag -F - tag-with-trailers <<-\EOF &&
+	body
+
+	one: foo
+	one: bar
+	two: baz
+	two: qux
+	EOF
+	t1="%(trailers:key=one,key_value_separator=W,separator=X)" &&
+	t2="%(trailers:key=two,key_value_separator=Y,separator=Z)" &&
+	${git_for_each_ref} --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual &&
+	cat >expect <<-\EOF &&
+	oneWfooXoneWbar
+	twoYbazZtwoYqux
+	EOF
+	test_cmp expect actual
+'
+
+test_failing_trailer_option () {
+	title=$1 option=$2
+	cat >expect
+	test_expect_success "$title" '
+		# error message cannot be checked under i18n
+		test_must_fail ${git_for_each_ref} --format="%($option)" refs/heads/main 2>actual &&
+		test_cmp expect actual &&
+		test_must_fail ${git_for_each_ref} --format="%(contents:$option)" refs/heads/main 2>actual &&
+		test_cmp expect actual
+	'
+}
+
+test_failing_trailer_option '%(trailers) rejects unknown trailers arguments' \
+	'trailers:unsupported' <<-\EOF
+	fatal: unknown %(trailers) argument: unsupported
+	EOF
+
+test_failing_trailer_option '%(trailers:key) without value is error' \
+	'trailers:key' <<-\EOF
+	fatal: expected %(trailers:key=<value>)
+	EOF
+
+test_expect_success 'if arguments, %(contents:trailers) shows error if colon is missing' '
+	cat >expect <<-EOF &&
+	fatal: unrecognized %(contents) argument: trailersonly
+	EOF
+	test_must_fail ${git_for_each_ref} --format="%(contents:trailersonly)" 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'basic atom: head contents:trailers' '
+	${git_for_each_ref} --format="%(contents:trailers)" refs/heads/main >actual &&
+	sanitize_pgp <actual >actual.clean &&
+	# ${git_for_each_ref} ends with a blank line
+	cat >expect <<-EOF &&
+	$(cat trailers)
+
+	EOF
+	test_cmp expect actual.clean
+'
+
+test_expect_success 'basic atom: rest must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(rest)" refs/heads/main
+'
+
+test_expect_success 'HEAD atom does not take arguments' '
+	test_must_fail ${git_for_each_ref} --format="%(HEAD:foo)" 2>err &&
+	echo "fatal: %(HEAD) does not take arguments" >expect &&
+	test_cmp expect err
+'
+
+test_expect_success 'subject atom rejects unknown arguments' '
+	test_must_fail ${git_for_each_ref} --format="%(subject:foo)" 2>err &&
+	echo "fatal: unrecognized %(subject) argument: foo" >expect &&
+	test_cmp expect err
+'
+
+test_expect_success 'refname atom rejects unknown arguments' '
+	test_must_fail ${git_for_each_ref} --format="%(refname:foo)" 2>err &&
+	echo "fatal: unrecognized %(refname) argument: foo" >expect &&
+	test_cmp expect err
+'
+
+test_expect_success 'trailer parsing not fooled by --- line' '
+	git commit --allow-empty -F - <<-\EOF &&
+	this is the subject
+
+	This is the body. The message has a "---" line which would confuse a
+	message+patch parser. But here we know we have only a commit message,
+	so we get it right.
+
+	trailer: wrong
+	---
+	This is more body.
+
+	trailer: right
+	EOF
+
+	{
+		echo "trailer: right" &&
+		echo
+	} >expect &&
+	${git_for_each_ref} --format="%(trailers)" refs/heads/main >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'Add symbolic ref for the following tests' '
+	git symbolic-ref refs/heads/sym refs/heads/main
+'
+
+cat >expected <<EOF
+refs/heads/main
+EOF
+
+test_expect_success 'Verify usage of %(symref) atom' '
+	${git_for_each_ref} --format="%(symref)" refs/heads/sym >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+heads/main
+EOF
+
+test_expect_success 'Verify usage of %(symref:short) atom' '
+	${git_for_each_ref} --format="%(symref:short)" refs/heads/sym >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+main
+heads/main
+EOF
+
+test_expect_success 'Verify usage of %(symref:lstrip) atom' '
+	${git_for_each_ref} --format="%(symref:lstrip=2)" refs/heads/sym > actual &&
+	${git_for_each_ref} --format="%(symref:lstrip=-2)" refs/heads/sym >> actual &&
+	test_cmp expected actual &&
+
+	${git_for_each_ref} --format="%(symref:strip=2)" refs/heads/sym > actual &&
+	${git_for_each_ref} --format="%(symref:strip=-2)" refs/heads/sym >> actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+refs
+refs/heads
+EOF
+
+test_expect_success 'Verify usage of %(symref:rstrip) atom' '
+	${git_for_each_ref} --format="%(symref:rstrip=2)" refs/heads/sym > actual &&
+	${git_for_each_ref} --format="%(symref:rstrip=-2)" refs/heads/sym >> actual &&
+	test_cmp expected actual
+'
+
+test_expect_success ':remotename and :remoteref' '
+	git init remote-tests &&
+	(
+		cd remote-tests &&
+		test_commit initial &&
+		git branch -M main &&
+		git remote add from fifth.coffee:blub &&
+		git config branch.main.remote from &&
+		git config branch.main.merge refs/heads/stable &&
+		git remote add to southridge.audio:repo &&
+		git config remote.to.push "refs/heads/*:refs/heads/pushed/*" &&
+		git config branch.main.pushRemote to &&
+		for pair in "%(upstream)=refs/remotes/from/stable" \
+			"%(upstream:remotename)=from" \
+			"%(upstream:remoteref)=refs/heads/stable" \
+			"%(push)=refs/remotes/to/pushed/main" \
+			"%(push:remotename)=to" \
+			"%(push:remoteref)=refs/heads/pushed/main"
+		do
+			echo "${pair#*=}" >expect &&
+			${git_for_each_ref} --format="${pair%=*}" \
+				refs/heads/main >actual &&
+			test_cmp expect actual || exit 1
+		done &&
+		git branch push-simple &&
+		git config branch.push-simple.pushRemote from &&
+		actual="$(${git_for_each_ref} \
+			--format="%(push:remotename),%(push:remoteref)" \
+			refs/heads/push-simple)" &&
+		test from, = "$actual"
+	)
+'
+
+test_expect_success "${git_for_each_ref} --ignore-case ignores case" '
+	${git_for_each_ref} --format="%(refname)" refs/heads/MAIN >actual &&
+	test_must_be_empty actual &&
+
+	echo refs/heads/main >expect &&
+	${git_for_each_ref} --format="%(refname)" --ignore-case \
+		refs/heads/MAIN >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --omit-empty works" '
+	${git_for_each_ref} --format="%(refname)" >actual &&
+	test_line_count -gt 1 actual &&
+	${git_for_each_ref} --format="%(if:equals=refs/heads/main)%(refname)%(then)%(refname)%(end)" --omit-empty >actual &&
+	echo refs/heads/main >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --ignore-case works on multiple sort keys" '
+	# name refs numerically to avoid case-insensitive filesystem conflicts
+	nr=0 &&
+	for email in a A b B
+	do
+		for subject in a A b B
+		do
+			GIT_COMMITTER_EMAIL="$email@example.com" \
+			git tag -m "tag $subject" icase-$(printf %02d $nr) &&
+			nr=$((nr+1))||
+			return 1
+		done
+	done &&
+	${git_for_each_ref} --ignore-case \
+		--format="%(taggeremail) %(subject) %(refname)" \
+		--sort=refname \
+		--sort=subject \
+		--sort=taggeremail \
+		refs/tags/icase-* >actual &&
+	cat >expect <<-\EOF &&
+	<a@example.com> tag a refs/tags/icase-00
+	<a@example.com> tag A refs/tags/icase-01
+	<A@example.com> tag a refs/tags/icase-04
+	<A@example.com> tag A refs/tags/icase-05
+	<a@example.com> tag b refs/tags/icase-02
+	<a@example.com> tag B refs/tags/icase-03
+	<A@example.com> tag b refs/tags/icase-06
+	<A@example.com> tag B refs/tags/icase-07
+	<b@example.com> tag a refs/tags/icase-08
+	<b@example.com> tag A refs/tags/icase-09
+	<B@example.com> tag a refs/tags/icase-12
+	<B@example.com> tag A refs/tags/icase-13
+	<b@example.com> tag b refs/tags/icase-10
+	<b@example.com> tag B refs/tags/icase-11
+	<B@example.com> tag b refs/tags/icase-14
+	<B@example.com> tag B refs/tags/icase-15
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} reports broken tags" '
+	git tag -m "good tag" broken-tag-good HEAD &&
+	git cat-file tag broken-tag-good >good &&
+	sed s/commit/blob/ <good >bad &&
+	bad=$(git hash-object -w -t tag bad) &&
+	git update-ref refs/tags/broken-tag-bad $bad &&
+	test_must_fail ${git_for_each_ref} --format="%(*objectname)" \
+		refs/tags/broken-tag-*
+'
+
+test_expect_success 'set up tag with signature and no blank lines' '
+	git tag -F - fake-sig-no-blanks <<-\EOF
+	this is the subject
+	-----BEGIN PGP SIGNATURE-----
+	not a real signature, but we just care about the
+	subject/body parsing. It is important here that
+	there are no blank lines in the signature.
+	-----END PGP SIGNATURE-----
+	EOF
+'
+
+test_atom refs/tags/fake-sig-no-blanks contents:subject 'this is the subject'
+test_atom refs/tags/fake-sig-no-blanks contents:body ''
+test_atom refs/tags/fake-sig-no-blanks contents:signature "$sig"
+
+test_expect_success 'set up tag with CRLF signature' '
+	append_cr <<-\EOF |
+	this is the subject
+	-----BEGIN PGP SIGNATURE-----
+
+	not a real signature, but we just care about
+	the subject/body parsing. It is important here
+	that there is a blank line separating this
+	from the signature header.
+	-----END PGP SIGNATURE-----
+	EOF
+	git tag -F - --cleanup=verbatim fake-sig-crlf
+'
+
+test_atom refs/tags/fake-sig-crlf contents:subject 'this is the subject'
+test_atom refs/tags/fake-sig-crlf contents:body ''
+
+# CRLF is retained in the signature, so we have to pass our expected value
+# through append_cr. But test_atom requires a shell string, which means command
+# substitution, and the shell will strip trailing newlines from the output of
+# the substitution. Hack around it by adding and then removing a dummy line.
+sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)"
+sig_crlf=${sig_crlf%dummy}
+test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf"
+
+test_expect_success 'set up tag with signature and trailers' '
+	git tag -F - fake-sig-trailer <<-\EOF
+	this is the subject
+
+	this is the body
+
+	My-Trailer: foo
+	-----BEGIN PGP SIGNATURE-----
+
+	not a real signature, but we just care about the
+	subject/body/trailer parsing.
+	-----END PGP SIGNATURE-----
+	EOF
+'
+
+# use "separator=" here to suppress the terminating newline
+test_atom refs/tags/fake-sig-trailer trailers:separator= 'My-Trailer: foo'
+
+test_expect_success "${git_for_each_ref} --stdin: empty" '
+	>in &&
+	${git_for_each_ref} --format="%(refname)" --stdin <in >actual &&
+	${git_for_each_ref} --format="%(refname)" >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --stdin: fails if extra args" '
+	>in &&
+	test_must_fail ${git_for_each_ref} --format="%(refname)" \
+		--stdin refs/heads/extra <in 2>err &&
+	grep "unknown arguments supplied with --stdin" err
+'
+
+test_expect_success "${git_for_each_ref} --stdin: matches" '
+	cat >in <<-EOF &&
+	refs/tags/multi*
+	refs/heads/amb*
+	EOF
+
+	cat >expect <<-EOF &&
+	refs/heads/ambiguous
+	refs/tags/multi-ref1-100000-user1
+	refs/tags/multi-ref1-100000-user2
+	refs/tags/multi-ref1-200000-user1
+	refs/tags/multi-ref1-200000-user2
+	refs/tags/multi-ref2-100000-user1
+	refs/tags/multi-ref2-100000-user2
+	refs/tags/multi-ref2-200000-user1
+	refs/tags/multi-ref2-200000-user2
+	refs/tags/multiline
+	EOF
+
+	${git_for_each_ref} --format="%(refname)" --stdin <in >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} with non-existing refs" '
+	cat >in <<-EOF &&
+	refs/heads/this-ref-does-not-exist
+	refs/tags/bogus
+	EOF
+
+	${git_for_each_ref} --format="%(refname)" --stdin <in >actual &&
+	test_must_be_empty actual &&
+
+	xargs ${git_for_each_ref} --format="%(refname)" <in >actual &&
+	test_must_be_empty actual
+'
+
+test_expect_success "${git_for_each_ref} with nested tags" '
+	git tag -am "Normal tag" nested/base HEAD &&
+	git tag -am "Nested tag" nested/nest1 refs/tags/nested/base &&
+	git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 &&
+
+	head_oid="$(git rev-parse HEAD)" &&
+	base_tag_oid="$(git rev-parse refs/tags/nested/base)" &&
+	nest1_tag_oid="$(git rev-parse refs/tags/nested/nest1)" &&
+	nest2_tag_oid="$(git rev-parse refs/tags/nested/nest2)" &&
+
+	cat >expect <<-EOF &&
+	refs/tags/nested/base $base_tag_oid tag $head_oid commit
+	refs/tags/nested/nest1 $nest1_tag_oid tag $head_oid commit
+	refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit
+	EOF
+
+	${git_for_each_ref} \
+		--format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \
+		refs/tags/nested/ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'is-base atom with non-commits' '
+	${git_for_each_ref} --format="%(is-base:HEAD) %(refname)" >out 2>err &&
+	grep "(HEAD) refs/heads/main" out &&
+
+	test_line_count = 2 err &&
+	grep "error: object .* is a commit, not a blob" err &&
+	grep "error: bad tag pointer to" err
+'
+
+GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
+TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
+
+test_expect_success GPG 'setup for signature atom using gpg' '
+	git checkout -b signed &&
+
+	test_when_finished "test_unconfig commit.gpgSign" &&
+
+	echo "1" >file &&
+	git add file &&
+	test_tick &&
+	git commit -S -m "file: 1" &&
+	git tag first-signed &&
+
+	echo "2" >file &&
+	test_tick &&
+	git commit -a -m "file: 2" &&
+	git tag second-unsigned &&
+
+	git config commit.gpgSign 1 &&
+	echo "3" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 3" &&
+	git tag third-unsigned &&
+
+	test_tick &&
+	git rebase -f HEAD^^ && git tag second-signed HEAD^ &&
+	git tag third-signed &&
+
+	echo "4" >file &&
+	test_tick &&
+	git commit -a -SB7227189 -m "file: 4" &&
+	git tag fourth-signed &&
+
+	echo "5" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 5" &&
+	git tag fifth-unsigned &&
+
+	echo "6" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 6" &&
+
+	test_tick &&
+	git rebase -f HEAD^^ &&
+	git tag fifth-signed HEAD^ &&
+	git tag sixth-signed &&
+
+	echo "7" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 7" &&
+	git tag seventh-unsigned
+'
+
+test_expect_success GPGSSH 'setup for signature atom using ssh' '
+	test_when_finished "test_unconfig gpg.format user.signingkey" &&
+
+	test_config gpg.format ssh &&
+	test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+	echo "8" >file &&
+	test_tick &&
+	git add file &&
+	git commit -S -m "file: 8" &&
+	git tag eighth-signed-ssh
+'
+
+test_expect_success GPG2 'bare signature atom' '
+	git verify-commit first-signed 2>expect &&
+	echo  >>expect &&
+	${git_for_each_ref} refs/tags/first-signed \
+		--format="%(signature)" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show good signature with custom format' '
+	git verify-commit first-signed &&
+	cat >expect <<-\EOF &&
+	G
+	13B6F51ECDDE430D
+	C O Mitter <committer@example.com>
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	EOF
+	${git_for_each_ref} refs/tags/first-signed \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+test_expect_success GPGSSH 'show good signature with custom format with ssh' '
+	test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+	FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
+	cat >expect.tmpl <<-\EOF &&
+	G
+	FINGERPRINT
+	principal with number 1
+	FINGERPRINT
+
+	EOF
+	sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
+	${git_for_each_ref} refs/tags/eighth-signed-ssh \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'signature atom with grade option and bad signature' '
+	git cat-file commit third-signed >raw &&
+	sed -e "s/^file: 3/file: 3 forged/" raw >forged1 &&
+	FORGED1=$(git hash-object -w -t commit forged1) &&
+	git update-ref refs/tags/third-signed "$FORGED1" &&
+	test_must_fail git verify-commit "$FORGED1" &&
+
+	cat >expect <<-\EOF &&
+	B
+	13B6F51ECDDE430D
+	C O Mitter <committer@example.com>
+
+
+	EOF
+	${git_for_each_ref} refs/tags/third-signed \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with custom format' '
+	cat >expect <<-\EOF &&
+	U
+	65A0EEA02E30CAD7
+	Eris Discordia <discord@example.net>
+	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
+	D4BE22311AD3131E5EDA29A461092E85B7227189
+	EOF
+	${git_for_each_ref} refs/tags/fourth-signed \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with undefined trust level' '
+	cat >expect <<-\EOF &&
+	undefined
+	65A0EEA02E30CAD7
+	Eris Discordia <discord@example.net>
+	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
+	D4BE22311AD3131E5EDA29A461092E85B7227189
+	EOF
+	${git_for_each_ref} refs/tags/fourth-signed \
+		--format="$TRUSTLEVEL_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with ultimate trust level' '
+	cat >expect <<-\EOF &&
+	ultimate
+	13B6F51ECDDE430D
+	C O Mitter <committer@example.com>
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	EOF
+	${git_for_each_ref} refs/tags/sixth-signed \
+		--format="$TRUSTLEVEL_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show unknown signature with custom format' '
+	cat >expect <<-\EOF &&
+	E
+	13B6F51ECDDE430D
+
+
+
+	EOF
+	GNUPGHOME="$GNUPGHOME_NOT_USED" ${git_for_each_ref} \
+		refs/tags/sixth-signed --format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show lack of signature with custom format' '
+	cat >expect <<-\EOF &&
+	N
+
+
+
+
+	EOF
+	${git_for_each_ref} refs/tags/seventh-unsigned \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_done
diff --git a/t/helper/test-find-pack.c b/t/helper/test-find-pack.c
index 611a13a..e001dc3 100644
--- a/t/helper/test-find-pack.c
+++ b/t/helper/test-find-pack.c
@@ -39,7 +39,7 @@ int cmd__find_pack(int argc, const char **argv)
 	if (repo_get_oid(the_repository, argv[0], &oid))
 		die("cannot parse %s as an object name", argv[0]);
 
-	for (p = get_all_packs(the_repository); p; p = p->next)
+	for (p = packfile_store_get_all_packs(the_repository->objects->packfiles); p; p = p->next)
 		if (find_pack_entry_one(&oid, p)) {
 			printf("%s\n", p->pack_name);
 			actual_count++;
diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c
index 16a3145..56d223a 100644
--- a/t/helper/test-hashmap.c
+++ b/t/helper/test-hashmap.c
@@ -154,8 +154,8 @@ int cmd__hashmap(int argc UNUSED, const char **argv UNUSED)
 
 		/* break line into command and up to two parameters */
 		string_list_setlen(&parts, 0);
-		string_list_split_in_place(&parts, line.buf, DELIM, 2);
-		string_list_remove_empty_items(&parts, 0);
+		string_list_split_in_place_f(&parts, line.buf, DELIM, 2,
+					     STRING_LIST_SPLIT_NONEMPTY);
 
 		/* ignore empty lines */
 		if (!parts.nr)
diff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c
index a288069..f8316a7 100644
--- a/t/helper/test-json-writer.c
+++ b/t/helper/test-json-writer.c
@@ -492,8 +492,8 @@ static int scripted(void)
 
 		/* break line into command and zero or more tokens */
 		string_list_setlen(&parts, 0);
-		string_list_split_in_place(&parts, line, " ", -1);
-		string_list_remove_empty_items(&parts, 0);
+		string_list_split_in_place_f(&parts, line, " ", -1,
+					     STRING_LIST_SPLIT_NONEMPTY);
 
 		/* ignore empty lines */
 		if (!parts.nr || !*parts.items[0].string)
diff --git a/t/helper/test-pack-deltas.c b/t/helper/test-pack-deltas.c
index 4caa024..4981401 100644
--- a/t/helper/test-pack-deltas.c
+++ b/t/helper/test-pack-deltas.c
@@ -51,16 +51,14 @@ static void write_ref_delta(struct hashfile *f,
 	unsigned long size, base_size, delta_size, compressed_size, hdrlen;
 	enum object_type type;
 	void *base_buf, *delta_buf;
-	void *buf = repo_read_object_file(the_repository,
-					  oid, &type,
-					  &size);
+	void *buf = odb_read_object(the_repository->objects,
+				    oid, &type, &size);
 
 	if (!buf)
 		die("unable to read %s", oid_to_hex(oid));
 
-	base_buf = repo_read_object_file(the_repository,
-					 base, &type,
-					 &base_size);
+	base_buf = odb_read_object(the_repository->objects,
+				   base, &type, &base_size);
 
 	if (!base_buf)
 		die("unable to read %s", oid_to_hex(base));
diff --git a/t/helper/test-pack-mtimes.c b/t/helper/test-pack-mtimes.c
index d51aaa3..7c428c1 100644
--- a/t/helper/test-pack-mtimes.c
+++ b/t/helper/test-pack-mtimes.c
@@ -37,7 +37,7 @@ int cmd__pack_mtimes(int argc, const char **argv)
 	if (argc != 2)
 		usage(pack_mtimes_usage);
 
-	for (p = get_all_packs(the_repository); p; p = p->next) {
+	for (p = packfile_store_get_all_packs(the_repository->objects->packfiles); p; p = p->next) {
 		strbuf_addstr(&buf, basename(p->pack_name));
 		strbuf_strip_suffix(&buf, ".pack");
 		strbuf_addstr(&buf, ".mtimes");
diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c
index 086238c..f5f3375 100644
--- a/t/helper/test-path-utils.c
+++ b/t/helper/test-path-utils.c
@@ -348,6 +348,7 @@ int cmd__path_utils(int argc, const char **argv)
 	if (argc == 4 && !strcmp(argv[1], "longest_ancestor_length")) {
 		int len;
 		struct string_list ceiling_dirs = STRING_LIST_INIT_DUP;
+		const char path_sep[] = { PATH_SEP, '\0' };
 		char *path = xstrdup(argv[2]);
 
 		/*
@@ -362,7 +363,7 @@ int cmd__path_utils(int argc, const char **argv)
 		 */
 		if (normalize_path_copy(path, path))
 			die("Path \"%s\" could not be normalized", argv[2]);
-		string_list_split(&ceiling_dirs, argv[3], PATH_SEP, -1);
+		string_list_split(&ceiling_dirs, argv[3], path_sep, -1);
 		filter_string_list(&ceiling_dirs, 0,
 				   normalize_ceiling_entry, NULL);
 		len = longest_ancestor_length(path, &ceiling_dirs);
diff --git a/t/helper/test-read-graph.c b/t/helper/test-read-graph.c
index ef5339b..6a5f64e 100644
--- a/t/helper/test-read-graph.c
+++ b/t/helper/test-read-graph.c
@@ -81,7 +81,7 @@ int cmd__read_graph(int argc, const char **argv)
 
 	prepare_repo_settings(the_repository);
 
-	graph = read_commit_graph_one(the_repository, source);
+	graph = read_commit_graph_one(source);
 	if (!graph) {
 		ret = 1;
 		goto done;
diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c
index da2aa03..6de5d16 100644
--- a/t/helper/test-read-midx.c
+++ b/t/helper/test-read-midx.c
@@ -11,14 +11,24 @@
 #include "gettext.h"
 #include "pack-revindex.h"
 
+static struct multi_pack_index *setup_midx(const char *object_dir)
+{
+	struct odb_source *source;
+	setup_git_directory();
+	source = odb_find_source(the_repository->objects, object_dir);
+	if (!source)
+		source = odb_add_to_alternates_memory(the_repository->objects,
+						      object_dir);
+	return load_multi_pack_index(source);
+}
+
 static int read_midx_file(const char *object_dir, const char *checksum,
 			  int show_objects)
 {
 	uint32_t i;
 	struct multi_pack_index *m;
 
-	setup_git_directory();
-	m = load_multi_pack_index(the_repository, object_dir, 1);
+	m = setup_midx(object_dir);
 
 	if (!m)
 		return 1;
@@ -56,7 +66,7 @@ static int read_midx_file(const char *object_dir, const char *checksum,
 	for (i = 0; i < m->num_packs; i++)
 		printf("%s\n", m->pack_names[i]);
 
-	printf("object-dir: %s\n", m->object_dir);
+	printf("object-dir: %s\n", m->source->path);
 
 	if (show_objects) {
 		struct object_id oid;
@@ -65,7 +75,7 @@ static int read_midx_file(const char *object_dir, const char *checksum,
 		for (i = 0; i < m->num_objects; i++) {
 			nth_midxed_object_oid(&oid, m,
 					      i + m->num_objects_in_base);
-			fill_midx_entry(the_repository, &oid, &e, m);
+			fill_midx_entry(m, &oid, &e);
 
 			printf("%s %"PRIu64"\t%s\n",
 			       oid_to_hex(&oid), e.offset, e.p->pack_name);
@@ -81,8 +91,7 @@ static int read_midx_checksum(const char *object_dir)
 {
 	struct multi_pack_index *m;
 
-	setup_git_directory();
-	m = load_multi_pack_index(the_repository, object_dir, 1);
+	m = setup_midx(object_dir);
 	if (!m)
 		return 1;
 	printf("%s\n", hash_to_hex(get_midx_checksum(m)));
@@ -96,9 +105,7 @@ static int read_midx_preferred_pack(const char *object_dir)
 	struct multi_pack_index *midx = NULL;
 	uint32_t preferred_pack;
 
-	setup_git_directory();
-
-	midx = load_multi_pack_index(the_repository, object_dir, 1);
+	midx = setup_midx(object_dir);
 	if (!midx)
 		return 1;
 
@@ -119,14 +126,12 @@ static int read_midx_bitmapped_packs(const char *object_dir)
 	struct bitmapped_pack pack;
 	uint32_t i;
 
-	setup_git_directory();
-
-	midx = load_multi_pack_index(the_repository, object_dir, 1);
+	midx = setup_midx(object_dir);
 	if (!midx)
 		return 1;
 
 	for (i = 0; i < midx->num_packs + midx->num_packs_in_base; i++) {
-		if (nth_bitmapped_pack(the_repository, midx, &pack, i) < 0) {
+		if (nth_bitmapped_pack(midx, &pack, i) < 0) {
 			close_midx(midx);
 			return 1;
 		}
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index 8d9a271..83b06d3 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -29,7 +29,7 @@ static unsigned int parse_flags(const char *str, struct flag_definition *defs)
 	if (!strcmp(str, "0"))
 		return 0;
 
-	string_list_split(&masks, str, ',', 64);
+	string_list_split(&masks, str, ",", 64);
 	for (size_t i = 0; i < masks.nr; i++) {
 		const char *name = masks.items[i].string;
 		struct flag_definition *def = defs;
@@ -215,7 +215,8 @@ static int cmd_for_each_reflog(struct ref_store *refs,
 	return refs_for_each_reflog(refs, each_reflog, NULL);
 }
 
-static int each_reflog_ent(struct object_id *old_oid, struct object_id *new_oid,
+static int each_reflog_ent(const char *refname UNUSED,
+			   struct object_id *old_oid, struct object_id *new_oid,
 			   const char *committer, timestamp_t timestamp,
 			   int tz, const char *msg, void *cb_data UNUSED)
 {
diff --git a/t/meson.build b/t/meson.build
index bbeba1a..401b24e 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -1,5 +1,6 @@
 clar_test_suites = [
   'unit-tests/u-ctype.c',
+  'unit-tests/u-dir.c',
   'unit-tests/u-example-decorate.c',
   'unit-tests/u-hash.c',
   'unit-tests/u-hashmap.c',
@@ -145,6 +146,7 @@
   't0611-reftable-httpd.sh',
   't0612-reftable-jgit-compatibility.sh',
   't0613-reftable-write-options.sh',
+  't0614-reftable-fsck.sh',
   't1000-read-tree-m-3way.sh',
   't1001-read-tree-m-2way.sh',
   't1002-read-tree-m-u-2way.sh',
@@ -204,10 +206,15 @@
   't1418-reflog-exists.sh',
   't1419-exclude-refs.sh',
   't1420-lost-found.sh',
+  't1421-reflog-write.sh',
+  't1422-show-ref-exists.sh',
   't1430-bad-ref-name.sh',
   't1450-fsck.sh',
   't1451-fsck-buffer.sh',
   't1460-refs-migrate.sh',
+  't1461-refs-list.sh',
+  't1462-refs-exists.sh',
+  't1463-refs-optimize.sh',
   't1500-rev-parse.sh',
   't1501-work-tree.sh',
   't1502-rev-parse-parseopt.sh',
@@ -230,6 +237,7 @@
   't1700-split-index.sh',
   't1701-racy-split-index.sh',
   't1800-hook.sh',
+  't1900-repo.sh',
   't2000-conflict-when-checking-files-out.sh',
   't2002-checkout-cache-u.sh',
   't2003-checkout-cache-mkdir.sh',
@@ -486,6 +494,7 @@
   't4069-remerge-diff.sh',
   't4070-diff-pairs.sh',
   't4071-diff-minimal.sh',
+  't4072-diff-max-depth.sh',
   't4100-apply-stat.sh',
   't4101-apply-nonl.sh',
   't4102-apply-rename.sh',
@@ -946,6 +955,7 @@
   't8012-blame-colors.sh',
   't8013-blame-ignore-revs.sh',
   't8014-blame-ignore-fuzzy.sh',
+  't8020-last-modified.sh',
   't9001-send-email.sh',
   't9002-column.sh',
   't9003-help-autocorrect.sh',
@@ -1026,6 +1036,7 @@
   't9302-fast-import-unpack-limit.sh',
   't9303-fast-import-compression.sh',
   't9304-fast-import-marks.sh',
+  't9305-fast-import-signatures.sh',
   't9350-fast-export.sh',
   't9351-fast-export-anonymize.sh',
   't9400-git-cvsserver-server.sh',
@@ -1139,6 +1150,7 @@
   'perf/p7820-grep-engines.sh',
   'perf/p7821-grep-engines-fixed.sh',
   'perf/p7822-grep-perl-character.sh',
+  'perf/p8020-last-modified.sh',
   'perf/p9210-scalar.sh',
   'perf/p9300-fast-import-export.sh',
 ]
@@ -1214,4 +1226,4 @@
       timeout: 0,
     )
   endforeach
-endif
\ No newline at end of file
+endif
diff --git a/t/pack-refs-tests.sh b/t/pack-refs-tests.sh
new file mode 100644
index 0000000..3dbcc01
--- /dev/null
+++ b/t/pack-refs-tests.sh
@@ -0,0 +1,431 @@
+pack_refs=${pack_refs:-pack-refs}
+
+test_expect_success 'enable reflogs' '
+	git config core.logallrefupdates true
+'
+
+test_expect_success 'prepare a trivial repository' '
+	echo Hello > A &&
+	git update-index --add A &&
+	git commit -m "Initial commit." &&
+	HEAD=$(git rev-parse --verify HEAD)
+'
+
+test_expect_success '${pack_refs} --prune --all' '
+	test_path_is_missing .git/packed-refs &&
+	git ${pack_refs} --no-prune --all &&
+	test_path_is_file .git/packed-refs &&
+	N=$(find .git/refs -type f | wc -l) &&
+	test "$N" != 0 &&
+
+	git ${pack_refs} --prune --all &&
+	test_path_is_file .git/packed-refs &&
+	N=$(find .git/refs -type f) &&
+	test -z "$N"
+'
+
+SHA1=
+
+test_expect_success 'see if git show-ref works as expected' '
+	git branch a &&
+	SHA1=$(cat .git/refs/heads/a) &&
+	echo "$SHA1 refs/heads/a" >expect &&
+	git show-ref a >result &&
+	test_cmp expect result
+'
+
+test_expect_success 'see if a branch still exists when packed' '
+	git branch b &&
+	git ${pack_refs} --all &&
+	rm -f .git/refs/heads/b &&
+	echo "$SHA1 refs/heads/b" >expect &&
+	git show-ref b >result &&
+	test_cmp expect result
+'
+
+test_expect_success 'git branch c/d should barf if branch c exists' '
+	git branch c &&
+	git ${pack_refs} --all &&
+	rm -f .git/refs/heads/c &&
+	test_must_fail git branch c/d
+'
+
+test_expect_success 'see if a branch still exists after git ${pack_refs} --prune' '
+	git branch e &&
+	git ${pack_refs} --all --prune &&
+	echo "$SHA1 refs/heads/e" >expect &&
+	git show-ref e >result &&
+	test_cmp expect result
+'
+
+test_expect_success 'see if git ${pack_refs} --prune remove ref files' '
+	git branch f &&
+	git ${pack_refs} --all --prune &&
+	! test -f .git/refs/heads/f
+'
+
+test_expect_success 'see if git ${pack_refs} --prune removes empty dirs' '
+	git branch r/s/t &&
+	git ${pack_refs} --all --prune &&
+	! test -e .git/refs/heads/r
+'
+
+test_expect_success 'git branch g should work when git branch g/h has been deleted' '
+	git branch g/h &&
+	git ${pack_refs} --all --prune &&
+	git branch -d g/h &&
+	git branch g &&
+	git ${pack_refs} --all &&
+	git branch -d g
+'
+
+test_expect_success 'git branch i/j/k should barf if branch i exists' '
+	git branch i &&
+	git ${pack_refs} --all --prune &&
+	test_must_fail git branch i/j/k
+'
+
+test_expect_success 'test git branch k after branch k/l/m and k/lm have been deleted' '
+	git branch k/l &&
+	git branch k/lm &&
+	git branch -d k/l &&
+	git branch k/l/m &&
+	git branch -d k/l/m &&
+	git branch -d k/lm &&
+	git branch k
+'
+
+test_expect_success 'test git branch n after some branch deletion and pruning' '
+	git branch n/o &&
+	git branch n/op &&
+	git branch -d n/o &&
+	git branch n/o/p &&
+	git branch -d n/op &&
+	git ${pack_refs} --all --prune &&
+	git branch -d n/o/p &&
+	git branch n
+'
+
+test_expect_success 'test excluded refs are not packed' '
+	git branch dont_pack1 &&
+	git branch dont_pack2 &&
+	git branch pack_this &&
+	git ${pack_refs} --all --exclude "refs/heads/dont_pack*" &&
+	test -f .git/refs/heads/dont_pack1 &&
+	test -f .git/refs/heads/dont_pack2 &&
+	! test -f .git/refs/heads/pack_this'
+
+test_expect_success 'test --no-exclude refs clears excluded refs' '
+	git branch dont_pack3 &&
+	git branch dont_pack4 &&
+	git ${pack_refs} --all --exclude "refs/heads/dont_pack*" --no-exclude &&
+	! test -f .git/refs/heads/dont_pack3 &&
+	! test -f .git/refs/heads/dont_pack4'
+
+test_expect_success 'test only included refs are packed' '
+	git branch pack_this1 &&
+	git branch pack_this2 &&
+	git tag dont_pack5 &&
+	git ${pack_refs} --include "refs/heads/pack_this*" &&
+	test -f .git/refs/tags/dont_pack5 &&
+	! test -f .git/refs/heads/pack_this1 &&
+	! test -f .git/refs/heads/pack_this2'
+
+test_expect_success 'test --no-include refs clears included refs' '
+	git branch pack1 &&
+	git branch pack2 &&
+	git ${pack_refs} --include "refs/heads/pack*" --no-include &&
+	test -f .git/refs/heads/pack1 &&
+	test -f .git/refs/heads/pack2'
+
+test_expect_success 'test --exclude takes precedence over --include' '
+	git branch dont_pack5 &&
+	git ${pack_refs} --include "refs/heads/pack*" --exclude "refs/heads/pack*" &&
+	test -f .git/refs/heads/dont_pack5'
+
+test_expect_success 'see if up-to-date packed refs are preserved' '
+	git branch q &&
+	git ${pack_refs} --all --prune &&
+	git update-ref refs/heads/q refs/heads/q &&
+	! test -f .git/refs/heads/q
+'
+
+test_expect_success 'pack, prune and repack' '
+	git tag foo &&
+	git ${pack_refs} --all --prune &&
+	git show-ref >all-of-them &&
+	git ${pack_refs} &&
+	git show-ref >again &&
+	test_cmp all-of-them again
+'
+
+test_expect_success 'explicit ${pack_refs} with dangling packed reference' '
+	git commit --allow-empty -m "soon to be garbage-collected" &&
+	git ${pack_refs} --all &&
+	git reset --hard HEAD^ &&
+	git reflog expire --expire=all --all &&
+	git prune --expire=all &&
+	git ${pack_refs} --all 2>result &&
+	test_must_be_empty result
+'
+
+test_expect_success 'delete ref with dangling packed version' '
+	git checkout -b lamb &&
+	git commit --allow-empty -m "future garbage" &&
+	git ${pack_refs} --all &&
+	git reset --hard HEAD^ &&
+	git checkout main &&
+	git reflog expire --expire=all --all &&
+	git prune --expire=all &&
+	git branch -d lamb 2>result &&
+	test_must_be_empty result
+'
+
+test_expect_success 'delete ref while another dangling packed ref' '
+	git branch lamb &&
+	git commit --allow-empty -m "future garbage" &&
+	git ${pack_refs} --all &&
+	git reset --hard HEAD^ &&
+	git reflog expire --expire=all --all &&
+	git prune --expire=all &&
+	git branch -d lamb 2>result &&
+	test_must_be_empty result
+'
+
+test_expect_success 'pack ref directly below refs/' '
+	git update-ref refs/top HEAD &&
+	git ${pack_refs} --all --prune &&
+	grep refs/top .git/packed-refs &&
+	test_path_is_missing .git/refs/top
+'
+
+test_expect_success 'do not pack ref in refs/bisect' '
+	git update-ref refs/bisect/local HEAD &&
+	git ${pack_refs} --all --prune &&
+	! grep refs/bisect/local .git/packed-refs >/dev/null &&
+	test_path_is_file .git/refs/bisect/local
+'
+
+test_expect_success 'disable reflogs' '
+	git config core.logallrefupdates false &&
+	rm -rf .git/logs
+'
+
+test_expect_success 'create packed foo/bar/baz branch' '
+	git branch foo/bar/baz &&
+	git ${pack_refs} --all --prune &&
+	test_path_is_missing .git/refs/heads/foo/bar/baz &&
+	test_must_fail git reflog exists refs/heads/foo/bar/baz
+'
+
+test_expect_success 'notice d/f conflict with existing directory' '
+	test_must_fail git branch foo &&
+	test_must_fail git branch foo/bar
+'
+
+test_expect_success 'existing directory reports concrete ref' '
+	test_must_fail git branch foo 2>stderr &&
+	test_grep refs/heads/foo/bar/baz stderr
+'
+
+test_expect_success 'notice d/f conflict with existing ref' '
+	test_must_fail git branch foo/bar/baz/extra &&
+	test_must_fail git branch foo/bar/baz/lots/of/extra/components
+'
+
+test_expect_success 'reject packed-refs with unterminated line' '
+	cp .git/packed-refs .git/packed-refs.bak &&
+	test_when_finished "mv .git/packed-refs.bak .git/packed-refs" &&
+	printf "%s" "$HEAD refs/zzzzz" >>.git/packed-refs &&
+	echo "fatal: unterminated line in .git/packed-refs: $HEAD refs/zzzzz" >expected_err &&
+	test_must_fail git for-each-ref >out 2>err &&
+	test_cmp expected_err err
+'
+
+test_expect_success 'reject packed-refs containing junk' '
+	cp .git/packed-refs .git/packed-refs.bak &&
+	test_when_finished "mv .git/packed-refs.bak .git/packed-refs" &&
+	printf "%s\n" "bogus content" >>.git/packed-refs &&
+	echo "fatal: unexpected line in .git/packed-refs: bogus content" >expected_err &&
+	test_must_fail git for-each-ref >out 2>err &&
+	test_cmp expected_err err
+'
+
+test_expect_success 'reject packed-refs with a short SHA-1' '
+	cp .git/packed-refs .git/packed-refs.bak &&
+	test_when_finished "mv .git/packed-refs.bak .git/packed-refs" &&
+	printf "%.7s %s\n" $HEAD refs/zzzzz >>.git/packed-refs &&
+	printf "fatal: unexpected line in .git/packed-refs: %.7s %s\n" $HEAD refs/zzzzz >expected_err &&
+	test_must_fail git for-each-ref >out 2>err &&
+	test_cmp expected_err err
+'
+
+test_expect_success 'timeout if packed-refs.lock exists' '
+	LOCK=.git/packed-refs.lock &&
+	>"$LOCK" &&
+	test_when_finished "rm -f $LOCK" &&
+	test_must_fail git ${pack_refs} --all --prune
+'
+
+test_expect_success 'retry acquiring packed-refs.lock' '
+	LOCK=.git/packed-refs.lock &&
+	>"$LOCK" &&
+	test_when_finished "wait && rm -f $LOCK" &&
+	{
+		( sleep 1 && rm -f $LOCK ) &
+	} &&
+	git -c core.packedrefstimeout=3000 ${pack_refs} --all --prune
+'
+
+test_expect_success SYMLINKS 'pack symlinked packed-refs' '
+	# First make sure that symlinking works when reading:
+	git update-ref refs/heads/lossy refs/heads/main &&
+	git for-each-ref >all-refs-before &&
+	mv .git/packed-refs .git/my-deviant-packed-refs &&
+	ln -s my-deviant-packed-refs .git/packed-refs &&
+	git for-each-ref >all-refs-linked &&
+	test_cmp all-refs-before all-refs-linked &&
+	git ${pack_refs} --all --prune &&
+	git for-each-ref >all-refs-packed &&
+	test_cmp all-refs-before all-refs-packed &&
+	test -h .git/packed-refs &&
+	test "$(test_readlink .git/packed-refs)" = "my-deviant-packed-refs"
+'
+
+# The 'packed-refs' file is stored directly in .git/. This means it is global
+# to the repository, and can only contain refs that are shared across all
+# worktrees.
+test_expect_success 'refs/worktree must not be packed' '
+	test_commit initial &&
+	test_commit wt1 &&
+	test_commit wt2 &&
+	git worktree add wt1 wt1 &&
+	git worktree add wt2 wt2 &&
+	git checkout initial &&
+	git update-ref refs/worktree/foo HEAD &&
+	git -C wt1 update-ref refs/worktree/foo HEAD &&
+	git -C wt2 update-ref refs/worktree/foo HEAD &&
+	git ${pack_refs} --all &&
+	test_path_is_missing .git/refs/tags/wt1 &&
+	test_path_is_file .git/refs/worktree/foo &&
+	test_path_is_file .git/worktrees/wt1/refs/worktree/foo &&
+	test_path_is_file .git/worktrees/wt2/refs/worktree/foo
+'
+
+# we do not want to count on running ${pack_refs} to
+# actually pack it, as it is perfectly reasonable to
+# skip processing a broken ref
+test_expect_success 'create packed-refs file with broken ref' '
+	test_tick && git commit --allow-empty -m one &&
+	recoverable=$(git rev-parse HEAD) &&
+	test_tick && git commit --allow-empty -m two &&
+	missing=$(git rev-parse HEAD) &&
+	rm -f .git/refs/heads/main &&
+	cat >.git/packed-refs <<-EOF &&
+	$missing refs/heads/main
+	$recoverable refs/heads/other
+	EOF
+	echo $missing >expect &&
+	git rev-parse refs/heads/main >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success '${pack_refs} does not silently delete broken packed ref' '
+	git ${pack_refs} --all --prune &&
+	git rev-parse refs/heads/main >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success '${pack_refs} does not drop broken refs during deletion' '
+	git update-ref -d refs/heads/other &&
+	git rev-parse refs/heads/main >actual &&
+	test_cmp expect actual
+'
+
+for command in "git ${pack_refs} --all --auto" "git maintenance run --task=${pack_refs} --auto"
+do
+	test_expect_success "$command does not repack below 16 refs without packed-refs" '
+		test_when_finished "rm -rf repo" &&
+		git init repo &&
+		(
+			cd repo &&
+			git config set maintenance.auto false &&
+			git commit --allow-empty --message "initial" &&
+
+			# Create 14 additional references, which brings us to
+			# 15 together with the default branch.
+			printf "create refs/heads/loose-%d HEAD\n" $(test_seq 14) >stdin &&
+			git update-ref --stdin <stdin &&
+			test_path_is_missing .git/packed-refs &&
+			git ${pack_refs} --auto --all &&
+			test_path_is_missing .git/packed-refs &&
+
+			# Create the 16th reference, which should cause us to repack.
+			git update-ref refs/heads/loose-15 HEAD &&
+			git ${pack_refs} --auto --all &&
+			test_path_is_file .git/packed-refs
+		)
+	'
+
+	test_expect_success "$command does not repack below 16 refs with small packed-refs" '
+		test_when_finished "rm -rf repo" &&
+		git init repo &&
+		(
+			cd repo &&
+			git config set maintenance.auto false &&
+			git commit --allow-empty --message "initial" &&
+
+			git ${pack_refs} --all &&
+			test_line_count = 2 .git/packed-refs &&
+
+			# Create 15 loose references.
+			printf "create refs/heads/loose-%d HEAD\n" $(test_seq 15) >stdin &&
+			git update-ref --stdin <stdin &&
+			git ${pack_refs} --auto --all &&
+			test_line_count = 2 .git/packed-refs &&
+
+			# Create the 16th loose reference, which should cause us to repack.
+			git update-ref refs/heads/loose-17 HEAD &&
+			git ${pack_refs} --auto --all &&
+			test_line_count = 18 .git/packed-refs
+		)
+	'
+
+	test_expect_success "$command scales with size of packed-refs" '
+		test_when_finished "rm -rf repo" &&
+		git init repo &&
+		(
+			cd repo &&
+			git config set maintenance.auto false &&
+			git commit --allow-empty --message "initial" &&
+
+			# Create 99 packed refs. This should cause the heuristic
+			# to require more than the minimum amount of loose refs.
+			test_seq 99 |
+			while read i
+			do
+				printf "create refs/heads/packed-%d HEAD\n" $i || return 1
+			done >stdin &&
+			git update-ref --stdin <stdin &&
+			git ${pack_refs} --all &&
+			test_line_count = 101 .git/packed-refs &&
+
+			# Create 24 loose refs, which should not yet cause us to repack.
+			printf "create refs/heads/loose-%d HEAD\n" $(test_seq 24) >stdin &&
+			git update-ref --stdin <stdin &&
+			git ${pack_refs} --auto --all &&
+			test_line_count = 101 .git/packed-refs &&
+
+			# Create another handful of refs to cross the border.
+			# Note that we explicitly do not check for strict
+			# boundaries here, as this also depends on the size of
+			# the object hash.
+			printf "create refs/heads/addn-%d HEAD\n" $(test_seq 10) >stdin &&
+			git update-ref --stdin <stdin &&
+			git ${pack_refs} --auto --all &&
+			test_line_count = 135 .git/packed-refs
+		)
+	'
+done
+
+test_done
diff --git a/t/perf/p8020-last-modified.sh b/t/perf/p8020-last-modified.sh
new file mode 100755
index 0000000..cb1f98d
--- /dev/null
+++ b/t/perf/p8020-last-modified.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+test_description='last-modified perf tests'
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+test_perf 'top-level last-modified' '
+	git last-modified HEAD
+'
+
+test_perf 'top-level recursive last-modified' '
+	git last-modified -r HEAD
+'
+
+test_perf 'subdir last-modified' '
+	git ls-tree -d HEAD >subtrees &&
+	path="$(head -n 1 subtrees | cut -f2)" &&
+	git last-modified -r HEAD -- "$path"
+'
+
+test_done
diff --git a/t/show-ref-exists-tests.sh b/t/show-ref-exists-tests.sh
new file mode 100644
index 0000000..36e8e9d
--- /dev/null
+++ b/t/show-ref-exists-tests.sh
@@ -0,0 +1,77 @@
+git_show_ref_exists=${git_show_ref_exists:-git show-ref --exists}
+
+test_expect_success setup '
+	test_commit --annotate A &&
+	git checkout -b side &&
+	test_commit --annotate B &&
+	git checkout main &&
+	test_commit C &&
+	git branch B A^0
+'
+
+test_expect_success '--exists with existing reference' '
+	${git_show_ref_exists} refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+'
+
+test_expect_success '--exists with missing reference' '
+	test_expect_code 2 ${git_show_ref_exists} refs/heads/does-not-exist
+'
+
+test_expect_success '--exists does not use DWIM' '
+	test_expect_code 2 ${git_show_ref_exists} $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 2>err &&
+	grep "reference does not exist" err
+'
+
+test_expect_success '--exists with HEAD' '
+	${git_show_ref_exists} HEAD
+'
+
+test_expect_success '--exists with bad reference name' '
+	test_when_finished "git update-ref -d refs/heads/bad...name" &&
+	new_oid=$(git rev-parse HEAD) &&
+	test-tool ref-store main update-ref msg refs/heads/bad...name $new_oid $ZERO_OID REF_SKIP_REFNAME_VERIFICATION &&
+	${git_show_ref_exists} refs/heads/bad...name
+'
+
+test_expect_success '--exists with arbitrary symref' '
+	test_when_finished "git symbolic-ref -d refs/symref" &&
+	git symbolic-ref refs/symref refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
+	${git_show_ref_exists} refs/symref
+'
+
+test_expect_success '--exists with dangling symref' '
+	test_when_finished "git symbolic-ref -d refs/heads/dangling" &&
+	git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
+	${git_show_ref_exists} refs/heads/dangling
+'
+
+test_expect_success '--exists with nonexistent object ID' '
+	test-tool ref-store main update-ref msg refs/heads/missing-oid $(test_oid 001) $ZERO_OID REF_SKIP_OID_VERIFICATION &&
+	${git_show_ref_exists} refs/heads/missing-oid
+'
+
+test_expect_success '--exists with non-commit object' '
+	tree_oid=$(git rev-parse HEAD^{tree}) &&
+	test-tool ref-store main update-ref msg refs/heads/tree ${tree_oid} $ZERO_OID REF_SKIP_OID_VERIFICATION &&
+	${git_show_ref_exists} refs/heads/tree
+'
+
+test_expect_success '--exists with directory fails with generic error' '
+	cat >expect <<-EOF &&
+	error: reference does not exist
+	EOF
+	test_expect_code 2 ${git_show_ref_exists} refs/heads 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success '--exists with non-existent special ref' '
+	test_expect_code 2 ${git_show_ref_exists} FETCH_HEAD
+'
+
+test_expect_success '--exists with existing special ref' '
+	test_when_finished "rm .git/FETCH_HEAD" &&
+	git rev-parse HEAD >.git/FETCH_HEAD &&
+	${git_show_ref_exists} FETCH_HEAD
+'
+
+test_done
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index f593c53..618da08 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -883,6 +883,22 @@
 	test_grep ! "hint: " err
 '
 
+test_expect_success 'default branch name' '
+	if test_have_prereq WITH_BREAKING_CHANGES
+	then
+		expect=main
+	else
+		expect=master
+	fi &&
+	echo "refs/heads/$expect" >expect &&
+	(
+		sane_unset GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
+		git init default-initial-branch-name
+	) &&
+	git -C default-initial-branch-name symbolic-ref HEAD >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'overridden default main branch name (env)' '
 	test_config_global init.defaultBranch nmb &&
 	GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=env git init main-branch-env &&
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 854d59e..07a53e7 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -27,6 +27,20 @@
 	test_grep "^fatal: alias loop detected: expansion of" output
 '
 
+test_expect_success 'looping aliases - deprecated builtins' '
+	test_config alias.whatchanged pack-redundant &&
+	test_config alias.pack-redundant whatchanged &&
+	cat >expect <<-EOF &&
+	${SQ}whatchanged${SQ} is aliased to ${SQ}pack-redundant${SQ}
+	${SQ}pack-redundant${SQ} is aliased to ${SQ}whatchanged${SQ}
+	fatal: alias loop detected: expansion of ${SQ}whatchanged${SQ} does not terminate:
+	  whatchanged <==
+	  pack-redundant ==>
+	EOF
+	test_must_fail git whatchanged -h 2>actual &&
+	test_cmp expect actual
+'
+
 # This test is disabled until external loops are fixed, because would block
 # the test suite for a full minute.
 #
@@ -55,4 +69,47 @@
 	test_cmp expect actual
 '
 
+can_alias_deprecated_builtin () {
+	cmd="$1" &&
+	# some git(1) commands will fail for `-h` (the case for
+	# git-status as of 2025-09-07)
+	test_might_fail git status -h >expect &&
+	test_file_not_empty expect &&
+	test_might_fail git -c alias."$cmd"=status "$cmd" -h >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success 'can alias-shadow deprecated builtins' '
+	for cmd in $(git --list-cmds=deprecated)
+	do
+		can_alias_deprecated_builtin "$cmd" || return 1
+	done
+'
+
+test_expect_success 'can alias-shadow via two deprecated builtins' '
+	# some git(1) commands will fail... (see above)
+	test_might_fail git status -h >expect &&
+	test_file_not_empty expect &&
+	test_might_fail git -c alias.whatchanged=pack-redundant \
+		-c alias.pack-redundant=status whatchanged -h >actual &&
+	test_cmp expect actual
+'
+
+cannot_alias_regular_builtin () {
+	cmd="$1" &&
+	# some git(1) commands will fail... (see above)
+	test_might_fail git "$cmd" -h >expect &&
+	test_file_not_empty expect &&
+	test_might_fail git -c alias."$cmd"=status "$cmd" -h >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success 'cannot alias-shadow a sample of regular builtins' '
+	for cmd in grep check-ref-format interpret-trailers \
+		checkout-index fast-import diagnose rev-list prune
+	do
+		cannot_alias_regular_builtin "$cmd" || return 1
+	done
+'
+
 test_done
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
index cb3a85c..07aa834 100755
--- a/t/t0300-credentials.sh
+++ b/t/t0300-credentials.sh
@@ -991,18 +991,24 @@
 
 test_expect_success 'credential config with partial URLs' '
 	echo "echo password=yep" | write_script git-credential-yep &&
-	test_write_lines url=https://user@example.com/repo.git >stdin &&
+	test_write_lines url=https://user@example.com/org/repo.git >stdin &&
 	for partial in \
 		example.com \
+		example.com/org/repo.git \
 		user@example.com \
+		user@example.com/org/repo.git \
 		https:// \
 		https://example.com \
 		https://example.com/ \
+		https://example.com/org \
+		https://example.com/org/ \
+		https://example.com/org/repo.git \
 		https://user@example.com \
 		https://user@example.com/ \
-		https://example.com/repo.git \
-		https://user@example.com/repo.git \
-		/repo.git
+		https://user@example.com/org \
+		https://user@example.com/org/ \
+		https://user@example.com/org/repo.git \
+		/org/repo.git
 	do
 		git -c credential.$partial.helper=yep \
 			credential fill <stdin >stdout &&
@@ -1012,7 +1018,12 @@
 
 	for partial in \
 		dont.use.this \
+		example.com/o \
+		user@example.com/o \
 		http:// \
+		https://example.com/o \
+		https://user@example.com/o \
+		/o \
 		/repo
 	do
 		git -c credential.$partial.helper=yep \
diff --git a/t/t0450-txt-doc-vs-help.sh b/t/t0450-txt-doc-vs-help.sh
index 2f7504a..e12e18f 100755
--- a/t/t0450-txt-doc-vs-help.sh
+++ b/t/t0450-txt-doc-vs-help.sh
@@ -41,7 +41,7 @@
 }
 
 builtin_to_adoc () {
-       echo "$GIT_BUILD_DIR/Documentation/git-$1.adoc"
+       echo "$GIT_SOURCE_DIR/Documentation/git-$1.adoc"
 }
 
 adoc_to_synopsis () {
@@ -112,10 +112,19 @@
 	adoc="$(builtin_to_adoc "$builtin")" &&
 	preq="$(echo BUILTIN_ADOC_$builtin | tr '[:lower:]-' '[:upper:]_')" &&
 
-	if test -f "$adoc"
+	# If and only if *.adoc is missing, builtin shall be listed in t0450/adoc-missing.
+	if grep -q "^$builtin$" "$TEST_DIRECTORY"/t0450/adoc-missing
 	then
+		test_expect_success "$builtin appropriately marked as not having .adoc" '
+			! test -f "$adoc"
+		'
+	else
 		test_set_prereq "$preq"
-	fi &&
+
+		test_expect_success "$builtin appropriately marked as having .adoc" '
+			test -f "$adoc"
+		'
+	fi
 
 	# *.adoc output assertions
 	test_expect_success "$preq" "$builtin *.adoc SYNOPSIS has dashed labels" '
diff --git a/t/t0450/adoc-help-mismatches b/t/t0450/adoc-help-mismatches
index 06b469b..2c6ecd5 100644
--- a/t/t0450/adoc-help-mismatches
+++ b/t/t0450/adoc-help-mismatches
@@ -17,7 +17,6 @@
 fast-import
 fetch-pack
 fmt-merge-msg
-for-each-ref
 format-patch
 fsck-objects
 fsmonitor--daemon
diff --git a/t/t0450/adoc-missing b/t/t0450/adoc-missing
new file mode 100644
index 0000000..1ec9f8d
--- /dev/null
+++ b/t/t0450/adoc-missing
@@ -0,0 +1,9 @@
+checkout--worker
+merge-ours
+merge-recursive
+merge-recursive-ours
+merge-recursive-theirs
+merge-subtree
+pickaxe
+submodule--helper
+upload-archive--writer
diff --git a/t/t0601-reffiles-pack-refs.sh b/t/t0601-reffiles-pack-refs.sh
index aa7f6ec..12cf5d1 100755
--- a/t/t0601-reffiles-pack-refs.sh
+++ b/t/t0601-reffiles-pack-refs.sh
@@ -17,432 +17,4 @@
 
 . ./test-lib.sh
 
-test_expect_success 'enable reflogs' '
-	git config core.logallrefupdates true
-'
-
-test_expect_success 'prepare a trivial repository' '
-	echo Hello > A &&
-	git update-index --add A &&
-	git commit -m "Initial commit." &&
-	HEAD=$(git rev-parse --verify HEAD)
-'
-
-test_expect_success 'pack-refs --prune --all' '
-	test_path_is_missing .git/packed-refs &&
-	git pack-refs --no-prune --all &&
-	test_path_is_file .git/packed-refs &&
-	N=$(find .git/refs -type f | wc -l) &&
-	test "$N" != 0 &&
-
-	git pack-refs --prune --all &&
-	test_path_is_file .git/packed-refs &&
-	N=$(find .git/refs -type f) &&
-	test -z "$N"
-'
-
-SHA1=
-
-test_expect_success 'see if git show-ref works as expected' '
-	git branch a &&
-	SHA1=$(cat .git/refs/heads/a) &&
-	echo "$SHA1 refs/heads/a" >expect &&
-	git show-ref a >result &&
-	test_cmp expect result
-'
-
-test_expect_success 'see if a branch still exists when packed' '
-	git branch b &&
-	git pack-refs --all &&
-	rm -f .git/refs/heads/b &&
-	echo "$SHA1 refs/heads/b" >expect &&
-	git show-ref b >result &&
-	test_cmp expect result
-'
-
-test_expect_success 'git branch c/d should barf if branch c exists' '
-	git branch c &&
-	git pack-refs --all &&
-	rm -f .git/refs/heads/c &&
-	test_must_fail git branch c/d
-'
-
-test_expect_success 'see if a branch still exists after git pack-refs --prune' '
-	git branch e &&
-	git pack-refs --all --prune &&
-	echo "$SHA1 refs/heads/e" >expect &&
-	git show-ref e >result &&
-	test_cmp expect result
-'
-
-test_expect_success 'see if git pack-refs --prune remove ref files' '
-	git branch f &&
-	git pack-refs --all --prune &&
-	! test -f .git/refs/heads/f
-'
-
-test_expect_success 'see if git pack-refs --prune removes empty dirs' '
-	git branch r/s/t &&
-	git pack-refs --all --prune &&
-	! test -e .git/refs/heads/r
-'
-
-test_expect_success 'git branch g should work when git branch g/h has been deleted' '
-	git branch g/h &&
-	git pack-refs --all --prune &&
-	git branch -d g/h &&
-	git branch g &&
-	git pack-refs --all &&
-	git branch -d g
-'
-
-test_expect_success 'git branch i/j/k should barf if branch i exists' '
-	git branch i &&
-	git pack-refs --all --prune &&
-	test_must_fail git branch i/j/k
-'
-
-test_expect_success 'test git branch k after branch k/l/m and k/lm have been deleted' '
-	git branch k/l &&
-	git branch k/lm &&
-	git branch -d k/l &&
-	git branch k/l/m &&
-	git branch -d k/l/m &&
-	git branch -d k/lm &&
-	git branch k
-'
-
-test_expect_success 'test git branch n after some branch deletion and pruning' '
-	git branch n/o &&
-	git branch n/op &&
-	git branch -d n/o &&
-	git branch n/o/p &&
-	git branch -d n/op &&
-	git pack-refs --all --prune &&
-	git branch -d n/o/p &&
-	git branch n
-'
-
-test_expect_success 'test excluded refs are not packed' '
-	git branch dont_pack1 &&
-	git branch dont_pack2 &&
-	git branch pack_this &&
-	git pack-refs --all --exclude "refs/heads/dont_pack*" &&
-	test -f .git/refs/heads/dont_pack1 &&
-	test -f .git/refs/heads/dont_pack2 &&
-	! test -f .git/refs/heads/pack_this'
-
-test_expect_success 'test --no-exclude refs clears excluded refs' '
-	git branch dont_pack3 &&
-	git branch dont_pack4 &&
-	git pack-refs --all --exclude "refs/heads/dont_pack*" --no-exclude &&
-	! test -f .git/refs/heads/dont_pack3 &&
-	! test -f .git/refs/heads/dont_pack4'
-
-test_expect_success 'test only included refs are packed' '
-	git branch pack_this1 &&
-	git branch pack_this2 &&
-	git tag dont_pack5 &&
-	git pack-refs --include "refs/heads/pack_this*" &&
-	test -f .git/refs/tags/dont_pack5 &&
-	! test -f .git/refs/heads/pack_this1 &&
-	! test -f .git/refs/heads/pack_this2'
-
-test_expect_success 'test --no-include refs clears included refs' '
-	git branch pack1 &&
-	git branch pack2 &&
-	git pack-refs --include "refs/heads/pack*" --no-include &&
-	test -f .git/refs/heads/pack1 &&
-	test -f .git/refs/heads/pack2'
-
-test_expect_success 'test --exclude takes precedence over --include' '
-	git branch dont_pack5 &&
-	git pack-refs --include "refs/heads/pack*" --exclude "refs/heads/pack*" &&
-	test -f .git/refs/heads/dont_pack5'
-
-test_expect_success 'see if up-to-date packed refs are preserved' '
-	git branch q &&
-	git pack-refs --all --prune &&
-	git update-ref refs/heads/q refs/heads/q &&
-	! test -f .git/refs/heads/q
-'
-
-test_expect_success 'pack, prune and repack' '
-	git tag foo &&
-	git pack-refs --all --prune &&
-	git show-ref >all-of-them &&
-	git pack-refs &&
-	git show-ref >again &&
-	test_cmp all-of-them again
-'
-
-test_expect_success 'explicit pack-refs with dangling packed reference' '
-	git commit --allow-empty -m "soon to be garbage-collected" &&
-	git pack-refs --all &&
-	git reset --hard HEAD^ &&
-	git reflog expire --expire=all --all &&
-	git prune --expire=all &&
-	git pack-refs --all 2>result &&
-	test_must_be_empty result
-'
-
-test_expect_success 'delete ref with dangling packed version' '
-	git checkout -b lamb &&
-	git commit --allow-empty -m "future garbage" &&
-	git pack-refs --all &&
-	git reset --hard HEAD^ &&
-	git checkout main &&
-	git reflog expire --expire=all --all &&
-	git prune --expire=all &&
-	git branch -d lamb 2>result &&
-	test_must_be_empty result
-'
-
-test_expect_success 'delete ref while another dangling packed ref' '
-	git branch lamb &&
-	git commit --allow-empty -m "future garbage" &&
-	git pack-refs --all &&
-	git reset --hard HEAD^ &&
-	git reflog expire --expire=all --all &&
-	git prune --expire=all &&
-	git branch -d lamb 2>result &&
-	test_must_be_empty result
-'
-
-test_expect_success 'pack ref directly below refs/' '
-	git update-ref refs/top HEAD &&
-	git pack-refs --all --prune &&
-	grep refs/top .git/packed-refs &&
-	test_path_is_missing .git/refs/top
-'
-
-test_expect_success 'do not pack ref in refs/bisect' '
-	git update-ref refs/bisect/local HEAD &&
-	git pack-refs --all --prune &&
-	! grep refs/bisect/local .git/packed-refs >/dev/null &&
-	test_path_is_file .git/refs/bisect/local
-'
-
-test_expect_success 'disable reflogs' '
-	git config core.logallrefupdates false &&
-	rm -rf .git/logs
-'
-
-test_expect_success 'create packed foo/bar/baz branch' '
-	git branch foo/bar/baz &&
-	git pack-refs --all --prune &&
-	test_path_is_missing .git/refs/heads/foo/bar/baz &&
-	test_must_fail git reflog exists refs/heads/foo/bar/baz
-'
-
-test_expect_success 'notice d/f conflict with existing directory' '
-	test_must_fail git branch foo &&
-	test_must_fail git branch foo/bar
-'
-
-test_expect_success 'existing directory reports concrete ref' '
-	test_must_fail git branch foo 2>stderr &&
-	test_grep refs/heads/foo/bar/baz stderr
-'
-
-test_expect_success 'notice d/f conflict with existing ref' '
-	test_must_fail git branch foo/bar/baz/extra &&
-	test_must_fail git branch foo/bar/baz/lots/of/extra/components
-'
-
-test_expect_success 'reject packed-refs with unterminated line' '
-	cp .git/packed-refs .git/packed-refs.bak &&
-	test_when_finished "mv .git/packed-refs.bak .git/packed-refs" &&
-	printf "%s" "$HEAD refs/zzzzz" >>.git/packed-refs &&
-	echo "fatal: unterminated line in .git/packed-refs: $HEAD refs/zzzzz" >expected_err &&
-	test_must_fail git for-each-ref >out 2>err &&
-	test_cmp expected_err err
-'
-
-test_expect_success 'reject packed-refs containing junk' '
-	cp .git/packed-refs .git/packed-refs.bak &&
-	test_when_finished "mv .git/packed-refs.bak .git/packed-refs" &&
-	printf "%s\n" "bogus content" >>.git/packed-refs &&
-	echo "fatal: unexpected line in .git/packed-refs: bogus content" >expected_err &&
-	test_must_fail git for-each-ref >out 2>err &&
-	test_cmp expected_err err
-'
-
-test_expect_success 'reject packed-refs with a short SHA-1' '
-	cp .git/packed-refs .git/packed-refs.bak &&
-	test_when_finished "mv .git/packed-refs.bak .git/packed-refs" &&
-	printf "%.7s %s\n" $HEAD refs/zzzzz >>.git/packed-refs &&
-	printf "fatal: unexpected line in .git/packed-refs: %.7s %s\n" $HEAD refs/zzzzz >expected_err &&
-	test_must_fail git for-each-ref >out 2>err &&
-	test_cmp expected_err err
-'
-
-test_expect_success 'timeout if packed-refs.lock exists' '
-	LOCK=.git/packed-refs.lock &&
-	>"$LOCK" &&
-	test_when_finished "rm -f $LOCK" &&
-	test_must_fail git pack-refs --all --prune
-'
-
-test_expect_success 'retry acquiring packed-refs.lock' '
-	LOCK=.git/packed-refs.lock &&
-	>"$LOCK" &&
-	test_when_finished "wait && rm -f $LOCK" &&
-	{
-		( sleep 1 && rm -f $LOCK ) &
-	} &&
-	git -c core.packedrefstimeout=3000 pack-refs --all --prune
-'
-
-test_expect_success SYMLINKS 'pack symlinked packed-refs' '
-	# First make sure that symlinking works when reading:
-	git update-ref refs/heads/lossy refs/heads/main &&
-	git for-each-ref >all-refs-before &&
-	mv .git/packed-refs .git/my-deviant-packed-refs &&
-	ln -s my-deviant-packed-refs .git/packed-refs &&
-	git for-each-ref >all-refs-linked &&
-	test_cmp all-refs-before all-refs-linked &&
-	git pack-refs --all --prune &&
-	git for-each-ref >all-refs-packed &&
-	test_cmp all-refs-before all-refs-packed &&
-	test -h .git/packed-refs &&
-	test "$(test_readlink .git/packed-refs)" = "my-deviant-packed-refs"
-'
-
-# The 'packed-refs' file is stored directly in .git/. This means it is global
-# to the repository, and can only contain refs that are shared across all
-# worktrees.
-test_expect_success 'refs/worktree must not be packed' '
-	test_commit initial &&
-	test_commit wt1 &&
-	test_commit wt2 &&
-	git worktree add wt1 wt1 &&
-	git worktree add wt2 wt2 &&
-	git checkout initial &&
-	git update-ref refs/worktree/foo HEAD &&
-	git -C wt1 update-ref refs/worktree/foo HEAD &&
-	git -C wt2 update-ref refs/worktree/foo HEAD &&
-	git pack-refs --all &&
-	test_path_is_missing .git/refs/tags/wt1 &&
-	test_path_is_file .git/refs/worktree/foo &&
-	test_path_is_file .git/worktrees/wt1/refs/worktree/foo &&
-	test_path_is_file .git/worktrees/wt2/refs/worktree/foo
-'
-
-# we do not want to count on running pack-refs to
-# actually pack it, as it is perfectly reasonable to
-# skip processing a broken ref
-test_expect_success 'create packed-refs file with broken ref' '
-	test_tick && git commit --allow-empty -m one &&
-	recoverable=$(git rev-parse HEAD) &&
-	test_tick && git commit --allow-empty -m two &&
-	missing=$(git rev-parse HEAD) &&
-	rm -f .git/refs/heads/main &&
-	cat >.git/packed-refs <<-EOF &&
-	$missing refs/heads/main
-	$recoverable refs/heads/other
-	EOF
-	echo $missing >expect &&
-	git rev-parse refs/heads/main >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'pack-refs does not silently delete broken packed ref' '
-	git pack-refs --all --prune &&
-	git rev-parse refs/heads/main >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'pack-refs does not drop broken refs during deletion' '
-	git update-ref -d refs/heads/other &&
-	git rev-parse refs/heads/main >actual &&
-	test_cmp expect actual
-'
-
-for command in "git pack-refs --all --auto" "git maintenance run --task=pack-refs --auto"
-do
-	test_expect_success "$command does not repack below 16 refs without packed-refs" '
-		test_when_finished "rm -rf repo" &&
-		git init repo &&
-		(
-			cd repo &&
-			git config set maintenance.auto false &&
-			git commit --allow-empty --message "initial" &&
-
-			# Create 14 additional references, which brings us to
-			# 15 together with the default branch.
-			printf "create refs/heads/loose-%d HEAD\n" $(test_seq 14) >stdin &&
-			git update-ref --stdin <stdin &&
-			test_path_is_missing .git/packed-refs &&
-			git pack-refs --auto --all &&
-			test_path_is_missing .git/packed-refs &&
-
-			# Create the 16th reference, which should cause us to repack.
-			git update-ref refs/heads/loose-15 HEAD &&
-			git pack-refs --auto --all &&
-			test_path_is_file .git/packed-refs
-		)
-	'
-
-	test_expect_success "$command does not repack below 16 refs with small packed-refs" '
-		test_when_finished "rm -rf repo" &&
-		git init repo &&
-		(
-			cd repo &&
-			git config set maintenance.auto false &&
-			git commit --allow-empty --message "initial" &&
-
-			git pack-refs --all &&
-			test_line_count = 2 .git/packed-refs &&
-
-			# Create 15 loose references.
-			printf "create refs/heads/loose-%d HEAD\n" $(test_seq 15) >stdin &&
-			git update-ref --stdin <stdin &&
-			git pack-refs --auto --all &&
-			test_line_count = 2 .git/packed-refs &&
-
-			# Create the 16th loose reference, which should cause us to repack.
-			git update-ref refs/heads/loose-17 HEAD &&
-			git pack-refs --auto --all &&
-			test_line_count = 18 .git/packed-refs
-		)
-	'
-
-	test_expect_success "$command scales with size of packed-refs" '
-		test_when_finished "rm -rf repo" &&
-		git init repo &&
-		(
-			cd repo &&
-			git config set maintenance.auto false &&
-			git commit --allow-empty --message "initial" &&
-
-			# Create 99 packed refs. This should cause the heuristic
-			# to require more than the minimum amount of loose refs.
-			test_seq 99 |
-			while read i
-			do
-				printf "create refs/heads/packed-%d HEAD\n" $i || return 1
-			done >stdin &&
-			git update-ref --stdin <stdin &&
-			git pack-refs --all &&
-			test_line_count = 101 .git/packed-refs &&
-
-			# Create 24 loose refs, which should not yet cause us to repack.
-			printf "create refs/heads/loose-%d HEAD\n" $(test_seq 24) >stdin &&
-			git update-ref --stdin <stdin &&
-			git pack-refs --auto --all &&
-			test_line_count = 101 .git/packed-refs &&
-
-			# Create another handful of refs to cross the border.
-			# Note that we explicitly do not check for strict
-			# boundaries here, as this also depends on the size of
-			# the object hash.
-			printf "create refs/heads/addn-%d HEAD\n" $(test_seq 10) >stdin &&
-			git update-ref --stdin <stdin &&
-			git pack-refs --auto --all &&
-			test_line_count = 135 .git/packed-refs
-		)
-	'
-done
-
-test_done
+. "$TEST_DIRECTORY"/pack-refs-tests.sh
diff --git a/t/t0613-reftable-write-options.sh b/t/t0613-reftable-write-options.sh
index d77e601..e334751 100755
--- a/t/t0613-reftable-write-options.sh
+++ b/t/t0613-reftable-write-options.sh
@@ -11,16 +11,18 @@
 # Block sizes depend on the hash function, so we force SHA1 here.
 GIT_TEST_DEFAULT_HASH=sha1
 export GIT_TEST_DEFAULT_HASH
-# Block sizes also depend on the actual refs we write, so we force "master" to
-# be the default initial branch name.
-GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master
-export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
 . ./test-lib.sh
 
+# Block sizes depend on the actual refs we write, so, for tests
+# that check block size, we force the initial branch name to be "master".
+init_repo () {
+	git init --initial-branch master repo
+}
+
 test_expect_success 'default write options' '
 	test_when_finished "rm -rf repo" &&
-	git init repo &&
+	init_repo &&
 	(
 		cd repo &&
 		test_commit initial &&
@@ -43,7 +45,7 @@
 test_expect_success 'disabled reflog writes no log blocks' '
 	test_config_global core.logAllRefUpdates false &&
 	test_when_finished "rm -rf repo" &&
-	git init repo &&
+	init_repo &&
 	(
 		cd repo &&
 		test_commit initial &&
@@ -62,7 +64,7 @@
 
 test_expect_success 'many refs results in multiple blocks' '
 	test_when_finished "rm -rf repo" &&
-	git init repo &&
+	init_repo &&
 	(
 		cd repo &&
 		test_commit initial &&
@@ -115,7 +117,7 @@
 test_expect_success 'small block size leads to multiple ref blocks' '
 	test_config_global core.logAllRefUpdates false &&
 	test_when_finished "rm -rf repo" &&
-	git init repo &&
+	init_repo &&
 	(
 		cd repo &&
 		test_commit A &&
@@ -172,7 +174,7 @@
 
 test_expect_success 'restart interval at every single record' '
 	test_when_finished "rm -rf repo" &&
-	git init repo &&
+	init_repo &&
 	(
 		cd repo &&
 		test_commit initial &&
@@ -212,7 +214,7 @@
 test_expect_success 'object index gets written by default with ref index' '
 	test_config_global core.logAllRefUpdates false &&
 	test_when_finished "rm -rf repo" &&
-	git init repo &&
+	init_repo &&
 	(
 		cd repo &&
 		test_commit initial &&
@@ -247,7 +249,7 @@
 test_expect_success 'object index can be disabled' '
 	test_config_global core.logAllRefUpdates false &&
 	test_when_finished "rm -rf repo" &&
-	git init repo &&
+	init_repo &&
 	(
 		cd repo &&
 		test_commit initial &&
diff --git a/t/t0614-reftable-fsck.sh b/t/t0614-reftable-fsck.sh
new file mode 100755
index 0000000..85cc47d
--- /dev/null
+++ b/t/t0614-reftable-fsck.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+test_description='Test reftable backend consistency check'
+
+GIT_TEST_DEFAULT_REF_FORMAT=reftable
+export GIT_TEST_DEFAULT_REF_FORMAT
+
+. ./test-lib.sh
+
+test_expect_success "no errors reported on a well formed repository" '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		git commit --allow-empty -m initial &&
+
+		for i in $(test_seq 20)
+		do
+			git update-ref refs/heads/branch-$i HEAD || return 1
+		done &&
+
+		# The repository should end up with multiple tables.
+		test_line_count ">" 1 .git/reftable/tables.list &&
+
+		git refs verify 2>err &&
+		test_must_be_empty err
+	)
+'
+
+for TABLE_NAME in "foo-bar-e4d12d59.ref" \
+	"0x00000000zzzz-0x00000000zzzz-e4d12d59.ref" \
+	"0x000000000001-0x000000000002-e4d12d59.abc" \
+	"0x000000000001-0x000000000002-e4d12d59.refabc"; do
+	test_expect_success "table name $TABLE_NAME should be checked" '
+		test_when_finished "rm -rf repo" &&
+		git init repo &&
+		(
+			cd repo &&
+			git commit --allow-empty -m initial &&
+
+			git refs verify 2>err &&
+			test_must_be_empty err &&
+
+			EXISTING_TABLE=$(head -n1 .git/reftable/tables.list) &&
+			mv ".git/reftable/$EXISTING_TABLE" ".git/reftable/$TABLE_NAME" &&
+			sed "s/${EXISTING_TABLE}/${TABLE_NAME}/g" .git/reftable/tables.list > tables.list &&
+			mv tables.list .git/reftable/tables.list &&
+
+			git refs verify 2>err &&
+			cat >expect <<-EOF &&
+			warning: ${TABLE_NAME}: badReftableTableName: invalid reftable table name
+			EOF
+			test_cmp expect err
+		)
+	'
+done
+
+test_done
diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh
index d810113..b0f691c 100755
--- a/t/t1092-sparse-checkout-compatibility.sh
+++ b/t/t1092-sparse-checkout-compatibility.sh
@@ -1506,6 +1506,8 @@
 	ensure_not_expanded reset --hard &&
 	ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1 &&
 
+	ensure_not_expanded ls-files deep/deeper1 &&
+
 	echo >>sparse-index/README.md &&
 	ensure_not_expanded add -A &&
 	echo >>sparse-index/extra.txt &&
@@ -1607,6 +1609,17 @@
 	test_all_match git describe --dirty
 '
 
+test_expect_success 'ls-files filtering and expansion' '
+	init_repos &&
+
+	# This filtering will hit a sparse directory midway
+	# through the iteration.
+	test_all_match git ls-files deep &&
+
+	# This pathspec will filter the index to only a sparse
+	# directory.
+	test_all_match git ls-files folder1
+'
 
 test_expect_success 'sparse-index is not expanded: describe' '
 	init_repos &&
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index f856821..358d636 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -9,6 +9,7 @@
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
 
 for mode in legacy subcommands
 do
@@ -134,38 +135,39 @@
 	rm -f .git/config
 '
 
-cat > expect << EOF
+test_expect_success 'initial' '
+	cat >expect <<\EOF &&
 [section]
 	penguin = little blue
 EOF
-test_expect_success 'initial' '
 	git config ${mode_set} section.penguin "little blue" &&
 	test_cmp expect .git/config
 '
 
-cat > expect << EOF
+test_expect_success 'mixed case' '
+	cat >expect <<\EOF &&
 [section]
 	penguin = little blue
 	Movie = BadPhysics
 EOF
-test_expect_success 'mixed case' '
 	git config ${mode_set} Section.Movie BadPhysics &&
 	test_cmp expect .git/config
 '
 
-cat > expect << EOF
+test_expect_success 'similar section' '
+	cat >expect <<\EOF &&
 [section]
 	penguin = little blue
 	Movie = BadPhysics
 [Sections]
 	WhatEver = Second
 EOF
-test_expect_success 'similar section' '
 	git config ${mode_set} Sections.WhatEver Second &&
 	test_cmp expect .git/config
 '
 
-cat > expect << EOF
+test_expect_success 'uppercase section' '
+	cat >expect <<\EOF &&
 [section]
 	penguin = little blue
 	Movie = BadPhysics
@@ -173,7 +175,6 @@
 [Sections]
 	WhatEver = Second
 EOF
-test_expect_success 'uppercase section' '
 	git config ${mode_set} SECTION.UPPERCASE true &&
 	test_cmp expect .git/config
 '
@@ -186,7 +187,8 @@
 	git config section.penguin "very blue" !kingpin
 '
 
-cat > expect << EOF
+test_expect_success 'append comments' '
+	cat >expect <<\EOF &&
 [section]
 	Movie = BadPhysics
 	UPPERCASE = true
@@ -198,8 +200,6 @@
 [Sections]
 	WhatEver = Second
 EOF
-
-test_expect_success 'append comments' '
 	git config --replace-all --comment="Pygoscelis papua" section.penguin gentoo &&
 	git config ${mode_set} --comment="find fish" section.disposition peckish &&
 	git config ${mode_set} --comment="#abc" section.foo bar &&
@@ -214,7 +214,9 @@
 	test_must_fail git config ${mode_set} --comment="a${LF}b" section.k v
 '
 
-test_expect_success 'non-match result' 'test_cmp expect .git/config'
+test_expect_success 'non-match result' '
+	test_cmp expect .git/config
+'
 
 test_expect_success 'find mixed-case key by canonical name' '
 	test_cmp_config Second sections.whatever
@@ -265,14 +267,15 @@
 	git config ${mode_unset} beta.baz
 '
 
-cat > expect <<\EOF
-[alpha]
-bar = foo
-[beta]
-foo = bar
-EOF
-
-test_expect_success 'unset with cont. lines is correct' 'test_cmp expect .git/config'
+test_expect_success 'unset with cont. lines is correct' '
+	cat >expect <<-\EOF &&
+	[alpha]
+	bar = foo
+	[beta]
+	foo = bar
+	EOF
+	test_cmp expect .git/config
+'
 
 cat > .git/config << EOF
 [beta] ; silly comment # another comment
@@ -292,16 +295,15 @@
 	git config ${mode_unset_all} beta.haha
 '
 
-cat > expect << EOF
+test_expect_success 'multiple unset is correct' '
+	cat >expect <<EOF &&
 [beta] ; silly comment # another comment
-noIndent= sillyValue ; 'nother silly comment
+noIndent= sillyValue ; ${SQ}nother silly comment
 
 # empty line
 		; comment
 [nextSection] noNewline = ouch
 EOF
-
-test_expect_success 'multiple unset is correct' '
 	test_cmp expect .git/config
 '
 
@@ -318,37 +320,37 @@
 	git config ${mode_replace_all} beta.haha gamma
 '
 
-cat > expect << EOF
+test_expect_success 'all replaced' '
+	cat >expect <<EOF &&
 [beta] ; silly comment # another comment
-noIndent= sillyValue ; 'nother silly comment
+noIndent= sillyValue ; ${SQ}nother silly comment
 
 # empty line
 		; comment
 	haha = gamma
 [nextSection] noNewline = ouch
 EOF
-
-test_expect_success 'all replaced' '
 	test_cmp expect .git/config
 '
 
-cat > expect << EOF
+test_expect_success 'really mean test' '
+	cat >expect <<EOF &&
 [beta] ; silly comment # another comment
-noIndent= sillyValue ; 'nother silly comment
+noIndent= sillyValue ; ${SQ}nother silly comment
 
 # empty line
 		; comment
 	haha = alpha
 [nextSection] noNewline = ouch
 EOF
-test_expect_success 'really mean test' '
 	git config ${mode_set} beta.haha alpha &&
 	test_cmp expect .git/config
 '
 
-cat > expect << EOF
+test_expect_success 'really really mean test' '
+	cat >expect <<EOF &&
 [beta] ; silly comment # another comment
-noIndent= sillyValue ; 'nother silly comment
+noIndent= sillyValue ; ${SQ}nother silly comment
 
 # empty line
 		; comment
@@ -356,7 +358,6 @@
 [nextSection]
 	nonewline = wow
 EOF
-test_expect_success 'really really mean test' '
 	git config ${mode_set} nextsection.nonewline wow &&
 	test_cmp expect .git/config
 '
@@ -365,23 +366,24 @@
 	test_cmp_config alpha beta.haha
 '
 
-cat > expect << EOF
+test_expect_success 'unset' '
+	cat >expect <<EOF &&
 [beta] ; silly comment # another comment
-noIndent= sillyValue ; 'nother silly comment
+noIndent= sillyValue ; ${SQ}nother silly comment
 
 # empty line
 		; comment
 [nextSection]
 	nonewline = wow
 EOF
-test_expect_success 'unset' '
 	git config ${mode_unset} beta.haha &&
 	test_cmp expect .git/config
 '
 
-cat > expect << EOF
+test_expect_success 'multivar' '
+	cat  >expect <<EOF &&
 [beta] ; silly comment # another comment
-noIndent= sillyValue ; 'nother silly comment
+noIndent= sillyValue ; ${SQ}nother silly comment
 
 # empty line
 		; comment
@@ -389,7 +391,6 @@
 	nonewline = wow
 	NoNewLine = wow2 for me
 EOF
-test_expect_success 'multivar' '
 	git config nextsection.NoNewLine "wow2 for me" "for me$" &&
 	test_cmp expect .git/config
 '
@@ -415,9 +416,10 @@
 	test_cmp expect actual
 '
 
-cat > expect << EOF
+test_expect_success 'multivar replace' '
+	cat >expect <<EOF &&
 [beta] ; silly comment # another comment
-noIndent= sillyValue ; 'nother silly comment
+noIndent= sillyValue ; ${SQ}nother silly comment
 
 # empty line
 		; comment
@@ -425,7 +427,6 @@
 	nonewline = wow3
 	NoNewLine = wow2 for me
 EOF
-test_expect_success 'multivar replace' '
 	git config nextsection.nonewline "wow3" "wow$" &&
 	test_cmp expect .git/config
 '
@@ -438,17 +439,16 @@
 	test_must_fail git config ${mode_unset} somesection.nonewline
 '
 
-cat > expect << EOF
+test_expect_success 'multivar unset' '
+	cat >expect <<EOF &&
 [beta] ; silly comment # another comment
-noIndent= sillyValue ; 'nother silly comment
+noIndent= sillyValue ; ${SQ}nother silly comment
 
 # empty line
 		; comment
 [nextSection]
 	NoNewLine = wow2 for me
 EOF
-
-test_expect_success 'multivar unset' '
 	case "$mode" in
 	legacy)
 		git config --unset nextsection.nonewline "wow3$";;
@@ -458,17 +458,22 @@
 	test_cmp expect .git/config
 '
 
-test_expect_success 'invalid key' 'test_must_fail git config inval.2key blabla'
+test_expect_success 'invalid key' '
+	test_must_fail git config inval.2key blabla
+'
 
-test_expect_success 'correct key' 'git config 123456.a123 987'
+test_expect_success 'correct key' '
+	git config 123456.a123 987
+'
 
 test_expect_success 'hierarchical section' '
 	git config Version.1.2.3eX.Alpha beta
 '
 
-cat > expect << EOF
+test_expect_success 'hierarchical section value' '
+	cat >expect <<EOF &&
 [beta] ; silly comment # another comment
-noIndent= sillyValue ; 'nother silly comment
+noIndent= sillyValue ; ${SQ}nother silly comment
 
 # empty line
 		; comment
@@ -479,65 +484,59 @@
 [Version "1.2.3eX"]
 	Alpha = beta
 EOF
-
-test_expect_success 'hierarchical section value' '
 	test_cmp expect .git/config
 '
 
-cat > expect << EOF
-beta.noindent=sillyValue
-nextsection.nonewline=wow2 for me
-123456.a123=987
-version.1.2.3eX.alpha=beta
-EOF
-
 test_expect_success 'working --list' '
+	cat >expect <<-\EOF &&
+	beta.noindent=sillyValue
+	nextsection.nonewline=wow2 for me
+	123456.a123=987
+	version.1.2.3eX.alpha=beta
+	EOF
 	git config ${mode_prefix}list > output &&
 	test_cmp expect output
 '
+
 test_expect_success '--list without repo produces empty output' '
 	git --git-dir=nonexistent config ${mode_prefix}list >output &&
 	test_must_be_empty output
 '
 
-cat > expect << EOF
-beta.noindent
-nextsection.nonewline
-123456.a123
-version.1.2.3eX.alpha
-EOF
-
 test_expect_success '--name-only --list' '
+	cat >expect <<-\EOF &&
+	beta.noindent
+	nextsection.nonewline
+	123456.a123
+	version.1.2.3eX.alpha
+	EOF
 	git config ${mode_prefix}list --name-only >output &&
 	test_cmp expect output
 '
 
-cat > expect << EOF
-beta.noindent sillyValue
-nextsection.nonewline wow2 for me
-EOF
-
 test_expect_success '--get-regexp' '
+	cat >expect <<-\EOF &&
+	beta.noindent sillyValue
+	nextsection.nonewline wow2 for me
+	EOF
 	git config ${mode_get_regexp} in >output &&
 	test_cmp expect output
 '
 
-cat > expect << EOF
-beta.noindent
-nextsection.nonewline
-EOF
-
 test_expect_success '--name-only --get-regexp' '
+	cat >expect <<-\EOF &&
+	beta.noindent
+	nextsection.nonewline
+	EOF
 	git config ${mode_get_regexp} --name-only in >output &&
 	test_cmp expect output
 '
 
-cat > expect << EOF
-wow2 for me
-wow4 for you
-EOF
-
 test_expect_success '--add' '
+	cat >expect <<-\EOF &&
+	wow2 for me
+	wow4 for you
+	EOF
 	git config --add nextsection.nonewline "wow4 for you" &&
 	git config ${mode_get_all} nextsection.nonewline > output &&
 	test_cmp expect output
@@ -558,37 +557,32 @@
 	git config --get emptyvalue.variable ^$
 '
 
-echo novalue.variable > expect
-
 test_expect_success 'get-regexp variable with no value' '
+	echo novalue.variable >expect &&
 	git config ${mode_get_regexp} novalue > output &&
 	test_cmp expect output
 '
 
-echo 'novalue.variable true' > expect
-
 test_expect_success 'get-regexp --bool variable with no value' '
+	echo "novalue.variable true" >expect &&
 	git config ${mode_get_regexp} --bool novalue > output &&
 	test_cmp expect output
 '
 
-echo 'emptyvalue.variable ' > expect
-
 test_expect_success 'get-regexp variable with empty value' '
+	echo "emptyvalue.variable " >expect &&
 	git config ${mode_get_regexp} emptyvalue > output &&
 	test_cmp expect output
 '
 
-echo true > expect
-
 test_expect_success 'get bool variable with no value' '
+	echo true >expect &&
 	git config --bool novalue.variable > output &&
 	test_cmp expect output
 '
 
-echo false > expect
-
 test_expect_success 'get bool variable with empty value' '
+	echo false >expect &&
 	git config --bool emptyvalue.variable > output &&
 	test_cmp expect output
 '
@@ -604,19 +598,19 @@
 	c = d
 EOF
 
-cat > expect << EOF
+test_expect_success 'new section is partial match of another' '
+	cat >expect <<\EOF &&
 [a.b]
 	c = d
 [a]
 	x = y
 EOF
-
-test_expect_success 'new section is partial match of another' '
 	git config a.x y &&
 	test_cmp expect .git/config
 '
 
-cat > expect << EOF
+test_expect_success 'new variable inserts into proper section' '
+	cat >expect <<\EOF &&
 [a.b]
 	c = d
 [a]
@@ -625,8 +619,6 @@
 [b]
 	x = y
 EOF
-
-test_expect_success 'new variable inserts into proper section' '
 	git config b.x y &&
 	git config a.b c &&
 	test_cmp expect .git/config
@@ -642,11 +634,10 @@
 	bahn = strasse
 EOF
 
-cat > expect << EOF
-ein.bahn=strasse
-EOF
-
 test_expect_success 'alternative GIT_CONFIG' '
+	cat >expect <<-\EOF &&
+	ein.bahn=strasse
+	EOF
 	GIT_CONFIG=other-config git config ${mode_prefix}list >output &&
 	test_cmp expect output
 '
@@ -675,14 +666,13 @@
 	test_cmp_config -C x strasse --file=../other-config --get ein.bahn
 '
 
-cat > expect << EOF
+test_expect_success '--set in alternative file' '
+	cat >expect <<\EOF &&
 [ein]
 	bahn = strasse
 [anwohner]
 	park = ausweis
 EOF
-
-test_expect_success '--set in alternative file' '
 	git config --file=other-config anwohner.park ausweis &&
 	test_cmp expect other-config
 '
@@ -730,7 +720,8 @@
 	git config ${mode_prefix}rename-section branch."1 234 blabl/a" branch.drei
 '
 
-cat > expect << EOF
+test_expect_success 'rename succeeded' '
+	cat >expect <<\EOF &&
 # Hallo
 	#Bello
 [branch "zwei"]
@@ -740,8 +731,6 @@
 [branch "drei"]
 weird
 EOF
-
-test_expect_success 'rename succeeded' '
 	test_cmp expect .git/config
 '
 
@@ -753,7 +742,8 @@
 	git config ${mode_prefix}rename-section branch.vier branch.zwei
 '
 
-cat > expect << EOF
+test_expect_success 'rename succeeded' '
+	cat >expect <<\EOF &&
 # Hallo
 	#Bello
 [branch "zwei"]
@@ -765,8 +755,6 @@
 [branch "zwei"]
 	z = 1
 EOF
-
-test_expect_success 'rename succeeded' '
 	test_cmp expect .git/config
 '
 
@@ -816,32 +804,29 @@
 	git config ${mode_prefix}remove-section branch.zwei
 '
 
-cat > expect << EOF
+test_expect_success 'section was removed properly' '
+	cat >expect <<\EOF &&
 # Hallo
 	#Bello
 [branch "drei"]
 weird
 EOF
-
-test_expect_success 'section was removed properly' '
 	test_cmp expect .git/config
 '
 
-cat > expect << EOF
+test_expect_success 'section ending' '
+	cat >expect <<\EOF &&
 [gitcvs]
 	enabled = true
 	dbname = %Ggitcvs2.%a.%m.sqlite
 [gitcvs "ext"]
 	dbname = %Ggitcvs1.%a.%m.sqlite
 EOF
-
-test_expect_success 'section ending' '
 	rm -f .git/config &&
 	git config ${mode_set} gitcvs.enabled true &&
 	git config ${mode_set} gitcvs.ext.dbname %Ggitcvs1.%a.%m.sqlite &&
 	git config ${mode_set} gitcvs.dbname %Ggitcvs2.%a.%m.sqlite &&
 	test_cmp expect .git/config
-
 '
 
 test_expect_success numbers '
@@ -885,19 +870,17 @@
 	test_grep "bad config line 1 in standard input" output
 '
 
-cat > expect << EOF
-true
-false
-true
-false
-true
-false
-true
-false
-EOF
-
 test_expect_success bool '
-
+	cat >expect <<-\EOF &&
+	true
+	false
+	true
+	false
+	true
+	false
+	true
+	false
+	EOF
 	git config ${mode_set} bool.true1 01 &&
 	git config ${mode_set} bool.true2 -1 &&
 	git config ${mode_set} bool.true3 YeS &&
@@ -912,18 +895,20 @@
 	    git config --bool --get bool.true$i >>result &&
 	    git config --bool --get bool.false$i >>result || return 1
 	done &&
-	test_cmp expect result'
+	test_cmp expect result
+'
 
 test_expect_success 'invalid bool (--get)' '
-
 	git config ${mode_set} bool.nobool foobar &&
-	test_must_fail git config --bool --get bool.nobool'
+	test_must_fail git config --bool --get bool.nobool
+'
 
 test_expect_success 'invalid bool (set)' '
+	test_must_fail git config --bool bool.nobool foobar
+'
 
-	test_must_fail git config --bool bool.nobool foobar'
-
-cat > expect <<\EOF
+test_expect_success 'set --bool' '
+	cat >expect <<\EOF &&
 [bool]
 	true1 = true
 	true2 = true
@@ -934,9 +919,6 @@
 	false3 = false
 	false4 = false
 EOF
-
-test_expect_success 'set --bool' '
-
 	rm -f .git/config &&
 	git config --bool bool.true1 01 &&
 	git config --bool bool.true2 -1 &&
@@ -948,15 +930,13 @@
 	git config --bool bool.false4 FALSE &&
 	test_cmp expect .git/config'
 
-cat > expect <<\EOF
+test_expect_success 'set --int' '
+	cat >expect <<\EOF &&
 [int]
 	val1 = 1
 	val2 = -1
 	val3 = 5242880
 EOF
-
-test_expect_success 'set --int' '
-
 	rm -f .git/config &&
 	git config --int int.val1 01 &&
 	git config --int int.val2 -1 &&
@@ -994,7 +974,8 @@
 	test_cmp expect actual
 '
 
-cat >expect <<\EOF
+test_expect_success 'set --bool-or-int' '
+	cat >expect <<\EOF &&
 [bool]
 	true1 = true
 	false1 = false
@@ -1005,8 +986,6 @@
 	int2 = 1
 	int3 = -1
 EOF
-
-test_expect_success 'set --bool-or-int' '
 	rm -f .git/config &&
 	git config --bool-or-int bool.true1 true &&
 	git config --bool-or-int bool.false1 false &&
@@ -1018,44 +997,42 @@
 	test_cmp expect .git/config
 '
 
-cat >expect <<\EOF
+test_expect_success !MINGW 'set --path' '
+	cat >expect <<\EOF &&
 [path]
 	home = ~/
 	normal = /dev/null
 	trailingtilde = foo~
 EOF
-
-test_expect_success !MINGW 'set --path' '
 	rm -f .git/config &&
 	git config --path path.home "~/" &&
 	git config --path path.normal "/dev/null" &&
 	git config --path path.trailingtilde "foo~" &&
-	test_cmp expect .git/config'
+	test_cmp expect .git/config
+'
 
 if test_have_prereq !MINGW && test "${HOME+set}"
 then
 	test_set_prereq HOMEVAR
 fi
 
-cat >expect <<EOF
-$HOME/
-/dev/null
-foo~
-EOF
-
 test_expect_success HOMEVAR 'get --path' '
+	cat >expect <<-EOF &&
+	$HOME/
+	/dev/null
+	foo~
+	EOF
 	git config --get --path path.home > result &&
 	git config --get --path path.normal >> result &&
 	git config --get --path path.trailingtilde >> result &&
 	test_cmp expect result
 '
 
-cat >expect <<\EOF
-/dev/null
-foo~
-EOF
-
 test_expect_success !MINGW 'get --path copes with unset $HOME' '
+	cat >expect <<-\EOF &&
+	/dev/null
+	foo~
+	EOF
 	(
 		sane_unset HOME &&
 		test_must_fail git config --get --path path.home \
@@ -1107,17 +1084,35 @@
 	rm .git/config &&
 	git config ${mode_set} foo.color "red" &&
 	git config --get --type=color foo.color >actual.raw &&
+	git config get --type=color foo.color >actual-subcommand.raw &&
+	test_cmp actual.raw actual-subcommand.raw &&
 	test_decode_color <actual.raw >actual &&
 	echo "<RED>" >expect &&
 	test_cmp expect actual
 '
 
-cat >expect << EOF
+test_expect_success 'get --type=color with default value only' '
+	git config --get-color "" "red" >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	echo "<RED>" >expect &&
+	test_cmp expect actual &&
+	git config get --type=color --default="red" "" >actual-subcommand.raw &&
+	test_cmp actual.raw actual-subcommand.raw
+'
+
+test_expect_success TTY 'get --type=color does not use a pager' '
+	test_config core.pager "echo foobar" &&
+	test_terminal git config get --type=color --default="red" "" >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	echo "<RED>" >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'set --type=color' '
+	cat >expect <<\EOF &&
 [foo]
 	color = red
 EOF
-
-test_expect_success 'set --type=color' '
 	rm .git/config &&
 	git config --type=color foo.color "red" &&
 	test_cmp expect .git/config
@@ -1133,14 +1128,14 @@
 	test_grep "cannot parse color" error
 '
 
-cat > expect << EOF
+test_expect_success 'quoting' '
+	cat >expect <<\EOF &&
 [quote]
 	leading = " test"
 	ending = "test "
 	semicolon = "test;test"
 	hash = "test#test"
 EOF
-test_expect_success 'quoting' '
 	rm -f .git/config &&
 	git config ${mode_set} quote.leading " test" &&
 	git config ${mode_set} quote.ending "test " &&
@@ -1151,10 +1146,13 @@
 
 test_expect_success 'key with newline' '
 	test_must_fail git config ${mode_get} "key.with
-newline" 123'
+newline" 123
+'
 
-test_expect_success 'value with newline' 'git config ${mode_set} key.sub value.with\\\
-newline'
+test_expect_success 'value with newline' '
+	git config ${mode_set} key.sub value.with\\\
+newline
+'
 
 cat > .git/config <<\EOF
 [section]
@@ -1166,13 +1164,12 @@
 inued"
 EOF
 
-cat > expect <<\EOF
-section.continued=continued
-section.noncont=not continued
-section.quotecont=cont;inued
-EOF
-
 test_expect_success 'value continued on next line' '
+	cat >expect <<-\EOF &&
+	section.continued=continued
+	section.noncont=not continued
+	section.quotecont=cont;inued
+	EOF
 	git config ${mode_prefix}list > result &&
 	test_cmp expect result
 '
@@ -1365,7 +1362,6 @@
 '
 
 test_expect_success 'last one wins: two level vars' '
-
 	# sec.var and sec.VAR are the same variable, as the first
 	# and the last level of a configuration variable name is
 	# case insensitive.
@@ -1384,7 +1380,6 @@
 '
 
 test_expect_success 'last one wins: three level vars' '
-
 	# v.a.r and v.A.r are not the same variable, as the middle
 	# level of a three-level configuration variable name is
 	# case sensitive.
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index 96648a6..db7f544 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -2294,6 +2294,59 @@
 		)
 	'
 
+	test_expect_success CASE_INSENSITIVE_FS,REFFILES "stdin $type batch-updates existing reference" '
+		git init repo &&
+		test_when_finished "rm -fr repo" &&
+		(
+			cd repo &&
+			test_commit one &&
+			old_head=$(git rev-parse HEAD) &&
+			test_commit two &&
+			head=$(git rev-parse HEAD) &&
+
+			{
+				format_command $type "create refs/heads/foo" "$head" &&
+				format_command $type "create refs/heads/ref" "$old_head" &&
+				format_command $type "create refs/heads/Foo" "$old_head"
+			} >stdin &&
+			git update-ref $type --stdin --batch-updates <stdin >stdout &&
+
+			echo $head >expect &&
+			git rev-parse refs/heads/foo >actual &&
+			echo $old_head >expect &&
+			git rev-parse refs/heads/ref >actual &&
+			test_cmp expect actual &&
+			test_grep -q "reference conflict due to case-insensitive filesystem" stdout
+		)
+	'
+
+	test_expect_success CASE_INSENSITIVE_FS "stdin $type batch-updates existing reference" '
+		git init --ref-format=reftable repo &&
+		test_when_finished "rm -fr repo" &&
+		(
+			cd repo &&
+			test_commit one &&
+			old_head=$(git rev-parse HEAD) &&
+			test_commit two &&
+			head=$(git rev-parse HEAD) &&
+
+			{
+				format_command $type "create refs/heads/foo" "$head" &&
+				format_command $type "create refs/heads/ref" "$old_head" &&
+				format_command $type "create refs/heads/Foo" "$old_head"
+			} >stdin &&
+			git update-ref $type --stdin --batch-updates <stdin >stdout &&
+
+			echo $head >expect &&
+			git rev-parse refs/heads/foo >actual &&
+			echo $old_head >expect &&
+			git rev-parse refs/heads/ref >actual &&
+			test_cmp expect actual &&
+			git rev-parse refs/heads/Foo >actual &&
+			test_cmp expect actual
+		)
+	'
+
 	test_expect_success "stdin $type batch-updates delete incorrect symbolic ref" '
 		git init repo &&
 		test_when_finished "rm -fr repo" &&
@@ -2368,4 +2421,25 @@
 	test_path_is_missing .git/refs/heads/nested
 '
 
+test_expect_success 'dangling symref not overwritten by creation' '
+	test_when_finished "git update-ref -d refs/heads/dangling" &&
+	git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
+	test_must_fail git update-ref --no-deref --stdin 2>err <<-\EOF &&
+	create refs/heads/dangling HEAD
+	EOF
+	test_grep "cannot lock.*dangling symref already exists" err &&
+	test_must_fail git rev-parse --verify refs/heads/dangling &&
+	test_must_fail git rev-parse --verify refs/heads/does-not-exist
+'
+
+test_expect_success 'dangling symref overwritten without old oid' '
+	test_when_finished "git update-ref -d refs/heads/dangling" &&
+	git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
+	git update-ref --no-deref --stdin <<-\EOF &&
+	update refs/heads/dangling HEAD
+	EOF
+	git rev-parse --verify refs/heads/dangling &&
+	test_must_fail git rev-parse --verify refs/heads/does-not-exist
+'
+
 test_done
diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
index 9da3650..36c903c 100755
--- a/t/t1403-show-ref.sh
+++ b/t/t1403-show-ref.sh
@@ -228,69 +228,4 @@
 	grep "cannot be used together" err
 '
 
-test_expect_success '--exists with existing reference' '
-	git show-ref --exists refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
-'
-
-test_expect_success '--exists with missing reference' '
-	test_expect_code 2 git show-ref --exists refs/heads/does-not-exist
-'
-
-test_expect_success '--exists does not use DWIM' '
-	test_expect_code 2 git show-ref --exists $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 2>err &&
-	grep "reference does not exist" err
-'
-
-test_expect_success '--exists with HEAD' '
-	git show-ref --exists HEAD
-'
-
-test_expect_success '--exists with bad reference name' '
-	test_when_finished "git update-ref -d refs/heads/bad...name" &&
-	new_oid=$(git rev-parse HEAD) &&
-	test-tool ref-store main update-ref msg refs/heads/bad...name $new_oid $ZERO_OID REF_SKIP_REFNAME_VERIFICATION &&
-	git show-ref --exists refs/heads/bad...name
-'
-
-test_expect_success '--exists with arbitrary symref' '
-	test_when_finished "git symbolic-ref -d refs/symref" &&
-	git symbolic-ref refs/symref refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
-	git show-ref --exists refs/symref
-'
-
-test_expect_success '--exists with dangling symref' '
-	test_when_finished "git symbolic-ref -d refs/heads/dangling" &&
-	git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
-	git show-ref --exists refs/heads/dangling
-'
-
-test_expect_success '--exists with nonexistent object ID' '
-	test-tool ref-store main update-ref msg refs/heads/missing-oid $(test_oid 001) $ZERO_OID REF_SKIP_OID_VERIFICATION &&
-	git show-ref --exists refs/heads/missing-oid
-'
-
-test_expect_success '--exists with non-commit object' '
-	tree_oid=$(git rev-parse HEAD^{tree}) &&
-	test-tool ref-store main update-ref msg refs/heads/tree ${tree_oid} $ZERO_OID REF_SKIP_OID_VERIFICATION &&
-	git show-ref --exists refs/heads/tree
-'
-
-test_expect_success '--exists with directory fails with generic error' '
-	cat >expect <<-EOF &&
-	error: reference does not exist
-	EOF
-	test_expect_code 2 git show-ref --exists refs/heads 2>err &&
-	test_cmp expect err
-'
-
-test_expect_success '--exists with non-existent special ref' '
-	test_expect_code 2 git show-ref --exists FETCH_HEAD
-'
-
-test_expect_success '--exists with existing special ref' '
-	test_when_finished "rm .git/FETCH_HEAD" &&
-	git rev-parse HEAD >.git/FETCH_HEAD &&
-	git show-ref --exists FETCH_HEAD
-'
-
 test_done
diff --git a/t/t1421-reflog-write.sh b/t/t1421-reflog-write.sh
new file mode 100755
index 0000000..603ec3f
--- /dev/null
+++ b/t/t1421-reflog-write.sh
@@ -0,0 +1,162 @@
+#!/bin/sh
+
+test_description='Manually write reflog entries'
+
+. ./test-lib.sh
+
+SIGNATURE="C O Mitter <committer@example.com> 1112911993 -0700"
+
+test_reflog_matches () {
+	repo="$1" &&
+	refname="$2" &&
+	cat >actual &&
+	test-tool -C "$repo" ref-store main for-each-reflog-ent "$refname" >expected &&
+	test_cmp expected actual
+}
+
+test_expect_success 'invalid number of arguments' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		for args in "" "1" "1 2" "1 2 3" "1 2 3 4 5"
+		do
+			test_must_fail git reflog write $args 2>err &&
+			test_grep "usage: git reflog write" err || return 1
+		done
+	)
+'
+
+test_expect_success 'invalid refname' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_must_fail git reflog write "refs/heads/ invalid" $ZERO_OID $ZERO_OID first 2>err &&
+		test_grep "invalid reference name: " err
+	)
+'
+
+test_expect_success 'unqualified refname is rejected' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_must_fail git reflog write unqualified $ZERO_OID $ZERO_OID first 2>err &&
+		test_grep "invalid reference name: " err
+	)
+'
+
+test_expect_success 'nonexistent object IDs' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_must_fail git reflog write refs/heads/something $(test_oid deadbeef) $ZERO_OID old-object-id 2>err &&
+		test_grep "old object .* does not exist" err &&
+		test_must_fail git reflog write refs/heads/something $ZERO_OID $(test_oid deadbeef) new-object-id 2>err &&
+		test_grep "new object .* does not exist" err
+	)
+'
+
+test_expect_success 'abbreviated object IDs' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit initial &&
+		abbreviated_oid=$(git rev-parse HEAD | test_copy_bytes 8) &&
+		test_must_fail git reflog write refs/heads/something $abbreviated_oid $ZERO_OID old-object-id 2>err &&
+		test_grep "invalid old object ID" err &&
+		test_must_fail git reflog write refs/heads/something $ZERO_OID $abbreviated_oid new-object-id 2>err &&
+		test_grep "invalid new object ID" err
+	)
+'
+
+test_expect_success 'reflog message gets normalized' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit initial &&
+		COMMIT_OID=$(git rev-parse HEAD) &&
+		git reflog write HEAD $COMMIT_OID $COMMIT_OID "$(printf "message\nwith\nnewlines")" &&
+		git reflog show -1 --format=%gs HEAD >actual &&
+		echo "message with newlines" >expected &&
+		test_cmp expected actual
+	)
+'
+
+test_expect_success 'simple writes' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit initial &&
+		COMMIT_OID=$(git rev-parse HEAD) &&
+
+		git reflog write refs/heads/something $ZERO_OID $COMMIT_OID first &&
+		test_reflog_matches . refs/heads/something <<-EOF &&
+		$ZERO_OID $COMMIT_OID $SIGNATURE	first
+		EOF
+
+		git reflog write refs/heads/something $COMMIT_OID $COMMIT_OID second &&
+		test_reflog_matches . refs/heads/something <<-EOF
+		$ZERO_OID $COMMIT_OID $SIGNATURE	first
+		$COMMIT_OID $COMMIT_OID $SIGNATURE	second
+		EOF
+	)
+'
+
+test_expect_success 'uses user.name and user.email config' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit initial &&
+		COMMIT_OID=$(git rev-parse HEAD) &&
+
+		sane_unset GIT_COMMITTER_NAME &&
+		sane_unset GIT_COMMITTER_EMAIL &&
+		git config --local user.name "Author" &&
+		git config --local user.email "a@uth.or" &&
+		git reflog write refs/heads/something $ZERO_OID $COMMIT_OID first &&
+		test_reflog_matches . refs/heads/something <<-EOF
+		$ZERO_OID $COMMIT_OID Author <a@uth.or> $GIT_COMMITTER_DATE	first
+		EOF
+	)
+'
+
+test_expect_success 'environment variables take precedence over config' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit initial &&
+		COMMIT_OID=$(git rev-parse HEAD) &&
+
+		git config --local user.name "Author" &&
+		git config --local user.email "a@uth.or" &&
+		git reflog write refs/heads/something $ZERO_OID $COMMIT_OID first &&
+		test_reflog_matches . refs/heads/something <<-EOF
+		$ZERO_OID $COMMIT_OID $SIGNATURE	first
+		EOF
+	)
+'
+
+test_expect_success 'can write to root ref' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit initial &&
+		COMMIT_OID=$(git rev-parse HEAD) &&
+
+		git reflog write ROOT_REF_HEAD $ZERO_OID $COMMIT_OID first &&
+		test_reflog_matches . ROOT_REF_HEAD <<-EOF
+		$ZERO_OID $COMMIT_OID $SIGNATURE	first
+		EOF
+	)
+'
+
+test_done
diff --git a/t/t1422-show-ref-exists.sh b/t/t1422-show-ref-exists.sh
new file mode 100755
index 0000000..fdca3f1
--- /dev/null
+++ b/t/t1422-show-ref-exists.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+test_description='show-ref --exists'
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+
+. "$TEST_DIRECTORY"/show-ref-exists-tests.sh
diff --git a/t/t1460-refs-migrate.sh b/t/t1460-refs-migrate.sh
index 2ab97e1..0e1116a 100755
--- a/t/t1460-refs-migrate.sh
+++ b/t/t1460-refs-migrate.sh
@@ -7,6 +7,17 @@
 
 . ./test-lib.sh
 
+print_all_reflog_entries () {
+	repo=$1 &&
+	test-tool -C "$repo" ref-store main for-each-reflog >reflogs &&
+	while read reflog
+	do
+		echo "REFLOG: $reflog" &&
+		test-tool -C "$repo" ref-store main for-each-reflog-ent "$reflog" ||
+		return 1
+	done <reflogs
+}
+
 # Migrate the provided repository from one format to the other and
 # verify that the references and logs are migrated over correctly.
 # Usage: test_migration <repo> <format> [<skip_reflog_verify> [<options...>]]
@@ -28,8 +39,7 @@
 		--format='%(refname) %(objectname) %(symref)' >expect &&
 	if ! $skip_reflog_verify
 	then
-	   git -C "$repo" reflog --all >expect_logs &&
-	   git -C "$repo" reflog list >expect_log_list
+		print_all_reflog_entries "$repo" >expect_logs
 	fi &&
 
 	git -C "$repo" refs migrate --ref-format="$format" "$@" &&
@@ -39,10 +49,8 @@
 	test_cmp expect actual &&
 	if ! $skip_reflog_verify
 	then
-		git -C "$repo" reflog --all >actual_logs &&
-		git -C "$repo" reflog list >actual_log_list &&
-		test_cmp expect_logs actual_logs &&
-		test_cmp expect_log_list actual_log_list
+		print_all_reflog_entries "$repo" >actual_logs &&
+		test_cmp expect_logs actual_logs
 	fi &&
 
 	git -C "$repo" rev-parse --show-ref-format >actual &&
@@ -273,7 +281,7 @@
 	test_commit -C repo second &&
 	printf "update refs/heads/ref-%d HEAD\n" $(test_seq 3000) >stdin &&
 	git -C repo update-ref --stdin <stdin &&
-	test_migration repo reftable
+	test_migration repo reftable true
 '
 
 test_expect_success 'migrating from files format deletes backend files' '
diff --git a/t/t1461-refs-list.sh b/t/t1461-refs-list.sh
new file mode 100755
index 0000000..36e3d81
--- /dev/null
+++ b/t/t1461-refs-list.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+test_description='git refs list tests'
+
+. ./test-lib.sh
+
+git_for_each_ref='git refs list'
+. "$TEST_DIRECTORY"/for-each-ref-tests.sh
diff --git a/t/t1462-refs-exists.sh b/t/t1462-refs-exists.sh
new file mode 100755
index 0000000..349453c
--- /dev/null
+++ b/t/t1462-refs-exists.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+test_description='refs exists'
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+
+git_show_ref_exists='git refs exists'
+. "$TEST_DIRECTORY"/show-ref-exists-tests.sh
diff --git a/t/t1463-refs-optimize.sh b/t/t1463-refs-optimize.sh
new file mode 100755
index 0000000..c11c905
--- /dev/null
+++ b/t/t1463-refs-optimize.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test_description='git refs optimize should not change the branch semantic
+
+This test runs git refs optimize and git show-ref and checks that the branch
+semantic is still the same.
+'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+GIT_TEST_DEFAULT_REF_FORMAT=files
+export GIT_TEST_DEFAULT_REF_FORMAT
+
+. ./test-lib.sh
+
+pack_refs='refs optimize'
+. "$TEST_DIRECTORY"/pack-refs-tests.sh
diff --git a/t/t1517-outside-repo.sh b/t/t1517-outside-repo.sh
index 8f59b86..c824c1a 100755
--- a/t/t1517-outside-repo.sh
+++ b/t/t1517-outside-repo.sh
@@ -107,18 +107,46 @@
 	test_grep "^error: remote-curl" actual
 '
 
-test_expect_success 'update-server-info does not crash with -h' '
-	test_expect_code 129 git update-server-info -h >usage &&
-	test_grep "[Uu]sage: git update-server-info " usage &&
-	test_expect_code 129 nongit git update-server-info -h >usage &&
-	test_grep "[Uu]sage: git update-server-info " usage
-'
+for cmd in $(git --list-cmds=main)
+do
+	cmd=${cmd%.*} # strip .sh, .perl, etc.
+	case "$cmd" in
+	archimport | citool | credential-netrc | credential-libsecret | \
+	credential-osxkeychain | cvsexportcommit | cvsimport | cvsserver | \
+	daemon | \
+	difftool--helper | filter-branch | fsck-objects | get-tar-commit-id | \
+	gui | gui--askpass | \
+	http-backend | http-fetch | http-push | init-db | \
+	merge-octopus | merge-one-file | merge-resolve | mergetool | \
+	mktag | p4 | p4.py | pickaxe | remote-ftp | remote-ftps | \
+	remote-http | remote-https | replay | send-email | \
+	sh-i18n--envsubst | shell | show | stage | submodule | svn | \
+	upload-archive--writer | upload-pack | web--browse | whatchanged)
+		expect_outcome=expect_failure ;;
+	*)
+		expect_outcome=expect_success ;;
+	esac
+	case "$cmd" in
+	instaweb)
+		prereq=PERL ;;
+	*)
+		prereq= ;;
+	esac
+	test_$expect_outcome $prereq "'git $cmd -h' outside a repository" '
+		test_expect_code 129 nongit git $cmd -h >usage &&
+		test_grep "[Uu]sage: git $cmd " usage
+	'
+	test_$expect_outcome $prereq "'git $cmd --help-all' outside a repository" '
+		test_expect_code 129 nongit git $cmd --help-all >usage &&
+		test_grep "[Uu]sage: git $cmd " usage
+	'
+done
 
-test_expect_success 'prune does not crash with -h' '
-	test_expect_code 129 git prune -h >usage &&
-	test_grep "[Uu]sage: git prune " usage &&
-	test_expect_code 129 nongit git prune -h >usage &&
-	test_grep "[Uu]sage: git prune " usage
+test_expect_success 'fmt-merge-msg does not crash with -h' '
+	test_expect_code 129 git fmt-merge-msg -h >usage &&
+	test_grep "[Uu]sage: git fmt-merge-msg " usage &&
+	test_expect_code 129 nongit git fmt-merge-msg -h >usage &&
+	test_grep "[Uu]sage: git fmt-merge-msg " usage
 '
 
 test_done
diff --git a/t/t1900-repo.sh b/t/t1900-repo.sh
new file mode 100755
index 0000000..2beba67
--- /dev/null
+++ b/t/t1900-repo.sh
@@ -0,0 +1,113 @@
+#!/bin/sh
+
+test_description='test git repo-info'
+
+. ./test-lib.sh
+
+# Test whether a key-value pair is correctly returned
+#
+# Usage: test_repo_info <label> <init command> <repo_name> <key> <expected value>
+#
+# Arguments:
+#   label: the label of the test
+#   init_command: a command which creates a repository
+#   repo_name: the name of the repository that will be created in init_command
+#   key: the key of the field that is being tested
+#   expected_value: the value that the field should contain
+test_repo_info () {
+	label=$1
+	init_command=$2
+	repo_name=$3
+	key=$4
+	expected_value=$5
+
+	test_expect_success "setup: $label" '
+		eval "$init_command $repo_name"
+	'
+
+	test_expect_success "keyvalue: $label" '
+		echo "$key=$expected_value" > expect &&
+		git -C "$repo_name" repo info "$key" >actual &&
+		test_cmp expect actual
+	'
+
+	test_expect_success "nul: $label" '
+		printf "%s\n%s\0" "$key" "$expected_value" >expect &&
+		git -C "$repo_name" repo info --format=nul "$key" >actual &&
+		test_cmp_bin expect actual
+	'
+}
+
+test_repo_info 'ref format files is retrieved correctly' \
+	'git init --ref-format=files' 'format-files' 'references.format' 'files'
+
+test_repo_info 'ref format reftable is retrieved correctly' \
+	'git init --ref-format=reftable' 'format-reftable' 'references.format' 'reftable'
+
+test_repo_info 'bare repository = false is retrieved correctly' \
+	'git init' 'nonbare' 'layout.bare' 'false'
+
+test_repo_info 'bare repository = true is retrieved correctly' \
+	'git init --bare' 'bare' 'layout.bare' 'true'
+
+test_repo_info 'shallow repository = false is retrieved correctly' \
+	'git init' 'nonshallow' 'layout.shallow' 'false'
+
+test_expect_success 'setup remote' '
+	git init remote &&
+	echo x >remote/x &&
+	git -C remote add x &&
+	git -C remote commit -m x
+'
+
+test_repo_info 'shallow repository = true is retrieved correctly' \
+	'git clone --depth 1 "file://$PWD/remote"' 'shallow' 'layout.shallow' 'true'
+
+test_repo_info 'object.format = sha1 is retrieved correctly' \
+	'git init --object-format=sha1' 'sha1' 'object.format' 'sha1'
+
+test_repo_info 'object.format = sha256 is retrieved correctly' \
+	'git init --object-format=sha256' 'sha256' 'object.format' 'sha256'
+
+test_expect_success 'values returned in order requested' '
+	cat >expect <<-\EOF &&
+	layout.bare=false
+	references.format=files
+	layout.bare=false
+	EOF
+	git init --ref-format=files ordered &&
+	git -C ordered repo info layout.bare references.format layout.bare >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git-repo-info fails if an invalid key is requested' '
+	echo "error: key ${SQ}foo${SQ} not found" >expect &&
+	test_must_fail git repo info foo 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git-repo-info outputs data even if there is an invalid field' '
+	echo "references.format=$(test_detect_ref_format)" >expect &&
+	test_must_fail git repo info foo references.format bar >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git-repo-info aborts when requesting an invalid format' '
+	echo "fatal: invalid format ${SQ}foo${SQ}" >expect &&
+	test_must_fail git repo info --format=foo 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success '-z uses nul-terminated format' '
+	printf "layout.bare\nfalse\0layout.shallow\nfalse\0" >expected &&
+	git repo info -z layout.bare layout.shallow >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'git repo info uses the last requested format' '
+	echo "layout.bare=false" >expected &&
+	git repo info --format=nul -z --format=keyvalue layout.bare >actual &&
+	test_cmp expected actual
+'
+
+test_done
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/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 6bac217..e778dd8 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -1176,7 +1176,7 @@
 	test B = $(git cat-file commit HEAD^ | sed -ne \$p)
 '
 
-test_expect_success 'rebase -i respects core.commentchar=auto' '
+test_expect_success !WITH_BREAKING_CHANGES 'rebase -i respects core.commentchar=auto' '
 	test_config core.commentchar auto &&
 	write_script copy-edit-script.sh <<-\EOF &&
 	cp "$1" edit-script
@@ -1184,8 +1184,23 @@
 	test_when_finished "git rebase --abort || :" &&
 	(
 		test_set_editor "$(pwd)/copy-edit-script.sh" &&
-		git rebase -i HEAD^
+		git rebase -i HEAD^ 2>err
 	) &&
+	sed -n "s/^hint: *\$//p; s/^hint: //p; s/^warning: //p" err >actual &&
+	cat >expect <<-EOF &&
+	Support for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0
+
+	To use the default comment string (#) please run
+
+	    git config unset core.commentChar
+
+	To set a custom comment string please run
+
+	    git config set core.commentChar <comment string>
+
+	where ${SQ}<comment string>${SQ} is the string you wish to use.
+	EOF
+	test_cmp expect actual &&
 	test -z "$(grep -ve "^#" -e "^\$" -e "^pick" edit-script)"
 '
 
@@ -2263,6 +2278,7 @@
 	edit $oid
 	fixup $oid
 	squash $oid
+	drop $oid # acceptable, no advice
 	EOF
 	(
 		set_replace_editor todo &&
diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh
index 5d093e3..5033411 100755
--- a/t/t3415-rebase-autosquash.sh
+++ b/t/t3415-rebase-autosquash.sh
@@ -486,12 +486,28 @@
 	test XZWY = $(git show | tr -cd W-Z)
 '
 
-test_expect_success 'fixup does not clean up commit message' '
-	oneline="#818" &&
-	git commit --allow-empty -m "$oneline" &&
-	git commit --fixup HEAD --allow-empty &&
-	git -c commit.cleanup=strip rebase -ki --autosquash HEAD~2 &&
-	test "$oneline" = "$(git show -s --format=%s)"
+test_expect_success 'pick and fixup respect commit.cleanup' '
+	git reset --hard base &&
+	test_commit --no-tag "fixup! second commit" file1 fixup &&
+	test_commit something &&
+	write_script .git/hooks/prepare-commit-msg <<-\EOF &&
+	printf "\n# Prepared\n" >> "$1"
+	EOF
+	git rebase -i --autosquash HEAD~3 &&
+	test_commit_message HEAD~1 <<-\EOF &&
+	second commit
+
+	# Prepared
+	EOF
+	test_commit_message HEAD <<-\EOF &&
+	something
+
+	# Prepared
+	EOF
+	git reset --hard something &&
+	git -c commit.cleanup=strip rebase -i --autosquash HEAD~3 &&
+	test_commit_message HEAD~1 -m "second commit" &&
+	test_commit_message HEAD -m "something"
 '
 
 test_done
diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh
index b8a8dd7..f9b8999 100755
--- a/t/t3418-rebase-continue.sh
+++ b/t/t3418-rebase-continue.sh
@@ -328,7 +328,7 @@
 	test_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec
 '
 
-test_expect_success 'no change in comment character due to conflicts markers with core.commentChar=auto' '
+test_expect_success !WITH_BREAKING_CHANGES 'no change in comment character due to conflicts markers with core.commentChar=auto' '
 	git checkout -b branch-a &&
 	test_commit A F1 &&
 	git checkout -b branch-b HEAD^ &&
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 04d2a19..b5e6edc 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -333,8 +333,8 @@
 	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/2) Stage mode change [y,n,q,a,d,k,K,j,J,g,/,p,?]?
+	(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,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,?]? + 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,?]?_
 	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,?]?_
 	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,?]? @@ -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,?]?_
 	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,?]?_
 	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,?]? @@ -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,?]?_
 	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,?]? <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,?]? <RESET>
 	EOF
 	test_write_lines s y g 1 P |
 	(
@@ -802,15 +802,15 @@
 	<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,?]? <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,?]? <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,?]? <RESET>
 	EOF
 	test_cmp expect actual
 '
@@ -866,6 +866,44 @@
 	test_grep "old<" output
 '
 
+test_expect_success 'diff color respects color.diff' '
+	git reset --hard &&
+
+	echo old >test &&
+	git add test &&
+	echo new >test &&
+
+	printf n >n &&
+	force_color git \
+		-c color.interactive=auto \
+		-c color.interactive.prompt=blue \
+		-c color.diff=false \
+		-c color.diff.old=red \
+		add -p >output.raw 2>&1 <n &&
+	test_decode_color <output.raw >output &&
+	test_grep "BLUE.*Stage this hunk" output &&
+	test_grep ! "RED" output
+'
+
+test_expect_success 're-coloring diff without color.interactive' '
+	git reset --hard &&
+
+	test_write_lines 1 2 3 >test &&
+	git add test &&
+	test_write_lines one 2 three >test &&
+
+	test_write_lines s n n |
+	force_color git \
+		-c color.interactive=false \
+		-c color.interactive.prompt=blue \
+		-c color.diff=true \
+		-c color.diff.frag="bold magenta" \
+		add -p >output.raw 2>&1 &&
+	test_decode_color <output.raw >output &&
+	test_grep "<BOLD;MAGENTA>@@" output &&
+	test_grep ! "BLUE" output
+'
+
 test_expect_success 'diffFilter filters diff' '
 	git reset --hard &&
 
@@ -1283,6 +1321,12 @@
 	test_grep "@@ -2,20 +2,20 @@" actual
 '
 
+test_expect_success 'set up base for -p color tests' '
+	echo commit >file &&
+	git commit -am "commit state" &&
+	git tag patch-base
+'
+
 for cmd in add checkout commit reset restore "stash save" "stash push"
 do
 	test_expect_success "$cmd rejects invalid context options" '
@@ -1299,6 +1343,92 @@
 		test_must_fail git $cmd --inter-hunk-context 2 2>actual &&
 		test_grep -E ".--inter-hunk-context. requires .(--interactive/)?--patch." actual
 	'
+
+	test_expect_success "$cmd falls back to color.ui" '
+		git reset --hard patch-base &&
+		echo working-tree >file &&
+		test_write_lines y |
+		force_color git -c color.ui=false $cmd -p >output.raw 2>&1 &&
+		test_decode_color <output.raw >output &&
+		test_cmp output.raw output
+	'
 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,?]?
+	(2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]? Sorry, cannot split this hunk
+	(2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,?]?
+	EOF
+	test_cmp expect actual
+'
+
 test_done
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 0bb4648..7087994 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -902,6 +902,7 @@
 
 test_expect_success 'apply: show same status as git status (relative to ./)' '
 	git stash clear &&
+	mkdir -p subdir &&
 	echo 1 >subdir/subfile1 &&
 	echo 2 >subdir/subfile2 &&
 	git add subdir/subfile1 &&
@@ -1356,6 +1357,7 @@
 
 test_expect_success 'stash -- <subdir> leaves untracked files in subdir intact' '
 	git reset &&
+	mkdir -p subdir &&
 	>subdir/untracked &&
 	>subdir/tracked1 &&
 	>subdir/tracked2 &&
@@ -1372,6 +1374,7 @@
 
 test_expect_success 'stash -- <subdir> works with binary files' '
 	git reset &&
+	mkdir -p subdir &&
 	>subdir/untracked &&
 	>subdir/tracked &&
 	cp "$TEST_DIRECTORY"/test-binary-1.png subdir/tracked-binary &&
@@ -1741,4 +1744,50 @@
 	)
 '
 
+test_expect_success SANITIZE_LEAK 'stash show handles -- without leaking' '
+	git stash show --
+'
+
+test_expect_success 'controlled error return on unrecognized option' '
+	test_expect_code 129 git stash show -p --invalid 2>usage &&
+	grep -e "^usage: git stash show" usage
+'
+
+test_expect_success 'stash.index=true implies --index' '
+	# setup for a few related tests
+	test_commit file base &&
+	echo index >file &&
+	git add file &&
+	echo working >file &&
+	git stash &&
+
+	test_when_finished "git reset --hard" &&
+	git -c stash.index=true stash apply &&
+	echo index >expect &&
+	git show :0:file >actual &&
+	test_cmp expect actual &&
+	echo working >expect &&
+	test_cmp expect file
+'
+
+test_expect_success 'stash.index=true overridden by --no-index' '
+	test_when_finished "git reset --hard" &&
+	git -c stash.index=true stash apply --no-index &&
+	echo base >expect &&
+	git show :0:file >actual &&
+	test_cmp expect actual &&
+	echo working >expect &&
+	test_cmp expect file
+'
+
+test_expect_success 'stash.index=false overridden by --index' '
+	test_when_finished "git reset --hard" &&
+	git -c stash.index=false stash apply --index &&
+	echo index >expect &&
+	git show :0:file >actual &&
+	test_cmp expect actual &&
+	echo working >expect &&
+	test_cmp expect file
+'
+
 test_done
diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh
index ae313e3..90a4ff2 100755
--- a/t/t3904-stash-patch.sh
+++ b/t/t3904-stash-patch.sh
@@ -107,4 +107,23 @@
 	! grep "added line 2" test
 '
 
+test_expect_success 'stash -p not confused by GIT_PAGER_IN_USE' '
+	echo to-stash >test &&
+	# Set both GIT_PAGER_IN_USE and TERM. Our goal is to entice any
+	# diff subprocesses into thinking that they could output
+	# color, even though their stdout is not going into a tty.
+	echo y |
+	GIT_PAGER_IN_USE=1 TERM=vt100 git stash -p &&
+	git diff --exit-code
+'
+
+test_expect_success 'index push not confused by GIT_PAGER_IN_USE' '
+	echo index >test &&
+	git add test &&
+	echo working-tree >test &&
+	# As above, we try to entice the child diff into using color.
+	GIT_PAGER_IN_USE=1 TERM=vt100 git stash push test &&
+	git diff --exit-code
+'
+
 test_done
diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh
index 1289ae3..7704709 100755
--- a/t/t3905-stash-include-untracked.sh
+++ b/t/t3905-stash-include-untracked.sh
@@ -87,7 +87,6 @@
 
 test_expect_success 'clean up untracked/untracked file to prepare for next tests' '
 	git clean --force --quiet
-
 '
 
 test_expect_success 'stash pop after save --include-untracked leaves files untracked again' '
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index 8ebd170..55a06ea 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -5,7 +5,7 @@
 
 test_description='Various diff formatting options'
 
-GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
 . ./test-lib.sh
@@ -70,7 +70,7 @@
 	GIT_COMMITTER_DATE="2006-06-26 00:04:00 +0000" &&
 	export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
 
-	git checkout master &&
+	git checkout main &&
 	git pull -s ours --no-rebase . side &&
 
 	GIT_AUTHOR_DATE="2006-06-26 00:05:00 +0000" &&
@@ -95,7 +95,7 @@
 	test_write_lines B A >dir/sub &&
 	git add dir/sub &&
 	git commit -m "Rearranged lines in dir/sub" &&
-	git checkout master &&
+	git checkout main &&
 
 	GIT_AUTHOR_DATE="2006-06-26 00:06:00 +0000" &&
 	GIT_COMMITTER_DATE="2006-06-26 00:06:00 +0000" &&
@@ -103,7 +103,7 @@
 	git checkout -b mode initial &&
 	git update-index --chmod=+x file0 &&
 	git commit -m "update mode" &&
-	git checkout -f master &&
+	git checkout -f main &&
 
 	GIT_AUTHOR_DATE="2006-06-26 00:06:00 +0000" &&
 	GIT_COMMITTER_DATE="2006-06-26 00:06:00 +0000" &&
@@ -112,12 +112,12 @@
 	git update-index --chmod=+x file2 &&
 	git commit -m "update mode (file2)" &&
 	git notes add -m "note" &&
-	git checkout -f master &&
+	git checkout -f main &&
 
-	# Same merge as master, but with parents reversed. Hide it in a
+	# Same merge as main, but with parents reversed. Hide it in a
 	# pseudo-ref to avoid impacting tests with --all.
 	commit=$(echo reverse |
-		 git commit-tree -p master^2 -p master^1 master^{tree}) &&
+		 git commit-tree -p main^2 -p main^1 main^{tree}) &&
 	git update-ref REVERSE $commit &&
 
 	git config diff.renames false &&
@@ -127,15 +127,15 @@
 
 : <<\EOF
 ! [initial] Initial
- * [master] Merge branch 'side'
+ * [main] Merge branch 'side'
   ! [rearrange] Rearranged lines in dir/sub
    ! [side] Side
 ----
   +  [rearrange] Rearranged lines in dir/sub
- -   [master] Merge branch 'side'
+ -   [main] Merge branch 'side'
  * + [side] Side
- *   [master^] Third
- *   [master~2] Second
+ *   [main^] Third
+ *   [main~2] Second
 +*++ [initial] Initial
 EOF
 
@@ -311,64 +311,64 @@
 diff-tree --stat initial mode
 diff-tree --summary initial mode
 
-diff-tree master
-diff-tree -m master
-diff-tree -p master
-diff-tree -p -m master
-diff-tree -c master
-diff-tree -c --abbrev master
-:noellipses diff-tree -c --abbrev master
-diff-tree --cc master
+diff-tree main
+diff-tree -m main
+diff-tree -p main
+diff-tree -p -m main
+diff-tree -c main
+diff-tree -c --abbrev main
+:noellipses diff-tree -c --abbrev main
+diff-tree --cc main
 # stat only should show the diffstat with the first parent
-diff-tree -c --stat master
-diff-tree --cc --stat master
-diff-tree -c --stat --summary master
-diff-tree --cc --stat --summary master
+diff-tree -c --stat main
+diff-tree --cc --stat main
+diff-tree -c --stat --summary main
+diff-tree --cc --stat --summary main
 # stat summary should show the diffstat and summary with the first parent
 diff-tree -c --stat --summary side
 diff-tree --cc --stat --summary side
-diff-tree --cc --shortstat master
+diff-tree --cc --shortstat main
 diff-tree --cc --summary REVERSE
 # improved by Timo's patch
-diff-tree --cc --patch-with-stat master
+diff-tree --cc --patch-with-stat main
 # improved by Timo's patch
-diff-tree --cc --patch-with-stat --summary master
+diff-tree --cc --patch-with-stat --summary main
 # this is correct
 diff-tree --cc --patch-with-stat --summary side
 
-log master
-log -p master
-log --root master
-log --root -p master
-log --patch-with-stat master
-log --root --patch-with-stat master
-log --root --patch-with-stat --summary master
+log main
+log -p main
+log --root main
+log --root -p main
+log --patch-with-stat main
+log --root --patch-with-stat main
+log --root --patch-with-stat --summary main
 # improved by Timo's patch
-log --root -c --patch-with-stat --summary master
+log --root -c --patch-with-stat --summary main
 # improved by Timo's patch
-log --root --cc --patch-with-stat --summary master
-log --no-diff-merges -p --first-parent master
-log --diff-merges=off -p --first-parent master
-log --first-parent --diff-merges=off -p master
-log -p --first-parent master
-log -p --diff-merges=first-parent master
-log --diff-merges=first-parent master
-log -m -p --first-parent master
-log -m -p master
-log --cc -m -p master
-log -c -m -p master
-log -m --raw master
-log -m --stat master
-log -SF master
-log -S F master
-log -SF -p master
-log -SF master --max-count=0
-log -SF master --max-count=1
-log -SF master --max-count=2
-log -GF master
-log -GF -p master
-log -GF -p --pickaxe-all master
-log -IA -IB -I1 -I2 -p master
+log --root --cc --patch-with-stat --summary main
+log --no-diff-merges -p --first-parent main
+log --diff-merges=off -p --first-parent main
+log --first-parent --diff-merges=off -p main
+log -p --first-parent main
+log -p --diff-merges=first-parent main
+log --diff-merges=first-parent main
+log -m -p --first-parent main
+log -m -p main
+log --cc -m -p main
+log -c -m -p main
+log -m --raw main
+log -m --stat main
+log -SF main
+log -S F main
+log -SF -p main
+log -SF main --max-count=0
+log -SF main --max-count=1
+log -SF main --max-count=2
+log -GF main
+log -GF -p main
+log -GF -p --pickaxe-all main
+log -IA -IB -I1 -I2 -p main
 log --decorate --all
 log --decorate=full --all
 log --decorate --clear-decorations --all
@@ -377,35 +377,35 @@
 rev-list --parents HEAD
 rev-list --children HEAD
 
-whatchanged master
-:noellipses whatchanged master
-whatchanged -p master
-whatchanged --root master
-:noellipses whatchanged --root master
-whatchanged --root -p master
-whatchanged --patch-with-stat master
-whatchanged --root --patch-with-stat master
-whatchanged --root --patch-with-stat --summary master
+whatchanged main
+:noellipses whatchanged main
+whatchanged -p main
+whatchanged --root main
+:noellipses whatchanged --root main
+whatchanged --root -p main
+whatchanged --patch-with-stat main
+whatchanged --root --patch-with-stat main
+whatchanged --root --patch-with-stat --summary main
 # improved by Timo's patch
-whatchanged --root -c --patch-with-stat --summary master
+whatchanged --root -c --patch-with-stat --summary main
 # improved by Timo's patch
-whatchanged --root --cc --patch-with-stat --summary master
-whatchanged -SF master
-:noellipses whatchanged -SF master
-whatchanged -SF -p master
+whatchanged --root --cc --patch-with-stat --summary main
+whatchanged -SF main
+:noellipses whatchanged -SF main
+whatchanged -SF -p main
 
-log --patch-with-stat master -- dir/
-whatchanged --patch-with-stat master -- dir/
-log --patch-with-stat --summary master -- dir/
-whatchanged --patch-with-stat --summary master -- dir/
+log --patch-with-stat main -- dir/
+whatchanged --patch-with-stat main -- dir/
+log --patch-with-stat --summary main -- dir/
+whatchanged --patch-with-stat --summary main -- dir/
 
 show initial
 show --root initial
 show side
-show master
-show -c master
-show -m master
-show --first-parent master
+show main
+show -c main
+show -m main
+show --first-parent main
 show --stat side
 show --stat --summary side
 show --patch-with-stat side
@@ -414,22 +414,22 @@
 show --patch-with-stat --summary side
 
 format-patch --stdout initial..side
-format-patch --stdout initial..master^
-format-patch --stdout initial..master
-format-patch --stdout --no-numbered initial..master
-format-patch --stdout --numbered initial..master
+format-patch --stdout initial..main^
+format-patch --stdout initial..main
+format-patch --stdout --no-numbered initial..main
+format-patch --stdout --numbered initial..main
 format-patch --attach --stdout initial..side
 format-patch --attach --stdout --suffix=.diff initial..side
-format-patch --attach --stdout initial..master^
-format-patch --attach --stdout initial..master
+format-patch --attach --stdout initial..main^
+format-patch --attach --stdout initial..main
 format-patch --inline --stdout initial..side
-format-patch --inline --stdout initial..master^
-format-patch --inline --stdout --numbered-files initial..master
-format-patch --inline --stdout initial..master
-format-patch --inline --stdout --subject-prefix=TESTCASE initial..master
+format-patch --inline --stdout initial..main^
+format-patch --inline --stdout --numbered-files initial..main
+format-patch --inline --stdout initial..main
+format-patch --inline --stdout --subject-prefix=TESTCASE initial..main
 config format.subjectprefix DIFFERENT_PREFIX
-format-patch --inline --stdout initial..master^^
-format-patch --stdout --cover-letter -n initial..master^
+format-patch --inline --stdout initial..main^^
+format-patch --stdout --cover-letter -n initial..main^
 
 diff --abbrev initial..side
 diff -U initial..side
@@ -448,13 +448,13 @@
 diff --no-index --name-status dir2 dir
 diff --no-index --name-status -- dir2 dir
 diff --no-index dir dir3
-diff master master^ side
+diff main main^ side
 # Can't use spaces...
-diff --line-prefix=abc master master^ side
-diff --dirstat master~1 master~2
+diff --line-prefix=abc main main^ side
+diff --dirstat main~1 main~2
 diff --dirstat initial rearrange
 diff --dirstat-by-file initial rearrange
-diff --dirstat --cc master~1 master
+diff --dirstat --cc main~1 main
 # No-index --abbrev and --no-abbrev
 diff --raw initial
 :noellipses diff --raw initial
@@ -482,7 +482,7 @@
 '
 
 test_expect_success 'log -m matches pure log' '
-	git log master >result &&
+	git log main >result &&
 	process_diffs result >expected &&
 	git log -m >result &&
 	process_diffs result >actual &&
@@ -490,17 +490,17 @@
 '
 
 test_expect_success 'log --diff-merges=on matches --diff-merges=separate' '
-	git log -p --diff-merges=separate master >result &&
+	git log -p --diff-merges=separate main >result &&
 	process_diffs result >expected &&
-	git log -p --diff-merges=on master >result &&
+	git log -p --diff-merges=on main >result &&
 	process_diffs result >actual &&
 	test_cmp expected actual
 '
 
 test_expect_success 'log --dd matches --diff-merges=1 -p' '
-	git log --diff-merges=1 -p master >result &&
+	git log --diff-merges=1 -p main >result &&
 	process_diffs result >expected &&
-	git log --dd master >result &&
+	git log --dd main >result &&
 	process_diffs result >actual &&
 	test_cmp expected actual
 '
@@ -511,19 +511,19 @@
 '
 
 test_expect_success 'git config log.diffMerges first-parent' '
-	git log -p --diff-merges=first-parent master >result &&
+	git log -p --diff-merges=first-parent main >result &&
 	process_diffs result >expected &&
 	test_config log.diffMerges first-parent &&
-	git log -p --diff-merges=on master >result &&
+	git log -p --diff-merges=on main >result &&
 	process_diffs result >actual &&
 	test_cmp expected actual
 '
 
 test_expect_success 'git config log.diffMerges first-parent vs -m' '
-	git log -p --diff-merges=first-parent master >result &&
+	git log -p --diff-merges=first-parent main >result &&
 	process_diffs result >expected &&
 	test_config log.diffMerges first-parent &&
-	git log -p -m master >result &&
+	git log -p -m main >result &&
 	process_diffs result >actual &&
 	test_cmp expected actual
 '
@@ -572,7 +572,7 @@
 	Third
 	Second
 	EOF
-	git rev-list master | git diff-tree --stdin --format=%s -s >actual &&
+	git rev-list main | git diff-tree --stdin --format=%s -s >actual &&
 	test_cmp expect actual
 '
 
@@ -585,16 +585,16 @@
 
 	dir/sub
 	EOF
-	git rev-list master^ |
+	git rev-list main^ |
 	git diff-tree -r --stdin --name-only --format=%s dir >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'show A B ... -- <pathspec>' '
 	# side touches dir/sub, file0, and file3
-	# master^ touches dir/sub, and file1
-	# master^^ touches dir/sub, file0, and file2
-	git show --name-only --format="<%s>" side master^ master^^ -- dir >actual &&
+	# main^ touches dir/sub, and file1
+	# main^^ touches dir/sub, file0, and file2
+	git show --name-only --format="<%s>" side main^ main^^ -- dir >actual &&
 	cat >expect <<-\EOF &&
 	<Side>
 
@@ -610,7 +610,7 @@
 '
 
 test_expect_success 'diff -I<regex>: setup' '
-	git checkout master &&
+	git checkout main &&
 	test_seq 50 >file0 &&
 	git commit -m "Set up -I<regex> test file" file0 &&
 	test_seq 50 | sed -e "s/13/ten and three/" -e "/7\$/d" >file0 &&
@@ -648,6 +648,19 @@
 	test_grep "invalid regex given to -I: " error
 '
 
+test_expect_success 'diff -I<regex>: ignore matching file' '
+	test_when_finished "git rm -f file1" &&
+	test_seq 50 >file1 &&
+	git add file1 &&
+	test_seq 50 | sed -e "s/13/ten and three/" -e "s/^[124-9].*/& /" >file1 &&
+
+	: >actual &&
+	git diff --raw --ignore-blank-lines -I"ten.*e" -I"^[124-9]" >>actual &&
+	git diff --name-only --ignore-blank-lines -I"ten.*e" -I"^[124-9]" >>actual &&
+	git diff --name-status --ignore-blank-lines -I"ten.*e" -I"^[124-9]" >>actual &&
+	test_grep ! "file1" actual
+'
+
 # check_prefix <patch> <src> <dst>
 # check only lines with paths to avoid dependency on exact oid/contents
 check_prefix () {
diff --git a/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_main
similarity index 86%
rename from t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master
rename to t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_main
index 9951e36..af1cf20 100644
--- a/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master
+++ b/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_main
@@ -1,4 +1,4 @@
-$ git diff-tree --cc --patch-with-stat --summary master
+$ git diff-tree --cc --patch-with-stat --summary main
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub | 2 ++
  file0   | 3 +++
diff --git a/t/t4013/diff.diff-tree_--cc_--patch-with-stat_master b/t/t4013/diff.diff-tree_--cc_--patch-with-stat_main
similarity index 88%
rename from t/t4013/diff.diff-tree_--cc_--patch-with-stat_master
rename to t/t4013/diff.diff-tree_--cc_--patch-with-stat_main
index db3c0a7..0ec6042 100644
--- a/t/t4013/diff.diff-tree_--cc_--patch-with-stat_master
+++ b/t/t4013/diff.diff-tree_--cc_--patch-with-stat_main
@@ -1,4 +1,4 @@
-$ git diff-tree --cc --patch-with-stat master
+$ git diff-tree --cc --patch-with-stat main
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub | 2 ++
  file0   | 3 +++
diff --git a/t/t4013/diff.diff-tree_--cc_--shortstat_master b/t/t4013/diff.diff-tree_--cc_--shortstat_main
similarity index 65%
rename from t/t4013/diff.diff-tree_--cc_--shortstat_master
rename to t/t4013/diff.diff-tree_--cc_--shortstat_main
index a4ca42d..9a4ef03 100644
--- a/t/t4013/diff.diff-tree_--cc_--shortstat_master
+++ b/t/t4013/diff.diff-tree_--cc_--shortstat_main
@@ -1,4 +1,4 @@
-$ git diff-tree --cc --shortstat master
+$ git diff-tree --cc --shortstat main
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  2 files changed, 5 insertions(+)
 $
diff --git a/t/t4013/diff.diff-tree_--cc_--stat_master b/t/t4013/diff.diff-tree_--cc_--stat_--summary_main
similarity index 71%
copy from t/t4013/diff.diff-tree_--cc_--stat_master
copy to t/t4013/diff.diff-tree_--cc_--stat_--summary_main
index 40b9179..9db08a4 100644
--- a/t/t4013/diff.diff-tree_--cc_--stat_master
+++ b/t/t4013/diff.diff-tree_--cc_--stat_--summary_main
@@ -1,4 +1,4 @@
-$ git diff-tree --cc --stat master
+$ git diff-tree --cc --stat --summary main
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub | 2 ++
  file0   | 3 +++
diff --git a/t/t4013/diff.diff-tree_--cc_--stat_--summary_master b/t/t4013/diff.diff-tree_--cc_--stat_--summary_master
deleted file mode 100644
index d019867..0000000
--- a/t/t4013/diff.diff-tree_--cc_--stat_--summary_master
+++ /dev/null
@@ -1,6 +0,0 @@
-$ git diff-tree --cc --stat --summary master
-59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub | 2 ++
- file0   | 3 +++
- 2 files changed, 5 insertions(+)
-$
diff --git a/t/t4013/diff.diff-tree_-c_--stat_master b/t/t4013/diff.diff-tree_--cc_--stat_main
similarity index 76%
rename from t/t4013/diff.diff-tree_-c_--stat_master
rename to t/t4013/diff.diff-tree_--cc_--stat_main
index 89d59b1..7ecc67a 100644
--- a/t/t4013/diff.diff-tree_-c_--stat_master
+++ b/t/t4013/diff.diff-tree_--cc_--stat_main
@@ -1,4 +1,4 @@
-$ git diff-tree -c --stat master
+$ git diff-tree --cc --stat main
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub | 2 ++
  file0   | 3 +++
diff --git a/t/t4013/diff.diff-tree_--cc_master b/t/t4013/diff.diff-tree_--cc_main
similarity index 91%
rename from t/t4013/diff.diff-tree_--cc_master
rename to t/t4013/diff.diff-tree_--cc_main
index 5ecb4e1..1a96285 100644
--- a/t/t4013/diff.diff-tree_--cc_master
+++ b/t/t4013/diff.diff-tree_--cc_main
@@ -1,4 +1,4 @@
-$ git diff-tree --cc master
+$ git diff-tree --cc main
 59d314ad6f356dd08601a4cd5e530381da3e3c64
 diff --cc dir/sub
 index cead32e,7289e35..992913c
diff --git a/t/t4013/diff.diff-tree_-c_--abbrev_master b/t/t4013/diff.diff-tree_-c_--abbrev_main
similarity index 82%
rename from t/t4013/diff.diff-tree_-c_--abbrev_master
rename to t/t4013/diff.diff-tree_-c_--abbrev_main
index b8e4aa2..039d127 100644
--- a/t/t4013/diff.diff-tree_-c_--abbrev_master
+++ b/t/t4013/diff.diff-tree_-c_--abbrev_main
@@ -1,4 +1,4 @@
-$ git diff-tree -c --abbrev master
+$ git diff-tree -c --abbrev main
 59d314ad6f356dd08601a4cd5e530381da3e3c64
 ::100644 100644 100644 cead32e... 7289e35... 992913c... MM	dir/sub
 ::100644 100644 100644 b414108... f4615da... 10a8a9f... MM	file0
diff --git a/t/t4013/diff.diff-tree_--cc_--stat_master b/t/t4013/diff.diff-tree_-c_--stat_--summary_main
similarity index 72%
rename from t/t4013/diff.diff-tree_--cc_--stat_master
rename to t/t4013/diff.diff-tree_-c_--stat_--summary_main
index 40b9179..05a8d16 100644
--- a/t/t4013/diff.diff-tree_--cc_--stat_master
+++ b/t/t4013/diff.diff-tree_-c_--stat_--summary_main
@@ -1,4 +1,4 @@
-$ git diff-tree --cc --stat master
+$ git diff-tree -c --stat --summary main
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub | 2 ++
  file0   | 3 +++
diff --git a/t/t4013/diff.diff-tree_-c_--stat_--summary_master b/t/t4013/diff.diff-tree_-c_--stat_--summary_master
deleted file mode 100644
index 81c3021..0000000
--- a/t/t4013/diff.diff-tree_-c_--stat_--summary_master
+++ /dev/null
@@ -1,6 +0,0 @@
-$ git diff-tree -c --stat --summary master
-59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub | 2 ++
- file0   | 3 +++
- 2 files changed, 5 insertions(+)
-$
diff --git a/t/t4013/diff.diff-tree_-c_--stat_master b/t/t4013/diff.diff-tree_-c_--stat_main
similarity index 76%
copy from t/t4013/diff.diff-tree_-c_--stat_master
copy to t/t4013/diff.diff-tree_-c_--stat_main
index 89d59b1..61d9f45 100644
--- a/t/t4013/diff.diff-tree_-c_--stat_master
+++ b/t/t4013/diff.diff-tree_-c_--stat_main
@@ -1,4 +1,4 @@
-$ git diff-tree -c --stat master
+$ git diff-tree -c --stat main
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub | 2 ++
  file0   | 3 +++
diff --git a/t/t4013/diff.diff-tree_-c_master b/t/t4013/diff.diff-tree_-c_main
similarity index 92%
rename from t/t4013/diff.diff-tree_-c_master
rename to t/t4013/diff.diff-tree_-c_main
index e2d2bb2..a84e118 100644
--- a/t/t4013/diff.diff-tree_-c_master
+++ b/t/t4013/diff.diff-tree_-c_main
@@ -1,4 +1,4 @@
-$ git diff-tree -c master
+$ git diff-tree -c main
 59d314ad6f356dd08601a4cd5e530381da3e3c64
 ::100644 100644 100644 cead32e925b1420c84c14cbf7cf755e7e45af8ad 7289e35bff32727c08dda207511bec138fdb9ea5 992913c5aa0a5476d10c49ed0f21fc0c6d1aedf3 MM	dir/sub
 ::100644 100644 100644 b414108e81e5091fe0974a1858b4d0d22b107f70 f4615da674c09df322d6ba8d6b21ecfb1b1ba510 10a8a9f3657f91a156b9f0184ed79a20adef9f7f MM	file0
diff --git a/t/t4013/diff.diff-tree_-m_master b/t/t4013/diff.diff-tree_-m_main
similarity index 96%
rename from t/t4013/diff.diff-tree_-m_master
rename to t/t4013/diff.diff-tree_-m_main
index 6d0a220..5da1f7f 100644
--- a/t/t4013/diff.diff-tree_-m_master
+++ b/t/t4013/diff.diff-tree_-m_main
@@ -1,4 +1,4 @@
-$ git diff-tree -m master
+$ git diff-tree -m main
 59d314ad6f356dd08601a4cd5e530381da3e3c64
 :040000 040000 65f5c9dd60ce3b2b3324b618ac7accf8d912c113 0564e026437809817a64fff393079714b6dd4628 M	dir
 :100644 100644 b414108e81e5091fe0974a1858b4d0d22b107f70 10a8a9f3657f91a156b9f0184ed79a20adef9f7f M	file0
diff --git a/t/t4013/diff.diff-tree_-p_-m_master b/t/t4013/diff.diff-tree_-p_-m_main
similarity index 96%
rename from t/t4013/diff.diff-tree_-p_-m_master
rename to t/t4013/diff.diff-tree_-p_-m_main
index b60bea0..29c9fc2 100644
--- a/t/t4013/diff.diff-tree_-p_-m_master
+++ b/t/t4013/diff.diff-tree_-p_-m_main
@@ -1,4 +1,4 @@
-$ git diff-tree -p -m master
+$ git diff-tree -p -m main
 59d314ad6f356dd08601a4cd5e530381da3e3c64
 diff --git a/dir/sub b/dir/sub
 index cead32e..992913c 100644
diff --git a/t/t4013/diff.diff-tree_-p_main b/t/t4013/diff.diff-tree_-p_main
new file mode 100644
index 0000000..c658062
--- /dev/null
+++ b/t/t4013/diff.diff-tree_-p_main
@@ -0,0 +1,2 @@
+$ git diff-tree -p main
+$
diff --git a/t/t4013/diff.diff-tree_-p_master b/t/t4013/diff.diff-tree_-p_master
deleted file mode 100644
index b182875..0000000
--- a/t/t4013/diff.diff-tree_-p_master
+++ /dev/null
@@ -1,2 +0,0 @@
-$ git diff-tree -p master
-$
diff --git a/t/t4013/diff.diff-tree_main b/t/t4013/diff.diff-tree_main
new file mode 100644
index 0000000..dc5b9fd
--- /dev/null
+++ b/t/t4013/diff.diff-tree_main
@@ -0,0 +1,2 @@
+$ git diff-tree main
+$
diff --git a/t/t4013/diff.diff-tree_master b/t/t4013/diff.diff-tree_master
deleted file mode 100644
index fe9226f..0000000
--- a/t/t4013/diff.diff-tree_master
+++ /dev/null
@@ -1,2 +0,0 @@
-$ git diff-tree master
-$
diff --git a/t/t4013/diff.diff_--dirstat_--cc_main~1_main b/t/t4013/diff.diff_--dirstat_--cc_main~1_main
new file mode 100644
index 0000000..168a357
--- /dev/null
+++ b/t/t4013/diff.diff_--dirstat_--cc_main~1_main
@@ -0,0 +1,3 @@
+$ git diff --dirstat --cc main~1 main
+  40.0% dir/
+$
diff --git a/t/t4013/diff.diff_--dirstat_--cc_master~1_master b/t/t4013/diff.diff_--dirstat_--cc_master~1_master
deleted file mode 100644
index fba4e34..0000000
--- a/t/t4013/diff.diff_--dirstat_--cc_master~1_master
+++ /dev/null
@@ -1,3 +0,0 @@
-$ git diff --dirstat --cc master~1 master
-  40.0% dir/
-$
diff --git a/t/t4013/diff.diff_--dirstat_main~1_main~2 b/t/t4013/diff.diff_--dirstat_main~1_main~2
new file mode 100644
index 0000000..6809733
--- /dev/null
+++ b/t/t4013/diff.diff_--dirstat_main~1_main~2
@@ -0,0 +1,3 @@
+$ git diff --dirstat main~1 main~2
+  40.0% dir/
+$
diff --git a/t/t4013/diff.diff_--dirstat_master~1_master~2 b/t/t4013/diff.diff_--dirstat_master~1_master~2
deleted file mode 100644
index b672e1c..0000000
--- a/t/t4013/diff.diff_--dirstat_master~1_master~2
+++ /dev/null
@@ -1,3 +0,0 @@
-$ git diff --dirstat master~1 master~2
-  40.0% dir/
-$
diff --git a/t/t4013/diff.diff_--line-prefix=abc_master_master^_side b/t/t4013/diff.diff_--line-prefix=abc_main_main^_side
similarity index 87%
rename from t/t4013/diff.diff_--line-prefix=abc_master_master^_side
rename to t/t4013/diff.diff_--line-prefix=abc_main_main^_side
index 99f91e7..67a2145 100644
--- a/t/t4013/diff.diff_--line-prefix=abc_master_master^_side
+++ b/t/t4013/diff.diff_--line-prefix=abc_main_main^_side
@@ -1,4 +1,4 @@
-$ git diff --line-prefix=abc master master^ side
+$ git diff --line-prefix=abc main main^ side
 abcdiff --cc dir/sub
 abcindex cead32e,7289e35..992913c
 abc--- a/dir/sub
diff --git a/t/t4013/diff.diff_master_master^_side b/t/t4013/diff.diff_main_main^_side
similarity index 89%
rename from t/t4013/diff.diff_master_master^_side
rename to t/t4013/diff.diff_main_main^_side
index 50ec9ca..ab81ec9 100644
--- a/t/t4013/diff.diff_master_master^_side
+++ b/t/t4013/diff.diff_main_main^_side
@@ -1,4 +1,4 @@
-$ git diff master master^ side
+$ git diff main main^ side
 diff --cc dir/sub
 index cead32e,7289e35..992913c
 --- a/dir/sub
diff --git a/t/t4013/diff.format-patch_--attach_--stdout_initial..master b/t/t4013/diff.format-patch_--attach_--stdout_initial..main
similarity index 97%
rename from t/t4013/diff.format-patch_--attach_--stdout_initial..master
rename to t/t4013/diff.format-patch_--attach_--stdout_initial..main
index 52fedc1..9f56380 100644
--- a/t/t4013/diff.format-patch_--attach_--stdout_initial..master
+++ b/t/t4013/diff.format-patch_--attach_--stdout_initial..main
@@ -1,4 +1,4 @@
-$ git format-patch --attach --stdout initial..master
+$ git format-patch --attach --stdout initial..main
 From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
 From: A U Thor <author@example.com>
 Date: Mon, 26 Jun 2006 00:01:00 +0000
diff --git a/t/t4013/diff.format-patch_--attach_--stdout_initial..master^ b/t/t4013/diff.format-patch_--attach_--stdout_initial..main^
similarity index 97%
rename from t/t4013/diff.format-patch_--attach_--stdout_initial..master^
rename to t/t4013/diff.format-patch_--attach_--stdout_initial..main^
index 1c3cde2..80132ea 100644
--- a/t/t4013/diff.format-patch_--attach_--stdout_initial..master^
+++ b/t/t4013/diff.format-patch_--attach_--stdout_initial..main^
@@ -1,4 +1,4 @@
-$ git format-patch --attach --stdout initial..master^
+$ git format-patch --attach --stdout initial..main^
 From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
 From: A U Thor <author@example.com>
 Date: Mon, 26 Jun 2006 00:01:00 +0000
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master b/t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..main
similarity index 99%
rename from t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master
rename to t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..main
index 02c4db7..8e88909 100644
--- a/t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master
+++ b/t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..main
@@ -1,4 +1,4 @@
-$ git format-patch --inline --stdout --numbered-files initial..master
+$ git format-patch --inline --stdout --numbered-files initial..main
 From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
 From: A U Thor <author@example.com>
 Date: Mon, 26 Jun 2006 00:01:00 +0000
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master b/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..main
similarity index 99%
rename from t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master
rename to t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..main
index c7677c5..d7d2b12 100644
--- a/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master
+++ b/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..main
@@ -1,4 +1,4 @@
-$ git format-patch --inline --stdout --subject-prefix=TESTCASE initial..master
+$ git format-patch --inline --stdout --subject-prefix=TESTCASE initial..main
 From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
 From: A U Thor <author@example.com>
 Date: Mon, 26 Jun 2006 00:01:00 +0000
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_initial..master b/t/t4013/diff.format-patch_--inline_--stdout_initial..main
similarity index 97%
rename from t/t4013/diff.format-patch_--inline_--stdout_initial..master
rename to t/t4013/diff.format-patch_--inline_--stdout_initial..main
index 5b3e34e..c49c423 100644
--- a/t/t4013/diff.format-patch_--inline_--stdout_initial..master
+++ b/t/t4013/diff.format-patch_--inline_--stdout_initial..main
@@ -1,4 +1,4 @@
-$ git format-patch --inline --stdout initial..master
+$ git format-patch --inline --stdout initial..main
 From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
 From: A U Thor <author@example.com>
 Date: Mon, 26 Jun 2006 00:01:00 +0000
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_initial..master^ b/t/t4013/diff.format-patch_--inline_--stdout_initial..main^
similarity index 97%
rename from t/t4013/diff.format-patch_--inline_--stdout_initial..master^
rename to t/t4013/diff.format-patch_--inline_--stdout_initial..main^
index d13f8a8..8669dbf 100644
--- a/t/t4013/diff.format-patch_--inline_--stdout_initial..master^
+++ b/t/t4013/diff.format-patch_--inline_--stdout_initial..main^
@@ -1,4 +1,4 @@
-$ git format-patch --inline --stdout initial..master^
+$ git format-patch --inline --stdout initial..main^
 From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
 From: A U Thor <author@example.com>
 Date: Mon, 26 Jun 2006 00:01:00 +0000
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_initial..master^^ b/t/t4013/diff.format-patch_--inline_--stdout_initial..main^^
similarity index 95%
rename from t/t4013/diff.format-patch_--inline_--stdout_initial..master^^
rename to t/t4013/diff.format-patch_--inline_--stdout_initial..main^^
index caec553..b749be5 100644
--- a/t/t4013/diff.format-patch_--inline_--stdout_initial..master^^
+++ b/t/t4013/diff.format-patch_--inline_--stdout_initial..main^^
@@ -1,4 +1,4 @@
-$ git format-patch --inline --stdout initial..master^^
+$ git format-patch --inline --stdout initial..main^^
 From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
 From: A U Thor <author@example.com>
 Date: Mon, 26 Jun 2006 00:01:00 +0000
diff --git a/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ b/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..main^
similarity index 96%
rename from t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
rename to t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..main^
index 244d964..567f222 100644
--- a/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
+++ b/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..main^
@@ -1,4 +1,4 @@
-$ git format-patch --stdout --cover-letter -n initial..master^
+$ git format-patch --stdout --cover-letter -n initial..main^
 From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
 From: C O Mitter <committer@example.com>
 Date: Mon, 26 Jun 2006 00:06:00 +0000
diff --git a/t/t4013/diff.format-patch_--stdout_--no-numbered_initial..master b/t/t4013/diff.format-patch_--stdout_--no-numbered_initial..main
similarity index 96%
rename from t/t4013/diff.format-patch_--stdout_--no-numbered_initial..master
rename to t/t4013/diff.format-patch_--stdout_--no-numbered_initial..main
index bfc287a..195b62e 100644
--- a/t/t4013/diff.format-patch_--stdout_--no-numbered_initial..master
+++ b/t/t4013/diff.format-patch_--stdout_--no-numbered_initial..main
@@ -1,4 +1,4 @@
-$ git format-patch --stdout --no-numbered initial..master
+$ git format-patch --stdout --no-numbered initial..main
 From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
 From: A U Thor <author@example.com>
 Date: Mon, 26 Jun 2006 00:01:00 +0000
diff --git a/t/t4013/diff.format-patch_--stdout_--numbered_initial..master b/t/t4013/diff.format-patch_--stdout_--numbered_initial..main
similarity index 96%
rename from t/t4013/diff.format-patch_--stdout_--numbered_initial..master
rename to t/t4013/diff.format-patch_--stdout_--numbered_initial..main
index 568f6f5..0678a38 100644
--- a/t/t4013/diff.format-patch_--stdout_--numbered_initial..master
+++ b/t/t4013/diff.format-patch_--stdout_--numbered_initial..main
@@ -1,4 +1,4 @@
-$ git format-patch --stdout --numbered initial..master
+$ git format-patch --stdout --numbered initial..main
 From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
 From: A U Thor <author@example.com>
 Date: Mon, 26 Jun 2006 00:01:00 +0000
diff --git a/t/t4013/diff.format-patch_--stdout_initial..master b/t/t4013/diff.format-patch_--stdout_initial..main
similarity index 97%
rename from t/t4013/diff.format-patch_--stdout_initial..master
rename to t/t4013/diff.format-patch_--stdout_initial..main
index 5f0352f..b4a6302 100644
--- a/t/t4013/diff.format-patch_--stdout_initial..master
+++ b/t/t4013/diff.format-patch_--stdout_initial..main
@@ -1,4 +1,4 @@
-$ git format-patch --stdout initial..master
+$ git format-patch --stdout initial..main
 From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
 From: A U Thor <author@example.com>
 Date: Mon, 26 Jun 2006 00:01:00 +0000
diff --git a/t/t4013/diff.format-patch_--stdout_initial..master^ b/t/t4013/diff.format-patch_--stdout_initial..main^
similarity index 96%
rename from t/t4013/diff.format-patch_--stdout_initial..master^
rename to t/t4013/diff.format-patch_--stdout_initial..main^
index 2ae454d..36b3221 100644
--- a/t/t4013/diff.format-patch_--stdout_initial..master^
+++ b/t/t4013/diff.format-patch_--stdout_initial..main^
@@ -1,4 +1,4 @@
-$ git format-patch --stdout initial..master^
+$ git format-patch --stdout initial..main^
 From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
 From: A U Thor <author@example.com>
 Date: Mon, 26 Jun 2006 00:01:00 +0000
diff --git a/t/t4013/diff.log_-m_-p_master b/t/t4013/diff.log_--cc_-m_-p_main
similarity index 98%
copy from t/t4013/diff.log_-m_-p_master
copy to t/t4013/diff.log_--cc_-m_-p_main
index 9ca62a0..f32746e 100644
--- a/t/t4013/diff.log_-m_-p_master
+++ b/t/t4013/diff.log_--cc_-m_-p_main
@@ -1,4 +1,4 @@
-$ git log -m -p master
+$ git log --cc -m -p main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0)
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_--cc_-m_-p_master b/t/t4013/diff.log_--cc_-m_-p_master
deleted file mode 100644
index 7c217cf..0000000
--- a/t/t4013/diff.log_--cc_-m_-p_master
+++ /dev/null
@@ -1,200 +0,0 @@
-$ git log --cc -m -p master
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0)
-Merge: 9a6d494 c7a2ab9
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:04:00 2006 +0000
-
-    Merge branch 'side'
-
-diff --git a/dir/sub b/dir/sub
-index cead32e..992913c 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -4,3 +4,5 @@ C
- D
- E
- F
-+1
-+2
-diff --git a/file0 b/file0
-index b414108..10a8a9f 100644
---- a/file0
-+++ b/file0
-@@ -4,3 +4,6 @@
- 4
- 5
- 6
-+A
-+B
-+C
-
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a)
-Merge: 9a6d494 c7a2ab9
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:04:00 2006 +0000
-
-    Merge branch 'side'
-
-diff --git a/dir/sub b/dir/sub
-index 7289e35..992913c 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -1,4 +1,8 @@
- A
- B
-+C
-+D
-+E
-+F
- 1
- 2
-diff --git a/file0 b/file0
-index f4615da..10a8a9f 100644
---- a/file0
-+++ b/file0
-@@ -1,6 +1,9 @@
- 1
- 2
- 3
-+4
-+5
-+6
- A
- B
- C
-diff --git a/file1 b/file1
-new file mode 100644
-index 0000000..b1e6722
---- /dev/null
-+++ b/file1
-@@ -0,0 +1,3 @@
-+A
-+B
-+C
-diff --git a/file2 b/file2
-deleted file mode 100644
-index 01e79c3..0000000
---- a/file2
-+++ /dev/null
-@@ -1,3 +0,0 @@
--1
--2
--3
-diff --git a/file3 b/file3
-deleted file mode 100644
-index 7289e35..0000000
---- a/file3
-+++ /dev/null
-@@ -1,4 +0,0 @@
--A
--B
--1
--2
-
-commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:03:00 2006 +0000
-
-    Side
-
-diff --git a/dir/sub b/dir/sub
-index 35d242b..7289e35 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -1,2 +1,4 @@
- A
- B
-+1
-+2
-diff --git a/file0 b/file0
-index 01e79c3..f4615da 100644
---- a/file0
-+++ b/file0
-@@ -1,3 +1,6 @@
- 1
- 2
- 3
-+A
-+B
-+C
-diff --git a/file3 b/file3
-new file mode 100644
-index 0000000..7289e35
---- /dev/null
-+++ b/file3
-@@ -0,0 +1,4 @@
-+A
-+B
-+1
-+2
-
-commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:02:00 2006 +0000
-
-    Third
-
-diff --git a/dir/sub b/dir/sub
-index 8422d40..cead32e 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -2,3 +2,5 @@ A
- B
- C
- D
-+E
-+F
-diff --git a/file1 b/file1
-new file mode 100644
-index 0000000..b1e6722
---- /dev/null
-+++ b/file1
-@@ -0,0 +1,3 @@
-+A
-+B
-+C
-
-commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:01:00 2006 +0000
-
-    Second
-    
-    This is the second commit.
-
-diff --git a/dir/sub b/dir/sub
-index 35d242b..8422d40 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -1,2 +1,4 @@
- A
- B
-+C
-+D
-diff --git a/file0 b/file0
-index 01e79c3..b414108 100644
---- a/file0
-+++ b/file0
-@@ -1,3 +1,6 @@
- 1
- 2
- 3
-+4
-+5
-+6
-diff --git a/file2 b/file2
-deleted file mode 100644
-index 01e79c3..0000000
---- a/file2
-+++ /dev/null
-@@ -1,3 +0,0 @@
--1
--2
--3
-
-commit 444ac553ac7612cc88969031b02b3767fb8a353a
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:00:00 2006 +0000
-
-    Initial
-$
diff --git a/t/t4013/diff.log_--decorate=full_--all b/t/t4013/diff.log_--decorate=full_--all
index 6b0b334..c099399 100644
--- a/t/t4013/diff.log_--decorate=full_--all
+++ b/t/t4013/diff.log_--decorate=full_--all
@@ -26,7 +26,7 @@
 
     Notes added by 'git notes add'
 
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> refs/heads/master)
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> refs/heads/main)
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
diff --git a/t/t4013/diff.log_--decorate=full_--clear-decorations_--all b/t/t4013/diff.log_--decorate=full_--clear-decorations_--all
index 1c030a6..c43684e 100644
--- a/t/t4013/diff.log_--decorate=full_--clear-decorations_--all
+++ b/t/t4013/diff.log_--decorate=full_--clear-decorations_--all
@@ -26,7 +26,7 @@
 
     Notes added by 'git notes add'
 
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> refs/heads/master)
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> refs/heads/main)
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
diff --git a/t/t4013/diff.log_--decorate=full_--decorate-all_--all b/t/t4013/diff.log_--decorate=full_--decorate-all_--all
index d6e7928..48dca61 100644
--- a/t/t4013/diff.log_--decorate=full_--decorate-all_--all
+++ b/t/t4013/diff.log_--decorate=full_--decorate-all_--all
@@ -26,7 +26,7 @@
 
     Notes added by 'git notes add'
 
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> refs/heads/master)
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> refs/heads/main)
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
diff --git a/t/t4013/diff.log_--decorate_--all b/t/t4013/diff.log_--decorate_--all
index c7df1f5..8bbf891 100644
--- a/t/t4013/diff.log_--decorate_--all
+++ b/t/t4013/diff.log_--decorate_--all
@@ -26,7 +26,7 @@
 
     Notes added by 'git notes add'
 
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> master)
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> main)
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
diff --git a/t/t4013/diff.log_--decorate_--clear-decorations_--all b/t/t4013/diff.log_--decorate_--clear-decorations_--all
index 88be82c..86b1353 100644
--- a/t/t4013/diff.log_--decorate_--clear-decorations_--all
+++ b/t/t4013/diff.log_--decorate_--clear-decorations_--all
@@ -26,7 +26,7 @@
 
     Notes added by 'git notes add'
 
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> master)
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> main)
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
diff --git a/t/t4013/diff.log_--decorate_--decorate-all_--all b/t/t4013/diff.log_--decorate_--decorate-all_--all
index 5d22618..59fb17b 100644
--- a/t/t4013/diff.log_--decorate_--decorate-all_--all
+++ b/t/t4013/diff.log_--decorate_--decorate-all_--all
@@ -26,7 +26,7 @@
 
     Notes added by 'git notes add'
 
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> master)
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> main)
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
diff --git a/t/t4013/diff.log_--diff-merges=first-parent_master b/t/t4013/diff.log_--diff-merges=first-parent_main
similarity index 95%
rename from t/t4013/diff.log_--diff-merges=first-parent_master
rename to t/t4013/diff.log_--diff-merges=first-parent_main
index fa63a55..bacee62 100644
--- a/t/t4013/diff.log_--diff-merges=first-parent_master
+++ b/t/t4013/diff.log_--diff-merges=first-parent_main
@@ -1,4 +1,4 @@
-$ git log --diff-merges=first-parent master
+$ git log --diff-merges=first-parent main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_--no-diff-merges_-p_--first-parent_master b/t/t4013/diff.log_--diff-merges=off_-p_--first-parent_main
similarity index 95%
rename from t/t4013/diff.log_--no-diff-merges_-p_--first-parent_master
rename to t/t4013/diff.log_--diff-merges=off_-p_--first-parent_main
index 5970022..fe180fd 100644
--- a/t/t4013/diff.log_--no-diff-merges_-p_--first-parent_master
+++ b/t/t4013/diff.log_--diff-merges=off_-p_--first-parent_main
@@ -1,4 +1,4 @@
-$ git log --no-diff-merges -p --first-parent master
+$ git log --diff-merges=off -p --first-parent main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_--diff-merges=off_-p_--first-parent_master b/t/t4013/diff.log_--diff-merges=off_-p_--first-parent_master
deleted file mode 100644
index 194e893..0000000
--- a/t/t4013/diff.log_--diff-merges=off_-p_--first-parent_master
+++ /dev/null
@@ -1,78 +0,0 @@
-$ git log --diff-merges=off -p --first-parent master
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494 c7a2ab9
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:04:00 2006 +0000
-
-    Merge branch 'side'
-
-commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:02:00 2006 +0000
-
-    Third
-
-diff --git a/dir/sub b/dir/sub
-index 8422d40..cead32e 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -2,3 +2,5 @@ A
- B
- C
- D
-+E
-+F
-diff --git a/file1 b/file1
-new file mode 100644
-index 0000000..b1e6722
---- /dev/null
-+++ b/file1
-@@ -0,0 +1,3 @@
-+A
-+B
-+C
-
-commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:01:00 2006 +0000
-
-    Second
-    
-    This is the second commit.
-
-diff --git a/dir/sub b/dir/sub
-index 35d242b..8422d40 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -1,2 +1,4 @@
- A
- B
-+C
-+D
-diff --git a/file0 b/file0
-index 01e79c3..b414108 100644
---- a/file0
-+++ b/file0
-@@ -1,3 +1,6 @@
- 1
- 2
- 3
-+4
-+5
-+6
-diff --git a/file2 b/file2
-deleted file mode 100644
-index 01e79c3..0000000
---- a/file2
-+++ /dev/null
-@@ -1,3 +0,0 @@
--1
--2
--3
-
-commit 444ac553ac7612cc88969031b02b3767fb8a353a
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:00:00 2006 +0000
-
-    Initial
-$
diff --git a/t/t4013/diff.log_--first-parent_--diff-merges=off_-p_master b/t/t4013/diff.log_--first-parent_--diff-merges=off_-p_main
similarity index 95%
rename from t/t4013/diff.log_--first-parent_--diff-merges=off_-p_master
rename to t/t4013/diff.log_--first-parent_--diff-merges=off_-p_main
index 5d7461a..dca62d4 100644
--- a/t/t4013/diff.log_--first-parent_--diff-merges=off_-p_master
+++ b/t/t4013/diff.log_--first-parent_--diff-merges=off_-p_main
@@ -1,4 +1,4 @@
-$ git log --first-parent --diff-merges=off -p master
+$ git log --first-parent --diff-merges=off -p main
 commit 80e25ffa65bcdbe82ef654b4d06dbbde7945c37f
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_--no-diff-merges_-p_--first-parent_master b/t/t4013/diff.log_--no-diff-merges_-p_--first-parent_main
similarity index 95%
copy from t/t4013/diff.log_--no-diff-merges_-p_--first-parent_master
copy to t/t4013/diff.log_--no-diff-merges_-p_--first-parent_main
index 5970022..0b54118 100644
--- a/t/t4013/diff.log_--no-diff-merges_-p_--first-parent_master
+++ b/t/t4013/diff.log_--no-diff-merges_-p_--first-parent_main
@@ -1,4 +1,4 @@
-$ git log --no-diff-merges -p --first-parent master
+$ git log --no-diff-merges -p --first-parent main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_--patch-with-stat_master_--_dir_ b/t/t4013/diff.log_--patch-with-stat_--summary_main_--_dir_
similarity index 95%
copy from t/t4013/diff.log_--patch-with-stat_master_--_dir_
copy to t/t4013/diff.log_--patch-with-stat_--summary_main_--_dir_
index d5207ca..3ed46cc 100644
--- a/t/t4013/diff.log_--patch-with-stat_master_--_dir_
+++ b/t/t4013/diff.log_--patch-with-stat_--summary_main_--_dir_
@@ -1,4 +1,4 @@
-$ git log --patch-with-stat master -- dir/
+$ git log --patch-with-stat --summary main -- dir/
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_ b/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_
deleted file mode 100644
index a18f147..0000000
--- a/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_
+++ /dev/null
@@ -1,74 +0,0 @@
-$ git log --patch-with-stat --summary master -- dir/
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494 c7a2ab9
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:04:00 2006 +0000
-
-    Merge branch 'side'
-
-commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:03:00 2006 +0000
-
-    Side
----
- dir/sub | 2 ++
- 1 file changed, 2 insertions(+)
-
-diff --git a/dir/sub b/dir/sub
-index 35d242b..7289e35 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -1,2 +1,4 @@
- A
- B
-+1
-+2
-
-commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:02:00 2006 +0000
-
-    Third
----
- dir/sub | 2 ++
- 1 file changed, 2 insertions(+)
-
-diff --git a/dir/sub b/dir/sub
-index 8422d40..cead32e 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -2,3 +2,5 @@ A
- B
- C
- D
-+E
-+F
-
-commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:01:00 2006 +0000
-
-    Second
-    
-    This is the second commit.
----
- dir/sub | 2 ++
- 1 file changed, 2 insertions(+)
-
-diff --git a/dir/sub b/dir/sub
-index 35d242b..8422d40 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -1,2 +1,4 @@
- A
- B
-+C
-+D
-
-commit 444ac553ac7612cc88969031b02b3767fb8a353a
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:00:00 2006 +0000
-
-    Initial
-$
diff --git a/t/t4013/diff.log_--patch-with-stat_master b/t/t4013/diff.log_--patch-with-stat_main
similarity index 97%
rename from t/t4013/diff.log_--patch-with-stat_master
rename to t/t4013/diff.log_--patch-with-stat_main
index ae425c4..2e12b55 100644
--- a/t/t4013/diff.log_--patch-with-stat_master
+++ b/t/t4013/diff.log_--patch-with-stat_main
@@ -1,4 +1,4 @@
-$ git log --patch-with-stat master
+$ git log --patch-with-stat main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_--patch-with-stat_master_--_dir_ b/t/t4013/diff.log_--patch-with-stat_main_--_dir_
similarity index 96%
rename from t/t4013/diff.log_--patch-with-stat_master_--_dir_
rename to t/t4013/diff.log_--patch-with-stat_main_--_dir_
index d5207ca..d511ea7 100644
--- a/t/t4013/diff.log_--patch-with-stat_master_--_dir_
+++ b/t/t4013/diff.log_--patch-with-stat_main_--_dir_
@@ -1,4 +1,4 @@
-$ git log --patch-with-stat master -- dir/
+$ git log --patch-with-stat main -- dir/
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_main
similarity index 97%
rename from t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master
rename to t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_main
index 0fc1e8c..3cfd0e6 100644
--- a/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master
+++ b/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_main
@@ -1,4 +1,4 @@
-$ git log --root --cc --patch-with-stat --summary master
+$ git log --root --cc --patch-with-stat --summary main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_--root_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_--patch-with-stat_--summary_main
similarity index 97%
rename from t/t4013/diff.log_--root_--patch-with-stat_--summary_master
rename to t/t4013/diff.log_--root_--patch-with-stat_--summary_main
index dffc09d..9f4d6df 100644
--- a/t/t4013/diff.log_--root_--patch-with-stat_--summary_master
+++ b/t/t4013/diff.log_--root_--patch-with-stat_--summary_main
@@ -1,4 +1,4 @@
-$ git log --root --patch-with-stat --summary master
+$ git log --root --patch-with-stat --summary main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_--root_--patch-with-stat_master b/t/t4013/diff.log_--root_--patch-with-stat_main
similarity index 97%
rename from t/t4013/diff.log_--root_--patch-with-stat_master
rename to t/t4013/diff.log_--root_--patch-with-stat_main
index 55aa980..0d69ae2 100644
--- a/t/t4013/diff.log_--root_--patch-with-stat_master
+++ b/t/t4013/diff.log_--root_--patch-with-stat_main
@@ -1,4 +1,4 @@
-$ git log --root --patch-with-stat master
+$ git log --root --patch-with-stat main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_main
similarity index 97%
copy from t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master
copy to t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_main
index d1d32bd..1b71add 100644
--- a/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master
+++ b/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_main
@@ -1,4 +1,4 @@
-$ git whatchanged --root -c --patch-with-stat --summary master
+$ git log --root -c --patch-with-stat --summary main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master
deleted file mode 100644
index 019d85f..0000000
--- a/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master
+++ /dev/null
@@ -1,199 +0,0 @@
-$ git log --root -c --patch-with-stat --summary master
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494 c7a2ab9
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:04:00 2006 +0000
-
-    Merge branch 'side'
-
- dir/sub | 2 ++
- file0   | 3 +++
- 2 files changed, 5 insertions(+)
-
-diff --combined dir/sub
-index cead32e,7289e35..992913c
---- a/dir/sub
-+++ b/dir/sub
-@@@ -1,6 -1,4 +1,8 @@@
-  A
-  B
- +C
- +D
- +E
- +F
-+ 1
-+ 2
-diff --combined file0
-index b414108,f4615da..10a8a9f
---- a/file0
-+++ b/file0
-@@@ -1,6 -1,6 +1,9 @@@
-  1
-  2
-  3
- +4
- +5
- +6
-+ A
-+ B
-+ C
-
-commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:03:00 2006 +0000
-
-    Side
----
- dir/sub | 2 ++
- file0   | 3 +++
- file3   | 4 ++++
- 3 files changed, 9 insertions(+)
- create mode 100644 file3
-
-diff --git a/dir/sub b/dir/sub
-index 35d242b..7289e35 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -1,2 +1,4 @@
- A
- B
-+1
-+2
-diff --git a/file0 b/file0
-index 01e79c3..f4615da 100644
---- a/file0
-+++ b/file0
-@@ -1,3 +1,6 @@
- 1
- 2
- 3
-+A
-+B
-+C
-diff --git a/file3 b/file3
-new file mode 100644
-index 0000000..7289e35
---- /dev/null
-+++ b/file3
-@@ -0,0 +1,4 @@
-+A
-+B
-+1
-+2
-
-commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:02:00 2006 +0000
-
-    Third
----
- dir/sub | 2 ++
- file1   | 3 +++
- 2 files changed, 5 insertions(+)
- create mode 100644 file1
-
-diff --git a/dir/sub b/dir/sub
-index 8422d40..cead32e 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -2,3 +2,5 @@ A
- B
- C
- D
-+E
-+F
-diff --git a/file1 b/file1
-new file mode 100644
-index 0000000..b1e6722
---- /dev/null
-+++ b/file1
-@@ -0,0 +1,3 @@
-+A
-+B
-+C
-
-commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:01:00 2006 +0000
-
-    Second
-    
-    This is the second commit.
----
- dir/sub | 2 ++
- file0   | 3 +++
- file2   | 3 ---
- 3 files changed, 5 insertions(+), 3 deletions(-)
- delete mode 100644 file2
-
-diff --git a/dir/sub b/dir/sub
-index 35d242b..8422d40 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -1,2 +1,4 @@
- A
- B
-+C
-+D
-diff --git a/file0 b/file0
-index 01e79c3..b414108 100644
---- a/file0
-+++ b/file0
-@@ -1,3 +1,6 @@
- 1
- 2
- 3
-+4
-+5
-+6
-diff --git a/file2 b/file2
-deleted file mode 100644
-index 01e79c3..0000000
---- a/file2
-+++ /dev/null
-@@ -1,3 +0,0 @@
--1
--2
--3
-
-commit 444ac553ac7612cc88969031b02b3767fb8a353a
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:00:00 2006 +0000
-
-    Initial
----
- dir/sub | 2 ++
- file0   | 3 +++
- file2   | 3 +++
- 3 files changed, 8 insertions(+)
- create mode 100644 dir/sub
- create mode 100644 file0
- create mode 100644 file2
-
-diff --git a/dir/sub b/dir/sub
-new file mode 100644
-index 0000000..35d242b
---- /dev/null
-+++ b/dir/sub
-@@ -0,0 +1,2 @@
-+A
-+B
-diff --git a/file0 b/file0
-new file mode 100644
-index 0000000..01e79c3
---- /dev/null
-+++ b/file0
-@@ -0,0 +1,3 @@
-+1
-+2
-+3
-diff --git a/file2 b/file2
-new file mode 100644
-index 0000000..01e79c3
---- /dev/null
-+++ b/file2
-@@ -0,0 +1,3 @@
-+1
-+2
-+3
-$
diff --git a/t/t4013/diff.log_--root_-p_master b/t/t4013/diff.log_--root_-p_main
similarity index 98%
rename from t/t4013/diff.log_--root_-p_master
rename to t/t4013/diff.log_--root_-p_main
index b42c334..0458129 100644
--- a/t/t4013/diff.log_--root_-p_master
+++ b/t/t4013/diff.log_--root_-p_main
@@ -1,4 +1,4 @@
-$ git log --root -p master
+$ git log --root -p main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_master b/t/t4013/diff.log_--root_main
similarity index 96%
copy from t/t4013/diff.log_master
copy to t/t4013/diff.log_--root_main
index a8f6ce5..d5e90fd 100644
--- a/t/t4013/diff.log_master
+++ b/t/t4013/diff.log_--root_main
@@ -1,4 +1,4 @@
-$ git log master
+$ git log --root main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_--root_master b/t/t4013/diff.log_--root_master
deleted file mode 100644
index e8f4615..0000000
--- a/t/t4013/diff.log_--root_master
+++ /dev/null
@@ -1,34 +0,0 @@
-$ git log --root master
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494 c7a2ab9
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:04:00 2006 +0000
-
-    Merge branch 'side'
-
-commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:03:00 2006 +0000
-
-    Side
-
-commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:02:00 2006 +0000
-
-    Third
-
-commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:01:00 2006 +0000
-
-    Second
-    
-    This is the second commit.
-
-commit 444ac553ac7612cc88969031b02b3767fb8a353a
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:00:00 2006 +0000
-
-    Initial
-$
diff --git a/t/t4013/diff.log_-GF_-p_--pickaxe-all_master b/t/t4013/diff.log_-GF_-p_--pickaxe-all_main
similarity index 90%
rename from t/t4013/diff.log_-GF_-p_--pickaxe-all_master
rename to t/t4013/diff.log_-GF_-p_--pickaxe-all_main
index d36f880..1f7a497 100644
--- a/t/t4013/diff.log_-GF_-p_--pickaxe-all_master
+++ b/t/t4013/diff.log_-GF_-p_--pickaxe-all_main
@@ -1,4 +1,4 @@
-$ git log -GF -p --pickaxe-all master
+$ git log -GF -p --pickaxe-all main
 commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:02:00 2006 +0000
diff --git a/t/t4013/diff.log_-SF_-p_master b/t/t4013/diff.log_-GF_-p_main
similarity index 91%
rename from t/t4013/diff.log_-SF_-p_master
rename to t/t4013/diff.log_-GF_-p_main
index 5e32438..c80dda4 100644
--- a/t/t4013/diff.log_-SF_-p_master
+++ b/t/t4013/diff.log_-GF_-p_main
@@ -1,4 +1,4 @@
-$ git log -SF -p master
+$ git log -GF -p main
 commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:02:00 2006 +0000
diff --git a/t/t4013/diff.log_-GF_-p_master b/t/t4013/diff.log_-GF_-p_master
deleted file mode 100644
index 9d93f2c..0000000
--- a/t/t4013/diff.log_-GF_-p_master
+++ /dev/null
@@ -1,18 +0,0 @@
-$ git log -GF -p master
-commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:02:00 2006 +0000
-
-    Third
-
-diff --git a/dir/sub b/dir/sub
-index 8422d40..cead32e 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -2,3 +2,5 @@ A
- B
- C
- D
-+E
-+F
-$
diff --git a/t/t4013/diff.log_-GF_master b/t/t4013/diff.log_-GF_main
similarity index 86%
copy from t/t4013/diff.log_-GF_master
copy to t/t4013/diff.log_-GF_main
index 4c6708d..b94a7f7 100644
--- a/t/t4013/diff.log_-GF_master
+++ b/t/t4013/diff.log_-GF_main
@@ -1,4 +1,4 @@
-$ git log -GF master
+$ git log -GF main
 commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:02:00 2006 +0000
diff --git a/t/t4013/diff.log_-IA_-IB_-I1_-I2_-p_master b/t/t4013/diff.log_-IA_-IB_-I1_-I2_-p_main
similarity index 97%
rename from t/t4013/diff.log_-IA_-IB_-I1_-I2_-p_master
rename to t/t4013/diff.log_-IA_-IB_-I1_-I2_-p_main
index 929f35a..67e26b4 100644
--- a/t/t4013/diff.log_-IA_-IB_-I1_-I2_-p_master
+++ b/t/t4013/diff.log_-IA_-IB_-I1_-I2_-p_main
@@ -1,4 +1,4 @@
-$ git log -IA -IB -I1 -I2 -p master
+$ git log -IA -IB -I1 -I2 -p main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_-SF_-p_master b/t/t4013/diff.log_-SF_-p_main
similarity index 91%
copy from t/t4013/diff.log_-SF_-p_master
copy to t/t4013/diff.log_-SF_-p_main
index 5e32438..fa82ac1 100644
--- a/t/t4013/diff.log_-SF_-p_master
+++ b/t/t4013/diff.log_-SF_-p_main
@@ -1,4 +1,4 @@
-$ git log -SF -p master
+$ git log -SF -p main
 commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:02:00 2006 +0000
diff --git a/t/t4013/diff.log_-GF_master b/t/t4013/diff.log_-SF_main
similarity index 86%
rename from t/t4013/diff.log_-GF_master
rename to t/t4013/diff.log_-SF_main
index 4c6708d..dbf770d 100644
--- a/t/t4013/diff.log_-GF_master
+++ b/t/t4013/diff.log_-SF_main
@@ -1,4 +1,4 @@
-$ git log -GF master
+$ git log -SF main
 commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:02:00 2006 +0000
diff --git a/t/t4013/diff.log_-SF_main_--max-count=0 b/t/t4013/diff.log_-SF_main_--max-count=0
new file mode 100644
index 0000000..683b17e
--- /dev/null
+++ b/t/t4013/diff.log_-SF_main_--max-count=0
@@ -0,0 +1,2 @@
+$ git log -SF main --max-count=0
+$
diff --git a/t/t4013/diff.log_-GF_master b/t/t4013/diff.log_-SF_main_--max-count=1
similarity index 80%
copy from t/t4013/diff.log_-GF_master
copy to t/t4013/diff.log_-SF_main_--max-count=1
index 4c6708d..2102426 100644
--- a/t/t4013/diff.log_-GF_master
+++ b/t/t4013/diff.log_-SF_main_--max-count=1
@@ -1,4 +1,4 @@
-$ git log -GF master
+$ git log -SF main --max-count=1
 commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:02:00 2006 +0000
diff --git a/t/t4013/diff.log_-GF_master b/t/t4013/diff.log_-SF_main_--max-count=2
similarity index 80%
copy from t/t4013/diff.log_-GF_master
copy to t/t4013/diff.log_-SF_main_--max-count=2
index 4c6708d..23e12a4 100644
--- a/t/t4013/diff.log_-GF_master
+++ b/t/t4013/diff.log_-SF_main_--max-count=2
@@ -1,4 +1,4 @@
-$ git log -GF master
+$ git log -SF main --max-count=2
 commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:02:00 2006 +0000
diff --git a/t/t4013/diff.log_-SF_master b/t/t4013/diff.log_-SF_master
deleted file mode 100644
index c1599f2..0000000
--- a/t/t4013/diff.log_-SF_master
+++ /dev/null
@@ -1,7 +0,0 @@
-$ git log -SF master
-commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:02:00 2006 +0000
-
-    Third
-$
diff --git a/t/t4013/diff.log_-SF_master_--max-count=0 b/t/t4013/diff.log_-SF_master_--max-count=0
deleted file mode 100644
index c1fc6c8..0000000
--- a/t/t4013/diff.log_-SF_master_--max-count=0
+++ /dev/null
@@ -1,2 +0,0 @@
-$ git log -SF master --max-count=0
-$
diff --git a/t/t4013/diff.log_-SF_master_--max-count=1 b/t/t4013/diff.log_-SF_master_--max-count=1
deleted file mode 100644
index c981a03..0000000
--- a/t/t4013/diff.log_-SF_master_--max-count=1
+++ /dev/null
@@ -1,7 +0,0 @@
-$ git log -SF master --max-count=1
-commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:02:00 2006 +0000
-
-    Third
-$
diff --git a/t/t4013/diff.log_-SF_master_--max-count=2 b/t/t4013/diff.log_-SF_master_--max-count=2
deleted file mode 100644
index a6c55fd..0000000
--- a/t/t4013/diff.log_-SF_master_--max-count=2
+++ /dev/null
@@ -1,7 +0,0 @@
-$ git log -SF master --max-count=2
-commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:02:00 2006 +0000
-
-    Third
-$
diff --git a/t/t4013/diff.log_-GF_master b/t/t4013/diff.log_-S_F_main
similarity index 86%
copy from t/t4013/diff.log_-GF_master
copy to t/t4013/diff.log_-S_F_main
index 4c6708d..a75a42e 100644
--- a/t/t4013/diff.log_-GF_master
+++ b/t/t4013/diff.log_-S_F_main
@@ -1,4 +1,4 @@
-$ git log -GF master
+$ git log -S F main
 commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:02:00 2006 +0000
diff --git a/t/t4013/diff.log_-S_F_master b/t/t4013/diff.log_-S_F_master
deleted file mode 100644
index 978d2b4..0000000
--- a/t/t4013/diff.log_-S_F_master
+++ /dev/null
@@ -1,7 +0,0 @@
-$ git log -S F master
-commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:02:00 2006 +0000
-
-    Third
-$
diff --git a/t/t4013/diff.log_-m_-p_master b/t/t4013/diff.log_-c_-m_-p_main
similarity index 98%
copy from t/t4013/diff.log_-m_-p_master
copy to t/t4013/diff.log_-c_-m_-p_main
index 9ca62a0..427f732 100644
--- a/t/t4013/diff.log_-m_-p_master
+++ b/t/t4013/diff.log_-c_-m_-p_main
@@ -1,4 +1,4 @@
-$ git log -m -p master
+$ git log -c -m -p main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0)
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_-c_-m_-p_master b/t/t4013/diff.log_-c_-m_-p_master
deleted file mode 100644
index b660f3d..0000000
--- a/t/t4013/diff.log_-c_-m_-p_master
+++ /dev/null
@@ -1,200 +0,0 @@
-$ git log -c -m -p master
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0)
-Merge: 9a6d494 c7a2ab9
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:04:00 2006 +0000
-
-    Merge branch 'side'
-
-diff --git a/dir/sub b/dir/sub
-index cead32e..992913c 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -4,3 +4,5 @@ C
- D
- E
- F
-+1
-+2
-diff --git a/file0 b/file0
-index b414108..10a8a9f 100644
---- a/file0
-+++ b/file0
-@@ -4,3 +4,6 @@
- 4
- 5
- 6
-+A
-+B
-+C
-
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a)
-Merge: 9a6d494 c7a2ab9
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:04:00 2006 +0000
-
-    Merge branch 'side'
-
-diff --git a/dir/sub b/dir/sub
-index 7289e35..992913c 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -1,4 +1,8 @@
- A
- B
-+C
-+D
-+E
-+F
- 1
- 2
-diff --git a/file0 b/file0
-index f4615da..10a8a9f 100644
---- a/file0
-+++ b/file0
-@@ -1,6 +1,9 @@
- 1
- 2
- 3
-+4
-+5
-+6
- A
- B
- C
-diff --git a/file1 b/file1
-new file mode 100644
-index 0000000..b1e6722
---- /dev/null
-+++ b/file1
-@@ -0,0 +1,3 @@
-+A
-+B
-+C
-diff --git a/file2 b/file2
-deleted file mode 100644
-index 01e79c3..0000000
---- a/file2
-+++ /dev/null
-@@ -1,3 +0,0 @@
--1
--2
--3
-diff --git a/file3 b/file3
-deleted file mode 100644
-index 7289e35..0000000
---- a/file3
-+++ /dev/null
-@@ -1,4 +0,0 @@
--A
--B
--1
--2
-
-commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:03:00 2006 +0000
-
-    Side
-
-diff --git a/dir/sub b/dir/sub
-index 35d242b..7289e35 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -1,2 +1,4 @@
- A
- B
-+1
-+2
-diff --git a/file0 b/file0
-index 01e79c3..f4615da 100644
---- a/file0
-+++ b/file0
-@@ -1,3 +1,6 @@
- 1
- 2
- 3
-+A
-+B
-+C
-diff --git a/file3 b/file3
-new file mode 100644
-index 0000000..7289e35
---- /dev/null
-+++ b/file3
-@@ -0,0 +1,4 @@
-+A
-+B
-+1
-+2
-
-commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:02:00 2006 +0000
-
-    Third
-
-diff --git a/dir/sub b/dir/sub
-index 8422d40..cead32e 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -2,3 +2,5 @@ A
- B
- C
- D
-+E
-+F
-diff --git a/file1 b/file1
-new file mode 100644
-index 0000000..b1e6722
---- /dev/null
-+++ b/file1
-@@ -0,0 +1,3 @@
-+A
-+B
-+C
-
-commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:01:00 2006 +0000
-
-    Second
-    
-    This is the second commit.
-
-diff --git a/dir/sub b/dir/sub
-index 35d242b..8422d40 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -1,2 +1,4 @@
- A
- B
-+C
-+D
-diff --git a/file0 b/file0
-index 01e79c3..b414108 100644
---- a/file0
-+++ b/file0
-@@ -1,3 +1,6 @@
- 1
- 2
- 3
-+4
-+5
-+6
-diff --git a/file2 b/file2
-deleted file mode 100644
-index 01e79c3..0000000
---- a/file2
-+++ /dev/null
-@@ -1,3 +0,0 @@
--1
--2
--3
-
-commit 444ac553ac7612cc88969031b02b3767fb8a353a
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:00:00 2006 +0000
-
-    Initial
-$
diff --git a/t/t4013/diff.log_-m_--raw_master b/t/t4013/diff.log_-m_--raw_main
similarity index 98%
rename from t/t4013/diff.log_-m_--raw_master
rename to t/t4013/diff.log_-m_--raw_main
index cd2ecc4..31d9bc7 100644
--- a/t/t4013/diff.log_-m_--raw_master
+++ b/t/t4013/diff.log_-m_--raw_main
@@ -1,4 +1,4 @@
-$ git log -m --raw master
+$ git log -m --raw main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0)
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_-m_--stat_master b/t/t4013/diff.log_-m_--stat_main
similarity index 97%
rename from t/t4013/diff.log_-m_--stat_master
rename to t/t4013/diff.log_-m_--stat_main
index c7db084..4c89092 100644
--- a/t/t4013/diff.log_-m_--stat_master
+++ b/t/t4013/diff.log_-m_--stat_main
@@ -1,4 +1,4 @@
-$ git log -m --stat master
+$ git log -m --stat main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0)
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_-p_--first-parent_master b/t/t4013/diff.log_-m_-p_--first-parent_main
similarity index 97%
rename from t/t4013/diff.log_-p_--first-parent_master
rename to t/t4013/diff.log_-m_-p_--first-parent_main
index 28840eb..459e107 100644
--- a/t/t4013/diff.log_-p_--first-parent_master
+++ b/t/t4013/diff.log_-m_-p_--first-parent_main
@@ -1,4 +1,4 @@
-$ git log -p --first-parent master
+$ git log -m -p --first-parent main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_-m_-p_--first-parent_master b/t/t4013/diff.log_-m_-p_--first-parent_master
deleted file mode 100644
index 7a0073f..0000000
--- a/t/t4013/diff.log_-m_-p_--first-parent_master
+++ /dev/null
@@ -1,100 +0,0 @@
-$ git log -m -p --first-parent master
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494 c7a2ab9
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:04:00 2006 +0000
-
-    Merge branch 'side'
-
-diff --git a/dir/sub b/dir/sub
-index cead32e..992913c 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -4,3 +4,5 @@ C
- D
- E
- F
-+1
-+2
-diff --git a/file0 b/file0
-index b414108..10a8a9f 100644
---- a/file0
-+++ b/file0
-@@ -4,3 +4,6 @@
- 4
- 5
- 6
-+A
-+B
-+C
-
-commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:02:00 2006 +0000
-
-    Third
-
-diff --git a/dir/sub b/dir/sub
-index 8422d40..cead32e 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -2,3 +2,5 @@ A
- B
- C
- D
-+E
-+F
-diff --git a/file1 b/file1
-new file mode 100644
-index 0000000..b1e6722
---- /dev/null
-+++ b/file1
-@@ -0,0 +1,3 @@
-+A
-+B
-+C
-
-commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:01:00 2006 +0000
-
-    Second
-    
-    This is the second commit.
-
-diff --git a/dir/sub b/dir/sub
-index 35d242b..8422d40 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -1,2 +1,4 @@
- A
- B
-+C
-+D
-diff --git a/file0 b/file0
-index 01e79c3..b414108 100644
---- a/file0
-+++ b/file0
-@@ -1,3 +1,6 @@
- 1
- 2
- 3
-+4
-+5
-+6
-diff --git a/file2 b/file2
-deleted file mode 100644
-index 01e79c3..0000000
---- a/file2
-+++ /dev/null
-@@ -1,3 +0,0 @@
--1
--2
--3
-
-commit 444ac553ac7612cc88969031b02b3767fb8a353a
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:00:00 2006 +0000
-
-    Initial
-$
diff --git a/t/t4013/diff.log_-m_-p_master b/t/t4013/diff.log_-m_-p_main
similarity index 98%
rename from t/t4013/diff.log_-m_-p_master
rename to t/t4013/diff.log_-m_-p_main
index 9ca62a0..07453c5 100644
--- a/t/t4013/diff.log_-m_-p_master
+++ b/t/t4013/diff.log_-m_-p_main
@@ -1,4 +1,4 @@
-$ git log -m -p master
+$ git log -m -p main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0)
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_-p_--diff-merges=first-parent_master b/t/t4013/diff.log_-p_--diff-merges=first-parent_main
similarity index 97%
rename from t/t4013/diff.log_-p_--diff-merges=first-parent_master
rename to t/t4013/diff.log_-p_--diff-merges=first-parent_main
index 9538a27..264a2f3 100644
--- a/t/t4013/diff.log_-p_--diff-merges=first-parent_master
+++ b/t/t4013/diff.log_-p_--diff-merges=first-parent_main
@@ -1,4 +1,4 @@
-$ git log -p --diff-merges=first-parent master
+$ git log -p --diff-merges=first-parent main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_-p_--first-parent_master b/t/t4013/diff.log_-p_--first-parent_main
similarity index 97%
copy from t/t4013/diff.log_-p_--first-parent_master
copy to t/t4013/diff.log_-p_--first-parent_main
index 28840eb..2479808 100644
--- a/t/t4013/diff.log_-p_--first-parent_master
+++ b/t/t4013/diff.log_-p_--first-parent_main
@@ -1,4 +1,4 @@
-$ git log -p --first-parent master
+$ git log -p --first-parent main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_-p_master b/t/t4013/diff.log_-p_main
similarity index 98%
rename from t/t4013/diff.log_-p_master
rename to t/t4013/diff.log_-p_main
index bf1326d..c82b4db 100644
--- a/t/t4013/diff.log_-p_master
+++ b/t/t4013/diff.log_-p_main
@@ -1,4 +1,4 @@
-$ git log -p master
+$ git log -p main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_master b/t/t4013/diff.log_main
similarity index 97%
rename from t/t4013/diff.log_master
rename to t/t4013/diff.log_main
index a8f6ce5..50401f7 100644
--- a/t/t4013/diff.log_master
+++ b/t/t4013/diff.log_main
@@ -1,4 +1,4 @@
-$ git log master
+$ git log main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.noellipses-diff-tree_-c_--abbrev_master b/t/t4013/diff.noellipses-diff-tree_-c_--abbrev_main
similarity index 81%
rename from t/t4013/diff.noellipses-diff-tree_-c_--abbrev_master
rename to t/t4013/diff.noellipses-diff-tree_-c_--abbrev_main
index bb80f01..3aa1f80 100644
--- a/t/t4013/diff.noellipses-diff-tree_-c_--abbrev_master
+++ b/t/t4013/diff.noellipses-diff-tree_-c_--abbrev_main
@@ -1,4 +1,4 @@
-$ git diff-tree -c --abbrev master
+$ git diff-tree -c --abbrev main
 59d314ad6f356dd08601a4cd5e530381da3e3c64
 ::100644 100644 100644 cead32e 7289e35 992913c MM	dir/sub
 ::100644 100644 100644 b414108 f4615da 10a8a9f MM	file0
diff --git a/t/t4013/diff.noellipses-whatchanged_--root_master b/t/t4013/diff.noellipses-whatchanged_--root_main
similarity index 96%
rename from t/t4013/diff.noellipses-whatchanged_--root_master
rename to t/t4013/diff.noellipses-whatchanged_--root_main
index c2cfd4e..2bec055 100644
--- a/t/t4013/diff.noellipses-whatchanged_--root_master
+++ b/t/t4013/diff.noellipses-whatchanged_--root_main
@@ -1,4 +1,4 @@
-$ git whatchanged --root master
+$ git whatchanged --root main
 commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:03:00 2006 +0000
diff --git a/t/t4013/diff.noellipses-whatchanged_-SF_master b/t/t4013/diff.noellipses-whatchanged_-SF_main
similarity index 85%
rename from t/t4013/diff.noellipses-whatchanged_-SF_master
rename to t/t4013/diff.noellipses-whatchanged_-SF_main
index b36ce58..0c1476d 100644
--- a/t/t4013/diff.noellipses-whatchanged_-SF_master
+++ b/t/t4013/diff.noellipses-whatchanged_-SF_main
@@ -1,4 +1,4 @@
-$ git whatchanged -SF master
+$ git whatchanged -SF main
 commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:02:00 2006 +0000
diff --git a/t/t4013/diff.noellipses-whatchanged_master b/t/t4013/diff.noellipses-whatchanged_main
similarity index 96%
rename from t/t4013/diff.noellipses-whatchanged_master
rename to t/t4013/diff.noellipses-whatchanged_main
index 55e500f..c48d285 100644
--- a/t/t4013/diff.noellipses-whatchanged_master
+++ b/t/t4013/diff.noellipses-whatchanged_main
@@ -1,4 +1,4 @@
-$ git whatchanged master
+$ git whatchanged main
 commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:03:00 2006 +0000
diff --git a/t/t4013/diff.show_--first-parent_master b/t/t4013/diff.show_--first-parent_main
similarity index 92%
rename from t/t4013/diff.show_--first-parent_master
rename to t/t4013/diff.show_--first-parent_main
index 3dcbe47..480502d 100644
--- a/t/t4013/diff.show_--first-parent_master
+++ b/t/t4013/diff.show_--first-parent_main
@@ -1,4 +1,4 @@
-$ git show --first-parent master
+$ git show --first-parent main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.show_-c_master b/t/t4013/diff.show_-c_main
similarity index 95%
rename from t/t4013/diff.show_-c_master
rename to t/t4013/diff.show_-c_main
index 81aba8d..74ef8bc 100644
--- a/t/t4013/diff.show_-c_master
+++ b/t/t4013/diff.show_-c_main
@@ -1,4 +1,4 @@
-$ git show -c master
+$ git show -c main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.show_-m_master b/t/t4013/diff.show_-m_main
similarity index 97%
rename from t/t4013/diff.show_-m_master
rename to t/t4013/diff.show_-m_main
index 4ea2ee4..8fd5673 100644
--- a/t/t4013/diff.show_-m_master
+++ b/t/t4013/diff.show_-m_main
@@ -1,4 +1,4 @@
-$ git show -m master
+$ git show -m main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0)
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.show_master b/t/t4013/diff.show_main
similarity index 95%
rename from t/t4013/diff.show_master
rename to t/t4013/diff.show_main
index fb08ce0..630b52a 100644
--- a/t/t4013/diff.show_master
+++ b/t/t4013/diff.show_main
@@ -1,4 +1,4 @@
-$ git show master
+$ git show main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.whatchanged_--patch-with-stat_--summary_master_--_dir_ b/t/t4013/diff.whatchanged_--patch-with-stat_--summary_main_--_dir_
similarity index 93%
rename from t/t4013/diff.whatchanged_--patch-with-stat_--summary_master_--_dir_
rename to t/t4013/diff.whatchanged_--patch-with-stat_--summary_main_--_dir_
index c8b6af2..ce0754d 100644
--- a/t/t4013/diff.whatchanged_--patch-with-stat_--summary_master_--_dir_
+++ b/t/t4013/diff.whatchanged_--patch-with-stat_--summary_main_--_dir_
@@ -1,4 +1,4 @@
-$ git whatchanged --patch-with-stat --summary master -- dir/
+$ git whatchanged --patch-with-stat --summary main -- dir/
 commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:03:00 2006 +0000
diff --git a/t/t4013/diff.whatchanged_--patch-with-stat_master b/t/t4013/diff.whatchanged_--patch-with-stat_main
similarity index 97%
rename from t/t4013/diff.whatchanged_--patch-with-stat_master
rename to t/t4013/diff.whatchanged_--patch-with-stat_main
index 1ac431b..aabccf3 100644
--- a/t/t4013/diff.whatchanged_--patch-with-stat_master
+++ b/t/t4013/diff.whatchanged_--patch-with-stat_main
@@ -1,4 +1,4 @@
-$ git whatchanged --patch-with-stat master
+$ git whatchanged --patch-with-stat main
 commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:03:00 2006 +0000
diff --git a/t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_ b/t/t4013/diff.whatchanged_--patch-with-stat_main_--_dir_
similarity index 94%
rename from t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_
rename to t/t4013/diff.whatchanged_--patch-with-stat_main_--_dir_
index b30c285..c05a0e8 100644
--- a/t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_
+++ b/t/t4013/diff.whatchanged_--patch-with-stat_main_--_dir_
@@ -1,4 +1,4 @@
-$ git whatchanged --patch-with-stat master -- dir/
+$ git whatchanged --patch-with-stat main -- dir/
 commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:03:00 2006 +0000
diff --git a/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_main
similarity index 97%
copy from t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master
copy to t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_main
index 0fc1e8c..1f74b1b 100644
--- a/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master
+++ b/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_main
@@ -1,4 +1,4 @@
-$ git log --root --cc --patch-with-stat --summary master
+$ git whatchanged --root --cc --patch-with-stat --summary main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master
deleted file mode 100644
index 30aae78..0000000
--- a/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master
+++ /dev/null
@@ -1,199 +0,0 @@
-$ git whatchanged --root --cc --patch-with-stat --summary master
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494 c7a2ab9
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:04:00 2006 +0000
-
-    Merge branch 'side'
-
- dir/sub | 2 ++
- file0   | 3 +++
- 2 files changed, 5 insertions(+)
-
-diff --cc dir/sub
-index cead32e,7289e35..992913c
---- a/dir/sub
-+++ b/dir/sub
-@@@ -1,6 -1,4 +1,8 @@@
-  A
-  B
- +C
- +D
- +E
- +F
-+ 1
-+ 2
-diff --cc file0
-index b414108,f4615da..10a8a9f
---- a/file0
-+++ b/file0
-@@@ -1,6 -1,6 +1,9 @@@
-  1
-  2
-  3
- +4
- +5
- +6
-+ A
-+ B
-+ C
-
-commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:03:00 2006 +0000
-
-    Side
----
- dir/sub | 2 ++
- file0   | 3 +++
- file3   | 4 ++++
- 3 files changed, 9 insertions(+)
- create mode 100644 file3
-
-diff --git a/dir/sub b/dir/sub
-index 35d242b..7289e35 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -1,2 +1,4 @@
- A
- B
-+1
-+2
-diff --git a/file0 b/file0
-index 01e79c3..f4615da 100644
---- a/file0
-+++ b/file0
-@@ -1,3 +1,6 @@
- 1
- 2
- 3
-+A
-+B
-+C
-diff --git a/file3 b/file3
-new file mode 100644
-index 0000000..7289e35
---- /dev/null
-+++ b/file3
-@@ -0,0 +1,4 @@
-+A
-+B
-+1
-+2
-
-commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:02:00 2006 +0000
-
-    Third
----
- dir/sub | 2 ++
- file1   | 3 +++
- 2 files changed, 5 insertions(+)
- create mode 100644 file1
-
-diff --git a/dir/sub b/dir/sub
-index 8422d40..cead32e 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -2,3 +2,5 @@ A
- B
- C
- D
-+E
-+F
-diff --git a/file1 b/file1
-new file mode 100644
-index 0000000..b1e6722
---- /dev/null
-+++ b/file1
-@@ -0,0 +1,3 @@
-+A
-+B
-+C
-
-commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:01:00 2006 +0000
-
-    Second
-    
-    This is the second commit.
----
- dir/sub | 2 ++
- file0   | 3 +++
- file2   | 3 ---
- 3 files changed, 5 insertions(+), 3 deletions(-)
- delete mode 100644 file2
-
-diff --git a/dir/sub b/dir/sub
-index 35d242b..8422d40 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -1,2 +1,4 @@
- A
- B
-+C
-+D
-diff --git a/file0 b/file0
-index 01e79c3..b414108 100644
---- a/file0
-+++ b/file0
-@@ -1,3 +1,6 @@
- 1
- 2
- 3
-+4
-+5
-+6
-diff --git a/file2 b/file2
-deleted file mode 100644
-index 01e79c3..0000000
---- a/file2
-+++ /dev/null
-@@ -1,3 +0,0 @@
--1
--2
--3
-
-commit 444ac553ac7612cc88969031b02b3767fb8a353a
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:00:00 2006 +0000
-
-    Initial
----
- dir/sub | 2 ++
- file0   | 3 +++
- file2   | 3 +++
- 3 files changed, 8 insertions(+)
- create mode 100644 dir/sub
- create mode 100644 file0
- create mode 100644 file2
-
-diff --git a/dir/sub b/dir/sub
-new file mode 100644
-index 0000000..35d242b
---- /dev/null
-+++ b/dir/sub
-@@ -0,0 +1,2 @@
-+A
-+B
-diff --git a/file0 b/file0
-new file mode 100644
-index 0000000..01e79c3
---- /dev/null
-+++ b/file0
-@@ -0,0 +1,3 @@
-+1
-+2
-+3
-diff --git a/file2 b/file2
-new file mode 100644
-index 0000000..01e79c3
---- /dev/null
-+++ b/file2
-@@ -0,0 +1,3 @@
-+1
-+2
-+3
-$
diff --git a/t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_main
similarity index 97%
rename from t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master
rename to t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_main
index db90e51..80d9812 100644
--- a/t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master
+++ b/t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_main
@@ -1,4 +1,4 @@
-$ git whatchanged --root --patch-with-stat --summary master
+$ git whatchanged --root --patch-with-stat --summary main
 commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:03:00 2006 +0000
diff --git a/t/t4013/diff.whatchanged_--root_--patch-with-stat_master b/t/t4013/diff.whatchanged_--root_--patch-with-stat_main
similarity index 97%
rename from t/t4013/diff.whatchanged_--root_--patch-with-stat_master
rename to t/t4013/diff.whatchanged_--root_--patch-with-stat_main
index 9a6cc92..c0b9082 100644
--- a/t/t4013/diff.whatchanged_--root_--patch-with-stat_master
+++ b/t/t4013/diff.whatchanged_--root_--patch-with-stat_main
@@ -1,4 +1,4 @@
-$ git whatchanged --root --patch-with-stat master
+$ git whatchanged --root --patch-with-stat main
 commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:03:00 2006 +0000
diff --git a/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_main
similarity index 97%
rename from t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master
rename to t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_main
index d1d32bd..0002c69 100644
--- a/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master
+++ b/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_main
@@ -1,4 +1,4 @@
-$ git whatchanged --root -c --patch-with-stat --summary master
+$ git whatchanged --root -c --patch-with-stat --summary main
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.whatchanged_--root_-p_master b/t/t4013/diff.whatchanged_--root_-p_main
similarity index 97%
rename from t/t4013/diff.whatchanged_--root_-p_master
rename to t/t4013/diff.whatchanged_--root_-p_main
index ebf1f06..39f3e2b 100644
--- a/t/t4013/diff.whatchanged_--root_-p_master
+++ b/t/t4013/diff.whatchanged_--root_-p_main
@@ -1,4 +1,4 @@
-$ git whatchanged --root -p master
+$ git whatchanged --root -p main
 commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:03:00 2006 +0000
diff --git a/t/t4013/diff.whatchanged_--root_master b/t/t4013/diff.whatchanged_--root_main
similarity index 96%
rename from t/t4013/diff.whatchanged_--root_master
rename to t/t4013/diff.whatchanged_--root_main
index a405cb6..36f4d66 100644
--- a/t/t4013/diff.whatchanged_--root_master
+++ b/t/t4013/diff.whatchanged_--root_main
@@ -1,4 +1,4 @@
-$ git whatchanged --root master
+$ git whatchanged --root main
 commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:03:00 2006 +0000
diff --git a/t/t4013/diff.log_-SF_-p_master b/t/t4013/diff.whatchanged_-SF_-p_main
similarity index 89%
copy from t/t4013/diff.log_-SF_-p_master
copy to t/t4013/diff.whatchanged_-SF_-p_main
index 5e32438..0e2e67c 100644
--- a/t/t4013/diff.log_-SF_-p_master
+++ b/t/t4013/diff.whatchanged_-SF_-p_main
@@ -1,4 +1,4 @@
-$ git log -SF -p master
+$ git whatchanged -SF -p main
 commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:02:00 2006 +0000
diff --git a/t/t4013/diff.whatchanged_-SF_-p_master b/t/t4013/diff.whatchanged_-SF_-p_master
deleted file mode 100644
index f39da84..0000000
--- a/t/t4013/diff.whatchanged_-SF_-p_master
+++ /dev/null
@@ -1,18 +0,0 @@
-$ git whatchanged -SF -p master
-commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
-Author: A U Thor <author@example.com>
-Date:   Mon Jun 26 00:02:00 2006 +0000
-
-    Third
-
-diff --git a/dir/sub b/dir/sub
-index 8422d40..cead32e 100644
---- a/dir/sub
-+++ b/dir/sub
-@@ -2,3 +2,5 @@ A
- B
- C
- D
-+E
-+F
-$
diff --git a/t/t4013/diff.whatchanged_-SF_master b/t/t4013/diff.whatchanged_-SF_main
similarity index 86%
rename from t/t4013/diff.whatchanged_-SF_master
rename to t/t4013/diff.whatchanged_-SF_main
index 0499321..34c6bf6 100644
--- a/t/t4013/diff.whatchanged_-SF_master
+++ b/t/t4013/diff.whatchanged_-SF_main
@@ -1,4 +1,4 @@
-$ git whatchanged -SF master
+$ git whatchanged -SF main
 commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:02:00 2006 +0000
diff --git a/t/t4013/diff.whatchanged_-p_master b/t/t4013/diff.whatchanged_-p_main
similarity index 97%
rename from t/t4013/diff.whatchanged_-p_master
rename to t/t4013/diff.whatchanged_-p_main
index f18d432..18f3bde 100644
--- a/t/t4013/diff.whatchanged_-p_master
+++ b/t/t4013/diff.whatchanged_-p_main
@@ -1,4 +1,4 @@
-$ git whatchanged -p master
+$ git whatchanged -p main
 commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:03:00 2006 +0000
diff --git a/t/t4013/diff.whatchanged_master b/t/t4013/diff.whatchanged_main
similarity index 96%
rename from t/t4013/diff.whatchanged_master
rename to t/t4013/diff.whatchanged_main
index cd3bcc2..d6c83ed 100644
--- a/t/t4013/diff.whatchanged_master
+++ b/t/t4013/diff.whatchanged_main
@@ -1,4 +1,4 @@
-$ git whatchanged master
+$ git whatchanged main
 commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:03:00 2006 +0000
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index 52e3e47..9de7f73 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -11,12 +11,8 @@
 . "$TEST_DIRECTORY"/lib-diff.sh
 
 for opt_res in --patch --quiet -s --stat --shortstat --dirstat=lines \
-	       --raw! --name-only! --name-status!
+	       --raw --name-only --name-status
 do
-	opts=${opt_res%!} expect_failure=
-	test "$opts" = "$opt_res" ||
-		expect_failure="test_expect_code 1"
-
 	test_expect_success "status with $opts (different)" '
 		echo foo >x &&
 		git add x &&
@@ -43,7 +39,7 @@
 		echo foo >x &&
 		git add x &&
 		echo " foo" >x &&
-		$expect_failure git diff -w $opts --exit-code x
+		git diff -w $opts --exit-code x
 	'
 done
 
diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh
index 01db924..6959927 100755
--- a/t/t4053-diff-no-index.sh
+++ b/t/t4053-diff-no-index.sh
@@ -26,6 +26,23 @@
 	test_line_count = 14 cnt
 '
 
+test_expect_success 'git diff --no-index with -' '
+	cat >expect <<-\EOF &&
+	diff --git a/- b/-
+	new file mode 100644
+	--- /dev/null
+	+++ b/-
+	@@ -0,0 +1 @@
+	+frotz
+	EOF
+	(
+		cd a &&
+		echo frotz |
+		test_expect_code 1 git diff --no-index /dev/null - >../actual
+	) &&
+	test_cmp expect actual
+'
+
 test_expect_success 'git diff --no-index relative path outside repo' '
 	(
 		cd repo &&
@@ -322,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/t4072-diff-max-depth.sh b/t/t4072-diff-max-depth.sh
new file mode 100755
index 0000000..0fbf132
--- /dev/null
+++ b/t/t4072-diff-max-depth.sh
@@ -0,0 +1,116 @@
+#!/bin/sh
+
+test_description='check that diff --max-depth will limit recursion'
+. ./test-lib.sh
+
+make_dir() {
+	mkdir -p "$1" &&
+	echo "$2" >"$1/file"
+}
+
+make_files() {
+	echo "$1" >file &&
+	make_dir one "$1" &&
+	make_dir one/two "$1" &&
+	make_dir one/two/three "$1"
+}
+
+test_expect_success 'setup' '
+	git commit --allow-empty -m empty &&
+	git tag empty &&
+	make_files added &&
+	git add . &&
+	git commit -m added &&
+	make_files modified &&
+	git add . &&
+	git commit -m modified &&
+	make_files index &&
+	git add . &&
+	make_files worktree
+'
+
+test_expect_success '--max-depth is disallowed with wildcard pathspecs' '
+	test_must_fail git diff-tree --max-depth=0 HEAD^ HEAD -- "f*"
+'
+
+check_one() {
+	type=$1; shift
+	args=$1; shift
+	path=$1; shift
+	depth=$1; shift
+	test_expect_${expect:-success} "diff-$type $args, path=$path, depth=$depth" "
+		for i in $*; do echo \$i; done >expect &&
+		git diff-$type --max-depth=$depth --name-only $args -- $path >actual &&
+		test_cmp expect actual
+	"
+}
+
+# For tree comparisons, we expect to see subtrees at the boundary
+# get their own entry.
+check_trees() {
+	check_one tree "$*" '' 0 file one
+	check_one tree "$*" '' 1 file one/file one/two
+	check_one tree "$*" '' 2 file one/file one/two/file one/two/three
+	check_one tree "$*" '' 3 file one/file one/two/file one/two/three/file
+	check_one tree "$*" '' -1 file one/file one/two/file one/two/three/file
+	check_one tree "$*" one 0 one
+	check_one tree "$*" one 1 one/file one/two
+	check_one tree "$*" one 2 one/file one/two/file one/two/three
+	check_one tree "$*" one 3 one/file one/two/file one/two/three/file
+	check_one tree "$*" one/two 0 one/two
+	check_one tree "$*" one/two 1 one/two/file one/two/three
+	check_one tree "$*" one/two 2 one/two/file one/two/three/file
+	check_one tree "$*" one/two 2 one/two/file one/two/three/file
+	check_one tree "$*" one/two/three 0 one/two/three
+	check_one tree "$*" one/two/three 1 one/two/three/file
+}
+
+# But for index comparisons, we do not store subtrees at all, so we do not
+# expect them.
+check_index() {
+	check_one "$@" '' 0 file
+	check_one "$@" '' 1 file one/file
+	check_one "$@" '' 2 file one/file one/two/file
+	check_one "$@" '' 3 file one/file one/two/file one/two/three/file
+	check_one "$@" one 0
+	check_one "$@" one 1 one/file
+	check_one "$@" one 2 one/file one/two/file
+	check_one "$@" one 3 one/file one/two/file one/two/three/file
+	check_one "$@" one/two 0
+	check_one "$@" one/two 1 one/two/file
+	check_one "$@" one/two 2 one/two/file one/two/three/file
+	check_one "$@" one/two/three 0
+	check_one "$@" one/two/three 1 one/two/three/file
+
+	# Value '-1' for '--max-depth is the same as recursion without limit,
+	# and thus should always succeed.
+	local expect=
+	check_one "$@" '' -1 file one/file one/two/file one/two/three/file
+}
+
+# Check as a modification...
+check_trees HEAD^ HEAD
+# ...and as an addition...
+check_trees empty HEAD
+# ...and as a deletion.
+check_trees HEAD empty
+
+# We currently only implement max-depth for trees.
+expect=failure
+# Check index against a tree
+check_index index "--cached HEAD"
+# and index against the worktree
+check_index files ""
+expect=
+
+test_expect_success 'find shortest path within embedded pathspecs' '
+	cat >expect <<-\EOF &&
+	one/file
+	one/two/file
+	one/two/three/file
+	EOF
+	git diff-tree --max-depth=2 --name-only HEAD^ HEAD -- one one/two >actual &&
+	test_cmp expect actual
+'
+
+test_done
diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh
index 950451c..0a7c3ca 100755
--- a/t/t4211-line-log.sh
+++ b/t/t4211-line-log.sh
@@ -78,6 +78,8 @@
 canned_test "-L 4:a.c -L 8,12:a.c simple" multiple-superset
 canned_test "-L 8,12:a.c -L 4:a.c simple" multiple-superset
 
+canned_test "-L 10,16:b.c -L 18,26:b.c main" no-assertion-error
+
 test_bad_opts "-L" "switch.*requires a value"
 test_bad_opts "-L b.c" "argument not .start,end:file"
 test_bad_opts "-L 1:" "argument not .start,end:file"
diff --git a/t/t4211/sha1/expect.multiple b/t/t4211/sha1/expect.multiple
index 76ad5b5..1eee8a7 100644
--- a/t/t4211/sha1/expect.multiple
+++ b/t/t4211/sha1/expect.multiple
@@ -102,3 +102,9 @@
 +		s++;
 +	}
 +}
+@@ -0,0 +16,5 @@
++int main ()
++{
++	printf("%d\n", f(15));
++	return 0;
++}
diff --git a/t/t4211/sha1/expect.no-assertion-error b/t/t4211/sha1/expect.no-assertion-error
new file mode 100644
index 0000000..994c37d
--- /dev/null
+++ b/t/t4211/sha1/expect.no-assertion-error
@@ -0,0 +1,90 @@
+commit 0d8dcfc6b968e06a27d5215bad1fdde3de9d6235
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:50:24 2013 +0100
+
+    move within the file
+
+diff --git a/b.c b/b.c
+--- a/b.c
++++ b/b.c
+@@ -25,0 +18,9 @@
++long f(long x)
++{
++	int s = 0;
++	while (x) {
++		x /= 2;
++		s++;
++	}
++	return s;
++}
+
+commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:43 2013 +0100
+
+    change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,7 @@
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
+\ No newline at end of file
++}
++
++/* incomplete lines are bad! */
+
+commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:10 2013 +0100
+
+    change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
++}
+\ No newline at end of file
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:16 2013 +0100
+
+    touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+-	printf("%d\n", f(15));
++	printf("%ld\n", f(15));
+ 	return 0;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +16,5 @@
++int main ()
++{
++	printf("%d\n", f(15));
++	return 0;
++}
diff --git a/t/t4211/sha1/expect.two-ranges b/t/t4211/sha1/expect.two-ranges
index 6109aa0..c5164f3 100644
--- a/t/t4211/sha1/expect.two-ranges
+++ b/t/t4211/sha1/expect.two-ranges
@@ -100,3 +100,9 @@
 +		s++;
 +	}
 +}
+@@ -0,0 +16,5 @@
++int main ()
++{
++	printf("%d\n", f(15));
++	return 0;
++}
diff --git a/t/t4211/sha256/expect.multiple b/t/t4211/sha256/expect.multiple
index ca00409..dbd987b 100644
--- a/t/t4211/sha256/expect.multiple
+++ b/t/t4211/sha256/expect.multiple
@@ -102,3 +102,9 @@
 +		s++;
 +	}
 +}
+@@ -0,0 +16,5 @@
++int main ()
++{
++	printf("%d\n", f(15));
++	return 0;
++}
diff --git a/t/t4211/sha256/expect.no-assertion-error b/t/t4211/sha256/expect.no-assertion-error
new file mode 100644
index 0000000..36ed12a
--- /dev/null
+++ b/t/t4211/sha256/expect.no-assertion-error
@@ -0,0 +1,90 @@
+commit eb871b8aa9aff323e484723039c9a92ab0266e060bc0ef2afb08fadda25c5ace
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:50:24 2013 +0100
+
+    move within the file
+
+diff --git a/b.c b/b.c
+--- a/b.c
++++ b/b.c
+@@ -25,0 +18,9 @@
++long f(long x)
++{
++	int s = 0;
++	while (x) {
++		x /= 2;
++		s++;
++	}
++	return s;
++}
+
+commit 5526ed05c2476b56af8b7be499e8f78bd50f490740733a9ca7e1f55878fa90a9
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:43 2013 +0100
+
+    change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,7 @@
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
+\ No newline at end of file
++}
++
++/* incomplete lines are bad! */
+
+commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:10 2013 +0100
+
+    change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
++}
+\ No newline at end of file
+
+commit ccf97b9878189c40a981da50b15713bb80a35755326320ec80900caf22ced46f
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:16 2013 +0100
+
+    touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+-	printf("%d\n", f(15));
++	printf("%ld\n", f(15));
+ 	return 0;
+ }
+
+commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +16,5 @@
++int main ()
++{
++	printf("%d\n", f(15));
++	return 0;
++}
diff --git a/t/t4211/sha256/expect.two-ranges b/t/t4211/sha256/expect.two-ranges
index af57c8b..6a94d3b 100644
--- a/t/t4211/sha256/expect.two-ranges
+++ b/t/t4211/sha256/expect.two-ranges
@@ -100,3 +100,9 @@
 +		s++;
 +	}
 +}
+@@ -0,0 +16,5 @@
++int main ()
++{
++	printf("%d\n", f(15));
++	return 0;
++}
diff --git a/t/t4216-log-bloom.sh b/t/t4216-log-bloom.sh
index 639868a..1064990 100755
--- a/t/t4216-log-bloom.sh
+++ b/t/t4216-log-bloom.sh
@@ -154,11 +154,34 @@
 	test_bloom_filters_used "-- file*"
 '
 
-test_expect_success 'git log with path contains a wildcard does not use Bloom filter' '
+test_expect_success 'git log with paths all contain non-wildcard part uses Bloom filter' '
+	test_bloom_filters_used "-- A/\* file4" &&
+	test_bloom_filters_used "-- A/file\*" &&
+	test_bloom_filters_used "-- * A/\*"
+'
+
+test_expect_success 'git log with path only contains wildcard part does not use Bloom filter' '
 	test_bloom_filters_not_used "-- file\*" &&
-	test_bloom_filters_not_used "-- A/\* file4" &&
-	test_bloom_filters_not_used "-- file4 A/\*" &&
-	test_bloom_filters_not_used "-- * A/\*"
+	test_bloom_filters_not_used "-- file\* A/\*" &&
+	test_bloom_filters_not_used "-- file\* *" &&
+	test_bloom_filters_not_used "-- \*"
+'
+
+test_expect_success 'git log with path contains various magic signatures' '
+	cd A &&
+	test_bloom_filters_used "-- \:\(top\)B" &&
+	cd .. &&
+
+	test_bloom_filters_used "-- \:\(glob\)A/\*\*/C" &&
+	test_bloom_filters_not_used "-- \:\(icase\)FILE4" &&
+	test_bloom_filters_not_used "-- \:\(exclude\)A/B/C" &&
+
+	test_when_finished "rm -f .gitattributes" &&
+	cat >.gitattributes <<-EOF &&
+	A/file1 text
+	A/B/file2 -text
+	EOF
+	test_bloom_filters_used "-- \:\(attr\:text\)A"
 '
 
 test_expect_success 'setup - add commit-graph to the chain without Bloom filters' '
diff --git a/t/t5200-update-server-info.sh b/t/t5200-update-server-info.sh
index 8365907..a551e95 100755
--- a/t/t5200-update-server-info.sh
+++ b/t/t5200-update-server-info.sh
@@ -46,4 +46,9 @@
 	test_must_be_empty dups
 '
 
+test_expect_success 'update-server-info does not crash with -h' '
+	test_expect_code 129 git update-server-info -h >usage &&
+	test_grep "[Uu]sage: git update-server-info " usage
+'
+
 test_done
diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh
index 1f1f664..2be7cd3 100755
--- a/t/t5304-prune.sh
+++ b/t/t5304-prune.sh
@@ -364,4 +364,9 @@
 	git cat-file -p $BLOB
 '
 
+test_expect_success 'prune does not crash with -h' '
+	test_expect_code 129 git prune -h >usage &&
+	test_grep "[Uu]sage: git prune " usage
+'
+
 test_done
diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh
index bd75dea..93f319a 100755
--- a/t/t5319-multi-pack-index.sh
+++ b/t/t5319-multi-pack-index.sh
@@ -28,11 +28,11 @@
 		EOF
 		if test $NUM_PACKS -ge 1
 		then
-			ls $OBJECT_DIR/pack/ | grep idx | sort
+			ls "$OBJECT_DIR"/pack/ | grep idx | sort
 		fi &&
 		printf "object-dir: $OBJECT_DIR\n"
 	} >expect &&
-	test-tool read-midx $OBJECT_DIR >actual &&
+	test-tool read-midx "$OBJECT_DIR" >actual &&
 	test_cmp expect actual
 }
 
@@ -305,7 +305,7 @@
 
 		ofs=$(git show-index <objects/pack/test-BC-$bc.idx | grep $b |
 			cut -d" " -f1) &&
-		printf "%s %s\tobjects/pack/test-BC-%s.pack\n" \
+		printf "%s %s\t./objects/pack/test-BC-%s.pack\n" \
 			"$b" "$ofs" "$bc" >expect &&
 		grep ^$b out >actual &&
 
@@ -639,7 +639,7 @@
 		( cd ../objects64 && pwd ) >.git/objects/info/alternates &&
 		midx64=$(git multi-pack-index --object-dir=../objects64 write)
 	) &&
-	midx_read_expect 1 63 5 objects64 " large-offsets"
+	midx_read_expect 1 63 5 "$(pwd)/objects64" " large-offsets"
 '
 
 test_expect_success 'verify multi-pack-index with 64-bit offsets' '
@@ -989,6 +989,23 @@
 	)
 '
 
+test_expect_success EXPENSIVE 'repack/expire with many packs' '
+	cp -r dup many &&
+	(
+		cd many &&
+
+		for i in $(test_seq 1 100)
+		do
+			test_commit extra$i &&
+			git maintenance run --task=loose-objects || return 1
+		done &&
+
+		git multi-pack-index write &&
+		git multi-pack-index repack &&
+		git multi-pack-index expire
+	)
+'
+
 test_expect_success 'repack --batch-size=<large> repacks everything' '
 	(
 		cd dup2 &&
@@ -1083,7 +1100,10 @@
 		mv $idx.bak $idx &&
 
 		mv $pack $pack.bak &&
-		git cat-file --batch-check="%(objectsize:disk)" <tip
+		git cat-file --batch-check="%(objectsize:disk)" <tip &&
+
+		test_must_fail git multi-pack-index write 2>err &&
+		test_grep "could not load pack" err
 	)
 '
 
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 2701eef..e592c0b 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -1658,4 +1658,77 @@
 	test_grep ".outer. is a superset of existing remote .outer/inner." err
 '
 
+test_expect_success 'rename handles unborn HEAD' '
+	test_when_finished "git remote remove unborn-renamed" &&
+	git remote add unborn url &&
+	git symbolic-ref refs/remotes/unborn/HEAD refs/remotes/unborn/nonexistent &&
+	git remote rename unborn unborn-renamed &&
+	git symbolic-ref refs/remotes/unborn-renamed/HEAD >actual &&
+	echo refs/remotes/unborn-renamed/nonexistent >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'rename can nest a remote into itself' '
+	test_commit parent-commit &&
+	COMMIT_ID=$(git rev-parse HEAD) &&
+	test_when_finished "git remote remove parent || true" &&
+	git remote add parent url &&
+	git update-ref refs/remotes/parent/branch $COMMIT_ID &&
+	test_when_finished "git remote remove parent/child" &&
+	git remote rename parent parent/child &&
+	git for-each-ref refs/remotes/ >actual &&
+	printf "$COMMIT_ID commit\trefs/remotes/parent/child/branch\n" >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'rename can nest a remote into itself with a conflicting branch name' '
+	test_commit parent-conflict &&
+	COMMIT_ID=$(git rev-parse HEAD) &&
+	test_when_finished "git remote remove parent || true" &&
+	git remote add parent url &&
+	git update-ref refs/remotes/parent/child $COMMIT_ID &&
+	test_when_finished "git remote remove parent/child" &&
+	test_must_fail git remote rename parent parent/child 2>err &&
+	test_grep "renaming remote references failed" err &&
+	test_grep "The remote you are trying to rename has conflicting references" err &&
+	git for-each-ref refs/remotes/ >actual &&
+	printf "$COMMIT_ID commit\trefs/remotes/parent/child\n" >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'rename can unnest a remote' '
+	test_commit parent-child-commit &&
+	COMMIT_ID=$(git rev-parse HEAD) &&
+	test_when_finished "git remote remove parent/child || true" &&
+	git remote add parent/child url &&
+	git update-ref refs/remotes/parent/child/branch $COMMIT_ID &&
+	git remote rename parent/child parent &&
+	git for-each-ref refs/remotes/ >actual &&
+	printf "$COMMIT_ID commit\trefs/remotes/parent/branch\n" >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'rename moves around the reflog' '
+	test_commit reflog-old &&
+	COMMIT_ID=$(git rev-parse HEAD) &&
+	test_config core.logAllRefUpdates true &&
+	test_when_finished "git remote remove reflog-old || true" &&
+	git remote add reflog-old url &&
+	git update-ref refs/remotes/reflog-old/branch $COMMIT_ID &&
+	test-tool ref-store main for-each-reflog >actual &&
+	test_grep refs/remotes/reflog-old/branch actual &&
+	test-tool ref-store main for-each-reflog-ent refs/remotes/reflog-old/branch >reflog-entries-old &&
+	test_line_count = 1 reflog-entries-old &&
+	git remote rename reflog-old reflog-new &&
+	test-tool ref-store main for-each-reflog >actual &&
+	test_grep ! refs/remotes/reflog-old actual &&
+	test_grep refs/remotes/reflog-new/branch actual &&
+	test-tool ref-store main for-each-reflog-ent refs/remotes/reflog-new/branch >reflog-entries-new &&
+	cat >expect <<-EOF &&
+	$(cat reflog-entries-old)
+	$COMMIT_ID $COMMIT_ID $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1112912173 -0700	remote: renamed refs/remotes/reflog-old/branch to refs/remotes/reflog-new/branch
+	EOF
+	test_cmp expect reflog-entries-new
+'
+
 test_done
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index ebc6965..b7059cc 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -14,8 +14,6 @@
 	test_done
 fi
 
-D=$(pwd)
-
 test_expect_success setup '
 	echo >file original &&
 	git add file &&
@@ -47,50 +45,72 @@
 		git config set branch.main.merge refs/heads/one
 	) &&
 	git clone . bundle &&
-	git clone . seven
+	git clone . seven &&
+	git clone --ref-format=reftable . case_sensitive &&
+	(
+		cd case_sensitive &&
+		git branch branch1 &&
+		git branch bRanch1
+	) &&
+	git clone --ref-format=reftable . case_sensitive_fd &&
+	(
+		cd case_sensitive_fd &&
+		git branch foo/bar &&
+		git branch Foo
+	) &&
+	git clone --ref-format=reftable . case_sensitive_df &&
+	(
+		cd case_sensitive_df &&
+		git branch Foo/bar &&
+		git branch foo
+	)
 '
 
 test_expect_success "fetch test" '
-	cd "$D" &&
 	echo >file updated by origin &&
 	git commit -a -m "updated by origin" &&
-	cd two &&
-	git fetch &&
-	git rev-parse --verify refs/heads/one &&
-	mine=$(git rev-parse refs/heads/one) &&
-	his=$(cd ../one && git rev-parse refs/heads/main) &&
-	test "z$mine" = "z$his"
+	(
+		cd two &&
+		git fetch &&
+		git rev-parse --verify refs/heads/one &&
+		mine=$(git rev-parse refs/heads/one) &&
+		his=$(cd ../one && git rev-parse refs/heads/main) &&
+		test "z$mine" = "z$his"
+	)
 '
 
 test_expect_success "fetch test for-merge" '
-	cd "$D" &&
-	cd three &&
-	git fetch &&
-	git rev-parse --verify refs/heads/two &&
-	git rev-parse --verify refs/heads/one &&
-	main_in_two=$(cd ../two && git rev-parse main) &&
-	one_in_two=$(cd ../two && git rev-parse one) &&
-	{
-		echo "$one_in_two	" &&
-		echo "$main_in_two	not-for-merge"
-	} >expected &&
-	cut -f -2 .git/FETCH_HEAD >actual &&
-	test_cmp expected actual'
+	(
+		cd three &&
+		git fetch &&
+		git rev-parse --verify refs/heads/two &&
+		git rev-parse --verify refs/heads/one &&
+		main_in_two=$(cd ../two && git rev-parse main) &&
+		one_in_two=$(cd ../two && git rev-parse one) &&
+		{
+			echo "$one_in_two	" &&
+			echo "$main_in_two	not-for-merge"
+		} >expected &&
+		cut -f -2 .git/FETCH_HEAD >actual &&
+		test_cmp expected actual
+	)
+'
 
 test_expect_success "fetch test remote HEAD" '
-	cd "$D" &&
-	cd two &&
-	git fetch &&
-	git rev-parse --verify refs/remotes/origin/HEAD &&
-	git rev-parse --verify refs/remotes/origin/main &&
-	head=$(git rev-parse refs/remotes/origin/HEAD) &&
-	branch=$(git rev-parse refs/remotes/origin/main) &&
-	test "z$head" = "z$branch"'
+	(
+		cd two &&
+		git fetch &&
+		git rev-parse --verify refs/remotes/origin/HEAD &&
+		git rev-parse --verify refs/remotes/origin/main &&
+		head=$(git rev-parse refs/remotes/origin/HEAD) &&
+		branch=$(git rev-parse refs/remotes/origin/main) &&
+		test "z$head" = "z$branch"
+	)
+'
 
 test_expect_success "fetch test remote HEAD in bare repository" '
 	test_when_finished rm -rf barerepo &&
 	(
-		cd "$D" &&
 		git init --bare barerepo &&
 		cd barerepo &&
 		git remote add upstream ../two &&
@@ -105,262 +125,235 @@
 
 
 test_expect_success "fetch test remote HEAD change" '
-	cd "$D" &&
-	cd two &&
-	git switch -c other &&
-	git push -u origin other &&
-	git rev-parse --verify refs/remotes/origin/HEAD &&
-	git rev-parse --verify refs/remotes/origin/main &&
-	git rev-parse --verify refs/remotes/origin/other &&
-	git remote set-head origin other &&
-	git fetch &&
-	head=$(git rev-parse refs/remotes/origin/HEAD) &&
-	branch=$(git rev-parse refs/remotes/origin/other) &&
-	test "z$head" = "z$branch"'
+	(
+		cd two &&
+		git switch -c other &&
+		git push -u origin other &&
+		git rev-parse --verify refs/remotes/origin/HEAD &&
+		git rev-parse --verify refs/remotes/origin/main &&
+		git rev-parse --verify refs/remotes/origin/other &&
+		git remote set-head origin other &&
+		git fetch &&
+		head=$(git rev-parse refs/remotes/origin/HEAD) &&
+		branch=$(git rev-parse refs/remotes/origin/other) &&
+		test "z$head" = "z$branch"
+	)
+'
 
 test_expect_success "fetch test followRemoteHEAD never" '
-	test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
-	(
-		cd "$D" &&
-		cd two &&
-		git update-ref --no-deref -d refs/remotes/origin/HEAD &&
-		git config set remote.origin.followRemoteHEAD "never" &&
-		GIT_TRACE_PACKET=$PWD/trace.out git fetch &&
-		# Confirm that we do not even ask for HEAD when we are
-		# not going to act on it.
-		test_grep ! "ref-prefix HEAD" trace.out &&
-		test_must_fail git rev-parse --verify refs/remotes/origin/HEAD
-	)
+	git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
+	test_config -C two remote.origin.followRemoteHEAD "never" &&
+	GIT_TRACE_PACKET=$PWD/trace.out git -C two fetch &&
+	# Confirm that we do not even ask for HEAD when we are
+	# not going to act on it.
+	test_grep ! "ref-prefix HEAD" trace.out &&
+	test_must_fail git -C two rev-parse --verify refs/remotes/origin/HEAD
 '
 
 test_expect_success "fetch test followRemoteHEAD warn no change" '
-	test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
-	(
-		cd "$D" &&
-		cd two &&
-		git rev-parse --verify refs/remotes/origin/other &&
-		git remote set-head origin other &&
-		git rev-parse --verify refs/remotes/origin/HEAD &&
-		git rev-parse --verify refs/remotes/origin/main &&
-		git config set remote.origin.followRemoteHEAD "warn" &&
-		git fetch >output &&
-		echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
-			"but we have ${SQ}other${SQ} locally." >expect &&
-		test_cmp expect output &&
-		head=$(git rev-parse refs/remotes/origin/HEAD) &&
-		branch=$(git rev-parse refs/remotes/origin/other) &&
-		test "z$head" = "z$branch"
-	)
+	git -C two rev-parse --verify refs/remotes/origin/other &&
+	git -C two remote set-head origin other &&
+	git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+	git -C two rev-parse --verify refs/remotes/origin/main &&
+	test_config -C two remote.origin.followRemoteHEAD "warn" &&
+	git -C two fetch >output &&
+	echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+		"but we have ${SQ}other${SQ} locally." >expect &&
+	test_cmp expect output &&
+	head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+	branch=$(git -C two rev-parse refs/remotes/origin/other) &&
+	test "z$head" = "z$branch"
 '
 
 test_expect_success "fetch test followRemoteHEAD warn create" '
-	test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
-	(
-		cd "$D" &&
-		cd two &&
-		git update-ref --no-deref -d refs/remotes/origin/HEAD &&
-		git config set remote.origin.followRemoteHEAD "warn" &&
-		git rev-parse --verify refs/remotes/origin/main &&
-		output=$(git fetch) &&
-		test "z" = "z$output" &&
-		head=$(git rev-parse refs/remotes/origin/HEAD) &&
-		branch=$(git rev-parse refs/remotes/origin/main) &&
-		test "z$head" = "z$branch"
-	)
+	git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
+	test_config -C two remote.origin.followRemoteHEAD "warn" &&
+	git -C two rev-parse --verify refs/remotes/origin/main &&
+	output=$(git -C two fetch) &&
+	test "z" = "z$output" &&
+	head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+	branch=$(git -C two rev-parse refs/remotes/origin/main) &&
+	test "z$head" = "z$branch"
 '
 
 test_expect_success "fetch test followRemoteHEAD warn detached" '
-	test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
-	(
-		cd "$D" &&
-		cd two &&
-		git update-ref --no-deref -d refs/remotes/origin/HEAD &&
-		git update-ref refs/remotes/origin/HEAD HEAD &&
-		HEAD=$(git log --pretty="%H") &&
-		git config set remote.origin.followRemoteHEAD "warn" &&
-		git fetch >output &&
-		echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
-			"but we have a detached HEAD pointing to" \
-			"${SQ}${HEAD}${SQ} locally." >expect &&
-		test_cmp expect output
-	)
+	git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
+	git -C two update-ref refs/remotes/origin/HEAD HEAD &&
+	HEAD=$(git -C two log --pretty="%H") &&
+	test_config -C two remote.origin.followRemoteHEAD "warn" &&
+	git -C two fetch >output &&
+	echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+		"but we have a detached HEAD pointing to" \
+		"${SQ}${HEAD}${SQ} locally." >expect &&
+	test_cmp expect output
 '
 
 test_expect_success "fetch test followRemoteHEAD warn quiet" '
-	test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
-	(
-		cd "$D" &&
-		cd two &&
-		git rev-parse --verify refs/remotes/origin/other &&
-		git remote set-head origin other &&
-		git rev-parse --verify refs/remotes/origin/HEAD &&
-		git rev-parse --verify refs/remotes/origin/main &&
-		git config set remote.origin.followRemoteHEAD "warn" &&
-		output=$(git fetch --quiet) &&
-		test "z" = "z$output" &&
-		head=$(git rev-parse refs/remotes/origin/HEAD) &&
-		branch=$(git rev-parse refs/remotes/origin/other) &&
-		test "z$head" = "z$branch"
-	)
+	git -C two rev-parse --verify refs/remotes/origin/other &&
+	git -C two remote set-head origin other &&
+	git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+	git -C two rev-parse --verify refs/remotes/origin/main &&
+	test_config -C two remote.origin.followRemoteHEAD "warn" &&
+	output=$(git -C two fetch --quiet) &&
+	test "z" = "z$output" &&
+	head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+	branch=$(git -C two rev-parse refs/remotes/origin/other) &&
+	test "z$head" = "z$branch"
 '
 
 test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is same" '
-	test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
-	(
-		cd "$D" &&
-		cd two &&
-		git rev-parse --verify refs/remotes/origin/other &&
-		git remote set-head origin other &&
-		git rev-parse --verify refs/remotes/origin/HEAD &&
-		git rev-parse --verify refs/remotes/origin/main &&
-		git config set remote.origin.followRemoteHEAD "warn-if-not-main" &&
-		actual=$(git fetch) &&
-		test "z" = "z$actual" &&
-		head=$(git rev-parse refs/remotes/origin/HEAD) &&
-		branch=$(git rev-parse refs/remotes/origin/other) &&
-		test "z$head" = "z$branch"
-	)
+	git -C two rev-parse --verify refs/remotes/origin/other &&
+	git -C two remote set-head origin other &&
+	git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+	git -C two rev-parse --verify refs/remotes/origin/main &&
+	test_config -C two remote.origin.followRemoteHEAD "warn-if-not-main" &&
+	actual=$(git -C two fetch) &&
+	test "z" = "z$actual" &&
+	head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+	branch=$(git -C two rev-parse refs/remotes/origin/other) &&
+	test "z$head" = "z$branch"
 '
 
 test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is different" '
-	test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
-	(
-		cd "$D" &&
-		cd two &&
-		git rev-parse --verify refs/remotes/origin/other &&
-		git remote set-head origin other &&
-		git rev-parse --verify refs/remotes/origin/HEAD &&
-		git rev-parse --verify refs/remotes/origin/main &&
-		git config set remote.origin.followRemoteHEAD "warn-if-not-some/different-branch" &&
-		git fetch >actual &&
-		echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
-			"but we have ${SQ}other${SQ} locally." >expect &&
-		test_cmp expect actual &&
-		head=$(git rev-parse refs/remotes/origin/HEAD) &&
-		branch=$(git rev-parse refs/remotes/origin/other) &&
-		test "z$head" = "z$branch"
-	)
+	git -C two rev-parse --verify refs/remotes/origin/other &&
+	git -C two remote set-head origin other &&
+	git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+	git -C two rev-parse --verify refs/remotes/origin/main &&
+	test_config -C two remote.origin.followRemoteHEAD "warn-if-not-some/different-branch" &&
+	git -C two fetch >actual &&
+	echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+		"but we have ${SQ}other${SQ} locally." >expect &&
+	test_cmp expect actual &&
+	head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+	branch=$(git -C two rev-parse refs/remotes/origin/other) &&
+	test "z$head" = "z$branch"
 '
 
 test_expect_success "fetch test followRemoteHEAD always" '
-	test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
-	(
-		cd "$D" &&
-		cd two &&
-		git rev-parse --verify refs/remotes/origin/other &&
-		git remote set-head origin other &&
-		git rev-parse --verify refs/remotes/origin/HEAD &&
-		git rev-parse --verify refs/remotes/origin/main &&
-		git config set remote.origin.followRemoteHEAD "always" &&
-		git fetch &&
-		head=$(git rev-parse refs/remotes/origin/HEAD) &&
-		branch=$(git rev-parse refs/remotes/origin/main) &&
-		test "z$head" = "z$branch"
-	)
+	git -C two rev-parse --verify refs/remotes/origin/other &&
+	git -C two remote set-head origin other &&
+	git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+	git -C two rev-parse --verify refs/remotes/origin/main &&
+	test_config -C two remote.origin.followRemoteHEAD "always" &&
+	git -C two fetch &&
+	head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+	branch=$(git -C two rev-parse refs/remotes/origin/main) &&
+	test "z$head" = "z$branch"
 '
 
 test_expect_success 'followRemoteHEAD does not kick in with refspecs' '
-	test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
-	(
-		cd "$D" &&
-		cd two &&
-		git remote set-head origin other &&
-		git config set remote.origin.followRemoteHEAD always &&
-		git fetch origin refs/heads/main:refs/remotes/origin/main &&
-		echo refs/remotes/origin/other >expect &&
-		git symbolic-ref refs/remotes/origin/HEAD >actual &&
-		test_cmp expect actual
-	)
+	git -C two remote set-head origin other &&
+	test_config -C two remote.origin.followRemoteHEAD always &&
+	git -C two fetch origin refs/heads/main:refs/remotes/origin/main &&
+	echo refs/remotes/origin/other >expect &&
+	git -C two symbolic-ref refs/remotes/origin/HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'followRemoteHEAD create does not overwrite dangling symref' '
+	git -C two remote add -m does-not-exist custom-head ../one &&
+	test_config -C two remote.custom-head.followRemoteHEAD create &&
+	git -C two fetch custom-head &&
+	echo refs/remotes/custom-head/does-not-exist >expect &&
+	git -C two symbolic-ref refs/remotes/custom-head/HEAD >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'fetch --prune on its own works as expected' '
-	cd "$D" &&
 	git clone . prune &&
-	cd prune &&
-	git update-ref refs/remotes/origin/extrabranch main &&
+	(
+		cd prune &&
+		git update-ref refs/remotes/origin/extrabranch main &&
 
-	git fetch --prune origin &&
-	test_must_fail git rev-parse origin/extrabranch
+		git fetch --prune origin &&
+		test_must_fail git rev-parse origin/extrabranch
+	)
 '
 
 test_expect_success 'fetch --prune with a branch name keeps branches' '
-	cd "$D" &&
 	git clone . prune-branch &&
-	cd prune-branch &&
-	git update-ref refs/remotes/origin/extrabranch main &&
+	(
+		cd prune-branch &&
+		git update-ref refs/remotes/origin/extrabranch main &&
 
-	git fetch --prune origin main &&
-	git rev-parse origin/extrabranch
+		git fetch --prune origin main &&
+		git rev-parse origin/extrabranch
+	)
 '
 
 test_expect_success 'fetch --prune with a namespace keeps other namespaces' '
-	cd "$D" &&
 	git clone . prune-namespace &&
-	cd prune-namespace &&
+	(
+		cd prune-namespace &&
 
-	git fetch --prune origin refs/heads/a/*:refs/remotes/origin/a/* &&
-	git rev-parse origin/main
+		git fetch --prune origin refs/heads/a/*:refs/remotes/origin/a/* &&
+		git rev-parse origin/main
+	)
 '
 
 test_expect_success 'fetch --prune handles overlapping refspecs' '
-	cd "$D" &&
 	git update-ref refs/pull/42/head main &&
 	git clone . prune-overlapping &&
-	cd prune-overlapping &&
-	git config --add remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* &&
+	(
+		cd prune-overlapping &&
+		git config --add remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* &&
 
-	git fetch --prune origin &&
-	git rev-parse origin/main &&
-	git rev-parse origin/pr/42 &&
+		git fetch --prune origin &&
+		git rev-parse origin/main &&
+		git rev-parse origin/pr/42 &&
 
-	git config --unset-all remote.origin.fetch &&
-	git config remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* &&
-	git config --add remote.origin.fetch refs/heads/*:refs/remotes/origin/* &&
+		git config --unset-all remote.origin.fetch &&
+		git config remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* &&
+		git config --add remote.origin.fetch refs/heads/*:refs/remotes/origin/* &&
 
-	git fetch --prune origin &&
-	git rev-parse origin/main &&
-	git rev-parse origin/pr/42
+		git fetch --prune origin &&
+		git rev-parse origin/main &&
+		git rev-parse origin/pr/42
+	)
 '
 
 test_expect_success 'fetch --prune --tags prunes branches but not tags' '
-	cd "$D" &&
 	git clone . prune-tags &&
-	cd prune-tags &&
-	git tag sometag main &&
-	# Create what looks like a remote-tracking branch from an earlier
-	# fetch that has since been deleted from the remote:
-	git update-ref refs/remotes/origin/fake-remote main &&
+	(
+		cd prune-tags &&
+		git tag sometag main &&
+		# Create what looks like a remote-tracking branch from an earlier
+		# fetch that has since been deleted from the remote:
+		git update-ref refs/remotes/origin/fake-remote main &&
 
-	git fetch --prune --tags origin &&
-	git rev-parse origin/main &&
-	test_must_fail git rev-parse origin/fake-remote &&
-	git rev-parse sometag
+		git fetch --prune --tags origin &&
+		git rev-parse origin/main &&
+		test_must_fail git rev-parse origin/fake-remote &&
+		git rev-parse sometag
+	)
 '
 
 test_expect_success 'fetch --prune --tags with branch does not prune other things' '
-	cd "$D" &&
 	git clone . prune-tags-branch &&
-	cd prune-tags-branch &&
-	git tag sometag main &&
-	git update-ref refs/remotes/origin/extrabranch main &&
+	(
+		cd prune-tags-branch &&
+		git tag sometag main &&
+		git update-ref refs/remotes/origin/extrabranch main &&
 
-	git fetch --prune --tags origin main &&
-	git rev-parse origin/extrabranch &&
-	git rev-parse sometag
+		git fetch --prune --tags origin main &&
+		git rev-parse origin/extrabranch &&
+		git rev-parse sometag
+	)
 '
 
 test_expect_success 'fetch --prune --tags with refspec prunes based on refspec' '
-	cd "$D" &&
 	git clone . prune-tags-refspec &&
-	cd prune-tags-refspec &&
-	git tag sometag main &&
-	git update-ref refs/remotes/origin/foo/otherbranch main &&
-	git update-ref refs/remotes/origin/extrabranch main &&
+	(
+		cd prune-tags-refspec &&
+		git tag sometag main &&
+		git update-ref refs/remotes/origin/foo/otherbranch main &&
+		git update-ref refs/remotes/origin/extrabranch main &&
 
-	git fetch --prune --tags origin refs/heads/foo/*:refs/remotes/origin/foo/* &&
-	test_must_fail git rev-parse refs/remotes/origin/foo/otherbranch &&
-	git rev-parse origin/extrabranch &&
-	git rev-parse sometag
+		git fetch --prune --tags origin refs/heads/foo/*:refs/remotes/origin/foo/* &&
+		test_must_fail git rev-parse refs/remotes/origin/foo/otherbranch &&
+		git rev-parse origin/extrabranch &&
+		git rev-parse sometag
+	)
 '
 
 test_expect_success 'fetch --tags gets tags even without a configured remote' '
@@ -381,21 +374,21 @@
 '
 
 test_expect_success REFFILES 'fetch --prune fails to delete branches' '
-	cd "$D" &&
 	git clone . prune-fail &&
-	cd prune-fail &&
-	git update-ref refs/remotes/origin/extrabranch main &&
-	git pack-refs --all &&
-	: this will prevent --prune from locking packed-refs for deleting refs, but adding loose refs still succeeds  &&
-	>.git/packed-refs.new &&
+	(
+		cd prune-fail &&
+		git update-ref refs/remotes/origin/extrabranch main &&
+		git pack-refs --all &&
+		: this will prevent --prune from locking packed-refs for deleting refs, but adding loose refs still succeeds  &&
+		>.git/packed-refs.new &&
 
-	test_must_fail git fetch --prune origin
+		test_must_fail git fetch --prune origin
+	)
 '
 
 test_expect_success 'fetch --atomic works with a single branch' '
-	test_when_finished "rm -rf \"$D\"/atomic" &&
+	test_when_finished "rm -rf atomic" &&
 
-	cd "$D" &&
 	git clone . atomic &&
 	git branch atomic-branch &&
 	oid=$(git rev-parse atomic-branch) &&
@@ -408,9 +401,8 @@
 '
 
 test_expect_success 'fetch --atomic works with multiple branches' '
-	test_when_finished "rm -rf \"$D\"/atomic" &&
+	test_when_finished "rm -rf atomic" &&
 
-	cd "$D" &&
 	git clone . atomic &&
 	git branch atomic-branch-1 &&
 	git branch atomic-branch-2 &&
@@ -423,9 +415,8 @@
 '
 
 test_expect_success 'fetch --atomic works with mixed branches and tags' '
-	test_when_finished "rm -rf \"$D\"/atomic" &&
+	test_when_finished "rm -rf atomic" &&
 
-	cd "$D" &&
 	git clone . atomic &&
 	git branch atomic-mixed-branch &&
 	git tag atomic-mixed-tag &&
@@ -437,9 +428,8 @@
 '
 
 test_expect_success 'fetch --atomic prunes references' '
-	test_when_finished "rm -rf \"$D\"/atomic" &&
+	test_when_finished "rm -rf atomic" &&
 
-	cd "$D" &&
 	git branch atomic-prune-delete &&
 	git clone . atomic &&
 	git branch --delete atomic-prune-delete &&
@@ -453,9 +443,8 @@
 '
 
 test_expect_success 'fetch --atomic aborts with non-fast-forward update' '
-	test_when_finished "rm -rf \"$D\"/atomic" &&
+	test_when_finished "rm -rf atomic" &&
 
-	cd "$D" &&
 	git branch atomic-non-ff &&
 	git clone . atomic &&
 	git rev-parse HEAD >actual &&
@@ -472,9 +461,8 @@
 '
 
 test_expect_success 'fetch --atomic executes a single reference transaction only' '
-	test_when_finished "rm -rf \"$D\"/atomic" &&
+	test_when_finished "rm -rf atomic" &&
 
-	cd "$D" &&
 	git clone . atomic &&
 	git branch atomic-hooks-1 &&
 	git branch atomic-hooks-2 &&
@@ -499,9 +487,8 @@
 '
 
 test_expect_success 'fetch --atomic aborts all reference updates if hook aborts' '
-	test_when_finished "rm -rf \"$D\"/atomic" &&
+	test_when_finished "rm -rf atomic" &&
 
-	cd "$D" &&
 	git clone . atomic &&
 	git branch atomic-hooks-abort-1 &&
 	git branch atomic-hooks-abort-2 &&
@@ -536,9 +523,8 @@
 '
 
 test_expect_success 'fetch --atomic --append appends to FETCH_HEAD' '
-	test_when_finished "rm -rf \"$D\"/atomic" &&
+	test_when_finished "rm -rf atomic" &&
 
-	cd "$D" &&
 	git clone . atomic &&
 	oid=$(git rev-parse HEAD) &&
 
@@ -574,8 +560,7 @@
 '
 
 test_expect_success '--refmap="" ignores configured refspec' '
-	cd "$TRASH_DIRECTORY" &&
-	git clone "$D" remote-refs &&
+	git clone . remote-refs &&
 	git -C remote-refs rev-parse remotes/origin/main >old &&
 	git -C remote-refs update-ref refs/remotes/origin/main main~1 &&
 	git -C remote-refs rev-parse remotes/origin/main >new &&
@@ -599,34 +584,26 @@
 
 test_expect_success 'fetch tags when there is no tags' '
 
-    cd "$D" &&
-
-    mkdir notags &&
-    cd notags &&
-    git init &&
-
-    git fetch -t ..
+	git init notags &&
+	git -C notags fetch -t ..
 
 '
 
 test_expect_success 'fetch following tags' '
 
-	cd "$D" &&
 	git tag -a -m "annotated" anno HEAD &&
 	git tag light HEAD &&
 
-	mkdir four &&
-	cd four &&
-	git init &&
-
-	git fetch .. :track &&
-	git show-ref --verify refs/tags/anno &&
-	git show-ref --verify refs/tags/light
-
+	git init four &&
+	(
+		cd four &&
+		git fetch .. :track &&
+		git show-ref --verify refs/tags/anno &&
+		git show-ref --verify refs/tags/light
+	)
 '
 
 test_expect_success 'fetch uses remote ref names to describe new refs' '
-	cd "$D" &&
 	git init descriptive &&
 	(
 		cd descriptive &&
@@ -654,30 +631,20 @@
 
 test_expect_success 'fetch must not resolve short tag name' '
 
-	cd "$D" &&
-
-	mkdir five &&
-	cd five &&
-	git init &&
-
-	test_must_fail git fetch .. anno:five
+	git init five &&
+	test_must_fail git -C five fetch .. anno:five
 
 '
 
 test_expect_success 'fetch can now resolve short remote name' '
 
-	cd "$D" &&
 	git update-ref refs/remotes/six/HEAD HEAD &&
 
-	mkdir six &&
-	cd six &&
-	git init &&
-
-	git fetch .. six:six
+	git init six &&
+	git -C six fetch .. six:six
 '
 
 test_expect_success 'create bundle 1' '
-	cd "$D" &&
 	echo >file updated again by origin &&
 	git commit -a -m "tip" &&
 	git bundle create --version=3 bundle1 main^..main
@@ -691,35 +658,36 @@
 	OID refs/heads/main
 
 	EOF
-	sed -e "s/$OID_REGEX/OID/g" -e "5q" "$D"/bundle1 >actual &&
+	sed -e "s/$OID_REGEX/OID/g" -e "5q" bundle1 >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'create bundle 2' '
-	cd "$D" &&
 	git bundle create bundle2 main~2..main
 '
 
 test_expect_success 'unbundle 1' '
-	cd "$D/bundle" &&
-	git checkout -b some-branch &&
-	test_must_fail git fetch "$D/bundle1" main:main
+	(
+		cd bundle &&
+		git checkout -b some-branch &&
+		test_must_fail git fetch bundle1 main:main
+	)
 '
 
 
 test_expect_success 'bundle 1 has only 3 files ' '
-	cd "$D" &&
 	test_bundle_object_count bundle1 3
 '
 
 test_expect_success 'unbundle 2' '
-	cd "$D/bundle" &&
-	git fetch ../bundle2 main:main &&
-	test "tip" = "$(git log -1 --pretty=oneline main | cut -d" " -f2)"
+	(
+		cd bundle &&
+		git fetch ../bundle2 main:main &&
+		test "tip" = "$(git log -1 --pretty=oneline main | cut -d" " -f2)"
+	)
 '
 
 test_expect_success 'bundle does not prerequisite objects' '
-	cd "$D" &&
 	touch file2 &&
 	git add file2 &&
 	git commit -m add.file2 file2 &&
@@ -729,7 +697,6 @@
 
 test_expect_success 'bundle should be able to create a full history' '
 
-	cd "$D" &&
 	git tag -a -m "1.0" v1.0 main &&
 	git bundle create bundle4 v1.0
 
@@ -783,7 +750,6 @@
 
 test_expect_success 'bundle should record HEAD correctly' '
 
-	cd "$D" &&
 	git bundle create bundle5 HEAD main &&
 	git bundle list-heads bundle5 >actual &&
 	for h in HEAD refs/heads/main
@@ -803,7 +769,6 @@
 
 test_expect_success 'explicit fetch should update tracking' '
 
-	cd "$D" &&
 	git branch -f side &&
 	(
 		cd three &&
@@ -818,7 +783,6 @@
 
 test_expect_success 'explicit pull should update tracking' '
 
-	cd "$D" &&
 	git branch -f side &&
 	(
 		cd three &&
@@ -832,7 +796,6 @@
 '
 
 test_expect_success 'explicit --refmap is allowed only with command-line refspec' '
-	cd "$D" &&
 	(
 		cd three &&
 		test_must_fail git fetch --refmap="*:refs/remotes/none/*"
@@ -840,7 +803,6 @@
 '
 
 test_expect_success 'explicit --refmap option overrides remote.*.fetch' '
-	cd "$D" &&
 	git branch -f side &&
 	(
 		cd three &&
@@ -855,7 +817,6 @@
 '
 
 test_expect_success 'explicitly empty --refmap option disables remote.*.fetch' '
-	cd "$D" &&
 	git branch -f side &&
 	(
 		cd three &&
@@ -870,7 +831,6 @@
 
 test_expect_success 'configured fetch updates tracking' '
 
-	cd "$D" &&
 	git branch -f side &&
 	(
 		cd three &&
@@ -884,7 +844,6 @@
 '
 
 test_expect_success 'non-matching refspecs do not confuse tracking update' '
-	cd "$D" &&
 	git update-ref refs/odd/location HEAD &&
 	(
 		cd three &&
@@ -901,14 +860,12 @@
 
 test_expect_success 'pushing nonexistent branch by mistake should not segv' '
 
-	cd "$D" &&
 	test_must_fail git push seven no:no
 
 '
 
 test_expect_success 'auto tag following fetches minimum' '
 
-	cd "$D" &&
 	git clone .git follow &&
 	git checkout HEAD^0 &&
 	(
@@ -1307,7 +1264,7 @@
 		cd only-prunes &&
 		git fetch --prune origin 2>&1 | head -n1 >../actual
 	) &&
-	echo "From ${D}/." >expect &&
+	echo "From $(pwd)/." >expect &&
 	test_cmp expect actual
 '
 
@@ -1357,14 +1314,14 @@
 	echo "$*" &&
 	false
 	EOF
-	git clone "file://$D" auto-gc &&
+	git clone "file://$PWD" auto-gc &&
 	test_commit test2 &&
 	(
 		cd auto-gc &&
 		git config fetch.unpackLimit 1 &&
 		git config gc.autoPackLimit 1 &&
 		git config gc.autoDetach false &&
-		GIT_ASK_YESNO="$D/askyesno" git fetch --verbose >fetch.out 2>&1 &&
+		GIT_ASK_YESNO="$TRASH_DIRECTORY/askyesno" git fetch --verbose >fetch.out 2>&1 &&
 		test_grep "Auto packing the repository" fetch.out &&
 		! grep "Should I try again" fetch.out
 	)
@@ -1526,6 +1483,100 @@
 	test_path_is_missing whoops
 '
 
+test_expect_success CASE_INSENSITIVE_FS,REFFILES 'existing references in a case insensitive filesystem' '
+	test_when_finished rm -rf case_insensitive &&
+	(
+		git init --bare case_insensitive &&
+		cd case_insensitive &&
+		git remote add origin -- ../case_sensitive &&
+		test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err &&
+		test_grep "You${SQ}re on a case-insensitive filesystem" err &&
+		git rev-parse refs/heads/main >expect &&
+		git rev-parse refs/heads/branch1 >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success REFFILES 'existing reference lock in repo' '
+	test_when_finished rm -rf base repo &&
+	(
+		git init --ref-format=reftable base &&
+		cd base &&
+		echo >file update &&
+		git add . &&
+		git commit -m "updated" &&
+		git branch -M main &&
+
+		git update-ref refs/heads/foo @ &&
+		git update-ref refs/heads/branch @ &&
+		cd .. &&
+
+		git init --ref-format=files --bare repo &&
+		cd repo &&
+		git remote add origin ../base &&
+		touch refs/heads/foo.lock &&
+		test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err &&
+		test_grep "error: fetching ref refs/heads/foo failed: reference already exists" err &&
+		git rev-parse refs/heads/main >expect &&
+		git rev-parse refs/heads/branch >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success CASE_INSENSITIVE_FS,REFFILES 'F/D conflict on case insensitive filesystem' '
+	test_when_finished rm -rf case_insensitive &&
+	(
+		git init --bare case_insensitive &&
+		cd case_insensitive &&
+		git remote add origin -- ../case_sensitive_fd &&
+		test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err &&
+		test_grep "failed: refname conflict" err &&
+		git rev-parse refs/heads/main >expect &&
+		git rev-parse refs/heads/foo/bar >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success CASE_INSENSITIVE_FS,REFFILES 'D/F conflict on case insensitive filesystem' '
+	test_when_finished rm -rf case_insensitive &&
+	(
+		git init --bare case_insensitive &&
+		cd case_insensitive &&
+		git remote add origin -- ../case_sensitive_df &&
+		test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err &&
+		test_grep "failed: refname conflict" err &&
+		git rev-parse refs/heads/main >expect &&
+		git rev-parse refs/heads/Foo/bar >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success REFFILES 'D/F conflict on case sensitive filesystem with lock' '
+	(
+		git init --ref-format=reftable base &&
+		cd base &&
+		echo >file update &&
+		git add . &&
+		git commit -m "updated" &&
+		git branch -M main &&
+
+		git update-ref refs/heads/foo @ &&
+		git update-ref refs/heads/branch @ &&
+		cd .. &&
+
+		git init --ref-format=files --bare repo &&
+		cd repo &&
+		git remote add origin ../base &&
+		mkdir refs/heads/foo &&
+		touch refs/heads/foo/random.lock &&
+		test_must_fail git fetch origin "refs/heads/*:refs/heads/*" 2>err &&
+		test_grep "some local refs could not be updated; try running" err &&
+		git rev-parse refs/heads/main >expect &&
+		git rev-parse refs/heads/branch >actual &&
+		test_cmp expect actual
+	)
+'
+
 . "$TEST_DIRECTORY"/lib-httpd.sh
 start_httpd
 
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 4e9c27b..46926e7 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -105,7 +105,6 @@
 }
 
 test_expect_success setup '
-
 	>path1 &&
 	git add path1 &&
 	test_tick &&
@@ -117,7 +116,6 @@
 	test_tick &&
 	git commit -a -m second &&
 	the_commit=$(git show-ref -s --verify refs/heads/main)
-
 '
 
 for cmd in push fetch
@@ -322,104 +320,82 @@
 '
 
 test_expect_success 'push with matching heads' '
-
 	mk_test testrepo heads/main &&
 	git push testrepo : &&
 	check_push_result testrepo $the_commit heads/main
-
 '
 
 test_expect_success 'push with matching heads on the command line' '
-
 	mk_test testrepo heads/main &&
 	git push testrepo : &&
 	check_push_result testrepo $the_commit heads/main
-
 '
 
 test_expect_success 'failed (non-fast-forward) push with matching heads' '
-
 	mk_test testrepo heads/main &&
 	git push testrepo : &&
 	git commit --amend -massaged &&
 	test_must_fail git push testrepo &&
 	check_push_result testrepo $the_commit heads/main &&
 	git reset --hard $the_commit
-
 '
 
 test_expect_success 'push --force with matching heads' '
-
 	mk_test testrepo heads/main &&
 	git push testrepo : &&
 	git commit --amend -massaged &&
 	git push --force testrepo : &&
 	! check_push_result testrepo $the_commit heads/main &&
 	git reset --hard $the_commit
-
 '
 
 test_expect_success 'push with matching heads and forced update' '
-
 	mk_test testrepo heads/main &&
 	git push testrepo : &&
 	git commit --amend -massaged &&
 	git push testrepo +: &&
 	! check_push_result testrepo $the_commit heads/main &&
 	git reset --hard $the_commit
-
 '
 
 test_expect_success 'push with no ambiguity (1)' '
-
 	mk_test testrepo heads/main &&
 	git push testrepo main:main &&
 	check_push_result testrepo $the_commit heads/main
-
 '
 
 test_expect_success 'push with no ambiguity (2)' '
-
 	mk_test testrepo remotes/origin/main &&
 	git push testrepo main:origin/main &&
 	check_push_result testrepo $the_commit remotes/origin/main
-
 '
 
 test_expect_success 'push with colon-less refspec, no ambiguity' '
-
 	mk_test testrepo heads/main heads/t/main &&
 	git branch -f t/main main &&
 	git push testrepo main &&
 	check_push_result testrepo $the_commit heads/main &&
 	check_push_result testrepo $the_first_commit heads/t/main
-
 '
 
 test_expect_success 'push with weak ambiguity (1)' '
-
 	mk_test testrepo heads/main remotes/origin/main &&
 	git push testrepo main:main &&
 	check_push_result testrepo $the_commit heads/main &&
 	check_push_result testrepo $the_first_commit remotes/origin/main
-
 '
 
 test_expect_success 'push with weak ambiguity (2)' '
-
 	mk_test testrepo heads/main remotes/origin/main remotes/another/main &&
 	git push testrepo main:main &&
 	check_push_result testrepo $the_commit heads/main &&
 	check_push_result testrepo $the_first_commit remotes/origin/main remotes/another/main
-
 '
 
 test_expect_success 'push with ambiguity' '
-
 	mk_test testrepo heads/frotz tags/frotz &&
 	test_must_fail git push testrepo main:frotz &&
 	check_push_result testrepo $the_first_commit heads/frotz tags/frotz
-
 '
 
 test_expect_success 'push with onelevel ref' '
@@ -428,17 +404,14 @@
 '
 
 test_expect_success 'push with colon-less refspec (1)' '
-
 	mk_test testrepo heads/frotz tags/frotz &&
 	git branch -f frotz main &&
 	git push testrepo frotz &&
 	check_push_result testrepo $the_commit heads/frotz &&
 	check_push_result testrepo $the_first_commit tags/frotz
-
 '
 
 test_expect_success 'push with colon-less refspec (2)' '
-
 	mk_test testrepo heads/frotz tags/frotz &&
 	if git show-ref --verify -q refs/heads/frotz
 	then
@@ -448,7 +421,6 @@
 	git push -f testrepo frotz &&
 	check_push_result testrepo $the_commit tags/frotz &&
 	check_push_result testrepo $the_first_commit heads/frotz
-
 '
 
 test_expect_success 'push with colon-less refspec (3)' '
@@ -465,7 +437,6 @@
 '
 
 test_expect_success 'push with colon-less refspec (4)' '
-
 	mk_test testrepo &&
 	if git show-ref --verify -q refs/heads/frotz
 	then
@@ -475,38 +446,34 @@
 	git push testrepo frotz &&
 	check_push_result testrepo $the_commit tags/frotz &&
 	test 1 = $( cd testrepo && git show-ref | wc -l )
-
 '
 
 test_expect_success 'push head with non-existent, incomplete dest' '
-
 	mk_test testrepo &&
 	git push testrepo main:branch &&
 	check_push_result testrepo $the_commit heads/branch
-
 '
 
 test_expect_success 'push tag with non-existent, incomplete dest' '
-
 	mk_test testrepo &&
 	git tag -f v1.0 &&
 	git push testrepo v1.0:tag &&
 	check_push_result testrepo $the_commit tags/tag
-
 '
 
 test_expect_success 'push oid with non-existent, incomplete dest' '
-
 	mk_test testrepo &&
 	test_must_fail git push testrepo $(git rev-parse main):foo
-
 '
 
 test_expect_success 'push ref expression with non-existent, incomplete dest' '
-
 	mk_test testrepo &&
 	test_must_fail git push testrepo main^:branch
+'
 
+test_expect_success 'push ref expression with non-existent oid src' '
+	mk_test testrepo &&
+	test_must_fail git push testrepo $(test_oid 001):branch
 '
 
 for head in HEAD @
@@ -550,7 +517,6 @@
 		git checkout main &&
 		git push testrepo $head:branch &&
 		check_push_result testrepo $the_commit heads/branch
-
 	'
 
 	test_expect_success "push with config remote.*.push = $head" '
@@ -596,7 +562,6 @@
 '
 
 test_expect_success 'push with config remote.*.pushurl' '
-
 	mk_test testrepo heads/main &&
 	git checkout main &&
 	test_config remote.there.url test2repo &&
@@ -655,7 +620,6 @@
 '
 
 test_expect_success 'push with dry-run' '
-
 	mk_test testrepo heads/main &&
 	old_commit=$(git -C testrepo show-ref -s --verify refs/heads/main) &&
 	git push --dry-run testrepo : &&
@@ -663,7 +627,6 @@
 '
 
 test_expect_success 'push updates local refs' '
-
 	mk_test testrepo heads/main &&
 	mk_child testrepo child &&
 	(
@@ -673,11 +636,9 @@
 		test $(git rev-parse main) = \
 			$(git rev-parse remotes/origin/main)
 	)
-
 '
 
 test_expect_success 'push updates up-to-date local refs' '
-
 	mk_test testrepo heads/main &&
 	mk_child testrepo child1 &&
 	mk_child testrepo child2 &&
@@ -689,11 +650,9 @@
 		test $(git rev-parse main) = \
 			$(git rev-parse remotes/origin/main)
 	)
-
 '
 
 test_expect_success 'push preserves up-to-date packed refs' '
-
 	mk_test testrepo heads/main &&
 	mk_child testrepo child &&
 	(
@@ -701,11 +660,9 @@
 		git push &&
 		! test -f .git/refs/remotes/origin/main
 	)
-
 '
 
 test_expect_success 'push does not update local refs on failure' '
-
 	mk_test testrepo heads/main &&
 	mk_child testrepo child &&
 	echo "#!/no/frobnication/today" >testrepo/.git/hooks/pre-receive &&
@@ -717,16 +674,13 @@
 		test $(git rev-parse main) != \
 			$(git rev-parse remotes/origin/main)
 	)
-
 '
 
 test_expect_success 'allow deleting an invalid remote ref' '
-
 	mk_test testrepo heads/branch &&
 	rm -f testrepo/.git/objects/??/* &&
 	git push testrepo :refs/heads/branch &&
 	(cd testrepo && test_must_fail git rev-parse --verify refs/heads/branch)
-
 '
 
 test_expect_success 'pushing valid refs triggers post-receive and post-update hooks' '
diff --git a/t/t5530-upload-pack-error.sh b/t/t5530-upload-pack-error.sh
index 558eedf..d40292c 100755
--- a/t/t5530-upload-pack-error.sh
+++ b/t/t5530-upload-pack-error.sh
@@ -4,8 +4,6 @@
 
 . ./test-lib.sh
 
-D=$(pwd)
-
 corrupt_repo () {
 	object_sha1=$(git rev-parse "$1") &&
 	ob=$(expr "$object_sha1" : "\(..\)") &&
@@ -21,11 +19,7 @@
 	test_tick &&
 	echo changed >file &&
 	git commit -a -m changed &&
-	corrupt_repo HEAD:file
-
-'
-
-test_expect_success 'fsck fails' '
+	corrupt_repo HEAD:file &&
 	test_must_fail git fsck
 '
 
@@ -40,17 +34,12 @@
 '
 
 test_expect_success 'corrupt repo differently' '
-
 	git hash-object -w file &&
-	corrupt_repo HEAD^^{tree}
-
-'
-
-test_expect_success 'fsck fails' '
+	corrupt_repo HEAD^^{tree} &&
 	test_must_fail git fsck
 '
-test_expect_success 'upload-pack fails due to error in rev-list' '
 
+test_expect_success 'upload-pack fails due to error in rev-list' '
 	printf "%04xwant %s\n%04xshallow %s00000009done\n0000" \
 		$(($hexsz + 10)) $(git rev-parse HEAD) \
 		$(($hexsz + 12)) $(git rev-parse HEAD^) >input &&
@@ -59,7 +48,6 @@
 '
 
 test_expect_success 'upload-pack fails due to bad want (no object)' '
-
 	printf "%04xwant %s multi_ack_detailed\n00000009done\n0000" \
 		$(($hexsz + 29)) $(test_oid deadbeef) >input &&
 	test_must_fail git upload-pack . <input >output 2>output.err &&
@@ -69,7 +57,6 @@
 '
 
 test_expect_success 'upload-pack fails due to bad want (not tip)' '
-
 	oid=$(echo an object we have | git hash-object -w --stdin) &&
 	printf "%04xwant %s multi_ack_detailed\n00000009done\n0000" \
 		$(($hexsz + 29)) "$oid" >input &&
@@ -80,7 +67,6 @@
 '
 
 test_expect_success 'upload-pack fails due to error in pack-objects enumeration' '
-
 	printf "%04xwant %s\n00000009done\n0000" \
 		$((hexsz + 10)) $(git rev-parse HEAD) >input &&
 	test_must_fail git upload-pack . <input >/dev/null 2>output.err &&
@@ -105,18 +91,48 @@
 	test_cmp expect actual
 '
 
-test_expect_success 'create empty repository' '
-
-	mkdir foo &&
-	cd foo &&
-	git init
-
+test_expect_success 'fetch fails' '
+	git init foo &&
+	test_must_fail git -C foo fetch .. main
 '
 
-test_expect_success 'fetch fails' '
+test_expect_success 'upload-pack ACKs repeated non-commit objects repeatedly (protocol v0)' '
+	commit_id=$(git rev-parse HEAD) &&
+	tree_id=$(git rev-parse HEAD^{tree}) &&
+	test-tool pkt-line pack >request <<-EOF &&
+	want $commit_id
+	0000
+	have $tree_id
+	have $tree_id
+	0000
+	EOF
+	git upload-pack --stateless-rpc . <request >actual &&
+	depacketize <actual >actual.raw &&
+	grep ^ACK actual.raw >actual.acks &&
+	cat >expect <<-EOF &&
+	ACK $tree_id
+	ACK $tree_id
+	EOF
+	test_cmp expect actual.acks
+'
 
-	test_must_fail git fetch .. main
-
+test_expect_success 'upload-pack ACKs repeated non-commit objects once only (protocol v2)' '
+	commit_id=$(git rev-parse HEAD) &&
+	tree_id=$(git rev-parse HEAD^{tree}) &&
+	test-tool pkt-line pack >request <<-EOF &&
+	command=fetch
+	object-format=$(test_oid algo)
+	0001
+	want $commit_id
+	have $tree_id
+	have $tree_id
+	0000
+	EOF
+	GIT_PROTOCOL=version=2 git upload-pack . <request >actual &&
+	depacketize <actual >actual.raw &&
+	grep ^ACK actual.raw >actual.acks &&
+	echo "ACK $tree_id" >expect &&
+	test_cmp expect actual.acks
 '
 
 test_done
diff --git a/t/t5564-http-proxy.sh b/t/t5564-http-proxy.sh
index b27e481..c3903fa 100755
--- a/t/t5564-http-proxy.sh
+++ b/t/t5564-http-proxy.sh
@@ -72,7 +72,9 @@
 	test_when_finished "rm -rf clone" &&
 	test_config_global http.proxy "socks4://localhost$PWD/%2530.sock" && {
 		{
-			GIT_TRACE_CURL=$PWD/trace git clone "$HTTPD_URL/smart/repo.git" clone 2>err &&
+			GIT_TRACE_CURL=$PWD/trace \
+			GIT_TRACE_CURL_COMPONENTS=socks \
+			git clone "$HTTPD_URL/smart/repo.git" clone 2>err &&
 			grep -i "SOCKS4 request granted" trace
 		} ||
 		old_libcurl_error err
diff --git a/t/t5710-promisor-remote-capability.sh b/t/t5710-promisor-remote-capability.sh
index cb061b1..023735d 100755
--- a/t/t5710-promisor-remote-capability.sh
+++ b/t/t5710-promisor-remote-capability.sh
@@ -295,6 +295,71 @@
 	check_missing_objects server 1 "$oid"
 '
 
+test_expect_success "clone with promisor.sendFields" '
+	git -C server config promisor.advertise true &&
+	test_when_finished "rm -rf client" &&
+
+	git -C server remote add otherLop "https://invalid.invalid"  &&
+	git -C server config remote.otherLop.token "fooBar" &&
+	git -C server config remote.otherLop.stuff "baz" &&
+	git -C server config remote.otherLop.partialCloneFilter "blob:limit=10k" &&
+	test_when_finished "git -C server remote remove otherLop" &&
+	test_config -C server promisor.sendFields "partialCloneFilter, token" &&
+	test_when_finished "rm trace" &&
+
+	# Clone from server to create a client
+	GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \
+		-c remote.lop.promisor=true \
+		-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
+		-c remote.lop.url="file://$(pwd)/lop" \
+		-c promisor.acceptfromserver=All \
+		--no-local --filter="blob:limit=5k" server client &&
+
+	# Check that fields are properly transmitted
+	ENCODED_URL=$(echo "file://$(pwd)/lop" | sed -e "s/ /%20/g") &&
+	PR1="name=lop,url=$ENCODED_URL,partialCloneFilter=blob:none" &&
+	PR2="name=otherLop,url=https://invalid.invalid,partialCloneFilter=blob:limit=10k,token=fooBar" &&
+	test_grep "clone< promisor-remote=$PR1;$PR2" trace &&
+	test_grep "clone> promisor-remote=lop;otherLop" trace &&
+
+	# Check that the largest object is still missing on the server
+	check_missing_objects server 1 "$oid"
+'
+
+test_expect_success "clone with promisor.checkFields" '
+	git -C server config promisor.advertise true &&
+	test_when_finished "rm -rf client" &&
+
+	git -C server remote add otherLop "https://invalid.invalid"  &&
+	git -C server config remote.otherLop.token "fooBar" &&
+	git -C server config remote.otherLop.stuff "baz" &&
+	git -C server config remote.otherLop.partialCloneFilter "blob:limit=10k" &&
+	test_when_finished "git -C server remote remove otherLop" &&
+	test_config -C server promisor.sendFields "partialCloneFilter, token" &&
+	test_when_finished "rm trace" &&
+
+	# Clone from server to create a client
+	GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \
+		-c remote.lop.promisor=true \
+		-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
+		-c remote.lop.url="file://$(pwd)/lop" \
+		-c remote.lop.partialCloneFilter="blob:none" \
+		-c promisor.acceptfromserver=All \
+		-c promisor.checkFields=partialcloneFilter \
+		--no-local --filter="blob:limit=5k" server client &&
+
+	# Check that fields are properly transmitted
+	ENCODED_URL=$(echo "file://$(pwd)/lop" | sed -e "s/ /%20/g") &&
+	PR1="name=lop,url=$ENCODED_URL,partialCloneFilter=blob:none" &&
+	PR2="name=otherLop,url=https://invalid.invalid,partialCloneFilter=blob:limit=10k,token=fooBar" &&
+	test_grep "clone< promisor-remote=$PR1;$PR2" trace &&
+	test_grep "clone> promisor-remote=lop" trace &&
+	test_grep ! "clone> promisor-remote=lop;otherLop" trace &&
+
+	# Check that the largest object is still missing on the server
+	check_missing_objects server 1 "$oid"
+'
+
 test_expect_success "clone with promisor.advertise set to 'true' but don't delete the client" '
 	git -C server config promisor.advertise true &&
 
diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh
index 256ccae..2c70cc5 100755
--- a/t/t6120-describe.sh
+++ b/t/t6120-describe.sh
@@ -409,6 +409,36 @@
 	test_grep "fatal: test-blob-1 is neither a commit nor blob" actual
 '
 
+test_expect_success 'describe an unreachable blob' '
+	blob=$(echo not-found-anywhere | git hash-object -w --stdin) &&
+	test_must_fail git describe $blob 2>actual &&
+	test_grep "blob .$blob. not reachable from HEAD" actual
+'
+
+test_expect_success 'describe blob on an unborn branch' '
+	oldbranch=$(git symbolic-ref HEAD) &&
+	test_when_finished "git symbolic-ref HEAD $oldbranch" &&
+	git symbolic-ref HEAD refs/heads/does-not-exist &&
+	test_must_fail git describe test-blob 2>actual &&
+	test_grep "cannot search .* on an unborn branch" actual
+'
+
+# This test creates a repository state that we generally try to disallow: HEAD
+# is pointing to an object that is not a commit. The ref update code forbids
+# non-commit writes directly to HEAD or to any branch in refs/heads/.  But we
+# can use the loophole of pointing HEAD to another non-branch ref (something we
+# should forbid, but don't for historical reasons).
+#
+# Do not take this test as an endorsement of the loophole! If we ever tighten
+# it, it is reasonable to just drop this test entirely.
+test_expect_success 'describe blob on a non-commit HEAD' '
+	oldbranch=$(git symbolic-ref HEAD) &&
+	test_when_finished "git symbolic-ref HEAD $oldbranch" &&
+	git symbolic-ref HEAD refs/tags/test-blob &&
+	test_must_fail git describe test-blob 2>actual &&
+	test_grep "blob .* not reachable from HEAD" actual
+'
+
 test_expect_success ULIMIT_STACK_SIZE 'name-rev works in a deep repo' '
 	i=1 &&
 	while test $i -lt 8000
diff --git a/t/t6137-pathspec-wildcards-literal.sh b/t/t6137-pathspec-wildcards-literal.sh
index 20abad5..17a0308 100755
--- a/t/t6137-pathspec-wildcards-literal.sh
+++ b/t/t6137-pathspec-wildcards-literal.sh
@@ -3,8 +3,8 @@
 
 . ./test-lib.sh
 
-test_have_prereq FUNNYNAMES || {
-	skip_all='skipping: needs FUNNYNAMES (non-Windows only)'
+test_have_prereq BSLASHPSPEC || {
+	skip_all='skipping: needs BSLASHPSPEC (backslashes in pathspecs)'
 	test_done
 }
 
@@ -184,7 +184,7 @@
 	)
 '
 
-test_expect_success 'add literal \? literal' '
+test_expect_success 'add literal \?' '
 	git init test-q-lit &&
 	(
 		cd test-q-lit &&
@@ -241,7 +241,7 @@
 	)
 '
 
-test_expect_success 'add literal [abc]' '
+test_expect_success 'add literal \[abc\]' '
 	git init test-brackets-lit &&
 	(
 		cd test-brackets-lit &&
@@ -280,7 +280,7 @@
 	)
 '
 
-test_expect_success 'commit: literal *' '
+test_expect_success 'commit: literal \*' '
 	git init test-c-asterisk-lit &&
 	(
 		cd test-c-asterisk-lit &&
@@ -328,7 +328,7 @@
 	)
 '
 
-test_expect_success 'commit: wildcard pathspec limits commit' '
+test_expect_success 'commit: wildcard f**' '
 	git init test-c-pathlimit &&
 	(
 		cd test-c-pathlimit &&
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index ce9af79..1d98091 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -6,2150 +6,14 @@
 test_description='for-each-ref test'
 
 . ./test-lib.sh
-GNUPGHOME_NOT_USED=$GNUPGHOME
-. "$TEST_DIRECTORY"/lib-gpg.sh
-. "$TEST_DIRECTORY"/lib-terminal.sh
 
-# Mon Jul 3 23:18:43 2006 +0000
-datestamp=1151968723
-setdate_and_increment () {
-    GIT_COMMITTER_DATE="$datestamp +0200"
-    datestamp=$(expr "$datestamp" + 1)
-    GIT_AUTHOR_DATE="$datestamp +0200"
-    datestamp=$(expr "$datestamp" + 1)
-    export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
-}
-
-test_object_file_size () {
-	oid=$(git rev-parse "$1")
-	path=".git/objects/$(test_oid_to_path $oid)"
-	test_file_size "$path"
-}
-
-test_expect_success setup '
-	# setup .mailmap
-	cat >.mailmap <<-EOF &&
-	A Thor <athor@example.com> A U Thor <author@example.com>
-	C Mitter <cmitter@example.com> C O Mitter <committer@example.com>
-	EOF
-
-	setdate_and_increment &&
-	echo "Using $datestamp" > one &&
-	git add one &&
-	git commit -m "Initial" &&
-	git branch -M main &&
-	setdate_and_increment &&
-	git tag -a -m "Tagging at $datestamp" testtag &&
-	git update-ref refs/remotes/origin/main main &&
-	git remote add origin nowhere &&
-	git config branch.main.remote origin &&
-	git config branch.main.merge refs/heads/main &&
-	git remote add myfork elsewhere &&
-	git config remote.pushdefault myfork &&
-	git config push.default current
-'
-
-test_atom () {
-	case "$1" in
-		head) ref=refs/heads/main ;;
-		 tag) ref=refs/tags/testtag ;;
-		 sym) ref=refs/heads/sym ;;
-		   *) ref=$1 ;;
-	esac
-	format=$2
-	test_do=test_expect_${4:-success}
-
-	printf '%s\n' "$3" >expected
-	$test_do $PREREQ "basic atom: $ref $format" '
-		git for-each-ref --format="%($format)" "$ref" >actual &&
-		sanitize_pgp <actual >actual.clean &&
-		test_cmp expected actual.clean
-	'
-
-	# Automatically test "contents:size" atom after testing "contents"
-	if test "$format" = "contents"
-	then
-		# for commit leg, $3 is changed there
-		expect=$(printf '%s' "$3" | wc -c)
-		$test_do $PREREQ "basic atom: $ref contents:size" '
-			type=$(git cat-file -t "$ref") &&
-			case $type in
-			tag)
-				# We cannot use $3 as it expects sanitize_pgp to run
-				git cat-file tag $ref >out &&
-				expect=$(tail -n +6 out | wc -c) &&
-				rm -f out ;;
-			tree | blob)
-				expect="" ;;
-			commit)
-				: "use the calculated expect" ;;
-			*)
-				BUG "unknown object type" ;;
-			esac &&
-			# Leave $expect unquoted to lose possible leading whitespaces
-			echo $expect >expected &&
-			git for-each-ref --format="%(contents:size)" "$ref" >actual &&
-			test_cmp expected actual
-		'
-	fi
-}
-
-hexlen=$(test_oid hexsz)
-
-test_atom head refname refs/heads/main
-test_atom head refname: refs/heads/main
-test_atom head refname:short main
-test_atom head refname:lstrip=1 heads/main
-test_atom head refname:lstrip=2 main
-test_atom head refname:lstrip=-1 main
-test_atom head refname:lstrip=-2 heads/main
-test_atom head refname:rstrip=1 refs/heads
-test_atom head refname:rstrip=2 refs
-test_atom head refname:rstrip=-1 refs
-test_atom head refname:rstrip=-2 refs/heads
-test_atom head refname:strip=1 heads/main
-test_atom head refname:strip=2 main
-test_atom head refname:strip=-1 main
-test_atom head refname:strip=-2 heads/main
-test_atom head upstream refs/remotes/origin/main
-test_atom head upstream:short origin/main
-test_atom head upstream:lstrip=2 origin/main
-test_atom head upstream:lstrip=-2 origin/main
-test_atom head upstream:rstrip=2 refs/remotes
-test_atom head upstream:rstrip=-2 refs/remotes
-test_atom head upstream:strip=2 origin/main
-test_atom head upstream:strip=-2 origin/main
-test_atom head push refs/remotes/myfork/main
-test_atom head push:short myfork/main
-test_atom head push:lstrip=1 remotes/myfork/main
-test_atom head push:lstrip=-1 main
-test_atom head push:rstrip=1 refs/remotes/myfork
-test_atom head push:rstrip=-1 refs
-test_atom head push:strip=1 remotes/myfork/main
-test_atom head push:strip=-1 main
-test_atom head objecttype commit
-test_atom head objectsize $((131 + hexlen))
-test_atom head objectsize:disk $(test_object_file_size refs/heads/main)
-test_atom head deltabase $ZERO_OID
-test_atom head objectname $(git rev-parse refs/heads/main)
-test_atom head objectname:short $(git rev-parse --short refs/heads/main)
-test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
-test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
-test_atom head tree $(git rev-parse refs/heads/main^{tree})
-test_atom head tree:short $(git rev-parse --short refs/heads/main^{tree})
-test_atom head tree:short=1 $(git rev-parse --short=1 refs/heads/main^{tree})
-test_atom head tree:short=10 $(git rev-parse --short=10 refs/heads/main^{tree})
-test_atom head parent ''
-test_atom head parent:short ''
-test_atom head parent:short=1 ''
-test_atom head parent:short=10 ''
-test_atom head numparent 0
-test_atom head object ''
-test_atom head type ''
-test_atom head raw "$(git cat-file commit refs/heads/main)
-"
-test_atom head '*objectname' ''
-test_atom head '*objecttype' ''
-test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
-test_atom head authorname 'A U Thor'
-test_atom head authorname:mailmap 'A Thor'
-test_atom head authoremail '<author@example.com>'
-test_atom head authoremail:trim 'author@example.com'
-test_atom head authoremail:localpart 'author'
-test_atom head authoremail:trim,localpart 'author'
-test_atom head authoremail:mailmap '<athor@example.com>'
-test_atom head authoremail:mailmap,trim 'athor@example.com'
-test_atom head authoremail:trim,mailmap 'athor@example.com'
-test_atom head authoremail:mailmap,localpart 'athor'
-test_atom head authoremail:localpart,mailmap 'athor'
-test_atom head authoremail:mailmap,trim,localpart,mailmap,trim 'athor'
-test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
-test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
-test_atom head committername 'C O Mitter'
-test_atom head committername:mailmap 'C Mitter'
-test_atom head committeremail '<committer@example.com>'
-test_atom head committeremail:trim 'committer@example.com'
-test_atom head committeremail:localpart 'committer'
-test_atom head committeremail:localpart,trim 'committer'
-test_atom head committeremail:mailmap '<cmitter@example.com>'
-test_atom head committeremail:mailmap,trim 'cmitter@example.com'
-test_atom head committeremail:trim,mailmap 'cmitter@example.com'
-test_atom head committeremail:mailmap,localpart 'cmitter'
-test_atom head committeremail:localpart,mailmap 'cmitter'
-test_atom head committeremail:trim,mailmap,trim,trim,localpart 'cmitter'
-test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
-test_atom head tag ''
-test_atom head tagger ''
-test_atom head taggername ''
-test_atom head taggeremail ''
-test_atom head taggeremail:trim ''
-test_atom head taggeremail:localpart ''
-test_atom head taggerdate ''
-test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
-test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
-test_atom head subject 'Initial'
-test_atom head subject:sanitize 'Initial'
-test_atom head contents:subject 'Initial'
-test_atom head body ''
-test_atom head contents:body ''
-test_atom head contents:signature ''
-test_atom head contents 'Initial
-'
-test_atom head HEAD '*'
-
-test_atom tag refname refs/tags/testtag
-test_atom tag refname:short testtag
-test_atom tag upstream ''
-test_atom tag push ''
-test_atom tag objecttype tag
-test_atom tag objectsize $((114 + hexlen))
-test_atom tag objectsize:disk $(test_object_file_size refs/tags/testtag)
-test_atom tag '*objectsize:disk' $(test_object_file_size refs/heads/main)
-test_atom tag deltabase $ZERO_OID
-test_atom tag '*deltabase' $ZERO_OID
-test_atom tag objectname $(git rev-parse refs/tags/testtag)
-test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
-test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
-test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
-test_atom tag tree ''
-test_atom tag tree:short ''
-test_atom tag tree:short=1 ''
-test_atom tag tree:short=10 ''
-test_atom tag parent ''
-test_atom tag parent:short ''
-test_atom tag parent:short=1 ''
-test_atom tag parent:short=10 ''
-test_atom tag numparent ''
-test_atom tag object $(git rev-parse refs/tags/testtag^0)
-test_atom tag type 'commit'
-test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{})
-test_atom tag '*objecttype' 'commit'
-test_atom tag author ''
-test_atom tag authorname ''
-test_atom tag authorname:mailmap ''
-test_atom tag authoremail ''
-test_atom tag authoremail:trim ''
-test_atom tag authoremail:localpart ''
-test_atom tag authoremail:trim,localpart ''
-test_atom tag authoremail:mailmap ''
-test_atom tag authoremail:mailmap,trim ''
-test_atom tag authoremail:trim,mailmap ''
-test_atom tag authoremail:mailmap,localpart ''
-test_atom tag authoremail:localpart,mailmap ''
-test_atom tag authoremail:mailmap,trim,localpart,mailmap,trim ''
-test_atom tag authordate ''
-test_atom tag committer ''
-test_atom tag committername ''
-test_atom tag committername:mailmap ''
-test_atom tag committeremail ''
-test_atom tag committeremail:trim ''
-test_atom tag committeremail:localpart ''
-test_atom tag committeremail:localpart,trim ''
-test_atom tag committeremail:mailmap ''
-test_atom tag committeremail:mailmap,trim ''
-test_atom tag committeremail:trim,mailmap ''
-test_atom tag committeremail:mailmap,localpart ''
-test_atom tag committeremail:localpart,mailmap ''
-test_atom tag committeremail:trim,mailmap,trim,trim,localpart ''
-test_atom tag committerdate ''
-test_atom tag tag 'testtag'
-test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
-test_atom tag taggername 'C O Mitter'
-test_atom tag taggername:mailmap 'C Mitter'
-test_atom tag taggeremail '<committer@example.com>'
-test_atom tag taggeremail:trim 'committer@example.com'
-test_atom tag taggeremail:localpart 'committer'
-test_atom tag taggeremail:trim,localpart 'committer'
-test_atom tag taggeremail:mailmap '<cmitter@example.com>'
-test_atom tag taggeremail:mailmap,trim 'cmitter@example.com'
-test_atom tag taggeremail:trim,mailmap 'cmitter@example.com'
-test_atom tag taggeremail:mailmap,localpart 'cmitter'
-test_atom tag taggeremail:localpart,mailmap 'cmitter'
-test_atom tag taggeremail:trim,mailmap,trim,localpart,localpart 'cmitter'
-test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
-test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
-test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
-test_atom tag subject 'Tagging at 1151968727'
-test_atom tag subject:sanitize 'Tagging-at-1151968727'
-test_atom tag contents:subject 'Tagging at 1151968727'
-test_atom tag body ''
-test_atom tag contents:body ''
-test_atom tag contents:signature ''
-test_atom tag contents 'Tagging at 1151968727
-'
-test_atom tag HEAD ' '
-
-test_expect_success 'basic atom: refs/tags/testtag *raw' '
-	git cat-file commit refs/tags/testtag^{} >expected &&
-	git for-each-ref --format="%(*raw)" refs/tags/testtag >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_expect_success 'Check invalid atoms names are errors' '
-	test_must_fail git for-each-ref --format="%(INVALID)" refs/heads
-'
-
-test_expect_success 'for-each-ref does not crash with -h' '
+test_expect_success "for-each-ref does not crash with -h" '
 	test_expect_code 129 git for-each-ref -h >usage &&
 	test_grep "[Uu]sage: git for-each-ref " usage &&
 	test_expect_code 129 nongit git for-each-ref -h >usage &&
 	test_grep "[Uu]sage: git for-each-ref " usage
 '
 
-test_expect_success 'Check format specifiers are ignored in naming date atoms' '
-	git for-each-ref --format="%(authordate)" refs/heads &&
-	git for-each-ref --format="%(authordate:default) %(authordate)" refs/heads &&
-	git for-each-ref --format="%(authordate) %(authordate:default)" refs/heads &&
-	git for-each-ref --format="%(authordate:default) %(authordate:default)" refs/heads
-'
-
-test_expect_success 'Check valid format specifiers for date fields' '
-	git for-each-ref --format="%(authordate:default)" refs/heads &&
-	git for-each-ref --format="%(authordate:relative)" refs/heads &&
-	git for-each-ref --format="%(authordate:short)" refs/heads &&
-	git for-each-ref --format="%(authordate:local)" refs/heads &&
-	git for-each-ref --format="%(authordate:iso8601)" refs/heads &&
-	git for-each-ref --format="%(authordate:rfc2822)" refs/heads
-'
-
-test_expect_success 'Check invalid format specifiers are errors' '
-	test_must_fail git for-each-ref --format="%(authordate:INVALID)" refs/heads
-'
-
-test_expect_success 'arguments to %(objectname:short=) must be positive integers' '
-	test_must_fail git for-each-ref --format="%(objectname:short=0)" &&
-	test_must_fail git for-each-ref --format="%(objectname:short=-1)" &&
-	test_must_fail git for-each-ref --format="%(objectname:short=foo)"
-'
-
-test_bad_atom () {
-	case "$1" in
-	head) ref=refs/heads/main ;;
-	 tag) ref=refs/tags/testtag ;;
-	 sym) ref=refs/heads/sym ;;
-	   *) ref=$1 ;;
-	esac
-	format=$2
-	test_do=test_expect_${4:-success}
-
-	printf '%s\n' "$3" >expect
-	$test_do $PREREQ "err basic atom: $ref $format" '
-		test_must_fail git for-each-ref \
-			--format="%($format)" "$ref" 2>error &&
-		test_cmp expect error
-	'
-}
-
-test_bad_atom head 'authoremail:foo' \
-	'fatal: unrecognized %(authoremail) argument: foo'
-
-test_bad_atom head 'authoremail:mailmap,trim,bar' \
-	'fatal: unrecognized %(authoremail) argument: bar'
-
-test_bad_atom head 'authoremail:trim,' \
-	'fatal: unrecognized %(authoremail) argument: '
-
-test_bad_atom head 'authoremail:mailmaptrim' \
-	'fatal: unrecognized %(authoremail) argument: trim'
-
-test_bad_atom head 'committeremail: ' \
-	'fatal: unrecognized %(committeremail) argument:  '
-
-test_bad_atom head 'committeremail: trim,foo' \
-	'fatal: unrecognized %(committeremail) argument:  trim,foo'
-
-test_bad_atom head 'committeremail:mailmap,localpart ' \
-	'fatal: unrecognized %(committeremail) argument:  '
-
-test_bad_atom head 'committeremail:trim_localpart' \
-	'fatal: unrecognized %(committeremail) argument: _localpart'
-
-test_bad_atom head 'committeremail:localpart,,,trim' \
-	'fatal: unrecognized %(committeremail) argument: ,,trim'
-
-test_bad_atom tag 'taggeremail:mailmap,trim, foo ' \
-	'fatal: unrecognized %(taggeremail) argument:  foo '
-
-test_bad_atom tag 'taggeremail:trim,localpart,' \
-	'fatal: unrecognized %(taggeremail) argument: '
-
-test_bad_atom tag 'taggeremail:mailmap;localpart trim' \
-	'fatal: unrecognized %(taggeremail) argument: ;localpart trim'
-
-test_bad_atom tag 'taggeremail:localpart trim' \
-	'fatal: unrecognized %(taggeremail) argument:  trim'
-
-test_bad_atom tag 'taggeremail:mailmap,mailmap,trim,qux,localpart,trim' \
-	'fatal: unrecognized %(taggeremail) argument: qux,localpart,trim'
-
-test_date () {
-	f=$1 &&
-	committer_date=$2 &&
-	author_date=$3 &&
-	tagger_date=$4 &&
-	cat >expected <<-EOF &&
-	'refs/heads/main' '$committer_date' '$author_date'
-	'refs/tags/testtag' '$tagger_date'
-	EOF
-	(
-		git for-each-ref --shell \
-			--format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \
-			refs/heads &&
-		git for-each-ref --shell \
-			--format="%(refname) %(taggerdate${f:+:$f})" \
-			refs/tags
-	) >actual &&
-	test_cmp expected actual
-}
-
-test_expect_success 'Check unformatted date fields output' '
-	test_date "" \
-		"Tue Jul 4 01:18:43 2006 +0200" \
-		"Tue Jul 4 01:18:44 2006 +0200" \
-		"Tue Jul 4 01:18:45 2006 +0200"
-'
-
-test_expect_success 'Check format "default" formatted date fields output' '
-	test_date default \
-		"Tue Jul 4 01:18:43 2006 +0200" \
-		"Tue Jul 4 01:18:44 2006 +0200" \
-		"Tue Jul 4 01:18:45 2006 +0200"
-'
-
-test_expect_success 'Check format "default-local" date fields output' '
-	test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006"
-'
-
-# Don't know how to do relative check because I can't know when this script
-# is going to be run and can't fake the current time to git, and hence can't
-# provide expected output.  Instead, I'll just make sure that "relative"
-# doesn't exit in error
-test_expect_success 'Check format "relative" date fields output' '
-	f=relative &&
-	(git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-	git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
-'
-
-# We just check that this is the same as "relative" for now.
-test_expect_success 'Check format "relative-local" date fields output' '
-	test_date relative-local \
-		"$(git for-each-ref --format="%(committerdate:relative)" refs/heads)" \
-		"$(git for-each-ref --format="%(authordate:relative)" refs/heads)" \
-		"$(git for-each-ref --format="%(taggerdate:relative)" refs/tags)"
-'
-
-test_expect_success 'Check format "short" date fields output' '
-	test_date short 2006-07-04 2006-07-04 2006-07-04
-'
-
-test_expect_success 'Check format "short-local" date fields output' '
-	test_date short-local 2006-07-03 2006-07-03 2006-07-03
-'
-
-test_expect_success 'Check format "local" date fields output' '
-	test_date local \
-		"Mon Jul 3 23:18:43 2006" \
-		"Mon Jul 3 23:18:44 2006" \
-		"Mon Jul 3 23:18:45 2006"
-'
-
-test_expect_success 'Check format "iso8601" date fields output' '
-	test_date iso8601 \
-		"2006-07-04 01:18:43 +0200" \
-		"2006-07-04 01:18:44 +0200" \
-		"2006-07-04 01:18:45 +0200"
-'
-
-test_expect_success 'Check format "iso8601-local" date fields output' '
-	test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000"
-'
-
-test_expect_success 'Check format "rfc2822" date fields output' '
-	test_date rfc2822 \
-		"Tue, 4 Jul 2006 01:18:43 +0200" \
-		"Tue, 4 Jul 2006 01:18:44 +0200" \
-		"Tue, 4 Jul 2006 01:18:45 +0200"
-'
-
-test_expect_success 'Check format "rfc2822-local" date fields output' '
-	test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000"
-'
-
-test_expect_success 'Check format "raw" date fields output' '
-	test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200"
-'
-
-test_expect_success 'Check format "raw-local" date fields output' '
-	test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000"
-'
-
-test_expect_success 'Check format of strftime date fields' '
-	echo "my date is 2006-07-04" >expected &&
-	git for-each-ref \
-	  --format="%(authordate:format:my date is %Y-%m-%d)" \
-	  refs/heads >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Check format of strftime-local date fields' '
-	echo "my date is 2006-07-03" >expected &&
-	git for-each-ref \
-	  --format="%(authordate:format-local:my date is %Y-%m-%d)" \
-	  refs/heads >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'exercise strftime with odd fields' '
-	echo >expected &&
-	git for-each-ref --format="%(authordate:format:)" refs/heads >actual &&
-	test_cmp expected actual &&
-	long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" &&
-	echo $long >expected &&
-	git for-each-ref --format="%(authordate:format:$long)" refs/heads >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/heads/main
-refs/remotes/origin/main
-refs/tags/testtag
-EOF
-
-test_expect_success 'Verify ascending sort' '
-	git for-each-ref --format="%(refname)" --sort=refname >actual &&
-	test_cmp expected actual
-'
-
-
-cat >expected <<\EOF
-refs/tags/testtag
-refs/remotes/origin/main
-refs/heads/main
-EOF
-
-test_expect_success 'Verify descending sort' '
-	git for-each-ref --format="%(refname)" --sort=-refname >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Give help even with invalid sort atoms' '
-	test_expect_code 129 git for-each-ref --sort=bogus -h >actual 2>&1 &&
-	grep "^usage: git for-each-ref" actual
-'
-
-cat >expected <<\EOF
-refs/tags/testtag
-refs/tags/testtag-2
-EOF
-
-test_expect_success 'exercise patterns with prefixes' '
-	git tag testtag-2 &&
-	test_when_finished "git tag -d testtag-2" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/testtag refs/tags/testtag-2 >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/tags/testtag
-refs/tags/testtag-2
-EOF
-
-test_expect_success 'exercise glob patterns with prefixes' '
-	git tag testtag-2 &&
-	test_when_finished "git tag -d testtag-2" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/testtag "refs/tags/testtag-*" >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/tags/bar
-refs/tags/baz
-refs/tags/testtag
-EOF
-
-test_expect_success 'exercise patterns with prefix exclusions' '
-	for tag in foo/one foo/two foo/three bar baz
-	do
-		git tag "$tag" || return 1
-	done &&
-	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/ --exclude=refs/tags/foo >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/tags/bar
-refs/tags/baz
-refs/tags/foo/one
-refs/tags/testtag
-EOF
-
-test_expect_success 'exercise patterns with pattern exclusions' '
-	for tag in foo/one foo/two foo/three bar baz
-	do
-		git tag "$tag" || return 1
-	done &&
-	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/ --exclude="refs/tags/foo/t*" >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-'refs/heads/main'
-'refs/remotes/origin/main'
-'refs/tags/testtag'
-EOF
-
-test_expect_success 'Quoting style: shell' '
-	git for-each-ref --shell --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Quoting style: perl' '
-	git for-each-ref --perl --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Quoting style: python' '
-	git for-each-ref --python --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-"refs/heads/main"
-"refs/remotes/origin/main"
-"refs/tags/testtag"
-EOF
-
-test_expect_success 'Quoting style: tcl' '
-	git for-each-ref --tcl --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
-	test_expect_success "more than one quoting style: $i" "
-		test_must_fail git for-each-ref $i 2>err &&
-		grep '^error: more than one quoting style' err
-	"
-done
-
-test_expect_success 'setup for upstream:track[short]' '
-	test_commit two
-'
-
-test_atom head upstream:track '[ahead 1]'
-test_atom head upstream:trackshort '>'
-test_atom head upstream:track,nobracket 'ahead 1'
-test_atom head upstream:nobracket,track 'ahead 1'
-
-test_expect_success 'setup for push:track[short]' '
-	test_commit third &&
-	git update-ref refs/remotes/myfork/main main &&
-	git reset main~1
-'
-
-test_atom head push:track '[behind 1]'
-test_atom head push:trackshort '<'
-
-test_expect_success 'Check that :track[short] cannot be used with other atoms' '
-	test_must_fail git for-each-ref --format="%(refname:track)" 2>/dev/null &&
-	test_must_fail git for-each-ref --format="%(refname:trackshort)" 2>/dev/null
-'
-
-test_expect_success 'Check that :track[short] works when upstream is invalid' '
-	cat >expected <<-\EOF &&
-	[gone]
-
-	EOF
-	test_when_finished "git config branch.main.merge refs/heads/main" &&
-	git config branch.main.merge refs/heads/does-not-exist &&
-	git for-each-ref \
-		--format="%(upstream:track)$LF%(upstream:trackshort)" \
-		refs/heads >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Check for invalid refname format' '
-	test_must_fail git for-each-ref --format="%(refname:INVALID)"
-'
-
-test_expect_success 'set up color tests' '
-	cat >expected.color <<-EOF &&
-	$(git rev-parse --short refs/heads/main) <GREEN>main<RESET>
-	$(git rev-parse --short refs/remotes/myfork/main) <GREEN>myfork/main<RESET>
-	$(git rev-parse --short refs/remotes/origin/main) <GREEN>origin/main<RESET>
-	$(git rev-parse --short refs/tags/testtag) <GREEN>testtag<RESET>
-	$(git rev-parse --short refs/tags/third) <GREEN>third<RESET>
-	$(git rev-parse --short refs/tags/two) <GREEN>two<RESET>
-	EOF
-	sed "s/<[^>]*>//g" <expected.color >expected.bare &&
-	color_format="%(objectname:short) %(color:green)%(refname:short)"
-'
-
-test_expect_success TTY '%(color) shows color with a tty' '
-	test_terminal git for-each-ref --format="$color_format" >actual.raw &&
-	test_decode_color <actual.raw >actual &&
-	test_cmp expected.color actual
-'
-
-test_expect_success '%(color) does not show color without tty' '
-	TERM=vt100 git for-each-ref --format="$color_format" >actual &&
-	test_cmp expected.bare actual
-'
-
-test_expect_success '--color can override tty check' '
-	git for-each-ref --color --format="$color_format" >actual.raw &&
-	test_decode_color <actual.raw >actual &&
-	test_cmp expected.color actual
-'
-
-test_expect_success 'color.ui=always does not override tty check' '
-	git -c color.ui=always for-each-ref --format="$color_format" >actual &&
-	test_cmp expected.bare actual
-'
-
-test_expect_success 'setup for describe atom tests' '
-	git init -b master describe-repo &&
-	(
-		cd describe-repo &&
-
-		test_commit --no-tag one &&
-		git tag tagone &&
-
-		test_commit --no-tag two &&
-		git tag -a -m "tag two" tagtwo
-	)
-'
-
-test_expect_success 'describe atom vs git describe' '
-	(
-		cd describe-repo &&
-
-		git for-each-ref --format="%(objectname)" \
-			refs/tags/ >obj &&
-		while read hash
-		do
-			if desc=$(git describe $hash)
-			then
-				: >expect-contains-good
-			else
-				: >expect-contains-bad
-			fi &&
-			echo "$hash $desc" || return 1
-		done <obj >expect &&
-		test_path_exists expect-contains-good &&
-		test_path_exists expect-contains-bad &&
-
-		git for-each-ref --format="%(objectname) %(describe)" \
-			refs/tags/ >actual 2>err &&
-		test_cmp expect actual &&
-		test_must_be_empty err
-	)
-'
-
-test_expect_success 'describe:tags vs describe --tags' '
-	(
-		cd describe-repo &&
-		git describe --tags >expect &&
-		git for-each-ref --format="%(describe:tags)" \
-				refs/heads/master >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'describe:abbrev=... vs describe --abbrev=...' '
-	(
-		cd describe-repo &&
-
-		# Case 1: We have commits between HEAD and the most
-		#	  recent tag reachable from it
-		test_commit --no-tag file &&
-		git describe --abbrev=14 >expect &&
-		git for-each-ref --format="%(describe:abbrev=14)" \
-			refs/heads/master >actual &&
-		test_cmp expect actual &&
-
-		# Make sure the hash used is at least 14 digits long
-		sed -e "s/^.*-g\([0-9a-f]*\)$/\1/" <actual >hexpart &&
-		test 15 -le $(wc -c <hexpart) &&
-
-		# Case 2: We have a tag at HEAD, describe directly gives
-		#	  the name of the tag
-		git tag -a -m tagged tagname &&
-		git describe --abbrev=14 >expect &&
-		git for-each-ref --format="%(describe:abbrev=14)" \
-			refs/heads/master >actual &&
-		test_cmp expect actual &&
-		test tagname = $(cat actual)
-	)
-'
-
-test_expect_success 'describe:match=... vs describe --match ...' '
-	(
-		cd describe-repo &&
-		git tag -a -m "tag foo" tag-foo &&
-		git describe --match "*-foo" >expect &&
-		git for-each-ref --format="%(describe:match="*-foo")" \
-			refs/heads/master >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'describe:exclude:... vs describe --exclude ...' '
-	(
-		cd describe-repo &&
-		git tag -a -m "tag bar" tag-bar &&
-		git describe --exclude "*-bar" >expect &&
-		git for-each-ref --format="%(describe:exclude="*-bar")" \
-			refs/heads/master >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'deref with describe atom' '
-	(
-		cd describe-repo &&
-		cat >expect <<-\EOF &&
-
-		tagname
-		tagname
-		tagname
-
-		tagtwo
-		EOF
-		git for-each-ref --format="%(*describe)" >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'err on bad describe atom arg' '
-	(
-		cd describe-repo &&
-
-		# The bad arg is the only arg passed to describe atom
-		cat >expect <<-\EOF &&
-		fatal: unrecognized %(describe) argument: baz
-		EOF
-		test_must_fail git for-each-ref --format="%(describe:baz)" \
-			refs/heads/master 2>actual &&
-		test_cmp expect actual &&
-
-		# The bad arg is in the middle of the option string
-		# passed to the describe atom
-		cat >expect <<-\EOF &&
-		fatal: unrecognized %(describe) argument: qux=1,abbrev=14
-		EOF
-		test_must_fail git for-each-ref \
-			--format="%(describe:tags,qux=1,abbrev=14)" \
-			ref/heads/master 2>actual &&
-		test_cmp expect actual
-	)
-'
-
-cat >expected <<\EOF
-heads/main
-tags/main
-EOF
-
-test_expect_success 'Check ambiguous head and tag refs (strict)' '
-	git config --bool core.warnambiguousrefs true &&
-	git checkout -b newtag &&
-	echo "Using $datestamp" > one &&
-	git add one &&
-	git commit -m "Branch" &&
-	setdate_and_increment &&
-	git tag -m "Tagging at $datestamp" main &&
-	git for-each-ref --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-heads/main
-main
-EOF
-
-test_expect_success 'Check ambiguous head and tag refs (loose)' '
-	git config --bool core.warnambiguousrefs false &&
-	git for-each-ref --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-heads/ambiguous
-ambiguous
-EOF
-
-test_expect_success 'Check ambiguous head and tag refs II (loose)' '
-	git checkout main &&
-	git tag ambiguous testtag^0 &&
-	git branch ambiguous testtag^0 &&
-	git for-each-ref --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'create tag without tagger' '
-	git tag -a -m "Broken tag" taggerless &&
-	git tag -f taggerless $(git cat-file tag taggerless |
-		sed -e "/^tagger /d" |
-		git hash-object --literally --stdin -w -t tag)
-'
-
-test_atom refs/tags/taggerless type 'commit'
-test_atom refs/tags/taggerless tag 'taggerless'
-test_atom refs/tags/taggerless tagger ''
-test_atom refs/tags/taggerless taggername ''
-test_atom refs/tags/taggerless taggeremail ''
-test_atom refs/tags/taggerless taggeremail:trim ''
-test_atom refs/tags/taggerless taggeremail:localpart ''
-test_atom refs/tags/taggerless taggerdate ''
-test_atom refs/tags/taggerless committer ''
-test_atom refs/tags/taggerless committername ''
-test_atom refs/tags/taggerless committeremail ''
-test_atom refs/tags/taggerless committeremail:trim ''
-test_atom refs/tags/taggerless committeremail:localpart ''
-test_atom refs/tags/taggerless committerdate ''
-test_atom refs/tags/taggerless subject 'Broken tag'
-
-test_expect_success 'an unusual tag with an incomplete line' '
-
-	git tag -m "bogo" bogo &&
-	bogo=$(git cat-file tag bogo) &&
-	bogo=$(printf "%s" "$bogo" | git mktag) &&
-	git tag -f bogo "$bogo" &&
-	git for-each-ref --format "%(body)" refs/tags/bogo
-
-'
-
-test_expect_success 'create tag with subject and body content' '
-	cat >>msg <<-\EOF &&
-		the subject line
-
-		first body line
-		second body line
-	EOF
-	git tag -F msg subject-body
-'
-test_atom refs/tags/subject-body subject 'the subject line'
-test_atom refs/tags/subject-body subject:sanitize 'the-subject-line'
-test_atom refs/tags/subject-body body 'first body line
-second body line
-'
-test_atom refs/tags/subject-body contents 'the subject line
-
-first body line
-second body line
-'
-
-test_expect_success 'create tag with multiline subject' '
-	cat >msg <<-\EOF &&
-		first subject line
-		second subject line
-
-		first body line
-		second body line
-	EOF
-	git tag -F msg multiline
-'
-test_atom refs/tags/multiline subject 'first subject line second subject line'
-test_atom refs/tags/multiline subject:sanitize 'first-subject-line-second-subject-line'
-test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
-test_atom refs/tags/multiline body 'first body line
-second body line
-'
-test_atom refs/tags/multiline contents:body 'first body line
-second body line
-'
-test_atom refs/tags/multiline contents:signature ''
-test_atom refs/tags/multiline contents 'first subject line
-second subject line
-
-first body line
-second body line
-'
-
-test_expect_success GPG 'create signed tags' '
-	git tag -s -m "" signed-empty &&
-	git tag -s -m "subject line" signed-short &&
-	cat >msg <<-\EOF &&
-	subject line
-
-	body contents
-	EOF
-	git tag -s -F msg signed-long
-'
-
-sig='-----BEGIN PGP SIGNATURE-----
------END PGP SIGNATURE-----
-'
-
-PREREQ=GPG
-test_atom refs/tags/signed-empty subject ''
-test_atom refs/tags/signed-empty subject:sanitize ''
-test_atom refs/tags/signed-empty contents:subject ''
-test_atom refs/tags/signed-empty body "$sig"
-test_atom refs/tags/signed-empty contents:body ''
-test_atom refs/tags/signed-empty contents:signature "$sig"
-test_atom refs/tags/signed-empty contents "$sig"
-
-test_expect_success GPG 'basic atom: refs/tags/signed-empty raw' '
-	git cat-file tag refs/tags/signed-empty >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-empty >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_atom refs/tags/signed-short subject 'subject line'
-test_atom refs/tags/signed-short subject:sanitize 'subject-line'
-test_atom refs/tags/signed-short contents:subject 'subject line'
-test_atom refs/tags/signed-short body "$sig"
-test_atom refs/tags/signed-short contents:body ''
-test_atom refs/tags/signed-short contents:signature "$sig"
-test_atom refs/tags/signed-short contents "subject line
-$sig"
-
-test_expect_success GPG 'basic atom: refs/tags/signed-short raw' '
-	git cat-file tag refs/tags/signed-short >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-short >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_atom refs/tags/signed-long subject 'subject line'
-test_atom refs/tags/signed-long subject:sanitize 'subject-line'
-test_atom refs/tags/signed-long contents:subject 'subject line'
-test_atom refs/tags/signed-long body "body contents
-$sig"
-test_atom refs/tags/signed-long contents:body 'body contents
-'
-test_atom refs/tags/signed-long contents:signature "$sig"
-test_atom refs/tags/signed-long contents "subject line
-
-body contents
-$sig"
-
-test_expect_success GPG 'basic atom: refs/tags/signed-long raw' '
-	git cat-file tag refs/tags/signed-long >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-long >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_expect_success 'set up refs pointing to tree and blob' '
-	git update-ref refs/mytrees/first refs/heads/main^{tree} &&
-	git update-ref refs/myblobs/first refs/heads/main:one
-'
-
-test_atom refs/mytrees/first subject ""
-test_atom refs/mytrees/first contents:subject ""
-test_atom refs/mytrees/first body ""
-test_atom refs/mytrees/first contents:body ""
-test_atom refs/mytrees/first contents:signature ""
-test_atom refs/mytrees/first contents ""
-
-test_expect_success 'basic atom: refs/mytrees/first raw' '
-	git cat-file tree refs/mytrees/first >expected &&
-	echo >>expected &&
-	git for-each-ref --format="%(raw)" refs/mytrees/first >actual &&
-	test_cmp expected actual &&
-	git cat-file -s refs/mytrees/first >expected &&
-	git for-each-ref --format="%(raw:size)" refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_atom refs/myblobs/first subject ""
-test_atom refs/myblobs/first contents:subject ""
-test_atom refs/myblobs/first body ""
-test_atom refs/myblobs/first contents:body ""
-test_atom refs/myblobs/first contents:signature ""
-test_atom refs/myblobs/first contents ""
-
-test_expect_success 'basic atom: refs/myblobs/first raw' '
-	git cat-file blob refs/myblobs/first >expected &&
-	echo >>expected &&
-	git for-each-ref --format="%(raw)" refs/myblobs/first >actual &&
-	test_cmp expected actual &&
-	git cat-file -s refs/myblobs/first >expected &&
-	git for-each-ref --format="%(raw:size)" refs/myblobs/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'set up refs pointing to binary blob' '
-	printf "a\0b\0c" >blob1 &&
-	printf "a\0c\0b" >blob2 &&
-	printf "\0a\0b\0c" >blob3 &&
-	printf "abc" >blob4 &&
-	printf "\0 \0 \0 " >blob5 &&
-	printf "\0 \0a\0 " >blob6 &&
-	printf "  " >blob7 &&
-	>blob8 &&
-	obj=$(git hash-object -w blob1) &&
-	git update-ref refs/myblobs/blob1 "$obj" &&
-	obj=$(git hash-object -w blob2) &&
-	git update-ref refs/myblobs/blob2 "$obj" &&
-	obj=$(git hash-object -w blob3) &&
-	git update-ref refs/myblobs/blob3 "$obj" &&
-	obj=$(git hash-object -w blob4) &&
-	git update-ref refs/myblobs/blob4 "$obj" &&
-	obj=$(git hash-object -w blob5) &&
-	git update-ref refs/myblobs/blob5 "$obj" &&
-	obj=$(git hash-object -w blob6) &&
-	git update-ref refs/myblobs/blob6 "$obj" &&
-	obj=$(git hash-object -w blob7) &&
-	git update-ref refs/myblobs/blob7 "$obj" &&
-	obj=$(git hash-object -w blob8) &&
-	git update-ref refs/myblobs/blob8 "$obj"
-'
-
-test_expect_success 'Verify sorts with raw' '
-	cat >expected <<-EOF &&
-	refs/myblobs/blob8
-	refs/myblobs/blob5
-	refs/myblobs/blob6
-	refs/myblobs/blob3
-	refs/myblobs/blob7
-	refs/mytrees/first
-	refs/myblobs/first
-	refs/myblobs/blob1
-	refs/myblobs/blob2
-	refs/myblobs/blob4
-	refs/heads/main
-	EOF
-	git for-each-ref --format="%(refname)" --sort=raw \
-		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Verify sorts with raw:size' '
-	cat >expected <<-EOF &&
-	refs/myblobs/blob8
-	refs/myblobs/blob7
-	refs/myblobs/blob4
-	refs/myblobs/blob1
-	refs/myblobs/blob2
-	refs/myblobs/blob3
-	refs/myblobs/blob5
-	refs/myblobs/blob6
-	refs/myblobs/first
-	refs/mytrees/first
-	refs/heads/main
-	EOF
-	git for-each-ref --format="%(refname)" --sort=raw:size \
-		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'validate raw atom with %(if:equals)' '
-	cat >expected <<-EOF &&
-	not equals
-	not equals
-	not equals
-	not equals
-	not equals
-	not equals
-	refs/myblobs/blob4
-	not equals
-	not equals
-	not equals
-	not equals
-	not equals
-	EOF
-	git for-each-ref --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
-		refs/myblobs/ refs/heads/ >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'validate raw atom with %(if:notequals)' '
-	cat >expected <<-EOF &&
-	refs/heads/ambiguous
-	refs/heads/main
-	refs/heads/newtag
-	refs/myblobs/blob1
-	refs/myblobs/blob2
-	refs/myblobs/blob3
-	equals
-	refs/myblobs/blob5
-	refs/myblobs/blob6
-	refs/myblobs/blob7
-	refs/myblobs/blob8
-	refs/myblobs/first
-	EOF
-	git for-each-ref --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
-		refs/myblobs/ refs/heads/ >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'empty raw refs with %(if)' '
-	cat >expected <<-EOF &&
-	refs/myblobs/blob1 not empty
-	refs/myblobs/blob2 not empty
-	refs/myblobs/blob3 not empty
-	refs/myblobs/blob4 not empty
-	refs/myblobs/blob5 not empty
-	refs/myblobs/blob6 not empty
-	refs/myblobs/blob7 empty
-	refs/myblobs/blob8 empty
-	refs/myblobs/first not empty
-	EOF
-	git for-each-ref --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
-		refs/myblobs/ >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '%(raw) with --python must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --python
-'
-
-test_expect_success '%(raw) with --tcl must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --tcl
-'
-
-test_expect_success PERL_TEST_HELPERS '%(raw) with --perl' '
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual &&
-	cmp blob1 actual &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/blob3 --perl | perl >actual &&
-	cmp blob3 actual &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/blob8 --perl | perl >actual &&
-	cmp blob8 actual &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/first --perl | perl >actual &&
-	cmp one actual &&
-	git cat-file tree refs/mytrees/first > expected &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/mytrees/first --perl | perl >actual &&
-	cmp expected actual
-'
-
-test_expect_success '%(raw) with --shell must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --shell
-'
-
-test_expect_success '%(raw) with --shell and --sort=raw must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --sort=raw --shell
-'
-
-test_expect_success '%(raw:size) with --shell' '
-	git for-each-ref --format="%(raw:size)" | sed "s/^/$SQ/;s/$/$SQ/" >expect &&
-	git for-each-ref --format="%(raw:size)" --shell >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref --format compare with cat-file --batch' '
-	git rev-parse refs/mytrees/first | git cat-file --batch >expected &&
-	git for-each-ref --format="%(objectname) %(objecttype) %(objectsize)
-%(raw)" refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'verify sorts with contents:size' '
-	cat >expect <<-\EOF &&
-	refs/heads/main
-	refs/heads/newtag
-	refs/heads/ambiguous
-	EOF
-	git for-each-ref --format="%(refname)" \
-		--sort=contents:size refs/heads/ >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'set up multiple-sort tags' '
-	for when in 100000 200000
-	do
-		for email in user1 user2
-		do
-			for ref in ref1 ref2
-			do
-				GIT_COMMITTER_DATE="@$when +0000" \
-				GIT_COMMITTER_EMAIL="$email@example.com" \
-				git tag -m "tag $ref-$when-$email" \
-				multi-$ref-$when-$email || return 1
-			done
-		done
-	done
-'
-
-test_expect_success 'Verify sort with multiple keys' '
-	cat >expected <<-\EOF &&
-	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
-	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
-	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
-	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
-	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
-	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
-	EOF
-	git for-each-ref \
-		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
-		--sort=-refname \
-		--sort=taggeremail \
-		--sort=taggerdate \
-		"refs/tags/multi-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'equivalent sorts fall back on refname' '
-	cat >expected <<-\EOF &&
-	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
-	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
-	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
-	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
-	EOF
-	git for-each-ref \
-		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
-		--sort=taggerdate \
-		"refs/tags/multi-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '--no-sort cancels the previous sort keys' '
-	cat >expected <<-\EOF &&
-	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
-	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
-	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
-	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
-	EOF
-	git for-each-ref \
-		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
-		--sort=-refname \
-		--sort=taggeremail \
-		--no-sort \
-		--sort=taggerdate \
-		"refs/tags/multi-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '--no-sort without subsequent --sort prints expected refs' '
-	cat >expected <<-\EOF &&
-	refs/tags/multi-ref1-100000-user1
-	refs/tags/multi-ref1-100000-user2
-	refs/tags/multi-ref1-200000-user1
-	refs/tags/multi-ref1-200000-user2
-	refs/tags/multi-ref2-100000-user1
-	refs/tags/multi-ref2-100000-user2
-	refs/tags/multi-ref2-200000-user1
-	refs/tags/multi-ref2-200000-user2
-	EOF
-
-	# Sort the results with `sort` for a consistent comparison against
-	# expected
-	git for-each-ref \
-		--format="%(refname)" \
-		--no-sort \
-		"refs/tags/multi-*" | sort >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'set up custom date sorting' '
-	# Dates:
-	# - Wed Feb 07 2024 21:34:20 +0000
-	# - Tue Dec 14 1999 00:05:22 +0000
-	# - Fri Jun 04 2021 11:26:51 +0000
-	# - Mon Jan 22 2007 16:44:01 GMT+0000
-	i=1 &&
-	for when in 1707341660 945129922 1622806011 1169484241
-	do
-		GIT_COMMITTER_DATE="@$when +0000" \
-		GIT_COMMITTER_EMAIL="user@example.com" \
-		git tag -m "tag $when" custom-dates-$i &&
-		i=$(($i+1)) || return 1
-	done
-'
-
-test_expect_success 'sort by date defaults to full timestamp' '
-	cat >expected <<-\EOF &&
-	945129922 refs/tags/custom-dates-2
-	1169484241 refs/tags/custom-dates-4
-	1622806011 refs/tags/custom-dates-3
-	1707341660 refs/tags/custom-dates-1
-	EOF
-
-	git for-each-ref \
-		--format="%(creatordate:unix) %(refname)" \
-		--sort=creatordate \
-		"refs/tags/custom-dates-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'sort by custom date format' '
-	cat >expected <<-\EOF &&
-	00:05:22 refs/tags/custom-dates-2
-	11:26:51 refs/tags/custom-dates-3
-	16:44:01 refs/tags/custom-dates-4
-	21:34:20 refs/tags/custom-dates-1
-	EOF
-
-	git for-each-ref \
-		--format="%(creatordate:format:%H:%M:%S) %(refname)" \
-		--sort="creatordate:format:%H:%M:%S" \
-		"refs/tags/custom-dates-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
-	test_when_finished "git checkout main" &&
-	git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
-	sed -e "s/^\* /  /" actual >expect &&
-	git checkout --orphan orphaned-branch &&
-	git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
-	test_cmp expect actual
-'
-
-cat >trailers <<EOF
-Reviewed-by: A U Thor <author@example.com>
-Signed-off-by: A U Thor <author@example.com>
-[ v2 updated patch description ]
-Acked-by: A U Thor
-  <author@example.com>
-EOF
-
-unfold () {
-	perl -0pe 's/\n\s+/ /g'
-}
-
-test_expect_success 'set up trailers for next test' '
-	echo "Some contents" > two &&
-	git add two &&
-	git commit -F - <<-EOF
-	trailers: this commit message has trailers
-
-	Some message contents
-
-	$(cat trailers)
-	EOF
-'
-
-test_trailer_option () {
-	if test "$#" -eq 3
-	then
-		prereq="$1"
-		shift
-	fi &&
-	title=$1 option=$2
-	cat >expect
-	test_expect_success $prereq "$title" '
-		git for-each-ref --format="%($option)" refs/heads/main >actual &&
-		test_cmp expect actual &&
-		git for-each-ref --format="%(contents:$option)" refs/heads/main >actual &&
-		test_cmp expect actual
-	'
-}
-
-test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) unfolds trailers' \
-	'trailers:unfold' <<-EOF
-	$(unfold <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only) shows only "key: value" trailers' \
-	'trailers:only' <<-EOF
-	$(grep -v patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only=no,only=true) shows only "key: value" trailers' \
-	'trailers:only=no,only=true' <<-EOF
-	$(grep -v patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only=yes) shows only "key: value" trailers' \
-	'trailers:only=yes' <<-EOF
-	$(grep -v patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only=no) shows all trailers' \
-	'trailers:only=no' <<-EOF
-	$(cat trailers)
-
-	EOF
-
-test_trailer_option PERL_TEST_HELPERS '%(trailers:only) and %(trailers:unfold) work together' \
-	'trailers:only,unfold' <<-EOF
-	$(grep -v patch.description <trailers | unfold)
-
-	EOF
-
-test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) and %(trailers:only) work together' \
-	'trailers:unfold,only' <<-EOF
-	$(grep -v patch.description <trailers | unfold)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) shows that trailer' \
-	'trailers:key=Signed-off-by' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) is case insensitive' \
-	'trailers:key=SiGned-oFf-bY' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo:) trailing colon also works' \
-	'trailers:key=Signed-off-by:' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) multiple keys' \
-	'trailers:key=Reviewed-by:,key=Signed-off-by' <<-EOF
-	Reviewed-by: A U Thor <author@example.com>
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=nonexistent) becomes empty' \
-	'trailers:key=Shined-off-by:' <<-EOF
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) handles multiple lines even if folded' \
-	'trailers:key=Acked-by' <<-EOF
-	$(grep -v patch.description <trailers | grep -v Signed-off-by | grep -v Reviewed-by)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo,unfold) properly unfolds' \
-	'trailers:key=Signed-Off-by,unfold' <<-EOF
-	$(unfold <trailers | grep Signed-off-by)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo,only=no) also includes nontrailer lines' \
-	'trailers:key=Signed-off-by,only=no' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-	$(grep patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo,valueonly) shows only value' \
-	'trailers:key=Signed-off-by,valueonly' <<-EOF
-	A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:separator) changes separator' \
-	'trailers:separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
-	Reviewed-by: A U Thor <author@example.com>,Signed-off-by: A U Thor <author@example.com>
-	EOF
-
-test_trailer_option '%(trailers:key_value_separator) changes key-value separator' \
-	'trailers:key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
-	Reviewed-by,A U Thor <author@example.com>
-	Signed-off-by,A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:separator,key_value_separator) changes both separators' \
-	'trailers:separator=%x2C,key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
-	Reviewed-by,A U Thor <author@example.com>,Signed-off-by,A U Thor <author@example.com>
-	EOF
-
-test_expect_success 'multiple %(trailers) use their own options' '
-	git tag -F - tag-with-trailers <<-\EOF &&
-	body
-
-	one: foo
-	one: bar
-	two: baz
-	two: qux
-	EOF
-	t1="%(trailers:key=one,key_value_separator=W,separator=X)" &&
-	t2="%(trailers:key=two,key_value_separator=Y,separator=Z)" &&
-	git for-each-ref --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual &&
-	cat >expect <<-\EOF &&
-	oneWfooXoneWbar
-	twoYbazZtwoYqux
-	EOF
-	test_cmp expect actual
-'
-
-test_failing_trailer_option () {
-	title=$1 option=$2
-	cat >expect
-	test_expect_success "$title" '
-		# error message cannot be checked under i18n
-		test_must_fail git for-each-ref --format="%($option)" refs/heads/main 2>actual &&
-		test_cmp expect actual &&
-		test_must_fail git for-each-ref --format="%(contents:$option)" refs/heads/main 2>actual &&
-		test_cmp expect actual
-	'
-}
-
-test_failing_trailer_option '%(trailers) rejects unknown trailers arguments' \
-	'trailers:unsupported' <<-\EOF
-	fatal: unknown %(trailers) argument: unsupported
-	EOF
-
-test_failing_trailer_option '%(trailers:key) without value is error' \
-	'trailers:key' <<-\EOF
-	fatal: expected %(trailers:key=<value>)
-	EOF
-
-test_expect_success 'if arguments, %(contents:trailers) shows error if colon is missing' '
-	cat >expect <<-EOF &&
-	fatal: unrecognized %(contents) argument: trailersonly
-	EOF
-	test_must_fail git for-each-ref --format="%(contents:trailersonly)" 2>actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'basic atom: head contents:trailers' '
-	git for-each-ref --format="%(contents:trailers)" refs/heads/main >actual &&
-	sanitize_pgp <actual >actual.clean &&
-	# git for-each-ref ends with a blank line
-	cat >expect <<-EOF &&
-	$(cat trailers)
-
-	EOF
-	test_cmp expect actual.clean
-'
-
-test_expect_success 'basic atom: rest must fail' '
-	test_must_fail git for-each-ref --format="%(rest)" refs/heads/main
-'
-
-test_expect_success 'HEAD atom does not take arguments' '
-	test_must_fail git for-each-ref --format="%(HEAD:foo)" 2>err &&
-	echo "fatal: %(HEAD) does not take arguments" >expect &&
-	test_cmp expect err
-'
-
-test_expect_success 'subject atom rejects unknown arguments' '
-	test_must_fail git for-each-ref --format="%(subject:foo)" 2>err &&
-	echo "fatal: unrecognized %(subject) argument: foo" >expect &&
-	test_cmp expect err
-'
-
-test_expect_success 'refname atom rejects unknown arguments' '
-	test_must_fail git for-each-ref --format="%(refname:foo)" 2>err &&
-	echo "fatal: unrecognized %(refname) argument: foo" >expect &&
-	test_cmp expect err
-'
-
-test_expect_success 'trailer parsing not fooled by --- line' '
-	git commit --allow-empty -F - <<-\EOF &&
-	this is the subject
-
-	This is the body. The message has a "---" line which would confuse a
-	message+patch parser. But here we know we have only a commit message,
-	so we get it right.
-
-	trailer: wrong
-	---
-	This is more body.
-
-	trailer: right
-	EOF
-
-	{
-		echo "trailer: right" &&
-		echo
-	} >expect &&
-	git for-each-ref --format="%(trailers)" refs/heads/main >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'Add symbolic ref for the following tests' '
-	git symbolic-ref refs/heads/sym refs/heads/main
-'
-
-cat >expected <<EOF
-refs/heads/main
-EOF
-
-test_expect_success 'Verify usage of %(symref) atom' '
-	git for-each-ref --format="%(symref)" refs/heads/sym >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<EOF
-heads/main
-EOF
-
-test_expect_success 'Verify usage of %(symref:short) atom' '
-	git for-each-ref --format="%(symref:short)" refs/heads/sym >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<EOF
-main
-heads/main
-EOF
-
-test_expect_success 'Verify usage of %(symref:lstrip) atom' '
-	git for-each-ref --format="%(symref:lstrip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:lstrip=-2)" refs/heads/sym >> actual &&
-	test_cmp expected actual &&
-
-	git for-each-ref --format="%(symref:strip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:strip=-2)" refs/heads/sym >> actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<EOF
-refs
-refs/heads
-EOF
-
-test_expect_success 'Verify usage of %(symref:rstrip) atom' '
-	git for-each-ref --format="%(symref:rstrip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:rstrip=-2)" refs/heads/sym >> actual &&
-	test_cmp expected actual
-'
-
-test_expect_success ':remotename and :remoteref' '
-	git init remote-tests &&
-	(
-		cd remote-tests &&
-		test_commit initial &&
-		git branch -M main &&
-		git remote add from fifth.coffee:blub &&
-		git config branch.main.remote from &&
-		git config branch.main.merge refs/heads/stable &&
-		git remote add to southridge.audio:repo &&
-		git config remote.to.push "refs/heads/*:refs/heads/pushed/*" &&
-		git config branch.main.pushRemote to &&
-		for pair in "%(upstream)=refs/remotes/from/stable" \
-			"%(upstream:remotename)=from" \
-			"%(upstream:remoteref)=refs/heads/stable" \
-			"%(push)=refs/remotes/to/pushed/main" \
-			"%(push:remotename)=to" \
-			"%(push:remoteref)=refs/heads/pushed/main"
-		do
-			echo "${pair#*=}" >expect &&
-			git for-each-ref --format="${pair%=*}" \
-				refs/heads/main >actual &&
-			test_cmp expect actual || exit 1
-		done &&
-		git branch push-simple &&
-		git config branch.push-simple.pushRemote from &&
-		actual="$(git for-each-ref \
-			--format="%(push:remotename),%(push:remoteref)" \
-			refs/heads/push-simple)" &&
-		test from, = "$actual"
-	)
-'
-
-test_expect_success 'for-each-ref --ignore-case ignores case' '
-	git for-each-ref --format="%(refname)" refs/heads/MAIN >actual &&
-	test_must_be_empty actual &&
-
-	echo refs/heads/main >expect &&
-	git for-each-ref --format="%(refname)" --ignore-case \
-		refs/heads/MAIN >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref --omit-empty works' '
-	git for-each-ref --format="%(refname)" >actual &&
-	test_line_count -gt 1 actual &&
-	git for-each-ref --format="%(if:equals=refs/heads/main)%(refname)%(then)%(refname)%(end)" --omit-empty >actual &&
-	echo refs/heads/main >expect &&
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' '
-	# name refs numerically to avoid case-insensitive filesystem conflicts
-	nr=0 &&
-	for email in a A b B
-	do
-		for subject in a A b B
-		do
-			GIT_COMMITTER_EMAIL="$email@example.com" \
-			git tag -m "tag $subject" icase-$(printf %02d $nr) &&
-			nr=$((nr+1))||
-			return 1
-		done
-	done &&
-	git for-each-ref --ignore-case \
-		--format="%(taggeremail) %(subject) %(refname)" \
-		--sort=refname \
-		--sort=subject \
-		--sort=taggeremail \
-		refs/tags/icase-* >actual &&
-	cat >expect <<-\EOF &&
-	<a@example.com> tag a refs/tags/icase-00
-	<a@example.com> tag A refs/tags/icase-01
-	<A@example.com> tag a refs/tags/icase-04
-	<A@example.com> tag A refs/tags/icase-05
-	<a@example.com> tag b refs/tags/icase-02
-	<a@example.com> tag B refs/tags/icase-03
-	<A@example.com> tag b refs/tags/icase-06
-	<A@example.com> tag B refs/tags/icase-07
-	<b@example.com> tag a refs/tags/icase-08
-	<b@example.com> tag A refs/tags/icase-09
-	<B@example.com> tag a refs/tags/icase-12
-	<B@example.com> tag A refs/tags/icase-13
-	<b@example.com> tag b refs/tags/icase-10
-	<b@example.com> tag B refs/tags/icase-11
-	<B@example.com> tag b refs/tags/icase-14
-	<B@example.com> tag B refs/tags/icase-15
-	EOF
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref reports broken tags' '
-	git tag -m "good tag" broken-tag-good HEAD &&
-	git cat-file tag broken-tag-good >good &&
-	sed s/commit/blob/ <good >bad &&
-	bad=$(git hash-object -w -t tag bad) &&
-	git update-ref refs/tags/broken-tag-bad $bad &&
-	test_must_fail git for-each-ref --format="%(*objectname)" \
-		refs/tags/broken-tag-*
-'
-
-test_expect_success 'set up tag with signature and no blank lines' '
-	git tag -F - fake-sig-no-blanks <<-\EOF
-	this is the subject
-	-----BEGIN PGP SIGNATURE-----
-	not a real signature, but we just care about the
-	subject/body parsing. It is important here that
-	there are no blank lines in the signature.
-	-----END PGP SIGNATURE-----
-	EOF
-'
-
-test_atom refs/tags/fake-sig-no-blanks contents:subject 'this is the subject'
-test_atom refs/tags/fake-sig-no-blanks contents:body ''
-test_atom refs/tags/fake-sig-no-blanks contents:signature "$sig"
-
-test_expect_success 'set up tag with CRLF signature' '
-	append_cr <<-\EOF |
-	this is the subject
-	-----BEGIN PGP SIGNATURE-----
-
-	not a real signature, but we just care about
-	the subject/body parsing. It is important here
-	that there is a blank line separating this
-	from the signature header.
-	-----END PGP SIGNATURE-----
-	EOF
-	git tag -F - --cleanup=verbatim fake-sig-crlf
-'
-
-test_atom refs/tags/fake-sig-crlf contents:subject 'this is the subject'
-test_atom refs/tags/fake-sig-crlf contents:body ''
-
-# CRLF is retained in the signature, so we have to pass our expected value
-# through append_cr. But test_atom requires a shell string, which means command
-# substitution, and the shell will strip trailing newlines from the output of
-# the substitution. Hack around it by adding and then removing a dummy line.
-sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)"
-sig_crlf=${sig_crlf%dummy}
-test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf"
-
-test_expect_success 'set up tag with signature and trailers' '
-	git tag -F - fake-sig-trailer <<-\EOF
-	this is the subject
-
-	this is the body
-
-	My-Trailer: foo
-	-----BEGIN PGP SIGNATURE-----
-
-	not a real signature, but we just care about the
-	subject/body/trailer parsing.
-	-----END PGP SIGNATURE-----
-	EOF
-'
-
-# use "separator=" here to suppress the terminating newline
-test_atom refs/tags/fake-sig-trailer trailers:separator= 'My-Trailer: foo'
-
-test_expect_success 'git for-each-ref --stdin: empty' '
-	>in &&
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	git for-each-ref --format="%(refname)" >expect &&
-	test_cmp expect actual
-'
-
-test_expect_success 'git for-each-ref --stdin: fails if extra args' '
-	>in &&
-	test_must_fail git for-each-ref --format="%(refname)" \
-		--stdin refs/heads/extra <in 2>err &&
-	grep "unknown arguments supplied with --stdin" err
-'
-
-test_expect_success 'git for-each-ref --stdin: matches' '
-	cat >in <<-EOF &&
-	refs/tags/multi*
-	refs/heads/amb*
-	EOF
-
-	cat >expect <<-EOF &&
-	refs/heads/ambiguous
-	refs/tags/multi-ref1-100000-user1
-	refs/tags/multi-ref1-100000-user2
-	refs/tags/multi-ref1-200000-user1
-	refs/tags/multi-ref1-200000-user2
-	refs/tags/multi-ref2-100000-user1
-	refs/tags/multi-ref2-100000-user2
-	refs/tags/multi-ref2-200000-user1
-	refs/tags/multi-ref2-200000-user2
-	refs/tags/multiline
-	EOF
-
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'git for-each-ref with non-existing refs' '
-	cat >in <<-EOF &&
-	refs/heads/this-ref-does-not-exist
-	refs/tags/bogus
-	EOF
-
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	test_must_be_empty actual &&
-
-	xargs git for-each-ref --format="%(refname)" <in >actual &&
-	test_must_be_empty actual
-'
-
-test_expect_success 'git for-each-ref with nested tags' '
-	git tag -am "Normal tag" nested/base HEAD &&
-	git tag -am "Nested tag" nested/nest1 refs/tags/nested/base &&
-	git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 &&
-
-	head_oid="$(git rev-parse HEAD)" &&
-	base_tag_oid="$(git rev-parse refs/tags/nested/base)" &&
-	nest1_tag_oid="$(git rev-parse refs/tags/nested/nest1)" &&
-	nest2_tag_oid="$(git rev-parse refs/tags/nested/nest2)" &&
-
-	cat >expect <<-EOF &&
-	refs/tags/nested/base $base_tag_oid tag $head_oid commit
-	refs/tags/nested/nest1 $nest1_tag_oid tag $head_oid commit
-	refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit
-	EOF
-
-	git for-each-ref \
-		--format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \
-		refs/tags/nested/ >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'is-base atom with non-commits' '
-	git for-each-ref --format="%(is-base:HEAD) %(refname)" >out 2>err &&
-	grep "(HEAD) refs/heads/main" out &&
-
-	test_line_count = 2 err &&
-	grep "error: object .* is a commit, not a blob" err &&
-	grep "error: bad tag pointer to" err
-'
-
-GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
-TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
-
-test_expect_success GPG 'setup for signature atom using gpg' '
-	git checkout -b signed &&
-
-	test_when_finished "test_unconfig commit.gpgSign" &&
-
-	echo "1" >file &&
-	git add file &&
-	test_tick &&
-	git commit -S -m "file: 1" &&
-	git tag first-signed &&
-
-	echo "2" >file &&
-	test_tick &&
-	git commit -a -m "file: 2" &&
-	git tag second-unsigned &&
-
-	git config commit.gpgSign 1 &&
-	echo "3" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 3" &&
-	git tag third-unsigned &&
-
-	test_tick &&
-	git rebase -f HEAD^^ && git tag second-signed HEAD^ &&
-	git tag third-signed &&
-
-	echo "4" >file &&
-	test_tick &&
-	git commit -a -SB7227189 -m "file: 4" &&
-	git tag fourth-signed &&
-
-	echo "5" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 5" &&
-	git tag fifth-unsigned &&
-
-	echo "6" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 6" &&
-
-	test_tick &&
-	git rebase -f HEAD^^ &&
-	git tag fifth-signed HEAD^ &&
-	git tag sixth-signed &&
-
-	echo "7" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 7" &&
-	git tag seventh-unsigned
-'
-
-test_expect_success GPGSSH 'setup for signature atom using ssh' '
-	test_when_finished "test_unconfig gpg.format user.signingkey" &&
-
-	test_config gpg.format ssh &&
-	test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
-	echo "8" >file &&
-	test_tick &&
-	git add file &&
-	git commit -S -m "file: 8" &&
-	git tag eighth-signed-ssh
-'
-
-test_expect_success GPG2 'bare signature atom' '
-	git verify-commit first-signed 2>expect &&
-	echo  >>expect &&
-	git for-each-ref refs/tags/first-signed \
-		--format="%(signature)" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show good signature with custom format' '
-	git verify-commit first-signed &&
-	cat >expect <<-\EOF &&
-	G
-	13B6F51ECDDE430D
-	C O Mitter <committer@example.com>
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	EOF
-	git for-each-ref refs/tags/first-signed \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-test_expect_success GPGSSH 'show good signature with custom format with ssh' '
-	test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
-	FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
-	cat >expect.tmpl <<-\EOF &&
-	G
-	FINGERPRINT
-	principal with number 1
-	FINGERPRINT
-
-	EOF
-	sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
-	git for-each-ref refs/tags/eighth-signed-ssh \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'signature atom with grade option and bad signature' '
-	git cat-file commit third-signed >raw &&
-	sed -e "s/^file: 3/file: 3 forged/" raw >forged1 &&
-	FORGED1=$(git hash-object -w -t commit forged1) &&
-	git update-ref refs/tags/third-signed "$FORGED1" &&
-	test_must_fail git verify-commit "$FORGED1" &&
-
-	cat >expect <<-\EOF &&
-	B
-	13B6F51ECDDE430D
-	C O Mitter <committer@example.com>
-
-
-	EOF
-	git for-each-ref refs/tags/third-signed \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show untrusted signature with custom format' '
-	cat >expect <<-\EOF &&
-	U
-	65A0EEA02E30CAD7
-	Eris Discordia <discord@example.net>
-	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
-	D4BE22311AD3131E5EDA29A461092E85B7227189
-	EOF
-	git for-each-ref refs/tags/fourth-signed \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show untrusted signature with undefined trust level' '
-	cat >expect <<-\EOF &&
-	undefined
-	65A0EEA02E30CAD7
-	Eris Discordia <discord@example.net>
-	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
-	D4BE22311AD3131E5EDA29A461092E85B7227189
-	EOF
-	git for-each-ref refs/tags/fourth-signed \
-		--format="$TRUSTLEVEL_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show untrusted signature with ultimate trust level' '
-	cat >expect <<-\EOF &&
-	ultimate
-	13B6F51ECDDE430D
-	C O Mitter <committer@example.com>
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	EOF
-	git for-each-ref refs/tags/sixth-signed \
-		--format="$TRUSTLEVEL_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show unknown signature with custom format' '
-	cat >expect <<-\EOF &&
-	E
-	13B6F51ECDDE430D
-
-
-
-	EOF
-	GNUPGHOME="$GNUPGHOME_NOT_USED" git for-each-ref \
-		refs/tags/sixth-signed --format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show lack of signature with custom format' '
-	cat >expect <<-\EOF &&
-	N
-
-
-
-
-	EOF
-	git for-each-ref refs/tags/seventh-unsigned \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
+. "$TEST_DIRECTORY"/for-each-ref-tests.sh
 
 test_done
diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh
index 9b80ea1..7f060d9 100755
--- a/t/t6302-for-each-ref-filter.sh
+++ b/t/t6302-for-each-ref-filter.sh
@@ -754,4 +754,69 @@
 	test_cmp expect actual
 '
 
+test_expect_success 'start after with packed refs' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit default &&
+
+		git update-ref --stdin <<-\EOF &&
+		create refs/heads/branch @
+		create refs/heads/side @
+		create refs/odd/spot @
+		create refs/tags/one @
+		create refs/tags/two @
+		commit
+		EOF
+
+		cat >expect <<-\EOF &&
+		refs/tags/default
+		refs/tags/one
+		refs/tags/two
+		EOF
+
+		git pack-refs --all &&
+		git for-each-ref --format="%(refname)" --start-after=refs/odd/spot >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'start after with packed refs and some loose refs' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit default &&
+
+		git update-ref --stdin <<-\EOF &&
+		create refs/heads/branch @
+		create refs/heads/side @
+		create refs/odd/spot @
+		create refs/tags/one @
+		create refs/tags/two @
+		commit
+		EOF
+
+		git pack-refs --all &&
+
+		git update-ref --stdin <<-\EOF &&
+		create refs/heads/foo @
+		create refs/odd/tee @
+		commit
+		EOF
+
+		cat >expect <<-\EOF &&
+		refs/odd/tee
+		refs/tags/default
+		refs/tags/one
+		refs/tags/two
+		EOF
+
+
+		git for-each-ref --format="%(refname)" --start-after=refs/odd/spot >actual &&
+		test_cmp expect actual
+	)
+'
+
 test_done
diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh
index f48ed6d..533ac85 100755
--- a/t/t6423-merge-rename-directories.sh
+++ b/t/t6423-merge-rename-directories.sh
@@ -4731,7 +4731,7 @@
 
 		mkdir -p source/subdir &&
 		echo foo >source/subdir/foo &&
-		echo bar >source/bar &&
+		printf "%d\n" 1 2 3 4 5 6 7 >source/bar &&
 		echo baz >source/baz &&
 		git add source &&
 		git commit -m orig &&
@@ -4747,6 +4747,7 @@
 		git switch B &&
 		git mv source/bar source/subdir/bar &&
 		echo more baz >>source/baz &&
+		git add source/baz &&
 		git commit -m B
 	)
 }
@@ -4758,6 +4759,88 @@
 
 		git checkout A^0 &&
 
+		# NOTE: A potentially better resolution would be for
+		#     source/bar -> source/subdir/bar
+		# to use the directory rename to become
+		#     source/bar -> source/bar
+		# (a rename to self), and thus we end up with bar with
+		# a path conflict (given merge.directoryRenames=conflict).
+		# However, since the relevant renames optimization
+		# prevents us from noticing
+		#     source/bar -> source/subdir/bar
+		# as a rename and looking at it just as
+		#     delete source/bar
+		#     add source/subdir/bar
+		# the directory rename of source/subdir/bar -> source/bar does
+		# not look like a rename-to-self situation but a
+		# rename-on-top-of-other-file situation.  We do not want
+		# stage 1 entries from an unrelated file, so we expect an
+		# error about there being a file in the way.
+
+		test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out &&
+
+		grep "CONFLICT (implicit dir rename).*source/bar in the way" out &&
+		test_path_is_missing source/bar &&
+		test_path_is_file source/subdir/bar &&
+		test_path_is_file source/baz &&
+
+		git ls-files >actual &&
+		uniq <actual >tracked &&
+		test_line_count = 3 tracked &&
+
+		git status --porcelain -uno >actual &&
+		cat >expect <<-\EOF &&
+		M  source/baz
+		R  source/bar -> source/subdir/bar
+		EOF
+		test_cmp expect actual
+	)
+'
+
+# Testcase 12i2, Identical to 12i except that source/subdir/bar modified on unrenamed side
+#   Commit O: source/{subdir/foo, bar, baz_1}
+#   Commit A: source/{foo, bar_2, baz_1}
+#   Commit B: source/{subdir/{foo, bar}, baz_2}
+#   Expected: source/{foo, bar, baz_2}, with conflicts on
+#                source/bar vs. source/subdir/bar
+
+test_setup_12i2 () {
+	git init 12i2 &&
+	(
+		cd 12i2 &&
+
+		mkdir -p source/subdir &&
+		echo foo >source/subdir/foo &&
+		printf "%d\n" 1 2 3 4 5 6 7 >source/bar &&
+		echo baz >source/baz &&
+		git add source &&
+		git commit -m orig &&
+
+		git branch O &&
+		git branch A &&
+		git branch B &&
+
+		git switch A &&
+		git mv source/subdir/foo source/foo &&
+		echo 8 >> source/bar &&
+		git add source/bar &&
+		git commit -m A &&
+
+		git switch B &&
+		git mv source/bar source/subdir/bar &&
+		echo more baz >>source/baz &&
+		git add source/baz &&
+		git commit -m B
+	)
+}
+
+test_expect_success '12i2: Directory rename causes rename-to-self' '
+	test_setup_12i2 &&
+	(
+		cd 12i2 &&
+
+		git checkout A^0 &&
+
 		test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 &&
 
 		test_path_is_missing source/subdir &&
@@ -4771,7 +4854,7 @@
 		git status --porcelain -uno >actual &&
 		cat >expect <<-\EOF &&
 		UU source/bar
-		 M source/baz
+		M  source/baz
 		EOF
 		test_cmp expect actual
 	)
@@ -4806,6 +4889,7 @@
 		git switch B &&
 		git mv bar subdir/bar &&
 		echo more baz >>baz &&
+		git add baz &&
 		git commit -m B
 	)
 }
@@ -4817,10 +4901,29 @@
 
 		git checkout A^0 &&
 
-		test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 &&
+		# NOTE: A potentially better resolution would be for
+		#     bar -> subdir/bar
+		# to use the directory rename to become
+		#     bar -> bar
+		# (a rename to self), and thus we end up with bar with
+		# a path conflict (given merge.directoryRenames=conflict).
+		# However, since the relevant renames optimization
+		# prevents us from noticing
+		#     bar -> subdir/bar
+		# as a rename and looking at it just as
+		#     delete bar
+		#     add subdir/bar
+		# the directory rename of subdir/bar -> bar does not look
+		# like a rename-to-self situation but a
+		# rename-on-top-of-other-file situation.  We do not want
+		# stage 1 entries from an unrelated file, so we expect an
+		# error about there being a file in the way.
 
-		test_path_is_missing subdir &&
-		test_path_is_file bar &&
+		test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out &&
+		grep "CONFLICT (implicit dir rename).*bar in the way" out &&
+
+		test_path_is_missing bar &&
+		test_path_is_file subdir/bar &&
 		test_path_is_file baz &&
 
 		git ls-files >actual &&
@@ -4829,8 +4932,8 @@
 
 		git status --porcelain -uno >actual &&
 		cat >expect <<-\EOF &&
-		UU bar
-		 M baz
+		M  baz
+		R  bar -> subdir/bar
 		EOF
 		test_cmp expect actual
 	)
@@ -4865,6 +4968,7 @@
 		git switch B &&
 		git mv dirA/bar dirB/bar &&
 		echo more baz >>dirA/baz &&
+		git add dirA/baz &&
 		git commit -m B
 	)
 }
@@ -4876,10 +4980,29 @@
 
 		git checkout A^0 &&
 
-		test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 &&
+		# NOTE: A potentially better resolution would be for
+		#     dirA/bar -> dirB/bar
+		# to use the directory rename (dirB/ -> dirA/) to become
+		#     dirA/bar -> dirA/bar
+		# (a rename to self), and thus we end up with bar with
+		# a path conflict (given merge.directoryRenames=conflict).
+		# However, since the relevant renames optimization
+		# prevents us from noticing
+		#     dirA/bar -> dirB/bar
+		# as a rename and looking at it just as
+		#     delete dirA/bar
+		#     add dirB/bar
+		# the directory rename of dirA/bar -> dirB/bar does
+		# not look like a rename-to-self situation but a
+		# rename-on-top-of-other-file situation.  We do not want
+		# stage 1 entries from an unrelated file, so we expect an
+		# error about there being a file in the way.
 
-		test_path_is_missing dirB &&
-		test_path_is_file dirA/bar &&
+		test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out &&
+		grep "CONFLICT (implicit dir rename).*dirA/bar in the way" out &&
+
+		test_path_is_missing dirA/bar &&
+		test_path_is_file dirB/bar &&
 		test_path_is_file dirA/baz &&
 
 		git ls-files >actual &&
@@ -4888,8 +5011,8 @@
 
 		git status --porcelain -uno >actual &&
 		cat >expect <<-\EOF &&
-		UU dirA/bar
-		 M dirA/baz
+		M  dirA/baz
+		R  dirA/bar -> dirB/bar
 		EOF
 		test_cmp expect actual
 	)
@@ -5056,6 +5179,25 @@
 	)
 '
 
+# Testcase 12n, Directory rename transitively makes rename back to self
+#
+# (Since this is a cherry-pick instead of merge, the labels are a bit weird.
+#  O, the original commit, is A~1 rather than what branch O points to.)
+#
+#   Commit O:  tools/hello
+#              world
+#   Commit A:  tools/hello
+#              tools/world
+#   Commit B:  hello
+#   In words:
+#     A: world -> tools/world
+#     B: tools/ -> /, i.e. rename all of tools to toplevel directory
+#        delete world
+#
+#   Expected:
+#             CONFLICT (file location): tools/world vs. world
+#
+
 test_setup_12n () {
 	git init 12n &&
 	(
@@ -5092,10 +5234,357 @@
 		git checkout -q B^0 &&
 
 		test_must_fail git cherry-pick A^0 >out &&
-		grep "CONFLICT (file location).*should perhaps be moved" out
+		test_grep "CONFLICT (file location).*should perhaps be moved" out &&
+
+		# Should have 1 entry for hello, and 2 for world
+		test_stdout_line_count = 3 git ls-files -s &&
+		test_stdout_line_count = 1 git ls-files -s hello &&
+		test_stdout_line_count = 2 git ls-files -s world
 	)
 '
 
+# Testcase 12n2, Directory rename transitively makes rename back to self
+#
+#   Commit O:  tools/hello
+#              world
+#   Commit A:  tools/hello
+#              tools/world
+#   Commit B:  hello
+#   In words:
+#     A: world -> tools/world
+#     B: tools/ -> /, i.e. rename all of tools to toplevel directory
+#        delete world
+#
+#   Expected:
+#             CONFLICT (file location): tools/world vs. world
+#
+
+test_setup_12n2 () {
+	git init 12n2 &&
+	(
+		cd 12n2 &&
+
+		mkdir tools &&
+		echo hello >tools/hello &&
+		git add tools/hello &&
+		echo world >world &&
+		git add world &&
+		git commit -m "O" &&
+
+		git branch O &&
+		git branch A &&
+		git branch B &&
+
+		git switch A &&
+		git mv world tools/world &&
+		git commit -m "Move world into tools/" &&
+
+		git switch B &&
+		git mv tools/hello hello &&
+		git rm world &&
+		git commit -m "Move hello from tools/ to toplevel"
+	)
+}
+
+test_expect_success '12n2: Directory rename transitively makes rename back to self' '
+	test_setup_12n2 &&
+	(
+		cd 12n2 &&
+
+		git checkout -q B^0 &&
+
+		test_might_fail git -c merge.directoryRenames=true merge A^0 >out &&
+
+		# Should have 1 entry for hello, and either 0 or 2 for world
+		#
+		# NOTE: Since merge.directoryRenames=true, there is no path
+		# conflict for world vs. tools/world; it should end up at
+		# world.  The fact that world was unmodified on side A, means
+		# there was no content conflict; we should just take the
+		# content from side B -- i.e. delete the file.  So merging
+		# could just delete world.
+		#
+		# However, rename-to-self-via-directory-rename is a bit more
+		# challenging.  Relax this test to allow world to be treated
+		# as a modify/delete conflict as well, meaning it will have
+		# two higher order stages, that just so happen to match.
+		#
+		test_stdout_line_count = 1 git ls-files -s hello &&
+		test_stdout_line_count = 2 git ls-files -s world &&
+		test_grep "CONFLICT (modify/delete).*world deleted in HEAD" out
+	)
+'
+
+# Testcase 12o, Directory rename hits other rename source; file still in way on same side
+#   Commit O:  A/file1_1
+#              A/stuff
+#              B/file1_2
+#              B/stuff
+#              C/other
+#   Commit A:  A/file1_1
+#              A/stuff
+#              B/stuff
+#              C/file1_2
+#              C/other
+#   Commit B:  D/file2_1
+#              A/stuff
+#              B/file1_2
+#              B/stuff
+#              A/other
+#   In words:
+#     A: rename B/file1_2 -> C/file1_2
+#     B: rename C/ -> A/
+#        rename A/file1_1 -> D/file2_1
+#   Rationale:
+#       A/stuff is unmodified, it shows up in final output
+#       B/stuff is unmodified, it shows up in final output
+#       C/other touched on one side (rename to A), so A/other shows up in output
+#       A/file1 is renamed to D/file2
+#       B/file1 -> C/file1 and even though C/ -> A/, A/file1 is
+#               "in the way" so we don't do the directory rename
+#   Expected:  A/stuff
+#              B/stuff
+#              A/other
+#              D/file2
+#              C/file1
+#              + CONFLICT (implicit dir rename): A/file1 in way of C/file1
+#
+
+test_setup_12o () {
+	git init 12o &&
+	(
+		cd 12o &&
+
+		mkdir -p A B C &&
+		echo 1 >A/file1 &&
+		echo 2 >B/file1 &&
+		echo other >C/other &&
+		echo Astuff >A/stuff &&
+		echo Bstuff >B/stuff &&
+		git add . &&
+		git commit -m "O" &&
+
+		git branch O &&
+		git branch A &&
+		git branch B &&
+
+		git switch A &&
+		git mv B/file1 C/ &&
+		git add . &&
+		git commit -m "A" &&
+
+		git switch B &&
+		mkdir -p D &&
+		git mv A/file1 D/file2 &&
+		git mv C/other A/other &&
+		git add . &&
+		git commit -m "B"
+	)
+}
+
+test_expect_success '12o: Directory rename hits other rename source; file still in way on same side' '
+	test_setup_12o &&
+	(
+		cd 12o &&
+
+		git checkout -q A^0 &&
+
+		test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out &&
+
+		test_stdout_line_count = 5 git ls-files -s &&
+		test_stdout_line_count = 1 git ls-files -s A/other &&
+		test_stdout_line_count = 1 git ls-files -s A/stuff &&
+		test_stdout_line_count = 1 git ls-files -s B/stuff &&
+		test_stdout_line_count = 1 git ls-files -s D/file2 &&
+
+		grep "CONFLICT (implicit dir rename).*Existing file/dir at A/file1 in the way" out &&
+		test_stdout_line_count = 1 git ls-files -s C/file1
+	)
+'
+
+# Testcase 12p, Directory rename hits other rename source; file still in way on other side
+#   Commit O:  A/file1_1
+#              A/stuff
+#              B/file1_2
+#              B/stuff
+#              C/other
+#   Commit A:  D/file2_1
+#              A/stuff
+#              B/stuff
+#              C/file1_2
+#              C/other
+#   Commit B:  A/file1_1
+#              A/stuff
+#              B/file1_2
+#              B/stuff
+#              A/other
+#   Short version:
+#     A: rename A/file1_1 -> D/file2_1
+#        rename B/file1_2 -> C/file1_2
+#     B: Rename C/ -> A/
+#   Rationale:
+#       A/stuff is unmodified, it shows up in final output
+#       B/stuff is unmodified, it shows up in final output
+#       C/other touched on one side (rename to A), so A/other shows up in output
+#       A/file1 is renamed to D/file2
+#       B/file1 -> C/file1 and even though C/ -> A/, A/file1 is
+#               "in the way" so we don't do the directory rename
+#   Expected:  A/stuff
+#              B/stuff
+#              A/other
+#              D/file2
+#              C/file1
+#              + CONFLICT (implicit dir rename): A/file1 in way of C/file1
+#
+
+test_setup_12p () {
+	git init 12p &&
+	(
+		cd 12p &&
+
+		mkdir -p A B C &&
+		echo 1 >A/file1 &&
+		echo 2 >B/file1 &&
+		echo other >C/other &&
+		echo Astuff >A/stuff &&
+		echo Bstuff >B/stuff &&
+		git add . &&
+		git commit -m "O" &&
+
+		git branch O &&
+		git branch A &&
+		git branch B &&
+
+		git switch A &&
+		mkdir -p D &&
+		git mv A/file1 D/file2 &&
+		git mv B/file1 C/ &&
+		git add . &&
+		git commit -m "A" &&
+
+		git switch B &&
+		git mv C/other A/other &&
+		git add . &&
+		git commit -m "B"
+	)
+}
+
+test_expect_success '12p: Directory rename hits other rename source; file still in way on other side' '
+	test_setup_12p &&
+	(
+		cd 12p &&
+
+		git checkout -q A^0 &&
+
+		test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out &&
+
+		test_stdout_line_count = 5 git ls-files -s &&
+		test_stdout_line_count = 1 git ls-files -s A/other &&
+		test_stdout_line_count = 1 git ls-files -s A/stuff &&
+		test_stdout_line_count = 1 git ls-files -s B/stuff &&
+		test_stdout_line_count = 1 git ls-files -s D/file2 &&
+
+		grep "CONFLICT (implicit dir rename).*Existing file/dir at A/file1 in the way" out &&
+		test_stdout_line_count = 1 git ls-files -s C/file1
+	)
+'
+
+# Testcase 12q, Directory rename hits other rename source; file removed though
+#   Commit O:  A/file1_1
+#              A/stuff
+#              B/file1_2
+#              B/stuff
+#              C/other
+#   Commit A:  A/stuff
+#              B/stuff
+#              C/file1_2
+#              C/other
+#   Commit B:  D/file2_1
+#              A/stuff
+#              B/file1_2
+#              B/stuff
+#              A/other
+#   In words:
+#     A: delete A/file1_1, rename B/file1_2 -> C/file1_2
+#     B: Rename C/ -> A/, rename A/file1_1 -> D/file2_1
+#   Rationale:
+#       A/stuff is unmodified, it shows up in final output
+#       B/stuff is unmodified, it shows up in final output
+#       C/other touched on one side (rename to A), so A/other shows up in output
+#       A/file1 is rename/delete to D/file2, so two stages for D/file2
+#       B/file1 -> C/file1 and even though C/ -> A/, A/file1 as a source was
+#               "in the way" (ish) so we don't do the directory rename
+#   Expected:  A/stuff
+#              B/stuff
+#              A/other
+#              D/file2 (two stages)
+#              C/file1
+#              + CONFLICT (implicit dir rename): A/file1 in way of C/file1
+#              + CONFLICT (rename/delete): D/file2
+#
+
+test_setup_12q () {
+	git init 12q &&
+	(
+		cd 12q &&
+
+		mkdir -p A B C &&
+		echo 1 >A/file1 &&
+		echo 2 >B/file1 &&
+		echo other >C/other &&
+		echo Astuff >A/stuff &&
+		echo Bstuff >B/stuff &&
+		git add . &&
+		git commit -m "O" &&
+
+		git branch O &&
+		git branch A &&
+		git branch B &&
+
+		git switch A &&
+		git rm A/file1 &&
+		git mv B/file1 C/ &&
+		git add . &&
+		git commit -m "A" &&
+
+		git switch B &&
+		mkdir -p D &&
+		git mv A/file1 D/file2 &&
+		git mv C/other A/other &&
+		git add . &&
+		git commit -m "B"
+	)
+}
+
+test_expect_success '12q: Directory rename hits other rename source; file removed though' '
+	test_setup_12q &&
+	(
+		cd 12q &&
+
+		git checkout -q A^0 &&
+
+		test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out &&
+
+		grep "CONFLICT (rename/delete).*A/file1.*D/file2" out &&
+		grep "CONFLICT (implicit dir rename).*Existing file/dir at A/file1 in the way" out &&
+
+		test_stdout_line_count = 6 git ls-files -s &&
+		test_stdout_line_count = 1 git ls-files -s A/other &&
+		test_stdout_line_count = 1 git ls-files -s A/stuff &&
+		test_stdout_line_count = 1 git ls-files -s B/stuff &&
+		test_stdout_line_count = 2 git ls-files -s D/file2 &&
+
+		# This is a slightly suboptimal resolution; allowing the
+		# rename of C/ -> A/ to affect C/file1 and further rename
+		# it to A/file1 would probably be preferable, but since
+		# A/file1 existed as the source of another rename, allowing
+		# the dir rename of C/file1 -> A/file1 would mean modifying
+		# the code so that renames do not adjust both their source
+		# and target paths in all cases.
+		! grep "CONFLICT (file location)" out &&
+		test_stdout_line_count = 1 git ls-files -s C/file1
+	)
+'
 
 ###########################################################################
 # SECTION 13: Checking informational and conflict messages
diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh
index 5fcf281..c490e57 100755
--- a/t/t7005-editor.sh
+++ b/t/t7005-editor.sh
@@ -7,125 +7,96 @@
 unset EDITOR VISUAL GIT_EDITOR
 
 test_expect_success 'determine default editor' '
-
 	vi=$(TERM=vt100 git var GIT_EDITOR) &&
 	test -n "$vi"
-
 '
 
-if ! expr "$vi" : '[a-z]*$' >/dev/null
-then
-	vi=
-fi
-
-for i in GIT_EDITOR core_editor EDITOR VISUAL $vi
-do
-	cat >e-$i.sh <<-EOF
-	#!$SHELL_PATH
-	echo "Edited by $i" >"\$1"
-	EOF
-	chmod +x e-$i.sh
-done
-
-if ! test -z "$vi"
-then
-	mv e-$vi.sh $vi
-fi
-
 test_expect_success setup '
+	if ! expr "$vi" : "[a-z]*$" >/dev/null
+	then
+		vi=
+	fi &&
+
+	for i in GIT_EDITOR core_editor EDITOR VISUAL $vi
+	do
+		write_script e-$i.sh <<-EOF || return 1
+			echo "Edited by $i" >"\$1"
+		EOF
+	done &&
+
+	if ! test -z "$vi"
+	then
+		mv e-$vi.sh $vi
+	fi &&
 
 	msg="Hand-edited" &&
 	test_commit "$msg" &&
-	echo "$msg" >expect &&
-	git show -s --format=%s > actual &&
-	test_cmp expect actual
-
+	test_commit_message HEAD -m "$msg"
 '
 
-TERM=dumb
-export TERM
 test_expect_success 'dumb should error out when falling back on vi' '
-
-	if git commit --amend
-	then
-		echo "Oops?"
-		false
-	else
-		: happy
-	fi
+	test_must_fail env TERM=dumb git commit --amend
 '
 
 test_expect_success 'dumb should prefer EDITOR to VISUAL' '
-
-	EDITOR=./e-EDITOR.sh &&
-	VISUAL=./e-VISUAL.sh &&
-	export EDITOR VISUAL &&
-	git commit --amend &&
-	test "$(git show -s --format=%s)" = "Edited by EDITOR"
-
+	TERM=dumb EDITOR=./e-EDITOR.sh VISUAL=./e-VISUAL.sh \
+		git commit --amend &&
+	test_commit_message HEAD -m "Edited by EDITOR"
 '
 
-TERM=vt100
-export TERM
 for i in $vi EDITOR VISUAL core_editor GIT_EDITOR
 do
-	echo "Edited by $i" >expect
-	unset EDITOR VISUAL GIT_EDITOR
-	git config --unset-all core.editor
-	case "$i" in
-	core_editor)
-		git config core.editor ./e-core_editor.sh
-		;;
-	[A-Z]*)
-		eval "$i=./e-$i.sh"
-		export $i
-		;;
-	esac
 	test_expect_success "Using $i" '
-		git --exec-path=. commit --amend &&
-		git show -s --pretty=oneline |
-		sed -e "s/^[0-9a-f]* //" >actual &&
-		test_cmp expect actual
+		if test "$i" = core_editor
+		then
+			test_config core.editor ./e-core_editor.sh
+		fi &&
+		(
+			case "$i" in
+			[A-Z]*)
+				eval "$i=./e-$i.sh" &&
+				export $i
+				;;
+			esac &&
+			PATH="$PWD:$PATH" TERM=vt100 git commit --amend
+		) &&
+		test_commit_message HEAD -m "Edited by $i"
 	'
 done
 
-unset EDITOR VISUAL GIT_EDITOR
-git config --unset-all core.editor
-for i in $vi EDITOR VISUAL core_editor GIT_EDITOR
-do
-	echo "Edited by $i" >expect
-	case "$i" in
-	core_editor)
-		git config core.editor ./e-core_editor.sh
-		;;
-	[A-Z]*)
-		eval "$i=./e-$i.sh"
-		export $i
-		;;
-	esac
-	test_expect_success "Using $i (override)" '
-		git --exec-path=. commit --amend &&
-		git show -s --pretty=oneline |
-		sed -e "s/^[0-9a-f]* //" >actual &&
-		test_cmp expect actual
-	'
-done
+test_expect_success 'Using editors with overrides' '
+	(
+		TERM=vt100 &&
+		export TERM &&
+		for i in $vi EDITOR VISUAL core_editor GIT_EDITOR
+		do
+			echo "Edited by $i" >expect &&
+			case "$i" in
+			core_editor)
+				git config core.editor ./e-core_editor.sh
+				;;
+			[A-Z]*)
+				eval "$i=./e-$i.sh" &&
+				export $i
+				;;
+			esac &&
+			PATH="$PWD:$PATH" git commit --amend &&
+			test_commit_message HEAD expect || exit 1
+		done
+	)
+'
 
 test_expect_success 'editor with a space' '
 	echo "echo space >\"\$1\"" >"e space.sh" &&
 	chmod a+x "e space.sh" &&
 	GIT_EDITOR="./e\ space.sh" git commit --amend &&
-	test space = "$(git show -s --pretty=format:%s)"
-
+	test_commit_message HEAD -m space
 '
 
-unset GIT_EDITOR
 test_expect_success 'core.editor with a space' '
-
-	git config core.editor \"./e\ space.sh\" &&
+	test_config core.editor \"./e\ space.sh\" &&
 	git commit --amend &&
-	test space = "$(git show -s --pretty=format:%s)"
-
+	test_commit_message HEAD -m space
 '
 
 test_done
diff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh
index 4dca8d9..1935171 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -31,52 +31,70 @@
 	echo changes >> foo &&
 	git add foo &&
 	(
-		GIT_EDITOR="echo hello >\"\$1\"" &&
+		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 &&
+		git commit --template ":(optional)$PWD/notexist"
+	)
+'
+
 test_expect_success 'nonexistent template file in config should return error' '
 	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
 
 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"
 '
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index b37e201..05f6da4 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -956,13 +956,39 @@
 	test_grep "^; Changes to be committed:" .git/COMMIT_EDITMSG
 '
 
-test_expect_success 'switch core.commentchar' '
+test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' '
 	test_commit "#foo" foo &&
-	GIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend &&
+	cat >config-include <<-\EOF &&
+	[core]
+	    commentString=:
+	    commentString=%
+	    commentChar=auto
+	EOF
+	test_when_finished "rm config-include" &&
+	test_config include.path "$(pwd)/config-include" &&
+	test_config core.commentChar ! &&
+	GIT_EDITOR=.git/FAKE_EDITOR git commit --amend 2>err &&
+	sed -n "s/^hint: *\$//p; s/^hint: //p; s/^warning: //p" err >actual &&
+	cat >expect <<-EOF &&
+	Support for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0
+
+	To use the default comment string (#) please run
+
+	    git config unset core.commentChar
+	    git config unset --file ~/config-include --all core.commentString
+	    git config unset --file ~/config-include core.commentChar
+
+	To set a custom comment string please run
+
+	    git config set --file ~/config-include core.commentChar <comment string>
+
+	where ${SQ}<comment string>${SQ} is the string you wish to use.
+	EOF
+	test_cmp expect actual &&
 	test_grep "^; Changes to be committed:" .git/COMMIT_EDITMSG
 '
 
-test_expect_success 'switch core.commentchar but out of options' '
+test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar but out of options' '
 	cat >text <<\EOF &&
 # 1
 ; 2
@@ -982,4 +1008,24 @@
 	)
 '
 
+test_expect_success WITH_BREAKING_CHANGES 'core.commentChar=auto is rejected' '
+	test_config core.commentChar auto &&
+	test_must_fail git rev-parse --git-dir 2>err &&
+	sed -n "s/^hint: *\$//p; s/^hint: //p; s/^fatal: //p" err >actual &&
+	cat >expect <<-EOF &&
+	Support for ${SQ}core.commentChar=auto${SQ} has been removed in Git 3.0
+
+	To use the default comment string (#) please run
+
+	    git config unset core.commentChar
+
+	To set a custom comment string please run
+
+	    git config set core.commentChar <comment string>
+
+	where ${SQ}<comment string>${SQ} is the string you wish to use.
+	EOF
+	test_cmp expect actual
+'
+
 test_done
diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh
index 611755c..73b78bd 100755
--- a/t/t7700-repack.sh
+++ b/t/t7700-repack.sh
@@ -838,4 +838,67 @@
 	test_server_info_missing
 '
 
+test_expect_success 'pending objects are repacked appropriately' '
+	test_when_finished rm -rf pending &&
+	git init pending &&
+
+	(
+		cd pending &&
+
+		# Commit file, a/b/c and never change them.
+		mkdir -p a/b &&
+		echo singleton >file &&
+		echo stuff >a/b/c &&
+		echo more >a/d &&
+		git add file a &&
+		git commit -m "single blobs" &&
+
+		# Files a/d and a/e will not be singletons.
+		echo d >a/d &&
+		echo e >a/e &&
+		git add a &&
+		git commit -m "more blobs" &&
+
+		# This use of a sparse index helps to force
+		# test that the cache-tree is walked, too.
+		git sparse-checkout set --sparse-index a x &&
+
+		# Create staged changes:
+		# * a/e now has multiple versions.
+		# * a/i now has only one version.
+		echo f >a/d &&
+		echo h >a/e &&
+		echo i >a/i &&
+		git add a &&
+
+		# Stage and unstage a change to make use of
+		# resolve-undo cache and how that impacts fsck.
+		mkdir x &&
+		echo y >x/y &&
+		git add x &&
+		xy=$(git rev-parse :x/y) &&
+		git rm --cached x/y &&
+
+		# The blob for x/y must persist through repacks,
+		# but fsck currently ignores the REUC extension
+		# for finding links to the blob.
+		cat >expect <<-EOF &&
+		dangling blob $xy
+		EOF
+
+		# Bring the loose objects into a packfile to avoid
+		# leftovers in next test. Without this, the loose
+		# objects persist and the test succeeds for other
+		# reasons.
+		git repack -adf &&
+		git fsck >out &&
+		test_cmp expect out &&
+
+		# Test path walk version with pack.useSparse.
+		git -c pack.useSparse=true repack -adf --path-walk &&
+		git fsck >out &&
+		test_cmp expect out
+	)
+'
+
 test_done
diff --git a/t/t8020-last-modified.sh b/t/t8020-last-modified.sh
new file mode 100755
index 0000000..61f00bc
--- /dev/null
+++ b/t/t8020-last-modified.sh
@@ -0,0 +1,230 @@
+#!/bin/sh
+
+test_description='last-modified tests'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit 1 file &&
+	mkdir a &&
+	test_commit 2 a/file &&
+	mkdir a/b &&
+	test_commit 3 a/b/file
+'
+
+test_expect_success 'cannot run last-modified on two trees' '
+	test_must_fail git last-modified HEAD HEAD~1
+'
+
+check_last_modified() {
+	local indir= &&
+	while test $# != 0
+	do
+		case "$1" in
+		-C)
+			indir="$2"
+			shift
+			;;
+		*)
+			break
+			;;
+		esac &&
+		shift
+	done &&
+
+	cat >expect &&
+	git ${indir:+-C "$indir"} last-modified "$@" >tmp.1 &&
+	git name-rev --annotate-stdin --name-only --tags \
+		<tmp.1 >tmp.2 &&
+	tr '\t' ' ' <tmp.2 >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success 'last-modified non-recursive' '
+	check_last_modified <<-\EOF
+	3 a
+	1 file
+	EOF
+'
+
+test_expect_success 'last-modified recursive' '
+	check_last_modified -r <<-\EOF
+	3 a/b/file
+	2 a/file
+	1 file
+	EOF
+'
+
+test_expect_success 'last-modified recursive with show-trees' '
+	check_last_modified -r -t <<-\EOF
+	3 a
+	3 a/b
+	3 a/b/file
+	2 a/file
+	1 file
+	EOF
+'
+
+test_expect_success 'last-modified non-recursive with show-trees' '
+	check_last_modified -t <<-\EOF
+	3 a
+	1 file
+	EOF
+'
+
+test_expect_success 'last-modified subdir' '
+	check_last_modified a <<-\EOF
+	3 a
+	EOF
+'
+
+test_expect_success 'last-modified subdir recursive' '
+	check_last_modified -r a <<-\EOF
+	3 a/b/file
+	2 a/file
+	EOF
+'
+
+test_expect_success 'last-modified from non-HEAD commit' '
+	check_last_modified HEAD^ <<-\EOF
+	2 a
+	1 file
+	EOF
+'
+
+test_expect_success 'last-modified from subdir defaults to root' '
+	check_last_modified -C a <<-\EOF
+	3 a
+	1 file
+	EOF
+'
+
+test_expect_success 'last-modified from subdir uses relative pathspecs' '
+	check_last_modified -C a -r b <<-\EOF
+	3 a/b/file
+	EOF
+'
+
+test_expect_success 'limit last-modified traversal by count' '
+	check_last_modified -1 <<-\EOF
+	3 a
+	^2 file
+	EOF
+'
+
+test_expect_success 'limit last-modified traversal by commit' '
+	check_last_modified HEAD~2..HEAD <<-\EOF
+	3 a
+	^1 file
+	EOF
+'
+
+test_expect_success 'only last-modified files in the current tree' '
+	git rm -rf a &&
+	git commit -m "remove a" &&
+	check_last_modified <<-\EOF
+	1 file
+	EOF
+'
+
+test_expect_success 'subdirectory modified via merge' '
+	test_when_finished rm -rf repo &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit base &&
+		git switch --create left &&
+		mkdir subdir &&
+		test_commit left subdir/left &&
+		git switch --create right base &&
+		mkdir subdir &&
+		test_commit right subdir/right &&
+		git switch - &&
+		test_merge merge right &&
+		check_last_modified <<-\EOF
+		merge subdir
+		base base.t
+		EOF
+	)
+'
+
+test_expect_success 'cross merge boundaries in blaming' '
+	git checkout HEAD^0 &&
+	git rm -rf . &&
+	test_commit m1 &&
+	git checkout HEAD^ &&
+	git rm -rf . &&
+	test_commit m2 &&
+	git merge m1 &&
+	check_last_modified <<-\EOF
+	m2 m2.t
+	m1 m1.t
+	EOF
+'
+
+test_expect_success 'last-modified merge for resolved conflicts' '
+	git checkout HEAD^0 &&
+	git rm -rf . &&
+	test_commit c1 conflict &&
+	git checkout HEAD^ &&
+	git rm -rf . &&
+	test_commit c2 conflict &&
+	test_must_fail git merge c1 &&
+	test_commit resolved conflict &&
+	check_last_modified conflict <<-\EOF
+	resolved conflict
+	EOF
+'
+
+
+# Consider `file` with this content through history:
+#
+# A---B---B-------B---B
+#          \     /
+#           C---D
+test_expect_success 'last-modified merge ignores content from branch' '
+	git checkout HEAD^0 &&
+	git rm -rf . &&
+	test_commit a1 file A &&
+	test_commit a2 file B &&
+	test_commit a3 file C &&
+	test_commit a4 file D &&
+	git checkout a2 &&
+	git merge --no-commit --no-ff a4 &&
+	git checkout a2 -- file &&
+	git merge --continue &&
+	check_last_modified <<-\EOF
+	a2 file
+	EOF
+'
+
+# Consider `file` with this content through history:
+#
+#  A---B---B---C---D---B---B
+#           \         /
+#            B-------B
+test_expect_success 'last-modified merge undoes changes' '
+	git checkout HEAD^0 &&
+	git rm -rf . &&
+	test_commit b1 file A &&
+	test_commit b2 file B &&
+	test_commit b3 file C &&
+	test_commit b4 file D &&
+	git checkout b2 &&
+	test_commit b5 file2 2 &&
+	git checkout b4 &&
+	git merge --no-commit --no-ff b5 &&
+	git checkout b2 -- file &&
+	git merge --continue &&
+	check_last_modified <<-\EOF
+	b5 file2
+	b2 file
+	EOF
+'
+
+test_expect_success 'last-modified complains about unknown arguments' '
+	test_must_fail git last-modified --foo 2>err &&
+	grep "unknown last-modified argument: --foo" err
+'
+
+test_done
diff --git a/t/t9305-fast-import-signatures.sh b/t/t9305-fast-import-signatures.sh
new file mode 100755
index 0000000..c2b4271
--- /dev/null
+++ b/t/t9305-fast-import-signatures.sh
@@ -0,0 +1,106 @@
+#!/bin/sh
+
+test_description='git fast-import --signed-commits=<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 GPG 'set up OpenPGP signed commit' '
+	git checkout -b openpgp-signing main &&
+	echo "Content for OpenPGP signing." >file-sign &&
+	git add file-sign &&
+	git commit -S -m "OpenPGP signed commit" &&
+	OPENPGP_SIGNING=$(git rev-parse --verify openpgp-signing)
+'
+
+test_expect_success GPG 'import OpenPGP signature with --signed-commits=verbatim' '
+	git fast-export --signed-commits=verbatim openpgp-signing >output &&
+	git -C new fast-import --quiet --signed-commits=verbatim <output >log 2>&1 &&
+	IMPORTED=$(git -C new rev-parse --verify refs/heads/openpgp-signing) &&
+	test $OPENPGP_SIGNING = $IMPORTED &&
+	test_must_be_empty log
+'
+
+test_expect_success GPGSM 'set up X.509 signed commit' '
+	git checkout -b x509-signing main &&
+	test_config gpg.format x509 &&
+	test_config user.signingkey $GIT_COMMITTER_EMAIL &&
+	echo "Content for X.509 signing." >file-sign &&
+	git add file-sign &&
+	git commit -S -m "X.509 signed commit" &&
+	X509_SIGNING=$(git rev-parse HEAD)
+'
+
+test_expect_success GPGSM 'import X.509 signature fails with --signed-commits=abort' '
+	git fast-export --signed-commits=verbatim x509-signing >output &&
+	test_must_fail git -C new fast-import --quiet --signed-commits=abort <output
+'
+
+test_expect_success GPGSM 'import X.509 signature with --signed-commits=warn-verbatim' '
+	git -C new fast-import --quiet --signed-commits=warn-verbatim <output >log 2>&1 &&
+	IMPORTED=$(git -C new rev-parse --verify refs/heads/x509-signing) &&
+	test $X509_SIGNING = $IMPORTED &&
+	test_grep "importing a commit signature" log
+'
+
+test_expect_success GPGSSH 'set up SSH signed commit' '
+	git checkout -b ssh-signing main &&
+	test_config gpg.format ssh &&
+	test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+	echo "Content for SSH signing." >file-sign &&
+	git add file-sign &&
+	git commit -S -m "SSH signed commit" &&
+	SSH_SIGNING=$(git rev-parse HEAD)
+'
+
+test_expect_success GPGSSH 'strip SSH signature with --signed-commits=strip' '
+	git fast-export --signed-commits=verbatim ssh-signing >output &&
+	git -C new fast-import --quiet --signed-commits=strip <output >log 2>&1 &&
+	IMPORTED=$(git -C new rev-parse --verify refs/heads/ssh-signing) &&
+	test $SSH_SIGNING != $IMPORTED &&
+	git -C new cat-file commit "$IMPORTED" >actual &&
+	test_grep ! -E "^gpgsig" actual &&
+	test_must_be_empty log
+'
+
+test_expect_success GPG 'setup a commit with dual OpenPGP signatures on its SHA-1 and SHA-256 formats' '
+	# Create a signed SHA-256 commit
+	git init --object-format=sha256 explicit-sha256 &&
+	git -C explicit-sha256 config extensions.compatObjectFormat sha1 &&
+	git -C explicit-sha256 checkout -b dual-signed &&
+	test_commit -C explicit-sha256 A &&
+	echo B >explicit-sha256/B &&
+	git -C explicit-sha256 add B &&
+	test_tick &&
+	git -C explicit-sha256 commit -S -m "signed" B &&
+	SHA256_B=$(git -C explicit-sha256 rev-parse dual-signed) &&
+
+	# Create the corresponding SHA-1 commit
+	SHA1_B=$(git -C explicit-sha256 rev-parse --output-object-format=sha1 dual-signed) &&
+
+	# Check that the resulting SHA-1 commit has both signatures
+	git -C explicit-sha256 cat-file -p $SHA1_B >out &&
+	test_grep -E "^gpgsig " out &&
+	test_grep -E "^gpgsig-sha256 " out
+'
+
+test_expect_success GPG 'strip both OpenPGP signatures with --signed-commits=warn-strip' '
+	git -C explicit-sha256 fast-export --signed-commits=verbatim dual-signed >output &&
+	test_grep -E "^gpgsig sha1 openpgp" output &&
+	test_grep -E "^gpgsig sha256 openpgp" output &&
+	git -C new fast-import --quiet --signed-commits=warn-strip <output >log 2>&1 &&
+	git -C new cat-file commit refs/heads/dual-signed >actual &&
+	test_grep ! -E "^gpgsig " actual &&
+	test_grep ! -E "^gpgsig-sha256 " actual &&
+	test_grep "stripping a commit signature" log >out &&
+	test_line_count = 2 out
+'
+
+test_done
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 6650d33..964e1f1 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -11,9 +11,9 @@
 # untraceable with such ancient Bash versions.
 test_untraceable=UnfortunatelyYes
 
-# Override environment and always use master for the default initial branch
+# Override environment and always use main for the default initial branch
 # name for these tests, so that rev completion candidates are as expected.
-GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
 . ./lib-bash.sh
@@ -1453,7 +1453,7 @@
 		HEAD Z
 		final Z
 		initial Z
-		master Z
+		main Z
 		EOF
 	)
 '
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 621cd31..562f950 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -127,11 +127,18 @@
 	export GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS
 fi
 
-# Explicitly set the default branch name for testing, to avoid the
-# transitory "git init" warning under --verbose.
-: ${GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME:=master}
+# Explicitly set the default branch name for testing, to squelch hints
+# from "git init" during the transition period.  Should be removed
+# after we decide to remove ADVICE_DEFAULT_BRANCH_NAME
+if test -z "$WITH_BREAKING_CHANGES"
+then
+	: ${GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME:=master}
+else
+	: ${GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME:=main}
+fi
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+
 ################################################################
 # It appears that people try to run tests without building...
 GIT_BINARY="${GIT_TEST_INSTALLED:-$GIT_BUILD_DIR}/git$X"
diff --git a/t/unit-tests/clar/.github/workflows/ci.yml b/t/unit-tests/clar/.github/workflows/ci.yml
index 0065843..4d47242 100644
--- a/t/unit-tests/clar/.github/workflows/ci.yml
+++ b/t/unit-tests/clar/.github/workflows/ci.yml
@@ -13,23 +13,56 @@
         platform:
           - os: ubuntu-latest
             generator: Unix Makefiles
+            env:
+              CFLAGS: "-Werror -Wall -Wextra"
+          - os: ubuntu-latest
+            generator: Unix Makefiles
+            env:
+              CC: "clang"
+              CFLAGS: "-Werror -Wall -Wextra -fsanitize=leak"
+          - os: ubuntu-latest
+            generator: Unix Makefiles
+            image: i386/debian:latest
+            env:
+              CFLAGS: "-Werror -Wall -Wextra"
           - os: macos-latest
             generator: Unix Makefiles
+            env:
+              CFLAGS: "-Werror -Wall -Wextra"
           - os: windows-latest
             generator: Visual Studio 17 2022
           - os: windows-latest
             generator: MSYS Makefiles
+            env:
+              CFLAGS: "-Werror -Wall -Wextra"
           - os: windows-latest
             generator: MinGW Makefiles
+            env:
+              CFLAGS: "-Werror -Wall -Wextra"
+      fail-fast: false
 
     runs-on: ${{ matrix.platform.os }}
+    container: ${{matrix.platform.image}}
+
+    env:
+      CC: ${{matrix.platform.env.CC}}
+      CFLAGS: ${{matrix.platform.env.CFLAGS}}
 
     steps:
+    - name: Prepare 32 bit container image
+      if: matrix.platform.image == 'i386/debian:latest'
+      run: apt -q update && apt -q -y install cmake gcc libc6-amd64 lib64stdc++6 make python3
     - name: Check out
-      uses: actions/checkout@v2
+      uses: actions/checkout@v4
     - name: Build
+      shell: bash
       run: |
         mkdir build
         cd build
         cmake .. -G "${{matrix.platform.generator}}"
-        cmake --build .
+        cmake --build . --verbose
+    - name: Test
+      shell: bash
+      run: |
+        cd build
+        CTEST_OUTPUT_ON_FAILURE=1 ctest --build-config Debug
diff --git a/t/unit-tests/clar/CMakeLists.txt b/t/unit-tests/clar/CMakeLists.txt
index 12d4af1..125db05 100644
--- a/t/unit-tests/clar/CMakeLists.txt
+++ b/t/unit-tests/clar/CMakeLists.txt
@@ -1,8 +1,15 @@
+include(CheckFunctionExists)
+
 cmake_minimum_required(VERSION 3.16..3.29)
 
 project(clar LANGUAGES C)
 
-option(BUILD_TESTS "Build test executable" ON)
+option(BUILD_EXAMPLE "Build the example." ON)
+
+check_function_exists(realpath CLAR_HAS_REALPATH)
+if(CLAR_HAS_REALPATH)
+	add_compile_definitions(-DCLAR_HAS_REALPATH)
+endif()
 
 add_library(clar INTERFACE)
 target_sources(clar INTERFACE
@@ -25,4 +32,8 @@
 	if(BUILD_TESTING)
 		add_subdirectory(test)
 	endif()
+
+	if(BUILD_EXAMPLE)
+		add_subdirectory(example)
+	endif()
 endif()
diff --git a/t/unit-tests/clar/README.md b/t/unit-tests/clar/README.md
index a8961c5..4159598 100644
--- a/t/unit-tests/clar/README.md
+++ b/t/unit-tests/clar/README.md
@@ -26,8 +26,7 @@
     ~~~~ sh
     $ mkdir tests
     $ cp -r $CLAR_ROOT/clar* tests
-    $ cp $CLAR_ROOT/test/clar_test.h tests
-    $ cp $CLAR_ROOT/test/main.c.sample tests/main.c
+    $ cp $CLAR_ROOT/example/*.c tests
     ~~~~
 
 - **One: Write some tests**
@@ -147,7 +146,7 @@
 
 1. copy the Clar boilerplate to your test directory
 2. copy (and probably modify) the sample `main.c` (from
-   `$CLAR_PATH/test/main.c.sample`)
+   `$CLAR_PATH/example/main.c`)
 3. run the Clar mixer (a.k.a. `generate.py`) to scan your test directory and
    write out the test suite metadata.
 4. compile your test files and the Clar boilerplate into a single test
@@ -159,7 +158,7 @@
 the `clar.c` and `clar.h` files, plus the code in the `clar/` subdirectory.
 You should not need to edit these files.
 
-The sample `main.c` (i.e. `$CLAR_PATH/test/main.c.sample`) file invokes
+The sample `main.c` (i.e. `$CLAR_PATH/example/main.c`) file invokes
 `clar_test(argc, argv)` to run the tests.  Usually, you will edit this file
 to perform any framework specific initialization and teardown that you need.
 
@@ -251,11 +250,16 @@
 
 -   `cl_fixture(const char *)`: Gets the full path to a fixture file.
 
-Please do note that these methods are *always* available whilst running a
-test, even when calling auxiliary/static functions inside the same file.
+### Auxiliary / helper functions
 
-It's strongly encouraged to perform test assertions in auxiliary methods,
-instead of returning error values. This is considered good Clar style.
+The clar API is always available while running a test, even when calling
+"auxiliary" (helper) functions.
+
+You're encouraged to perform test assertions in those auxiliary
+methods, instead of returning error values. This is considered good
+Clar style. _However_, when you do this, you need to call `cl_invoke`
+to preserve the current state; this ensures that failures are reported
+as coming from the actual test, instead of the auxiliary method.
 
 Style Example:
 
@@ -310,20 +314,19 @@
 
 void test_example__a_test_with_auxiliary_methods(void)
 {
-    check_string("foo");
-    check_string("bar");
+    cl_invoke(check_string("foo"));
+    cl_invoke(check_string("bar"));
 }
 ~~~~
 
 About Clar
 ==========
 
-Clar has been written from scratch by [Vicent Martí](https://github.com/vmg),
-to replace the old testing framework in [libgit2][libgit2].
-
-Do you know what languages are *in* on the SF startup scene? Node.js *and*
-Latin.  Follow [@vmg](https://www.twitter.com/vmg) on Twitter to
-receive more lessons on word etymology. You can be hip too.
-
+Clar was originally written by [Vicent Martí](https://github.com/vmg),
+to replace the old testing framework in [libgit2][libgit2]. It is
+currently maintained by [Edward Thomson](https://github.com/ethomson),
+and used by the [libgit2][libgit2] and [git][git] projects, amongst
+others.
 
 [libgit2]: https://github.com/libgit2/libgit2
+[git]: https://github.com/git/git
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
index 03a3aa8..d6176e5 100644
--- a/t/unit-tests/clar/clar.c
+++ b/t/unit-tests/clar/clar.c
@@ -79,6 +79,8 @@
 #	else
 #		define p_snprintf snprintf
 #	endif
+
+#	define localtime_r(timer, buf) (localtime_s(buf, timer) == 0 ? buf : NULL)
 #else
 #	include <sys/wait.h> /* waitpid(2) */
 #	include <unistd.h>
@@ -150,7 +152,6 @@ static struct {
 
 	enum cl_output_format output_format;
 
-	int report_errors_only;
 	int exit_on_error;
 	int verbosity;
 
@@ -164,6 +165,10 @@ static struct {
 	struct clar_report *reports;
 	struct clar_report *last_report;
 
+	const char *invoke_file;
+	const char *invoke_func;
+	size_t invoke_line;
+
 	void (*local_cleanup)(void *);
 	void *local_cleanup_payload;
 
@@ -190,7 +195,7 @@ struct clar_suite {
 };
 
 /* From clar_print_*.c */
-static void clar_print_init(int test_count, int suite_count, const char *suite_names);
+static void clar_print_init(int test_count, int suite_count);
 static void clar_print_shutdown(int test_count, int suite_count, int error_count);
 static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error);
 static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status failed);
@@ -199,8 +204,10 @@ static void clar_print_onabortv(const char *msg, va_list argp);
 static void clar_print_onabort(const char *msg, ...);
 
 /* From clar_sandbox.c */
-static void clar_unsandbox(void);
-static void clar_sandbox(void);
+static void clar_tempdir_init(void);
+static void clar_tempdir_shutdown(void);
+static int clar_sandbox_create(const char *suite_name, const char *test_name);
+static int clar_sandbox_cleanup(void);
 
 /* From summary.h */
 static struct clar_summary *clar_summary_init(const char *filename);
@@ -304,6 +311,8 @@ clar_run_test(
 
 	CL_TRACE(CL_TRACE__TEST__BEGIN);
 
+	clar_sandbox_create(suite->name, test->name);
+
 	_clar.last_report->start = time(NULL);
 	clar_time_now(&start);
 
@@ -328,9 +337,13 @@ clar_run_test(
 	if (_clar.local_cleanup != NULL)
 		_clar.local_cleanup(_clar.local_cleanup_payload);
 
+	clar__clear_invokepoint();
+
 	if (cleanup->ptr != NULL)
 		cleanup->ptr();
 
+	clar_sandbox_cleanup();
+
 	CL_TRACE(CL_TRACE__TEST__END);
 
 	_clar.tests_ran++;
@@ -339,11 +352,7 @@ clar_run_test(
 	_clar.local_cleanup = NULL;
 	_clar.local_cleanup_payload = NULL;
 
-	if (_clar.report_errors_only) {
-		clar_report_errors(_clar.last_report);
-	} else {
-		clar_print_ontest(suite->name, test->name, _clar.tests_ran, _clar.last_report->status);
-	}
+	clar_print_ontest(suite->name, test->name, _clar.tests_ran, _clar.last_report->status);
 }
 
 static void
@@ -360,8 +369,7 @@ clar_run_suite(const struct clar_suite *suite, const char *filter)
 	if (_clar.exit_on_error && _clar.total_errors)
 		return;
 
-	if (!_clar.report_errors_only)
-		clar_print_onsuite(suite->name, ++_clar.suites_ran);
+	clar_print_onsuite(suite->name, ++_clar.suites_ran);
 
 	_clar.active_suite = suite->name;
 	_clar.active_test = NULL;
@@ -428,12 +436,12 @@ clar_usage(const char *arg)
 	printf("  -iname        Include the suite with `name`\n");
 	printf("  -xname        Exclude the suite with `name`\n");
 	printf("  -v            Increase verbosity (show suite names)\n");
-	printf("  -q            Only report tests that had an error\n");
+	printf("  -q            Decrease verbosity, inverse to -v\n");
 	printf("  -Q            Quit as soon as a test fails\n");
 	printf("  -t            Display results in tap format\n");
 	printf("  -l            Print suite names\n");
 	printf("  -r[filename]  Write summary file (to the optional filename)\n");
-	exit(-1);
+	exit(1);
 }
 
 static void
@@ -441,18 +449,11 @@ clar_parse_args(int argc, char **argv)
 {
 	int i;
 
-	/* Verify options before execute */
 	for (i = 1; i < argc; ++i) {
 		char *argument = argv[i];
 
-		if (argument[0] != '-' || argument[1] == '\0'
-		    || strchr("sixvqQtlr", argument[1]) == NULL) {
+		if (argument[0] != '-' || argument[1] == '\0')
 			clar_usage(argv[0]);
-		}
-	}
-
-	for (i = 1; i < argc; ++i) {
-		char *argument = argv[i];
 
 		switch (argument[1]) {
 		case 's':
@@ -465,8 +466,13 @@ clar_parse_args(int argc, char **argv)
 			argument += offset;
 			arglen = strlen(argument);
 
-			if (arglen == 0)
-				clar_usage(argv[0]);
+			if (arglen == 0) {
+				if (i + 1 == argc)
+					clar_usage(argv[0]);
+
+				argument = argv[++i];
+				arglen = strlen(argument);
+			}
 
 			for (j = 0; j < _clar_suite_count; ++j) {
 				suitelen = strlen(_clar_suites[j].name);
@@ -483,9 +489,6 @@ clar_parse_args(int argc, char **argv)
 
 					++found;
 
-					if (!exact)
-						_clar.verbosity = MAX(_clar.verbosity, 1);
-
 					switch (action) {
 					case 's': {
 						struct clar_explicit *explicit;
@@ -517,23 +520,37 @@ clar_parse_args(int argc, char **argv)
 
 			if (!found)
 				clar_abort("No suite matching '%s' found.\n", argument);
+
 			break;
 		}
 
 		case 'q':
-			_clar.report_errors_only = 1;
+			if (argument[2] != '\0')
+				clar_usage(argv[0]);
+
+			_clar.verbosity--;
 			break;
 
 		case 'Q':
+			if (argument[2] != '\0')
+				clar_usage(argv[0]);
+
 			_clar.exit_on_error = 1;
 			break;
 
 		case 't':
+			if (argument[2] != '\0')
+				clar_usage(argv[0]);
+
 			_clar.output_format = CL_OUTPUT_TAP;
 			break;
 
 		case 'l': {
 			size_t j;
+
+			if (argument[2] != '\0')
+				clar_usage(argv[0]);
+
 			printf("Test suites (use -s<name> to run just one):\n");
 			for (j = 0; j < _clar_suite_count; ++j)
 				printf(" %3d: %s\n", (int)j, _clar_suites[j].name);
@@ -542,23 +559,27 @@ clar_parse_args(int argc, char **argv)
 		}
 
 		case 'v':
+			if (argument[2] != '\0')
+				clar_usage(argv[0]);
+
 			_clar.verbosity++;
 			break;
 
 		case 'r':
 			_clar.write_summary = 1;
 			free(_clar.summary_filename);
+
 			if (*(argument + 2)) {
 				if ((_clar.summary_filename = strdup(argument + 2)) == NULL)
 					clar_abort("Failed to allocate summary filename.\n");
 			} else {
 				_clar.summary_filename = NULL;
 			}
+
 			break;
 
 		default:
-			clar_abort("Unexpected commandline argument '%s'.\n",
-				   argument[1]);
+			clar_usage(argv[0]);
 		}
 	}
 }
@@ -571,11 +592,7 @@ clar_test_init(int argc, char **argv)
 	if (argc > 1)
 		clar_parse_args(argc, argv);
 
-	clar_print_init(
-		(int)_clar_callback_count,
-		(int)_clar_suite_count,
-		""
-	);
+	clar_print_init((int)_clar_callback_count, (int)_clar_suite_count);
 
 	if (!_clar.summary_filename &&
 	    (summary_env = getenv("CLAR_SUMMARY")) != NULL) {
@@ -591,7 +608,7 @@ clar_test_init(int argc, char **argv)
 	if (_clar.write_summary)
 	    _clar.summary = clar_summary_init(_clar.summary_filename);
 
-	clar_sandbox();
+	clar_tempdir_init();
 }
 
 int
@@ -623,7 +640,7 @@ clar_test_shutdown(void)
 		_clar.total_errors
 	);
 
-	clar_unsandbox();
+	clar_tempdir_shutdown();
 
 	if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0)
 		clar_abort("Failed to write the summary file '%s: %s.\n",
@@ -635,6 +652,14 @@ clar_test_shutdown(void)
 	}
 
 	for (report = _clar.reports; report; report = report_next) {
+		struct clar_error *error, *error_next;
+
+		for (error = report->errors; error; error = error_next) {
+			free(error->description);
+			error_next = error->next;
+			free(error);
+		}
+
 		report_next = report->next;
 		free(report);
 	}
@@ -660,7 +685,7 @@ static void abort_test(void)
 		clar_print_onabort(
 				"Fatal error: a cleanup method raised an exception.\n");
 		clar_report_errors(_clar.last_report);
-		exit(-1);
+		exit(1);
 	}
 
 	CL_TRACE(CL_TRACE__TEST__LONGJMP);
@@ -695,9 +720,9 @@ void clar__fail(
 
 	_clar.last_report->last_error = error;
 
-	error->file = file;
-	error->function = function;
-	error->line_number = line;
+	error->file = _clar.invoke_file ? _clar.invoke_file : file;
+	error->function = _clar.invoke_func ? _clar.invoke_func : function;
+	error->line_number = _clar.invoke_line ? _clar.invoke_line : line;
 	error->error_msg = error_msg;
 
 	if (description != NULL &&
@@ -754,7 +779,12 @@ void clar__assert_equal(
 				p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)",
 					s1, s2, pos);
 			} else {
-				p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2);
+				const char *q1 = s1 ? "'" : "";
+				const char *q2 = s2 ? "'" : "";
+				s1 = s1 ? s1 : "NULL";
+				s2 = s2 ? s2 : "NULL";
+				p_snprintf(buf, sizeof(buf), "%s%s%s != %s%s%s",
+					   q1, s1, q1, q2, s2, q2);
 			}
 		}
 	}
@@ -767,12 +797,17 @@ void clar__assert_equal(
 		if (!is_equal) {
 			if (s1 && s2) {
 				int pos;
-				for (pos = 0; s1[pos] == s2[pos] && pos < len; ++pos)
+				for (pos = 0; pos < len && s1[pos] == s2[pos]; ++pos)
 					/* find differing byte offset */;
 				p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s' (at byte %d)",
 					len, s1, len, s2, pos);
 			} else {
-				p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s'", len, s1, len, s2);
+				const char *q1 = s1 ? "'" : "";
+				const char *q2 = s2 ? "'" : "";
+				s1 = s1 ? s1 : "NULL";
+				s2 = s2 ? s2 : "NULL";
+				p_snprintf(buf, sizeof(buf), "%s%.*s%s != %s%.*s%s",
+					   q1, len, s1, q1, q2, len, s2, q2);
 			}
 		}
 	}
@@ -790,7 +825,12 @@ void clar__assert_equal(
 				p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)",
 					wcs1, wcs2, pos);
 			} else {
-				p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2);
+				const char *q1 = wcs1 ? "'" : "";
+				const char *q2 = wcs2 ? "'" : "";
+				wcs1 = wcs1 ? wcs1 : L"NULL";
+				wcs2 = wcs2 ? wcs2 : L"NULL";
+				p_snprintf(buf, sizeof(buf), "%s%ls%s != %s%ls%s",
+					   q1, wcs1, q1, q2, wcs2, q2);
 			}
 		}
 	}
@@ -803,12 +843,17 @@ void clar__assert_equal(
 		if (!is_equal) {
 			if (wcs1 && wcs2) {
 				int pos;
-				for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++pos)
+				for (pos = 0; pos < len && wcs1[pos] == wcs2[pos]; ++pos)
 					/* find differing byte offset */;
 				p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls' (at byte %d)",
 					len, wcs1, len, wcs2, pos);
 			} else {
-				p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls'", len, wcs1, len, wcs2);
+				const char *q1 = wcs1 ? "'" : "";
+				const char *q2 = wcs2 ? "'" : "";
+				wcs1 = wcs1 ? wcs1 : L"NULL";
+				wcs2 = wcs2 ? wcs2 : L"NULL";
+				p_snprintf(buf, sizeof(buf), "%s%.*ls%s != %s%.*ls%s",
+					   q1, len, wcs1, q1, q2, len, wcs2, q2);
 			}
 		}
 	}
@@ -850,6 +895,23 @@ void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
 	_clar.local_cleanup_payload = opaque;
 }
 
+void clar__set_invokepoint(
+	const char *file,
+	const char *func,
+	size_t line)
+{
+	_clar.invoke_file = file;
+	_clar.invoke_func = func;
+	_clar.invoke_line = line;
+}
+
+void clar__clear_invokepoint(void)
+{
+	_clar.invoke_file = NULL;
+	_clar.invoke_func = NULL;
+	_clar.invoke_line = 0;
+}
+
 #include "clar/sandbox.h"
 #include "clar/fixtures.h"
 #include "clar/fs.h"
diff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h
index 8c22382..ca72292 100644
--- a/t/unit-tests/clar/clar.h
+++ b/t/unit-tests/clar/clar.h
@@ -8,6 +8,25 @@
 #define __CLAR_TEST_H__
 
 #include <stdlib.h>
+#include <limits.h>
+
+#if defined(_WIN32) && defined(CLAR_WIN32_LONGPATHS)
+# define CLAR_MAX_PATH 4096
+#elif defined(_WIN32)
+# define CLAR_MAX_PATH MAX_PATH
+#else
+# define CLAR_MAX_PATH PATH_MAX
+#endif
+
+#ifndef CLAR_SELFTEST
+# define CLAR_CURRENT_FILE __FILE__
+# define CLAR_CURRENT_LINE __LINE__
+# define CLAR_CURRENT_FUNC __func__
+#else
+# define CLAR_CURRENT_FILE "file"
+# define CLAR_CURRENT_LINE 42
+# define CLAR_CURRENT_FUNC "func"
+#endif
 
 enum cl_test_status {
 	CL_TEST_OK,
@@ -30,6 +49,7 @@ void clar_test_shutdown(void);
 int clar_test(int argc, char *argv[]);
 
 const char *clar_sandbox_path(void);
+const char *clar_tempdir_path(void);
 
 void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
 void cl_fs_cleanup(void);
@@ -84,18 +104,32 @@ const char *cl_fixture_basename(const char *fixture_name);
 #endif
 
 /**
+ * Invoke a helper function, which itself will use `cl_assert`
+ * constructs. This will preserve the stack information of the
+ * current call point, so that function name and line number
+ * information is shown from the line of the test, instead of
+ * the helper function.
+ */
+#define cl_invoke(expr) \
+	do { \
+		clar__set_invokepoint(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE); \
+		expr; \
+		clar__clear_invokepoint(); \
+	} while(0)
+
+/**
  * Assertion macros with explicit error message
  */
-#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 1)
-#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 1)
-#define cl_assert_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 1)
+#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Function call failed: " #expr, desc, 1)
+#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Expected function call to fail: " #expr, desc, 1)
+#define cl_assert_(expr, desc) clar__assert((expr) != 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Expression is not true: " #expr, desc, 1)
 
 /**
  * Check macros with explicit error message
  */
-#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 0)
-#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 0)
-#define cl_check_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 0)
+#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Function call failed: " #expr, desc, 0)
+#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Expected function call to fail: " #expr, desc, 0)
+#define cl_check_(expr, desc) clar__assert((expr) != 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Expression is not true: " #expr, desc, 0)
 
 /**
  * Assertion macros with no error message
@@ -114,33 +148,33 @@ const char *cl_fixture_basename(const char *fixture_name);
 /**
  * Forced failure/warning
  */
-#define cl_fail(desc) clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1)
-#define cl_warning(desc) clar__fail(__FILE__, __func__, __LINE__, "Warning during test execution:", desc, 0)
+#define cl_fail(desc) clar__fail(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Test failed.", desc, 1)
+#define cl_warning(desc) clar__fail(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Warning during test execution:", desc, 0)
 
 #define cl_skip() clar__skip()
 
 /**
  * Typed assertion macros
  */
-#define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2))
-#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2))
+#define cl_assert_equal_s(s1,s2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2))
+#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2))
 
-#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2))
-#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2))
+#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2))
+#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2))
 
-#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len))
-#define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len))
+#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len))
+#define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len))
 
-#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len))
-#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len))
+#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len))
+#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len))
 
-#define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2))
-#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2))
-#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2))
+#define cl_assert_equal_i(i1,i2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2))
+#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2))
+#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2))
 
-#define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__func__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0))
+#define cl_assert_equal_b(b1,b2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0))
 
-#define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__func__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2))
+#define cl_assert_equal_p(p1,p2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2))
 
 void clar__skip(void);
 
@@ -170,4 +204,11 @@ void clar__assert_equal(
 	const char *fmt,
 	...);
 
+void clar__set_invokepoint(
+	const char *file,
+	const char *func,
+	size_t line);
+
+void clar__clear_invokepoint(void);
+
 #endif
diff --git a/t/unit-tests/clar/clar/fixtures.h b/t/unit-tests/clar/clar/fixtures.h
index 6ec6423..9f1023d 100644
--- a/t/unit-tests/clar/clar/fixtures.h
+++ b/t/unit-tests/clar/clar/fixtures.h
@@ -2,7 +2,7 @@
 static const char *
 fixture_path(const char *base, const char *fixture_name)
 {
-	static char _path[4096];
+	static char _path[CLAR_MAX_PATH];
 	size_t root_len;
 
 	root_len = strlen(base);
@@ -28,7 +28,7 @@ const char *cl_fixture(const char *fixture_name)
 
 void cl_fixture_sandbox(const char *fixture_name)
 {
-	fs_copy(cl_fixture(fixture_name), _clar_path);
+	fs_copy(cl_fixture(fixture_name), clar_sandbox_path());
 }
 
 const char *cl_fixture_basename(const char *fixture_name)
@@ -45,6 +45,6 @@ const char *cl_fixture_basename(const char *fixture_name)
 
 void cl_fixture_cleanup(const char *fixture_name)
 {
-	fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name)));
+	fs_rm(fixture_path(clar_sandbox_path(), cl_fixture_basename(fixture_name)));
 }
 #endif
diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
index 2203743..f1311d9 100644
--- a/t/unit-tests/clar/clar/fs.h
+++ b/t/unit-tests/clar/clar/fs.h
@@ -8,12 +8,6 @@
 
 #ifdef _WIN32
 
-#ifdef CLAR_WIN32_LONGPATHS
-# define CLAR_MAX_PATH 4096
-#else
-# define CLAR_MAX_PATH MAX_PATH
-#endif
-
 #define RM_RETRY_COUNT	5
 #define RM_RETRY_DELAY	10
 
@@ -296,7 +290,7 @@ void
 cl_fs_cleanup(void)
 {
 #ifdef CLAR_FIXTURE_PATH
-	fs_rm(fixture_path(_clar_path, "*"));
+	fs_rm(fixture_path(clar_tempdir_path(), "*"));
 #else
 	((void)fs_copy); /* unused */
 #endif
@@ -371,17 +365,19 @@ static void
 fs_copydir_helper(const char *source, const char *dest, int dest_mode)
 {
 	DIR *source_dir;
-	struct dirent *d;
 
 	mkdir(dest, dest_mode);
 
 	cl_assert_(source_dir = opendir(source), "Could not open source dir");
-	for (;;) {
+	while (1) {
+		struct dirent *d;
 		char *child;
 
 		errno = 0;
-		if ((d = readdir(source_dir)) == NULL)
+		d = readdir(source_dir);
+		if (!d)
 			break;
+
 		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
 			continue;
 
@@ -479,15 +475,18 @@ static void
 fs_rmdir_helper(const char *path)
 {
 	DIR *dir;
-	struct dirent *d;
 
 	cl_assert_(dir = opendir(path), "Could not open dir");
-	for (;;) {
+
+	while (1) {
+		struct dirent *d;
 		char *child;
 
 		errno = 0;
-		if ((d = readdir(dir)) == NULL)
+		d = readdir(dir);
+		if (!d)
 			break;
+
 		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
 			continue;
 
@@ -524,7 +523,7 @@ fs_rm(const char *path)
 void
 cl_fs_cleanup(void)
 {
-	clar_unsandbox();
-	clar_sandbox();
+	clar_tempdir_shutdown();
+	clar_tempdir_init();
 }
 #endif
diff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h
index 69d0ee9..89b6659 100644
--- a/t/unit-tests/clar/clar/print.h
+++ b/t/unit-tests/clar/clar/print.h
@@ -1,9 +1,13 @@
 /* clap: clar protocol, the traditional clar output format */
 
-static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names)
+static void clar_print_clap_init(int test_count, int suite_count)
 {
 	(void)test_count;
-	printf("Loaded %d suites: %s\n", (int)suite_count, suite_names);
+
+	if (_clar.verbosity < 0)
+		return;
+
+	printf("Loaded %d suites:\n", (int)suite_count);
 	printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n");
 }
 
@@ -13,10 +17,27 @@ static void clar_print_clap_shutdown(int test_count, int suite_count, int error_
 	(void)suite_count;
 	(void)error_count;
 
-	printf("\n\n");
+	if (_clar.verbosity >= 0)
+		printf("\n\n");
 	clar_report_all();
 }
 
+
+static void clar_print_indented(const char *str, int indent)
+{
+	const char *bol, *eol;
+
+	for (bol = str; *bol; bol = eol) {
+		eol = strchr(bol, '\n');
+		if (eol)
+			eol++;
+		else
+			eol = bol + strlen(bol);
+		printf("%*s%.*s", indent, "", (int)(eol - bol), bol);
+	}
+	putc('\n', stdout);
+}
+
 static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error)
 {
 	printf("  %d) Failure:\n", num);
@@ -27,10 +48,10 @@ static void clar_print_clap_error(int num, const struct clar_report *report, con
 		error->file,
 		error->line_number);
 
-	printf("  %s\n", error->error_msg);
+	clar_print_indented(error->error_msg, 2);
 
 	if (error->description != NULL)
-		printf("  %s\n", error->description);
+		clar_print_indented(error->description, 2);
 
 	printf("\n");
 	fflush(stdout);
@@ -41,14 +62,17 @@ static void clar_print_clap_ontest(const char *suite_name, const char *test_name
 	(void)test_name;
 	(void)test_number;
 
+	if (_clar.verbosity < 0)
+		return;
+
 	if (_clar.verbosity > 1) {
 		printf("%s::%s: ", suite_name, test_name);
 
 		switch (status) {
 		case CL_TEST_OK: printf("ok\n"); break;
 		case CL_TEST_FAILURE: printf("fail\n"); break;
-		case CL_TEST_SKIP: printf("skipped"); break;
-		case CL_TEST_NOTRUN: printf("notrun"); break;
+		case CL_TEST_SKIP: printf("skipped\n"); break;
+		case CL_TEST_NOTRUN: printf("notrun\n"); break;
 		}
 	} else {
 		switch (status) {
@@ -64,6 +88,8 @@ static void clar_print_clap_ontest(const char *suite_name, const char *test_name
 
 static void clar_print_clap_onsuite(const char *suite_name, int suite_index)
 {
+	if (_clar.verbosity < 0)
+		return;
 	if (_clar.verbosity == 1)
 		printf("\n%s", suite_name);
 
@@ -77,11 +103,10 @@ static void clar_print_clap_onabort(const char *fmt, va_list arg)
 
 /* tap: test anywhere protocol format */
 
-static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names)
+static void clar_print_tap_init(int test_count, int suite_count)
 {
 	(void)test_count;
 	(void)suite_count;
-	(void)suite_names;
 	printf("TAP version 13\n");
 }
 
@@ -127,18 +152,20 @@ static void clar_print_tap_ontest(const char *suite_name, const char *test_name,
 	case CL_TEST_FAILURE:
 		printf("not ok %d - %s::%s\n", test_number, suite_name, test_name);
 
-		printf("    ---\n");
-		printf("    reason: |\n");
-		printf("      %s\n", error->error_msg);
+		if (_clar.verbosity >= 0) {
+			printf("    ---\n");
+			printf("    reason: |\n");
+			clar_print_indented(error->error_msg, 6);
 
-		if (error->description)
-			printf("      %s\n", error->description);
+			if (error->description)
+				clar_print_indented(error->description, 6);
 
-		printf("    at:\n");
-		printf("      file: '"); print_escaped(error->file); printf("'\n");
-		printf("      line: %" PRIuMAX "\n", error->line_number);
-		printf("      function: '%s'\n", error->function);
-		printf("    ---\n");
+			printf("    at:\n");
+			printf("      file: '"); print_escaped(error->file); printf("'\n");
+			printf("      line: %" PRIuMAX "\n", error->line_number);
+			printf("      function: '%s'\n", error->function);
+			printf("    ---\n");
+		}
 
 		break;
 	case CL_TEST_SKIP:
@@ -152,6 +179,8 @@ static void clar_print_tap_ontest(const char *suite_name, const char *test_name,
 
 static void clar_print_tap_onsuite(const char *suite_name, int suite_index)
 {
+	if (_clar.verbosity < 0)
+		return;
 	printf("# start of suite %d: %s\n", suite_index, suite_name);
 }
 
@@ -177,9 +206,9 @@ static void clar_print_tap_onabort(const char *fmt, va_list arg)
 		} \
 	} while (0)
 
-static void clar_print_init(int test_count, int suite_count, const char *suite_names)
+static void clar_print_init(int test_count, int suite_count)
 {
-	PRINT(init, test_count, suite_count, suite_names);
+	PRINT(init, test_count, suite_count);
 }
 
 static void clar_print_shutdown(int test_count, int suite_count, int error_count)
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
index bc960f5..52add8a 100644
--- a/t/unit-tests/clar/clar/sandbox.h
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -2,7 +2,17 @@
 #include <sys/syslimits.h>
 #endif
 
-static char _clar_path[4096 + 1];
+/*
+ * The tempdir is the temporary directory for the entirety of the clar
+ * process execution. The sandbox is an individual temporary directory
+ * for the execution of an individual test. Sandboxes are deleted
+ * entirely after test execution to avoid pollution across tests.
+ */
+
+static char _clar_tempdir[CLAR_MAX_PATH];
+static size_t _clar_tempdir_len;
+
+static char _clar_sandbox[CLAR_MAX_PATH];
 
 static int
 is_valid_tmp_path(const char *path)
@@ -15,7 +25,10 @@ is_valid_tmp_path(const char *path)
 	if (!S_ISDIR(st.st_mode))
 		return 0;
 
-	return (access(path, W_OK) == 0);
+	if (access(path, W_OK) != 0)
+		return 0;
+
+	return (strlen(path) < CLAR_MAX_PATH);
 }
 
 static int
@@ -31,14 +44,11 @@ find_tmp_path(char *buffer, size_t length)
 
 	for (i = 0; i < var_count; ++i) {
 		const char *env = getenv(env_vars[i]);
+
 		if (!env)
 			continue;
 
 		if (is_valid_tmp_path(env)) {
-#ifdef __APPLE__
-			if (length >= PATH_MAX && realpath(env, buffer) != NULL)
-				return 0;
-#endif
 			strncpy(buffer, env, length - 1);
 			buffer[length - 1] = '\0';
 			return 0;
@@ -47,21 +57,18 @@ find_tmp_path(char *buffer, size_t length)
 
 	/* If the environment doesn't say anything, try to use /tmp */
 	if (is_valid_tmp_path("/tmp")) {
-#ifdef __APPLE__
-		if (length >= PATH_MAX && realpath("/tmp", buffer) != NULL)
-			return 0;
-#endif
 		strncpy(buffer, "/tmp", length - 1);
 		buffer[length - 1] = '\0';
 		return 0;
 	}
 
 #else
-	DWORD env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length);
-	if (env_len > 0 && env_len < (DWORD)length)
+	DWORD len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length);
+	if (len > 0 && len < (DWORD)length)
 		return 0;
 
-	if (GetTempPath((DWORD)length, buffer))
+	len = GetTempPath((DWORD)length, buffer);
+	if (len > 0 && len < (DWORD)length)
 		return 0;
 #endif
 
@@ -75,17 +82,53 @@ find_tmp_path(char *buffer, size_t length)
 	return -1;
 }
 
-static void clar_unsandbox(void)
+static int canonicalize_tmp_path(char *buffer)
 {
-	if (_clar_path[0] == '\0')
+#ifdef _WIN32
+	char tmp[CLAR_MAX_PATH], *p;
+	DWORD ret;
+
+	ret = GetFullPathName(buffer, CLAR_MAX_PATH, tmp, NULL);
+
+	if (ret == 0 || ret > CLAR_MAX_PATH)
+		return -1;
+
+	ret = GetLongPathName(tmp, buffer, CLAR_MAX_PATH);
+
+	if (ret == 0 || ret > CLAR_MAX_PATH)
+		return -1;
+
+	/* normalize path to POSIX forward slashes */
+	for (p = buffer; *p; p++)
+		if (*p == '\\')
+			*p = '/';
+
+	return 0;
+#elif defined(CLAR_HAS_REALPATH)
+	char tmp[CLAR_MAX_PATH];
+
+	if (realpath(buffer, tmp) == NULL)
+		return -1;
+
+	strcpy(buffer, tmp);
+	return 0;
+#else
+	(void)buffer;
+	return 0;
+#endif
+}
+
+static void clar_tempdir_shutdown(void)
+{
+	if (_clar_tempdir[0] == '\0')
 		return;
 
 	cl_must_pass(chdir(".."));
 
-	fs_rm(_clar_path);
+	fs_rm(_clar_tempdir);
 }
 
-static int build_sandbox_path(void)
+static int build_tempdir_path(void)
 {
 #ifdef CLAR_TMPDIR
 	const char path_tail[] = CLAR_TMPDIR "_XXXXXX";
@@ -95,64 +138,153 @@ static int build_sandbox_path(void)
 
 	size_t len;
 
-	if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)
+	if (find_tmp_path(_clar_tempdir, sizeof(_clar_tempdir)) < 0 ||
+	    canonicalize_tmp_path(_clar_tempdir) < 0)
 		return -1;
 
-	len = strlen(_clar_path);
+	len = strlen(_clar_tempdir);
 
-#ifdef _WIN32
-	{ /* normalize path to POSIX forward slashes */
-		size_t i;
-		for (i = 0; i < len; ++i) {
-			if (_clar_path[i] == '\\')
-				_clar_path[i] = '/';
-		}
-	}
-#endif
+	if (len + strlen(path_tail) + 2 > CLAR_MAX_PATH)
+		return -1;
 
-	if (_clar_path[len - 1] != '/') {
-		_clar_path[len++] = '/';
-	}
+	if (_clar_tempdir[len - 1] != '/')
+		_clar_tempdir[len++] = '/';
 
-	strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
+	strncpy(_clar_tempdir + len, path_tail, sizeof(_clar_tempdir) - len);
 
 #if defined(__MINGW32__)
-	if (_mktemp(_clar_path) == NULL)
+	if (_mktemp(_clar_tempdir) == NULL)
 		return -1;
 
-	if (mkdir(_clar_path, 0700) != 0)
+	if (mkdir(_clar_tempdir, 0700) != 0)
 		return -1;
 #elif defined(_WIN32)
-	if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)
+	if (_mktemp_s(_clar_tempdir, sizeof(_clar_tempdir)) != 0)
 		return -1;
 
-	if (mkdir(_clar_path, 0700) != 0)
+	if (mkdir(_clar_tempdir, 0700) != 0)
 		return -1;
-#elif defined(__sun) || defined(__TANDEM)
-	if (mktemp(_clar_path) == NULL)
+#elif defined(__sun) || defined(__TANDEM) || defined(__hpux)
+	if (mktemp(_clar_tempdir) == NULL)
 		return -1;
 
-	if (mkdir(_clar_path, 0700) != 0)
+	if (mkdir(_clar_tempdir, 0700) != 0)
 		return -1;
 #else
-	if (mkdtemp(_clar_path) == NULL)
+	if (mkdtemp(_clar_tempdir) == NULL)
 		return -1;
 #endif
 
+	_clar_tempdir_len = strlen(_clar_tempdir);
+	return 0;
+}
+
+static void clar_tempdir_init(void)
+{
+	if (_clar_tempdir[0] == '\0' && build_tempdir_path() < 0)
+		clar_abort("Failed to build tempdir path.\n");
+
+	if (chdir(_clar_tempdir) != 0)
+		clar_abort("Failed to change into tempdir '%s': %s.\n",
+			   _clar_tempdir, strerror(errno));
+
+#if !defined(CLAR_SANDBOX_TEST_NAMES) && defined(_WIN32)
+	srand(clock() ^ (unsigned int)time(NULL) ^ GetCurrentProcessId() ^ GetCurrentThreadId());
+#elif !defined(CLAR_SANDBOX_TEST_NAMES)
+	srand(clock() ^ time(NULL) ^ ((unsigned)getpid() << 16));
+#endif
+}
+
+static void append(char *dst, const char *src)
+{
+	char *d;
+	const char *s;
+
+	for (d = dst; *d; d++)
+		;
+
+	for (s = src; *s; d++, s++)
+		if (*s == ':')
+			*d = '_';
+		else
+			*d = *s;
+
+	*d = '\0';
+}
+
+static int clar_sandbox_create(const char *suite_name, const char *test_name)
+{
+#ifndef CLAR_SANDBOX_TEST_NAMES
+	char alpha[] = "0123456789abcdef";
+	int num = rand();
+#endif
+
+	cl_assert(_clar_sandbox[0] == '\0');
+
+	/*
+	 * We may want to use test names as sandbox directory names for
+	 * readability, _however_ on platforms with restrictions for short
+	 * file / folder names (eg, Windows), this may be too long.
+	 */
+#ifdef CLAR_SANDBOX_TEST_NAMES
+	cl_assert(strlen(_clar_tempdir) + strlen(suite_name) + strlen(test_name) + 3 < CLAR_MAX_PATH);
+
+	strcpy(_clar_sandbox, _clar_tempdir);
+	_clar_sandbox[_clar_tempdir_len] = '/';
+	_clar_sandbox[_clar_tempdir_len + 1] = '\0';
+
+	append(_clar_sandbox, suite_name);
+	append(_clar_sandbox, "__");
+	append(_clar_sandbox, test_name);
+#else
+	((void)suite_name);
+	((void)test_name);
+	((void)append);
+
+	cl_assert(strlen(_clar_tempdir) + 9 < CLAR_MAX_PATH);
+
+	strcpy(_clar_sandbox, _clar_tempdir);
+	_clar_sandbox[_clar_tempdir_len] = '/';
+
+	_clar_sandbox[_clar_tempdir_len + 1] = alpha[(num & 0xf0000000) >> 28];
+	_clar_sandbox[_clar_tempdir_len + 2] = alpha[(num & 0x0f000000) >> 24];
+	_clar_sandbox[_clar_tempdir_len + 3] = alpha[(num & 0x00f00000) >> 20];
+	_clar_sandbox[_clar_tempdir_len + 4] = alpha[(num & 0x000f0000) >> 16];
+	_clar_sandbox[_clar_tempdir_len + 5] = alpha[(num & 0x0000f000) >> 12];
+	_clar_sandbox[_clar_tempdir_len + 6] = alpha[(num & 0x00000f00) >> 8];
+	_clar_sandbox[_clar_tempdir_len + 7] = alpha[(num & 0x000000f0) >> 4];
+	_clar_sandbox[_clar_tempdir_len + 8] = alpha[(num & 0x0000000f) >> 0];
+	_clar_sandbox[_clar_tempdir_len + 9] = '\0';
+#endif
+
+	if (mkdir(_clar_sandbox, 0700) != 0)
+		return -1;
+
+	if (chdir(_clar_sandbox) != 0)
+		return -1;
+
 	return 0;
 }
 
-static void clar_sandbox(void)
+static int clar_sandbox_cleanup(void)
 {
-	if (_clar_path[0] == '\0' && build_sandbox_path() < 0)
-		clar_abort("Failed to build sandbox path.\n");
+	cl_assert(_clar_sandbox[0] != '\0');
 
-	if (chdir(_clar_path) != 0)
-		clar_abort("Failed to change into sandbox directory '%s': %s.\n",
-			   _clar_path, strerror(errno));
+	if (chdir(_clar_tempdir) != 0)
+		return -1;
+
+	fs_rm(_clar_sandbox);
+	_clar_sandbox[0] = '\0';
+
+	return 0;
+}
+
+const char *clar_tempdir_path(void)
+{
+	return _clar_tempdir;
 }
 
 const char *clar_sandbox_path(void)
 {
-	return _clar_path;
+	return _clar_sandbox;
 }
diff --git a/t/unit-tests/clar/clar/summary.h b/t/unit-tests/clar/clar/summary.h
index 0d0b646..7b85f16 100644
--- a/t/unit-tests/clar/clar/summary.h
+++ b/t/unit-tests/clar/clar/summary.h
@@ -23,10 +23,11 @@ static int clar_summary_testsuite(struct clar_summary *summary,
     int idn, const char *name, time_t timestamp,
     int test_count, int fail_count, int error_count)
 {
-	struct tm *tm = localtime(&timestamp);
+	struct tm tm;
 	char iso_dt[20];
 
-	if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0)
+	localtime_r(&timestamp, &tm);
+	if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", &tm) == 0)
 		return -1;
 
 	return fprintf(summary->fp, "\t<testsuite"
diff --git a/t/unit-tests/clar/example/CMakeLists.txt b/t/unit-tests/clar/example/CMakeLists.txt
new file mode 100644
index 0000000..b72f187
--- /dev/null
+++ b/t/unit-tests/clar/example/CMakeLists.txt
@@ -0,0 +1,28 @@
+find_package(Python COMPONENTS Interpreter REQUIRED)
+
+add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/clar.suite"
+	COMMAND "${Python_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/generate.py" --output "${CMAKE_CURRENT_BINARY_DIR}"
+	DEPENDS main.c example.c
+	WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+)
+
+add_executable(example)
+set_target_properties(example PROPERTIES
+	C_STANDARD 90
+	C_STANDARD_REQUIRED ON
+	C_EXTENSIONS OFF
+)
+target_sources(example PRIVATE
+	main.c
+	example.c
+	"${CMAKE_CURRENT_BINARY_DIR}/clar.suite"
+)
+target_compile_definitions(example PRIVATE)
+target_compile_options(example PRIVATE
+	$<IF:$<CXX_COMPILER_ID:MSVC>,/W4,-Wall>
+)
+target_include_directories(example PRIVATE
+	"${CMAKE_SOURCE_DIR}"
+	"${CMAKE_CURRENT_BINARY_DIR}"
+)
+target_link_libraries(example clar)
diff --git a/t/unit-tests/clar/example/example.c b/t/unit-tests/clar/example/example.c
new file mode 100644
index 0000000..c07d6bf
--- /dev/null
+++ b/t/unit-tests/clar/example/example.c
@@ -0,0 +1,6 @@
+#include "clar.h"
+
+void test_example__simple_assert(void)
+{
+	cl_assert_equal_i(1, 1);
+}
diff --git a/t/unit-tests/clar/test/main.c.sample b/t/unit-tests/clar/example/main.c
similarity index 96%
rename from t/unit-tests/clar/test/main.c.sample
rename to t/unit-tests/clar/example/main.c
index a4d91b7..f8def7f 100644
--- a/t/unit-tests/clar/test/main.c.sample
+++ b/t/unit-tests/clar/example/main.c
@@ -5,7 +5,7 @@
  * For full terms see the included COPYING file.
  */
 
-#include "clar_test.h"
+#include "clar.h"
 
 /*
  * Minimal main() for clar tests.
diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py
index 80996ac..fd2f0ee 100755
--- a/t/unit-tests/clar/generate.py
+++ b/t/unit-tests/clar/generate.py
@@ -158,17 +158,24 @@ def should_generate(self, path):
 
     def find_modules(self):
         modules = []
-        for root, _, files in os.walk(self.path):
-            module_root = root[len(self.path):]
-            module_root = [c for c in module_root.split(os.sep) if c]
 
-            tests_in_module = fnmatch.filter(files, "*.c")
+        if os.path.isfile(self.path):
+            full_path = os.path.abspath(self.path)
+            module_name = os.path.basename(self.path)
+            module_name = os.path.splitext(module_name)[0]
+            modules.append((full_path, module_name))
+        else:
+            for root, _, files in os.walk(self.path):
+                module_root = root[len(self.path):]
+                module_root = [c for c in module_root.split(os.sep) if c]
 
-            for test_file in tests_in_module:
-                full_path = os.path.join(root, test_file)
-                module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_")
+                tests_in_module = fnmatch.filter(files, "*.c")
 
-                modules.append((full_path, module_name))
+                for test_file in tests_in_module:
+                    full_path = os.path.join(root, test_file)
+                    module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_")
+
+                    modules.append((full_path, module_name))
 
         return modules
 
@@ -217,6 +224,7 @@ def callback_count(self):
 
     def write(self):
         output = os.path.join(self.output, 'clar.suite')
+        os.makedirs(self.output, exist_ok=True)
 
         if not self.should_generate(output):
             return False
@@ -258,7 +266,11 @@ def write(self):
         sys.exit(1)
 
     path = args.pop() if args else '.'
+    if os.path.isfile(path) and not options.output:
+        print("Must provide --output when specifying a file")
+        sys.exit(1)
     output = options.output or path
+
     suite = TestSuite(path, output)
     suite.load(options.force)
     suite.disable(options.excluded)
diff --git a/t/unit-tests/clar/test/CMakeLists.txt b/t/unit-tests/clar/test/CMakeLists.txt
index 7f2c1dc..f240166 100644
--- a/t/unit-tests/clar/test/CMakeLists.txt
+++ b/t/unit-tests/clar/test/CMakeLists.txt
@@ -2,12 +2,12 @@
 
 add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/clar.suite"
 	COMMAND "${Python_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/generate.py" --output "${CMAKE_CURRENT_BINARY_DIR}"
-	DEPENDS main.c sample.c clar_test.h
+	DEPENDS main.c selftest.c
 	WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
 )
 
-add_executable(clar_test)
-set_target_properties(clar_test PROPERTIES
+add_executable(selftest)
+set_target_properties(selftest PROPERTIES
 	C_STANDARD 90
 	C_STANDARD_REQUIRED ON
 	C_EXTENSIONS OFF
@@ -15,25 +15,35 @@
 
 # MSVC generates all kinds of warnings. We may want to fix these in the future
 # and then unconditionally treat warnings as errors.
-if(NOT MSVC)
-	set_target_properties(clar_test PROPERTIES
+if (NOT MSVC)
+	set_target_properties(selftest PROPERTIES
 		COMPILE_WARNING_AS_ERROR ON
 	)
 endif()
 
-target_sources(clar_test PRIVATE
+target_sources(selftest PRIVATE
 	main.c
-	sample.c
+	selftest.c
 	"${CMAKE_CURRENT_BINARY_DIR}/clar.suite"
 )
-target_compile_definitions(clar_test PRIVATE
-	CLAR_FIXTURE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/resources/"
+target_compile_definitions(selftest PRIVATE
+	CLAR_FIXTURE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/expected/"
 )
-target_compile_options(clar_test PRIVATE
+target_compile_options(selftest PRIVATE
 	$<IF:$<CXX_COMPILER_ID:MSVC>,/W4,-Wall>
 )
-target_include_directories(clar_test PRIVATE
+target_include_directories(selftest PRIVATE
 	"${CMAKE_SOURCE_DIR}"
 	"${CMAKE_CURRENT_BINARY_DIR}"
 )
-target_link_libraries(clar_test clar)
+target_link_libraries(selftest clar)
+
+add_test(NAME build_selftest
+	COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" --config "$<CONFIG>" --target selftest
+)
+set_tests_properties(build_selftest PROPERTIES FIXTURES_SETUP clar_test_fixture)
+
+add_subdirectory(suites)
+
+add_test(NAME selftest COMMAND "${CMAKE_CURRENT_BINARY_DIR}/selftest" $<TARGET_FILE_DIR:combined_suite>)
+set_tests_properties(selftest PROPERTIES FIXTURES_REQUIRED clar_test_fixture)
diff --git a/t/unit-tests/clar/test/clar_test.h b/t/unit-tests/clar/test/clar_test.h
deleted file mode 100644
index 0fcaa63..0000000
--- a/t/unit-tests/clar/test/clar_test.h
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
- * Copyright (c) Vicent Marti. All rights reserved.
- *
- * This file is part of clar, distributed under the ISC license.
- * For full terms see the included COPYING file.
- */
-#ifndef __CLAR_TEST__
-#define __CLAR_TEST__
-
-/* Import the standard clar helper functions */
-#include "clar.h"
-
-/* Your custom shared includes / defines here */
-extern int global_test_counter;
-
-#endif
diff --git a/t/unit-tests/clar/test/expected/help b/t/unit-tests/clar/test/expected/help
new file mode 100644
index 0000000..9428def
--- /dev/null
+++ b/t/unit-tests/clar/test/expected/help
@@ -0,0 +1,12 @@
+Usage: combined [options]
+
+Options:
+  -sname        Run only the suite with `name` (can go to individual test name)
+  -iname        Include the suite with `name`
+  -xname        Exclude the suite with `name`
+  -v            Increase verbosity (show suite names)
+  -q            Decrease verbosity, inverse to -v
+  -Q            Quit as soon as a test fails
+  -t            Display results in tap format
+  -l            Print suite names
+  -r[filename]  Write summary file (to the optional filename)
diff --git a/t/unit-tests/clar/test/expected/quiet b/t/unit-tests/clar/test/expected/quiet
new file mode 100644
index 0000000..280c99d
--- /dev/null
+++ b/t/unit-tests/clar/test/expected/quiet
@@ -0,0 +1,44 @@
+  1) Failure:
+combined::1 [file:42]
+  Function call failed: -1
+
+  2) Failure:
+combined::2 [file:42]
+  Expression is not true: 100 == 101
+
+  3) Failure:
+combined::strings [file:42]
+  String mismatch: "mismatched" != actual ("this one fails")
+  'mismatched' != 'expected' (at byte 0)
+
+  4) Failure:
+combined::strings_with_length [file:42]
+  String mismatch: "exactly" != actual ("this one fails")
+  'exa' != 'exp' (at byte 2)
+
+  5) Failure:
+combined::int [file:42]
+  101 != value ("extra note on failing test")
+  101 != 100
+
+  6) Failure:
+combined::int_fmt [file:42]
+  022 != value
+  0022 != 0144
+
+  7) Failure:
+combined::bool [file:42]
+  0 != value
+  0 != 1
+
+  8) Failure:
+combined::multiline_description [file:42]
+  Function call failed: -1
+  description line 1
+  description line 2
+
+  9) Failure:
+combined::null_string [file:42]
+  String mismatch: "expected" != actual ("this one fails")
+  'expected' != NULL
+
diff --git a/t/unit-tests/clar/test/expected/specific_test b/t/unit-tests/clar/test/expected/specific_test
new file mode 100644
index 0000000..6c22e9f
--- /dev/null
+++ b/t/unit-tests/clar/test/expected/specific_test
@@ -0,0 +1,9 @@
+Loaded 1 suites:
+Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')
+F
+
+  1) Failure:
+combined::bool [file:42]
+  0 != value
+  0 != 1
+
diff --git a/t/unit-tests/clar/test/expected/stop_on_failure b/t/unit-tests/clar/test/expected/stop_on_failure
new file mode 100644
index 0000000..c236107
--- /dev/null
+++ b/t/unit-tests/clar/test/expected/stop_on_failure
@@ -0,0 +1,8 @@
+Loaded 1 suites:
+Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')
+F
+
+  1) Failure:
+combined::1 [file:42]
+  Function call failed: -1
+
diff --git a/t/unit-tests/clar/test/expected/suite_names b/t/unit-tests/clar/test/expected/suite_names
new file mode 100644
index 0000000..10d1538
--- /dev/null
+++ b/t/unit-tests/clar/test/expected/suite_names
@@ -0,0 +1,2 @@
+Test suites (use -s<name> to run just one):
+   0: combined
diff --git a/t/unit-tests/clar/test/expected/summary.xml b/t/unit-tests/clar/test/expected/summary.xml
new file mode 100644
index 0000000..9a89d43
--- /dev/null
+++ b/t/unit-tests/clar/test/expected/summary.xml
@@ -0,0 +1,41 @@
+<testsuites>
+	<testsuite id="0" name="selftest" hostname="localhost" timestamp="2024-09-06T10:04:08" tests="8" failures="8" errors="0">
+		<testcase name="1" classname="selftest" time="0.00">
+			<failure type="assert"><![CDATA[Function call failed: -1
+(null)]]></failure>
+		</testcase>
+		<testcase name="2" classname="selftest" time="0.00">
+			<failure type="assert"><![CDATA[Expression is not true: 100 == 101
+(null)]]></failure>
+		</testcase>
+		<testcase name="strings" classname="selftest" time="0.00">
+			<failure type="assert"><![CDATA[String mismatch: "mismatched" != actual ("this one fails")
+'mismatched' != 'expected' (at byte 0)]]></failure>
+		</testcase>
+		<testcase name="strings_with_length" classname="selftest" time="0.00">
+			<failure type="assert"><![CDATA[String mismatch: "exactly" != actual ("this one fails")
+'exa' != 'exp' (at byte 2)]]></failure>
+		</testcase>
+		<testcase name="int" classname="selftest" time="0.00">
+			<failure type="assert"><![CDATA[101 != value ("extra note on failing test")
+101 != 100]]></failure>
+		</testcase>
+		<testcase name="int_fmt" classname="selftest" time="0.00">
+			<failure type="assert"><![CDATA[022 != value
+0022 != 0144]]></failure>
+		</testcase>
+		<testcase name="bool" classname="selftest" time="0.00">
+			<failure type="assert"><![CDATA[0 != value
+0 != 1]]></failure>
+		</testcase>
+		<testcase name="multiline_description" classname="selftest" time="0.00">
+			<failure type="assert"><![CDATA[Function call failed: −1
+description line 1
+description line 2]]></failure>
+		</testcase>
+		<testcase name="null_string" classname="selftest" time="0.00">
+			<failure type="assert"><![CDATA[String mismatch: "expected" != actual ("this one fails")
+'expected' != NULL]]></failure>
+		</testcase>
+	</testsuite>
+</testsuites>
diff --git a/t/unit-tests/clar/test/expected/summary_with_filename b/t/unit-tests/clar/test/expected/summary_with_filename
new file mode 100644
index 0000000..4601607
--- /dev/null
+++ b/t/unit-tests/clar/test/expected/summary_with_filename
@@ -0,0 +1,49 @@
+Loaded 1 suites:
+Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')
+FFFFFFFFF
+
+  1) Failure:
+combined::1 [file:42]
+  Function call failed: -1
+
+  2) Failure:
+combined::2 [file:42]
+  Expression is not true: 100 == 101
+
+  3) Failure:
+combined::strings [file:42]
+  String mismatch: "mismatched" != actual ("this one fails")
+  'mismatched' != 'expected' (at byte 0)
+
+  4) Failure:
+combined::strings_with_length [file:42]
+  String mismatch: "exactly" != actual ("this one fails")
+  'exa' != 'exp' (at byte 2)
+
+  5) Failure:
+combined::int [file:42]
+  101 != value ("extra note on failing test")
+  101 != 100
+
+  6) Failure:
+combined::int_fmt [file:42]
+  022 != value
+  0022 != 0144
+
+  7) Failure:
+combined::bool [file:42]
+  0 != value
+  0 != 1
+
+  8) Failure:
+combined::multiline_description [file:42]
+  Function call failed: -1
+  description line 1
+  description line 2
+
+  9) Failure:
+combined::null_string [file:42]
+  String mismatch: "expected" != actual ("this one fails")
+  'expected' != NULL
+
+written summary file to different.xml
diff --git a/t/unit-tests/clar/test/expected/summary_without_filename b/t/unit-tests/clar/test/expected/summary_without_filename
new file mode 100644
index 0000000..7874c1d
--- /dev/null
+++ b/t/unit-tests/clar/test/expected/summary_without_filename
@@ -0,0 +1,49 @@
+Loaded 1 suites:
+Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')
+FFFFFFFFF
+
+  1) Failure:
+combined::1 [file:42]
+  Function call failed: -1
+
+  2) Failure:
+combined::2 [file:42]
+  Expression is not true: 100 == 101
+
+  3) Failure:
+combined::strings [file:42]
+  String mismatch: "mismatched" != actual ("this one fails")
+  'mismatched' != 'expected' (at byte 0)
+
+  4) Failure:
+combined::strings_with_length [file:42]
+  String mismatch: "exactly" != actual ("this one fails")
+  'exa' != 'exp' (at byte 2)
+
+  5) Failure:
+combined::int [file:42]
+  101 != value ("extra note on failing test")
+  101 != 100
+
+  6) Failure:
+combined::int_fmt [file:42]
+  022 != value
+  0022 != 0144
+
+  7) Failure:
+combined::bool [file:42]
+  0 != value
+  0 != 1
+
+  8) Failure:
+combined::multiline_description [file:42]
+  Function call failed: -1
+  description line 1
+  description line 2
+
+  9) Failure:
+combined::null_string [file:42]
+  String mismatch: "expected" != actual ("this one fails")
+  'expected' != NULL
+
+written summary file to summary.xml
diff --git a/t/unit-tests/clar/test/expected/tap b/t/unit-tests/clar/test/expected/tap
new file mode 100644
index 0000000..bddbd5d
--- /dev/null
+++ b/t/unit-tests/clar/test/expected/tap
@@ -0,0 +1,92 @@
+TAP version 13
+# start of suite 1: combined
+not ok 1 - combined::1
+    ---
+    reason: |
+      Function call failed: -1
+    at:
+      file: 'file'
+      line: 42
+      function: 'func'
+    ---
+not ok 2 - combined::2
+    ---
+    reason: |
+      Expression is not true: 100 == 101
+    at:
+      file: 'file'
+      line: 42
+      function: 'func'
+    ---
+not ok 3 - combined::strings
+    ---
+    reason: |
+      String mismatch: "mismatched" != actual ("this one fails")
+      'mismatched' != 'expected' (at byte 0)
+    at:
+      file: 'file'
+      line: 42
+      function: 'func'
+    ---
+not ok 4 - combined::strings_with_length
+    ---
+    reason: |
+      String mismatch: "exactly" != actual ("this one fails")
+      'exa' != 'exp' (at byte 2)
+    at:
+      file: 'file'
+      line: 42
+      function: 'func'
+    ---
+not ok 5 - combined::int
+    ---
+    reason: |
+      101 != value ("extra note on failing test")
+      101 != 100
+    at:
+      file: 'file'
+      line: 42
+      function: 'func'
+    ---
+not ok 6 - combined::int_fmt
+    ---
+    reason: |
+      022 != value
+      0022 != 0144
+    at:
+      file: 'file'
+      line: 42
+      function: 'func'
+    ---
+not ok 7 - combined::bool
+    ---
+    reason: |
+      0 != value
+      0 != 1
+    at:
+      file: 'file'
+      line: 42
+      function: 'func'
+    ---
+not ok 8 - combined::multiline_description
+    ---
+    reason: |
+      Function call failed: -1
+      description line 1
+      description line 2
+    at:
+      file: 'file'
+      line: 42
+      function: 'func'
+    ---
+not ok 9 - combined::null_string
+    ---
+    reason: |
+      String mismatch: "expected" != actual ("this one fails")
+      'expected' != NULL
+    at:
+      file: 'file'
+      line: 42
+      function: 'func'
+    ---
+1..9
diff --git a/t/unit-tests/clar/test/expected/without_arguments b/t/unit-tests/clar/test/expected/without_arguments
new file mode 100644
index 0000000..1111d41
--- /dev/null
+++ b/t/unit-tests/clar/test/expected/without_arguments
@@ -0,0 +1,48 @@
+Loaded 1 suites:
+Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')
+FFFFFFFFF
+
+  1) Failure:
+combined::1 [file:42]
+  Function call failed: -1
+
+  2) Failure:
+combined::2 [file:42]
+  Expression is not true: 100 == 101
+
+  3) Failure:
+combined::strings [file:42]
+  String mismatch: "mismatched" != actual ("this one fails")
+  'mismatched' != 'expected' (at byte 0)
+
+  4) Failure:
+combined::strings_with_length [file:42]
+  String mismatch: "exactly" != actual ("this one fails")
+  'exa' != 'exp' (at byte 2)
+
+  5) Failure:
+combined::int [file:42]
+  101 != value ("extra note on failing test")
+  101 != 100
+
+  6) Failure:
+combined::int_fmt [file:42]
+  022 != value
+  0022 != 0144
+
+  7) Failure:
+combined::bool [file:42]
+  0 != value
+  0 != 1
+
+  8) Failure:
+combined::multiline_description [file:42]
+  Function call failed: -1
+  description line 1
+  description line 2
+
+  9) Failure:
+combined::null_string [file:42]
+  String mismatch: "expected" != actual ("this one fails")
+  'expected' != NULL
+
diff --git a/t/unit-tests/clar/test/main.c b/t/unit-tests/clar/test/main.c
index 59e56ad..94af440 100644
--- a/t/unit-tests/clar/test/main.c
+++ b/t/unit-tests/clar/test/main.c
@@ -1,23 +1,9 @@
-/*
- * Copyright (c) Vicent Marti. All rights reserved.
- *
- * This file is part of clar, distributed under the ISC license.
- * For full terms see the included COPYING file.
- */
+#include <stdio.h>
+#include <string.h>
 
-#include "clar_test.h"
+#include "selftest.h"
 
-/*
- * Sample main() for clar tests.
- *
- * You should write your own main routine for clar tests that does specific
- * setup and teardown as necessary for your application.  The only required
- * line is the call to `clar_test(argc, argv)`, which will execute the test
- * suite.  If you want to check the return value of the test application,
- * your main() should return the same value returned by clar_test().
- */
-
-int global_test_counter = 0;
+const char *selftest_suite_directory;
 
 #ifdef _WIN32
 int __cdecl main(int argc, char *argv[])
@@ -25,16 +11,15 @@ int __cdecl main(int argc, char *argv[])
 int main(int argc, char *argv[])
 #endif
 {
-	int ret;
+	if (argc < 2) {
+		fprintf(stderr, "usage: %s <selftest-suite-directory> <options>\n",
+			argv[0]);
+		exit(1);
+	}
 
-	/* Your custom initialization here */
-	global_test_counter = 0;
+	selftest_suite_directory = argv[1];
+	memmove(argv + 1, argv + 2, argc - 1);
+	argc -= 1;
 
-	/* Run the test suite */
-	ret = clar_test(argc, argv);
-
-	/* Your custom cleanup here */
-	cl_assert_equal_i(8, global_test_counter);
-
-	return ret;
+	return clar_test(argc, argv);
 }
diff --git a/t/unit-tests/clar/test/selftest.c b/t/unit-tests/clar/test/selftest.c
new file mode 100644
index 0000000..eed83e4
--- /dev/null
+++ b/t/unit-tests/clar/test/selftest.c
@@ -0,0 +1,370 @@
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "selftest.h"
+
+#ifdef _WIN32
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+
+static char *read_full(HANDLE h, int is_pipe)
+{
+	char *data = NULL;
+	size_t data_size = 0;
+
+	while (1) {
+		CHAR buf[4096];
+		DWORD bytes_read;
+
+		if (!ReadFile(h, buf, sizeof(buf), &bytes_read, NULL)) {
+			if (!is_pipe)
+				cl_fail("Failed reading file handle.");
+			cl_assert_equal_i(GetLastError(), ERROR_BROKEN_PIPE);
+			break;
+		}
+		if (!bytes_read)
+			break;
+
+		data = realloc(data, data_size + bytes_read);
+		cl_assert(data);
+		memcpy(data + data_size, buf, bytes_read);
+		data_size += bytes_read;
+	}
+
+	data = realloc(data, data_size + 1);
+	cl_assert(data);
+	data[data_size] = '\0';
+
+	while (strstr(data, "\r\n")) {
+		char *ptr = strstr(data, "\r\n");
+		memmove(ptr, ptr + 1, strlen(ptr));
+	}
+
+	return data;
+}
+
+static char *read_file(const char *path)
+{
+	char *content;
+	HANDLE file;
+
+	file = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL,
+			  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+	cl_assert(file != INVALID_HANDLE_VALUE);
+	content = read_full(file, 0);
+	cl_assert_equal_b(1, CloseHandle(file));
+
+	return content;
+}
+
+static char *execute(const char *suite, int expected_error_code, const char **args, size_t nargs)
+{
+	SECURITY_ATTRIBUTES security_attributes = { 0 };
+	PROCESS_INFORMATION process_info = { 0 };
+	STARTUPINFO startup_info = { 0 };
+	char binary_path[4096] = { 0 };
+	char cmdline[4096] = { 0 };
+	char *output = NULL;
+	HANDLE stdout_write;
+	HANDLE stdout_read;
+	DWORD exit_code;
+	size_t i;
+
+	snprintf(binary_path, sizeof(binary_path), "%s/%s_suite.exe",
+		 selftest_suite_directory, suite);
+
+	/*
+	 * Assemble command line arguments. In theory we'd have to properly
+	 * quote them. In practice none of our tests actually care.
+	 */
+	snprintf(cmdline, sizeof(cmdline), suite);
+	for (i = 0; i < nargs; i++) {
+		size_t cmdline_len = strlen(cmdline);
+		const char *arg = args[i];
+		cl_assert(cmdline_len + strlen(arg) < sizeof(cmdline));
+		snprintf(cmdline + cmdline_len, sizeof(cmdline) - cmdline_len,
+			 " %s", arg);
+	}
+
+	/*
+	 * Create a pipe that we will use to read data from the child process.
+	 * The writing side needs to be inheritable such that the child can use
+	 * it as stdout and stderr. The reading side should only be used by the
+	 * parent.
+	 */
+	security_attributes.nLength = sizeof(security_attributes);
+	security_attributes.bInheritHandle = TRUE;
+	cl_assert_equal_b(1, CreatePipe(&stdout_read, &stdout_write, &security_attributes, 0));
+	cl_assert_equal_b(1, SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0));
+
+	/*
+	 * Create the child process with our pipe.
+	 */
+	startup_info.cb = sizeof(startup_info);
+	startup_info.hStdError = stdout_write;
+	startup_info.hStdOutput = stdout_write;
+	startup_info.dwFlags |= STARTF_USESTDHANDLES;
+	cl_assert_equal_b(1, CreateProcess(binary_path, cmdline, NULL, NULL, TRUE,
+					   0, NULL, NULL, &startup_info, &process_info));
+	cl_assert_equal_b(1, CloseHandle(stdout_write));
+
+	output = read_full(stdout_read, 1);
+	cl_assert_equal_b(1, CloseHandle(stdout_read));
+	cl_assert_equal_b(1, GetExitCodeProcess(process_info.hProcess, &exit_code));
+	cl_assert_equal_i(exit_code, expected_error_code);
+
+	return output;
+}
+
+static void assert_output(const char *suite, const char *expected_output_file, int expected_error_code, ...)
+{
+	char *expected_output = NULL;
+	char *output = NULL;
+	const char *args[16];
+	va_list ap;
+	size_t i;
+
+	va_start(ap, expected_error_code);
+	for (i = 0; ; i++) {
+		const char *arg = va_arg(ap, const char *);
+		if (!arg)
+			break;
+		cl_assert(i < sizeof(args) / sizeof(*args));
+		args[i] = arg;
+	}
+	va_end(ap);
+
+	output = execute(suite, expected_error_code, args, i);
+	expected_output = read_file(cl_fixture(expected_output_file));
+	cl_assert_equal_s(output, expected_output);
+
+	free(expected_output);
+	free(output);
+}
+
+#else
+# include <errno.h>
+# include <fcntl.h>
+# include <limits.h>
+# include <unistd.h>
+# include <sys/wait.h>
+
+static char *read_full(int fd)
+{
+	size_t data_bytes = 0;
+	char *data = NULL;
+
+	while (1) {
+		char buf[4096];
+		ssize_t n;
+
+		n = read(fd, buf, sizeof(buf));
+		if (n < 0) {
+			if (errno == EAGAIN || errno == EINTR)
+				continue;
+			cl_fail("Failed reading from child process.");
+		}
+		if (!n)
+			break;
+
+		data = realloc(data, data_bytes + n);
+		cl_assert(data);
+
+		memcpy(data + data_bytes, buf, n);
+		data_bytes += n;
+	}
+
+	data = realloc(data, data_bytes + 1);
+	cl_assert(data);
+	data[data_bytes] = '\0';
+
+	return data;
+}
+
+static char *read_file(const char *path)
+{
+	char *data;
+	int fd;
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		cl_fail("Failed reading expected file.");
+
+	data = read_full(fd);
+	cl_must_pass(close(fd));
+
+	return data;
+}
+
+static char *execute(const char *suite, int expected_error_code, const char **args, size_t nargs)
+{
+	int pipe_fds[2];
+	pid_t pid;
+
+	cl_must_pass(pipe(pipe_fds));
+
+	pid = fork();
+	if (!pid) {
+		const char *final_args[17] = { NULL };
+		char binary_path[4096];
+		size_t len = 0;
+		size_t i;
+
+		cl_assert(nargs < sizeof(final_args) / sizeof(*final_args));
+		final_args[0] = suite;
+		for (i = 0; i < nargs; i++)
+			final_args[i + 1] = args[i];
+
+		if (dup2(pipe_fds[1], STDOUT_FILENO) < 0 ||
+		    dup2(pipe_fds[1], STDERR_FILENO) < 0 ||
+		    close(0) < 0 ||
+		    close(pipe_fds[0]) < 0 ||
+		    close(pipe_fds[1]) < 0)
+			exit(1);
+
+		cl_assert(len + strlen(selftest_suite_directory) < sizeof(binary_path));
+		strcpy(binary_path, selftest_suite_directory);
+		len += strlen(selftest_suite_directory);
+
+		cl_assert(len + 1 < sizeof(binary_path));
+		binary_path[len] = '/';
+		len += 1;
+
+		cl_assert(len + strlen(suite) < sizeof(binary_path));
+		strcpy(binary_path + len, suite);
+		len += strlen(suite);
+
+		cl_assert(len + strlen("_suite") < sizeof(binary_path));
+		strcpy(binary_path + len, "_suite");
+		len += strlen("_suite");
+
+		binary_path[len] = '\0';
+
+		execv(binary_path, (char **) final_args);
+		exit(1);
+	} else if (pid > 0) {
+		pid_t waited_pid;
+		char *output;
+		int stat;
+
+		cl_must_pass(close(pipe_fds[1]));
+
+		output = read_full(pipe_fds[0]);
+
+		waited_pid = waitpid(pid, &stat, 0);
+		cl_assert_equal_i(pid, waited_pid);
+		cl_assert(WIFEXITED(stat));
+		cl_assert_equal_i(WEXITSTATUS(stat), expected_error_code);
+
+		return output;
+	} else {
+		cl_fail("Fork failed.");
+	}
+
+	return NULL;
+}
+
+static void assert_output(const char *suite, const char *expected_output_file, int expected_error_code, ...)
+{
+	char *expected_output, *output;
+	const char *args[16];
+	va_list ap;
+	size_t i;
+
+	va_start(ap, expected_error_code);
+	for (i = 0; ; i++) {
+		cl_assert(i < sizeof(args) / sizeof(*args));
+		args[i] = va_arg(ap, const char *);
+		if (!args[i])
+			break;
+	}
+	va_end(ap);
+
+	output = execute(suite, expected_error_code, args, i);
+	expected_output = read_file(cl_fixture(expected_output_file));
+	cl_assert_equal_s(output, expected_output);
+
+	free(expected_output);
+	free(output);
+}
+#endif
+
+void test_selftest__help(void)
+{
+	cl_invoke(assert_output("combined", "help", 1, "-h", NULL));
+}
+
+void test_selftest__without_arguments(void)
+{
+	cl_invoke(assert_output("combined", "without_arguments", 9, NULL));
+}
+
+void test_selftest__specific_test(void)
+{
+	cl_invoke(assert_output("combined", "specific_test", 1, "-scombined::bool", NULL));
+}
+
+void test_selftest__stop_on_failure(void)
+{
+	cl_invoke(assert_output("combined", "stop_on_failure", 1, "-Q", NULL));
+}
+
+void test_selftest__quiet(void)
+{
+	cl_invoke(assert_output("combined", "quiet", 9, "-q", NULL));
+}
+
+void test_selftest__tap(void)
+{
+	cl_invoke(assert_output("combined", "tap", 9, "-t", NULL));
+}
+
+void test_selftest__suite_names(void)
+{
+	cl_invoke(assert_output("combined", "suite_names", 0, "-l", NULL));
+}
+
+void test_selftest__summary_without_filename(void)
+{
+	struct stat st;
+	cl_invoke(assert_output("combined", "summary_without_filename", 9, "-r", NULL));
+	/* The summary contains timestamps, so we cannot verify its contents. */
+	cl_must_pass(stat("summary.xml", &st));
+}
+
+void test_selftest__summary_with_filename(void)
+{
+	struct stat st;
+	cl_invoke(assert_output("combined", "summary_with_filename", 9, "-rdifferent.xml", NULL));
+	/* The summary contains timestamps, so we cannot verify its contents. */
+	cl_must_pass(stat("different.xml", &st));
+}
+
+void test_selftest__pointer_equal(void)
+{
+	const char *args[] = {
+		"-spointer::equal",
+		"-t"
+	};
+	char *output = execute("pointer", 0, args, 2);
+	cl_assert_equal_s(output,
+		   "TAP version 13\n"
+		   "# start of suite 1: pointer\n"
+		   "ok 1 - pointer::equal\n"
+		   "1..1\n"
+	);
+	free(output);
+}
+
+void test_selftest__pointer_unequal(void)
+{
+	const char *args[] = {
+		"-spointer::unequal",
+	};
+	char *output = execute("pointer", 1, args, 1);
+	cl_assert(output);
+	cl_assert(strstr(output, "Pointer mismatch: "));
+	free(output);
+}
diff --git a/t/unit-tests/clar/test/selftest.h b/t/unit-tests/clar/test/selftest.h
new file mode 100644
index 0000000..c24e0c5
--- /dev/null
+++ b/t/unit-tests/clar/test/selftest.h
@@ -0,0 +1,3 @@
+#include "clar.h"
+
+extern const char *selftest_suite_directory;
diff --git a/t/unit-tests/clar/test/suites/CMakeLists.txt b/t/unit-tests/clar/test/suites/CMakeLists.txt
new file mode 100644
index 0000000..fa8ab94
--- /dev/null
+++ b/t/unit-tests/clar/test/suites/CMakeLists.txt
@@ -0,0 +1,53 @@
+list(APPEND suites
+	"combined"
+	"pointer"
+)
+
+foreach(suite IN LISTS suites)
+	add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${suite}/clar.suite"
+		COMMAND "${Python_EXECUTABLE}"
+			"${CMAKE_SOURCE_DIR}/generate.py"
+			"${CMAKE_CURRENT_SOURCE_DIR}/${suite}.c"
+			--output "${CMAKE_CURRENT_BINARY_DIR}/${suite}"
+		DEPENDS ${suite}.c
+		WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+	)
+
+	add_executable(${suite}_suite)
+	set_target_properties(${suite}_suite PROPERTIES
+		C_STANDARD 90
+		C_STANDARD_REQUIRED ON
+		C_EXTENSIONS OFF
+	)
+
+	# MSVC generates all kinds of warnings. We may want to fix these in the future
+	# and then unconditionally treat warnings as errors.
+	if(NOT MSVC)
+		set_target_properties(${suite}_suite PROPERTIES
+			COMPILE_WARNING_AS_ERROR ON
+		)
+	endif()
+
+	target_sources(${suite}_suite PRIVATE
+		main.c
+		${suite}.c
+		"${CMAKE_CURRENT_BINARY_DIR}/${suite}/clar.suite"
+	)
+	target_compile_definitions(${suite}_suite PRIVATE
+		CLAR_FIXTURE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/resources/"
+		CLAR_SELFTEST
+	)
+	target_compile_options(${suite}_suite PRIVATE
+		$<IF:$<CXX_COMPILER_ID:MSVC>,/W4,-Wall>
+	)
+	target_include_directories(${suite}_suite PRIVATE
+		"${CMAKE_SOURCE_DIR}"
+		"${CMAKE_CURRENT_BINARY_DIR}/${suite}"
+	)
+	target_link_libraries(${suite}_suite clar)
+
+	add_test(NAME build_${suite}_suite
+		COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" --config "$<CONFIG>" --target selftest
+	)
+	set_tests_properties(build_${suite}_suite PROPERTIES FIXTURES_SETUP clar_test_fixture)
+endforeach()
diff --git a/t/unit-tests/clar/test/sample.c b/t/unit-tests/clar/test/suites/combined.c
similarity index 69%
rename from t/unit-tests/clar/test/sample.c
rename to t/unit-tests/clar/test/suites/combined.c
index faa1209..e8b41c9 100644
--- a/t/unit-tests/clar/test/sample.c
+++ b/t/unit-tests/clar/test/suites/combined.c
@@ -1,6 +1,7 @@
-#include "clar_test.h"
 #include <sys/stat.h>
 
+#include "clar.h"
+
 static int file_size(const char *filename)
 {
 	struct stat st;
@@ -10,19 +11,14 @@ static int file_size(const char *filename)
 	return -1;
 }
 
-void test_sample__initialize(void)
-{
-	global_test_counter++;
-}
-
-void test_sample__cleanup(void)
+void test_combined__cleanup(void)
 {
 	cl_fixture_cleanup("test");
 
 	cl_assert(file_size("test/file") == -1);
 }
 
-void test_sample__1(void)
+void test_combined__1(void)
 {
 	cl_assert(1);
 	cl_must_pass(0);  /* 0 == success */
@@ -30,7 +26,7 @@ void test_sample__1(void)
 	cl_must_pass(-1); /* demonstrate a failing call */
 }
 
-void test_sample__2(void)
+void test_combined__2(void)
 {
 	cl_fixture_sandbox("test");
 
@@ -39,7 +35,7 @@ void test_sample__2(void)
 	cl_assert(100 == 101);
 }
 
-void test_sample__strings(void)
+void test_combined__strings(void)
 {
 	const char *actual = "expected";
 	cl_assert_equal_s("expected", actual);
@@ -47,7 +43,7 @@ void test_sample__strings(void)
 	cl_assert_equal_s_("mismatched", actual, "this one fails");
 }
 
-void test_sample__strings_with_length(void)
+void test_combined__strings_with_length(void)
 {
 	const char *actual = "expected";
 	cl_assert_equal_strn("expected_", actual, 8);
@@ -56,29 +52,34 @@ void test_sample__strings_with_length(void)
 	cl_assert_equal_strn_("exactly", actual, 3, "this one fails");
 }
 
-void test_sample__int(void)
+void test_combined__int(void)
 {
 	int value = 100;
 	cl_assert_equal_i(100, value);
 	cl_assert_equal_i_(101, value, "extra note on failing test");
 }
 
-void test_sample__int_fmt(void)
+void test_combined__int_fmt(void)
 {
 	int value = 100;
 	cl_assert_equal_i_fmt(022, value, "%04o");
 }
 
-void test_sample__bool(void)
+void test_combined__bool(void)
 {
 	int value = 100;
 	cl_assert_equal_b(1, value);       /* test equality as booleans */
 	cl_assert_equal_b(0, value);
 }
 
-void test_sample__ptr(void)
+void test_combined__multiline_description(void)
 {
-	const char *actual = "expected";
-	cl_assert_equal_p(actual, actual); /* pointers to same object */
-	cl_assert_equal_p(&actual, actual);
+	cl_must_pass_(-1, "description line 1\ndescription line 2");
+}
+
+void test_combined__null_string(void)
+{
+	const char *actual = NULL;
+	cl_assert_equal_s(actual, actual);
+	cl_assert_equal_s_("expected", actual, "this one fails");
 }
diff --git a/t/unit-tests/clar/test/suites/main.c b/t/unit-tests/clar/test/suites/main.c
new file mode 100644
index 0000000..3ab581d
--- /dev/null
+++ b/t/unit-tests/clar/test/suites/main.c
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) Vicent Marti. All rights reserved.
+ *
+ * This file is part of clar, distributed under the ISC license.
+ * For full terms see the included COPYING file.
+ */
+
+#include "clar.h"
+
+/*
+ * Selftest main() for clar tests.
+ *
+ * You should write your own main routine for clar tests that does specific
+ * setup and teardown as necessary for your application.  The only required
+ * line is the call to `clar_test(argc, argv)`, which will execute the test
+ * suite.  If you want to check the return value of the test application,
+ * your main() should return the same value returned by clar_test().
+ */
+
+#ifdef _WIN32
+int __cdecl main(int argc, char *argv[])
+#else
+int main(int argc, char *argv[])
+#endif
+{
+	return clar_test(argc, argv);
+}
diff --git a/t/unit-tests/clar/test/suites/pointer.c b/t/unit-tests/clar/test/suites/pointer.c
new file mode 100644
index 0000000..20535b1
--- /dev/null
+++ b/t/unit-tests/clar/test/suites/pointer.c
@@ -0,0 +1,13 @@
+#include "clar.h"
+
+void test_pointer__equal(void)
+{
+	void *p1 = (void *)0x1;
+	cl_assert_equal_p(p1, p1);
+}
+
+void test_pointer__unequal(void)
+{
+	void *p1 = (void *)0x1, *p2 = (void *)0x2;
+	cl_assert_equal_p(p1, p2);
+}
diff --git a/t/unit-tests/clar/test/resources/test/file b/t/unit-tests/clar/test/suites/resources/test/file
similarity index 100%
rename from t/unit-tests/clar/test/resources/test/file
rename to t/unit-tests/clar/test/suites/resources/test/file
diff --git a/t/unit-tests/u-dir.c b/t/unit-tests/u-dir.c
new file mode 100644
index 0000000..2d0adaa
--- /dev/null
+++ b/t/unit-tests/u-dir.c
@@ -0,0 +1,47 @@
+#include "unit-test.h"
+#include "dir.h"
+
+#define TEST_WITHIN_DEPTH(path, depth, max_depth, expect) do { \
+		int actual = within_depth(path, strlen(path), \
+					  depth, max_depth); \
+		if (actual != expect) \
+			cl_failf("path '%s' with depth '%d' and max-depth '%d': expected %d, got %d", \
+				 path, depth, max_depth, expect, actual); \
+	} while (0)
+
+void test_dir__within_depth(void)
+{
+	/* depth = 0; max_depth = 0 */
+	TEST_WITHIN_DEPTH("",         0, 0, 1);
+	TEST_WITHIN_DEPTH("file",     0, 0, 1);
+	TEST_WITHIN_DEPTH("a",        0, 0, 1);
+	TEST_WITHIN_DEPTH("a/file",   0, 0, 0);
+	TEST_WITHIN_DEPTH("a/b",      0, 0, 0);
+	TEST_WITHIN_DEPTH("a/b/file", 0, 0, 0);
+
+	/* depth = 0; max_depth = 1 */
+	TEST_WITHIN_DEPTH("",         0, 1, 1);
+	TEST_WITHIN_DEPTH("file",     0, 1, 1);
+	TEST_WITHIN_DEPTH("a",        0, 1, 1);
+	TEST_WITHIN_DEPTH("a/file",   0, 1, 1);
+	TEST_WITHIN_DEPTH("a/b",      0, 1, 1);
+	TEST_WITHIN_DEPTH("a/b/file", 0, 1, 0);
+
+	/* depth = 1; max_depth = 1 */
+	TEST_WITHIN_DEPTH("",         1, 1, 1);
+	TEST_WITHIN_DEPTH("file",     1, 1, 1);
+	TEST_WITHIN_DEPTH("a",        1, 1, 1);
+	TEST_WITHIN_DEPTH("a/file",   1, 1, 0);
+	TEST_WITHIN_DEPTH("a/b",      1, 1, 0);
+	TEST_WITHIN_DEPTH("a/b/file", 1, 1, 0);
+
+	/* depth = 1; max_depth = 0 */
+	TEST_WITHIN_DEPTH("",         1, 0, 0);
+	TEST_WITHIN_DEPTH("file",     1, 0, 0);
+	TEST_WITHIN_DEPTH("a",        1, 0, 0);
+	TEST_WITHIN_DEPTH("a/file",   1, 0, 0);
+	TEST_WITHIN_DEPTH("a/b",      1, 0, 0);
+	TEST_WITHIN_DEPTH("a/b/file", 1, 0, 0);
+
+
+}
diff --git a/t/unit-tests/u-reftable-basics.c b/t/unit-tests/u-reftable-basics.c
index a047108..73566ed 100644
--- a/t/unit-tests/u-reftable-basics.c
+++ b/t/unit-tests/u-reftable-basics.c
@@ -9,6 +9,7 @@ license that can be found in the LICENSE file or at
 #include "unit-test.h"
 #include "lib-reftable.h"
 #include "reftable/basics.h"
+#include "reftable/reftable-error.h"
 
 struct integer_needle_lesseq_args {
 	int needle;
@@ -79,14 +80,18 @@ void test_reftable_basics__names_equal(void)
 void test_reftable_basics__parse_names(void)
 {
 	char in1[] = "line\n";
-	char in2[] = "a\nb\nc";
-	char **out = parse_names(in1, strlen(in1));
+	char in2[] = "a\nb\nc\n";
+	char **out = NULL;
+	int err = parse_names(in1, strlen(in1), &out);
+	cl_assert(err == 0);
 	cl_assert(out != NULL);
 	cl_assert_equal_s(out[0], "line");
 	cl_assert(!out[1]);
 	free_names(out);
 
-	out = parse_names(in2, strlen(in2));
+	out = NULL;
+	err = parse_names(in2, strlen(in2), &out);
+	cl_assert(err == 0);
 	cl_assert(out != NULL);
 	cl_assert_equal_s(out[0], "a");
 	cl_assert_equal_s(out[1], "b");
@@ -95,10 +100,21 @@ void test_reftable_basics__parse_names(void)
 	free_names(out);
 }
 
+void test_reftable_basics__parse_names_missing_newline(void)
+{
+	char in1[] = "line\nline2";
+	char **out = NULL;
+	int err = parse_names(in1, strlen(in1), &out);
+	cl_assert(err == REFTABLE_FORMAT_ERROR);
+	cl_assert(out == NULL);
+}
+
 void test_reftable_basics__parse_names_drop_empty_string(void)
 {
 	char in[] = "a\n\nb\n";
-	char **out = parse_names(in, strlen(in));
+	char **out = NULL;
+	int err = parse_names(in, strlen(in), &out);
+	cl_assert(err == 0);
 	cl_assert(out != NULL);
 	cl_assert_equal_s(out[0], "a");
 	/* simply '\n' should be dropped as empty string */
diff --git a/t/unit-tests/u-reftable-stack.c b/t/unit-tests/u-reftable-stack.c
index e4ea571..a8b9181 100644
--- a/t/unit-tests/u-reftable-stack.c
+++ b/t/unit-tests/u-reftable-stack.c
@@ -128,7 +128,7 @@ static void write_n_ref_tables(struct reftable_stack *st,
 		cl_reftable_set_hash(ref.value.val1, i, REFTABLE_HASH_SHA1);
 
 		cl_assert_equal_i(reftable_stack_add(st,
-						     &write_test_ref, &ref), 0);
+						     &write_test_ref, &ref, 0), 0);
 	}
 
 	st->opts.disable_auto_compact = disable_auto_compact;
@@ -171,7 +171,7 @@ void test_reftable_stack__add_one(void)
 	err = reftable_new_stack(&st, dir, &opts);
 	cl_assert(!err);
 
-	err = reftable_stack_add(st, write_test_ref, &ref);
+	err = reftable_stack_add(st, write_test_ref, &ref, 0);
 	cl_assert(!err);
 
 	err = reftable_stack_read_ref(st, ref.refname, &dest);
@@ -235,12 +235,12 @@ void test_reftable_stack__uptodate(void)
 	cl_assert_equal_i(reftable_new_stack(&st1, dir, &opts), 0);
 	cl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0);
 	cl_assert_equal_i(reftable_stack_add(st1, write_test_ref,
-					     &ref1), 0);
+					     &ref1, 0), 0);
 	cl_assert_equal_i(reftable_stack_add(st2, write_test_ref,
-					     &ref2), REFTABLE_OUTDATED_ERROR);
+					     &ref2, 0), REFTABLE_OUTDATED_ERROR);
 	cl_assert_equal_i(reftable_stack_reload(st2), 0);
 	cl_assert_equal_i(reftable_stack_add(st2, write_test_ref,
-					     &ref2), 0);
+					     &ref2, 0), 0);
 	reftable_stack_destroy(st1);
 	reftable_stack_destroy(st2);
 	clear_dir(dir);
@@ -406,7 +406,7 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void)
 
 	cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);
 	cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
-					     &ref), 0);
+					     &ref, 0), 0);
 	cl_assert_equal_i(st->merged->tables_len, 1);
 	cl_assert_equal_i(st->stats.attempts, 0);
 	cl_assert_equal_i(st->stats.failures, 0);
@@ -424,7 +424,7 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void)
 	write_file_buf(table_path.buf, "", 0);
 
 	ref.update_index = 2;
-	err = reftable_stack_add(st, write_test_ref, &ref);
+	err = reftable_stack_add(st, write_test_ref, &ref, 0);
 	cl_assert(!err);
 	cl_assert_equal_i(st->merged->tables_len, 2);
 	cl_assert_equal_i(st->stats.attempts, 1);
@@ -460,9 +460,9 @@ void test_reftable_stack__update_index_check(void)
 
 	cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);
 	cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
-					     &ref1), 0);
+					     &ref1, 0), 0);
 	cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
-					     &ref2), REFTABLE_API_ERROR);
+					     &ref2, 0), REFTABLE_API_ERROR);
 	reftable_stack_destroy(st);
 	clear_dir(dir);
 }
@@ -477,7 +477,7 @@ void test_reftable_stack__lock_failure(void)
 	cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);
 	for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--)
 		cl_assert_equal_i(reftable_stack_add(st, write_error,
-						     &i), i);
+						     &i, 0), i);
 
 	reftable_stack_destroy(st);
 	clear_dir(dir);
@@ -521,7 +521,7 @@ void test_reftable_stack__add(void)
 
 	for (i = 0; i < N; i++)
 		cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
-					     &refs[i]), 0);
+						     &refs[i], 0), 0);
 
 	for (i = 0; i < N; i++) {
 		struct write_log_arg arg = {
@@ -529,7 +529,7 @@ void test_reftable_stack__add(void)
 			.update_index = reftable_stack_next_update_index(st),
 		};
 		cl_assert_equal_i(reftable_stack_add(st, write_test_log,
-						     &arg), 0);
+						     &arg, 0), 0);
 	}
 
 	cl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0);
@@ -612,8 +612,8 @@ void test_reftable_stack__iterator(void)
 	}
 
 	for (i = 0; i < N; i++)
-		cl_assert_equal_i(reftable_stack_add(st,
-						     write_test_ref, &refs[i]), 0);
+		cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
+						     &refs[i], 0), 0);
 
 	for (i = 0; i < N; i++) {
 		struct write_log_arg arg = {
@@ -621,8 +621,8 @@ void test_reftable_stack__iterator(void)
 			.update_index = reftable_stack_next_update_index(st),
 		};
 
-		cl_assert_equal_i(reftable_stack_add(st,
-						     write_test_log, &arg), 0);
+		cl_assert_equal_i(reftable_stack_add(st, write_test_log,
+						     &arg, 0), 0);
 	}
 
 	reftable_stack_init_ref_iterator(st, &it);
@@ -697,11 +697,11 @@ void test_reftable_stack__log_normalize(void)
 
 	input.value.update.message = (char *) "one\ntwo";
 	cl_assert_equal_i(reftable_stack_add(st, write_test_log,
-					     &arg), REFTABLE_API_ERROR);
+					     &arg, 0), REFTABLE_API_ERROR);
 
 	input.value.update.message = (char *) "one";
 	cl_assert_equal_i(reftable_stack_add(st, write_test_log,
-					     &arg), 0);
+					     &arg, 0), 0);
 	cl_assert_equal_i(reftable_stack_read_log(st, input.refname,
 						  &dest), 0);
 	cl_assert_equal_s(dest.value.update.message, "one\n");
@@ -709,7 +709,7 @@ void test_reftable_stack__log_normalize(void)
 	input.value.update.message = (char *) "two\n";
 	arg.update_index = 2;
 	cl_assert_equal_i(reftable_stack_add(st, write_test_log,
-					     &arg), 0);
+					     &arg, 0), 0);
 	cl_assert_equal_i(reftable_stack_read_log(st, input.refname,
 						  &dest), 0);
 	cl_assert_equal_s(dest.value.update.message, "two\n");
@@ -759,15 +759,16 @@ void test_reftable_stack__tombstone(void)
 		}
 	}
 	for (i = 0; i < N; i++)
-		cl_assert_equal_i(reftable_stack_add(st, write_test_ref, &refs[i]), 0);
+		cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
+						     &refs[i], 0), 0);
 
 	for (i = 0; i < N; i++) {
 		struct write_log_arg arg = {
 			.log = &logs[i],
 			.update_index = reftable_stack_next_update_index(st),
 		};
-		cl_assert_equal_i(reftable_stack_add(st,
-						     write_test_log, &arg), 0);
+		cl_assert_equal_i(reftable_stack_add(st, write_test_log,
+						     &arg, 0), 0);
 	}
 
 	cl_assert_equal_i(reftable_stack_read_ref(st, "branch",
@@ -815,7 +816,7 @@ void test_reftable_stack__hash_id(void)
 
 	cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);
 	cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
-					     &ref), 0);
+					     &ref, 0), 0);
 
 	/* can't read it with the wrong hash ID. */
 	cl_assert_equal_i(reftable_new_stack(&st32, dir,
@@ -884,7 +885,7 @@ void test_reftable_stack__reflog_expire(void)
 			.update_index = reftable_stack_next_update_index(st),
 		};
 		cl_assert_equal_i(reftable_stack_add(st, write_test_log,
-						     &arg), 0);
+						     &arg, 0), 0);
 	}
 
 	cl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0);
@@ -924,7 +925,7 @@ void test_reftable_stack__empty_add(void)
 
 	cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);
 	cl_assert_equal_i(reftable_stack_add(st, write_nothing,
-					     NULL), 0);
+					     NULL, 0), 0);
 	cl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0);
 	clear_dir(dir);
 	reftable_stack_destroy(st);
@@ -963,7 +964,7 @@ void test_reftable_stack__auto_compaction(void)
 		};
 		snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
 
-		err = reftable_stack_add(st, write_test_ref, &ref);
+		err = reftable_stack_add(st, write_test_ref, &ref, 0);
 		cl_assert(!err);
 
 		err = reftable_stack_auto_compact(st);
@@ -999,7 +1000,7 @@ void test_reftable_stack__auto_compaction_factor(void)
 		};
 		xsnprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
 
-		err = reftable_stack_add(st, &write_test_ref, &ref);
+		err = reftable_stack_add(st, &write_test_ref, &ref, 0);
 		cl_assert(!err);
 
 		cl_assert(i < 5 || st->merged->tables_len < 5 * fastlogN(i, 5));
@@ -1078,8 +1079,8 @@ void test_reftable_stack__add_performs_auto_compaction(void)
 		snprintf(buf, sizeof(buf), "branch-%04"PRIuMAX, (uintmax_t)i);
 		ref.refname = buf;
 
-		cl_assert_equal_i(reftable_stack_add(st,
-						     write_test_ref, &ref), 0);
+		cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
+						     &ref, 0), 0);
 
 		/*
 		 * The stack length should grow continuously for all runs where
diff --git a/t/unit-tests/u-string-list.c b/t/unit-tests/u-string-list.c
index d4ba5f9..a2457d7 100644
--- a/t/unit-tests/u-string-list.c
+++ b/t/unit-tests/u-string-list.c
@@ -43,7 +43,7 @@ static void t_string_list_equal(struct string_list *list,
 				  expected_strings->items[i].string);
 }
 
-static void t_string_list_split(const char *data, int delim, int maxsplit, ...)
+static void t_string_list_split(const char *data, const char *delim, int maxsplit, ...)
 {
 	struct string_list expected_strings = STRING_LIST_INIT_DUP;
 	struct string_list list = STRING_LIST_INIT_DUP;
@@ -63,15 +63,94 @@ static void t_string_list_split(const char *data, int delim, int maxsplit, ...)
 	string_list_clear(&list, 0);
 }
 
+static void t_string_list_split_f(const char *data, const char *delim,
+				  int maxsplit, unsigned flags, ...)
+{
+	struct string_list expected_strings = STRING_LIST_INIT_DUP;
+	struct string_list list = STRING_LIST_INIT_DUP;
+	va_list ap;
+	int len;
+
+	va_start(ap, flags);
+	t_vcreate_string_list_dup(&expected_strings, 0, ap);
+	va_end(ap);
+
+	string_list_clear(&list, 0);
+	len = string_list_split_f(&list, data, delim, maxsplit, flags);
+	cl_assert_equal_i(len, expected_strings.nr);
+	t_string_list_equal(&list, &expected_strings);
+
+	string_list_clear(&expected_strings, 0);
+	string_list_clear(&list, 0);
+}
+
+void test_string_list__split_f(void)
+{
+	t_string_list_split_f("::foo:bar:baz:", ":", -1, 0,
+			      "", "", "foo", "bar", "baz", "", NULL);
+	t_string_list_split_f(" foo:bar : baz", ":", -1, STRING_LIST_SPLIT_TRIM,
+			      "foo", "bar", "baz", NULL);
+	t_string_list_split_f("  a  b c  ", " ", 1, STRING_LIST_SPLIT_TRIM,
+			      "a", "b c", NULL);
+	t_string_list_split_f("::foo::bar:baz:", ":", -1, STRING_LIST_SPLIT_NONEMPTY,
+			      "foo", "bar", "baz", NULL);
+	t_string_list_split_f("foo:baz", ":", -1, STRING_LIST_SPLIT_NONEMPTY,
+			      "foo", "baz", NULL);
+	t_string_list_split_f("foo :: : baz", ":", -1,
+			      STRING_LIST_SPLIT_NONEMPTY | STRING_LIST_SPLIT_TRIM,
+			      "foo", "baz", NULL);
+}
+
+static void t_string_list_split_in_place_f(const char *data_, const char *delim,
+					   int maxsplit, unsigned flags, ...)
+{
+	struct string_list expected_strings = STRING_LIST_INIT_DUP;
+	struct string_list list = STRING_LIST_INIT_NODUP;
+	char *data = xstrdup(data_);
+	va_list ap;
+	int len;
+
+	va_start(ap, flags);
+	t_vcreate_string_list_dup(&expected_strings, 0, ap);
+	va_end(ap);
+
+	string_list_clear(&list, 0);
+	len = string_list_split_in_place_f(&list, data, delim, maxsplit, flags);
+	cl_assert_equal_i(len, expected_strings.nr);
+	t_string_list_equal(&list, &expected_strings);
+
+	free(data);
+	string_list_clear(&expected_strings, 0);
+	string_list_clear(&list, 0);
+}
+
+void test_string_list__split_in_place_f(void)
+{
+	t_string_list_split_in_place_f("::foo:bar:baz:", ":", -1, 0,
+				       "", "", "foo", "bar", "baz", "", NULL);
+	t_string_list_split_in_place_f(" foo:bar : baz", ":", -1, STRING_LIST_SPLIT_TRIM,
+				       "foo", "bar", "baz", NULL);
+	t_string_list_split_in_place_f("  a  b c  ", " ", 1, STRING_LIST_SPLIT_TRIM,
+				       "a", "b c", NULL);
+	t_string_list_split_in_place_f("::foo::bar:baz:", ":", -1,
+				       STRING_LIST_SPLIT_NONEMPTY,
+				       "foo", "bar", "baz", NULL);
+	t_string_list_split_in_place_f("foo:baz", ":", -1, STRING_LIST_SPLIT_NONEMPTY,
+				       "foo", "baz", NULL);
+	t_string_list_split_in_place_f("foo :: : baz", ":", -1,
+				       STRING_LIST_SPLIT_NONEMPTY | STRING_LIST_SPLIT_TRIM,
+				       "foo", "baz", NULL);
+}
+
 void test_string_list__split(void)
 {
-	t_string_list_split("foo:bar:baz", ':', -1, "foo", "bar", "baz", NULL);
-	t_string_list_split("foo:bar:baz", ':', 0, "foo:bar:baz", NULL);
-	t_string_list_split("foo:bar:baz", ':', 1, "foo", "bar:baz", NULL);
-	t_string_list_split("foo:bar:baz", ':', 2, "foo", "bar", "baz", NULL);
-	t_string_list_split("foo:bar:", ':', -1, "foo", "bar", "", NULL);
-	t_string_list_split("", ':', -1, "", NULL);
-	t_string_list_split(":", ':', -1, "", "", NULL);
+	t_string_list_split("foo:bar:baz", ":", -1, "foo", "bar", "baz", NULL);
+	t_string_list_split("foo:bar:baz", ":", 0, "foo:bar:baz", NULL);
+	t_string_list_split("foo:bar:baz", ":", 1, "foo", "bar:baz", NULL);
+	t_string_list_split("foo:bar:baz", ":", 2, "foo", "bar", "baz", NULL);
+	t_string_list_split("foo:bar:", ":", -1, "foo", "bar", "", NULL);
+	t_string_list_split("", ":", -1, "", NULL);
+	t_string_list_split(":", ":", -1, "", "", NULL);
 }
 
 static void t_string_list_split_in_place(const char *data, const char *delim,
diff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c
index 22a99a0..bbcfeda 100644
--- a/trace2/tr2_cfg.c
+++ b/trace2/tr2_cfg.c
@@ -8,89 +8,65 @@
 #include "trace2/tr2_sysenv.h"
 #include "wildmatch.h"
 
-static struct strbuf **tr2_cfg_patterns;
-static int tr2_cfg_count_patterns;
+static struct string_list tr2_cfg_patterns = STRING_LIST_INIT_DUP;
 static int tr2_cfg_loaded;
 
-static struct strbuf **tr2_cfg_env_vars;
-static int tr2_cfg_env_vars_count;
+static struct string_list tr2_cfg_env_vars = STRING_LIST_INIT_DUP;
 static int tr2_cfg_env_vars_loaded;
 
 /*
  * Parse a string containing a comma-delimited list of config keys
- * or wildcard patterns into a list of strbufs.
+ * or wildcard patterns into a string list.
  */
-static int tr2_cfg_load_patterns(void)
+static size_t tr2_cfg_load_patterns(void)
 {
-	struct strbuf **s;
 	const char *envvar;
 
 	if (tr2_cfg_loaded)
-		return tr2_cfg_count_patterns;
+		return tr2_cfg_patterns.nr;
 	tr2_cfg_loaded = 1;
 
 	envvar = tr2_sysenv_get(TR2_SYSENV_CFG_PARAM);
 	if (!envvar || !*envvar)
-		return tr2_cfg_count_patterns;
+		return tr2_cfg_patterns.nr;
 
-	tr2_cfg_patterns = strbuf_split_buf(envvar, strlen(envvar), ',', -1);
-	for (s = tr2_cfg_patterns; *s; s++) {
-		struct strbuf *buf = *s;
-
-		if (buf->len && buf->buf[buf->len - 1] == ',')
-			strbuf_setlen(buf, buf->len - 1);
-		strbuf_trim_trailing_newline(*s);
-		strbuf_trim(*s);
-	}
-
-	tr2_cfg_count_patterns = s - tr2_cfg_patterns;
-	return tr2_cfg_count_patterns;
+	string_list_split_f(&tr2_cfg_patterns, envvar, ",", -1,
+			    STRING_LIST_SPLIT_TRIM);
+	return tr2_cfg_patterns.nr;
 }
 
 void tr2_cfg_free_patterns(void)
 {
-	if (tr2_cfg_patterns)
-		strbuf_list_free(tr2_cfg_patterns);
-	tr2_cfg_count_patterns = 0;
+	if (tr2_cfg_patterns.nr)
+		string_list_clear(&tr2_cfg_patterns, 0);
 	tr2_cfg_loaded = 0;
 }
 
 /*
  * Parse a string containing a comma-delimited list of environment variable
- * names into a list of strbufs.
+ * names into a string list.
  */
-static int tr2_load_env_vars(void)
+static size_t tr2_load_env_vars(void)
 {
-	struct strbuf **s;
 	const char *varlist;
 
 	if (tr2_cfg_env_vars_loaded)
-		return tr2_cfg_env_vars_count;
+		return tr2_cfg_env_vars.nr;
 	tr2_cfg_env_vars_loaded = 1;
 
 	varlist = tr2_sysenv_get(TR2_SYSENV_ENV_VARS);
 	if (!varlist || !*varlist)
-		return tr2_cfg_env_vars_count;
+		return tr2_cfg_env_vars.nr;
 
-	tr2_cfg_env_vars = strbuf_split_buf(varlist, strlen(varlist), ',', -1);
-	for (s = tr2_cfg_env_vars; *s; s++) {
-		struct strbuf *buf = *s;
-
-		if (buf->len && buf->buf[buf->len - 1] == ',')
-			strbuf_setlen(buf, buf->len - 1);
-		strbuf_trim_trailing_newline(*s);
-		strbuf_trim(*s);
-	}
-
-	tr2_cfg_env_vars_count = s - tr2_cfg_env_vars;
-	return tr2_cfg_env_vars_count;
+	string_list_split_f(&tr2_cfg_env_vars, varlist, ",", -1,
+			    STRING_LIST_SPLIT_TRIM);
+	return tr2_cfg_env_vars.nr;
 }
 
 void tr2_cfg_free_env_vars(void)
 {
-	if (tr2_cfg_env_vars)
-		strbuf_list_free(tr2_cfg_env_vars);
-	tr2_cfg_env_vars_count = 0;
+	if (tr2_cfg_env_vars.nr)
+		string_list_clear(&tr2_cfg_env_vars, 0);
 	tr2_cfg_env_vars_loaded = 0;
 }
 
@@ -105,12 +81,11 @@ struct tr2_cfg_data {
 static int tr2_cfg_cb(const char *key, const char *value,
 		      const struct config_context *ctx, void *d)
 {
-	struct strbuf **s;
+	struct string_list_item *item;
 	struct tr2_cfg_data *data = (struct tr2_cfg_data *)d;
 
-	for (s = tr2_cfg_patterns; *s; s++) {
-		struct strbuf *buf = *s;
-		int wm = wildmatch(buf->buf, key, WM_CASEFOLD);
+	for_each_string_list_item(item, &tr2_cfg_patterns) {
+		int wm = wildmatch(item->string, key, WM_CASEFOLD);
 		if (wm == WM_MATCH) {
 			trace2_def_param_fl(data->file, data->line, key, value,
 					    ctx->kvi);
@@ -132,17 +107,16 @@ void tr2_cfg_list_config_fl(const char *file, int line)
 void tr2_list_env_vars_fl(const char *file, int line)
 {
 	struct key_value_info kvi = KVI_INIT;
-	struct strbuf **s;
+	struct string_list_item *item;
 
 	kvi_from_param(&kvi);
 	if (tr2_load_env_vars() <= 0)
 		return;
 
-	for (s = tr2_cfg_env_vars; *s; s++) {
-		struct strbuf *buf = *s;
-		const char *val = getenv(buf->buf);
+	for_each_string_list_item(item, &tr2_cfg_env_vars) {
+		const char *val = getenv(item->string);
 		if (val && *val)
-			trace2_def_param_fl(file, line, buf->buf, val, &kvi);
+			trace2_def_param_fl(file, line, item->string, val, &kvi);
 	}
 }
 
diff --git a/transport-helper.c b/transport-helper.c
index 0789e5b..4d95d84 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -450,7 +450,7 @@ static int fetch_with_fetch(struct transport *transport,
 	}
 	strbuf_release(&buf);
 
-	reprepare_packed_git(the_repository);
+	odb_reprepare(the_repository->objects);
 	return 0;
 }
 
diff --git a/transport.c b/transport.c
index e305d6b..c7f06a7 100644
--- a/transport.c
+++ b/transport.c
@@ -30,7 +30,7 @@
 #include "color.h"
 #include "bundle-uri.h"
 
-static int transport_use_color = -1;
+static enum git_colorbool transport_use_color = GIT_COLOR_UNKNOWN;
 static char transport_colors[][COLOR_MAXLEN] = {
 	GIT_COLOR_RESET,
 	GIT_COLOR_RED		/* REJECTED */
@@ -1042,7 +1042,7 @@ static const struct string_list *protocol_allow_list(void)
 	if (enabled < 0) {
 		const char *v = getenv("GIT_ALLOW_PROTOCOL");
 		if (v) {
-			string_list_split(&allowed, v, ':', -1);
+			string_list_split(&allowed, v, ":", -1);
 			string_list_sort(&allowed);
 			enabled = 1;
 		} else {
diff --git a/tree-diff.c b/tree-diff.c
index e00fc2f..5988148 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -13,6 +13,7 @@
 #include "tree-walk.h"
 #include "environment.h"
 #include "repository.h"
+#include "dir.h"
 
 /*
  * Some mode bits are also used internally for computations.
@@ -48,6 +49,73 @@
 		free((x)); \
 } while(0)
 
+/* Returns true if and only if "dir" is a leading directory of "path" */
+static int is_dir_prefix(const char *path, const char *dir, int dirlen)
+{
+	return !strncmp(path, dir, dirlen) &&
+		(!path[dirlen] || path[dirlen] == '/');
+}
+
+static int check_recursion_depth(const struct strbuf *name,
+				 const struct pathspec *ps,
+				 int max_depth)
+{
+	int i;
+
+	if (!ps->nr)
+		return within_depth(name->buf, name->len, 1, max_depth);
+
+	/*
+	 * We look through the pathspecs in reverse-sorted order, because we
+	 * want to find the longest match first (e.g., "a/b" is better for
+	 * checking depth than "a/b/c").
+	 */
+	for (i = ps->nr - 1; i >= 0; i--) {
+		const struct pathspec_item *item = ps->items+i;
+
+		/*
+		 * If the name to match is longer than the pathspec, then we
+		 * are only interested if the pathspec matches and we are
+		 * within the allowed depth.
+		 */
+		if (name->len >= item->len) {
+			if (!is_dir_prefix(name->buf, item->match, item->len))
+				continue;
+			return within_depth(name->buf + item->len,
+					    name->len - item->len,
+					    1, max_depth);
+		}
+
+		/*
+		 * Otherwise, our name is shorter than the pathspec. We need to
+		 * check if it is a prefix of the pathspec; if so, we must
+		 * always recurse in order to process further (the resulting
+		 * paths we find might or might not match our pathspec, but we
+		 * cannot know until we recurse).
+		 */
+		if (is_dir_prefix(item->match, name->buf, name->len))
+			return 1;
+	}
+	return 0;
+}
+
+static int should_recurse(const struct strbuf *name, struct diff_options *opt)
+{
+	if (!opt->flags.recursive)
+		return 0;
+	if (!opt->max_depth_valid)
+		return 1;
+
+	/*
+	 * We catch this during diff_setup_done, but let's double-check
+	 * against any internal munging.
+	 */
+	if (opt->pathspec.has_wildcard)
+		BUG("wildcard pathspecs are incompatible with max-depth");
+
+	return check_recursion_depth(name, &opt->pathspec, opt->max_depth);
+}
+
 static void ll_diff_tree_paths(
 	struct combine_diff_path ***tail, const struct object_id *oid,
 	const struct object_id **parents_oid, int nparent,
@@ -170,9 +238,13 @@ static void emit_path(struct combine_diff_path ***tail,
 		mode = 0;
 	}
 
-	if (opt->flags.recursive && isdir) {
-		recurse = 1;
-		emitthis = opt->flags.tree_in_recursive;
+	if (isdir) {
+		strbuf_add(base, path, pathlen);
+		if (should_recurse(base, opt)) {
+			recurse = 1;
+			emitthis = opt->flags.tree_in_recursive;
+		}
+		strbuf_setlen(base, old_baselen);
 	}
 
 	if (emitthis) {
diff --git a/upload-pack.c b/upload-pack.c
index 4f26f6a..1e87ae9 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -476,20 +476,17 @@ static void create_pack_file(struct upload_pack_data *pack_data,
 
 static int do_got_oid(struct upload_pack_data *data, const struct object_id *oid)
 {
-	int we_knew_they_have = 0;
 	struct object *o = parse_object_with_flags(the_repository, oid,
 						   PARSE_OBJECT_SKIP_HASH_CHECK |
 						   PARSE_OBJECT_DISCARD_TREE);
 
 	if (!o)
 		die("oops (%s)", oid_to_hex(oid));
+
 	if (o->type == OBJ_COMMIT) {
 		struct commit_list *parents;
 		struct commit *commit = (struct commit *)o;
-		if (o->flags & THEY_HAVE)
-			we_knew_they_have = 1;
-		else
-			o->flags |= THEY_HAVE;
+
 		if (!data->oldest_have || (commit->date < data->oldest_have))
 			data->oldest_have = commit->date;
 		for (parents = commit->parents;
@@ -497,11 +494,13 @@ static int do_got_oid(struct upload_pack_data *data, const struct object_id *oid
 		     parents = parents->next)
 			parents->item->object.flags |= THEY_HAVE;
 	}
-	if (!we_knew_they_have) {
-		add_object_array(o, NULL, &data->have_obj);
-		return 1;
-	}
-	return 0;
+
+	if (o->flags & THEY_HAVE)
+		return 0;
+	o->flags |= THEY_HAVE;
+
+	add_object_array(o, NULL, &data->have_obj);
+	return 1;
 }
 
 static int got_oid(struct upload_pack_data *data,
@@ -914,13 +913,12 @@ static void deepen(struct upload_pack_data *data, int depth)
 }
 
 static void deepen_by_rev_list(struct upload_pack_data *data,
-			       int ac,
-			       const char **av)
+			       struct strvec *argv)
 {
 	struct commit_list *result;
 
 	disable_commit_graph(the_repository);
-	result = get_shallow_commits_by_rev_list(ac, av, SHALLOW, NOT_SHALLOW);
+	result = get_shallow_commits_by_rev_list(argv, SHALLOW, NOT_SHALLOW);
 	send_shallow(data, result);
 	free_commit_list(result);
 	send_unshallow(data);
@@ -956,7 +954,7 @@ static int send_shallow_list(struct upload_pack_data *data)
 			struct object *o = data->want_obj.objects[i].item;
 			strvec_push(&av, oid_to_hex(&o->oid));
 		}
-		deepen_by_rev_list(data, av.nr, av.v);
+		deepen_by_rev_list(data, &av);
 		strvec_clear(&av);
 		ret = 1;
 	} else {
@@ -1685,7 +1683,7 @@ static void process_args(struct packet_reader *request,
 			if (data->uri_protocols.nr)
 				send_err_and_die(data,
 						 "multiple packfile-uris lines forbidden");
-			string_list_split(&data->uri_protocols, p, ',', -1);
+			string_list_split(&data->uri_protocols, p, ",", -1);
 			continue;
 		}
 
diff --git a/usage.c b/usage.c
index 8191323..527edb1 100644
--- a/usage.c
+++ b/usage.c
@@ -7,6 +7,7 @@
 #include "git-compat-util.h"
 #include "gettext.h"
 #include "trace2.h"
+#include "strbuf.h"
 
 static void vfreportf(FILE *f, const char *prefix, const char *err, va_list params)
 {
@@ -192,7 +193,8 @@ static void show_usage_if_asked_helper(const char *err, ...)
 
 void show_usage_if_asked(int ac, const char **av, const char *err)
 {
-	if (ac == 2 && !strcmp(av[1], "-h"))
+	if (ac == 2 && (!strcmp(av[1], "-h") ||
+			!strcmp(av[1], "--help-all")))
 		show_usage_if_asked_helper(err);
 }
 
@@ -375,14 +377,32 @@ void bug_fl(const char *file, int line, const char *fmt, ...)
 	va_end(ap);
 }
 
-NORETURN void you_still_use_that(const char *command_name)
+
+NORETURN void you_still_use_that(const char *command_name, const char *hint)
 {
+	struct strbuf percent_encoded = STRBUF_INIT;
+	strbuf_add_percentencode(&percent_encoded,
+				 command_name,
+				 STRBUF_ENCODE_SLASH);
+
 	fprintf(stderr,
-		_("'%s' is nominated for removal.\n"
-		  "If you still use this command, please add an extra\n"
-		  "option, '--i-still-use-this', on the command line\n"
-		  "and let us know you still use it by sending an e-mail\n"
-		  "to <git@vger.kernel.org>.  Thanks.\n"),
-		command_name);
+		_("'%s' is nominated for removal.\n"), command_name);
+
+	if (hint)
+		fputs(hint, stderr);
+
+	fprintf(stderr,
+		_("If you still use this command, here's what you can do:\n"
+		  "\n"
+		  "- read https://git-scm.com/docs/BreakingChanges.html\n"
+		  "- check if anyone has discussed this on the mailing\n"
+		  "  list and if they came up with something that can\n"
+		  "  help you: https://lore.kernel.org/git/?q=%s\n"
+		  "- send an email to <git@vger.kernel.org> to let us\n"
+		  "  know that you still use this command and were unable\n"
+		  "  to determine a suitable replacement\n"
+		  "\n"),
+		percent_encoded.buf);
+	strbuf_release(&percent_encoded);
 	die(_("refusing to run without --i-still-use-this"));
 }
diff --git a/varint.c b/varint.c
index 409c497..03cd544 100644
--- a/varint.c
+++ b/varint.c
@@ -1,11 +1,11 @@
 #include "git-compat-util.h"
 #include "varint.h"
 
-uintmax_t decode_varint(const unsigned char **bufp)
+uint64_t decode_varint(const unsigned char **bufp)
 {
 	const unsigned char *buf = *bufp;
 	unsigned char c = *buf++;
-	uintmax_t val = c & 127;
+	uint64_t val = c & 127;
 	while (c & 128) {
 		val += 1;
 		if (!val || MSB(val, 7))
@@ -17,7 +17,7 @@ uintmax_t decode_varint(const unsigned char **bufp)
 	return val;
 }
 
-int encode_varint(uintmax_t value, unsigned char *buf)
+uint8_t encode_varint(uint64_t value, unsigned char *buf)
 {
 	unsigned char varint[16];
 	unsigned pos = sizeof(varint) - 1;
diff --git a/varint.h b/varint.h
index f78bb0c..eb40193 100644
--- a/varint.h
+++ b/varint.h
@@ -1,7 +1,7 @@
 #ifndef VARINT_H
 #define VARINT_H
 
-int encode_varint(uintmax_t, unsigned char *);
-uintmax_t decode_varint(const unsigned char **);
+uint8_t encode_varint(uint64_t, unsigned char *);
+uint64_t decode_varint(const unsigned char **);
 
 #endif /* VARINT_H */
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 454601a..8ffe6d3 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -148,7 +148,7 @@ void wt_status_prepare(struct repository *r, struct wt_status *s)
 	memcpy(s->color_palette, default_wt_status_colors,
 	       sizeof(default_wt_status_colors));
 	s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
-	s->use_color = -1;
+	s->use_color = GIT_COLOR_UNKNOWN;
 	s->relative_paths = 1;
 	s->branch = refs_resolve_refdup(get_main_ref_store(the_repository),
 					"HEAD", 0, NULL, NULL);
@@ -972,7 +972,8 @@ static void wt_longstatus_print_changed(struct wt_status *s)
 	wt_longstatus_print_trailer(s);
 }
 
-static int stash_count_refs(struct object_id *ooid UNUSED,
+static int stash_count_refs(const char *refname UNUSED,
+			    struct object_id *ooid UNUSED,
 			    struct object_id *noid UNUSED,
 			    const char *email UNUSED,
 			    timestamp_t timestamp UNUSED, int tz UNUSED,
@@ -1164,7 +1165,7 @@ static void wt_longstatus_print_verbose(struct wt_status *s)
 	 * before.
 	 */
 	if (s->fp != stdout) {
-		rev.diffopt.use_color = 0;
+		rev.diffopt.use_color = GIT_COLOR_NEVER;
 		wt_status_add_cut_line(s);
 	}
 	if (s->verbose > 1 && s->committable) {
@@ -1351,8 +1352,8 @@ static int split_commit_in_progress(struct wt_status *s)
  */
 static void abbrev_oid_in_line(struct strbuf *line)
 {
-	struct strbuf **split;
-	int i;
+	struct string_list split = STRING_LIST_INIT_DUP;
+	struct object_id oid;
 
 	if (starts_with(line->buf, "exec ") ||
 	    starts_with(line->buf, "x ") ||
@@ -1360,26 +1361,15 @@ static void abbrev_oid_in_line(struct strbuf *line)
 	    starts_with(line->buf, "l "))
 		return;
 
-	split = strbuf_split_max(line, ' ', 3);
-	if (split[0] && split[1]) {
-		struct object_id oid;
-
-		/*
-		 * strbuf_split_max left a space. Trim it and re-add
-		 * it after abbreviation.
-		 */
-		strbuf_trim(split[1]);
-		if (!repo_get_oid(the_repository, split[1]->buf, &oid)) {
-			strbuf_reset(split[1]);
-			strbuf_add_unique_abbrev(split[1], &oid,
-						 DEFAULT_ABBREV);
-			strbuf_addch(split[1], ' ');
-			strbuf_reset(line);
-			for (i = 0; split[i]; i++)
-				strbuf_addbuf(line, split[i]);
-		}
+	if ((2 <= string_list_split(&split, line->buf, " ", 2)) &&
+	    !repo_get_oid(the_repository, split.items[1].string, &oid)) {
+		strbuf_reset(line);
+		strbuf_addf(line, "%s ", split.items[0].string);
+		strbuf_add_unique_abbrev(line, &oid, DEFAULT_ABBREV);
+		for (size_t i = 2; i < split.nr; i++)
+			strbuf_addf(line, " %s", split.items[i].string);
 	}
-	strbuf_list_free(split);
+	string_list_clear(&split, 0);
 }
 
 static int read_rebase_todolist(const char *fname, struct string_list *lines)
@@ -1664,7 +1654,8 @@ struct grab_1st_switch_cbdata {
 	struct object_id noid;
 };
 
-static int grab_1st_switch(struct object_id *ooid UNUSED,
+static int grab_1st_switch(const char *refname UNUSED,
+			   struct object_id *ooid UNUSED,
 			   struct object_id *noid,
 			   const char *email UNUSED,
 			   timestamp_t timestamp UNUSED, int tz UNUSED,
@@ -2164,7 +2155,7 @@ static void wt_shortstatus_print(struct wt_status *s)
 
 static void wt_porcelain_print(struct wt_status *s)
 {
-	s->use_color = 0;
+	s->use_color = GIT_COLOR_NEVER;
 	s->relative_paths = 0;
 	s->prefix = NULL;
 	s->no_gettext = 1;
diff --git a/wt-status.h b/wt-status.h
index 4e377ce..e40a272 100644
--- a/wt-status.h
+++ b/wt-status.h
@@ -111,7 +111,7 @@ struct wt_status {
 	int amend;
 	enum commit_whence whence;
 	int nowarn;
-	int use_color;
+	enum git_colorbool use_color;
 	int no_gettext;
 	int display_comment_prefix;
 	int relative_paths;
diff --git a/xdiff-interface.h b/xdiff-interface.h
index 1ed430b..dfc55da 100644
--- a/xdiff-interface.h
+++ b/xdiff-interface.h
@@ -28,9 +28,9 @@
  * from an error internal to xdiff, xdiff itself will see that
  * non-zero return and translate it to -1.
  *
- * See "diff_grep" in diffcore-pickaxe.c for a trick to work around
- * this, i.e. using the "consume_callback_data" to note the desired
- * early return.
+ * See "diff_grep" in diffcore-pickaxe.c and "quick_consume" in diff.c
+ * for a trick to work around this, i.e. using the "consume_callback_data"
+ * to note the desired early return.
  */
 typedef int (*xdiff_emit_line_fn)(void *, char *, unsigned long);
 typedef void (*xdiff_emit_hunk_fn)(void *data,
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 444a108..447e66c 100644
--- a/xdiff/xutils.c
+++ b/xdiff/xutils.c
@@ -249,7 +249,7 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
 	return 1;
 }
 
-static unsigned long xdl_hash_record_with_whitespace(char const **data,
+unsigned long xdl_hash_record_with_whitespace(char const **data,
 		char const *top, long flags) {
 	unsigned long ha = 5381;
 	char const *ptr = *data;
@@ -294,19 +294,67 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data,
 	return ha;
 }
 
-unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
-	unsigned long ha = 5381;
+/*
+ * Compiler reassociation barrier: pretend to modify X and Y to disallow
+ * changing evaluation order with respect to following uses of X and Y.
+ */
+#ifdef __GNUC__
+#define REASSOC_FENCE(x, y) __asm__("" : "+r"(x), "+r"(y))
+#else
+#define REASSOC_FENCE(x, y)
+#endif
+
+unsigned long xdl_hash_record_verbatim(char const **data, char const *top) {
+	unsigned long ha = 5381, c0, c1;
 	char const *ptr = *data;
-
-	if (flags & XDF_WHITESPACE_FLAGS)
-		return xdl_hash_record_with_whitespace(data, top, flags);
-
+#if 0
+	/*
+	 * The baseline form of the optimized loop below. This is the djb2
+	 * hash (the above function uses a variant with XOR instead of ADD).
+	 */
 	for (; ptr < top && *ptr != '\n'; ptr++) {
 		ha += (ha << 5);
-		ha ^= (unsigned long) *ptr;
+		ha += (unsigned long) *ptr;
 	}
 	*data = ptr < top ? ptr + 1: ptr;
+#else
+	/* Process two characters per iteration. */
+	if (top - ptr >= 2) do {
+		if ((c0 = ptr[0]) == '\n') {
+			*data = ptr + 1;
+			return ha;
+		}
+		if ((c1 = ptr[1]) == '\n') {
+			*data = ptr + 2;
+			c0 += ha;
+			REASSOC_FENCE(c0, ha);
+			ha = ha * 32 + c0;
+			return ha;
+		}
+		/*
+		 * Combine characters C0 and C1 into the hash HA. We have
+		 * HA = (HA * 33 + C0) * 33 + C1, and we want to ensure
+		 * that dependency chain over HA is just one multiplication
+		 * and one addition, i.e. we want to evaluate this as
+		 * HA = HA * 33 * 33 + (C0 * 33 + C1), and likewise prefer
+		 * (C0 * 32 + (C0 + C1)) for the expression in parenthesis.
+		 */
+		ha *= 33 * 33;
+		c1 += c0;
+		REASSOC_FENCE(c1, c0);
+		c1 += c0 * 32;
+		REASSOC_FENCE(c1, ha);
+		ha += c1;
 
+		ptr += 2;
+	} while (ptr < top - 1);
+	*data = top;
+	if (ptr < top && (c0 = ptr[0]) != '\n') {
+		c0 += ha;
+		REASSOC_FENCE(c0, ha);
+		ha = ha * 32 + c0;
+	}
+#endif
 	return ha;
 }
 
@@ -416,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);
 
diff --git a/xdiff/xutils.h b/xdiff/xutils.h
index fd0bba9..13f6831 100644
--- a/xdiff/xutils.h
+++ b/xdiff/xutils.h
@@ -34,7 +34,15 @@ void *xdl_cha_alloc(chastore_t *cha);
 long xdl_guess_lines(mmfile_t *mf, long sample);
 int xdl_blankline(const char *line, long size, long flags);
 int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags);
-unsigned long xdl_hash_record(char const **data, char const *top, long flags);
+unsigned long xdl_hash_record_verbatim(char const **data, char const *top);
+unsigned long xdl_hash_record_with_whitespace(char const **data, char const *top, long flags);
+static inline unsigned long xdl_hash_record(char const **data, char const *top, long flags)
+{
+	if (flags & XDF_WHITESPACE_FLAGS)
+		return xdl_hash_record_with_whitespace(data, top, flags);
+	else
+		return xdl_hash_record_verbatim(data, top);
+}
 unsigned int xdl_hashbits(unsigned int size);
 int xdl_num_out(char *out, long val);
 int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,