Merge branch 'bb/unicode-width-table-18'

Unicode character width table has been updated to match
Unicode 18 announced on 2026-09-16.

* bb/unicode-width-table-18:
  unicode: update the width tables to Unicode 18
diff --git a/.gitignore b/.gitignore
index 4da58c6..0209bd1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -233,6 +233,7 @@
 /.vscode/
 /tags
 /TAGS
+/tools/precompiled.h.gch
 /cscope*
 /compile_commands.json
 /.cache/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index cd6fd4a..3f24835 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -133,8 +133,11 @@
   before_script:
     - *windows_before_script
     - ./ci/install-sdk.ps1 -directory "git-sdk"
+    - ./ci/install-dependencies.ps1 -Mingw
   script:
-    - git-sdk/usr/bin/bash.exe -l -c 'ci/make-test-artifacts.sh artifacts'
+    # The minimal SDK's profile resets PATH.
+    - git-sdk/usr/bin/bash.exe -l -c
+        'PATH=$PATH:/c/Rust/bin ci/make-test-artifacts.sh artifacts'
   artifacts:
     paths:
       - artifacts
diff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc
index 73bb939..c2cef3f 100644
--- a/Documentation/BreakingChanges.adoc
+++ b/Documentation/BreakingChanges.adoc
@@ -156,7 +156,7 @@
      "packed-refs" file.
 +
 Users that get immediate benefit from the "reftable" backend could continue to
-opt-in to the "reftable" format manually by setting the "init.defaultRefFormat"
+opt-in to the "reftable" format manually by setting the "init.defaultRefStorageFormat"
 config. But defaults matter, and we think that overall users will have a better
 experience with less platform-specific quirks when they use the new backend by
 default.
diff --git a/Documentation/RelNotes/2.98.0.adoc b/Documentation/RelNotes/2.98.0.adoc
new file mode 100644
index 0000000..9e25392
--- /dev/null
+++ b/Documentation/RelNotes/2.98.0.adoc
@@ -0,0 +1,295 @@
+Git v2.98 Release Notes
+=======================
+
+This is meant to be the antepenultimate release to prepare for the
+long anticipated Git 3.0 which has been vaporware for so long.
+
+
+UI, Workflows & Features
+------------------------
+
+ * The experimental 'git history' command has been taught a new 'squash'
+   subcommand to fold a range of commits into a single commit, with any
+   descendants replayed on top.
+
+ * A new hook 'report' is added to 'git receive-pack', which runs after
+   reference updates and allows the server to filter or modify the
+   packet-line status report sent back to the client.
+
+ * The terminology regarding reference storage formats has been unified
+   across command-line options, environment variables, configuration
+   variables, and source code, standardizing on the phrase "ref storage
+   format" (e.g., `--ref-storage-format`, `'GIT_REF_STORAGE_FORMAT'`).
+   Additionally, the `--ref-storage-format` option has been updated to
+   accept payloads in the form `<format>://<payload>`.
+
+ * The build-time knob 'USE_NSEC' for nanosecond stat precision has been
+   converted to a runtime configuration 'core.useNanosec', allowing
+   distributions to bundle one binary that adapts to filesystem
+   capabilities dynamically.
+
+ * The command-line completion script (in 'contrib/') has been taught
+   to complete the 'repair' subcommand for 'git worktree'.
+
+ * Various documentation files have been updated to use explicit
+   '<<TARGET,TARGET>>' AsciiDoc cross-references instead of plain text
+   references.  This improves rendering on the web and avoids broken
+   links when manual pages are included into other pages.
+
+ * The 'git var' command has been updated to output individual components
+   of the author and committer identities, take multiple keys on the
+   command-line, and support NUL-delimited output with the '-z' option.
+
+ * The 'git-contacts' program (in contrib/) has been taught to read
+   patches from the standard input stream when an explicit dash ('-') is
+   passed in place of a file.
+
+ * The @{upstream} notation had short-hand @{u} from the beginning,
+   and @{push} learned corresponding @{p} to match.
+
+ * The 'format-patch' command has been updated with options to
+   configure notes specifically for range-diff output, allowing them to
+   differ from the notes displayed on the patches themselves.
+
+ * Validation of the 'fetch.followRemoteHEAD' configuration variable
+   has been deferred to when it is actually used, preventing irrelevant
+   warnings during 'git fetch' invocations that do not consult it.
+
+ * "git status" now suggests to use 'git merge --continue', not 'git commit',
+   when done with conflicts during a 'git merge'.
+
+
+Performance, Internal Implementation, Development Support etc.
+--------------------------------------------------------------
+
+ * The setup of alternates has been deferred to object database
+   creation time during clone, which drops the unused ad-hoc alternate
+   writing API, simplifying the object database backend interface.
+
+ * Updates to Coccinelle semantic patches to correctly handle the
+   'FREE_AND_NULL()' macro and avoid generating broken transformations
+   for negated pointer checks.
+
+ * The 'Makefile' has been taught to precompile 'git-compat-util.h' to
+   speed up overall compilation, while excluding sources that do not
+   include the compatibility header.
+
+ * The can_all_from_reach() and can_all_from_reach_with_flag()
+   functions have been updated to accept a repository context,
+   preventing bugs where submodule merging incorrectly reads from the
+   superproject's commit-graph.
+
+ * The error reporting machinery in the WinANSI compatibility layer has
+   been simplified to pass the exact Windows error code and correctly
+   format arguments for fatal errors.
+
+ * The Debian 12 CI job has been updated to skip a half-auth clone test
+   that occasionally fails due to a bug in the packaged curl version
+   handling early HTTP/2 responses.
+
+ * The CI script has been updated to check for one-indexed slice numbers
+   when deciding to run unit tests on Windows, mirroring the behaviour in
+   the modern GitHub and GitLab workflows.
+
+ * The Windows GitLab CI job has been updated to provision and use the
+   GCC-targeting Rust toolchain for MinGW builds, fixing job failures
+   caused by incomplete Rust setup and missing linker support.
+
+ * The external merge driver in 'git merge' now uses the tempfile API
+   to create its temporary files.  This ensures that these temporaries
+   are reliably cleaned up even when the merge driver or its parent Git
+   process is terminated abruptly.
+
+ * The performance test for `git fetch` pack-dir rescans has been
+   updated to use the `--no-deref` option when cleaning up
+   remote-tracking branches.  This prevents `git update-ref` from
+   failing with a multiple updates error when both a symref and its
+   target are deleted in the same transaction.
+
+ * The build configurations for Meson have been optimized to avoid
+   recompiling HTTP sources multiple times and to utilize precompiled
+   headers for test-helpers and unit tests, reducing clean build times.
+   Additionally, the shell completion tests and GitLab CI MSVC runner
+   jobs have been fixed.
+
+ * The handling of index conflicts in 'git stash apply --index' has
+   been optimized.  Instead of relying on expensive external
+   subprocesses using 'git diff-tree' and 'git apply', the index is now
+   merged in-core using the merge-ort machinery, which fixes a
+   concurrent lock file issue with autostash.
+
+ * The parse-options API has been updated to support grouping
+   subcommands, and the 'git refs' command has been updated to use this
+   new feature to categorize its subcommands in its help output.
+
+ * Fix definition for the "--all" option to use symbolic constant
+   in the options[] array passed to parse_options API.
+
+ * The shell quoting of a lazy prereq script was quoted incorrectly and
+   allowed variables dereferenced at the wrong moment, breaking the
+   curl version detection.
+
+ * The requirement for a 'SYNOPSIS' section in section 7 manual pages
+   has been relaxed by updating the linter script, and dummy
+   'SYNOPSIS' sections from existing section 7 documentation have been
+   removed to improve the rendered output
+
+ * A test script (t0450) has been updated to use the test_path_is_file
+   and test_path_is_missing helper functions, which provides better
+   diagnostic output on failure.
+
+ * The routine that rewrites the packed-refs file has been optimized to
+   directly pass unmodified reference records verbatim to the output
+   via fwrite(3p), instead of redundantly re-formatting them with
+   fprintf(3p).
+
+ * The fetch.writeCommitGraph behavior has been optimized to
+   incrementally update the commit graph using only the newly fetched
+   commits as seeds.  This avoids a full reachability scan of all
+   references and drastically reduces the post-fetch commit-graph
+   update time for repositories with many references.
+
+ * The odb_source API has been refactored to make alternates an
+   implementation detail of the files backend instead of managing them
+   at the core object database layer.
+
+ * The test for verifying a signed tag with a missing key has been
+   updated to point GNUPGHOME to an unused directory rather than
+   deleting the active gpghome directory, working around a flaky rm
+   failure caused by the background gpg-agent observed on Alpine.
+
+
+Fixes since v2.56
+-----------------
+
+ * Updates to Coccinelle semantic patches to correctly handle the
+   'FREE_AND_NULL()' macro and avoid generating broken transformations
+   for negated pointer checks.
+   (merge 32814b69d0 jc/cocci-free-updates later to maint).
+
+ * The files backend has been updated to avoid unconditionally locking
+   the 'packed-refs' file when deleting a root ref (which are never
+   packed).
+   (merge d8e4585822 ak/refs-files-root-ref-lock later to maint).
+
+ * The server-side protocol v2 response order for 'wanted-refs' and
+   'shallow-info' has been swapped to match the client's expectation,
+   fixing a fetch failure when the server has 'uploadpack.allowRefInWant'
+   enabled and the client performs a shallow fetch.
+   (merge 191740edaa rr/upload-pack-swap-shallow-wanted-ref later to maint).
+
+ * Assorted fixes for code paths that are not careful with boundary and
+   error conditions.
+   (merge c88341b7e1 js/coverity-fixes later to maint).
+
+ * A bug in git diff --no-index that mishandled reverse (-R) output
+   when conflicts existed between a file and a directory has been
+   fixed.
+   (merge 9a13b3742b hd/diff-no-index-reverse-fix later to maint).
+
+ * 'git pull' has been taught to fail more gracefully instead of
+   segfaulting when lookup_commit_reference() fails on unparseable
+   objects.
+   (merge 38bdda17dd jk/pull-null-merge-head later to maint).
+
+ * The sequencer machinery (used by 'git rebase', 'git cherry-pick', and
+   'git revert') has been updated to defer automatic maintenance tasks
+   until the end of the operation, preventing nested 'git commit', 'git
+   merge', and 'exec' commands from triggering GC operations that could
+   contend for locks or delete open packs while the sequence is in
+   progress.
+   (merge eba1a1c5dd tb/rerere-lock-grace later to maint).
+
+ * When 'core.fsyncMethod' is set to 'batch', the ODB transaction
+   failed to properly flush large blob packfiles residing in the
+   temporary directory before migrating the directory's contents to the
+   main object store.  The execution sequence has been corrected by
+   performing the packfile flush before the temporary directory
+   migration, averting failure.
+   (merge ef7faa6350 jt/object-file-batch-fsync-fix later to maint).
+
+ * The default expiry periods for both reachable and unreachable reflog
+   entries have been corrected from a past regression that swapped
+   them.
+   (merge 60cf500551 ps/reflog-expiry-fix later to maint).
+
+ * The directory traversal logic has been updated to correctly evaluate
+   pathspecs for ignored directories containing nested repositories,
+   and has been fortified to gracefully handle null pathspecs during
+   checkout operations.
+   (merge ebc067f3cc rs/dir-skip-nested-repo-fix later to maint).
+
+ * The command-line parsing of revision options has been updated to
+   properly distinguish between malformed and unknown options, and to
+   correctly report the name of unknown options after they have been
+   moved during the option parsing process.
+   (merge da2926c8f5 jk/parse-revision-opt-fixes later to maint).
+
+ * The documentation for 'git-name-rev' has been updated to refer to
+   object IDs instead of SHA-1, to more accurately reflect that the
+   active hash algorithm is used.
+   (merge b2ab8fccc3 jk/name-rev-update-hash-descriptions later to maint).
+
+ * The HTTP transport has been updated to handle changes in recent curl
+   versions that strip credentials from the effective URL, preventing
+   unnecessary credential prompts during redirects.
+   (merge 76524c2604 jk/http-curl-strip-creds later to maint).
+
+ * The timestamp used for checking the reflog of a remote-tracking
+   branch during 'git push --force-if-includes' was left uninitialized
+   when the reflog was completely empty, which has been corrected.
+   (merge c84ed3a0ab as/push-force-if-includes-no-reflog later to maint).
+
+ * CI resource exhaustion during test runs on GitHub Actions has been
+   mitigated by switching a comparison of a huge output to use a binary
+   comparison, and by capping the parallel jobs on Linux to the number
+   of available CPUs.
+   (merge 4f956cf43a td/ci-large-test-resources later to maint).
+
+ * The `t5520` test script has been updated to disable reflog expiration.
+   This prevents test flakiness caused by the auto-maintenance
+   reflog-expire task, which would otherwise immediately expire the
+   test's reflogs due to them carrying hardcoded timestamps from 2005.
+   (merge a68cca9953 tb/t5520-reflog-expire later to maint).
+
+ * The internal representation of timezones in the reflog for the
+   reftable format has been fixed to match the specification, which
+   dictates an offset in minutes rather than the parsed "+HHMM" integer
+   representation.
+   (merge e14e2b6cf2 ps/reftable-reflog-timezone later to maint).
+
+ * The internal diff and merge machinery has been updated to use
+   standard size_t types rather than signed longs for buffer
+   allocations and offsets, preventing subtle overflow issues on LLP64
+   architectures, and plugging uninitialized memory leaks along error
+   paths.
+   (merge 5b0456361d jk/xdiff-size_t later to maint).
+
+ * The documentation for 'git remote set-head' has been updated to
+   clarify that it only modifies the local repository's remote-tracking
+   symbolic reference, and does not alter the target remote
+   repository's own 'HEAD'.
+   (merge c5f8f60719 mg/doc-remote-set-head later to maint).
+
+ * The commit map populated from the state branch in 'git
+   filter-branch' has been corrected to parse the map using the
+   original-to-rewritten order.
+   (merge 4811dabed7 gm/filter-branch-state-map-fix later to maint).
+
+ * The documentation for core.pager has been updated to provide
+   examples of overriding the LESS environment variable for custom
+   pagers by prefixing the pager command in the configuration.
+   (merge c7860da7db tz/doc-override-less-env later to maint).
+
+ * Closing a packfile did not evict the entries belonging to it from
+   the delta base cache, which could lead to data corruption when a
+   newly allocated packfile happened to reuse the same memory address.
+   The cache eviction logic has been updated to remove these stale
+   entries upon closing a pack
+   (merge 8d596ea8e7 ps/packfile-stale-delta-base-cache-fix later to maint).
+
+ * Other code cleanup, docfix, build fix, etc.
+   (merge e638de60bc kz/doc-add-rm-typofix later to maint).
+   (merge 778c73948a kz/doc-user-manual-typo later to maint).
+   (merge 42017ff979 kh/doc-trailers-cmd-examples later to maint).
+   (merge 49e646f1c9 dk/fix-shattered-io-link later to maint).
diff --git a/Documentation/config/advice.adoc b/Documentation/config/advice.adoc
index 81f80a9..3d91c90 100644
--- a/Documentation/config/advice.adoc
+++ b/Documentation/config/advice.adoc
@@ -59,6 +59,10 @@
 	forceDeleteBranch::
 		Shown when the user tries to delete a not fully merged
 		branch without the force option set.
+	historyUpdateRefs::
+		Shown when `git history squash` refuses because a local branch
+		cannot be rewritten as a descendant of the squashed commit, to
+		tell the user about `--update-refs=head`.
 	ignoredHook::
 		Shown when a hook is ignored because the hook is not
 		set as executable.
diff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc
index 340329e..bdc74d2 100644
--- a/Documentation/config/core.adoc
+++ b/Documentation/config/core.adoc
@@ -118,6 +118,13 @@
 	crawlers and some backup systems).
 	See linkgit:git-update-index[1]. True by default.
 
+core.useNanosec::
+	If true, use nanosecond precision for ctime and mtime
+	comparisons between the index and the working tree (if Git
+	was compiled to respect this option).
+	This is unsafe on some platforms;
+	see link:technical/racy-git.html[Racy Git]. False by default.
+
 core.splitIndex::
 	If true, the split-index feature of the index will be used.
 	See linkgit:git-update-index[1]. False by default.
@@ -614,9 +621,17 @@
 long lines. Similarly, setting `core.pager` to `less -+F` will
 deactivate the `F` option specified by the environment from the
 command-line, deactivating the "quit if one screen" behavior of
-`less`.  One can specifically activate some flags for particular
-commands: for example, setting `pager.blame` to `less -S` enables
-line truncation only for `git blame`.
+`less`.
++
+Another way to deactivate an option is prefixing `core.pager` with
+`LESS="RX"` to remove `-F` or `LESS=""` to override all options.
+This is useful if the `core.pager` command eventually runs `less` or
+a command which respects the `LESS` environment variable but lacks
+command line options to override `LESS` options.
++
+One can specifically activate some flags for particular commands: for
+example, setting `pager.blame` to `less -S` enables line truncation
+only for `git blame`.
 +
 Likewise, when the `LV` environment variable is unset, Git sets it
 to `-c`.  You can override this setting by exporting `LV` with
diff --git a/Documentation/config/feature.adoc b/Documentation/config/feature.adoc
index 924f5ff..e258770 100644
--- a/Documentation/config/feature.adoc
+++ b/Documentation/config/feature.adoc
@@ -25,7 +25,7 @@
 significantly smaller in the presence of certain filename collisions with Git's
 default name-hash.
 +
-* `init.defaultRefFormat=reftable` causes newly initialized repositories to use
+* `init.defaultRefStorageFormat=reftable` causes newly initialized repositories to use
 the reftable format for storing references. This new format solves issues with
 case-insensitive filesystems, compresses better and performs significantly
 better with many use cases. Refer to Documentation/technical/reftable.adoc for
diff --git a/Documentation/config/init.adoc b/Documentation/config/init.adoc
index 7b4abda..e45bd69 100644
--- a/Documentation/config/init.adoc
+++ b/Documentation/config/init.adoc
@@ -13,11 +13,13 @@
 	`--object-format=` in linkgit:git-init[1]. Both the command line option
 	and the `GIT_DEFAULT_HASH` environment variable take precedence over
 	this config.
-`init.defaultRefFormat`::
+`init.defaultRefStorageFormat`::
 	Allows overriding the default ref storage format for new repositories.
-	See `--ref-format=` in linkgit:git-init[1]. Both the command line
-	option and the `GIT_DEFAULT_REF_FORMAT` environment variable take
+	See `--ref-storage-format=` in linkgit:git-init[1]. Both the command line
+	option and the `GIT_DEFAULT_REF_STORAGE_FORMAT` environment variable take
 	precedence over this config.
+`init.defaultRefFormat`::
+	Deprecated alias of `init.defaultRefStorageFormat`.
 
 init.defaultSubmodulePathConfig::
 	A boolean that specifies if `git init` and `git clone` should
diff --git a/Documentation/config/uploadpack.adoc b/Documentation/config/uploadpack.adoc
index 0e1dda9..242a2c4 100644
--- a/Documentation/config/uploadpack.adoc
+++ b/Documentation/config/uploadpack.adoc
@@ -86,3 +86,60 @@
 	is intended for the benefit of load-balanced servers which may
 	not have the same view of what OIDs their refs point to due to
 	replication delay.
+
+uploadpack.lazyFetchTrusted::
+	A multi-valued configuration variable, each value of which
+	specifies the absolute local path of a repository that
+	`upload-pack` is allowed to lazily fetch missing objects for.
++
+A repository is identified by its git directory, after following any
+`.git` file and resolving symbolic links. That is the repository
+itself if it is bare, the `.git` directory of a repository that has a
+worktree, or the directory that a `.git` file points to, for example
+when the repository was created with `--separate-git-dir` or for a
+linked worktree (see linkgit:git-worktree[1]). So a non-bare
+repository served as `/srv/repo` usually has to be allowlisted as
+`/srv/repo/.git`. Giving a path with `/*` appended to it will trust
+all repositories under the named directory. To trust all served
+repositories, set `uploadpack.lazyFetchTrusted` to the string `*`.
++
+The value of this setting is interpolated, i.e., `~/<path>` expands to
+a path relative to the home directory and `%(prefix)/<path>` expands
+to a path relative to Git's (runtime) prefix.
++
+By default, `upload-pack` refuses to lazily fetch (see the description
+of the `GIT_NO_LAZY_FETCH` environment variable in
+linkgit:git-upload-pack[1]), because doing so would run `git fetch`,
+which may execute arbitrary commands specified in the configuration
+and hooks of the served repository. Listing a repository here tells
+`upload-pack` that it is trusted, so lazy fetching from the promisor
+remotes configured in it is allowed. This is similar to setting
+`GIT_NO_LAZY_FETCH` to `0`, but only for the matching repositories:
+unlike that environment variable, the trust is not inherited by child
+processes. So if a trusted repository lazily fetches from a promisor
+remote that is itself served by `upload-pack` on the same machine,
+for example through a local path or a `file://` URL, lazy fetching is
+allowed there only if that promisor remote is also listed here. An
+explicitly set `GIT_NO_LAZY_FETCH` takes precedence over this setting.
++
+Note that this allows lazy fetching from any promisor remote
+configured in the served repository, not only from the promisor
+remotes that the client accepted using the "promisor-remote" protocol
+v2 capability (see linkgit:gitprotocol-v2[5]). The served repository
+is trusted as a whole, including its configuration, so the promisor
+remotes it configures are trusted too. It is the server operator's
+responsibility to make sure that the promisor remotes of a trusted
+repository are also trustworthy. In particular, a trusted repository
+should not be configured as its own promisor remote, as `upload-pack`
+would then try to lazily fetch missing objects from the repository
+itself, which is pointless.
++
+As this is a multi-valued setting, you can add more than one
+repository via `git config (--global|--system) --add`. To reset the
+list of trusted repositories (e.g., to override any such repositories
+specified in the system config), add an `uploadpack.lazyFetchTrusted`
+entry with an empty value.
++
+Note that this configuration variable is only respected when it is
+specified in protected configuration (see <<SCOPES>>). This prevents
+untrusted repositories from tampering with this value.
diff --git a/Documentation/fetch-options.adoc b/Documentation/fetch-options.adoc
index 035f780..47dea1d 100644
--- a/Documentation/fetch-options.adoc
+++ b/Documentation/fetch-options.adoc
@@ -199,7 +199,7 @@
 	providing the tag refspec.
 ifndef::git-pull[]
 +
-See the PRUNING section below for more details.
+See the <<PRUNING,PRUNING>> section below for more details.
 
 `-P`::
 `--prune-tags`::
@@ -210,7 +210,7 @@
 	a shorthand for providing the explicit tag refspec along with
 	`--prune`, see the discussion about that in its documentation.
 +
-See the PRUNING section below for more details.
+See the <<PRUNING,PRUNING>> section below for more details.
 
 endif::git-pull[]
 
diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc
index 16b06e3..40f94b1 100644
--- a/Documentation/git-add.adoc
+++ b/Documentation/git-add.adoc
@@ -117,7 +117,7 @@
 apply, or even to modify the contents of lines to be staged. This can be
 quicker and more flexible than using the interactive hunk selector.
 However, it is easy to confuse oneself and create a patch that does not
-apply to the index. See EDITING PATCHES below.
+apply to the index. See <<EDITING_PATCHES,EDITING PATCHES>> below.
 
 `-u`::
 `--update`::
@@ -223,7 +223,7 @@
 
 `--`::
 	This option can be used to separate command-line options from
-	the list of files, (useful when filenames might be mistaken
+	the list of files (useful when filenames might be mistaken
 	for command-line options).
 
 
@@ -375,6 +375,7 @@
   `HEAD` and index).
 
 
+[[EDITING_PATCHES]]
 EDITING PATCHES
 ---------------
 
diff --git a/Documentation/git-bundle.adoc b/Documentation/git-bundle.adoc
index 03cd36f..cd722bd 100644
--- a/Documentation/git-bundle.adoc
+++ b/Documentation/git-bundle.adoc
@@ -43,7 +43,7 @@
 
 Like the packed archive format itself bundles can either be
 self-contained, or be created using exclusions.
-See the "OBJECT PREREQUISITES" section below.
+See the <<OBJECT_PREREQUISITES,"OBJECT PREREQUISITES">> section below.
 
 Bundles created using revision exclusions are "thin packs" created
 using the `--thin` option to linkgit:git-pack-objects[1], and
@@ -94,7 +94,8 @@
 
 <git-rev-list-args>::
 	A list of arguments, acceptable to 'git rev-parse' and
-	'git rev-list' (and containing a named ref, see SPECIFYING REFERENCES
+	'git rev-list' (and containing a named ref, see
+	<<SPECIFYING_REFERENCES,SPECIFYING REFERENCES>>
 	below), that specifies the specific objects and references
 	to transport.  For example, `master~10..master` causes the
 	current master reference to be packaged along with all objects
@@ -127,6 +128,7 @@
 	This flag makes the command not to report its progress
 	on the standard error stream.
 
+[[SPECIFYING_REFERENCES]]
 SPECIFYING REFERENCES
 ---------------------
 
@@ -169,6 +171,7 @@
 fatal: Refusing to create empty bundle.
 ----------------
 
+[[OBJECT_PREREQUISITES]]
 OBJECT PREREQUISITES
 --------------------
 
diff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc
index 514bfc0..c4ea252 100644
--- a/Documentation/git-cat-file.adoc
+++ b/Documentation/git-cat-file.adoc
@@ -115,7 +115,7 @@
 --
 	* When used with `--textconv` or `--filters`, the input lines
 	  must specify the path, separated by whitespace. See the section
-	  `BATCH OUTPUT` below for details.
+	  <<BATCH_OUTPUT,`BATCH OUTPUT`>> below for details.
 
 	* When used with `--use-mailmap`, for commit and tag objects, the
 	  contents part of the output shows the identities replaced using the
@@ -133,7 +133,7 @@
 --
 	* When used with `--textconv` or `--filters`, the input lines must
 	 specify the path, separated by whitespace. See the section
-	 `BATCH OUTPUT` below for details.
+	 <<BATCH_OUTPUT,`BATCH OUTPUT`>> below for details.
 
 	* When used with `--use-mailmap`, for commit and tag objects, the
 	  printed object information shows the size of the object as if the
@@ -149,7 +149,7 @@
 --
 	* When used with `--textconv` or `--filters`, the input lines must
 	  specify the path, separated by whitespace. See the section
-	  `BATCH OUTPUT` below for details.
+	  <<BATCH_OUTPUT,`BATCH OUTPUT`>> below for details.
 
 	* When used with `--use-mailmap`, for commit and tag objects, the
 	  `contents` command shows the identities replaced using the
@@ -295,6 +295,7 @@
 If `<type>` is specified, the raw (though uncompressed) contents of the `<object>`
 will be returned.
 
+[[BATCH_OUTPUT]]
 BATCH OUTPUT
 ------------
 
@@ -333,12 +334,12 @@
 
 `objectsize:disk`::
 	The size, in bytes, that the object takes up on disk. See the
-	note about on-disk sizes in the `CAVEATS` section below.
+	note about on-disk sizes in the <<CAVEATS,`CAVEATS`>> section below.
 
 `deltabase`::
 	If the object is stored as a delta on-disk, this expands to the
 	full hex representation of the delta base object name.
-	Otherwise, expands to the null OID (all zeroes). See `CAVEATS`
+	Otherwise, expands to the null OID (all zeroes). See <<CAVEATS,`CAVEATS`>>
 	below.
 
 `rest`::
@@ -447,6 +448,7 @@
 the output itself would contain a linefeed and is thus recommended for
 scripting purposes.
 
+[[CAVEATS]]
 CAVEATS
 -------
 
diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc
index a8b3b8c..2aefea0 100644
--- a/Documentation/git-checkout.adoc
+++ b/Documentation/git-checkout.adoc
@@ -27,7 +27,8 @@
 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.
+See <<ARGUMENT_DISAMBIGUATION,ARGUMENT DISAMBIGUATION>> below
+for how Git decides which one to do.
 
 `git checkout [<branch>]`::
 	Switch to _<branch>_. This sets the current branch to _<branch>_ and
@@ -68,7 +69,7 @@
 
 	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.
+	See the <<DETACHED_HEAD,"DETACHED HEAD">> section below for more.
 +
 Omitting _<branch>_ detaches `HEAD` at the tip of the current branch.
 
@@ -210,8 +211,8 @@
 	Rather than checking out a branch to work on it, check out a
 	commit for inspection and discardable experiments.
 	This is the default behavior of `git checkout <commit>` when
-	_<commit>_ is not a branch name.  See the "DETACHED HEAD" section
-	below for details.
+	_<commit>_ is not a branch name.  See the
+	<<DETACHED_HEAD,"DETACHED HEAD">> section below for details.
 
 `--orphan <new-branch>`::
 	Create a new unborn branch, named _<new-branch>_, started from
@@ -372,6 +373,7 @@
 +
 For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
 
+[[DETACHED_HEAD]]
 DETACHED HEAD
 -------------
 `HEAD` normally refers to a named branch (e.g. `master`). Meanwhile, each
@@ -504,6 +506,7 @@
 $ git log -g -2 HEAD
 ------------
 
+[[ARGUMENT_DISAMBIGUATION]]
 ARGUMENT DISAMBIGUATION
 -----------------------
 
diff --git a/Documentation/git-clone.adoc b/Documentation/git-clone.adoc
index b6e1f8a..07f7cf1 100644
--- a/Documentation/git-clone.adoc
+++ b/Documentation/git-clone.adoc
@@ -348,12 +348,18 @@
 	The result is Git repository can be separated from working
 	tree.
 
-`--ref-format=<ref-format>`::
+`--ref-storage-format=<format>`::
 
-Specify the given ref storage format for the repository. The valid values are:
+Specify the given ref storage _<format>_ for the repository. Backends that
+require additional configuration accept a payload in the form
+`<format>://<payload>`, for example a connection string identifying the
+database that shall store the references. The valid values are:
 +
 include::ref-storage-format.adoc[]
 
+`--ref-format=<format>`::
+Deprecated alias of `--ref-storage-format=<format>`.
+
 `-j<n>`::
 `--jobs=<n>`::
 	The number of submodules fetched at the same time.
diff --git a/Documentation/git-credential-cache.adoc b/Documentation/git-credential-cache.adoc
index 54fa7a2..2f63959 100644
--- a/Documentation/git-credential-cache.adoc
+++ b/Documentation/git-credential-cache.adoc
@@ -24,7 +24,7 @@
 
 You probably don't want to invoke this command directly; it is meant to
 be used as a credential helper by other parts of Git. See
-linkgit:gitcredentials[7] or `EXAMPLES` below.
+linkgit:gitcredentials[7] or <<EXAMPLES,`EXAMPLES`>> below.
 
 OPTIONS
 -------
@@ -54,6 +54,7 @@
 git credential-cache exit
 --------------------------------------
 
+[[EXAMPLES]]
 EXAMPLES
 --------
 
diff --git a/Documentation/git-credential-store.adoc b/Documentation/git-credential-store.adoc
index 71864a8..3f8a426 100644
--- a/Documentation/git-credential-store.adoc
+++ b/Documentation/git-credential-store.adoc
@@ -24,7 +24,7 @@
 
 You probably don't want to invoke this command directly; it is meant to
 be used as a credential helper by other parts of git. See
-linkgit:gitcredentials[7] or `EXAMPLES` below.
+linkgit:gitcredentials[7] or <<EXAMPLES,`EXAMPLES`>> below.
 
 OPTIONS
 -------
@@ -67,6 +67,7 @@
 
 When erasing credentials, matching credentials will be erased from all files.
 
+[[EXAMPLES]]
 EXAMPLES
 --------
 
diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc
index 719aeca..0c2ce38 100644
--- a/Documentation/git-fast-export.adoc
+++ b/Documentation/git-fast-export.adoc
@@ -148,12 +148,12 @@
 --anonymize::
 	Anonymize the contents of the repository while still retaining
 	the shape of the history and stored tree.  See the section on
-	`ANONYMIZING` below.
+	<<ANONYMIZING,`ANONYMIZING`>> below.
 
 --anonymize-map=<from>[:<to>]::
 	Convert token `<from>` to `<to>` in the anonymized output. If
 	`<to>` is omitted, map `<from>` to itself (i.e., do not
-	anonymize it). See the section on `ANONYMIZING` below.
+	anonymize it). See the section on <<ANONYMIZING,`ANONYMIZING`>> below.
 
 --reference-excluded-parents::
 	By default, running a command such as `git fast-export
@@ -219,6 +219,7 @@
 'refs/heads/master'.
 
 
+[[ANONYMIZING]]
 ANONYMIZING
 -----------
 
diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc
index fd165e1..c5e1cec 100644
--- a/Documentation/git-fast-import.adoc
+++ b/Documentation/git-fast-import.adoc
@@ -31,6 +31,7 @@
 the frontend program in use.
 
 
+[[OPTIONS]]
 OPTIONS
 -------
 
@@ -456,7 +457,7 @@
 	supports the specified feature, and aborts if it does not.
 
 `option`::
-	Specify any of the options listed under OPTIONS that do not
+	Specify any of the options listed under <<OPTIONS,OPTIONS>> that do not
 	change stream semantic to suit the frontend's needs. This
 	command is optional and is not needed to perform an import.
 
@@ -1242,7 +1243,7 @@
 force::
 	Act as though the corresponding command-line option with
 	a leading `--` was passed on the command line
-	(see OPTIONS, above).
+	(see <<OPTIONS,OPTIONS>>, above).
 
 import-marks::
 import-marks-if-exists::
@@ -1291,7 +1292,7 @@
 ....
 
 The `<option>` part of the command may contain any of the options
-listed in the OPTIONS section that do not change import semantics,
+listed in the <<OPTIONS,OPTIONS>> section that do not change import semantics,
 without the leading `--` and is treated in the same way.
 
 Option commands must be the first commands on the input (not counting
diff --git a/Documentation/git-fetch.adoc b/Documentation/git-fetch.adoc
index db03541..61fed79 100644
--- a/Documentation/git-fetch.adoc
+++ b/Documentation/git-fetch.adoc
@@ -103,6 +103,7 @@
 overridden by giving the `--refmap=<refspec>` parameter(s) on the
 command line.
 
+[[PRUNING]]
 PRUNING
 -------
 
diff --git a/Documentation/git-filter-branch.adoc b/Documentation/git-filter-branch.adoc
index 5a4f853..80a55b3 100644
--- a/Documentation/git-filter-branch.adoc
+++ b/Documentation/git-filter-branch.adoc
@@ -125,7 +125,7 @@
 	This is the filter for rewriting the index.  It is similar to the
 	tree filter but does not check out the tree, which makes it much
 	faster.  Frequently used with `git rm --cached
-	--ignore-unmatch ...`, see EXAMPLES below.  For hairy
+	--ignore-unmatch ...`, see <<EXAMPLES,EXAMPLES>> below.  For hairy
 	cases, see linkgit:git-update-index[1].
 
 --parent-filter <command>::
@@ -243,6 +243,7 @@
 any other non-zero value.
 
 
+[[EXAMPLES]]
 EXAMPLES
 --------
 
diff --git a/Documentation/git-for-each-ref.adoc b/Documentation/git-for-each-ref.adoc
index c02cb7f..a7b31e8 100644
--- a/Documentation/git-for-each-ref.adoc
+++ b/Documentation/git-for-each-ref.adoc
@@ -64,7 +64,8 @@
 `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.
+	disk. See the note about on-disk sizes in the
+	<<CAVEATS,'CAVEATS'>> section below.
 `objectname`::
 	The object name (aka SHA-1).
 	For a non-ambiguous abbreviation of the object name append `:short`.
@@ -448,6 +449,7 @@
 git for-each-ref --format="%(refname)%(if)%(authorname)%(then) Authored by: %(authorname)%(end)"
 ------------
 
+[[CAVEATS]]
 CAVEATS
 -------
 
diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc
index 191f64b..c7dc736 100644
--- a/Documentation/git-format-patch.adoc
+++ b/Documentation/git-format-patch.adoc
@@ -378,6 +378,17 @@
 topic and the tool should find more correspondence between the two
 sets of patches.
 
+`--range-diff-notes=<ref>`::
+`--no-range-diff-notes`::
+	Used with `--range-diff`, tweak what notes to display in the
+	range diff.
++
+The default behavior is to display the same notes in the range diff as
+on the patches; see `--notes`. Use `--range-diff-notes=<ref>` to use
+_<ref>_ for the range diff instead. This option can be given multiple
+times to show notes from multiple refs. Use `--no-range-diff-notes` to
+disable notes in the range diff.
+
 `--notes[=<ref>]`::
 `--no-notes`::
 	Append the notes (see linkgit:git-notes[1]) for the commit
@@ -430,7 +441,8 @@
 `--no-base`::
 `--base[=<commit>]`::
 	Record the base tree information to identify the state the
-	patch series applies to.  See the BASE TREE INFORMATION section
+	patch series applies to.  See the
+	<<BASE_TREE_INFORMATION,BASE TREE INFORMATION>> section
 	below for details. If _<commit>_ is `auto`, a base commit is
 	automatically chosen. The `--no-base` option overrides a
 	`format.useAutoBase` configuration.
@@ -702,6 +714,7 @@
 5. Back in the compose window: add whatever other text you wish to the
    message, complete the addressing and subject fields, and press send.
 
+[[BASE_TREE_INFORMATION]]
 BASE TREE INFORMATION
 ---------------------
 
diff --git a/Documentation/git-gc.adoc b/Documentation/git-gc.adoc
index 6fed646..5788a43 100644
--- a/Documentation/git-gc.adoc
+++ b/Documentation/git-gc.adoc
@@ -39,14 +39,15 @@
 	space utilization and performance.  This option will cause
 	'git gc' to more aggressively optimize the repository at the expense
 	of taking much more time.  The effects of this optimization are
-	mostly persistent. See the "AGGRESSIVE" section below for details.
+	mostly persistent. See the <<AGGRESSIVE,"AGGRESSIVE">>
+	section below for details.
 
 --auto::
 	With this option, 'git gc' checks whether any housekeeping is
 	required; if not, it exits without performing any work.
 +
-See the `gc.auto` option in the "CONFIGURATION" section below for how
-this heuristic works.
+See the `gc.auto` option in the <<CONFIGURATION,"CONFIGURATION">>
+section below for how this heuristic works.
 +
 Once housekeeping is triggered by exceeding the limits of
 configuration options such as `gc.auto` and `gc.autoPackLimit`, all
@@ -83,7 +84,7 @@
 	overridable by the config variable `gc.pruneExpire`).
 	--prune=now prunes loose objects regardless of their age and
 	increases the risk of corruption if another process is writing to
-	the repository concurrently; see "NOTES" below. --prune is on by
+	the repository concurrently; see <<NOTES,"NOTES">> below. --prune is on by
 	default.
 
 --no-prune::
@@ -102,6 +103,7 @@
 	a single pack. When this option is used, `gc.bigPackThreshold`
 	is ignored.
 
+[[AGGRESSIVE]]
 AGGRESSIVE
 ----------
 
@@ -128,6 +130,7 @@
 be worth it. Not using this at all is the right trade-off for most
 users and their repositories.
 
+[[CONFIGURATION]]
 CONFIGURATION
 -------------
 
@@ -135,6 +138,7 @@
 
 include::config/gc.adoc[]
 
+[[NOTES]]
 NOTES
 -----
 
diff --git a/Documentation/git-grep.adoc b/Documentation/git-grep.adoc
index 19b3ade..dd4a7cc 100644
--- a/Documentation/git-grep.adoc
+++ b/Documentation/git-grep.adoc
@@ -58,7 +58,7 @@
 	in linkgit:gitglossary[7] for more information.
 +
 This option cannot be used together with `--cached` or `--untracked`.
-See also `grep.fallbackToNoIndex` in 'CONFIGURATION' below.
+See also `grep.fallbackToNoIndex` in <<CONFIGURATION,'CONFIGURATION'>> below.
 
 `--no-exclude-standard`::
 	Also search in ignored files by not honoring the `.gitignore`
@@ -256,8 +256,9 @@
 	a non-zero status.
 
 `--threads <num>`::
-	Number of `grep` worker threads to use.  See `NOTES ON THREADS`
-	and `grep.threads` in 'CONFIGURATION' for more information.
+	Number of `grep` worker threads to use. See
+	<<NOTES_ON_THREADS,`NOTES ON THREADS`>> and `grep.threads` in
+	<<CONFIGURATION,'CONFIGURATION'>> for more information.
 
 `-f <file>`::
 	Read patterns from _<file>_, one per line.
@@ -337,6 +338,7 @@
 `git grep solution -- :^Documentation`::
 	Looks for `solution`, excluding files in `Documentation`.
 
+[[NOTES_ON_THREADS]]
 NOTES ON THREADS
 ----------------
 
@@ -348,6 +350,7 @@
 is given and there are too many text conversions.  Thus, if low performance is
 experienced in this case, it might be desirable to use `--threads=1`.
 
+[[CONFIGURATION]]
 CONFIGURATION
 -------------
 
diff --git a/Documentation/git-history.adoc b/Documentation/git-history.adoc
index 28b477c..2e2e31f 100644
--- a/Documentation/git-history.adoc
+++ b/Documentation/git-history.adoc
@@ -12,6 +12,7 @@
 git history fixup <commit> [--dry-run] [--update-refs=(branches|head)] [--reedit-message] [--empty=(drop|keep|abort)]
 git history reword <commit> [--dry-run] [--update-refs=(branches|head)]
 git history split <commit> [--dry-run] [--update-refs=(branches|head)] [--] [<pathspec>...]
+git history squash [--dry-run] [--update-refs=(branches|head)] [--[no-]edit] <revision-range>
 
 DESCRIPTION
 -----------
@@ -43,8 +44,11 @@
 LIMITATIONS
 -----------
 
-This command does not (yet) work with histories that contain merges. You
-should use linkgit:git-rebase[1] with the `--rebase-merges` flag instead.
+This command does not (yet) replay merge commits onto the rewritten
+history: if a commit that would be replayed is a merge, the operation is
+rejected, and you should use linkgit:git-rebase[1] with the
+`--rebase-merges` flag instead. The `squash` subcommand can still fold merges
+that lie inside the selected range, subject to the restrictions below.
 
 Furthermore, the command does not support operations that can result in merge
 conflicts. This limitation is by design as history rewrites are not intended to
@@ -113,6 +117,51 @@
 It is invalid to select either all or no hunks, as that would lead to
 one of the commits becoming empty.
 
+`squash <revision-range>`::
+	Fold all commits in _<revision-range>_ into the oldest commit of that
+	range. The resulting commit keeps the oldest commit's authorship and
+	takes the tree of the range's newest commit, so the whole range
+	collapses into a single commit. Commits above the range are replayed
+	on top of the result.
++
+The range is given in the usual `<base>..<tip>` form, where _<base>_ is
+the commit just below the oldest commit to squash. For example, `git
+history squash HEAD~3..HEAD` folds the three most recent commits into
+one, and `git history squash HEAD~5..HEAD~2` squashes an interior range
+while leaving the two newest commits in place. Several revisions may be
+given, for example `HEAD~3..HEAD ^topic` to additionally exclude what is
+already on `topic`. Rev-list options may also be given, but any that would
+change how the range is walked are overridden with a warning.
++
+An editor opens pre-filled with the messages of all the folded commits so you
+can combine them. With `--no-edit`, the oldest commit's message is preserved
+instead, except that an `amend!` commit targeting it replaces its message.
++
+The selected commits must form a connected graph with a single tip and must
+not include a root commit. Every parent of a commit after the oldest one must
+either be selected or also be a parent of the oldest commit. When the oldest
+commit is a merge, all of its parents are preserved in the squashed commit.
++
+A `fixup!`, `squash!`, or `amend!` commit is refused unless the commit it
+targets is also in the range, so the fold does not silently absorb a
+marker meant for a commit outside it. As an exception, a range made up entirely
+of markers for one target is combined into a single commit. With `--no-edit`,
+the last `amend!` message is used if there is one; a `squash!` or `amend!` is
+otherwise refused if folding it would discard its message.
++
+The editor template mirrors `git rebase -i --autosquash`: each `fixup!`,
+`squash!`, or `amend!` is grouped under the commit it targets rather than
+shown in commit order. A `fixup!` message is dropped (commented out in full),
+a `squash!` keeps its body with only the marker subject commented, and an
+`amend!` replaces its target's message, unless a `squash!` folded into that
+target first, in which case it keeps its body like a `squash!`.
++
+A local branch descended from a selected commit but not from the range tip
+cannot be rewritten as a descendant of the result, so with the default
+`--update-refs=branches` the command refuses. Rerun with `--update-refs=head`
+to rewrite only the current branch and leave such branches unchanged. Tags
+and remote-tracking refs are always left unchanged.
+
 OPTIONS
 -------
 
@@ -122,6 +171,12 @@
 	objects will be written into the repository, so applying these printed
 	ref updates is generally safe.
 
+`--edit`::
+`--no-edit`::
+	For `squash`, open an editor to combine the messages of the folded commits.
+	This is the default; use `--no-edit` to keep the selected message without
+	opening an editor.
+
 `--reedit-message`::
 	Open an editor to modify the target commit's message.
 
diff --git a/Documentation/git-http-backend.adoc b/Documentation/git-http-backend.adoc
index 1dea426..5fabc85 100644
--- a/Documentation/git-http-backend.adoc
+++ b/Documentation/git-http-backend.adoc
@@ -18,7 +18,7 @@
 and the backwards-compatible dumb HTTP protocol, as well as clients
 pushing using the smart HTTP protocol. It also supports Git's
 more-efficient "v2" protocol if properly configured; see the
-discussion of `GIT_PROTOCOL` in the ENVIRONMENT section below.
+discussion of `GIT_PROTOCOL` in the <<ENVIRONMENT,ENVIRONMENT>> section below.
 
 It verifies that the directory has the magic file
 "git-daemon-export-ok", and it will refuse to export any Git directory
@@ -69,6 +69,7 @@
 set, 'git http-backend' reads PATH_TRANSLATED, which is also set
 automatically by the web server.
 
+[[EXAMPLES]]
 EXAMPLES
 --------
 All of the following examples map `http://$hostname/git/foo/bar.git`
@@ -257,6 +258,7 @@
 ----------------------------------------------------------------
 
 
+[[ENVIRONMENT]]
 ENVIRONMENT
 -----------
 'git http-backend' relies upon the `CGI` environment variables set
@@ -290,7 +292,7 @@
 automatically copy that to `GIT_PROTOCOL`. However, some webservers may
 be more selective about which headers they'll pass, in which case they
 need to be configured explicitly (see the mention of `Git-Protocol` in
-the Apache config from the earlier EXAMPLES section).
+the Apache config from the earlier <<EXAMPLES,EXAMPLES>> section).
 
 The backend process sets GIT_COMMITTER_NAME to '$REMOTE_USER' and
 GIT_COMMITTER_EMAIL to '$\{REMOTE_USER}@http.$\{REMOTE_ADDR\}',
diff --git a/Documentation/git-init.adoc b/Documentation/git-init.adoc
index bab99b9..b02af22 100644
--- a/Documentation/git-init.adoc
+++ b/Documentation/git-init.adoc
@@ -11,7 +11,7 @@
 [synopsis]
 git init [-q | --quiet] [--bare] [--template=<template-directory>]
 	 [--separate-git-dir <git-dir>] [--object-format=<format>]
-	 [--ref-format=<format>]
+	 [--ref-storage-format=<format>]
 	 [-b <branch-name> | --initial-branch=<branch-name>]
 	 [--shared[=<permissions>]] [<directory>]
 
@@ -57,11 +57,17 @@
 +
 include::object-format-disclaimer.adoc[]
 
-`--ref-format=<format>`::
-Specify the given ref storage _<format>_ for the repository. The valid values are:
+`--ref-storage-format=<format>`::
+Specify the given ref storage _<format>_ for the repository. Backends that
+require additional configuration accept a payload in the form
+`<format>://<payload>`, for example a connection string identifying the
+database that shall store the references. The valid values are:
 +
 include::ref-storage-format.adoc[]
 
+`--ref-format=<format>`::
+Deprecated alias of `--ref-storage-format=<format>`.
+
 `--template=<template-directory>`::
 Specify the directory from which templates will be used.  (See the "TEMPLATE
 DIRECTORY" section below.)
diff --git a/Documentation/git-interpret-trailers.adoc b/Documentation/git-interpret-trailers.adoc
index 903d598..947d504 100644
--- a/Documentation/git-interpret-trailers.adoc
+++ b/Documentation/git-interpret-trailers.adoc
@@ -329,9 +329,8 @@
 Fix #42
 ------------
 
-* Configure a `help` trailer with a cmd use a script `glog-find-author`
-  which search specified author identity from git log in git repository
-  and show how it works:
+* Configure a `help` trailer with a command that searches for an author
+  identity and show how it works:
 +
 ------------
 $ cat ~/bin/glog-find-author
@@ -353,9 +352,8 @@
 Helped-by: Christian Couder <christian.couder@gmail.com>
 ------------
 
-* Configure a `ref` trailer with a cmd use a script `glog-grep`
-  to grep last relevant commit from git log in the git repository
-  and show how it works:
+* Configure a `ref` trailer with a command that searches for the last
+  relevant commit and show how it works:
 +
 ------------
 $ cat ~/bin/glog-grep
diff --git a/Documentation/git-ls-files.adoc b/Documentation/git-ls-files.adoc
index 2b17538..14ebad8 100644
--- a/Documentation/git-ls-files.adoc
+++ b/Documentation/git-ls-files.adoc
@@ -98,7 +98,7 @@
 
 -z::
 	\0 line termination on output and do not quote filenames.
-	See OUTPUT below for more information.
+	See <<OUTPUT,OUTPUT>> below for more information.
 
 --deduplicate::
 	When only filenames are shown, suppress duplicates that may
@@ -110,8 +110,8 @@
 -x <pattern>::
 --exclude=<pattern>::
 	Skip untracked files matching pattern.
-	Note that pattern is a shell wildcard pattern. See EXCLUDE PATTERNS
-	below for more information.
+	Note that pattern is a shell wildcard pattern.
+	See <<EXCLUDE_PATTERNS,EXCLUDE PATTERNS>> below for more information.
 
 -X <file>::
 --exclude-from=<file>::
@@ -231,6 +231,7 @@
 	Files to show. If no files are given all files which match the other
 	specified criteria are shown.
 
+[[OUTPUT]]
 OUTPUT
 ------
 'git ls-files' just outputs the filenames unless `--stage` is specified in
@@ -292,6 +293,7 @@
 path::
 	The pathname of the file which is recorded in the index.
 
+[[EXCLUDE_PATTERNS]]
 EXCLUDE PATTERNS
 ----------------
 
diff --git a/Documentation/git-ls-tree.adoc b/Documentation/git-ls-tree.adoc
index 6572095..29bde93 100644
--- a/Documentation/git-ls-tree.adoc
+++ b/Documentation/git-ls-tree.adoc
@@ -54,7 +54,7 @@
 
 -z::
 	\0 line termination on output and do not quote filenames.
-	See OUTPUT FORMAT below for more information.
+	See <<OUTPUT_FORMAT,OUTPUT FORMAT>> below for more information.
 
 --name-only::
 --name-status::
@@ -99,6 +99,7 @@
 	implicitly uses the root level of the tree as the sole path argument.
 
 
+[[OUTPUT_FORMAT]]
 Output Format
 -------------
 
diff --git a/Documentation/git-maintenance.adoc b/Documentation/git-maintenance.adoc
index bda616f..85f2080 100644
--- a/Documentation/git-maintenance.adoc
+++ b/Documentation/git-maintenance.adoc
@@ -95,6 +95,7 @@
 `maintenance.<task>.enabled` config options are true. By default, only
 `maintenance.gc.enabled` is true.
 
+[[TASKS]]
 TASKS
 -----
 
@@ -215,7 +216,7 @@
 	specified tasks in the specified order. If no `--task=<task>`
 	arguments are specified, then only the tasks with
 	`maintenance.<task>.enabled` configured as `true` are considered.
-	See the 'TASKS' section for the list of accepted `<task>` values.
+	See the <<TASKS,'TASKS'>> section for the list of accepted `<task>` values.
 
 --scheduler=auto|crontab|systemd-timer|launchctl|schtasks::
 	When combined with the `start` subcommand, specify the scheduler
diff --git a/Documentation/git-merge-tree.adoc b/Documentation/git-merge-tree.adoc
index 4391bbe..c5351f9 100644
--- a/Documentation/git-merge-tree.adoc
+++ b/Documentation/git-merge-tree.adoc
@@ -35,7 +35,7 @@
   * etc.
 
 After the merge completes, a new toplevel tree object is created.  See
-`OUTPUT` below for details.
+<<OUTPUT,`OUTPUT`>> below for details.
 
 OPTIONS
 -------
diff --git a/Documentation/git-name-rev.adoc b/Documentation/git-name-rev.adoc
index d4f1c4d..9837fe5 100644
--- a/Documentation/git-name-rev.adoc
+++ b/Documentation/git-name-rev.adoc
@@ -43,7 +43,7 @@
 	List all commits reachable from all refs
 
 --annotate-stdin::
-	Transform stdin by substituting all the 40-character SHA-1
+	Transform stdin by substituting all the full-length object ID
 	hexes (say $hex) with "$hex ($rev_name)".  When used with
 	--name-only, substitute with "$rev_name", omitting $hex
 	altogether. This option was called `--stdin` in older versions
@@ -72,7 +72,7 @@
 -----------
 
 --name-only::
-	Instead of printing both the SHA-1 and the name, print only
+	Instead of printing both the object ID and the name, print only
 	the name.  If given with --tags the usual tag prefix of
 	"tags/" is also omitted from the name, matching the output
 	of `git-describe` more closely.
diff --git a/Documentation/git-notes.adoc b/Documentation/git-notes.adoc
index 46a232c..22d59ad 100644
--- a/Documentation/git-notes.adoc
+++ b/Documentation/git-notes.adoc
@@ -28,8 +28,9 @@
 the objects themselves.
 
 By default, notes are saved to and read from `refs/notes/commits`, but
-this default can be overridden.  See the OPTIONS, CONFIGURATION, and
-ENVIRONMENT sections below.  If this ref does not exist, it will be
+this default can be overridden.  See the <<OPTIONS,OPTIONS>>,
+<<CONFIGURATION,CONFIGURATION>>, and <<ENVIRONMENT,ENVIRONMENT>>
+sections below. If this ref does not exist, it will be
 quietly created when it is first needed to store a note.
 
 A typical use of notes is to supplement a commit message without
@@ -114,7 +115,8 @@
 	any) into the current notes ref (called "local").
 +
 If conflicts arise and a strategy for automatically resolving
-conflicting notes (see the "NOTES MERGE STRATEGIES" section) is not given,
+conflicting notes (see the
+<<NOTES_MERGE_STRATEGIES,"NOTES MERGE STRATEGIES">> section) is not given,
 the `manual` resolver is used. This resolver checks out the
 conflicting notes in a special worktree (`.git/NOTES_MERGE_WORKTREE`),
 and instructs the user to manually resolve the conflicts there.
@@ -139,6 +141,7 @@
 	Print the current notes ref. This provides an easy way to
 	retrieve the current notes ref (e.g. from scripts).
 
+[[OPTIONS]]
 OPTIONS
 -------
 `-f`::
@@ -225,7 +228,8 @@
 	strategy. The following strategies are recognized: `manual`
 	(default), `ours`, `theirs`, `union` and `cat_sort_uniq`.
 	This option overrides the `notes.mergeStrategy` configuration setting.
-	See the "NOTES MERGE STRATEGIES" section below for more
+	See the <<NOTES_MERGE_STRATEGIES,"NOTES MERGE STRATEGIES">>
+	section below for more
 	information on each notes merge strategy.
 
 `--commit`::
@@ -278,6 +282,7 @@
 `git log -p -g <refname>`.
 
 
+[[NOTES_MERGE_STRATEGIES]]
 NOTES MERGE STRATEGIES
 ----------------------
 
@@ -361,6 +366,7 @@
 include::config/notes.adoc[]
 
 
+[[ENVIRONMENT]]
 ENVIRONMENT
 -----------
 
diff --git a/Documentation/git-p4.adoc b/Documentation/git-p4.adoc
index 59edd24..acf14cd 100644
--- a/Documentation/git-p4.adoc
+++ b/Documentation/git-p4.adoc
@@ -119,7 +119,8 @@
 from a Git remote, this can be useful in a multi-developer environment.
 
 If there are multiple branches, doing 'git p4 sync' will automatically
-use the "BRANCH DETECTION" algorithm to try to partition new changes
+use the <<BRANCH_DETECTION,"BRANCH DETECTION">> algorithm
+to try to partition new changes
 into the right branch.  This can be overridden with the `--branch`
 option to specify just a single branch to update.
 
@@ -245,7 +246,7 @@
 
 --detect-branches::
 	Use the branch detection algorithm to find new paths in p4.  It is
-	documented below in "BRANCH DETECTION".
+	documented below in <<BRANCH_DETECTION,"BRANCH DETECTION">>.
 
 --changesfile <file>::
 	Import exactly the p4 change numbers listed in 'file', one per
@@ -297,7 +298,7 @@
 
 --use-client-spec::
 	Use a client spec to find the list of interesting files in p4.
-	See the "CLIENT SPEC" section below.
+	See the <<CLIENT_SPEC,"CLIENT SPEC">> section below.
 
 -/ <path>::
 	Exclude selected depot paths when cloning or syncing.
@@ -475,6 +476,7 @@
 See 'p4 help revisions' for the full syntax of p4 revision specifiers.
 
 
+[[CLIENT_SPEC]]
 CLIENT SPEC
 -----------
 The p4 client specification is maintained with the 'p4 client' command
@@ -505,6 +507,7 @@
 variable `P4CLIENT`, a file referenced by `P4CONFIG`, or the local host name.
 
 
+[[BRANCH_DETECTION]]
 BRANCH DETECTION
 ----------------
 P4 does not have the same concept of a branch as Git.  Instead,
@@ -643,7 +646,8 @@
 git-p4.useClientSpec::
 	Specify that the p4 client spec should be used to identify p4
 	depot paths of interest.  This is equivalent to specifying the
-	option `--use-client-spec`.  See the "CLIENT SPEC" section above.
+	option `--use-client-spec`.
+	See the <<CLIENT_SPEC,"CLIENT SPEC">> section above.
 	This variable is a boolean, not the name of a p4 client.
 
 git-p4.pathEncoding::
diff --git a/Documentation/git-pack-objects.adoc b/Documentation/git-pack-objects.adoc
index 65cd00c..ccad938 100644
--- a/Documentation/git-pack-objects.adoc
+++ b/Documentation/git-pack-objects.adoc
@@ -363,8 +363,8 @@
 	Keep unreachable objects in loose form. This implies `--revs`.
 
 --delta-islands::
-	Restrict delta matches based on "islands". See DELTA ISLANDS
-	below.
+	Restrict delta matches based on "islands".
+	See <<DELTA_ISLANDS,DELTA ISLANDS>> below.
 
 --name-hash-version=<n>::
 	While performing delta compression, Git groups objects that may be
@@ -411,6 +411,7 @@
 `combine:<spec>+<spec>` form.
 
 
+[[DELTA_ISLANDS]]
 DELTA ISLANDS
 -------------
 
diff --git a/Documentation/git-prune.adoc b/Documentation/git-prune.adoc
index 9a45571..d7836e6 100644
--- a/Documentation/git-prune.adoc
+++ b/Documentation/git-prune.adoc
@@ -15,7 +15,7 @@
 -----------
 
 NOTE: In most cases, users should run 'git gc', which calls
-'git prune'. See the section "NOTES", below.
+'git prune'. See the section <<NOTES,"NOTES">>, below.
 
 This runs 'git fsck --unreachable' using all the refs
 available in `refs/`, optionally with an additional set of
@@ -67,6 +67,7 @@
 $ git prune $(cd ../another && git rev-parse --all)
 ------------
 
+[[NOTES]]
 NOTES
 -----
 
diff --git a/Documentation/git-push.adoc b/Documentation/git-push.adoc
index aa221c3..e5b1023 100644
--- a/Documentation/git-push.adoc
+++ b/Documentation/git-push.adoc
@@ -132,7 +132,8 @@
     linkgit:git-config[1]) suggest what refs/ namespace you may have
     wanted to push to.
 
-Not all updates are allowed: see PUSH RULES below for the details.
+Not all updates are allowed: see
+<<PUSH_RULES,PUSH RULES>> below for the details.
 
 `--all`::
 `--branches`::
@@ -337,9 +338,9 @@
 	Usually, `git push` will refuse to update a branch that is not an
 	ancestor of the commit being pushed.
 +
-This flag disables that check, the other safety checks in PUSH RULES
-below, and the checks in `--force-with-lease`. It can cause the remote
-repository to lose commits; use it with care.
+This flag disables that check, the other safety checks in
+<<PUSH_RULES,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
@@ -568,6 +569,7 @@
 	refs, no explanation is needed. For a failed ref, the reason for
 	failure is described.
 
+[[PUSH_RULES]]
 PUSH RULES
 ----------
 
diff --git a/Documentation/git-rebase.adoc b/Documentation/git-rebase.adoc
index f6c22d1..6536ebd 100644
--- a/Documentation/git-rebase.adoc
+++ b/Documentation/git-rebase.adoc
@@ -17,8 +17,8 @@
 DESCRIPTION
 -----------
 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.
+You can also use `git rebase` to reorder or combine commits: see
+<<INTERACTIVE_MODE,INTERACTIVE MODE>> below for how to do that.
 
 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.
@@ -76,8 +76,8 @@
 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.
+   `git cherry-pick <commit>` for each commit.
+   See <<REBASING_MERGES,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>`.
 
@@ -89,6 +89,7 @@
 tip, however, is accessible using the reflog of the current branch (i.e. `@{1}`,
 see linkgit:gitrevisions[7]).
 
+[[TRANSPLANTING]]
 TRANSPLANTING A TOPIC BRANCH WITH --ONTO
 ----------------------------------------
 
@@ -218,7 +219,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.
+See <<TRANSPLANTING,TRANSPLANTING A TOPIC BRANCH WITH --ONTO>>
+above for examples.
 
 --keep-base::
 	Set the starting point at which to create the new commits to the
@@ -239,7 +241,7 @@
 point_ on which new commits will be created, whereas `--fork-point` uses
 the merge base to determine the _set of commits_ which will be rebased.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 <upstream>::
 	Upstream branch to compare against.  May be any valid commit,
@@ -254,7 +256,7 @@
 	internally).  This option may become a no-op in the future
 	once the merge backend handles everything the apply one does.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 --empty=(drop|keep|stop)::
 	How to handle commits that are not empty to start and are not
@@ -282,7 +284,7 @@
 preliminary step (unless `--reapply-cherry-picks` or `--keep-base` is
 passed).
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 --no-keep-empty::
 --keep-empty::
@@ -303,7 +305,7 @@
 For commits which do not start empty but become empty after rebasing,
 see the `--empty` flag.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 --reapply-cherry-picks::
 --no-reapply-cherry-picks::
@@ -325,7 +327,7 @@
 `--reapply-cherry-picks` allows rebase to forgo reading all upstream
 commits, potentially improving performance.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 --allow-empty-message::
 	No-op.  Rebasing commits with an empty message used to fail
@@ -333,7 +335,7 @@
 	with empty messages to be rebased.  Now commits with an empty
 	message do not cause rebasing to halt.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 -m::
 --merge::
@@ -345,7 +347,7 @@
 series, starting with `<upstream>`, and 'theirs' is the working branch.
 In other words, the sides are swapped.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 -s <strategy>::
 --strategy=<strategy>::
@@ -357,7 +359,7 @@
 the `ours` strategy simply empties all patches from the `<branch>`,
 which makes little sense.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 -X <strategy-option>::
 --strategy-option=<strategy-option>::
@@ -366,7 +368,7 @@
 	specified, `-s ort`.  Note the reversal of 'ours' and
 	'theirs' as noted above for the `-m` option.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 include::rerere-options.adoc[]
 
@@ -408,7 +410,7 @@
 	context exist they all must match.  By default no context is
 	ever ignored.  Implies `--apply`.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 --no-ff::
 --force-rebase::
@@ -443,7 +445,7 @@
 your branch contains commits which were dropped, this option can be used
 with `--keep-base` in order to drop those commits from your branch.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 --ignore-whitespace::
 	Ignore whitespace differences when trying to reconcile
@@ -468,7 +470,7 @@
 	(see linkgit:git-apply[1]) that applies the patch.
 	Implies `--apply`.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 --committer-date-is-author-date::
 	Instead of using the current time as the committer date, use
@@ -488,14 +490,14 @@
 	the current time as the	author date of the rebased commit.  This
 	option implies `--force-rebase`.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 --signoff::
 	Add a `Signed-off-by` trailer to all the rebased commits. Note
 	that if `--interactive` is given then only commits marked to be
 	picked, edited or reworded will have the trailer added.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 --trailer=<trailer>::
 	Append the given trailer to every rebased commit message, processed
@@ -508,13 +510,13 @@
 --interactive::
 	Make a list of the commits which are about to be rebased.  Let the
 	user edit that list before rebasing.  This mode can also be used to
-	split commits (see SPLITTING COMMITS below).
+	split commits (see <<SPLITTING_COMMITS,SPLITTING COMMITS>> below).
 +
 The commit list format can be changed by setting the configuration option
 rebase.instructionFormat.  A customized instruction format will automatically
 have the commit hash prepended to the format.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 -r::
 --rebase-merges[=(rebase-cousins|no-rebase-cousins)]::
@@ -542,7 +544,8 @@
 `ort` merge strategy; different merge strategies can be used only via
 explicit `exec git merge -s <strategy> [...]` commands.
 +
-See also REBASING MERGES and INCOMPATIBLE OPTIONS below.
+See also <<REBASING_MERGES,REBASING MERGES>> and
+<<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 -x <cmd>::
 --exec <cmd>::
@@ -567,14 +570,14 @@
 This uses the `--interactive` machinery internally, but it can be run
 without an explicit `--interactive`.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 --root::
 	Rebase all commits reachable from `<branch>`, instead of
 	limiting them with an `<upstream>`.  This allows you to rebase
 	the root commit(s) on a branch.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 --autosquash::
 --no-autosquash::
@@ -600,7 +603,7 @@
 auto-squashing by default for interactive rebase.  The `--no-autosquash`
 option can be used to override that setting.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
 --autostash::
 --no-autostash::
@@ -617,8 +620,9 @@
 +
 This option applies once a rebase is started. It is preserved for the whole
 rebase based on, in order, the command line option provided to the initial `git
-rebase`, the `rebase.rescheduleFailedExec` configuration (see
-linkgit:git-config[1] or "CONFIGURATION" below), or it defaults to false.
+rebase`, the `rebase.rescheduleFailedExec` configuration
+(see linkgit:git-config[1] or <<CONFIGURATION,"CONFIGURATION">> below),
+or it defaults to false.
 +
 Recording this option for the whole rebase is a convenience feature. Otherwise
 an explicit `--no-reschedule-failed-exec` at the start would be overridden by
@@ -635,8 +639,9 @@
 If the configuration variable `rebase.updateRefs` is set, then this option
 can be used to override and disable this setting.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also <<INCOMPATIBLE_OPTIONS,INCOMPATIBLE OPTIONS>> below.
 
+[[INCOMPATIBLE_OPTIONS]]
 INCOMPATIBLE OPTIONS
 --------------------
 
@@ -813,7 +818,8 @@
 -----
 
 You should understand the implications of using `git rebase` on a
-repository that you share.  See also RECOVERING FROM UPSTREAM REBASE
+repository that you share.
+See also <<RECOVERING_FROM_UPSTREAM_REBASE,RECOVERING FROM UPSTREAM REBASE>>
 below.
 
 When the rebase is run, it will first execute a `pre-rebase` hook if one
@@ -823,6 +829,7 @@
 
 Upon completion, `<branch>` will be the current branch.
 
+[[INTERACTIVE_MODE]]
 INTERACTIVE MODE
 ----------------
 
@@ -978,6 +985,7 @@
 exec make test
 --------------------
 
+[[SPLITTING_COMMITS]]
 SPLITTING COMMITS
 -----------------
 
@@ -1013,6 +1021,7 @@
 after each commit, test, and amend the commit if fixes are necessary.
 
 
+[[RECOVERING_FROM_UPSTREAM_REBASE]]
 RECOVERING FROM UPSTREAM REBASE
 -------------------------------
 
@@ -1138,6 +1147,7 @@
 'everyone' downstream from 'topic' will now have to perform a "hard
 case" recovery too!
 
+[[REBASING_MERGES]]
 REBASING MERGES
 ---------------
 
@@ -1276,6 +1286,7 @@
 merge cmake
 ------------
 
+[[CONFIGURATION]]
 CONFIGURATION
 -------------
 
diff --git a/Documentation/git-receive-pack.adoc b/Documentation/git-receive-pack.adoc
index 0956086..ab668ff 100644
--- a/Documentation/git-receive-pack.adoc
+++ b/Documentation/git-receive-pack.adoc
@@ -236,6 +236,23 @@
 exec git update-server-info
 ----
 
+PROC-RECEIVE HOOK
+-----------------
+This hook is invoked by linkgit:git-receive-pack[1].  If the server has
+set the multi-valued config variable `receive.procReceiveRefs`, and the
+commands sent to 'receive-pack' have matching reference names, these
+commands will be executed by this hook, instead of by the internal
+`execute_commands()` function.  This hook is responsible for updating
+the relevant references and reporting the results back to 'receive-pack'.
+
+RECEIVE-REPORT HOOK
+-------------------
+This hook is invoked by 'git-receive-pack' after all the ref updates
+have been applied but before the report is sent to the client. The hook
+receives the complete report in pkt-line format on stdin and its stdout
+replaces the report sent to the client, which allows the hook to rewrite
+the outcomes or abort the push completely. See linkgit:githooks[5] for
+the full protocol description.
 
 QUARANTINE ENVIRONMENT
 ----------------------
diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc
index 9dc08cb..601bc47 100644
--- a/Documentation/git-refs.adoc
+++ b/Documentation/git-refs.adoc
@@ -9,8 +9,9 @@
 SYNOPSIS
 --------
 [synopsis]
-git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]
+git refs migrate --ref-storage-format=<format> [--no-reflog] [--dry-run]
 git refs verify [--strict] [--verbose]
+git refs optimize [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]
 git refs list [--count=<count>] [--shell|--perl|--python|--tcl]
 		   [(--sort=<key>)...] [--format=<format>]
 		   [--include-root-refs] [--points-at=<object>]
@@ -19,7 +20,6 @@
 		   [(--exclude=<pattern>)...] [--start-after=<marker>]
 		   [ --stdin | (<pattern>...)]
 git refs exists <ref>
-git refs optimize [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]
 git refs create [--message=<reason>] [--no-deref] [--create-reflog] <ref> <new-value>
 git refs delete [--message=<reason>] [--no-deref] <ref> [<old-value>]
 git refs update [--message=<reason>] [--no-deref] [--create-reflog] <ref> <new-value> [<old-value>]
@@ -97,11 +97,14 @@
 
 The following options are specific to `git refs migrate`:
 
-`--ref-format=<format>`::
-	The ref format to migrate the ref store to. Can be one of:
+`--ref-storage-format=<format>`::
+	The ref storage format to migrate the ref store to. Can be one of:
 +
 include::ref-storage-format.adoc[]
 
+`--ref-format=<format>`::
+Deprecated alias of `--ref-storage-format=<format>`.
+
 `--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
diff --git a/Documentation/git-remote.adoc b/Documentation/git-remote.adoc
index eaae30a..3151004 100644
--- a/Documentation/git-remote.adoc
+++ b/Documentation/git-remote.adoc
@@ -28,6 +28,11 @@
 
 Manage the set of repositories ("remotes") whose branches you track.
 
+`git remote` changes only the local repository, i.e. its configuration
+and its refs, and never modifies a remote repository.  Some subcommands,
+such as `show`, `prune`, `update` and `set-head --auto`, contact a
+remote repository to read from it.
+
 
 OPTIONS
 -------
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index 58b4c0c..f3ac875 100644
--- a/Documentation/git-replay.adoc
+++ b/Documentation/git-replay.adoc
@@ -20,7 +20,7 @@
 relevant references using an atomic transaction (all refs update or
 none). Use `--ref-action=print` to avoid automatic ref updates and
 instead get update commands that can be piped to `git update-ref --stdin`
-(see the <<output,OUTPUT>> section below).
+(see the <<OUTPUT,OUTPUT>> section below).
 
 THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
 
@@ -118,7 +118,7 @@
 :git-replay: 1
 include::rev-list-options.adoc[]
 
-[[output]]
+[[OUTPUT]]
 OUTPUT
 ------
 
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index ed7d80c..cb8c1e7 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -22,8 +22,8 @@
 --------
 `info [--format=(lines|nul) | -z] [--all | <key>...]`::
 	Retrieve metadata-related information about the current repository. Only
-	the requested data will be returned based on their keys (see "INFO KEYS"
-	section below).
+	the requested data will be returned based on their keys
+	(see <<INFO_KEYS,"INFO KEYS">> section below).
 +
 The values are returned in the same order in which their respective keys were
 requested. The `--all` flag requests the values for all the available keys.
@@ -89,6 +89,7 @@
 +
 `-z` is an alias for `--format=nul`.
 
+[[INFO_KEYS]]
 INFO KEYS
 ---------
 In order to obtain a set of values from `git repo info`, you should provide
diff --git a/Documentation/git-rev-parse.adoc b/Documentation/git-rev-parse.adoc
index 5398691..5b866af 100644
--- a/Documentation/git-rev-parse.adoc
+++ b/Documentation/git-rev-parse.adoc
@@ -38,12 +38,13 @@
 Each of these options must appear first on the command line.
 
 --parseopt::
-	Use 'git rev-parse' in option parsing mode (see PARSEOPT section below).
+	Use 'git rev-parse' in option parsing mode
+	(see <<PARSEOPT,PARSEOPT>> section below).
 	The command in this mode can be used outside a repository or
 	a working tree controlled by a repository.
 
 --sq-quote::
-	Use 'git rev-parse' in shell quoting mode (see SQ-QUOTE
+	Use 'git rev-parse' in shell quoting mode (see <<SQ_QUOTE,SQ-QUOTE>>
 	section below). In contrast to the `--sq` option below, this
 	mode only does quoting. Nothing else is done to command input.
 	The command in this mode can be used outside a repository or
@@ -331,9 +332,12 @@
 	requested and no compatibility algorithm is enabled, prints an empty line. If
 	not specified, the default is "storage".
 
---show-ref-format::
+--show-ref-storage-format::
 	Show the reference storage format used for the repository.
 
+--show-ref-format::
+	Deprecated alias of `--show-ref-storage-format`.
+
 
 Other Options
 ~~~~~~~~~~~~~
@@ -354,6 +358,7 @@
 
 include::revisions.adoc[]
 
+[[PARSEOPT]]
 PARSEOPT
 --------
 
@@ -460,6 +465,7 @@
     -C[...]               option C with an optional argument
 ------------
 
+[[SQ_QUOTE]]
 SQ-QUOTE
 --------
 
diff --git a/Documentation/git-rm.adoc b/Documentation/git-rm.adoc
index b5ead86..67061e9 100644
--- a/Documentation/git-rm.adoc
+++ b/Documentation/git-rm.adoc
@@ -61,7 +61,7 @@
 
 `--`::
 	This option can be used to separate command-line options from
-	the list of files, (useful when filenames might be mistaken
+	the list of files (useful when filenames might be mistaken
 	for command-line options).
 
 `--cached`::
diff --git a/Documentation/git-send-email.adoc b/Documentation/git-send-email.adoc
index 5c9ab39..4a5e0c3 100644
--- a/Documentation/git-send-email.adoc
+++ b/Documentation/git-send-email.adoc
@@ -50,7 +50,7 @@
 
 `--annotate`::
 	Review and edit each patch you're about to send. Default is the value
-	of `sendemail.annotate`. See the CONFIGURATION section for
+	of `sendemail.annotate`. See the <<CONFIGURATION,CONFIGURATION>> section for
 	`sendemail.multiEdit`.
 
 `--bcc=<address>,...`::
@@ -78,7 +78,7 @@
 +
 Missing `From` or `In-Reply-To` headers will be prompted for.
 +
-See the CONFIGURATION section for `sendemail.multiEdit`.
+See the <<CONFIGURATION,CONFIGURATION>> section for `sendemail.multiEdit`.
 
 `--from=<address>`::
 	Specify the sender of the emails.  If not specified on the command line,
@@ -559,6 +559,7 @@
 	address to standard output, one per line. See `sendemail.aliasFile`
 	for more information about aliases.
 
+[[CONFIGURATION]]
 CONFIGURATION
 -------------
 
diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc
index fc6a9a0..d6e8c41 100644
--- a/Documentation/git-stash.adoc
+++ b/Documentation/git-stash.adoc
@@ -133,7 +133,7 @@
 `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,'EXAMPLES'>> below for a possible strategy).
 
 `drop [-q | --quiet] [<stash>]`::
 	Remove a single stash entry from the list of stash entries.
@@ -315,6 +315,7 @@
 tree.
 
 
+[[EXAMPLES]]
 EXAMPLES
 --------
 
diff --git a/Documentation/git-submodule.adoc b/Documentation/git-submodule.adoc
index 722d827..e9c81a7 100644
--- a/Documentation/git-submodule.adoc
+++ b/Documentation/git-submodule.adoc
@@ -34,7 +34,7 @@
 With no arguments, shows the status of existing submodules.  Several
 subcommands are available to perform operations on the submodules.
 
-`add [-b <branch>] [-f | --force] [--name <name>] [--reference <repository>] [--ref-format <format>] [--depth <depth>] [--] <repository> [<path>]`::
+`add [-b <branch>] [-f | --force] [--name <name>] [--reference <repository>] [--ref-storage-format <format>] [--depth <depth>] [--] <repository> [<path>]`::
 	Add the given repository as a submodule at the given path
 	to the changeset to be committed next to the current
 	project: the current project is termed the "superproject".
@@ -72,8 +72,9 @@
 git-submodule will correctly locate the submodule using the relative
 URL in `.gitmodules`.
 +
-If `--ref-format <format>`  is specified, the ref storage format of newly
-cloned submodules will be set accordingly.
+If `--ref-storage-format <format>` or its deprecated alias `--ref-format
+<format>` is specified, the ref storage format of the newly cloned submodules
+will be set accordingly.
 
 `status [--cached] [--recursive] [--] [<path>...]`::
 	Show the status of the submodules. This will print the SHA-1 of the
@@ -139,7 +140,7 @@
 that use linkgit:git-rm[1] instead. See linkgit:gitsubmodules[7] for removal
 options.
 
-`update [--init] [--remote] [-N | --no-fetch] [--[no-]recommend-shallow] [-f | --force] [--checkout | --rebase | --merge] [--reference=<repository>] [--ref-format=<format>] [--depth=<depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--filter=<filter-spec>] [--] [<path>...]`::
+`update [--init] [--remote] [-N | --no-fetch] [--[no-]recommend-shallow] [-f | --force] [--checkout | --rebase | --merge] [--reference=<repository>] [--ref-storage-format=<format>] [--depth=<depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--filter=<filter-spec>] [--] [<path>...]`::
 +
 --
 Update the registered submodules to match what the superproject
@@ -188,8 +189,9 @@
 If `--recursive` is specified, this command will recurse into the
 registered submodules, and update any nested submodules within.
 
-If `--ref-format <format>`  is specified, the ref storage format of newly
-cloned submodules will be set accordingly.
+If `--ref-storage-format <format>` or its deprecated alias `--ref-format
+<format>` is specified, the ref storage format of the newly cloned submodules
+will be set accordingly.
 
 If `--filter <filter-spec>` is specified, the given partial clone filter will be
 applied to the submodule. See linkgit:git-rev-list[1] for details on filter
diff --git a/Documentation/git-svn.adoc b/Documentation/git-svn.adoc
index 2a7fa60..baaf3bf 100644
--- a/Documentation/git-svn.adoc
+++ b/Documentation/git-svn.adoc
@@ -126,7 +126,7 @@
 	command-line argument.
 +
 This automatically updates the rev_map if needed (see
-'$GIT_DIR/svn/\**/.rev_map.*' in the FILES section below for details).
+'$GIT_DIR/svn/\**/.rev_map.*' in the <<FILES,FILES>> section below for details).
 
 --localtime;;
 	Store Git commit times in the local time zone instead of UTC.  This
@@ -239,7 +239,7 @@
 and have no uncommitted changes.
 +
 This automatically updates the rev_map if needed (see
-'$GIT_DIR/svn/\**/.rev_map.*' in the FILES section below for details).
+'$GIT_DIR/svn/\**/.rev_map.*' in the <<FILES,FILES>> section below for details).
 
 -l;;
 --local;;
@@ -524,7 +524,7 @@
 	way to repair the repo is to use 'reset'.
 +
 Only the rev_map and refs/remotes/git-svn are changed (see
-'$GIT_DIR/svn/\**/.rev_map.*' in the FILES section below for details).
+'$GIT_DIR/svn/\**/.rev_map.*' in the <<FILES,FILES>> section below for details).
 Follow 'reset' with a 'fetch' and then 'git reset' or 'git rebase' to
 move local branches onto the new tree.
 
@@ -946,7 +946,7 @@
 standard layout, it cannot yet represent merge history that happened
 inside git back upstream to SVN users.  Therefore it is advised that
 users keep history as linear as possible inside Git to ease
-compatibility with SVN (see the CAVEATS section below).
+compatibility with SVN (see the <<CAVEATS,CAVEATS>> section below).
 
 HANDLING OF SVN BRANCHES
 ------------------------
@@ -994,6 +994,7 @@
 it will create a branch 'sub@200' pointing to the new parent commit of
 branch 'sub' (i.e. the commit for r.200 and trunk/sub/).
 
+[[CAVEATS]]
 CAVEATS
 -------
 
@@ -1135,6 +1136,7 @@
 fetching, then $GIT_DIR/svn/.metadata must be manually edited to remove
 (or reset) branches-maxRev and/or tags-maxRev as appropriate.
 
+[[FILES]]
 FILES
 -----
 $GIT_DIR/svn/\**/.rev_map.*::
diff --git a/Documentation/git-upload-pack.adoc b/Documentation/git-upload-pack.adoc
index 9167a32..9e3a3fe 100644
--- a/Documentation/git-upload-pack.adoc
+++ b/Documentation/git-upload-pack.adoc
@@ -70,7 +70,14 @@
 `GIT_NO_LAZY_FETCH` variable to `1`. If you want to override it
 (because you are fetching from a partial clone, and you are sure
 you trust it), you can explicitly set `GIT_NO_LAZY_FETCH` to
-`0`.
+`0`. As it is an environment variable, it is also inherited by child
+processes, including any `upload-pack` run to lazily fetch from a
+promisor remote on the same machine.
++
+Instead of setting `GIT_NO_LAZY_FETCH` to `0` in the environment, a
+server operator can allow lazy fetching on a per-repository basis by
+listing trusted repositories in the `uploadpack.lazyFetchTrusted`
+configuration variable. See linkgit:git-config[1].
 
 SECURITY
 --------
diff --git a/Documentation/git-var.adoc b/Documentation/git-var.adoc
index 697c10a..6982228 100644
--- a/Documentation/git-var.adoc
+++ b/Documentation/git-var.adoc
@@ -9,12 +9,23 @@
 SYNOPSIS
 --------
 [synopsis]
-git var (-l | <variable>)
+git var [-z] -l
+git var [-z] <variable>...
 
 DESCRIPTION
 -----------
-Prints a Git logical variable. Exits with code 1 if the variable has
-no value.
+Prints Git logical variables. When a single variable is requested, its
+bare value is printed, and the command exits with code 1 if it has no
+value. When multiple variables are requested, they are printed as
+`VARIABLE=value` pairs, separated by newlines; a variable that has no
+value is omitted from the output, which is not by itself an error.
+
+If `-z` is given, the output format changes depending on the mode:
+
+* With a single variable, the bare value is terminated by a NUL byte.
+* With multiple variables or with `-l`, the variable name and value are
+  separated by a newline, and each entry is terminated by a NUL byte
+  (`VARIABLE\nvalue\0`), in the same way as `git config list -z`.
 
 OPTIONS
 -------
@@ -24,19 +35,53 @@
 	as well. (However, the configuration variables listing functionality
 	is deprecated in favor of `git config list`.)
 
+`-z`::
+	Terminate entries with NUL instead of newline. When used with
+	`-l` or when multiple variables are requested, the variable name
+	and its value are separated by a newline, and each entry is
+	terminated with a NUL byte.
+
 EXAMPLES
 --------
-	$ git var GIT_AUTHOR_IDENT
-	Eric W. Biederman <ebiederm@lnxi.com> 1121223278 -0600
+* Get the author identity:
++
+------------
+$ git var GIT_AUTHOR_IDENT
+Eric W. Biederman <ebiederm@lnxi.com> 1121223278 -0600
+------------
+
+* Get the author name and email:
++
+------------
+$ git var GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
+GIT_AUTHOR_NAME=Eric W. Biederman
+GIT_AUTHOR_EMAIL=ebiederm@lnxi.com
+------------
 
 
 VARIABLES
 ---------
 `GIT_AUTHOR_IDENT`::
-    The author of a piece of code.
+`GIT_AUTHOR_NAME`::
+`GIT_AUTHOR_EMAIL`::
+`GIT_AUTHOR_DATE`::
+    The authorship information that would be recorded in the
+    resulting commit object if you ran `git commit` right now.
+    `GIT_AUTHOR_IDENT` consists of the author's name, e-mail
+    address, and timestamp+timezone. These three pieces of
+    information are available separately as `GIT_AUTHOR_NAME`,
+    `GIT_AUTHOR_EMAIL`, and `GIT_AUTHOR_DATE`.
 
 `GIT_COMMITTER_IDENT`::
-    The person who put a piece of code into Git.
+`GIT_COMMITTER_NAME`::
+`GIT_COMMITTER_EMAIL`::
+`GIT_COMMITTER_DATE`::
+    The committer information that would be recorded in the
+    resulting commit object if you ran `git commit` right now.
+    `GIT_COMMITTER_IDENT` consists of the committer's name, e-mail
+    address, and timestamp+timezone. These three pieces of
+    information are available separately as `GIT_COMMITTER_NAME`,
+    `GIT_COMMITTER_EMAIL`, and `GIT_COMMITTER_DATE`.
 
 `GIT_EDITOR`::
     Text editor for use by Git commands.  The value is meant to be
@@ -83,11 +128,14 @@
 
 `GIT_CONFIG_GLOBAL`::
     The path to the global (per-user) configuration files, if any.
+    This variable can have multiple values.
 
 Most path values contain only one value. However, some can contain multiple
-values, which are separated by newlines, and are listed in order from highest to
-lowest priority.  Callers should be prepared for any such path value to contain
-multiple items.
+values, which are separated by newlines (or NUL bytes if `-z` is given),
+and are listed in order from highest to lowest priority. When querying
+multiple variables (or using `-l`), each value is output as a separate
+`VARIABLE=value` entry (or `VARIABLE\nvalue\0` with `-z`). Callers should
+be prepared for any such path value to contain multiple items.
 
 Note that paths are printed even if they do not exist, but not if they are
 disabled by other environment variables.
diff --git a/Documentation/git-worktree.adoc b/Documentation/git-worktree.adoc
index 32787ea..e6e7725 100644
--- a/Documentation/git-worktree.adoc
+++ b/Documentation/git-worktree.adoc
@@ -50,8 +50,8 @@
 
 If a working tree is deleted without using `git worktree remove`, then
 its associated administrative files, which reside in the repository
-(see "DETAILS" below), will eventually be removed automatically (see
-`gc.worktreePruneExpire` in linkgit:git-config[1]), or you can run
+(see <<DETAILS,"DETAILS">> below), will eventually be removed automatically
+(see `gc.worktreePruneExpire` in linkgit:git-config[1]), or you can run
 `git worktree prune` in the main or any linked worktree to clean up any
 stale administrative files.
 
@@ -360,6 +360,7 @@
 See the documentation of `extensions.worktreeConfig` in
 linkgit:git-config[1] for more details.
 
+[[DETAILS]]
 DETAILS
 -------
 Each linked worktree has a private sub-directory in the repository's
diff --git a/Documentation/git.adoc b/Documentation/git.adoc
index 6f0075f..033de8d 100644
--- a/Documentation/git.adoc
+++ b/Documentation/git.adoc
@@ -593,15 +593,21 @@
 	is always used. The default is "sha1".
 	See `--object-format` in linkgit:git-init[1].
 
-`GIT_DEFAULT_REF_FORMAT`::
-	If this variable is set, the default reference backend format for new
+`GIT_DEFAULT_REF_STORAGE_FORMAT`::
+	If this variable is set, the default ref storage format for new
 	repositories will be set to this value. The default is "files".
-	See `--ref-format` in linkgit:git-init[1].
+	See `--ref-storage-format` in linkgit:git-init[1].
+
+`GIT_DEFAULT_REF_FORMAT`::
+	Deprecated alias of `GIT_DEFAULT_REF_STORAGE_FORMAT`.
+
+`GIT_REF_STORAGE_FORMAT`::
+	Specify which ref storage format to use along with its URI.
+	See `extensions.refStorage` option in linkgit:git-config[1] for more
+	details. Overrides the config variable when used.
 
 `GIT_REFERENCE_BACKEND`::
-    Specify which reference backend to be used along with its URI.
-    See `extensions.refStorage` option in linkgit:git-config[1] for more
-    details. Overrides the config variable when used.
+	Deprecated alias of `GIT_REF_STORAGE_FORMAT`.
 
 Git Commits
 ~~~~~~~~~~~
@@ -952,7 +958,9 @@
 `GIT_NO_LAZY_FETCH`::
 	Setting this Boolean environment variable to true tells Git
 	not to lazily fetch missing objects from the promisor remote
-	on demand.
+	on demand. On the server side, the `uploadpack.lazyFetchTrusted`
+	configuration variable can control this per-repository. See
+	linkgit:git-upload-pack[1].
 
 `GIT_REFLOG_ACTION`::
 	When a ref is updated, reflog entries are created to keep
diff --git a/Documentation/gitcli.adoc b/Documentation/gitcli.adoc
index 6815d6b..9c4598e 100644
--- a/Documentation/gitcli.adoc
+++ b/Documentation/gitcli.adoc
@@ -5,11 +5,6 @@
 ----
 gitcli - Git command-line interface and conventions
 
-SYNOPSIS
---------
-gitcli
-
-
 DESCRIPTION
 -----------
 
diff --git a/Documentation/gitcore-tutorial.adoc b/Documentation/gitcore-tutorial.adoc
index 2122aeb..71fda63 100644
--- a/Documentation/gitcore-tutorial.adoc
+++ b/Documentation/gitcore-tutorial.adoc
@@ -5,10 +5,6 @@
 ----
 gitcore-tutorial - A Git core tutorial for developers
 
-SYNOPSIS
---------
-git *
-
 DESCRIPTION
 -----------
 
diff --git a/Documentation/gitdatamodel.adoc b/Documentation/gitdatamodel.adoc
index 56b7635..8d9be02 100644
--- a/Documentation/gitdatamodel.adoc
+++ b/Documentation/gitdatamodel.adoc
@@ -5,10 +5,6 @@
 ----
 gitdatamodel - Git's core data model
 
-SYNOPSIS
---------
-gitdatamodel
-
 DESCRIPTION
 -----------
 
diff --git a/Documentation/giteveryday.adoc b/Documentation/giteveryday.adoc
index 6cfdd0e..0c9db2f 100644
--- a/Documentation/giteveryday.adoc
+++ b/Documentation/giteveryday.adoc
@@ -5,11 +5,6 @@
 ----
 giteveryday - A useful minimum set of commands for Everyday Git
 
-SYNOPSIS
---------
-
-Everyday Git With 20 Commands Or So
-
 DESCRIPTION
 -----------
 
diff --git a/Documentation/gitfaq.adoc b/Documentation/gitfaq.adoc
index f6c9b9d..b26e4e3 100644
--- a/Documentation/gitfaq.adoc
+++ b/Documentation/gitfaq.adoc
@@ -5,10 +5,6 @@
 ----
 gitfaq - Frequently asked questions about using Git
 
-SYNOPSIS
---------
-gitfaq
-
 DESCRIPTION
 -----------
 
diff --git a/Documentation/gitglossary.adoc b/Documentation/gitglossary.adoc
index b046d9c..eb1e608 100644
--- a/Documentation/gitglossary.adoc
+++ b/Documentation/gitglossary.adoc
@@ -5,10 +5,6 @@
 ----
 gitglossary - A Git Glossary
 
-SYNOPSIS
---------
-*
-
 DESCRIPTION
 -----------
 
diff --git a/Documentation/githooks.adoc b/Documentation/githooks.adoc
index ed04594..145642b 100644
--- a/Documentation/githooks.adoc
+++ b/Documentation/githooks.adoc
@@ -527,6 +527,67 @@
 status will cause the transaction to be aborted. The hook will not be
 called with "aborted" state in that case.
 
+receive-report
+~~~~~~~~~~~~~~
+
+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to
+`git push` and updates references in its repository. It executes on
+the repository once after all refs have been updated and after all
+accepted ref changes are applied to the repository, but before the
+pkt-line encoded status report is sent back to the client.
+
+The hook receives the complete pkt-line encoded status report on
+standard input, see linkgit:gitprotocol-pack[5] for details on the
+structure. The hook's standard output entirely replaces the report
+that is sent to the client. The hook must write a valid pkt-line
+encoded report in the same format it received. The hook's stdout is
+fully buffered by `receive-pack` before any data is sent to the client,
+so the hook's exit status is known before the client receives anything.
+
+There are three distinct ways the hook can affect the push outcome:
+
+* To reject the push, modify the unpack status from `ok` to the required
+  error message. While `git-push` will fail, individual references may
+  still show success messages unless modified.
+
+* To reject individual ref updates while keeping `receive-pack` alive,
+  rewrite the corresponding `ok <refname>` lines to
+  `ng <refname>[ <reason>]` lines in the output and exit with status 0.
+  The client will then mark those specific refs as rejected while
+  treating any `ok` refs as successful. The push as a whole is
+  considered failed if any ref is `ng`, and `git push` will exit with
+  a non-zero status on the client side.
+
+* To abort the entire push unconditionally, exit with a non-zero
+  status. In this case the hook's stdout is discarded, `receive-pack`
+  modifies all references to be rejected with a 'receive-report hook
+  failed' error.
+
+Any output written to standard error is forwarded to the client over
+the sideband channel and will appear as `remote:` lines on clients
+using 'git-push(1)', regardless of the hook's exit status. Writing to
+standard error alone does not affect the push outcome.
+
+Note that by the time this hook runs, all ref updates have already been
+applied to the repository. Neither a non-zero exit nor rewriting refs
+to `ng` rolls back any ref changes that were already committed
+server-side. The hook can cause the client to perceive the push as
+failed, but cannot undo the server-side updates.
+
+This means that reporting a reference as `ng` makes the client believe
+the update did not happen while the server has in fact applied it. The
+client leaves its remote-tracking reference at its old value, and a
+later `git fetch` may reveal the very update that the push reported as
+rejected. Neither Git nor the server can reconcile this; only the user,
+by fetching again, will find out.
+
+This hook is therefore only appropriate for servers which can guarantee
+that a rejected update is not observable by any reader, for example
+because the committed transaction produces a candidate state that is
+discarded rather than published. On a repository where a committed
+reference update is immediately visible, using this hook to reject a
+push will leave the pusher with a view that does not match the server.
+
 push-to-checkout
 ~~~~~~~~~~~~~~~~
 
diff --git a/Documentation/gitpacking.adoc b/Documentation/gitpacking.adoc
index e6de6ec..b0d952c 100644
--- a/Documentation/gitpacking.adoc
+++ b/Documentation/gitpacking.adoc
@@ -5,10 +5,6 @@
 ----
 gitpacking - Advanced concepts related to packing in Git
 
-SYNOPSIS
---------
-gitpacking
-
 DESCRIPTION
 -----------
 
diff --git a/Documentation/gitremote-helpers.adoc b/Documentation/gitremote-helpers.adoc
index 39cdece..4d794f3 100644
--- a/Documentation/gitremote-helpers.adoc
+++ b/Documentation/gitremote-helpers.adoc
@@ -86,7 +86,7 @@
 
 Each remote helper is expected to support only a subset of commands.
 The operations a helper supports are declared to Git in the response
-to the `capabilities` command (see COMMANDS, below).
+to the `capabilities` command (see <<COMMANDS,COMMANDS>>, below).
 
 In the following, we list all defined capabilities and for
 each we list which commands a helper with that capability
@@ -124,7 +124,7 @@
 
 If a helper advertises 'connect', Git will use it if possible and
 fall back to another capability if the helper requests so when
-connecting (see the 'connect' command under COMMANDS).
+connecting (see the 'connect' command under <<COMMANDS,COMMANDS>>).
 When choosing between 'push' and 'export', Git prefers 'push'.
 Other frontends may have some other order of preference.
 
@@ -173,7 +173,7 @@
 
 If a helper advertises 'connect', Git will use it if possible and
 fall back to another capability if the helper requests so when
-connecting (see the 'connect' command under COMMANDS).
+connecting (see the 'connect' command under <<COMMANDS,COMMANDS>>).
 When choosing between 'fetch' and 'import', Git prefers 'fetch'.
 Other frontends may have some other order of preference.
 
@@ -246,6 +246,7 @@
 	side using an explicit hash algorithm extension.
 
 
+[[COMMANDS]]
 COMMANDS
 --------
 
@@ -269,8 +270,10 @@
 	unrecognized attributes are ignored. The list ends with a
 	blank line.
 +
-See REF LIST ATTRIBUTES for a list of currently defined attributes.
-See REF LIST KEYWORDS for a list of currently defined keywords.
+See <<REF_LIST_ATTRIBUTES,REF LIST ATTRIBUTES>> for a list of currently
+defined attributes.
+See <<REF_LIST_KEYWORDS,REF LIST KEYWORDS>> for a list of currently
+defined keywords.
 +
 Supported if the helper has the "fetch" or "import" capability.
 
@@ -435,6 +438,7 @@
 Additional commands may be supported, as may be determined from
 capabilities reported by the helper.
 
+[[REF_LIST_ATTRIBUTES]]
 REF LIST ATTRIBUTES
 -------------------
 
@@ -446,6 +450,7 @@
 	This ref is unchanged since the last import or fetch, although
 	the helper cannot necessarily determine what value that produced.
 
+[[REF_LIST_KEYWORDS]]
 REF LIST KEYWORDS
 -----------------
 
diff --git a/Documentation/gitrevisions.adoc b/Documentation/gitrevisions.adoc
index 7146117..4412f84 100644
--- a/Documentation/gitrevisions.adoc
+++ b/Documentation/gitrevisions.adoc
@@ -5,11 +5,6 @@
 ----
 gitrevisions - Specifying revisions and ranges for Git
 
-SYNOPSIS
---------
-gitrevisions
-
-
 DESCRIPTION
 -----------
 
diff --git a/Documentation/gitsubmodules.adoc b/Documentation/gitsubmodules.adoc
index 2082296..8e81656 100644
--- a/Documentation/gitsubmodules.adoc
+++ b/Documentation/gitsubmodules.adoc
@@ -21,7 +21,8 @@
 The submodule has its own history; the repository it is embedded
 in is called a superproject.
 
-On the filesystem, a submodule usually (but not always - see FORMS below)
+On the filesystem, a submodule usually
+(but not always - see <<FORMS,FORMS>> below)
 consists of (i) a Git directory located under the `$GIT_DIR/modules/`
 directory of its superproject, (ii) a working directory inside the
 superproject's working directory, and a `.git` file at the root of
@@ -102,8 +103,8 @@
 file.
 
  * The configuration file `$GIT_DIR/config` in the superproject.
-   Git only recurses into active submodules (see "ACTIVE SUBMODULES"
-   section below).
+   Git only recurses into active submodules
+   (see <<ACTIVE_SUBMODULES,"ACTIVE SUBMODULES">> section below).
 +
 If the submodule is not yet initialized, then the configuration
 inside the submodule does not exist yet, so where to
@@ -122,6 +123,7 @@
 where submodule configuration is found. It serves as the last fallback
 to specify where to obtain the submodule from.
 
+[[FORMS]]
 FORMS
 -----
 
@@ -165,6 +167,7 @@
 To completely remove a submodule, manually delete
 `$GIT_DIR/modules/<name>/`.
 
+[[ACTIVE_SUBMODULES]]
 ACTIVE SUBMODULES
 -----------------
 
diff --git a/Documentation/gittutorial-2.adoc b/Documentation/gittutorial-2.adoc
index 8bdb7d0..6a4d482 100644
--- a/Documentation/gittutorial-2.adoc
+++ b/Documentation/gittutorial-2.adoc
@@ -5,11 +5,6 @@
 ----
 gittutorial-2 - A tutorial introduction to Git: part two
 
-SYNOPSIS
---------
-[verse]
-git *
-
 DESCRIPTION
 -----------
 
diff --git a/Documentation/gittutorial.adoc b/Documentation/gittutorial.adoc
index 519b8d8..03120ba 100644
--- a/Documentation/gittutorial.adoc
+++ b/Documentation/gittutorial.adoc
@@ -5,11 +5,6 @@
 ----
 gittutorial - A tutorial introduction to Git
 
-SYNOPSIS
---------
-[verse]
-git *
-
 DESCRIPTION
 -----------
 
diff --git a/Documentation/gitworkflows.adoc b/Documentation/gitworkflows.adoc
index 5930526..49291fe 100644
--- a/Documentation/gitworkflows.adoc
+++ b/Documentation/gitworkflows.adoc
@@ -5,12 +5,6 @@
 ----
 gitworkflows - An overview of recommended workflows with Git
 
-SYNOPSIS
---------
-[verse]
-git *
-
-
 DESCRIPTION
 -----------
 
@@ -245,8 +239,9 @@
 `git tag -s -m "Git X.Y.Z" vX.Y.Z master`
 =====================================
 
-You need to push the new tag to a public Git server (see
-"DISTRIBUTED WORKFLOWS" below). This makes the tag available to
+You need to push the new tag to a public Git server
+(see <<DISTRIBUTED_WORKFLOWS,"DISTRIBUTED WORKFLOWS">> below).
+This makes the tag available to
 others tracking your project. The push could also trigger a
 post-update hook to perform release-related items such as building
 release tarballs and preformatted documentation pages.
@@ -324,6 +319,7 @@
 described above.
 
 
+[[DISTRIBUTED_WORKFLOWS]]
 DISTRIBUTED WORKFLOWS
 ---------------------
 
diff --git a/Documentation/howto/revert-a-faulty-merge.adoc b/Documentation/howto/revert-a-faulty-merge.adoc
index 19f59cc..fc9a330 100644
--- a/Documentation/howto/revert-a-faulty-merge.adoc
+++ b/Documentation/howto/revert-a-faulty-merge.adoc
@@ -146,8 +146,8 @@
    revert of a merge was rebuilt from scratch (i.e. rebasing and fixing,
    as you seem to have interpreted), then re-merging the result without
    doing anything else fancy would be the right thing to do.
-   (See the ADDENDUM below for how to rebuild a branch from scratch
-   without changing its original branching-off point.)
+   (See the <<ADDENDUM,ADDENDUM>> below for how to rebuild a branch
+   from scratch without changing its original branching-off point.)
 
 However, there are things to keep in mind when reverting a merge (and
 reverting such a revert).
@@ -184,6 +184,7 @@
 really should revert the merge, but when you want to re-do the merge, you
 now need to do it by reverting the revert.
 
+[[ADDENDUM]]
 ADDENDUM
 
 Sometimes you have to rewrite one of a topic branch's commits *and* you can't
diff --git a/Documentation/lint-man-section-order.perl b/Documentation/lint-man-section-order.perl
index 02408a0..35af3b8 100755
--- a/Documentation/lint-man-section-order.perl
+++ b/Documentation/lint-man-section-order.perl
@@ -13,6 +13,9 @@
 		},
 		'SYNOPSIS' => {
 			required => 1,
+			optional_in_man_sections => {
+				'7' => 1,
+			},
 			order => $order++,
 		},
 		'DESCRIPTION' => {
@@ -53,10 +56,22 @@
 	$exit_code = 1;
 }
 
+my $man_section_number;
 my $last_was_section;
 my @actual_order;
 while (my $line = <>) {
 	chomp $line;
+
+	if ($. == 1) {
+		$line =~ m/\((\d)\)/;
+		$man_section_number = $1;
+		if (!$man_section_number) {
+			report("first line must be formatted like 'gitfaq(7)'");
+			# exit to avoid dealing with $man_section_number being undefined later
+			last;
+		}
+	}
+
 	if ($line =~ $SECTION_RX) {
 		push @actual_order => $line;
 		$last_was_section = 1;
@@ -92,7 +107,9 @@
 		@actual_sections{@actual_order} = ();
 
 		for my $section (sort keys %SECTIONS) {
-			next if !$SECTIONS{$section}->{required} or exists $actual_sections{$section};
+			next if !$SECTIONS{$section}->{required} or
+				$SECTIONS{$section}->{optional_in_man_sections}->{$man_section_number} or
+				exists $actual_sections{$section};
 			report("has no required '$section' section!");
 		}
 
diff --git a/Documentation/meson.build b/Documentation/meson.build
index f4854f8..cfa9c67 100644
--- a/Documentation/meson.build
+++ b/Documentation/meson.build
@@ -379,13 +379,18 @@
       output: fs.stem(manpage) + '.xml',
     )
 
+    man_base_url = 'file://' + htmldir + '/'
+    if get_option('man_base_url') != ''
+      man_base_url = get_option('man_base_url')
+    endif
+
     doc_targets += custom_target(
       command: [
         xmlto,
         '-m', '@INPUT0@',
         '-m', '@INPUT1@',
         '--stringparam',
-        'man.base.url.for.relative.links=' + get_option('prefix') / get_option('mandir'),
+        'man.base.url.for.relative.links=' + man_base_url,
         'man',
         manpage_xml_target,
         '-o',
diff --git a/Documentation/revisions.adoc b/Documentation/revisions.adoc
index 3fbfbd3..0ef2c89 100644
--- a/Documentation/revisions.adoc
+++ b/Documentation/revisions.adoc
@@ -1,3 +1,4 @@
+[[SPECIFYING_REVISIONS]]
 SPECIFYING REVISIONS
 --------------------
 
@@ -122,7 +123,7 @@
   `branch.<name>.remote`). B@{u} refers to the remote-tracking branch for
   the branch X taken from remote R, typically found at `refs/remotes/R/X`.
 
-'[<branchname>]@\{push\}', e.g. 'master@\{push\}', '@\{push\}'::
+'[<branchname>]@\{push\}', e.g. 'master@\{push\}', '@\{p\}'::
   The suffix '@\{push}' reports the branch "where we would push to" if
   `git push` were run while `branchname` was checked out (or the current
   `HEAD` if no branchname is specified). Like for '@\{upstream\}', we report
@@ -300,7 +301,8 @@
 The '..' (two-dot) Range Notation::
  The '{caret}r1 r2' set operation appears so often that there is a shorthand
  for it.  When you have two commits 'r1' and 'r2' (named according
- to the syntax explained in SPECIFYING REVISIONS above), you can ask
+ to the syntax explained in
+<<SPECIFYING_REVISIONS,SPECIFYING REVISIONS>> above), you can ask
  for commits that are reachable from r2 excluding those that are reachable
  from r1 by '{caret}r1 r2' and it can be written as 'r1..r2'.
 
diff --git a/Documentation/technical/api-merge.adoc b/Documentation/technical/api-merge.adoc
index c2ba018..b691599 100644
--- a/Documentation/technical/api-merge.adoc
+++ b/Documentation/technical/api-merge.adoc
@@ -20,11 +20,10 @@
 Data structures
 ---------------
 
-* `mmbuffer_t`, `mmfile_t`
+* `mmfile_t`
 
-These store data usable for use by the xdiff backend, for writing and
-for reading, respectively.  See `xdiff/xdiff.h` for the definitions
-and `diff.c` for examples.
+This stores a buffer and its size for input to or output from the xdiff
+backend. See `xdiff/xdiff.h` for the definition and `diff.c` for examples.
 
 * `struct ll_merge_options`
 
diff --git a/Documentation/technical/api-parse-options.adoc b/Documentation/technical/api-parse-options.adoc
index 95b7924..6dfea34 100644
--- a/Documentation/technical/api-parse-options.adoc
+++ b/Documentation/technical/api-parse-options.adoc
@@ -243,6 +243,7 @@
 	Start an option group. `description` is a short string that
 	describes the group or an empty string.
 	Start the description with an upper-case letter.
+	Groups apply to options and subcommands that have a help string.
 
 `OPT_HIDDEN_GROUP(description)`::
 	Like `OPT_GROUP()`, but the group header carries
@@ -362,7 +363,8 @@
 
 `OPT_SUBCOMMAND(long, &fn_ptr, subcommand_fn)`::
 	Define a subcommand.  `subcommand_fn` is put into `fn_ptr` when
-	this subcommand is used.
+	this subcommand is used. The subcommand is not listed in the
+	usage output.
 
 The last element of the array must be `OPT_END()`.
 
diff --git a/Documentation/technical/hash-function-transition.adoc b/Documentation/technical/hash-function-transition.adoc
index 241d2f7..f6b38dd 100644
--- a/Documentation/technical/hash-function-transition.adoc
+++ b/Documentation/technical/hash-function-transition.adoc
@@ -29,7 +29,8 @@
 
 Over time some flaws in SHA-1 have been discovered by security
 researchers. On 23 February 2017 the SHAttered attack
-(https://shattered.io) demonstrated a practical SHA-1 hash collision.
+(https://web.archive.org/web/20260207211148/https://shattered.io/)
+demonstrated a practical SHA-1 hash collision.
 
 Git v2.13.0 and later subsequently moved to a hardened SHA-1
 implementation by default, which isn't vulnerable to the SHAttered
diff --git a/Documentation/technical/racy-git.adoc b/Documentation/technical/racy-git.adoc
index 59bea66..4992315 100644
--- a/Documentation/technical/racy-git.adoc
+++ b/Documentation/technical/racy-git.adoc
@@ -39,8 +39,8 @@
 timestamps, `st_uid`, `st_gid`, `st_ino`, and `st_size` members.
 With a `USE_STDEV` compile-time option, `st_dev` is also
 compared, but this is not enabled by default because this member
-is not stable on network filesystems.  With `USE_NSEC`
-compile-time option, `st_mtim.tv_nsec` and `st_ctim.tv_nsec`
+is not stable on network filesystems.  With 'core.useNanosec'
+config setting, `st_mtim.tv_nsec` and `st_ctim.tv_nsec`
 members are also compared. On Linux, this is not enabled by default
 because in-core timestamps can have finer granularity than
 on-disk timestamps, resulting in meaningless changes when an
@@ -49,9 +49,10 @@
 ([PATCH] Sync in core time granularity with filesystems,
 2005-01-04). This patch is included in kernel 2.6.11 and newer, but
 only fixes the issue for file systems with exactly 1 ns or 1 s
-resolution. Other file systems are still broken in current Linux
-kernels (e.g. CEPH, CIFS, NTFS, UDF), see
-https://lore.kernel.org/lkml/5577240D.7020309@gmail.com/
+resolution.  As of kernel 4.3, other file systems (CEPH, CIFS, NTFS, UFS, FUSE)
+were fixed; see https://public-inbox.org/git/5605D88A.20104%40gmail.com/.  FAT
+has been fixed since 2015.  The usual suspects (ext2, ext4, XFS) are known to
+work, too.
 
 Racy Git
 --------
diff --git a/Documentation/user-manual.adoc b/Documentation/user-manual.adoc
index 5ec65ce..1652ab3 100644
--- a/Documentation/user-manual.adoc
+++ b/Documentation/user-manual.adoc
@@ -632,7 +632,7 @@
 $ git tag stable-1 1b2e1d63ff
 -------------------------------------------------
 
-You can use `stable-1` to refer to the commit 1b2e1d63ff.
+you can use `stable-1` to refer to the commit 1b2e1d63ff.
 
 This creates a "lightweight" tag.  If you would also like to include a
 comment with the tag, and possibly sign it cryptographically, then you
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 64f4abd..5ade9ce 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-DEF_VER=v2.56.0
+DEF_VER=v2.56.GIT
 
 LF='
 '
diff --git a/Makefile b/Makefile
index c649c93..a96be50 100644
--- a/Makefile
+++ b/Makefile
@@ -197,18 +197,11 @@
 # Define NO_NORETURN if using buggy versions of gcc 4.6+ and profile feedback,
 # as the compiler can crash (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=49299)
 #
-# Define USE_NSEC below if you want git to care about sub-second file mtimes
-# and ctimes. Note that you need recent glibc (at least 2.2.4) for this. On
-# Linux, kernel 2.6.11 or newer is required for reliable sub-second file times
-# on file systems with exactly 1 ns or 1 s resolution. If you intend to use Git
-# on other file systems (e.g. CEPH, CIFS, NTFS, UDF), don't enable USE_NSEC. See
-# Documentation/technical/racy-git.adoc for details.
-#
 # Define USE_ST_TIMESPEC if your "struct stat" uses "st_ctimespec" instead of
 # "st_ctim"
 #
 # Define NO_NSEC if your "struct stat" does not have "st_ctim.tv_nsec"
-# available.  This automatically turns USE_NSEC off.
+# available.
 #
 # Define USE_STDEV below if you want git to care about the underlying device
 # change being considered an inode change from the update-index perspective.
@@ -321,6 +314,9 @@
 # dependency rules.  The default is "auto", which means to use computed header
 # dependencies if your compiler is detected to support it.
 #
+# Define NO_PRECOMPILED_HEADER if you want to build Git without precompiling
+# "git-compat-util.h".
+#
 # Define NATIVE_CRLF if your platform uses CRLF for line endings.
 #
 # Define GIT_USER_AGENT if you want to change how git identifies itself during
@@ -695,7 +691,6 @@
 BUILT_INS =
 COMPAT_CFLAGS =
 COMPAT_OBJS =
-XDIFF_OBJS =
 GENERATED_H =
 EXTRA_CPPFLAGS =
 FUZZ_OBJS =
@@ -708,6 +703,7 @@
 OTHER_PROGRAMS =
 PROGRAM_OBJS =
 PROGRAMS =
+REFTABLE_OBJS =
 RUST_SOURCES =
 EXCLUDED_PROGRAMS =
 SCRIPT_PERL =
@@ -1285,20 +1281,6 @@
 LIB_OBJS += refs/packed-backend.o
 LIB_OBJS += refs/ref-cache.o
 LIB_OBJS += refspec.o
-LIB_OBJS += reftable/basics.o
-LIB_OBJS += reftable/block.o
-LIB_OBJS += reftable/blocksource.o
-LIB_OBJS += reftable/error.o
-LIB_OBJS += reftable/fsck.o
-LIB_OBJS += reftable/iter.o
-LIB_OBJS += reftable/merged.o
-LIB_OBJS += reftable/pq.o
-LIB_OBJS += reftable/record.o
-LIB_OBJS += reftable/stack.o
-LIB_OBJS += reftable/system.o
-LIB_OBJS += reftable/table.o
-LIB_OBJS += reftable/tree.o
-LIB_OBJS += reftable/writer.o
 LIB_OBJS += remote.o
 LIB_OBJS += repack.o
 LIB_OBJS += repack-cruft.o
@@ -1387,6 +1369,23 @@
 LIB_OBJS += xdiff/xprepare.o
 LIB_OBJS += xdiff/xutils.o
 
+REFTABLE_OBJS += reftable/basics.o
+REFTABLE_OBJS += reftable/block.o
+REFTABLE_OBJS += reftable/blocksource.o
+REFTABLE_OBJS += reftable/error.o
+REFTABLE_OBJS += reftable/fsck.o
+REFTABLE_OBJS += reftable/iter.o
+REFTABLE_OBJS += reftable/merged.o
+REFTABLE_OBJS += reftable/pq.o
+REFTABLE_OBJS += reftable/record.o
+REFTABLE_OBJS += reftable/stack.o
+REFTABLE_OBJS += reftable/system.o
+REFTABLE_OBJS += reftable/table.o
+REFTABLE_OBJS += reftable/tree.o
+REFTABLE_OBJS += reftable/writer.o
+
+LIB_OBJS += $(REFTABLE_OBJS)
+
 BUILTIN_OBJS += builtin/add.o
 BUILTIN_OBJS += builtin/am.o
 BUILTIN_OBJS += builtin/annotate.o
@@ -1935,9 +1934,6 @@
 ifdef NO_ST_BLOCKS_IN_STRUCT_STAT
 	BASIC_CFLAGS += -DNO_ST_BLOCKS_IN_STRUCT_STAT
 endif
-ifdef USE_NSEC
-	BASIC_CFLAGS += -DUSE_NSEC
-endif
 ifdef USE_ST_TIMESPEC
 	BASIC_CFLAGS += -DUSE_ST_TIMESPEC
 endif
@@ -2902,8 +2898,15 @@
 .PHONY: objects
 objects: $(OBJECTS)
 
-dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
-dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
+PRECOMPILED_HEADER := tools/precompiled.h
+PRECOMPILED_HEADER_GCH := $(addsuffix .gch,$(PRECOMPILED_HEADER))
+
+ifndef NO_PRECOMPILED_HEADER
+PRECOMPILED_HEADER_USERS := $(filter-out $(COMPAT_OBJS) $(REFTABLE_OBJS) $(patsubst %.c,%.o,$(THIRD_PARTY_SOURCES)),$(OBJECTS))
+endif
+
+dep_files := $(foreach f,$(OBJECTS) $(PRECOMPILED_HEADER_GCH),$(dir $f).depend/$(notdir $f).d)
+dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS) $(PRECOMPILED_HEADER_GCH))))
 
 ifeq ($(uname_S),Darwin)
 	dep_dirs += $(addsuffix .depend,$(sort $(dir contrib/credential/osxkeychain/git-credential-osxkeychain.o)))
@@ -2937,7 +2940,13 @@
 compdb_args =
 endif
 
-$(OBJECTS): %.o: %.c GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir)
+$(PRECOMPILED_HEADER_GCH): %.gch: % GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir)
+	$(QUIET_CC)$(CC) -o $@ -c $(dep_args) $(compdb_args) $(ALL_CFLAGS) $<
+
+$(PRECOMPILED_HEADER_USERS): %.o: %.c $(PRECOMPILED_HEADER_GCH) GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir)
+	$(QUIET_CC)$(CC) -o $*.o -c -include $(PRECOMPILED_HEADER) -Winvalid-pch $(dep_args) $(compdb_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
+
+$(filter-out $(PRECOMPILED_HEADER_USERS),$(OBJECTS)): %.o: %.c GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir)
 	$(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(compdb_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
 
 %.s: %.c GIT-CFLAGS FORCE
@@ -2951,7 +2960,7 @@
 include $(dep_files_present)
 endif
 else
-$(OBJECTS): $(LIB_H) $(GENERATED_H)
+$(OBJECTS) $(PRECOMPILED_HEADER_GCH): $(LIB_H) $(GENERATED_H)
 endif
 
 ifeq ($(GENERATE_COMPILATION_DATABASE),yes)
@@ -3905,7 +3914,7 @@
 	$(RM) GIT-TEST-SUITES
 	$(RM) po/git.pot po/git-core.pot
 	$(RM) git.rc git.res
-	$(RM) $(OBJECTS)
+	$(RM) $(OBJECTS) $(PRECOMPILED_HEADER_GCH)
 	$(RM) headless-git.o
 	$(RM) $(LIB_FILE)
 	$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS)
diff --git a/RelNotes b/RelNotes
index 752580e..513bc0b 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.56.0.adoc
\ No newline at end of file
+Documentation/RelNotes/2.98.0.adoc
\ No newline at end of file
diff --git a/advice.c b/advice.c
index 63bf8b0..401d047 100644
--- a/advice.c
+++ b/advice.c
@@ -58,6 +58,7 @@ static struct {
 	[ADVICE_FETCH_SHOW_FORCED_UPDATES]		= { "fetchShowForcedUpdates" },
 	[ADVICE_FORCE_DELETE_BRANCH]			= { "forceDeleteBranch" },
 	[ADVICE_GRAFT_FILE_DEPRECATED]			= { "graftFileDeprecated" },
+	[ADVICE_HISTORY_UPDATE_REFS]			= { "historyUpdateRefs" },
 	[ADVICE_IGNORED_HOOK]				= { "ignoredHook" },
 	[ADVICE_IMPLICIT_IDENTITY]			= { "implicitIdentity" },
 	[ADVICE_MERGE_CONFLICT]				= { "mergeConflict" },
diff --git a/advice.h b/advice.h
index 66f6cd6..3f0b4f0 100644
--- a/advice.h
+++ b/advice.h
@@ -25,6 +25,7 @@ enum advice_type {
 	ADVICE_FETCH_SHOW_FORCED_UPDATES,
 	ADVICE_FORCE_DELETE_BRANCH,
 	ADVICE_GRAFT_FILE_DEPRECATED,
+	ADVICE_HISTORY_UPDATE_REFS,
 	ADVICE_IGNORED_HOOK,
 	ADVICE_IMPLICIT_IDENTITY,
 	ADVICE_MERGE_CONFLICT,
diff --git a/apply.c b/apply.c
index f00b7ba..a0b7119 100644
--- a/apply.c
+++ b/apply.c
@@ -14,6 +14,7 @@
 #include "abspath.h"
 #include "base85.h"
 #include "config.h"
+#include "date.h"
 #include "odb.h"
 #include "delta.h"
 #include "diff.h"
@@ -851,7 +852,7 @@ static int has_epoch_timestamp(const char *nameline)
 	if (*colon == ':')
 		zoneoffset = zoneoffset * 60 + strtol(colon + 1, NULL, 10);
 	else
-		zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100);
+		zoneoffset = tz_to_minutes(zoneoffset);
 	if (timestamp[m[3].rm_so] == '-')
 		zoneoffset = -zoneoffset;
 
@@ -3646,7 +3647,7 @@ static int three_way_merge(struct apply_state *state,
 {
 	mmfile_t base_file, our_file, their_file;
 	struct ll_merge_options merge_opts = LL_MERGE_OPTIONS_INIT;
-	mmbuffer_t result = { NULL };
+	mmfile_t result = { NULL };
 	enum ll_merge_result status;
 
 	/* resolve trivial cases first */
diff --git a/builtin/checkout.c b/builtin/checkout.c
index c0f0d2c..2d575a4 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -320,7 +320,7 @@ static int checkout_merged(int pos, const struct checkout *state,
 	enum ll_merge_result merge_status;
 	int status;
 	struct object_id oid;
-	mmbuffer_t result_buf;
+	mmfile_t result_buf;
 	struct object_id threeway[3];
 	unsigned mode = 0;
 	struct ll_merge_options ll_opts = LL_MERGE_OPTIONS_INIT;
diff --git a/builtin/clone.c b/builtin/clone.c
index 5b25cca..44037dd 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
 	return canon;
 }
 
-static int add_one_reference(struct string_list_item *item, void *cb_data)
+struct collect_alternates_data {
+	struct strvec *alternates;
+	bool required;
+};
+
+static int collect_one_alternate(struct string_list_item *item, void *cb_data)
 {
+	struct collect_alternates_data *data = cb_data;
 	struct strbuf err = STRBUF_INIT;
-	int *required = cb_data;
 	char *ref_git = compute_alternate_path(item->string, &err);
 
 	if (!ref_git) {
-		if (*required)
+		if (data->required)
 			die("%s", err.buf);
 		else
 			fprintf(stderr,
 				_("info: Could not add alternate for '%s': %s\n"),
 				item->string, err.buf);
 	} else {
-		struct strbuf sb = STRBUF_INIT;
-		strbuf_addf(&sb, "%s/objects", ref_git);
-		odb_add_to_alternates_file(the_repository->objects, sb.buf);
-		strbuf_release(&sb);
+		strvec_pushf(data->alternates, "%s/objects", ref_git);
 	}
 
 	strbuf_release(&err);
@@ -179,17 +181,7 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
 	return 0;
 }
 
-static void setup_reference(void)
-{
-	int required = 1;
-	for_each_string_list(&option_required_reference,
-			     add_one_reference, &required);
-	required = 0;
-	for_each_string_list(&option_optional_reference,
-			     add_one_reference, &required);
-}
-
-static void copy_alternates(struct strbuf *src, const char *src_repo)
+static void read_alternates(struct strvec *alternates, const char *src_repo)
 {
 	/*
 	 * Read from the source objects/info/alternates file
@@ -203,29 +195,70 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	 * to turn entries with paths relative to the original
 	 * absolute, so that they can be used in the new repository.
 	 */
-	FILE *in = xfopen(src->buf, "r");
+	FILE *in;
+	struct strbuf path = STRBUF_INIT;
 	struct strbuf line = STRBUF_INIT;
 
+	strbuf_addf(&path, "%s/objects/info/alternates", src_repo);
+
+	in = fopen(path.buf, "r");
+	if (!in) {
+		if (errno == ENOENT)
+			goto out;
+		die_errno("could not read alternates file '%s'", path.buf);
+	}
+
 	while (strbuf_getline(&line, in) != EOF) {
 		char *abs_path;
 		if (!line.len || line.buf[0] == '#')
 			continue;
 		if (is_absolute_path(line.buf)) {
-			odb_add_to_alternates_file(the_repository->objects,
-						   line.buf);
+			strvec_push(alternates, line.buf);
 			continue;
 		}
 		abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
 		if (!normalize_path_copy(abs_path, abs_path))
-			odb_add_to_alternates_file(the_repository->objects,
-						   abs_path);
+			strvec_push(alternates, abs_path);
 		else
 			warning("skipping invalid relative alternate: %s/%s",
 				src_repo, line.buf);
 		free(abs_path);
 	}
+
+out:
+	strbuf_release(&path);
 	strbuf_release(&line);
-	fclose(in);
+	if (in)
+		fclose(in);
+}
+
+static void collect_alternates(struct strvec *alternates,
+			       const char *local_source_repo)
+{
+	if (option_required_reference.nr || option_optional_reference.nr) {
+		struct collect_alternates_data data = {
+			.alternates = alternates,
+			.required = true,
+		};
+
+		for_each_string_list(&option_required_reference,
+				     collect_one_alternate, &data);
+		data.required = false;
+		for_each_string_list(&option_optional_reference,
+				     collect_one_alternate, &data);
+	}
+
+	if (local_source_repo) {
+		struct strbuf commondir = STRBUF_INIT;
+
+		get_common_dir(&commondir, local_source_repo);
+		if (option_shared)
+			strvec_pushf(alternates, "%s/objects", commondir.buf);
+		else
+			read_alternates(alternates, commondir.buf);
+
+		strbuf_release(&commondir);
+	}
 }
 
 static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -301,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 			continue;
 		}
 
-		/* Files that cannot be copied bit-for-bit... */
-		if (!fspathcmp(iter->relative_path, "info/alternates")) {
-			copy_alternates(src, src_repo);
+		/* Alternates were already handled earlier. */
+		if (!fspathcmp(iter->relative_path, "info/alternates"))
 			continue;
-		}
 
 		if (unlink(dest->buf) && errno != ENOENT)
 			die_errno(_("failed to unlink '%s'"), dest->buf);
@@ -349,13 +380,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 
 static void clone_local(const char *src_repo, const char *dest_repo)
 {
-	if (option_shared) {
-		struct strbuf alt = STRBUF_INIT;
-		get_common_dir(&alt, src_repo);
-		strbuf_addstr(&alt, "/objects");
-		odb_add_to_alternates_file(the_repository->objects, alt.buf);
-		strbuf_release(&alt);
-	} else {
+	if (!option_shared) {
 		struct strbuf src = STRBUF_INIT;
 		struct strbuf dest = STRBUF_INIT;
 		get_common_dir(&src, src_repo);
@@ -725,7 +750,7 @@ static int checkout(int submodule_progress,
 		}
 
 		if (ref_storage_format != REF_STORAGE_FORMAT_UNKNOWN)
-			strvec_pushf(&cmd.args, "--ref-format=%s",
+			strvec_pushf(&cmd.args, "--ref-storage-format=%s",
 				     ref_storage_format_to_name(ref_storage_format));
 
 		if (filter_submodules && filter_options->choice)
@@ -901,7 +926,7 @@ int cmd_clone(int argc,
 	char *option_origin = NULL;
 	struct string_list option_not = STRING_LIST_INIT_NODUP;
 	const char *real_git_dir = NULL;
-	const char *ref_format = NULL;
+	const char *ref_storage_format_uri = NULL;
 	const char *option_upload_pack = "git-upload-pack";
 	int option_progress = -1;
 	int option_sparse_checkout = 0;
@@ -981,8 +1006,9 @@ int cmd_clone(int argc,
 			 N_("any cloned submodules will be shallow")),
 		OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
 			   N_("separate git dir from working tree")),
-		OPT_STRING(0, "ref-format", &ref_format, N_("format"),
-			   N_("specify the reference format to use")),
+		OPT_STRING(0, "ref-storage-format", &ref_storage_format_uri, N_("format"),
+			   N_("specify the reference storage format to use")),
+		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
 		OPT_STRING_LIST('c', "config", &option_config, N_("key=value"),
 				N_("set config inside the new repository")),
 		OPT_STRING_LIST(0, "server-option", &server_options,
@@ -999,6 +1025,7 @@ int cmd_clone(int argc,
 			   N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")),
 		OPT_END()
 	};
+	struct strvec alternates = STRVEC_INIT;
 
 	const char * const builtin_clone_usage[] = {
 		N_("git clone [<options>] [--] <repo> [<dir>]"),
@@ -1027,10 +1054,10 @@ int cmd_clone(int argc,
 	if (option_single_branch == -1)
 		option_single_branch = deepen ? 1 : 0;
 
-	if (ref_format) {
-		ref_storage_format = ref_storage_format_by_name(ref_format);
+	if (ref_storage_format_uri) {
+		ref_storage_format = ref_storage_format_by_uri(ref_storage_format_uri, NULL);
 		if (ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
-			die(_("unknown ref storage format '%s'"), ref_format);
+			die(_("unknown ref storage format '%s'"), ref_storage_format_uri);
 	}
 
 	if (option_mirror) {
@@ -1185,10 +1212,9 @@ int cmd_clone(int argc,
 	 * repository, and reference backends may persist that information into
 	 * their on-disk data structures.
 	 */
-	init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
-		GIT_HASH_UNKNOWN, ref_storage_format, NULL,
-		do_not_override_repo_unix_permissions,
-		INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
+	create_repository(the_repository, git_dir, real_git_dir, work_tree,
+			  option_template, GIT_HASH_UNKNOWN, ref_storage_format_uri,
+			  do_not_override_repo_unix_permissions, NULL);
 
 	if (real_git_dir) {
 		free((char *)git_dir);
@@ -1311,9 +1337,6 @@ int cmd_clone(int argc,
 		strbuf_reset(&key);
 	}
 
-	if (option_required_reference.nr || option_optional_reference.nr)
-		setup_reference();
-
 	remote = remote_get_early(remote_name);
 
 	if (!option_rev)
@@ -1342,6 +1365,9 @@ int cmd_clone(int argc,
 	if (option_local > 0 && !is_local)
 		warning(_("--local is ignored"));
 
+	collect_alternates(&alternates, is_local ? path : NULL);
+	create_object_database(the_repository, &alternates);
+
 	transport = transport_get(remote, path ? path : remote->url.v[0]);
 	transport_set_verbosity(transport, option_verbosity, option_progress);
 	transport->family = family;
@@ -1445,6 +1471,7 @@ int cmd_clone(int argc,
 	initialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1);
 	repo_set_hash_algo(the_repository, hash_algo);
 	create_reference_database(the_repository, NULL, 1);
+	startup_info->have_repository = 1;
 
 	/*
 	 * Before fetching from the remote, download and install bundle
@@ -1637,6 +1664,7 @@ int cmd_clone(int argc,
 	string_list_clear(&option_not, 0);
 	string_list_clear(&option_config, 0);
 	string_list_clear(&server_options, 0);
+	strvec_clear(&alternates);
 
 	free(remote_name);
 	strbuf_release(&reflog_msg);
diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c
index d62005e..ff244c8 100644
--- a/builtin/commit-graph.c
+++ b/builtin/commit-graph.c
@@ -8,6 +8,8 @@
 #include "parse-options.h"
 #include "commit-graph.h"
 #include "odb.h"
+#include "odb/source.h"
+#include "odb/source-files.h"
 #include "progress.h"
 #include "replace-object.h"
 #include "strbuf.h"
@@ -67,7 +69,7 @@ static int graph_verify(int argc, const char **argv, const char *prefix,
 			struct repository *repo UNUSED)
 {
 	struct commit_graph *graph = NULL;
-	struct odb_source *source = NULL;
+	struct odb_files_dir *dir = NULL;
 	char *graph_name;
 	char *chain_name;
 	enum { OPENED_NONE, OPENED_GRAPH, OPENED_CHAIN } opened = OPENED_NONE;
@@ -102,9 +104,12 @@ static int graph_verify(int argc, const char **argv, const char *prefix,
 	if (opts.progress)
 		flags |= COMMIT_GRAPH_WRITE_PROGRESS;
 
-	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);
+	dir = odb_source_files_find_dir(the_repository->objects, opts.obj_dir);
+	if (!dir)
+		die(_("could not find object directory matching %s"), opts.obj_dir);
+
+	graph_name = get_commit_graph_filename(dir->abspath);
+	chain_name = get_commit_graph_chain_filename(dir->abspath);
 	if (open_commit_graph(graph_name, &fd, &st))
 		opened = OPENED_GRAPH;
 	else if (errno != ENOENT)
@@ -122,7 +127,7 @@ 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(source, fd, &st);
+		graph = load_commit_graph_one_fd_st(the_repository, dir->abspath, fd, &st);
 	else
 		graph = load_commit_graph_chain_fd_st(the_repository->objects, fd, &st,
 						      &incomplete_chain);
@@ -225,7 +230,7 @@ static int graph_write(int argc, const char **argv, const char *prefix,
 	struct string_list pack_indexes = STRING_LIST_INIT_DUP;
 	struct strbuf buf = STRBUF_INIT;
 	struct oidset commits = OIDSET_INIT;
-	struct odb_source *source = NULL;
+	struct odb_files_dir *dir = NULL;
 	int result = 0;
 	enum commit_graph_write_flags flags = 0;
 	struct progress *progress = NULL;
@@ -293,18 +298,30 @@ 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_or_die(the_repository->objects, opts.obj_dir);
+	dir = odb_source_files_find_dir(the_repository->objects, opts.obj_dir);
+	if (!dir)
+		die(_("could not find object directory matching %s"), opts.obj_dir);
 
 	if (opts.reachable) {
-		if (write_commit_graph_reachable(source, flags, &write_opts))
+		if (write_commit_graph_reachable(the_repository, dir->abspath, flags, &write_opts))
 			result = 1;
 		goto cleanup;
 	}
 
 	if (opts.stdin_packs) {
-		while (strbuf_getline(&buf, stdin) != EOF)
-			string_list_append_nodup(&pack_indexes,
-						 strbuf_detach(&buf, NULL));
+		struct strbuf packname = STRBUF_INIT;
+		size_t dirlen;
+
+		strbuf_addf(&packname, "%s/pack/", dir->abspath);
+		dirlen = packname.len;
+
+		while (strbuf_getline(&buf, stdin) != EOF) {
+			strbuf_setlen(&packname, dirlen);
+			strbuf_addbuf(&packname, &buf);
+			string_list_append(&pack_indexes, packname.buf);
+		}
+
+		strbuf_release(&packname);
 	} else if (opts.stdin_commits) {
 		oidset_init(&commits, 0);
 		if (opts.progress)
@@ -323,7 +340,7 @@ static int graph_write(int argc, const char **argv, const char *prefix,
 		stop_progress(&progress);
 	}
 
-	if (write_commit_graph(source,
+	if (write_commit_graph(the_repository, dir->abspath,
 			       opts.stdin_packs ? &pack_indexes : NULL,
 			       opts.stdin_commits ? &commits : NULL,
 			       flags,
diff --git a/builtin/commit.c b/builtin/commit.c
index 840b6b4..0e750f6 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1959,7 +1959,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(the_repository->objects->sources);
+	git_test_write_commit_graph_or_die(the_repository);
 
 	repo_rerere(the_repository, 0);
 	run_auto_maintenance(the_repository, quiet);
diff --git a/builtin/count-objects.c b/builtin/count-objects.c
index 18f6e33..3f7b20f 100644
--- a/builtin/count-objects.c
+++ b/builtin/count-objects.c
@@ -81,10 +81,10 @@ static int count_cruft(const char *basename UNUSED, const char *path,
 	return 0;
 }
 
-static int print_alternate(struct odb_source *alternate, void *data UNUSED)
+static int print_alternate(struct odb_files_dir *alternate, void *data UNUSED)
 {
 	printf("alternate: ");
-	quote_c_style(alternate->path, NULL, stdout, 0);
+	quote_c_style(alternate->abspath, NULL, stdout, 0);
 	putchar('\n');
 	return 0;
 }
@@ -118,7 +118,7 @@ int cmd_count_objects(int argc,
 		report_linked_checkout_garbage(the_repository);
 	}
 
-	for_each_loose_file_in_source(the_repository->objects->sources,
+	for_each_loose_file_in_source(the_repository->objects->source,
 				      count_loose, count_cruft, NULL, NULL);
 
 	if (verbose) {
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index fbd9199..5fc082b 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -895,7 +895,7 @@ static void end_packfile(void)
 	running = 1;
 	clear_delta_base_cache();
 	if (object_count) {
-		struct odb_source_files *files = odb_source_files_downcast(pack_data->repo->objects->sources);
+		struct odb_source_files *files = odb_source_files_downcast(pack_data->repo->objects->source);
 		struct packed_git *new_p;
 		struct object_id cur_pack_oid;
 		char *idx_name;
@@ -921,7 +921,7 @@ static void end_packfile(void)
 		idx_name = keep_pack(create_index());
 
 		/* Register the packfile with core git's machinery. */
-		new_p = packfile_store_load_pack(files->packed, idx_name, 1);
+		new_p = packfile_store_load_pack(files->dirs->packed, idx_name, 1);
 		if (!new_p)
 			die(_("core Git rejected index %s"), idx_name);
 		all_packs[pack_id] = new_p;
@@ -975,7 +975,7 @@ static int store_object(
 	struct object_id *oidout,
 	uintmax_t mark)
 {
-	struct odb_source *source;
+	struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->source);
 	void *out, *delta;
 	struct object_entry *e;
 	unsigned char hdr[96];
@@ -1002,10 +1002,8 @@ static int store_object(
 		return 1;
 	}
 
-	for (source = the_repository->objects->sources; source; source = source->next) {
-		struct odb_source_files *files = odb_source_files_downcast(source);
-
-		if (!packfile_list_find_oid(packfile_store_get_packs(files->packed), &oid))
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		if (!packfile_list_find_oid(packfile_store_get_packs(dir->packed), &oid))
 			continue;
 		e->type = type;
 		e->pack_id = MAX_PACK_ID;
@@ -1125,10 +1123,10 @@ static void truncate_pack(struct hashfile_checkpoint *checkpoint)
 
 static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)
 {
+	struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->source);
 	size_t in_sz = 64 * 1024, out_sz = 64 * 1024;
 	unsigned char *in_buf = xmalloc(in_sz);
 	unsigned char *out_buf = xmalloc(out_sz);
-	struct odb_source *source;
 	struct object_entry *e;
 	struct object_id oid;
 	unsigned long hdrlen;
@@ -1212,10 +1210,8 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)
 		goto out;
 	}
 
-	for (source = the_repository->objects->sources; source; source = source->next) {
-		struct odb_source_files *files = odb_source_files_downcast(source);
-
-		if (!packfile_list_find_oid(packfile_store_get_packs(files->packed), &oid))
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		if (!packfile_list_find_oid(packfile_store_get_packs(dir->packed), &oid))
 			continue;
 		e->type = OBJ_BLOB;
 		e->pack_id = MAX_PACK_ID;
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 533fdfe..1f76ae6 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -15,6 +15,7 @@
 #include "refspec.h"
 #include "object-name.h"
 #include "odb.h"
+#include "odb/source.h"
 #include "oidset.h"
 #include "oid-array.h"
 #include "commit.h"
@@ -103,7 +104,8 @@ static struct string_list negotiation_include = STRING_LIST_INIT_NODUP;
 
 struct fetch_config {
 	enum display_format display_format;
-	enum follow_remote_head_settings follow_remote_head;
+	char *follow_remote_head_raw;
+	int follow_remote_head_seen;
 	int all;
 	int prune;
 	int prune_tags;
@@ -176,24 +178,31 @@ static int git_fetch_config(const char *k, const char *v,
 	}
 
 	if (!strcmp(k, "fetch.followremotehead")) {
-		if (!v)
-			return config_error_nonbool(k);
-		else if (!strcmp(v, "never"))
-			fetch_config->follow_remote_head = FOLLOW_REMOTE_NEVER;
-		else if (!strcmp(v, "create"))
-			fetch_config->follow_remote_head = FOLLOW_REMOTE_CREATE;
-		else if (!strcmp(v, "warn"))
-			fetch_config->follow_remote_head = FOLLOW_REMOTE_WARN;
-		else if (!strcmp(v, "always"))
-			fetch_config->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
-		else
-			warning(_("unrecognized fetch.followRemoteHEAD value '%s' ignored"), v);
+		free(fetch_config->follow_remote_head_raw);
+		fetch_config->follow_remote_head_raw = xstrdup_or_null(v);
+		fetch_config->follow_remote_head_seen = 1;
 		return 0;
 	}
 
 	return git_default_config(k, v, ctx, cb);
 }
 
+static enum follow_remote_head_settings get_follow_remote_head(const char *setting)
+{
+	if (!setting)
+		die(_("missing value for 'fetch.followRemoteHEAD'"));
+	else if (!strcmp(setting, "never"))
+		return FOLLOW_REMOTE_NEVER;
+	else if (!strcmp(setting, "create"))
+		return FOLLOW_REMOTE_CREATE;
+	else if (!strcmp(setting, "warn"))
+		return FOLLOW_REMOTE_WARN;
+	else if (!strcmp(setting, "always"))
+		return FOLLOW_REMOTE_ALWAYS;
+	warning(_("unrecognized fetch.followRemoteHEAD value '%s' ignored"), setting);
+	return BUILTIN_FOLLOW_REMOTE_HEAD_DFLT;
+}
+
 static int parse_refmap_arg(const struct option *opt, const char *arg, int unset)
 {
 	BUG_ON_OPT_NEG(unset);
@@ -1848,7 +1857,7 @@ static void ref_transaction_rejection_handler(const char *refname,
 			"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"
+			"following command:\n\n    git refs migrate --ref-storage-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"));
@@ -1903,10 +1912,34 @@ static int commit_ref_transaction(struct ref_transaction **transaction,
 	return retcode;
 }
 
+static void collect_updated_tips(struct oidset *tips, struct ref *ref_map)
+{
+	struct ref *rm;
+	for (rm = ref_map; rm; rm = rm->next) {
+		struct commit *commit;
+		/*
+		 * Shallow-rejected refs are not stored and their history
+		 * is incomplete, so skip them.
+		 */
+		if (rm->status == REF_STATUS_REJECT_SHALLOW)
+			continue;
+		if (is_null_oid(&rm->old_oid))
+			continue;
+		if (rm->peer_ref &&
+		    oideq(&rm->old_oid, &rm->peer_ref->old_oid))
+			continue;
+		commit = lookup_commit_reference_gently(the_repository,
+							&rm->old_oid, 1);
+		if (commit)
+			oidset_insert(tips, &commit->object.oid);
+	}
+}
+
 static int do_fetch(struct transport *transport,
 		    struct refspec *rs,
 		    const struct fetch_config *config,
-		    struct list_objects_filter_options *filter_options)
+		    struct list_objects_filter_options *filter_options,
+		    struct oidset *updated_tips)
 {
 	struct ref_transaction *transaction = NULL;
 	struct ref *ref_map = NULL;
@@ -1918,11 +1951,10 @@ static int do_fetch(struct transport *transport,
 		TRANSPORT_LS_REFS_OPTIONS_INIT;
 	struct fetch_head fetch_head = { 0 };
 	struct strbuf err = STRBUF_INIT;
-	int do_set_head = 0;
 	struct ref_update_display_info_array display_array = { 0 };
 	struct strmap rejected_refs = STRMAP_INIT;
 	int summary_width = 0;
-	int follow_remote_head;
+	int follow_remote_head = FOLLOW_REMOTE_NEVER;
 
 	if (tags == TAGS_DEFAULT) {
 		if (transport->remote->fetch_tags == 2)
@@ -1938,22 +1970,6 @@ static int do_fetch(struct transport *transport,
 			goto cleanup;
 	}
 
-	/*
-	 * NEEDSWORK: By the time this function executes, we have already parsed
-	 * all such followRemoteHEAD values from the external configuration,
-	 * potentially emitting warning messages for bogus values.  Ideally, if
-	 * this fetch ends up not needing to consult these values, then git would
-	 * not ever output a value warning. (eg: when pulling from a URL directly -
-	 * rather than a configured remote, or when a remote's followRemoteHEAD
-	 * overrides the fallback fetch setting)
-	 */
-	if (transport->remote->follow_remote_head)
-		follow_remote_head = transport->remote->follow_remote_head;
-	else if (config->follow_remote_head)
-		follow_remote_head = config->follow_remote_head;
-	else
-		follow_remote_head = BUILTIN_FOLLOW_REMOTE_HEAD_DFLT;
-
 	if (rs->nr) {
 		refspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes);
 	} else {
@@ -1962,8 +1978,16 @@ static int do_fetch(struct transport *transport,
 		if (transport->remote->fetch.nr) {
 			refspec_ref_prefixes(&transport->remote->fetch,
 					     &transport_ls_refs_options.ref_prefixes);
-			if (follow_remote_head != FOLLOW_REMOTE_NEVER)
-				do_set_head = 1;
+			/*
+			 * See remote.c's handling of remote.<name>.followRemoteHEAD
+			 * for the analogous, still-unresolved case.
+			 */
+			if (transport->remote->follow_remote_head)
+				follow_remote_head = transport->remote->follow_remote_head;
+			else if (config->follow_remote_head_seen)
+				follow_remote_head = get_follow_remote_head(config->follow_remote_head_raw);
+			else
+				follow_remote_head = BUILTIN_FOLLOW_REMOTE_HEAD_DFLT;
 		}
 		if (branch && branch_has_merge_config(branch) &&
 		    !strcmp(branch->remote_name, transport->remote->name)) {
@@ -1987,7 +2011,7 @@ static int do_fetch(struct transport *transport,
 		strvec_push(&transport_ls_refs_options.ref_prefixes,
 			    "refs/tags/");
 
-	if (do_set_head)
+	if (follow_remote_head != FOLLOW_REMOTE_NEVER)
 		strvec_push(&transport_ls_refs_options.ref_prefixes,
 			    "HEAD");
 
@@ -2111,6 +2135,8 @@ static int do_fetch(struct transport *transport,
 
 	commit_fetch_head(&fetch_head);
 
+	collect_updated_tips(updated_tips, ref_map);
+
 	if (set_upstream) {
 		struct branch *branch = branch_get("HEAD");
 		struct ref *rm;
@@ -2164,7 +2190,7 @@ static int do_fetch(struct transport *transport,
 				  "you need to specify exactly one branch with the --set-upstream option"));
 		}
 	}
-	if (do_set_head) {
+	if (follow_remote_head != FOLLOW_REMOTE_NEVER) {
 		/*
 		 * Way too many cases where this can go wrong so let's just
 		 * ignore errors and fail silently for now.
@@ -2427,7 +2453,8 @@ static inline void fetch_one_setup_partial(struct remote *remote,
 static int fetch_one(struct remote *remote, int argc, const char **argv,
 		     int prune_tags_ok, int use_stdin_refspecs,
 		     const struct fetch_config *config,
-		     struct list_objects_filter_options *filter_options)
+		     struct list_objects_filter_options *filter_options,
+		     struct oidset *updated_tips)
 {
 	struct refspec rs = REFSPEC_INIT_FETCH(the_hash_algo);
 	int i;
@@ -2494,7 +2521,8 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
 	sigchain_push_common(unlock_pack_on_signal);
 	atexit(unlock_pack_atexit);
 	sigchain_push(SIGPIPE, SIG_IGN);
-	exit_code = do_fetch(gtransport, &rs, config, filter_options);
+	exit_code = do_fetch(gtransport, &rs, config, filter_options,
+			     updated_tips);
 	sigchain_pop(SIGPIPE);
 	refspec_clear(&rs);
 	transport_disconnect(gtransport);
@@ -2509,7 +2537,8 @@ int cmd_fetch(int argc,
 {
 	struct fetch_config config = {
 		.display_format = DISPLAY_FORMAT_FULL,
-		.follow_remote_head = FOLLOW_REMOTE_UNCONFIGURED,
+		.follow_remote_head_raw = NULL,
+		.follow_remote_head_seen = 0,
 		.prune = -1,
 		.prune_tags = -1,
 		.show_forced_updates = 1,
@@ -2535,6 +2564,12 @@ int cmd_fetch(int argc,
 	int negotiate_only = 0;
 	int porcelain = 0;
 	int i;
+	enum {
+		GRAPH_WRITE_REACHABLE,
+		GRAPH_WRITE_TIPS,
+		GRAPH_WRITE_SKIP,
+	} graph_write_mode = GRAPH_WRITE_REACHABLE;
+	struct oidset updated_tips = OIDSET_INIT;
 
 	struct option builtin_fetch_options[] = {
 		OPT__VERBOSITY(&verbosity),
@@ -2822,7 +2857,13 @@ int cmd_fetch(int argc,
 		}
 		trace2_region_enter("fetch", "fetch-one", the_repository);
 		result = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs,
-				   &config, &filter_options);
+				   &config, &filter_options, &updated_tips);
+		if (prepare_commit_graph(the_repository)) {
+			if (oidset_size(&updated_tips))
+				graph_write_mode = GRAPH_WRITE_TIPS;
+			else
+				graph_write_mode = GRAPH_WRITE_SKIP;
+		}
 		trace2_region_leave("fetch", "fetch-one", the_repository);
 	} else {
 		int max_children = max_jobs;
@@ -2899,11 +2940,23 @@ int cmd_fetch(int argc,
 		if (progress)
 			commit_graph_flags |= COMMIT_GRAPH_WRITE_PROGRESS;
 
-		trace2_region_enter("fetch", "write-commit-graph", the_repository);
-		write_commit_graph_reachable(the_repository->objects->sources,
-					     commit_graph_flags,
-					     NULL);
-		trace2_region_leave("fetch", "write-commit-graph", the_repository);
+		if (graph_write_mode != GRAPH_WRITE_SKIP) {
+			trace2_region_enter("fetch", "write-commit-graph",
+					    the_repository);
+			if (graph_write_mode == GRAPH_WRITE_TIPS)
+				write_commit_graph(
+					the_repository,
+					the_repository->objects->source->path,
+					NULL, &updated_tips,
+					commit_graph_flags, NULL);
+			else
+				write_commit_graph_reachable(
+					the_repository,
+					the_repository->objects->source->path,
+					commit_graph_flags, NULL);
+			trace2_region_leave("fetch", "write-commit-graph",
+					    the_repository);
+		}
 	}
 
 	if (enable_auto_gc) {
@@ -2927,7 +2980,9 @@ int cmd_fetch(int argc,
 	}
 
  cleanup:
+	oidset_clear(&updated_tips);
 	string_list_clear(&list, 0);
 	list_objects_filter_release(&filter_options);
+	free(config.follow_remote_head_raw);
 	return result;
 }
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 9af4cc0..da90cf8 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -868,7 +868,6 @@ int cmd_fsck(int argc,
 		OPT_BOOL(0, "references", &check_references, N_("check reference database consistency")),
 		OPT_END(),
 	};
-	struct odb_source *source;
 	struct snapshot snap = {
 		.nr = 0,
 		.alloc = 0,
@@ -983,13 +982,14 @@ int cmd_fsck(int argc,
 	check_connectivity(repo);
 
 	if (repo->settings.core_commit_graph) {
+		struct odb_source_files *files = odb_source_files_downcast(repo->objects->source);
 		struct child_process commit_graph_verify = CHILD_PROCESS_INIT;
 
-		for (source = repo->objects->sources; source; source = source->next) {
+		for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
 			child_process_init(&commit_graph_verify);
 			commit_graph_verify.git_cmd = 1;
 			strvec_pushl(&commit_graph_verify.args, "commit-graph",
-				     "verify", "--object-dir", source->path, NULL);
+				     "verify", "--object-dir", dir->abspath, NULL);
 			if (show_progress)
 				strvec_push(&commit_graph_verify.args, "--progress");
 			else
diff --git a/builtin/gc.c b/builtin/gc.c
index 57a3520..28ada9f 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -733,7 +733,8 @@ int cmd_gc(int argc,
 	}
 
 	if (the_repository->settings.gc_write_commit_graph == 1)
-		write_commit_graph_reachable(the_repository->objects->sources,
+		write_commit_graph_reachable(the_repository,
+					     the_repository->objects->source->path,
 					     !opts.quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0,
 					     NULL);
 
@@ -997,7 +998,7 @@ static int loose_object_auto_condition(struct gc_config *cfg UNUSED)
 	if (loose_object_auto_limit < 0)
 		return 1;
 
-	return for_each_loose_file_in_source(the_repository->objects->sources,
+	return for_each_loose_file_in_source(the_repository->objects->source,
 					     loose_object_count,
 					     NULL, NULL, &count);
 }
@@ -1032,7 +1033,7 @@ static int pack_loose(struct maintenance_run_opts *opts)
 	 * Do not start pack-objects process
 	 * if there are no loose objects.
 	 */
-	if (!for_each_loose_file_in_source(r->objects->sources,
+	if (!for_each_loose_file_in_source(r->objects->source,
 					   bail_on_loose,
 					   NULL, NULL, NULL))
 		return 0;
@@ -1044,7 +1045,7 @@ static int pack_loose(struct maintenance_run_opts *opts)
 		strvec_push(&pack_proc.args, "--quiet");
 	else
 		strvec_push(&pack_proc.args, "--no-quiet");
-	strvec_pushf(&pack_proc.args, "%s/pack/loose", r->objects->sources->path);
+	strvec_pushf(&pack_proc.args, "%s/pack/loose", r->objects->source->path);
 
 	pack_proc.in = -1;
 
@@ -1072,7 +1073,7 @@ static int pack_loose(struct maintenance_run_opts *opts)
 	else if (data.batch_size > 0)
 		data.batch_size--; /* Decrease for equality on limit. */
 
-	for_each_loose_file_in_source(r->objects->sources,
+	for_each_loose_file_in_source(r->objects->source,
 				      write_loose_object_to_stdin,
 				      NULL, NULL, &data);
 
@@ -1395,7 +1396,7 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts,
 	int result = 0;
 	struct lock_file lk;
 	struct repository *r = the_repository;
-	char *lock_path = xstrfmt("%s/maintenance", r->objects->sources->path);
+	char *lock_path = xstrfmt("%s/maintenance", r->objects->source->path);
 	enum auto_gc_hook_result auto_gc_hook_result = AUTO_GC_HOOK_UNDECIDED;
 
 	if (repo_hold_lock_file_for_update(r, &lk, lock_path, LOCK_NO_DEREF) < 0) {
@@ -2971,7 +2972,7 @@ static int update_background_schedule(const struct maintenance_start_opts *opts,
 	unsigned int i;
 	int result = 0;
 	struct lock_file lk;
-	char *lock_path = xstrfmt("%s/schedule", the_repository->objects->sources->path);
+	char *lock_path = xstrfmt("%s/schedule", the_repository->objects->source->path);
 
 	if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
 		if (errno == EEXIST)
diff --git a/builtin/history.c b/builtin/history.c
index ab77295..54cea35 100644
--- a/builtin/history.c
+++ b/builtin/history.c
@@ -1,6 +1,7 @@
 #define USE_THE_REPOSITORY_VARIABLE
 
 #include "builtin.h"
+#include "advice.h"
 #include "cache-tree.h"
 #include "commit.h"
 #include "commit-reach.h"
@@ -16,10 +17,12 @@
 #include "path.h"
 #include "read-cache.h"
 #include "refs.h"
+#include "ref-filter.h"
 #include "replay.h"
 #include "reset.h"
 #include "revision.h"
 #include "sequencer.h"
+#include "string-list.h"
 #include "strvec.h"
 #include "tree.h"
 #include "tree-walk.h"
@@ -34,6 +37,8 @@
 	N_("git history reword <commit> [--dry-run] [--update-refs=(branches|head)]")
 #define GIT_HISTORY_SPLIT_USAGE \
 	N_("git history split <commit> [--dry-run] [--update-refs=(branches|head)] [--] [<pathspec>...]")
+#define GIT_HISTORY_SQUASH_USAGE \
+	N_("git history squash [--dry-run] [--update-refs=(branches|head)] [--[no-]edit] <revision-range>")
 
 static void change_data_free(void *util, const char *str UNUSED)
 {
@@ -108,6 +113,7 @@ enum commit_tree_flags {
 static int commit_tree_ext(struct repository *repo,
 			   const char *action,
 			   struct commit *commit_with_message,
+			   const char *message_template,
 			   const struct commit_list *parents,
 			   const struct object_id *old_tree,
 			   const struct object_id *new_tree,
@@ -137,13 +143,16 @@ static int commit_tree_ext(struct repository *repo,
 		original_author = xmemdupz(ptr, len);
 	find_commit_subject(original_message, &original_body);
 
+	if (!message_template)
+		message_template = original_body;
+
 	if (flags & COMMIT_TREE_EDIT_MESSAGE) {
 		ret = fill_commit_message(repo, old_tree, new_tree,
-					  original_body, action, &commit_message);
+					  message_template, action, &commit_message);
 		if (ret < 0)
 			goto out;
 	} else {
-		strbuf_addstr(&commit_message, original_body);
+		strbuf_addstr(&commit_message, message_template);
 	}
 
 	original_extra_headers = read_commit_extra_headers(commit_with_message,
@@ -165,6 +174,25 @@ static int commit_tree_ext(struct repository *repo,
 	return ret;
 }
 
+static int first_parent_tree_oid(struct repository *repo,
+				 struct commit *commit,
+				 struct object_id *out)
+{
+	struct commit *parent = commit->parents ? commit->parents->item : NULL;
+
+	if (!parent) {
+		oidcpy(out, repo->hash_algo->empty_tree);
+		return 0;
+	}
+
+	if (repo_parse_commit(repo, parent))
+		return error(_("unable to parse parent commit %s"),
+			     oid_to_hex(&parent->object.oid));
+
+	oidcpy(out, &repo_get_commit_tree(repo, parent)->object.oid);
+	return 0;
+}
+
 static int commit_tree_with_edited_message(struct repository *repo,
 					   const char *action,
 					   struct commit *original,
@@ -172,23 +200,13 @@ static int commit_tree_with_edited_message(struct repository *repo,
 {
 	struct object_id parent_tree_oid;
 	const struct object_id *tree_oid;
-	struct commit *parent;
 
 	tree_oid = &repo_get_commit_tree(repo, original)->object.oid;
 
-	parent = original->parents ? original->parents->item : NULL;
-	if (parent) {
-		if (repo_parse_commit(repo, parent)) {
-			return error(_("unable to parse parent commit %s"),
-				     oid_to_hex(&parent->object.oid));
-		}
+	if (first_parent_tree_oid(repo, original, &parent_tree_oid) < 0)
+		return -1;
 
-		parent_tree_oid = repo_get_commit_tree(repo, parent)->object.oid;
-	} else {
-		oidcpy(&parent_tree_oid, repo->hash_algo->empty_tree);
-	}
-
-	return commit_tree_ext(repo, action, original, original->parents,
+	return commit_tree_ext(repo, action, original, NULL, original->parents,
 			       &parent_tree_oid, tree_oid, out, COMMIT_TREE_EDIT_MESSAGE);
 }
 
@@ -476,18 +494,10 @@ static int commit_became_empty(struct repository *repo,
 			       struct commit *original,
 			       struct tree *result)
 {
-	struct commit *parent = original->parents ? original->parents->item : NULL;
 	struct object_id parent_tree_oid;
 
-	if (parent) {
-		if (repo_parse_commit(repo, parent))
-			return error(_("unable to parse parent of %s"),
-				     oid_to_hex(&original->object.oid));
-
-		parent_tree_oid = repo_get_commit_tree(repo, parent)->object.oid;
-	} else {
-		oidcpy(&parent_tree_oid, repo->hash_algo->empty_tree);
-	}
+	if (first_parent_tree_oid(repo, original, &parent_tree_oid) < 0)
+		return -1;
 
 	return oideq(&result->object.oid, &parent_tree_oid);
 }
@@ -675,7 +685,7 @@ static int cmd_history_fixup(int argc,
 		goto out;
 
 	if (!skip_commit) {
-		ret = commit_tree_ext(repo, "fixup", original, original->parents,
+		ret = commit_tree_ext(repo, "fixup", original, NULL, original->parents,
 				      &original_tree->object.oid, &merge_result.tree->object.oid,
 				      &rewritten, flags);
 		if (ret < 0) {
@@ -835,16 +845,9 @@ static int split_commit(struct repository *repo,
 	struct tree *split_tree;
 	int ret;
 
-	if (original->parents) {
-		if (repo_parse_commit(repo, original->parents->item)) {
-			ret = error(_("unable to parse parent commit %s"),
-				    oid_to_hex(&original->parents->item->object.oid));
-			goto out;
-		}
-
-		parent_tree_oid = *get_commit_tree_oid(original->parents->item);
-	} else {
-		oidcpy(&parent_tree_oid, repo->hash_algo->empty_tree);
+	if (first_parent_tree_oid(repo, original, &parent_tree_oid) < 0) {
+		ret = -1;
+		goto out;
 	}
 	original_commit_tree_oid = get_commit_tree_oid(original);
 
@@ -897,7 +900,7 @@ static int split_commit(struct repository *repo,
 	 * The first commit is constructed from the split-out tree. The base
 	 * that shall be diffed against is the parent of the original commit.
 	 */
-	ret = commit_tree_ext(repo, "split-out", original, original->parents, &parent_tree_oid,
+	ret = commit_tree_ext(repo, "split-out", original, NULL, original->parents, &parent_tree_oid,
 			      &split_tree->object.oid, &first_commit, COMMIT_TREE_EDIT_MESSAGE);
 	if (ret < 0) {
 		ret = error(_("failed writing first commit"));
@@ -914,7 +917,7 @@ static int split_commit(struct repository *repo,
 	old_tree_oid = &repo_get_commit_tree(repo, first_commit)->object.oid;
 	new_tree_oid = &repo_get_commit_tree(repo, original)->object.oid;
 
-	ret = commit_tree_ext(repo, "split-out", original, parents, old_tree_oid,
+	ret = commit_tree_ext(repo, "split-out", original, NULL, parents, old_tree_oid,
 			      new_tree_oid, &second_commit, COMMIT_TREE_EDIT_MESSAGE);
 	if (ret < 0) {
 		ret = error(_("failed writing second commit"));
@@ -1011,6 +1014,645 @@ static int cmd_history_split(int argc,
 	return ret;
 }
 
+#define SQUASH_SEEN (1u << 11)
+#define SQUASH_TIP (1u << 12)
+#define SQUASH_AMEND_TARGET (1u << 13)
+
+static bool is_autosquash_subject(const char *s)
+{
+	return starts_with(s, "amend!") || starts_with(s, "fixup!") ||
+		starts_with(s, "squash!");
+}
+
+static bool skip_one_autosquash_prefix(const char *s, const char **out)
+{
+	if (skip_prefix(s, "amend!", out) || skip_prefix(s, "fixup!", out) ||
+	    skip_prefix(s, "squash!", out)) {
+		while (**out == ' ')
+			(*out)++;
+		return true;
+	}
+	return false;
+}
+
+static void truncate_message_to_subject(struct strbuf *msg)
+{
+	const char *eos = strstr(msg->buf, "\n\n");
+
+	if (eos)
+		strbuf_setlen(msg, eos - msg->buf + 1);
+}
+
+struct subject_data {
+	struct strintmap subjects;
+	struct strbuf subject;
+	struct strbuf squash_message;
+	const char *message;
+	bool edit_message;
+};
+
+#define SUBJECT_DATA_INIT {		\
+	.subjects = STRINTMAP_INIT,	\
+	.subject = STRBUF_INIT,		\
+	.squash_message = STRBUF_INIT,	\
+}
+
+static void subject_data_clear(struct subject_data *data)
+{
+	strintmap_clear(&data->subjects);
+	strbuf_release(&data->subject);
+	strbuf_release(&data->squash_message);
+}
+
+static int squash_amend_message(struct repository *repo,
+				struct commit *commit,
+				struct subject_data *data,
+				unsigned flags)
+{
+	const char *body = data->message + data->subject.len;
+
+	while (isspace(*body))
+		body++;
+
+	if (!*body) {
+		warning(_("ignoring %s (%s): message body is empty"),
+			repo_find_unique_abbrev(repo, &commit->object.oid,
+						DEFAULT_ABBREV),
+			data->subject.buf);
+		return 0;
+	}
+
+	if (data->edit_message) {
+		return 0;
+	} else if (flags & SQUASH_AMEND_TARGET) {
+		if (starts_with(data->squash_message.buf, "squash!"))
+			return error(_("squashing %s (%s) would overwrite "
+				       "'squash!' message, please combine them "
+				       "using '--edit'"),
+				     repo_find_unique_abbrev(repo,
+							     &commit->object.oid,
+							     DEFAULT_ABBREV),
+				     data->subject.buf);
+		if (starts_with(data->squash_message.buf, "fixup!"))
+			strbuf_splice(&data->squash_message, 0, 5, "amend!", 5);
+		if (starts_with(data->squash_message.buf, "amend!")) {
+			truncate_message_to_subject(&data->squash_message);
+			strbuf_addch(&data->squash_message, '\n');
+		} else {
+			strbuf_reset(&data->squash_message);
+		}
+		strbuf_addstr(&data->squash_message, body);
+		strbuf_complete_line(&data->squash_message);
+	} else {
+		return error(_("cannot squash %s (%s) that does not target "
+			       "base commit without '--edit'"),
+			     repo_find_unique_abbrev(repo, &commit->object.oid,
+						     DEFAULT_ABBREV),
+			     data->subject.buf);
+	}
+	return 0;
+}
+
+static int squash_squash_message(struct repository *repo,
+				struct commit *commit,
+				struct subject_data *data,
+				unsigned flags)
+{
+	const char *body = data->message + data->subject.len;
+
+	while (isspace(*body))
+		body++;
+
+	if (data->edit_message) {
+		return 0;
+	} else if (flags & SQUASH_AMEND_TARGET) {
+		if (starts_with(data->squash_message.buf, "fixup!")) {
+			truncate_message_to_subject(&data->squash_message);
+			strbuf_splice(&data->squash_message, 0, 5, "squash", 6);
+		}
+		if (starts_with(data->squash_message.buf, "squash!")) {
+			strbuf_addch(&data->squash_message, '\n');
+			strbuf_addstr(&data->squash_message, body);
+			strbuf_complete_line(&data->squash_message);
+		} else {
+			return error(_("squashing %s (%s) would discard its "
+				       "message, please combine them using "
+				       "'--edit'"),
+				     repo_find_unique_abbrev(repo,
+							     &commit->object.oid,
+							     DEFAULT_ABBREV),
+				     data->subject.buf);
+		}
+	} else {
+		return error(_("cannot squash %s (%s) that does not target "
+			       "base commit without '--edit'"),
+			      repo_find_unique_abbrev(repo, &commit->object.oid,
+						      DEFAULT_ABBREV),
+			      data->subject.buf);
+	}
+	return 0;
+}
+
+static int squash_check_can_autosquash(struct repository *repo,
+				       struct commit *commit,
+				       struct subject_data *data,
+				       unsigned flags)
+{
+	commit->object.flags |= flags & SQUASH_AMEND_TARGET;
+	if (starts_with(data->subject.buf, "amend!"))
+		return squash_amend_message(repo, commit, data, flags);
+	else if (starts_with(data->subject.buf, "squash!"))
+		return squash_squash_message(repo, commit, data, flags);
+
+	return 0;
+}
+
+static int squash_check_autosquash_subject(struct repository *repo,
+					   struct commit *commit,
+					   struct subject_data *data)
+{
+	const char* s = data->subject.buf;
+	struct commit *target;
+	struct hashmap_iter iter;
+	struct strmap_entry *entry;
+	/* Try skipping autosquash prefixes one at a time to allow
+	 * squashing
+	 *     a commit
+	 *     fixup! fixup! a commit
+	 *
+	 * where we may have started with
+	 *     a commit
+	 *     fixup! a commit
+	 *     fixup! fixup! a commit
+	 *
+	 * and squashed the first fixup separately from the second
+	 */
+	while (skip_one_autosquash_prefix(s, &s)) {
+		unsigned flags = strintmap_get(&data->subjects, s);
+		if (flags)
+			return squash_check_can_autosquash(repo, commit, data, flags);
+	}
+	/*
+	 * Allow "fixup! <hex object id>", but not "fixup! HEAD^" or
+	 * "fixup! main". If the target is not being squashed check the subject
+	 * to allow "fixup! abc123" and "fixup! <subject of abc123>" to be
+	 * squashed together.
+	 */
+	target = lookup_commit_reference_by_name(s);
+	if (target && starts_with(oid_to_hex(&target->object.oid), s)) {
+		unsigned flags =
+			target->object.flags & (SQUASH_SEEN | SQUASH_AMEND_TARGET);
+		if (!flags) {
+			const char *subject_start;
+			const char *buffer = repo_logmsg_reencode(repo, target,
+								  NULL, NULL);
+			size_t subject_len = find_commit_subject(buffer,
+								 &subject_start);
+			char *subject = xmemdupz(subject_start, subject_len);
+
+			flags = strintmap_get(&data->subjects, subject);
+			free(subject);
+			repo_unuse_commit_buffer(repo, target, buffer);
+		}
+		if (flags)
+			return squash_check_can_autosquash(repo, commit,
+							   data, flags);
+	}
+	/* Try subject prefix matches */
+	strintmap_for_each_entry(&data->subjects, &iter, entry) {
+		s = data->subject.buf;
+		while(skip_one_autosquash_prefix(s, &s)) {
+			if (starts_with(entry->key, s)) {
+				unsigned value = (intptr_t)entry->value;
+
+				return squash_check_can_autosquash(repo, commit,
+								   data, value);
+			}
+		}
+	}
+	return error(_("cannot squash %s (%s): its target is not being "
+		       "squashed"),
+		       repo_find_unique_abbrev(repo, &commit->object.oid,
+					       DEFAULT_ABBREV),
+		       data->subject.buf);
+}
+
+static int squash_check_subject(struct repository *repo,
+				struct commit *commit,
+				struct subject_data *data)
+{
+	int ret = 0;
+	const char *buf = repo_logmsg_reencode(repo, commit, NULL, NULL);
+	size_t subject_len = find_commit_subject(buf, &data->message);
+
+	strbuf_reset(&data->subject);
+	strbuf_add(&data->subject, data->message, subject_len);
+
+	if (!strintmap_get_size(&data->subjects)) {
+		const char *s;
+
+		strbuf_addstr(&data->squash_message, data->message);
+		strbuf_complete_line(&data->squash_message);
+		/*
+		 * Strip a single autosquash prefix to allow squashing
+		 *     fixup! base
+		 *     amend! base
+		 */
+		s = data->subject.buf;
+		skip_one_autosquash_prefix(s, &s);
+		strintmap_set(&data->subjects, s, SQUASH_AMEND_TARGET | SQUASH_SEEN);
+		commit->object.flags |= SQUASH_AMEND_TARGET;
+	} else if (is_autosquash_subject(data->subject.buf)) {
+		ret = squash_check_autosquash_subject(repo, commit, data);
+	} else {
+		strintmap_set(&data->subjects, data->subject.buf, SQUASH_SEEN);
+	}
+	repo_unuse_commit_buffer(repo, commit, buf);
+	return ret;
+}
+
+static int build_squash_message(struct repository *repo,
+				const struct strbuf *todo_buf,
+				struct strbuf *out);
+
+static int setup_squash_revisions(struct repository *repo,
+				  int argc, const char **argv,
+				  struct rev_info *revs)
+{
+	repo_init_revisions(repo, revs, NULL);
+	revs->reverse = 1;
+	revs->topo_order = 1;
+	revs->sort_order = REV_SORT_IN_GRAPH_ORDER;
+	revs->simplify_history = 0;
+	revs->ancestry_path = 1;
+	revs->limited = 1;
+	revs->ancestry_path_implicit_bottoms = 1;
+
+	argc = setup_revisions(argc, argv, revs, NULL);
+	if (argc > 1)
+		return error(_("unrecognized argument: %s"), argv[1]);
+
+	if (revs->reverse != 1 || revs->topo_order != 1 ||
+	    revs->sort_order != REV_SORT_IN_GRAPH_ORDER ||
+	    revs->simplify_history != 0 || revs->boundary == 1 ||
+	    revs->ancestry_path != 1 || revs->limited != 1 ||
+	    revs->ancestry_path_implicit_bottoms != 1) {
+		warning(_("ignoring rev-list options that would change how the "
+			  "range is walked"));
+		revs->reverse = 1;
+		revs->topo_order = 1;
+		revs->sort_order = REV_SORT_IN_GRAPH_ORDER;
+		revs->simplify_history = 0;
+		revs->boundary = 0;
+		revs->ancestry_path = 1;
+		revs->limited = 1;
+		revs->ancestry_path_implicit_bottoms = 1;
+	}
+
+	/*
+	 * A squash needs a base to reparent onto, so the range has to exclude
+	 * something, as in "<base>..<tip>". A revision range with no such
+	 * bottom commit cannot be squashed.
+	 */
+	for (size_t i = 0; i < revs->cmdline.nr; i++)
+		if (revs->cmdline.rev[i].flags & BOTTOM)
+			return 0;
+
+	return error(_("not a '<base>..<tip>' revision range"));
+}
+
+/*
+ * Resolve a revision range into its oldest commit and single tip. Every
+ * parent after the oldest commit must either be selected or also be a parent
+ * of the oldest commit.
+ */
+static int resolve_squash_range(struct repository *repo,
+				bool update_branches,
+				bool edit_message,
+				int argc, const char **argv,
+				struct commit **oldest_out,
+				struct commit **tip_out,
+				char **message_out)
+{
+	struct rev_info revs;
+	struct subject_data subject_data = SUBJECT_DATA_INIT;
+	struct commit *commit, *oldest = NULL, *tip = NULL;
+	struct strbuf todo_buf = STRBUF_INIT;
+	int ret, tip_count = 0;
+	bool walk_started = false;
+	struct ref_filter filter = REF_FILTER_INIT;
+	struct ref_array refs = { 0 };
+
+	subject_data.edit_message = edit_message;
+	ret = setup_squash_revisions(repo, argc, argv, &revs);
+	if (ret < 0)
+		goto out;
+
+	if (prepare_revision_walk(&revs) < 0) {
+		ret = error(_("error preparing revisions"));
+		goto out;
+	}
+	walk_started = true;
+	while ((commit = get_revision(&revs))) {
+		struct commit_list *p;
+
+		if (edit_message)
+			strbuf_addf(&todo_buf, "pick %s\n",
+				    oid_to_hex(&commit->object.oid));
+
+		if (!commit->parents) {
+			ret = error(_("cannot squash down to root commit"));
+			goto out;
+		}
+		for (p = commit->parents; oldest && p; p = p->next) {
+			struct commit_list *q;
+			struct object *o;
+			bool seen;
+
+			if (repo_parse_commit(repo, p->item)) {
+				ret = error(_("cannot parse commit"));
+				goto out;
+			}
+			o = &p->item->object;
+			seen = o->flags & SQUASH_SEEN;
+			/*
+			 * Allow parents that match the parents of the
+			 * squashed commit.
+			 */
+			for (q = oldest->parents; !seen && q; q = q->next) {
+				if (p->item == q->item) {
+					seen = true;
+					commit_list_insert(commit, &filter.with_commit);
+				}
+			}
+			if (!seen) {
+				ret = error(_("parent %s of commit %s is "
+					      "outside the revision range"),
+					    repo_find_unique_abbrev(repo, &o->oid,
+								    DEFAULT_ABBREV),
+					    repo_find_unique_abbrev(repo,
+								    &commit->object.oid,
+								    DEFAULT_ABBREV));
+				goto out;
+			}
+			if (o->flags & SQUASH_TIP) {
+				tip_count--;
+				o->flags &= ~SQUASH_TIP;
+			}
+		}
+		if (!oldest) {
+			commit_list_insert(commit, &filter.with_commit);
+			oldest = commit;
+		}
+		if (squash_check_subject(repo, commit, &subject_data)) {
+			ret = -1;
+			goto out;
+		}
+		tip = commit;
+		tip->object.flags |= SQUASH_SEEN | SQUASH_TIP;
+		tip_count++;
+	}
+	clear_object_flags(repo, SQUASH_SEEN | SQUASH_TIP);
+	reset_revision_walk();
+	walk_started = false;
+
+	if (!tip_count) {
+		ret = error(_("the revision range is empty"));
+		goto out;
+	} else if (tip_count != 1) {
+		ret = error(_("the revision range contains more than one tip "
+			      "commit"));
+		goto out;
+	} else if (oldest == tip) {
+		ret = error(_("the revision range holds a single commit; "
+			      "nothing to squash"));
+		goto out;
+	} else if (!oldest->parents) {
+		BUG("an in-range commit must have a parent");
+	}
+
+	commit_list_insert(tip, &filter.no_commit);
+	filter.kind = FILTER_REFS_BRANCHES;
+	if (update_branches &&
+	    filter_refs(&refs, &filter, filter.kind)) {
+		ret = error(_("could not filter refs"));
+		goto out;
+	}
+	if (refs.nr) {
+		struct ref_format format = REF_FORMAT_INIT;
+		struct ref_sorting *sorting;
+		struct string_list sorting_options = STRING_LIST_INIT_DUP;
+		struct strbuf branches = STRBUF_INIT;
+		struct strbuf err = STRBUF_INIT;
+
+		format.format = "%(refname:short)";
+		if (verify_ref_format(&format))
+			BUG("invalid branch format");
+		string_list_append(&sorting_options, "refname");
+		sorting = ref_sorting_options(&sorting_options);
+		ref_array_sort(sorting, &refs);
+		for (int i = 0; i < refs.nr; i++) {
+			strbuf_reset(&err);
+			strbuf_addstr(&branches, "\n  ");
+			if (format_ref_array_item(refs.items[i], &format,
+						  &branches, &err))
+				BUG("could not format branch name: %s", err.buf);
+		}
+		/*
+		 * TODO: also check HEADS from other worktrees.
+		 */
+		ret = error(_("the following branches cannot be rewritten as "
+			      "descendants of the squashed commit:%s"), branches.buf);
+		advise_if_enabled(ADVICE_HISTORY_UPDATE_REFS,
+				  _("Use --update-refs=head to rewrite only "
+				    "the current branch and leave such branches "
+				    "untouched."));
+		strbuf_release(&err);
+		strbuf_release(&branches);
+		ref_sorting_release(sorting);
+		string_list_clear(&sorting_options, 0);
+		goto out;
+	}
+	if (edit_message) {
+		strbuf_reset(&subject_data.squash_message);
+		ret = build_squash_message(repo, &todo_buf,
+					   &subject_data.squash_message);
+		if (ret < 0)
+			goto out;
+	}
+
+	*oldest_out = oldest;
+	*tip_out = tip;
+	*message_out = strbuf_detach(&subject_data.squash_message, NULL);
+	ret = 0;
+
+out:
+	strbuf_release(&todo_buf);
+	clear_object_flags(repo, SQUASH_SEEN | SQUASH_TIP |
+			   SQUASH_AMEND_TARGET);
+	if (walk_started)
+		reset_revision_walk();
+	subject_data_clear(&subject_data);
+	release_revisions(&revs);
+	ref_filter_clear(&filter);
+	ref_array_clear(&refs);
+	return ret;
+}
+
+static bool amend_replaces_target(struct todo_list *todo, int target)
+{
+	for (int i = target + 1; i < todo->nr &&
+				 todo->items[i].command != TODO_PICK; i++) {
+		if (todo->items[i].command == TODO_SQUASH)
+			return false;
+		if (todo->items[i].flags & TODO_REPLACE_FIXUP_MSG)
+			return true;
+	}
+	return false;
+}
+
+static int build_squash_message(struct repository *repo,
+				const struct strbuf *todo_buf,
+				struct strbuf *out)
+{
+	struct todo_list todo = TODO_LIST_INIT;
+	struct replay_opts opts = REPLAY_OPTS_INIT;
+	int nr_commits, ret;
+
+	if (todo_list_parse_insn_buffer(repo, &opts, todo_buf->buf, &todo) < 0 ||
+	    todo_list_rearrange_squash(&todo) < 0) {
+		ret = error(_("could not prepare the squash message"));
+		goto out;
+	}
+
+	nr_commits = todo.nr;
+	for (int i = 0; i < nr_commits; i++) {
+		struct todo_item *item = &todo.items[i];
+		const char *message, *body;
+		size_t commented_len;
+		bool skip, squashing;
+
+		squashing = item->command == TODO_SQUASH ||
+			    (item->flags & TODO_REPLACE_FIXUP_MSG);
+		if (item->command == TODO_PICK)
+			skip = amend_replaces_target(&todo, i);
+		else
+			skip = !squashing;
+
+		message = repo_logmsg_reencode(repo, item->commit, NULL, NULL);
+		find_commit_subject(message, &body);
+
+		if (skip)
+			commented_len = strlen(body);
+		else if (squashing)
+			commented_len = squash_subject_comment_len(body, 1);
+		else
+			commented_len = 0;
+
+		if (!i)
+			add_squash_combination_header(out, nr_commits);
+		strbuf_addch(out, '\n');
+		add_squash_message_header(out, i + 1, skip);
+		strbuf_addstr(out, "\n\n");
+		strbuf_add_commented_lines(out, body, commented_len, comment_line_str);
+		strbuf_addstr(out, body + commented_len);
+		strbuf_complete_line(out);
+
+		repo_unuse_commit_buffer(repo, item->commit, message);
+	}
+
+	ret = 0;
+
+out:
+	todo_list_release(&todo);
+	replay_opts_release(&opts);
+	return ret;
+}
+
+static int cmd_history_squash(int argc,
+			      const char **argv,
+			      const char *prefix,
+			      struct repository *repo)
+{
+	const char * const usage[] = {
+		GIT_HISTORY_SQUASH_USAGE,
+		NULL,
+	};
+	enum ref_action action = REF_ACTION_DEFAULT;
+	int dry_run = 0;
+	int edit = 1;
+	struct option options[] = {
+		OPT_CALLBACK_F(0, "update-refs", &action, "(branches|head)",
+			       N_("control which refs should be updated"),
+			       PARSE_OPT_NONEG, parse_ref_action),
+		OPT_BOOL('n', "dry-run", &dry_run,
+			 N_("perform a dry-run without updating any refs")),
+		OPT_BOOL('e', "edit", &edit,
+			 N_("edit the commit message")),
+		OPT_END(),
+	};
+	struct strbuf reflog_msg = STRBUF_INIT;
+	struct commit *oldest, *tip, *rewritten;
+	const struct object_id *base_tree_oid, *tip_tree_oid;
+	char *message_template = NULL;
+	struct rev_info revs = { 0 };
+	int ret;
+
+	argc = parse_options(argc, argv, prefix, options, usage,
+			     PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_KEEP_ARGV0);
+	if (argc < 2) {
+		ret = error(_("command expects a revision range"));
+		goto out;
+	}
+	repo_config(repo, git_default_config, NULL);
+
+	if (action == REF_ACTION_DEFAULT)
+		action = REF_ACTION_BRANCHES;
+
+	strbuf_addstr(&reflog_msg, "squash: updating ");
+	strbuf_join_argv(&reflog_msg, argc - 1, argv + 1, ' ');
+
+	ret = resolve_squash_range(repo, action == REF_ACTION_BRANCHES,
+				   edit,
+				   argc, argv, &oldest, &tip,
+				   &message_template);
+	if (ret < 0)
+		goto out;
+
+	ret = setup_revwalk(repo, action, tip, &revs);
+	if (ret < 0)
+		goto out;
+
+	base_tree_oid = &repo_get_commit_tree(repo,
+					oldest->parents->item)->object.oid;
+	tip_tree_oid = &repo_get_commit_tree(repo, tip)->object.oid;
+
+	ret = commit_tree_ext(repo, "squash", oldest, message_template,
+			      oldest->parents, base_tree_oid, tip_tree_oid,
+			      &rewritten,
+			      edit ? COMMIT_TREE_EDIT_MESSAGE : 0);
+	if (ret < 0) {
+		ret = error(_("failed writing squashed commit"));
+		goto out;
+	}
+
+	ret = handle_reference_updates(&revs, action, tip, rewritten,
+				       reflog_msg.buf, dry_run,
+				       REPLAY_EMPTY_COMMIT_ABORT);
+	if (ret < 0) {
+		ret = error(_("failed replaying descendants"));
+		goto out;
+	}
+
+	ret = 0;
+
+out:
+	strbuf_release(&reflog_msg);
+	release_revisions(&revs);
+	free(message_template);
+	return ret;
+}
+
 static int update_worktree(struct repository *repo,
 			   const struct commit *old_head,
 			   const struct commit *new_head,
@@ -1199,6 +1841,7 @@ int cmd_history(int argc,
 		GIT_HISTORY_FIXUP_USAGE,
 		GIT_HISTORY_REWORD_USAGE,
 		GIT_HISTORY_SPLIT_USAGE,
+		GIT_HISTORY_SQUASH_USAGE,
 		NULL,
 	};
 	parse_opt_subcommand_fn *fn = NULL;
@@ -1207,6 +1850,7 @@ int cmd_history(int argc,
 		OPT_SUBCOMMAND("fixup", &fn, cmd_history_fixup),
 		OPT_SUBCOMMAND("reword", &fn, cmd_history_reword),
 		OPT_SUBCOMMAND("split", &fn, cmd_history_split),
+		OPT_SUBCOMMAND("squash", &fn, cmd_history_squash),
 		OPT_END(),
 	};
 
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 6b2a87e..0264c18 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -1640,8 +1640,8 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
 
 	if (do_fsck_object && startup_info->have_repository) {
 		struct odb_source_files *files =
-			odb_source_files_downcast(the_repository->objects->sources);
-		packfile_store_load_pack(files->packed, final_index_name, 0);
+			odb_source_files_downcast(the_repository->objects->source);
+		packfile_store_load_pack(files->dirs->packed, final_index_name, 0);
 	}
 
 	if (!from_stdin) {
diff --git a/builtin/init-db.c b/builtin/init-db.c
index e96b128..374ed3a 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -57,7 +57,7 @@ static int shared_callback(const struct option *opt, const char *arg, int unset)
 static const char *const init_db_usage[] = {
 	N_("git init [-q | --quiet] [--bare] [--template=<template-directory>]\n"
 	   "         [--separate-git-dir <git-dir>] [--object-format=<format>]\n"
-	   "         [--ref-format=<format>]\n"
+	   "         [--ref-storage-format=<format>]\n"
 	   "         [-b <branch-name> | --initial-branch=<branch-name>]\n"
 	   "         [--shared[=<permissions>]] [<directory>]"),
 	NULL
@@ -80,13 +80,12 @@ int cmd_init_db(int argc,
 	char *work_tree = NULL;
 	const char *template_dir = NULL;
 	char *template_dir_to_free = NULL;
-	unsigned int flags = 0;
+	int quiet = 0;
 	int bare = startup_info->force_bare_repository ? 1 : -1;
 	const char *object_format = NULL;
-	const char *ref_format = NULL;
+	const char *ref_storage_format_uri = NULL;
 	const char *initial_branch = NULL;
 	int hash_algo = GIT_HASH_UNKNOWN;
-	enum ref_storage_format ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN;
 	int init_shared_repository = -1;
 	const struct option init_db_options[] = {
 		OPT_STRING(0, "template", &template_dir, N_("template-directory"),
@@ -102,18 +101,19 @@ int cmd_init_db(int argc,
 			.flags = PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
 			.callback = shared_callback
 		},
-		OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
+		OPT_BOOL('q', "quiet", &quiet, N_("be quiet")),
 		OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
 			   N_("separate git dir from working tree")),
 		OPT_STRING('b', "initial-branch", &initial_branch, N_("name"),
 			   N_("override the name of the initial branch")),
 		OPT_STRING(0, "object-format", &object_format, N_("hash"),
 			   N_("specify the hash algorithm to use")),
-		OPT_STRING(0, "ref-format", &ref_format, N_("format"),
-			   N_("specify the reference format to use")),
+		OPT_STRING(0, "ref-storage-format", &ref_storage_format_uri, N_("format"),
+			   N_("specify the reference storage format to use")),
+		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
 		OPT_END()
 	};
-	int ret;
+	int reinit;
 
 	argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
 
@@ -173,12 +173,6 @@ int cmd_init_db(int argc,
 			die(_("unknown hash algorithm '%s'"), object_format);
 	}
 
-	if (ref_format) {
-		ref_storage_format = ref_storage_format_by_name(ref_format);
-		if (ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
-			die(_("unknown ref storage format '%s'"), ref_format);
-	}
-
 	if (init_shared_repository != -1)
 		repo_settings_set_shared_repository(the_repository, init_shared_repository);
 
@@ -247,14 +241,30 @@ int cmd_init_db(int argc,
 		die(_("--separate-git-dir incompatible with bare repository"));
 	}
 
-	flags |= INIT_DB_EXIST_OK;
-	ret = init_db(the_repository, git_dir, real_git_dir, work_tree,
-		      template_dir, hash_algo, ref_storage_format, initial_branch,
-		      init_shared_repository, flags);
+	create_repository(the_repository, git_dir, real_git_dir, work_tree,
+			  template_dir, hash_algo, ref_storage_format_uri,
+			  init_shared_repository, &reinit);
+	create_reference_database(the_repository, initial_branch, quiet);
+	create_object_database(the_repository, NULL);
+
+	if (!quiet) {
+		int len = strlen(git_dir);
+
+		if (reinit)
+			printf(repo_settings_get_shared_repository(the_repository)
+			       ? _("Reinitialized existing shared Git repository in %s%s\n")
+			       : _("Reinitialized existing Git repository in %s%s\n"),
+			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+		else
+			printf(repo_settings_get_shared_repository(the_repository)
+			       ? _("Initialized empty shared Git repository in %s%s\n")
+			       : _("Initialized empty Git repository in %s%s\n"),
+			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+	}
 
 	free(template_dir_to_free);
 	free(real_git_dir_to_free);
 	free(work_tree);
 	free(git_dir);
-	return ret;
+	return 0;
 }
diff --git a/builtin/log.c b/builtin/log.c
index 350b35c..445400b 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1327,22 +1327,53 @@ static void prepare_cover_text(struct pretty_print_context *pp,
 	strbuf_release(&subject_sb);
 }
 
+struct rdiff_notes {
+	/*
+	 * True if we want to override the notes behavior
+	 * of 'format-patch'
+	 */
+	bool override;
+	struct string_list notes;
+};
+
+static int rdiff_notes_cb(const struct option *option,
+			  const char *arg,
+			  int unset)
+{
+	struct option opt = *option;
+	struct rdiff_notes *rdiff_notes = option->value;
+
+	rdiff_notes->override = 1;
+	opt.value = &rdiff_notes->notes;
+	return parse_opt_string_list(&opt, arg, unset);
+}
+
 static int get_notes_refs(struct string_list_item *item, void *arg)
 {
 	strvec_pushf(arg, "--notes=%s", item->string);
 	return 0;
 }
 
-static void get_notes_args(struct strvec *arg, struct rev_info *rev)
+static void get_notes_args(struct rdiff_notes *rdiff_notes,
+			   struct rev_info *rev)
 {
-	if (!rev->show_notes) {
-		strvec_push(arg, "--no-notes");
+	if (rdiff_notes->override) {
+		if (rdiff_notes->notes.nr)
+			for_each_string_list(&rdiff_notes->notes,
+					     get_notes_refs,
+					     &rev->rdiff_log_arg);
+		else
+			strvec_push(&rev->rdiff_log_arg, "--no-notes");
+	} else if (!rev->show_notes) {
+		strvec_push(&rev->rdiff_log_arg, "--no-notes");
 	} else if (rev->notes_opt.use_default_notes > 0 ||
 		   (rev->notes_opt.use_default_notes == -1 &&
 		    !rev->notes_opt.extra_notes_refs.nr)) {
-		strvec_push(arg, "--notes");
+		strvec_push(&rev->rdiff_log_arg, "--notes");
 	} else {
-		for_each_string_list(&rev->notes_opt.extra_notes_refs, get_notes_refs, arg);
+		for_each_string_list(&rev->notes_opt.extra_notes_refs,
+				     get_notes_refs,
+				     &rev->rdiff_log_arg);
 	}
 }
 
@@ -1993,6 +2024,9 @@ int cmd_format_patch(int argc,
 	struct strbuf rdiff1 = STRBUF_INIT;
 	struct strbuf rdiff2 = STRBUF_INIT;
 	struct strbuf rdiff_title = STRBUF_INIT;
+	struct rdiff_notes rdiff_notes = {
+		.notes = STRING_LIST_INIT_NODUP,
+	};
 	const char *rfc = NULL;
 	int creation_factor = -1;
 	const char *signature = git_version_string;
@@ -2089,6 +2123,9 @@ int cmd_format_patch(int argc,
 			     parse_opt_object_name),
 		OPT_STRING(0, "range-diff", &rdiff_prev, N_("refspec"),
 			   N_("show changes against <refspec> in cover letter or single patch")),
+		OPT_CALLBACK_F(0, "range-diff-notes", &rdiff_notes, N_("note"),
+			       N_("override notes behavior for the range diff"),
+			       0, rdiff_notes_cb),
 		OPT_INTEGER(0, "creation-factor", &creation_factor,
 			    N_("percentage by which creation is weighted")),
 		OPT_BOOL(0, "force-in-body-from", &force_in_body_from,
@@ -2404,7 +2441,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);
+		get_notes_args(&rdiff_notes, &rev);
 	}
 
 	/*
@@ -2568,6 +2605,7 @@ int cmd_format_patch(int argc,
 	release_revisions(&rev);
 	format_config_release(&cfg);
 	strvec_clear(&rev.rdiff_log_arg);
+	string_list_clear(&rdiff_notes.notes, 0);
 	return 0;
 }
 
diff --git a/builtin/merge-file.c b/builtin/merge-file.c
index 8fa5765..ddca408 100644
--- a/builtin/merge-file.c
+++ b/builtin/merge-file.c
@@ -64,7 +64,7 @@ int cmd_merge_file(int argc,
 {
 	const char *names[3] = { 0 };
 	mmfile_t mmfs[3] = { 0 };
-	mmbuffer_t result = { 0 };
+	mmfile_t result = { 0 };
 	xmparam_t xmp = { 0 };
 	int ret = 0, i = 0, to_stdout = 0, object_id = 0;
 	int quiet = 0;
diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c
index 49f41e5..552c2ad 100644
--- a/builtin/merge-tree.c
+++ b/builtin/merge-tree.c
@@ -109,7 +109,7 @@ static void *origin(struct merge_list *entry, size_t *size)
 	return NULL;
 }
 
-static int show_outf(void *priv UNUSED, mmbuffer_t *mb, int nbuf)
+static int show_outf(void *priv UNUSED, mmfile_t *mb, int nbuf)
 {
 	int i;
 	for (i = 0; i < nbuf; i++)
diff --git a/builtin/merge.c b/builtin/merge.c
index 5b4eb23..d7bf209 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1863,7 +1863,7 @@ int cmd_merge(int argc,
 	if (squash) {
 		finish(head_commit, remoteheads, NULL, NULL);
 
-		git_test_write_commit_graph_or_die(the_repository->objects->sources);
+		git_test_write_commit_graph_or_die(the_repository);
 	} else
 		write_merge_state(remoteheads);
 
diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c
index 753bd53..c482122 100644
--- a/builtin/multi-pack-index.c
+++ b/builtin/multi-pack-index.c
@@ -80,19 +80,19 @@ static int parse_object_dir(const struct option *opt, const char *arg,
 	char **value = opt->value;
 	free(*value);
 	if (unset)
-		*value = xstrdup(the_repository->objects->sources->path);
+		*value = xstrdup(the_repository->objects->source->path);
 	else
 		*value = real_pathdup(arg, 1);
 	return 0;
 }
 
-static struct odb_source_files *handle_object_dir_option(struct repository *repo)
+static struct odb_source_packed *handle_object_dir_option(struct repository *repo)
 {
-	struct odb_source *source = odb_find_source(repo->objects, opts.object_dir);
-	if (!source)
+	struct odb_files_dir *dir = odb_source_files_find_dir(repo->objects, opts.object_dir);
+	if (!dir)
 		die(_("object directory is not an alternate of the current repository: '%s'"),
 		    opts.object_dir);
-	return odb_source_files_downcast(source);
+	return dir->packed;
 }
 
 static struct option common_opts[] = {
@@ -169,7 +169,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_files *source;
+	struct odb_source_packed *packed_source;
 	int ret;
 
 	opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE;
@@ -204,7 +204,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,
 				   options);
 	}
 
-	source = handle_object_dir_option(repo);
+	packed_source = handle_object_dir_option(repo);
 
 	FREE_AND_NULL(options);
 
@@ -213,7 +213,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,
 
 		read_packs_from_stdin(&packs);
 
-		ret = write_midx_file_only(source->packed, &packs,
+		ret = write_midx_file_only(packed_source, &packs,
 					   opts.preferred_pack,
 					   opts.refs_snapshot,
 					   opts.incremental_base, opts.flags);
@@ -225,7 +225,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,
 
 	}
 
-	ret = write_midx_file(source->packed, opts.preferred_pack,
+	ret = write_midx_file(packed_source, opts.preferred_pack,
 			      opts.refs_snapshot, opts.flags);
 
 	free(opts.refs_snapshot);
@@ -239,7 +239,7 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv,
 	struct multi_pack_index *m, *cur;
 	struct multi_pack_index *from_midx = NULL;
 	struct multi_pack_index *to_midx = NULL;
-	struct odb_source_files *source;
+	struct odb_source_packed *packed_source;
 	int ret;
 
 	struct option *options;
@@ -280,11 +280,11 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv,
 				   options);
 	}
 
-	source = handle_object_dir_option(the_repository);
+	packed_source = handle_object_dir_option(the_repository);
 
 	FREE_AND_NULL(options);
 
-	m = get_multi_pack_index(source->packed);
+	m = get_multi_pack_index(packed_source);
 
 	for (cur = m; cur && !(from_midx && to_midx); cur = cur->base_midx) {
 		const char *midx_csum = midx_get_checksum_hex(cur);
@@ -307,7 +307,7 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv,
 			die(_("MIDX %s must be an ancestor of %s"), argv[0], argv[1]);
 	}
 
-	ret = write_midx_file_compact(source->packed, from_midx, to_midx,
+	ret = write_midx_file_compact(packed_source, from_midx, to_midx,
 				      opts.incremental_base, opts.flags);
 
 	return ret;
@@ -321,7 +321,7 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv,
 	static struct option builtin_multi_pack_index_verify_options[] = {
 		OPT_END(),
 	};
-	struct odb_source_files *source;
+	struct odb_source_packed *packed_source;
 
 	options = add_common_options(builtin_multi_pack_index_verify_options);
 
@@ -335,11 +335,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);
+	packed_source = handle_object_dir_option(the_repository);
 
 	FREE_AND_NULL(options);
 
-	return verify_midx_file(source->packed, opts.flags);
+	return verify_midx_file(packed_source, opts.flags);
 }
 
 static int cmd_multi_pack_index_expire(int argc, const char **argv,
@@ -350,7 +350,7 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv,
 	static struct option builtin_multi_pack_index_expire_options[] = {
 		OPT_END(),
 	};
-	struct odb_source_files *source;
+	struct odb_source_packed *packed_source;
 
 	options = add_common_options(builtin_multi_pack_index_expire_options);
 
@@ -364,11 +364,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);
+	packed_source = handle_object_dir_option(the_repository);
 
 	FREE_AND_NULL(options);
 
-	return expire_midx_packs(source->packed, opts.flags);
+	return expire_midx_packs(packed_source, opts.flags);
 }
 
 static int cmd_multi_pack_index_repack(int argc, const char **argv,
@@ -381,7 +381,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv,
 		  N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")),
 		OPT_END(),
 	};
-	struct odb_source_files *source;
+	struct odb_source_packed *packed_source;
 
 	options = add_common_options(builtin_multi_pack_index_repack_options);
 
@@ -396,11 +396,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);
+	packed_source = handle_object_dir_option(the_repository);
 
 	FREE_AND_NULL(options);
 
-	return midx_repack(source->packed, (size_t)opts.batch_size, opts.flags);
+	return midx_repack(packed_source, (size_t)opts.batch_size, opts.flags);
 }
 
 int cmd_multi_pack_index(int argc,
@@ -426,8 +426,8 @@ int cmd_multi_pack_index(int argc,
 
 	if (the_repository &&
 	    the_repository->objects &&
-	    the_repository->objects->sources)
-		opts.object_dir = xstrdup(the_repository->objects->sources->path);
+	    the_repository->objects->source)
+		opts.object_dir = xstrdup(the_repository->objects->source->path);
 
 	argc = parse_options(argc, argv, prefix, options,
 			     builtin_multi_pack_index_usage, 0);
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index af9390a..ca3a891 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -1566,11 +1566,10 @@ static int want_cruft_object_mtime(struct repository *r,
 				   const struct object_id *oid,
 				   unsigned flags, uint32_t mtime)
 {
-	struct odb_source *source;
+	struct odb_source_files *files = odb_source_files_downcast(r->objects->source);
 
-	for (source = r->objects->sources; source; source = source->next) {
-		struct odb_source_files *files = odb_source_files_downcast(source);
-		struct packed_git **cache = packfile_store_get_kept_pack_cache(files->packed, flags);
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		struct packed_git **cache = packfile_store_get_kept_pack_cache(dir->packed, flags);
 
 		for (; *cache; cache++) {
 			struct packed_git *p = *cache;
@@ -1753,21 +1752,19 @@ static int want_object_in_pack_mtime(const struct object_id *oid,
 				     off_t *found_offset,
 				     uint32_t found_mtime)
 {
+	struct odb_source_files *files =
+		odb_source_files_downcast(the_repository->objects->source);
 	int want;
 	struct packfile_list_entry *e;
-	struct odb_source *source;
 
 	if (!exclude && local) {
 		/*
-		 * Note that we start iterating at `sources->next` so that we
-		 * skip the local object source.
+		 * Note that we start iterating at `dirs->next` so that we
+		 * skip the local object directory.
 		 */
-		struct odb_source *source = the_repository->objects->sources->next;
-		for (; source; source = source->next) {
-			struct odb_source_files *files = odb_source_files_downcast(source);
-			if (!odb_source_read_object_info(&files->loose->base, oid, NULL, 0, NULL))
+		for (struct odb_files_dir *dir = files->dirs->next; dir; dir = dir->next)
+			if (!odb_source_read_object_info(&dir->loose->base, oid, NULL, 0, NULL))
 				return 0;
-		}
 	}
 
 	/*
@@ -1785,9 +1782,8 @@ static int want_object_in_pack_mtime(const struct object_id *oid,
 		*found_offset = 0;
 	}
 
-	for (source = the_repository->objects->sources; source; source = source->next) {
-		struct odb_source_files *files = odb_source_files_downcast(source);
-		struct multi_pack_index *m = get_multi_pack_index(files->packed);
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		struct multi_pack_index *m = get_multi_pack_index(dir->packed);
 		struct pack_entry e;
 
 		if (m && midx_fill_entry(m, oid, &e, NULL) == MIDX_FILL_HIT) {
@@ -1797,14 +1793,12 @@ static int want_object_in_pack_mtime(const struct object_id *oid,
 		}
 	}
 
-	for (source = the_repository->objects->sources; source; source = source->next) {
-		struct odb_source_files *files = odb_source_files_downcast(source);
-
-		for (e = files->packed->packs.head; e; e = e->next) {
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		for (e = dir->packed->packs.head; e; e = e->next) {
 			struct packed_git *p = e->pack;
 			want = want_object_in_pack_one(p, oid, exclude, found_pack, found_offset, found_mtime);
 			if (!exclude && want > 0)
-				packfile_list_prepend(&files->packed->packs, p);
+				packfile_list_prepend(&dir->packed->packs, p);
 			if (want != -1)
 				return want;
 		}
@@ -4170,14 +4164,13 @@ static void add_cruft_object_entry(const struct object_id *oid, enum object_type
 		if (!want_object_in_pack_mtime(oid, 0, &pack, &offset, mtime))
 			return;
 		if (!pack && type == OBJ_BLOB) {
-			struct odb_source *source = the_repository->objects->sources;
+			struct odb_source_files *files =
+				odb_source_files_downcast(the_repository->objects->source);
 			int found = 0;
 
-			for (; !found && source; source = source->next) {
-				struct odb_source_files *files = odb_source_files_downcast(source);
-				if (!odb_source_read_object_info(&files->loose->base, oid, NULL, 0, NULL))
+			for (struct odb_files_dir *dir = files->dirs; !found && dir; dir = dir->next)
+				if (!odb_source_read_object_info(&dir->loose->base, oid, NULL, 0, NULL))
 					found = 1;
-			}
 
 			/*
 			 * If a traversed tree has a missing blob then we want
@@ -4512,7 +4505,8 @@ static int add_object_in_unpacked_pack(const struct object_id *oid,
 
 static void add_objects_in_unpacked_packs(void)
 {
-	struct odb_source *source;
+	struct odb_source_files *files =
+		odb_source_files_downcast(to_pack.repo->objects->source);
 	time_t mtime;
 	struct odb_for_each_object_options opts = {
 		.flags = ODB_FOR_EACH_OBJECT_PACK_ORDER |
@@ -4526,13 +4520,11 @@ static void add_objects_in_unpacked_packs(void)
 		.source_infop = &source_info,
 	};
 
-	for (source = to_pack.repo->objects->sources; source; source = source->next) {
-		struct odb_source_files *files = odb_source_files_downcast(source);
-
-		if (!source->local)
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		if (!dir->local)
 			continue;
 
-		if (odb_source_for_each_object(&files->packed->base, &oi,
+		if (odb_source_for_each_object(&dir->packed->base, &oi,
 					       add_object_in_unpacked_pack, NULL, &opts))
 			die(_("cannot open pack index"));
 	}
@@ -4576,7 +4568,7 @@ static int add_loose_object(const struct object_id *oid, const char *path,
  */
 static void add_unreachable_loose_objects(struct rev_info *revs)
 {
-	for_each_loose_file_in_source(the_repository->objects->sources,
+	for_each_loose_file_in_source(the_repository->objects->source,
 				      add_loose_object, NULL, NULL, revs);
 }
 
@@ -4640,11 +4632,9 @@ static int force_object_loose(struct odb_source *source,
 	size_t len;
 	int ret;
 
-	for (struct odb_source *s = source->odb->sources; s; s = s->next) {
-		struct odb_source_files *files = odb_source_files_downcast(s);
-		if (!odb_source_read_object_info(&files->loose->base, oid, NULL, 0, NULL))
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next)
+		if (!odb_source_read_object_info(&dir->loose->base, oid, NULL, 0, NULL))
 			return 0;
-	}
 
 	oi.typep = &type;
 	oi.sizep = &len;
@@ -4664,7 +4654,7 @@ static int force_object_loose(struct odb_source *source,
 		compat_oid_p = &compat_oid;
 	}
 
-	ret = odb_source_write_object(&files->loose->base, buf, len, type, oid,
+	ret = odb_source_write_object(&files->dirs->loose->base, buf, len, type, oid,
 				      compat_oid_p, mtime, 0);
 
 out:
@@ -4691,7 +4681,7 @@ static void loosen_unused_packed_objects(void)
 			if (!packlist_find(&to_pack, &oid) &&
 			    !has_sha1_pack_kept_or_nonlocal(&oid) &&
 			    !loosened_object_can_be_discarded(&oid, p->mtime)) {
-				if (force_object_loose(the_repository->objects->sources,
+				if (force_object_loose(the_repository->objects->source,
 						       &oid, &p->mtime))
 					die(_("unable to force loose object"));
 				loosened_objects_nr++;
diff --git a/builtin/prune.c b/builtin/prune.c
index a7e4678..97f4c14 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -198,7 +198,7 @@ int cmd_prune(int argc,
 		revs.exclude_promisor_objects = 1;
 	}
 
-	for_each_loose_file_in_source(repo->objects->sources,
+	for_each_loose_file_in_source(repo->objects->source,
 				      prune_object, prune_cruft, prune_subdir, &revs);
 
 	prune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0);
diff --git a/builtin/pull.c b/builtin/pull.c
index db3ee0a..80e79da 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -800,8 +800,12 @@ static int get_can_ff(struct object_id *orig_head,
 
 	orig_merge_head = &merge_heads->oid[0];
 	head = lookup_commit_reference(the_repository, orig_head);
-	commit_list_insert(head, &list);
+	if (!head)
+		return 0;
 	merge_head = lookup_commit_reference(the_repository, orig_merge_head);
+	if (!merge_head)
+		return 0;
+	commit_list_insert(head, &list);
 	ret = repo_is_descendant_of(the_repository, merge_head, list);
 	commit_list_free(list);
 	if (ret < 0)
@@ -820,12 +824,16 @@ static int already_up_to_date(struct object_id *orig_head,
 	struct commit *ours;
 
 	ours = lookup_commit_reference(the_repository, orig_head);
+	if (!ours)
+		return 0;
 	for (size_t i = 0; i < merge_heads->nr; i++) {
 		struct commit_list *list = NULL;
 		struct commit *theirs;
 		int ok;
 
 		theirs = lookup_commit_reference(the_repository, &merge_heads->oid[i]);
+		if (!theirs)
+			return 0;
 		commit_list_insert(theirs, &list);
 		ok = repo_is_descendant_of(the_repository, ours, list);
 		commit_list_free(list);
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 10a3063..535db60 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -762,9 +762,16 @@ static int run_specific_rebase(struct rebase_options *opts)
 
 	if (opts->dont_finish_rebase)
 		; /* do nothing */
-	else if (opts->type == REBASE_MERGE)
-		; /* merge backend cleans up after itself */
-	else if (status == 0) {
+	else if (opts->type == REBASE_MERGE) {
+		int quiet = !(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE));
+
+		/*
+		 * The sequencer cleans up after itself. Its state directory
+		 * is gone once it is done, and stays while it is stopped.
+		 */
+		if (status == 0 && !is_directory(opts->state_dir))
+			run_auto_maintenance(the_repository, quiet);
+	} else if (status == 0) {
 		if (!file_exists(state_dir_path("stopped-sha", opts)))
 			finish_rebase(opts);
 	} else if (status == 2) {
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e6e54ba..1d5b050 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -53,6 +53,12 @@ enum deny_action {
 	DENY_UPDATE_INSTEAD
 };
 
+enum report_status_version {
+	REPORT_STATUS_UNKNOWN = 0,
+	REPORT_STATUS_V0,
+	REPORT_STATUS_V2,
+};
+
 static int deny_deletes;
 static int deny_non_fast_forwards;
 static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
@@ -64,8 +70,6 @@ static int advertise_atomic_push = 1;
 static int advertise_push_options;
 static int advertise_sid;
 static off_t max_input_size;
-static int report_status;
-static int report_status_v2;
 static int use_sideband;
 static int use_atomic;
 static int use_push_options;
@@ -988,6 +992,41 @@ static int run_update_hook(struct command *cmd)
 	return code;
 }
 
+static int run_receive_report_hook(struct strbuf *report)
+{
+	struct child_process proc = CHILD_PROCESS_INIT;
+	struct async sideband_async;
+	int sideband_async_started = 0;
+	int saved_stderr = -1;
+	struct strbuf out = STRBUF_INIT;
+	const char *hook_path;
+	int ret;
+
+	hook_path = find_hook(the_repository, "receive-report");
+	if (!hook_path)
+		return 0;
+
+	strvec_push(&proc.args, hook_path);
+	proc.trace2_hook_name = "receive-report";
+
+	prepare_sideband_async(&sideband_async, &saved_stderr,
+			       &sideband_async_started);
+
+	sigchain_push(SIGPIPE, SIG_IGN);
+	ret = pipe_command(&proc, report->buf, report->len, &out,
+			   report->len, NULL, 0);
+	sigchain_pop(SIGPIPE);
+
+	finish_sideband_async(&sideband_async, saved_stderr,
+			      sideband_async_started);
+
+	if (!ret)
+		strbuf_swap(&out, report);
+
+	strbuf_release(&out);
+	return ret;
+}
+
 static struct command *find_command_by_refname(struct command *list,
 					       const char *refname)
 {
@@ -2191,7 +2230,8 @@ static void queue_commands_from_cert(struct command **tail,
 }
 
 static struct command *read_head_info(struct packet_reader *reader,
-				      struct oid_array *shallow)
+				      struct oid_array *shallow,
+				      enum report_status_version *version)
 {
 	struct command *commands = NULL;
 	struct command **p = &commands;
@@ -2217,9 +2257,9 @@ static struct command *read_head_info(struct packet_reader *reader,
 			const char *client_sid;
 			size_t len = 0;
 			if (parse_feature_request(feature_list, "report-status"))
-				report_status = 1;
+				*version = REPORT_STATUS_V0;
 			if (parse_feature_request(feature_list, "report-status-v2"))
-				report_status_v2 = 1;
+				*version = REPORT_STATUS_V2;
 			if (parse_feature_request(feature_list, "side-band-64k"))
 				use_sideband = LARGE_PACKET_MAX;
 			if (parse_feature_request(feature_list, "quiet"))
@@ -2409,67 +2449,72 @@ static void update_shallow_info(struct command *commands,
 	free(ref_status);
 }
 
-static void report(struct command *commands, const struct strbuf *unpack_status)
+static void override_cmds_error(struct command *commands, const char *err)
 {
-	struct command *cmd;
-	struct strbuf buf = STRBUF_INIT;
-
-	packet_buf_write(&buf, "unpack %s\n",
-			 unpack_status->len ? unpack_status->buf : "ok");
-	for (cmd = commands; cmd; cmd = cmd->next) {
-		if (!cmd->error_string)
-			packet_buf_write(&buf, "ok %s\n",
-					 cmd->ref_name);
-		else
-			packet_buf_write(&buf, "ng %s %s\n",
-					 cmd->ref_name, cmd->error_string);
+	for (struct command *cmd = commands; cmd; cmd = cmd->next) {
+		FREE_AND_NULL(cmd->error_string_owned);
+		cmd->error_string = err;
 	}
-	packet_buf_flush(&buf);
-
-	if (use_sideband)
-		send_sideband(1, 1, buf.buf, buf.len, use_sideband);
-	else
-		write_or_die(1, buf.buf, buf.len);
-	strbuf_release(&buf);
 }
 
-static void report_v2(struct command *commands, const struct strbuf *unpack_status)
+/*
+ * Generate the response to be sent to the client invoking 'git-receive-pack(1)'.
+ */
+static void generate_report(struct strbuf *buf, struct command *commands,
+			    const struct strbuf *unpack_status,
+			    enum report_status_version version)
 {
 	struct command *cmd;
-	struct strbuf buf = STRBUF_INIT;
-	struct ref_push_report *report;
 
-	packet_buf_write(&buf, "unpack %s\n",
+	packet_buf_write(buf, "unpack %s\n",
 			 unpack_status->len ? unpack_status->buf : "ok");
+
 	for (cmd = commands; cmd; cmd = cmd->next) {
+		struct ref_push_report *report;
 		int count = 0;
 
-		if (cmd->error_string) {
-			packet_buf_write(&buf, "ng %s %s\n",
-					 cmd->ref_name,
-					 cmd->error_string);
+		if (cmd->error_string)
+			packet_buf_write(buf, "ng %s %s\n",
+					 cmd->ref_name, cmd->error_string);
+		else
+			packet_buf_write(buf, "ok %s\n", cmd->ref_name);
+
+		if (version != REPORT_STATUS_V2 || cmd->error_string)
 			continue;
-		}
-		packet_buf_write(&buf, "ok %s\n",
-				 cmd->ref_name);
+
 		for (report = cmd->report; report; report = report->next) {
 			if (count++ > 0)
-				packet_buf_write(&buf, "ok %s\n",
+				packet_buf_write(buf, "ok %s\n",
 						 cmd->ref_name);
 			if (report->ref_name)
-				packet_buf_write(&buf, "option refname %s\n",
+				packet_buf_write(buf, "option refname %s\n",
 						 report->ref_name);
 			if (report->old_oid)
-				packet_buf_write(&buf, "option old-oid %s\n",
+				packet_buf_write(buf, "option old-oid %s\n",
 						 oid_to_hex(report->old_oid));
 			if (report->new_oid)
-				packet_buf_write(&buf, "option new-oid %s\n",
+				packet_buf_write(buf, "option new-oid %s\n",
 						 oid_to_hex(report->new_oid));
 			if (report->forced_update)
-				packet_buf_write(&buf, "option forced-update\n");
+				packet_buf_write(buf, "option forced-update\n");
 		}
 	}
-	packet_buf_flush(&buf);
+
+	packet_buf_flush(buf);
+}
+
+static void report(struct command *commands, const struct strbuf *unpack_status,
+		   enum report_status_version version)
+{
+	struct strbuf buf = STRBUF_INIT;
+
+	generate_report(&buf, commands, unpack_status, version);
+
+	if (run_receive_report_hook(&buf)) {
+		strbuf_reset(&buf);
+		override_cmds_error(commands, "receive-report hook failed");
+		generate_report(&buf, commands, unpack_status, version);
+	}
 
 	if (use_sideband)
 		send_sideband(1, 1, buf.buf, buf.len, use_sideband);
@@ -2500,6 +2545,7 @@ int cmd_receive_pack(int argc,
 	struct shallow_info si;
 	struct packet_reader reader;
 	struct odb_transaction *transaction = NULL;
+	enum report_status_version version = REPORT_STATUS_UNKNOWN;
 
 	struct option options[] = {
 		OPT__QUIET(&quiet, N_("quiet")),
@@ -2563,7 +2609,7 @@ int cmd_receive_pack(int argc,
 			   PACKET_READ_CHOMP_NEWLINE |
 			   PACKET_READ_DIE_ON_ERR_PACKET);
 
-	if ((commands = read_head_info(&reader, &shallow))) {
+	if ((commands = read_head_info(&reader, &shallow, &version))) {
 		struct string_list push_options = STRING_LIST_INIT_DUP;
 		struct strbuf unpack_status = STRBUF_INIT;
 
@@ -2596,10 +2642,16 @@ int cmd_receive_pack(int argc,
 				 &push_options);
 		odb_transaction_finalize(transaction);
 		sigchain_push(SIGPIPE, SIG_IGN);
-		if (report_status_v2)
-			report_v2(commands, &unpack_status);
-		else if (report_status)
-			report(commands, &unpack_status);
+
+		switch (version) {
+		case REPORT_STATUS_V2:
+		case REPORT_STATUS_V0:
+			report(commands, &unpack_status, version);
+			break;
+		case REPORT_STATUS_UNKNOWN:
+			break;
+		}
+
 		sigchain_pop(SIGPIPE);
 		run_receive_hook(commands, "post-receive", 1, NULL,
 				 &push_options);
diff --git a/builtin/refs.c b/builtin/refs.c
index 5cd21c2..0847dfd 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -10,7 +10,7 @@
 #include "refs/refs-internal.h"
 
 #define REFS_MIGRATE_USAGE \
-	N_("git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]")
+	N_("git refs migrate --ref-storage-format=<format> [--no-reflog] [--dry-run]")
 
 #define REFS_VERIFY_USAGE \
 	N_("git refs verify [--strict] [--verbose]")
@@ -44,9 +44,10 @@ static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,
 	enum ref_storage_format format;
 	unsigned int flags = 0;
 	struct option options[] = {
-		OPT_STRING_F(0, "ref-format", &format_str, N_("format"),
-			N_("specify the reference format to convert to"),
+		OPT_STRING_F(0, "ref-storage-format", &format_str, N_("format"),
+			N_("specify the reference storage format to convert to"),
 			PARSE_OPT_NONEG),
+		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
 		OPT_BIT(0, "dry-run", &flags,
 			N_("perform a non-destructive dry-run"),
 			REPO_MIGRATE_REF_STORAGE_FORMAT_DRYRUN),
@@ -62,7 +63,7 @@ static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,
 	if (argc)
 		usage(_("too many arguments"));
 	if (!format_str)
-		usage(_("missing --ref-format=<format>"));
+		usage(_("missing --ref-storage-format=<format>"));
 
 	format = ref_storage_format_by_name(format_str);
 	if (format == REF_STORAGE_FORMAT_UNKNOWN) {
@@ -382,9 +383,9 @@ int cmd_refs(int argc,
 	const char * const refs_usage[] = {
 		REFS_MIGRATE_USAGE,
 		REFS_VERIFY_USAGE,
+		REFS_OPTIMIZE_USAGE,
 		"git refs list " COMMON_USAGE_FOR_EACH_REF,
 		REFS_EXISTS_USAGE,
-		REFS_OPTIMIZE_USAGE,
 		REFS_CREATE_USAGE,
 		REFS_DELETE_USAGE,
 		REFS_UPDATE_USAGE,
@@ -393,15 +394,27 @@ int cmd_refs(int argc,
 	};
 	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_SUBCOMMAND("create", &fn, cmd_refs_create),
-		OPT_SUBCOMMAND("delete", &fn, cmd_refs_delete),
-		OPT_SUBCOMMAND("update", &fn, cmd_refs_update),
-		OPT_SUBCOMMAND("rename", &fn, cmd_refs_rename),
+		OPT_GROUP(N_("Reference database")),
+		OPT_SUBCOMMAND_F("migrate", &fn, cmd_refs_migrate,
+				 N_("migrate the reference database to a different format"), 0),
+		OPT_SUBCOMMAND_F("verify", &fn, cmd_refs_verify,
+				 N_("verify the consistency of the reference database"), 0),
+		OPT_SUBCOMMAND_F("optimize", &fn, cmd_refs_optimize,
+				 N_("optimize the reference database"), 0),
+		OPT_GROUP(N_("Reading references")),
+		OPT_SUBCOMMAND_F("list", &fn, cmd_refs_list,
+				 N_("list references"), 0),
+		OPT_SUBCOMMAND_F("exists", &fn, cmd_refs_exists,
+				 N_("check whether a reference exists"), 0),
+		OPT_GROUP(N_("Writing references")),
+		OPT_SUBCOMMAND_F("create", &fn, cmd_refs_create,
+				 N_("create a new reference"), 0),
+		OPT_SUBCOMMAND_F("delete", &fn, cmd_refs_delete,
+				 N_("delete a reference"), 0),
+		OPT_SUBCOMMAND_F("update", &fn, cmd_refs_update,
+				 N_("update an existing reference"), 0),
+		OPT_SUBCOMMAND_F("rename", &fn, cmd_refs_rename,
+				 N_("rename a reference"), 0),
 		OPT_END(),
 	};
 
diff --git a/builtin/remote.c b/builtin/remote.c
index de989ea..fb7e5b1 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1940,7 +1940,7 @@ int cmd_remote(int argc,
 		OPT__VERBOSE(&verbose, N_("be verbose; must be placed before a subcommand")),
 		OPT_SUBCOMMAND("add", &fn, add),
 		OPT_SUBCOMMAND("rename", &fn, mv),
-		OPT_SUBCOMMAND_F("rm", &fn, rm, PARSE_OPT_NOCOMPLETE),
+		OPT_SUBCOMMAND_F("rm", &fn, rm, NULL, PARSE_OPT_NOCOMPLETE),
 		OPT_SUBCOMMAND("remove", &fn, rm),
 		OPT_SUBCOMMAND("set-head", &fn, set_head),
 		OPT_SUBCOMMAND("set-branches", &fn, set_branches),
diff --git a/builtin/repack.c b/builtin/repack.c
index c436038..5d06872 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -617,7 +617,7 @@ int cmd_repack(int argc,
 		 * midx_has_unknown_packs() will make the decision for
 		 * us.
 		 */
-		if (!get_multi_pack_index(files->packed))
+		if (!get_multi_pack_index(files->dirs->packed))
 			midx_must_contain_cruft = 1;
 	}
 
@@ -775,7 +775,7 @@ int cmd_repack(int argc,
 
 		if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL, 0))
 			flags |= MIDX_WRITE_INCREMENTAL;
-		write_midx_file(files->packed, NULL, NULL, flags);
+		write_midx_file(files->dirs->packed, NULL, NULL, flags);
 	}
 
 cleanup:
diff --git a/builtin/rerere.c b/builtin/rerere.c
index a056cb7..ef03b79 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -16,7 +16,7 @@ static const char * const rerere_usage[] = {
 	NULL,
 };
 
-static int outf(void *dummy UNUSED, mmbuffer_t *ptr, int nbuf)
+static int outf(void *dummy UNUSED, mmfile_t *ptr, int nbuf)
 {
 	int i;
 	for (i = 0; i < nbuf; i++)
@@ -34,8 +34,12 @@ static int diff_two(const char *file1, const char *label1,
 	mmfile_t minus, plus;
 	int ret;
 
-	if (read_mmfile(&minus, file1) || read_mmfile(&plus, file2))
+	if (read_mmfile(&minus, file1))
 		return -1;
+	if (read_mmfile(&plus, file2)) {
+		free(minus.ptr);
+		return -1;
+	}
 
 	printf("--- a/%s\n+++ b/%s\n", label1, label2);
 	fflush(stdout);
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 4369345..ec33c19 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -1134,7 +1134,7 @@ int cmd_rev_parse(int argc,
 				}
 				continue;
 			}
-			if (!strcmp(arg, "--show-ref-format")) {
+			if (!strcmp(arg, "--show-ref-format") || !strcmp(arg, "--show-ref-storage-format")) {
 				puts(ref_storage_format_to_name(the_repository->ref_storage_format));
 				continue;
 			}
diff --git a/builtin/revert.c b/builtin/revert.c
index bedc40f..52100a2 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -8,6 +8,7 @@
 #include "gettext.h"
 #include "revision.h"
 #include "rerere.h"
+#include "run-command.h"
 #include "sequencer.h"
 #include "branch.h"
 
@@ -116,7 +117,7 @@ static int run_sequencer(int argc, const char **argv, const char *prefix,
 	const char *strategy = &sentinel_value;
 	const char *gpg_sign = &sentinel_value;
 	enum empty_action empty_opt = EMPTY_COMMIT_UNSPECIFIED;
-	int cmd = 0;
+	int cmd = 0, ret;
 	struct option base_options[] = {
 		OPT_CMDMODE(0, "quit", &cmd, N_("end revert or cherry-pick sequence"), 'q'),
 		OPT_CMDMODE(0, "continue", &cmd, N_("resume revert or cherry-pick sequence"), 'c'),
@@ -264,18 +265,22 @@ static int run_sequencer(int argc, const char **argv, const char *prefix,
 	free(options);
 
 	if (cmd == 'q') {
-		int ret = sequencer_remove_state(opts);
+		ret = sequencer_remove_state(opts);
 		if (!ret)
 			remove_branch_state(the_repository, 0);
 		return ret;
 	}
-	if (cmd == 'c')
-		return sequencer_continue(the_repository, opts);
 	if (cmd == 'a')
 		return sequencer_rollback(the_repository, opts);
-	if (cmd == 's')
-		return sequencer_skip(the_repository, opts);
-	return sequencer_pick_revisions(the_repository, opts);
+	if (cmd == 'c')
+		ret = sequencer_continue(the_repository, opts);
+	else if (cmd == 's')
+		ret = sequencer_skip(the_repository, opts);
+	else
+		ret = sequencer_pick_revisions(the_repository, opts);
+	if (!ret)
+		run_auto_maintenance(the_repository, opts->quiet);
+	return ret;
 }
 
 int cmd_revert(int argc,
diff --git a/builtin/stash.c b/builtin/stash.c
index 7a98434..e1a3a76 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -31,7 +31,6 @@
 #include "reflog.h"
 #include "reflog-walk.h"
 #include "add-interactive.h"
-#include "oid-array.h"
 #include "commit.h"
 
 #define INCLUDE_ALL_FILES 2
@@ -423,50 +422,6 @@ static int create_index_from_tree(const struct object_id *tree_id,
 	return ret;
 }
 
-static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit)
-{
-	struct child_process cp = CHILD_PROCESS_INIT;
-	const char *w_commit_hex = oid_to_hex(w_commit);
-
-	/*
-	 * Diff-tree would not be very hard to replace with a native function,
-	 * however it should be done together with apply_cached.
-	 */
-	cp.git_cmd = 1;
-	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);
-}
-
-static int apply_cached(struct strbuf *out)
-{
-	struct child_process cp = CHILD_PROCESS_INIT;
-
-	/*
-	 * Apply currently only reads either from stdin or a file, thus
-	 * apply_all_patches would have to be updated to optionally take a
-	 * buffer.
-	 */
-	cp.git_cmd = 1;
-	strvec_pushl(&cp.args, "apply", "--cached", NULL);
-	return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0);
-}
-
-static int reset_head(void)
-{
-	struct child_process cp = CHILD_PROCESS_INIT;
-
-	/*
-	 * Reset is overall quite simple, however there is no current public
-	 * API for resetting.
-	 */
-	cp.git_cmd = 1;
-	strvec_pushl(&cp.args, "reset", "--quiet", "--refresh", NULL);
-
-	return run_command(&cp);
-}
-
 static int is_path_a_directory(const char *path)
 {
 	/*
@@ -665,39 +620,43 @@ static enum stash_apply_result do_apply_stash(const char *prefix,
 				repo_get_index_file(the_repository), 0, NULL))
 		return error(_("cannot apply a stash in the middle of a merge"));
 
+	init_ui_merge_options(&o, the_repository);
+
+	if (quiet)
+		o.verbosity = 0;
+
 	if (index) {
 		if (oideq(&info->b_tree, &info->i_tree) ||
 		    oideq(&c_tree, &info->i_tree)) {
 			has_index = 0;
 		} else {
-			struct strbuf out = STRBUF_INIT;
+			struct merge_result result = { 0 };
 
-			if (diff_tree_binary(&out, &info->w_commit)) {
-				strbuf_release(&out);
-				return error(_("could not generate diff %s^!."),
-					     oid_to_hex(&info->w_commit));
-			}
+			o.branch1 = "Current index";
+			o.branch2 = "Stashed index changes";
+			o.ancestor = "Stash base";
 
-			ret = apply_cached(&out);
-			strbuf_release(&out);
-			if (ret)
+			head = lookup_tree(o.repo, &c_tree);
+			merge = lookup_tree(o.repo, &info->i_tree);
+			merge_base = lookup_tree(o.repo, &info->b_tree);
+
+			merge_incore_nonrecursive(&o, merge_base, head, merge,
+						  &result);
+
+			if (result.clean < 0) {
+				merge_finalize(&o, &result);
+				return error(_("index merge failed"));
+			} else if (!result.clean) {
+				merge_finalize(&o, &result);
 				return error(_("conflicts in index. "
 					       "Try without --index."));
-
-			discard_index(the_repository->index);
-			repo_read_index(the_repository);
-			if (write_index_as_tree(&index_tree, the_repository->index,
-						repo_get_index_file(the_repository), 0, NULL))
-				return error(_("could not save index tree"));
-
-			reset_head();
-			discard_index(the_repository->index);
-			repo_read_index(the_repository);
+			} else {
+				oidcpy(&index_tree, &result.tree->object.oid);
+				merge_finalize(&o, &result);
+			}
 		}
 	}
 
-	init_ui_merge_options(&o, the_repository);
-
 	o.branch1 = label_ours ? label_ours : "Updated upstream";
 	o.branch2 = label_theirs ? label_theirs : "Stashed changes";
 	o.ancestor = label_base ? label_base : "Stash base";
@@ -705,9 +664,6 @@ static enum stash_apply_result do_apply_stash(const char *prefix,
 	if (oideq(&info->b_tree, &c_tree))
 		o.branch1 = "Version stash was based on";
 
-	if (quiet)
-		o.verbosity = 0;
-
 	if (o.verbosity >= 3)
 		printf_ln(_("Merging %s with %s"), o.branch1, o.branch2);
 
@@ -1932,7 +1888,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 		OPT_BOOL('u', "include-untracked", &include_untracked,
 			 N_("include untracked files in stash")),
 		OPT_SET_INT('a', "all", &include_untracked,
-			    N_("include ignore files"), 2),
+			    N_("include ignore files"), INCLUDE_ALL_FILES),
 		OPT_STRING('m', "message", &stash_msg, N_("message"),
 			   N_("stash message")),
 		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
@@ -2039,7 +1995,7 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 		OPT_BOOL('u', "include-untracked", &include_untracked,
 			 N_("include untracked files in stash")),
 		OPT_SET_INT('a', "all", &include_untracked,
-			    N_("include ignore files"), 2),
+			    N_("include ignore files"), INCLUDE_ALL_FILES),
 		OPT_STRING('m', "message", &stash_msg, "message",
 			   N_("stash message")),
 		OPT_END()
@@ -2475,7 +2431,7 @@ int cmd_stash(int argc,
 		OPT_SUBCOMMAND("push", &fn, push_stash_unassumed),
 		OPT_SUBCOMMAND("export", &fn, export_stash),
 		OPT_SUBCOMMAND("import", &fn, import_stash),
-		OPT_SUBCOMMAND_F("save", &fn, save_stash, PARSE_OPT_NOCOMPLETE),
+		OPT_SUBCOMMAND_F("save", &fn, save_stash, NULL, PARSE_OPT_NOCOMPLETE),
 		OPT_END()
 	};
 	const char **args_copy;
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 5d3bcda..64412ad 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -30,6 +30,7 @@
 #include "object-name.h"
 #include "odb.h"
 #include "odb/source.h"
+#include "odb/source-files.h"
 #include "advice.h"
 #include "branch.h"
 #include "list-objects-filter-options.h"
@@ -1770,7 +1771,7 @@ static const char alternate_error_advice[] = N_(
 );
 
 static int add_possible_reference_from_superproject(
-		struct odb_source *alt_odb, void *sas_cb)
+		struct odb_files_dir *alt_odb, void *sas_cb)
 {
 	struct submodule_alternate_setup *sas = sas_cb;
 	size_t len;
@@ -1779,12 +1780,12 @@ static int add_possible_reference_from_superproject(
 	 * If the alternate object store is another repository, try the
 	 * standard layout with .git/(modules/<name>)+/objects
 	 */
-	if (strip_suffix(alt_odb->path, "/objects", &len)) {
+	if (strip_suffix(alt_odb->abspath, "/objects", &len)) {
 		struct repository alternate;
 		char *sm_alternate;
 		struct strbuf sb = STRBUF_INIT;
 		struct strbuf err = STRBUF_INIT;
-		strbuf_add(&sb, alt_odb->path, len);
+		strbuf_add(&sb, alt_odb->abspath, len);
 
 		if (repo_init(&alternate, sb.buf, NULL) < 0)
 			die(_("could not get a repository handle for gitdir '%s'"),
@@ -1945,7 +1946,7 @@ static int clone_submodule(const struct module_clone_data *clone_data,
 					     item->string, NULL);
 		}
 		if (clone_data->ref_storage_format != REF_STORAGE_FORMAT_UNKNOWN)
-			strvec_pushf(&cp.args, "--ref-format=%s",
+			strvec_pushf(&cp.args, "--ref-storage-format=%s",
 				     ref_storage_format_to_name(clone_data->ref_storage_format));
 		if (clone_data->dissociate)
 			strvec_push(&cp.args, "--dissociate");
@@ -2061,8 +2062,9 @@ static int module_clone(int argc, const char **argv, const char *prefix,
 		OPT_STRING_LIST(0, "reference", &reference,
 			   N_("repo"),
 			   N_("reference repository")),
-		OPT_STRING(0, "ref-format", &ref_storage_format, N_("format"),
-			   N_("specify the reference format to use")),
+		OPT_STRING(0, "ref-storage-format", &ref_storage_format, N_("format"),
+			   N_("specify the reference storage format to use")),
+		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
 		OPT_BOOL(0, "dissociate", &dissociate,
 			   N_("use --reference only while cloning")),
 		OPT_INTEGER(0, "depth", &clone_data.depth,
@@ -2361,7 +2363,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
 	if (suc->update_data->require_init)
 		strvec_push(&child->args, "--require-init");
 	if (suc->update_data->ref_storage_format != REF_STORAGE_FORMAT_UNKNOWN)
-		strvec_pushf(&child->args, "--ref-format=%s",
+		strvec_pushf(&child->args, "--ref-storage-format=%s",
 			     ref_storage_format_to_name(suc->update_data->ref_storage_format));
 	strvec_pushl(&child->args, "--path", sub->path, NULL);
 	strvec_pushl(&child->args, "--name", sub->name, NULL);
@@ -2805,7 +2807,7 @@ static void update_data_to_args(const struct update_data *update_data,
 			strvec_pushl(args, "--reference", item->string, NULL);
 	}
 	if (update_data->ref_storage_format != REF_STORAGE_FORMAT_UNKNOWN)
-		strvec_pushf(args, "--ref-format=%s",
+		strvec_pushf(args, "--ref-storage-format=%s",
 			     ref_storage_format_to_name(update_data->ref_storage_format));
 	if (update_data->filter_options && update_data->filter_options->choice)
 		strvec_pushf(args, "--filter=%s",
@@ -3014,8 +3016,9 @@ static int module_update(int argc, const char **argv, const char *prefix,
 			SM_UPDATE_REBASE),
 		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
 			   N_("reference repository")),
-		OPT_STRING(0, "ref-format", &ref_storage_format, N_("format"),
-			   N_("specify the reference format to use")),
+		OPT_STRING(0, "ref-storage-format", &ref_storage_format, N_("format"),
+			   N_("specify the reference storage format to use")),
+		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
 		OPT_BOOL(0, "dissociate", &opt.dissociate,
 			   N_("use --reference only while cloning")),
 		OPT_INTEGER(0, "depth", &opt.depth,
@@ -3663,8 +3666,9 @@ static int module_add(int argc, const char **argv, const char *prefix,
 		OPT_BOOL(0, "progress", &progress, N_("force cloning progress")),
 		OPT_STRING(0, "reference", &add_data.reference_path, N_("repository"),
 			   N_("reference repository")),
-		OPT_STRING(0, "ref-format", &ref_storage_format, N_("format"),
-			   N_("specify the reference format to use")),
+		OPT_STRING(0, "ref-storage-format", &ref_storage_format, N_("format"),
+			   N_("specify the reference storage format to use")),
+		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
 		OPT_BOOL(0, "dissociate", &dissociate, N_("borrow the objects from reference repositories")),
 		OPT_STRING(0, "name", &add_data.sm_name, N_("name"),
 			   N_("sets the submodule's name to the given string "
diff --git a/builtin/update-index.c b/builtin/update-index.c
index b25d4ec..018b78d 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -130,7 +130,7 @@ static void xrmdir(const char *path)
 static void avoid_racy(void)
 {
 	/*
-	 * not use if we could usleep(10) if USE_NSEC is defined. The
+	 * not use if we could usleep(10) if core.useNanosec is enabled. The
 	 * field nsec could be there, but the OS could choose to
 	 * ignore it?
 	 */
diff --git a/builtin/upload-pack.c b/builtin/upload-pack.c
index 32831fb..53e76de 100644
--- a/builtin/upload-pack.c
+++ b/builtin/upload-pack.c
@@ -46,7 +46,6 @@ int cmd_upload_pack(int argc,
 	packet_trace_identity("upload-pack");
 	disable_replace_refs();
 	save_commit_buffer = 0;
-	xsetenv(NO_LAZY_FETCH_ENVIRONMENT, "1", 0);
 
 	argc = parse_options(argc, argv, prefix, options, upload_pack_usage, 0);
 
@@ -62,6 +61,24 @@ int cmd_upload_pack(int argc,
 	if (!enter_repo(the_repository, dir, enter_repo_flags))
 		die("'%s' does not appear to be a git repository", dir);
 
+	/*
+	 * Lazily fetching while serving a client would run `git fetch`,
+	 * which may execute arbitrary commands from the configuration
+	 * and hooks of the served repo, so we disable it by default as
+	 * we trust nobody. There are two ways for a server operator to
+	 * allow it though:
+	 *
+	 *   - if GIT_NO_LAZY_FETCH is already set, we leave it alone and
+	 *     honor whatever the operator put there,
+	 *
+	 *   - otherwise, if the served repo is in the
+	 *     "uploadpack.lazyFetchTrusted" protected allowlist, we
+	 *     don't disable lazy fetching.
+	 */
+	if (!getenv(NO_LAZY_FETCH_ENVIRONMENT) &&
+	    !upload_pack_lazy_fetch_trusted(the_repository))
+		xsetenv(NO_LAZY_FETCH_ENVIRONMENT, "1", 1);
+
 	switch (determine_protocol_version_server()) {
 	case protocol_v2:
 		if (advertise_refs)
diff --git a/builtin/var.c b/builtin/var.c
index cc3a43c..dd4325e 100644
--- a/builtin/var.c
+++ b/builtin/var.c
@@ -14,23 +14,95 @@
 #include "environment.h"
 #include "ident.h"
 #include "pager.h"
-#include "refs.h"
+#include "parse-options.h"
 #include "path.h"
-#include "strbuf.h"
+#include "refs.h"
 #include "run-command.h"
+#include "strbuf.h"
+#include "string-list.h"
 
-static const char var_usage[] = "git var (-l | <variable>)";
+static const char * const var_usage[] = {
+	N_("git var [-z] -l"),
+	N_("git var [-z] <variable>..."),
+	NULL
+};
+
+enum ident_part {
+	IDENT_NAME,
+	IDENT_MAIL,
+	IDENT_DATE,
+};
+
+static char *ident_part(const char *ident, enum ident_part part)
+{
+	struct ident_split split;
+
+	if (!ident)
+		return NULL;
+	if (split_ident_line(&split, ident, strlen(ident)))
+		return NULL;
+
+	switch (part) {
+	case IDENT_NAME:
+		if (!split.name_begin || !split.name_end)
+			BUG("split_ident_line() gave NULL names???");
+		return xmemdupz(split.name_begin,
+				split.name_end - split.name_begin);
+	case IDENT_MAIL:
+		if (!split.mail_begin || !split.mail_end)
+			BUG("split_ident_line() gave NULL mail???");
+		return xmemdupz(split.mail_begin,
+				split.mail_end - split.mail_begin);
+	case IDENT_DATE:
+		if (!split.date_begin || !split.tz_end)
+			BUG("split_ident_line() gave NULL date/tz???");
+		return xmemdupz(split.date_begin,
+				split.tz_end - split.date_begin);
+	default:
+		BUG("unknown ident_part %d", part);
+	}
+}
 
 static char *committer(int ident_flag)
 {
 	return xstrdup_or_null(git_committer_info(ident_flag));
 }
 
+static char *committer_name(int ident_flag)
+{
+	return ident_part(git_committer_info(ident_flag), IDENT_NAME);
+}
+
+static char *committer_email(int ident_flag)
+{
+	return ident_part(git_committer_info(ident_flag), IDENT_MAIL);
+}
+
+static char *committer_date(int ident_flag)
+{
+	return ident_part(git_committer_info(ident_flag), IDENT_DATE);
+}
+
 static char *author(int ident_flag)
 {
 	return xstrdup_or_null(git_author_info(ident_flag));
 }
 
+static char *author_name(int ident_flag)
+{
+	return ident_part(git_author_info(ident_flag), IDENT_NAME);
+}
+
+static char *author_email(int ident_flag)
+{
+	return ident_part(git_author_info(ident_flag), IDENT_MAIL);
+}
+
+static char *author_date(int ident_flag)
+{
+	return ident_part(git_author_info(ident_flag), IDENT_DATE);
+}
+
 static char *editor(int ident_flag UNUSED)
 {
 	return xstrdup_or_null(git_editor());
@@ -90,35 +162,27 @@ static char *git_config_val_system(int ident_flag UNUSED)
 	return NULL;
 }
 
-static char *git_config_val_global(int ident_flag UNUSED)
+static void git_config_val_global(struct string_list *list)
 {
-	struct strbuf buf = STRBUF_INIT;
 	char *user, *xdg;
-	size_t unused;
 
 	git_global_config_paths(&user, &xdg);
 	if (xdg && *xdg) {
 		normalize_path_copy(xdg, xdg);
-		strbuf_addf(&buf, "%s\n", xdg);
+		string_list_append(list, xdg);
 	}
 	if (user && *user) {
 		normalize_path_copy(user, user);
-		strbuf_addf(&buf, "%s\n", user);
+		string_list_append(list, user);
 	}
 	free(xdg);
 	free(user);
-	strbuf_trim_trailing_newline(&buf);
-	if (buf.len == 0) {
-		strbuf_release(&buf);
-		return NULL;
-	}
-	return strbuf_detach(&buf, &unused);
 }
 
 struct git_var {
 	const char *name;
 	char *(*read)(int);
-	int multivalued;
+	void (*multiread)(struct string_list *);
 };
 static struct git_var git_vars[] = {
 	{
@@ -126,10 +190,34 @@ static struct git_var git_vars[] = {
 		.read = committer,
 	},
 	{
+		.name = "GIT_COMMITTER_NAME",
+		.read = committer_name,
+	},
+	{
+		.name = "GIT_COMMITTER_EMAIL",
+		.read = committer_email,
+	},
+	{
+		.name = "GIT_COMMITTER_DATE",
+		.read = committer_date,
+	},
+	{
 		.name = "GIT_AUTHOR_IDENT",
 		.read = author,
 	},
 	{
+		.name = "GIT_AUTHOR_NAME",
+		.read = author_name,
+	},
+	{
+		.name = "GIT_AUTHOR_EMAIL",
+		.read = author_email,
+	},
+	{
+		.name = "GIT_AUTHOR_DATE",
+		.read = author_date,
+	},
+	{
 		.name = "GIT_EDITOR",
 		.read = editor,
 	},
@@ -163,8 +251,7 @@ static struct git_var git_vars[] = {
 	},
 	{
 		.name = "GIT_CONFIG_GLOBAL",
-		.read = git_config_val_global,
-		.multivalued = 1,
+		.multiread = git_config_val_global,
 	},
 	{
 		.name = "",
@@ -172,31 +259,36 @@ static struct git_var git_vars[] = {
 	},
 };
 
-static void list_vars(void)
+static void list_vars(int nul_term)
 {
 	struct git_var *ptr;
-	char *val;
+	char delim = nul_term ? '\n' : '=';
+	char term = nul_term ? '\0' : '\n';
 
-	for (ptr = git_vars; ptr->read; ptr++)
-		if ((val = ptr->read(0))) {
-			if (ptr->multivalued && *val) {
-				struct string_list list = STRING_LIST_INIT_DUP;
+	for (ptr = git_vars; ptr->read || ptr->multiread; ptr++) {
+		if (ptr->read) {
+			char *val = ptr->read(0);
 
-				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);
-			} else {
-				printf("%s=%s\n", ptr->name, val);
+			if (val) {
+				printf("%s%c%s%c", ptr->name, delim, val, term);
+				free(val);
 			}
-			free(val);
+		} else {
+			struct string_list list = STRING_LIST_INIT_DUP;
+
+			ptr->multiread(&list);
+			for (size_t i = 0; i < list.nr; i++)
+				printf("%s%c%s%c", ptr->name, delim,
+				       list.items[i].string, term);
+			string_list_clear(&list, 0);
 		}
+	}
 }
 
 static const struct git_var *get_git_var(const char *var)
 {
 	struct git_var *ptr;
-	for (ptr = git_vars; ptr->read; ptr++) {
+	for (ptr = git_vars; ptr->read || ptr->multiread; ptr++) {
 		if (strcmp(var, ptr->name) == 0) {
 			return ptr;
 		}
@@ -207,42 +299,92 @@ static const struct git_var *get_git_var(const char *var)
 static int show_config(const char *var, const char *value,
 		       const struct config_context *ctx, void *cb)
 {
+	int *nul_term = cb;
+	char delim = *nul_term ? '\n' : '=';
+	char term = *nul_term ? '\0' : '\n';
+
 	if (value)
-		printf("%s=%s\n", var, value);
+		printf("%s%c%s%c", var, delim, value, term);
 	else
-		printf("%s\n", var);
+		printf("%s%c", var, term);
 	return git_default_config(var, value, ctx, cb);
 }
 
 int cmd_var(int argc,
 	    const char **argv,
-	    const char *prefix UNUSED,
+	    const char *prefix,
 	    struct repository *repo UNUSED)
 {
-	const struct git_var *git_var;
-	char *val;
+	int list = 0;
+	int nul_term = 0;
+	char delim;
+	char term;
+	struct option options[] = {
+		OPT_BOOL('l', NULL, &list,
+			 N_("list all variables")),
+		OPT_BOOL('z', NULL, &nul_term,
+			 N_("terminate entries with NUL")),
+		OPT_END(),
+	};
 
-	show_usage_if_asked(argc, argv, var_usage);
-	if (argc != 2)
-		usage(var_usage);
+	argc = parse_options(argc, argv, prefix, options,
+			     var_usage, PARSE_OPT_STOP_AT_NON_OPTION);
 
-	if (strcmp(argv[1], "-l") == 0) {
-		repo_config(the_repository, show_config, NULL);
-		list_vars();
+	if (list) {
+		if (argc)
+			usage_with_options(var_usage, options);
+		repo_config(the_repository, show_config, &nul_term);
+		list_vars(nul_term);
 		return 0;
 	}
+
+	if (!argc)
+		usage_with_options(var_usage, options);
+
 	repo_config(the_repository, git_default_config, NULL);
 
-	git_var = get_git_var(argv[1]);
-	if (!git_var)
-		usage(var_usage);
+	delim = nul_term ? '\n' : '=';
+	term = nul_term ? '\0' : '\n';
 
-	val = git_var->read(IDENT_STRICT);
-	if (!val)
-		return 1;
+	for (int i = 0; i < argc; i++) {
+		const struct git_var *git_var = get_git_var(argv[i]);
 
-	printf("%s\n", val);
-	free(val);
+		if (!git_var)
+			usage_with_options(var_usage, options);
+
+		if (git_var->read) {
+			char *val = git_var->read(IDENT_STRICT);
+
+			if (!val) {
+				if (argc == 1)
+					return 1;
+				continue;
+			}
+			if (argc == 1)
+				printf("%s%c", val, term);
+			else
+				printf("%s%c%s%c", git_var->name, delim,
+				       val, term);
+			free(val);
+		} else {
+			struct string_list list = STRING_LIST_INIT_DUP;
+
+			git_var->multiread(&list);
+			if (argc == 1 && !list.nr) {
+				string_list_clear(&list, 0);
+				return 1;
+			}
+			for (size_t j = 0; j < list.nr; j++) {
+				if (argc == 1)
+					printf("%s%c", list.items[j].string,
+					       term);
+				else
+					printf("%s%c%s%c", git_var->name, delim,
+					       list.items[j].string, term);
+			}
+			string_list_clear(&list, 0);
+		}
+	}
 
 	return 0;
 }
diff --git a/bundle.c b/bundle.c
index f55a521..a81fd29 100644
--- a/bundle.c
+++ b/bundle.c
@@ -239,7 +239,7 @@ int verify_bundle(struct repository *r,
 		.quiet = 1,
 	};
 
-	if (!r || !r->objects || !r->objects->sources)
+	if (!r || !r->objects || !r->objects->source)
 		return error(_("need a repository to verify a bundle"));
 
 	for (i = 0; i < p->nr; i++) {
diff --git a/ci/install-dependencies.ps1 b/ci/install-dependencies.ps1
index e3b367f..8397f63 100755
--- a/ci/install-dependencies.ps1
+++ b/ci/install-dependencies.ps1
@@ -1,5 +1,6 @@
 param(
-    [string]$DownloadDirectory = '.dependencies'
+    [string]$DownloadDirectory = '.dependencies',
+    [switch]$Mingw
 )
 
 $ErrorActionPreference = 'Stop'
@@ -11,7 +12,9 @@
 
 New-Item -Path $DownloadDirectory -ItemType Directory -Force | Out-Null
 New-Item -Path .git/info -ItemType Directory -Force | Out-Null
-New-Item -Path .git/info/exclude -ItemType File -Force | Out-Null
+if (-not (Test-Path .git/info/exclude)) {
+    New-Item -Path .git/info/exclude -ItemType File | Out-Null
+}
 Add-Content -Path .git/info/exclude -Value "/$DownloadDirectory"
 
 function Get-Installer {
@@ -41,6 +44,17 @@
     }
 }
 
+$rustTarget = if ($Mingw) { 'gnu' } else { 'msvc' }
+$rustMsi = Get-Installer "rust-$rustTarget.msi" (
+    "https://static.rust-lang.org/dist/" +
+    "rust-$RustVersion-x86_64-pc-windows-$rustTarget.msi")
+Invoke-Installer msiexec.exe @('/i', $rustMsi, 'INSTALLDIR=C:\Rust',
+    "ADDLOCAL=Rustc,Cargo,Std$(if ($Mingw) { ',Gcc' })", '/quiet', '/norestart')
+
+if ($Mingw) {
+    return
+}
+
 $gitAssetVersion = $GitVersion -replace '\.windows\.\d+$', ''
 $gitInstaller = Get-Installer "Git-Installer.exe" `
     "https://github.com/git-for-windows/git/releases/download/v$GitVersion/PortableGit-$gitAssetVersion-64-bit.7z.exe"
@@ -50,6 +64,10 @@
     "https://github.com/mesonbuild/meson/releases/download/$MesonVersion/meson-$MesonVersion-64.msi"
 Invoke-Installer msiexec.exe @('/i', $mesonMsi, 'INSTALLDIR=C:\Meson', '/quiet', '/norestart')
 
-$rustMsi = Get-Installer "rust.msi" `
-    "https://static.rust-lang.org/dist/rust-$RustVersion-x86_64-pc-windows-msvc.msi"
-Invoke-Installer msiexec.exe @('/i', $rustMsi, 'INSTALLDIR=C:\Rust', 'ADDLOCAL=Rustc,Cargo,Std', '/quiet', '/norestart')
+# Disable Git Credential Manager, which is auto-configured by PortableGit.
+# GitLab's runner picks up this Git in its cleanup stage and runs `git
+# credential reject`, which hangs in GCM and makes the job time out.
+& "C:\Program Files\Git\bin\git.exe" config unset --all --system credential.helper
+if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 5) {
+    throw "Failed to unset credential.helper with exit code $LASTEXITCODE"
+}
diff --git a/ci/lib.sh b/ci/lib.sh
index c6ccbf8..f0b7f35 100755
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -227,7 +227,6 @@
 	cache_dir="$HOME/none"
 
 	GIT_TEST_OPTS="--github-workflow-markup"
-	JOBS=10
 
 	distro=$(echo "$CI_JOB_IMAGE" | tr : -)
 elif test true = "$GITLAB_CI"
@@ -250,7 +249,6 @@
 	case "$OS,$CI_JOB_IMAGE" in
 	Windows_NT,*)
 		CI_OS_NAME=windows
-		JOBS=$NUMBER_OF_PROCESSORS
 		;;
 	*,macos-*)
 		# GitLab CI has Python installed via multiple package managers,
@@ -260,11 +258,9 @@
 		export PATH="$(brew --prefix)/bin:$PATH"
 
 		CI_OS_NAME=osx
-		JOBS=$(nproc)
 		;;
 	*,almalinux:*|*,alpine:*|*,debian:*|*,fedora:*|*,ubuntu:*|*,i386/ubuntu:*)
 		CI_OS_NAME=linux
-		JOBS=$(nproc)
 		;;
 	*)
 		echo "Could not identify OS image" >&2
@@ -291,6 +287,19 @@
 	exit 1
 fi
 
+case "$CI_OS_NAME" in
+windows|windows_nt)
+	JOBS=$NUMBER_OF_PROCESSORS
+	;;
+osx)
+	JOBS=$(sysctl -n hw.logicalcpu)
+	;;
+*)
+	JOBS=$(nproc)
+	;;
+esac
+JOBS=$((2 * JOBS))
+
 MAKEFLAGS="$MAKEFLAGS --jobs=$JOBS"
 GIT_PROVE_OPTS="--timer --jobs $JOBS"
 
diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
index 1d9a0a7..6a3b43b 100755
--- a/ci/run-build-and-tests.sh
+++ b/ci/run-build-and-tests.sh
@@ -45,7 +45,7 @@
 	export GIT_TEST_DEFAULT_HASH=sha256
 	;;
 linux-reftable|linux-reftable-leaks|osx-reftable)
-	export GIT_TEST_DEFAULT_REF_FORMAT=reftable
+	export GIT_TEST_DEFAULT_REF_STORAGE_FORMAT=reftable
 	;;
 
 esac
diff --git a/ci/run-test-slice.sh b/ci/run-test-slice.sh
index ff948e3..d0063ef 100755
--- a/ci/run-test-slice.sh
+++ b/ci/run-test-slice.sh
@@ -10,8 +10,8 @@
 group "Run tests" make --quiet -C t T="$(echo "$TESTS" | tr '\n' ' ')" ||
 handle_failed_tests
 
-# We only have one unit test at the moment, so run it in the first slice
-if [ "$1" == "0" ] ; then
+# We only have one unit test at the moment, so run it in the first slice.
+if [ "$1" == "1" ] ; then
 	group "Run unit tests" make --quiet -C t unit-tests-test-tool
 fi
 
diff --git a/commit-graph.c b/commit-graph.c
index 983c11c..6c141a6 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -1,6 +1,7 @@
 #define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "git-compat-util.h"
+#include "abspath.h"
 #include "config.h"
 #include "csum-file.h"
 #include "environment.h"
@@ -14,6 +15,7 @@
 #include "hash-lookup.h"
 #include "commit-graph.h"
 #include "odb.h"
+#include "odb/source-files.h"
 #include "oid-array.h"
 #include "path.h"
 #include "alloc.h"
@@ -28,7 +30,7 @@
 #include "tree.h"
 #include "chunk-format.h"
 
-void git_test_write_commit_graph_or_die(struct odb_source *source)
+void git_test_write_commit_graph_or_die(struct repository *repo)
 {
 	int flags = 0;
 	if (!git_env_bool(GIT_TEST_COMMIT_GRAPH, 0))
@@ -37,7 +39,7 @@ void git_test_write_commit_graph_or_die(struct odb_source *source)
 	if (git_env_bool(GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS, 0))
 		flags = COMMIT_GRAPH_WRITE_BLOOM_FILTERS;
 
-	if (write_commit_graph_reachable(source, flags, NULL))
+	if (write_commit_graph_reachable(repo, repo->objects->source->path, flags, NULL))
 		die("failed to write commit-graph under GIT_TEST_COMMIT_GRAPH");
 }
 
@@ -196,21 +198,21 @@ static int commit_gen_cmp(const void *va, const void *vb)
 	return 0;
 }
 
-char *get_commit_graph_filename(struct odb_source *source)
+char *get_commit_graph_filename(const char *dir)
 {
-	return xstrfmt("%s/info/commit-graph", source->path);
+	return xstrfmt("%s/info/commit-graph", dir);
 }
 
-static char *get_split_graph_filename(struct odb_source *source,
+static char *get_split_graph_filename(const char *dir,
 				      const char *oid_hex)
 {
-	return xstrfmt("%s/info/commit-graphs/graph-%s.graph", source->path,
+	return xstrfmt("%s/info/commit-graphs/graph-%s.graph", dir,
 		       oid_hex);
 }
 
-char *get_commit_graph_chain_filename(struct odb_source *source)
+char *get_commit_graph_chain_filename(const char *dir)
 {
-	return xstrfmt("%s/info/commit-graphs/commit-graph-chain", source->path);
+	return xstrfmt("%s/info/commit-graphs/commit-graph-chain", dir);
 }
 
 static struct commit_graph *alloc_commit_graph(void)
@@ -253,7 +255,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 odb_source *source,
+struct commit_graph *load_commit_graph_one_fd_st(struct repository *repo,
+						 const char *dir,
 						 int fd, struct stat *st)
 {
 	void *graph_map;
@@ -262,7 +265,7 @@ struct commit_graph *load_commit_graph_one_fd_st(struct odb_source *source,
 
 	graph_size = xsize_t(st->st_size);
 
-	if (graph_size < graph_min_size(source->odb->repo->hash_algo)) {
+	if (graph_size < graph_min_size(repo->hash_algo)) {
 		close(fd);
 		error(_("commit-graph file is too small"));
 		return NULL;
@@ -270,9 +273,9 @@ struct commit_graph *load_commit_graph_one_fd_st(struct odb_source *source,
 	graph_map = xmmap(NULL, graph_size, PROT_READ, MAP_PRIVATE, fd, 0);
 	close(fd);
 
-	ret = parse_commit_graph(source->odb->repo, graph_map, graph_size);
+	ret = parse_commit_graph(repo, graph_map, graph_size);
 	if (ret)
-		ret->odb_source = source;
+		ret->dir = absolute_pathdup(dir);
 	else
 		munmap(graph_map, graph_size);
 
@@ -410,6 +413,7 @@ struct commit_graph *parse_commit_graph(struct repository *r,
 
 	graph = alloc_commit_graph();
 
+	graph->repo = r;
 	graph->hash_algo = r->hash_algo;
 	graph->num_chunks = *(unsigned char*)(data + 6);
 	graph->data = graph_map;
@@ -490,7 +494,8 @@ struct commit_graph *parse_commit_graph(struct repository *r,
 	return NULL;
 }
 
-static struct commit_graph *load_commit_graph_one(struct odb_source *source,
+static struct commit_graph *load_commit_graph_one(struct repository *repo,
+						  const char *dir,
 						  const char *graph_file)
 {
 	struct stat st;
@@ -501,17 +506,18 @@ static struct commit_graph *load_commit_graph_one(struct odb_source *source,
 	if (!open_ok)
 		return NULL;
 
-	g = load_commit_graph_one_fd_st(source, fd, &st);
+	g = load_commit_graph_one_fd_st(repo, dir, fd, &st);
 	if (g)
-		g->filename = xstrdup(graph_file);
+		g->filename = absolute_pathdup(graph_file);
 
 	return g;
 }
 
-static struct commit_graph *load_commit_graph_v1(struct odb_source *source)
+static struct commit_graph *load_commit_graph_v1(struct repository *repo,
+						 const char *dir)
 {
-	char *graph_name = get_commit_graph_filename(source);
-	struct commit_graph *g = load_commit_graph_one(source, graph_name);
+	char *graph_name = get_commit_graph_filename(dir);
+	struct commit_graph *g = load_commit_graph_one(repo, dir, graph_name);
 	free(graph_name);
 
 	return g;
@@ -652,7 +658,7 @@ struct commit_graph *load_commit_graph_chain_fd_st(struct object_database *odb,
 	CALLOC_ARRAY(oids, count);
 
 	for (i = 0; i < count; i++) {
-		struct odb_source *source;
+		struct odb_source_files *files;
 
 		if (strbuf_getline_lf(&line, fp) == EOF)
 			break;
@@ -665,9 +671,11 @@ struct commit_graph *load_commit_graph_chain_fd_st(struct object_database *odb,
 		}
 
 		valid = 0;
-		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(source, graph_name);
+
+		files = odb_source_files_downcast(odb->source);
+		for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+			char *graph_name = get_split_graph_filename(dir->abspath, line.buf);
+			struct commit_graph *g = load_commit_graph_one(odb->repo, dir->abspath, graph_name);
 
 			free(graph_name);
 
@@ -700,29 +708,31 @@ struct commit_graph *load_commit_graph_chain_fd_st(struct object_database *odb,
 	return graph_chain;
 }
 
-static struct commit_graph *load_commit_graph_chain(struct odb_source *source)
+static struct commit_graph *load_commit_graph_chain(struct repository *repo,
+						    const char *dir)
 {
-	char *chain_file = get_commit_graph_chain_filename(source);
+	char *chain_file = get_commit_graph_chain_filename(dir);
 	struct stat st;
 	int fd;
 	struct commit_graph *g = NULL;
 
-	if (open_commit_graph_chain(chain_file, &fd, &st, source->odb->repo->hash_algo)) {
+	if (open_commit_graph_chain(chain_file, &fd, &st, repo->hash_algo)) {
 		int incomplete;
 		/* ownership of fd is taken over by load function */
-		g = load_commit_graph_chain_fd_st(source->odb, fd, &st, &incomplete);
+		g = load_commit_graph_chain_fd_st(repo->objects, fd, &st, &incomplete);
 	}
 
 	free(chain_file);
 	return g;
 }
 
-struct commit_graph *read_commit_graph_one(struct odb_source *source)
+struct commit_graph *read_commit_graph_one(struct repository *repo,
+					   const char *dir)
 {
-	struct commit_graph *g = load_commit_graph_v1(source);
+	struct commit_graph *g = load_commit_graph_v1(repo, dir);
 
 	if (!g)
-		g = load_commit_graph_chain(source);
+		g = load_commit_graph_chain(repo, dir);
 
 	return g;
 }
@@ -733,9 +743,9 @@ struct commit_graph *read_commit_graph_one(struct odb_source *source)
  * On the first invocation, this function attempts to load the commit
  * graph if the repository is configured to have one.
  */
-static struct commit_graph *prepare_commit_graph(struct repository *r)
+struct commit_graph *prepare_commit_graph(struct repository *r)
 {
-	struct odb_source *source;
+	struct odb_source_files *files;
 
 	/*
 	 * Early return if there is no object database or if the commit graph is
@@ -766,8 +776,9 @@ static struct commit_graph *prepare_commit_graph(struct repository *r)
 	if (!commit_graph_compatible(r))
 		return NULL;
 
-	for (source = r->objects->sources; source; source = source->next) {
-		r->objects->commit_graph = read_commit_graph_one(source);
+	files = odb_source_files_downcast(r->objects->source);
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		r->objects->commit_graph = read_commit_graph_one(r, dir->abspath);
 		if (r->objects->commit_graph)
 			break;
 	}
@@ -866,7 +877,7 @@ static struct commit_list **insert_parent_or_die(struct commit_graph *g,
 		die("invalid parent position %"PRIu32, pos);
 
 	load_oid_from_graph(g, pos, &oid);
-	c = lookup_commit(g->odb_source->odb->repo, &oid);
+	c = lookup_commit(g->repo, &oid);
 	if (!c)
 		die(_("could not find commit %s"), oid_to_hex(&oid));
 	commit_graph_data_at(c)->graph_pos = pos;
@@ -1103,7 +1114,7 @@ static struct tree *load_tree_for_commit(struct commit_graph *g,
 				graph_pos - g->num_commits_in_base);
 
 	oidread(&oid, commit_data, g->hash_algo);
-	set_commit_tree(c, lookup_tree(g->odb_source->odb->repo, &oid));
+	set_commit_tree(c, lookup_tree(g->repo, &oid));
 
 	return c->maybe_tree;
 }
@@ -1126,7 +1137,7 @@ struct tree *get_commit_tree_in_graph(struct repository *r, const struct commit
 
 struct write_commit_graph_context {
 	struct repository *r;
-	struct odb_source *odb_source;
+	char *dir;
 	char *graph_name;
 	struct oid_array oids;
 	struct commit_stack commits;
@@ -1902,7 +1913,8 @@ static int add_ref_to_set(const struct reference *ref, void *cb_data)
 	return 0;
 }
 
-int write_commit_graph_reachable(struct odb_source *source,
+int write_commit_graph_reachable(struct repository *repo,
+				 const char *dir,
 				 enum commit_graph_write_flags flags,
 				 const struct commit_graph_opts *opts)
 {
@@ -1911,20 +1923,20 @@ int write_commit_graph_reachable(struct odb_source *source,
 	int result;
 
 	memset(&data, 0, sizeof(data));
-	data.repo = source->odb->repo;
+	data.repo = repo;
 	data.commits = &commits;
 
 	if (flags & COMMIT_GRAPH_WRITE_PROGRESS)
 		data.progress = start_delayed_progress(
-			source->odb->repo,
+			repo,
 			_("Collecting referenced commits"), 0);
 
-	refs_for_each_ref(get_main_ref_store(source->odb->repo), add_ref_to_set,
+	refs_for_each_ref(get_main_ref_store(repo), add_ref_to_set,
 			  &data);
 
 	stop_progress(&data.progress);
 
-	result = write_commit_graph(source, NULL, &commits,
+	result = write_commit_graph(repo, dir, NULL, &commits,
 				    flags, opts);
 
 	oidset_clear(&commits);
@@ -1936,12 +1948,8 @@ static int fill_oids_from_packs(struct write_commit_graph_context *ctx,
 {
 	uint32_t i;
 	struct strbuf progress_title = STRBUF_INIT;
-	struct strbuf packname = STRBUF_INIT;
-	int dirlen;
 	int ret = 0;
 
-	strbuf_addf(&packname, "%s/pack/", ctx->odb_source->path);
-	dirlen = packname.len;
 	if (ctx->report_progress) {
 		strbuf_addf(&progress_title,
 			    Q_("Finding commits for commit graph in %"PRIuMAX" pack",
@@ -1954,15 +1962,15 @@ static int fill_oids_from_packs(struct write_commit_graph_context *ctx,
 	}
 	for (i = 0; i < pack_indexes->nr; i++) {
 		struct packed_git *p;
-		strbuf_setlen(&packname, dirlen);
-		strbuf_addstr(&packname, pack_indexes->items[i].string);
-		p = add_packed_git(ctx->r, packname.buf, packname.len, 1);
+
+		p = add_packed_git(ctx->r, pack_indexes->items[i].string,
+				   strlen(pack_indexes->items[i].string), 1);
 		if (!p) {
-			ret = error(_("error adding pack %s"), packname.buf);
+			ret = error(_("error adding pack %s"), pack_indexes->items[i].string);
 			goto cleanup;
 		}
 		if (open_pack_index(p)) {
-			ret = error(_("error opening index for %s"), packname.buf);
+			ret = error(_("error opening index for %s"), pack_indexes->items[i].string);
 			close_pack(p);
 			free(p);
 			goto cleanup;
@@ -1976,7 +1984,6 @@ static int fill_oids_from_packs(struct write_commit_graph_context *ctx,
 cleanup:
 	stop_progress(&ctx->progress);
 	strbuf_release(&progress_title);
-	strbuf_release(&packname);
 
 	return ret;
 }
@@ -2000,7 +2007,7 @@ static int fill_oids_from_commits(struct write_commit_graph_context *ctx,
 
 static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx)
 {
-	struct odb_source *source;
+	struct odb_source_files *files;
 	enum object_type type;
 	struct odb_for_each_object_options opts = {
 		.flags = ODB_FOR_EACH_OBJECT_PACK_ORDER,
@@ -2015,9 +2022,9 @@ static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx)
 			_("Finding commits for commit graph among packed objects"),
 			ctx->approx_nr_objects);
 
-	for (source = ctx->r->objects->sources; source; source = source->next) {
-		struct odb_source_files *files = odb_source_files_downcast(source);
-		odb_source_for_each_object(&files->packed->base, &oi, add_packed_commits_oi,
+	files = odb_source_files_downcast(ctx->r->objects->source);
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		odb_source_for_each_object(&dir->packed->base, &oi, add_packed_commits_oi,
 					   ctx, &opts);
 	}
 
@@ -2108,10 +2115,10 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
 
 		strbuf_addf(&tmp_file,
 			    "%s/info/commit-graphs/tmp_graph_XXXXXX",
-			    ctx->odb_source->path);
+			    ctx->dir);
 		ctx->graph_name = strbuf_detach(&tmp_file, NULL);
 	} else {
-		ctx->graph_name = get_commit_graph_filename(ctx->odb_source);
+		ctx->graph_name = get_commit_graph_filename(ctx->dir);
 	}
 
 	if (safe_create_leading_directories(ctx->r, ctx->graph_name)) {
@@ -2121,7 +2128,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
 	}
 
 	if (ctx->split) {
-		char *lock_name = get_commit_graph_chain_filename(ctx->odb_source);
+		char *lock_name = get_commit_graph_chain_filename(ctx->dir);
 
 		repo_hold_lock_file_for_update_mode(ctx->r, &lk, lock_name,
 						    LOCK_DIE_ON_ERROR, 0444);
@@ -2210,7 +2217,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
 
 	if (ctx->split && ctx->base_graph_name && ctx->num_commit_graphs_after > 1) {
 		char *new_base_hash = xstrdup(oid_to_hex(&ctx->new_base_graph->oid));
-		char *new_base_name = get_split_graph_filename(ctx->new_base_graph->odb_source, new_base_hash);
+		char *new_base_name = get_split_graph_filename(ctx->new_base_graph->dir, new_base_hash);
 
 		free(ctx->commit_graph_filenames_after[ctx->num_commit_graphs_after - 2]);
 		free(ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 2]);
@@ -2250,7 +2257,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
 				}
 			}
 		} else {
-			char *graph_name = get_commit_graph_filename(ctx->odb_source);
+			char *graph_name = get_commit_graph_filename(ctx->dir);
 			unlink(graph_name);
 			free(graph_name);
 		}
@@ -2258,7 +2265,7 @@ 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_algop(file_hash, ctx->r->hash_algo));
-		final_graph_name = get_split_graph_filename(ctx->odb_source,
+		final_graph_name = get_split_graph_filename(ctx->dir,
 					ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1]);
 		free(ctx->commit_graph_filenames_after[ctx->num_commit_graphs_after - 1]);
 		ctx->commit_graph_filenames_after[ctx->num_commit_graphs_after - 1] = final_graph_name;
@@ -2310,7 +2317,7 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx,
 	    flags != COMMIT_GRAPH_SPLIT_REPLACE) {
 		while (g && (g->num_commits <= st_mult(size_mult, num_commits) ||
 			    (max_commits && num_commits > max_commits))) {
-			if (g->odb_source != ctx->odb_source)
+			if (strcmp(g->dir, ctx->dir))
 				break;
 
 			if (unsigned_add_overflows(num_commits, g->num_commits))
@@ -2332,10 +2339,10 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx,
 		    "should be 1 with --split=replace");
 
 	if (ctx->num_commit_graphs_after == 2) {
-		char *old_graph_name = get_commit_graph_filename(g->odb_source);
+		char *old_graph_name = get_commit_graph_filename(g->dir);
 
 		if (!strcmp(g->filename, old_graph_name) &&
-		    g->odb_source != ctx->odb_source) {
+		    strcmp(g->dir, ctx->dir)) {
 			ctx->num_commit_graphs_after = 1;
 			ctx->new_base_graph = NULL;
 		}
@@ -2505,13 +2512,13 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx)
 	if (ctx->opts && ctx->opts->expire_time)
 		expire_time = ctx->opts->expire_time;
 	if (!ctx->split) {
-		char *chain_file_name = get_commit_graph_chain_filename(ctx->odb_source);
+		char *chain_file_name = get_commit_graph_chain_filename(ctx->dir);
 		unlink(chain_file_name);
 		free(chain_file_name);
 		ctx->num_commit_graphs_after = 0;
 	}
 
-	strbuf_addstr(&path, ctx->odb_source->path);
+	strbuf_addstr(&path, ctx->dir);
 	strbuf_addstr(&path, "/info/commit-graphs");
 	dir = opendir(path.buf);
 
@@ -2553,16 +2560,15 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx)
 	strbuf_release(&path);
 }
 
-int write_commit_graph(struct odb_source *source,
+int write_commit_graph(struct repository *r,
+		       const char *dir,
 		       const struct string_list *const pack_indexes,
 		       struct oidset *commits,
 		       enum commit_graph_write_flags flags,
 		       const struct commit_graph_opts *opts)
 {
-	struct repository *r = source->odb->repo;
 	struct write_commit_graph_context ctx = {
 		.r = r,
-		.odb_source = source,
 		.append = flags & COMMIT_GRAPH_WRITE_APPEND ? 1 : 0,
 		.report_progress = flags & COMMIT_GRAPH_WRITE_PROGRESS ? 1 : 0,
 		.split = flags & COMMIT_GRAPH_WRITE_SPLIT ? 1 : 0,
@@ -2593,6 +2599,8 @@ int write_commit_graph(struct odb_source *source,
 		return 0;
 	}
 
+	ctx.dir = absolute_pathdup(dir);
+
 	bloom_settings.hash_version = r->settings.commit_graph_changed_paths_version;
 	bloom_settings.bits_per_entry = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY",
 						      bloom_settings.bits_per_entry);
@@ -2715,6 +2723,7 @@ int write_commit_graph(struct odb_source *source,
 cleanup:
 	free(ctx.graph_name);
 	free(ctx.base_graph_name);
+	free(ctx.dir);
 	commit_stack_clear(&ctx.commits);
 	oid_array_clear(&ctx.oids);
 	clear_topo_level_slab(&topo_levels);
@@ -2767,7 +2776,7 @@ static int verify_one_commit_graph(struct commit_graph *g,
 				   struct progress *progress,
 				   uint64_t *seen)
 {
-	struct repository *r = g->odb_source->odb->repo;
+	struct repository *r = g->repo;
 	uint32_t i, cur_fanout_pos = 0;
 	struct object_id prev_oid, cur_oid;
 	struct commit *seen_gen_zero = NULL;
@@ -2931,7 +2940,7 @@ int verify_commit_graph(struct commit_graph *g, int flags)
 		if (!(flags & COMMIT_GRAPH_VERIFY_SHALLOW))
 			total += g->num_commits_in_base;
 
-		progress = start_progress(g->odb_source->odb->repo,
+		progress = start_progress(g->repo,
 					  _("Verifying commits in commit graph"),
 					  total);
 	}
@@ -2954,6 +2963,7 @@ void free_commit_graph(struct commit_graph *g)
 
 		if (g->data)
 			munmap((void *)g->data, g->data_len);
+		free(g->dir);
 		free(g->filename);
 		free(g->bloom_filter_settings);
 		free(g);
diff --git a/commit-graph.h b/commit-graph.h
index 13ca4ff..3b55192 100644
--- a/commit-graph.h
+++ b/commit-graph.h
@@ -21,7 +21,7 @@
  * call this method outside of a builtin, and only if you know what
  * you are doing!
  */
-void git_test_write_commit_graph_or_die(struct odb_source *source);
+void git_test_write_commit_graph_or_die(struct repository *repo);
 
 struct commit;
 struct bloom_filter_settings;
@@ -29,8 +29,9 @@ struct repository;
 struct object_database;
 struct string_list;
 
-char *get_commit_graph_filename(struct odb_source *source);
-char *get_commit_graph_chain_filename(struct odb_source *source);
+char *get_commit_graph_filename(const char *dir);
+char *get_commit_graph_chain_filename(const char *dir);
+struct commit_graph *prepare_commit_graph(struct repository *r);
 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,
 			    const struct git_hash_algo *hash_algo);
@@ -85,12 +86,13 @@ struct commit_graph {
 	const unsigned char *data;
 	size_t data_len;
 
+	struct repository *repo;
 	const struct git_hash_algo *hash_algo;
 	unsigned char num_chunks;
 	uint32_t num_commits;
 	struct object_id oid;
+	char *dir;
 	char *filename;
-	struct odb_source *odb_source;
 
 	uint32_t num_commits_in_base;
 	unsigned int read_generation_data;
@@ -114,12 +116,20 @@ struct commit_graph {
 	struct bloom_filter_settings *bloom_filter_settings;
 };
 
-struct commit_graph *load_commit_graph_one_fd_st(struct odb_source *source,
+/*
+ * Load commit graphs from the given object directory `dir`. The directory may
+ * be given as a relative path; it is canonicalized internally so that graphs
+ * loaded from the same directory compare equal regardless of how the caller
+ * spelled the path.
+ */
+struct commit_graph *load_commit_graph_one_fd_st(struct repository *repo,
+						 const char *dir,
 						 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 odb_source *source);
+struct commit_graph *read_commit_graph_one(struct repository *repo,
+					   const char *dir);
 
 struct repo_settings;
 
@@ -171,11 +181,17 @@ struct commit_graph_opts {
  * and a negative value on failure. Note that if the repository
  * is not compatible with the commit-graph feature, then the
  * methods will return 0 without writing a commit-graph.
+ *
+ * The object directory `dir` may be given as a relative path; it is
+ * canonicalized internally so that it compares equal to the directory of
+ * graphs that have already been loaded.
  */
-int write_commit_graph_reachable(struct odb_source *source,
+int write_commit_graph_reachable(struct repository *repo,
+				 const char *dir,
 				 enum commit_graph_write_flags flags,
 				 const struct commit_graph_opts *opts);
-int write_commit_graph(struct odb_source *source,
+int write_commit_graph(struct repository *r,
+		       const char *dir,
 		       const struct string_list *pack_indexes,
 		       struct oidset *commits,
 		       enum commit_graph_write_flags flags,
diff --git a/commit-reach.c b/commit-reach.c
index b16eea2..453aff0 100644
--- a/commit-reach.c
+++ b/commit-reach.c
@@ -658,7 +658,7 @@ int repo_is_descendant_of(struct repository *r,
 		struct commit_list *from_list = NULL;
 		int result;
 		commit_list_insert(commit, &from_list);
-		result = can_all_from_reach(from_list, with_commit, 0);
+		result = can_all_from_reach(r, from_list, with_commit, 0);
 		commit_list_free(from_list);
 		return result;
 	} else {
@@ -952,7 +952,8 @@ int commit_contains(struct ref_filter *filter, struct commit *commit,
 	return result;
 }
 
-int can_all_from_reach_with_flag(struct object_array *from,
+int can_all_from_reach_with_flag(struct repository *r,
+				 struct object_array *from,
 				 unsigned int with_flag,
 				 unsigned int assign_flag,
 				 timestamp_t min_commit_date,
@@ -971,8 +972,7 @@ int can_all_from_reach_with_flag(struct object_array *from,
 		if (!from_one || from_one->flags & assign_flag)
 			continue;
 
-		from_one = deref_tag(the_repository, from_one,
-				     "a from object", 0);
+		from_one = deref_tag(r, from_one, "a from object", 0);
 		if (!from_one || from_one->type != OBJ_COMMIT) {
 			/*
 			 * no way to tell if this is reachable by
@@ -985,7 +985,7 @@ int can_all_from_reach_with_flag(struct object_array *from,
 		}
 
 		list[nr_commits] = (struct commit *)from_one;
-		if (repo_parse_commit(the_repository, list[nr_commits]) ||
+		if (repo_parse_commit(r, list[nr_commits]) ||
 		    commit_graph_generation(list[nr_commits]) < min_generation) {
 			result = 0;
 			goto cleanup;
@@ -1020,7 +1020,7 @@ int can_all_from_reach_with_flag(struct object_array *from,
 				if (!(parent->item->object.flags & assign_flag)) {
 					parent->item->object.flags |= assign_flag;
 
-					if (repo_parse_commit(the_repository, parent->item) ||
+					if (repo_parse_commit(r, parent->item) ||
 					    parent->item->date < min_commit_date ||
 					    commit_graph_generation(parent->item) < min_generation)
 						continue;
@@ -1054,8 +1054,8 @@ int can_all_from_reach_with_flag(struct object_array *from,
 	return result;
 }
 
-int can_all_from_reach(struct commit_list *from, struct commit_list *to,
-		       int cutoff_by_min_date)
+int can_all_from_reach(struct repository *r, struct commit_list *from,
+		       struct commit_list *to, int cutoff_by_min_date)
 {
 	struct object_array from_objs = OBJECT_ARRAY_INIT;
 	struct commit_list *from_iter = from, *to_iter = to;
@@ -1066,7 +1066,7 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to,
 	while (from_iter) {
 		add_object_array(&from_iter->item->object, NULL, &from_objs);
 
-		if (!repo_parse_commit(the_repository, from_iter->item)) {
+		if (!repo_parse_commit(r, from_iter->item)) {
 			timestamp_t generation;
 			if (from_iter->item->date < min_commit_date)
 				min_commit_date = from_iter->item->date;
@@ -1080,7 +1080,7 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to,
 	}
 
 	while (to_iter) {
-		if (!repo_parse_commit(the_repository, to_iter->item)) {
+		if (!repo_parse_commit(r, to_iter->item)) {
 			timestamp_t generation;
 			if (to_iter->item->date < min_commit_date)
 				min_commit_date = to_iter->item->date;
@@ -1095,7 +1095,7 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to,
 		to_iter = to_iter->next;
 	}
 
-	result = can_all_from_reach_with_flag(&from_objs, PARENT2, PARENT1,
+	result = can_all_from_reach_with_flag(r, &from_objs, PARENT2, PARENT1,
 					      min_commit_date, min_generation);
 
 	while (from) {
diff --git a/commit-reach.h b/commit-reach.h
index f908d30..45dfa11 100644
--- a/commit-reach.h
+++ b/commit-reach.h
@@ -89,13 +89,14 @@ int commit_contains(struct ref_filter *filter, struct commit *commit,
  * commits with date below 'min_commit_date' or generation below
  * 'min_generation'.
  */
-int can_all_from_reach_with_flag(struct object_array *from,
+int can_all_from_reach_with_flag(struct repository *r,
+				 struct object_array *from,
 				 unsigned int with_flag,
 				 unsigned int assign_flag,
 				 timestamp_t min_commit_date,
 				 timestamp_t min_generation);
-int can_all_from_reach(struct commit_list *from, struct commit_list *to,
-		       int commit_date_cutoff);
+int can_all_from_reach(struct repository *r, struct commit_list *from,
+		       struct commit_list *to, int commit_date_cutoff);
 
 
 /*
diff --git a/compat/posix.h b/compat/posix.h
index 71cc731..108d38e 100644
--- a/compat/posix.h
+++ b/compat/posix.h
@@ -501,7 +501,6 @@ int git_qsort_s(void *base, size_t nmemb, size_t size,
 } while (0)
 
 #ifdef NO_NSEC
-#undef USE_NSEC
 #define ST_CTIME_NSEC(st) 0
 #define ST_MTIME_NSEC(st) 0
 #else
diff --git a/compat/winansi.c b/compat/winansi.c
index 3ce1900..088734a 100644
--- a/compat/winansi.c
+++ b/compat/winansi.c
@@ -436,15 +436,6 @@ static void winansi_exit(void)
 	CloseHandle(hthread);
 }
 
-static void die_lasterr(const char *fmt, ...)
-{
-	va_list params;
-	va_start(params, fmt);
-	errno = err_win_to_posix(GetLastError());
-	die_errno(fmt, params);
-	va_end(params);
-}
-
 #undef dup2
 int winansi_dup2(int oldfd, int newfd)
 {
@@ -462,8 +453,8 @@ static HANDLE duplicate_handle(HANDLE hnd)
 	HANDLE hresult, hproc = GetCurrentProcess();
 	if (!DuplicateHandle(hproc, hnd, hproc, &hresult, 0, TRUE,
 			DUPLICATE_SAME_ACCESS))
-		die_lasterr("DuplicateHandle(%li) failed",
-			(long) (intptr_t) hnd);
+		die("DuplicateHandle(%li) failed: %lu",
+		    (long) (intptr_t) hnd, GetLastError());
 	return hresult;
 }
 
@@ -609,16 +600,16 @@ void winansi_init(void)
 	hwrite = CreateNamedPipeW(name, PIPE_ACCESS_OUTBOUND,
 		PIPE_TYPE_BYTE | PIPE_WAIT, 1, BUFFER_SIZE, 0, 0, NULL);
 	if (hwrite == INVALID_HANDLE_VALUE)
-		die_lasterr("CreateNamedPipe failed");
+		die("CreateNamedPipe failed: %lu", GetLastError());
 
 	hread = CreateFileW(name, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
 	if (hread == INVALID_HANDLE_VALUE)
-		die_lasterr("CreateFile for named pipe failed");
+		die("CreateFile for named pipe failed: %lu", GetLastError());
 
 	/* start console spool thread on the pipe's read end */
 	hthread = CreateThread(NULL, 0, console_thread, NULL, 0, NULL);
 	if (!hthread)
-		die_lasterr("CreateThread(console_thread) failed");
+		die("CreateThread(console_thread) failed: %lu", GetLastError());
 
 	/* schedule cleanup routine */
 	if (atexit(winansi_exit))
diff --git a/config.c b/config.c
index d9019e7..e0bb29b 100644
--- a/config.c
+++ b/config.c
@@ -450,18 +450,24 @@ static int git_config_include(const char *var, const char *value,
 	return ret;
 }
 
+void git_config_append_parameter(struct strbuf *env, const char *key,
+				 const char *value)
+{
+	if (env->len)
+		strbuf_addch(env, ' ');
+	sq_quote_buf(env, key);
+	strbuf_addch(env, '=');
+	if (value)
+		sq_quote_buf(env, value);
+}
+
 static void git_config_push_split_parameter(const char *key, const char *value)
 {
 	struct strbuf env = STRBUF_INIT;
 	const char *old = getenv(CONFIG_DATA_ENVIRONMENT);
-	if (old && *old) {
+	if (old && *old)
 		strbuf_addstr(&env, old);
-		strbuf_addch(&env, ' ');
-	}
-	sq_quote_buf(&env, key);
-	strbuf_addch(&env, '=');
-	if (value)
-		sq_quote_buf(&env, value);
+	git_config_append_parameter(&env, key, value);
 	setenv(CONFIG_DATA_ENVIRONMENT, env.buf, 1);
 	strbuf_release(&env);
 }
diff --git a/config.h b/config.h
index b66dd08..b048f63 100644
--- a/config.h
+++ b/config.h
@@ -22,6 +22,7 @@
  */
 
 struct object_id;
+struct strbuf;
 
 /* git_config_parse_key() returns these negated: */
 #define CONFIG_INVALID_KEY 1
@@ -186,6 +187,17 @@ int git_config_from_blob_oid(config_fn_t fn, const char *name,
 			     enum config_scope scope);
 void git_config_push_parameter(const char *text);
 void git_config_push_env(const char *spec);
+
+/*
+ * Append a config option to the buffer that can be exported via the
+ * GIT_CONFIG_PARAMETERS environment variable, which allows us to
+ * propagate configuration across Git processes. The format of the
+ * variable is a space-separated list of quoted "'<key>'='<value>'"
+ * pairs. With a NULL `value`, only 'key'= is appended, which git reads
+ * back as a boolean true, like "-c key" on the command line.
+ */
+void git_config_append_parameter(struct strbuf *env, const char *key,
+				 const char *value);
 int git_config_from_parameters(config_fn_t fn, void *data);
 
 /*
diff --git a/configure.ac b/configure.ac
index cfb5011..fc95677 100644
--- a/configure.ac
+++ b/configure.ac
@@ -351,12 +351,6 @@
 
 ## --enable-FEATURE[=ARG] and --disable-FEATURE
 #
-# Define USE_NSEC below if you want git to care about sub-second file mtimes
-# and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
-# it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
-# randomly break unless your underlying filesystem supports those sub-second
-# times (my ext3 doesn't).
-#
 # Define USE_STDEV below if you want git to care about the underlying device
 # change being considered an inode change from the update-index perspective.
 
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index 7874e5a..9415c47 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -102,7 +102,7 @@
 macro(parse_makefile_for_sources list_var makefile regex)
 	file(STRINGS ${makefile} ${list_var} REGEX "^${regex} \\+=(.*)")
 	string(REPLACE "${regex} +=" "" ${list_var} ${${list_var}})
-	string(REPLACE "$(COMPAT_OBJS)" "" ${list_var} ${${list_var}}) #remove "$(COMPAT_OBJS)" This is only for libgit.
+	string(REGEX REPLACE "\\$\\([^)]*_OBJS\\)" "" ${list_var} ${${list_var}}) # remove any "$(*_OBJS)" variables
 	string(STRIP ${${list_var}} ${list_var}) #remove trailing/leading whitespaces
 	string(REPLACE ".o" ".c;" ${list_var} ${${list_var}}) #change .o to .c, ; is for converting the string into a list
 	list(TRANSFORM ${list_var} STRIP) #remove trailing/leading whitespaces for each element in list
@@ -675,6 +675,10 @@
 #libgit
 parse_makefile_for_sources(libgit_SOURCES ${CMAKE_SOURCE_DIR}/Makefile "LIB_OBJS")
 
+#reftable
+parse_makefile_for_sources(reftable_SOURCES ${CMAKE_SOURCE_DIR}/Makefile "REFTABLE_OBJS")
+list(APPEND libgit_SOURCES ${reftable_SOURCES})
+
 list(TRANSFORM libgit_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/")
 list(TRANSFORM compat_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/")
 
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 9f8b9b5..c3a49e2 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -3816,7 +3816,7 @@
 
 _git_worktree ()
 {
-	local subcommands="add list lock move prune remove unlock"
+	local subcommands="add list lock move prune remove repair unlock"
 	local subcommand subcommand_idx
 
 	subcommand="$(__git_find_on_cmdline --show-idx "$subcommands")"
@@ -3866,7 +3866,7 @@
 			;;
 		esac
 		;;
-	lock,*|remove,*|unlock,*)
+	lock,*|remove,*|repair,*|unlock,*)
 		__git_complete_worktree_paths
 		;;
 	move,*)
diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index 6186c47..754a5ed 100644
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@ -466,7 +466,7 @@
 
 	local repo_info rev_parse_exit_code
 	repo_info="$(git rev-parse --git-dir --is-inside-git-dir \
-		--is-bare-repository --is-inside-work-tree --show-ref-format \
+		--is-bare-repository --is-inside-work-tree --show-ref-storage-format \
 		--short HEAD 2>/dev/null)"
 	rev_parse_exit_code="$?"
 
diff --git a/contrib/completion/meson.build b/contrib/completion/meson.build
index 576125b..4483c5b 100644
--- a/contrib/completion/meson.build
+++ b/contrib/completion/meson.build
@@ -4,31 +4,23 @@
   'git-completion.zsh',
   'git-prompt.sh'
 ]
-  if meson.version().version_compare('>=1.3.0')
-    test_dependencies += fs.copyfile(script)
-  else
-    configure_file(
-      input: script,
-      output: script,
-      copy: true,
-    )
-  endif
+  # Note that we intentionally don't use `fs.copyfile()` here because we'd have
+  # to add it to our test dependencies in that case, but that creates a
+  # chicken-and-egg situation between including "t/" or "contrib/" first.
+  configure_file(
+    input: script,
+    output: script,
+    copy: true,
+  )
 endforeach
 
 # We have to discern between the test dependency and the installed file. Our
 # tests assume the completion scripts to have the same name as the in-tree
 # files, but the installed filenames need to match the executable's basename.
-if meson.version().version_compare('>=1.3.0')
-  fs.copyfile('git-completion.bash', 'git',
-    install: true,
-    install_dir: get_option('datadir') / 'bash-completion/completions',
-  )
-else
-  configure_file(
-    input: 'git-completion.bash',
-    output: 'git',
-    copy: true,
-    install: true,
-    install_dir: get_option('datadir') / 'bash-completion/completions',
-  )
-endif
+configure_file(
+  input: 'git-completion.bash',
+  output: 'git',
+  copy: true,
+  install: true,
+  install_dir: get_option('datadir') / 'bash-completion/completions',
+)
diff --git a/contrib/contacts/git-contacts b/contrib/contacts/git-contacts
index 85ad732..e4656af 100755
--- a/contrib/contacts/git-contacts
+++ b/contrib/contacts/git-contacts
@@ -3,7 +3,7 @@
 # List people who might be interested in a patch.  Useful as the argument to
 # git-send-email --cc-cmd option, and in other situations.
 #
-# Usage: git contacts <file | rev-list option> ...
+# Usage: git contacts <file | '-' | rev-list option> ...
 
 use strict;
 use warnings;
@@ -162,9 +162,11 @@
 	die "No input revisions or patch files\n";
 }
 
-my (@files, @rev_args);
+my ($read_from_stdin, @files, @rev_args);
 for (@ARGV) {
-	if (-e) {
+	if ($_ eq '-') {
+		$read_from_stdin = 1;
+	} elsif (-e) {
 		push @files, $_;
 	} else {
 		push @rev_args, $_;
@@ -172,6 +174,9 @@
 }
 
 my %sources;
+if ($read_from_stdin) {
+	scan_patches(\%sources, undef, \*STDIN);
+}
 for (@files) {
 	scan_patch_file(\%sources, $_);
 }
diff --git a/contrib/contacts/git-contacts.adoc b/contrib/contacts/git-contacts.adoc
index dd914d1..ea5dd58 100644
--- a/contrib/contacts/git-contacts.adoc
+++ b/contrib/contacts/git-contacts.adoc
@@ -9,7 +9,7 @@
 SYNOPSIS
 --------
 [verse]
-'git contacts' (<patch>|<range>|<rev>)...
+'git contacts' (<patch>|'-'|<range>|<rev>)...
 
 
 DESCRIPTION
@@ -23,8 +23,10 @@
 Input consists of one or more patch files or revision arguments.  A revision
 argument can be a range or a single `<rev>` which is interpreted as
 `<rev>..HEAD`, thus the same revision arguments are accepted as for
-linkgit:git-format-patch[1]. Patch files and revision arguments can be combined
-in the same invocation.
+linkgit:git-format-patch[1].  A single dash `'-'` character in place of a
+`<patch>` tells the command to read patch file(s) from the standard input.
+Patch files, standard input, and revision arguments can be combined in the same
+invocation.
 
 This command can be useful for determining the list of people with whom to
 discuss proposed changes, or for finding the list of recipients to Cc: when
@@ -73,6 +75,12 @@
 $ git contacts origin
 ------------
 
+* Input a patch via stdin:
++
+------------
+$ git contacts - <feature.patch
+------------
+
 * Helper for `git send-email`:
 +
 ------------
diff --git a/date.c b/date.c
index 014065b..c50f45d 100644
--- a/date.c
+++ b/date.c
@@ -45,13 +45,23 @@ static const char *weekday_names[] = {
 	"Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"
 };
 
+int tz_to_minutes(int tz)
+{
+	int minutes = tz < 0 ? -tz : tz;
+	minutes = (minutes / 100) * 60 + (minutes % 100);
+	return tz < 0 ? -minutes : minutes;
+}
+
+int minutes_to_tz(int minutes)
+{
+	int tz = minutes < 0 ? -minutes : minutes;
+	tz = (tz / 60) * 100 + (tz % 60);
+	return minutes < 0 ? -tz : tz;
+}
+
 static time_t gm_time_t(timestamp_t time, int tz)
 {
-	int minutes;
-
-	minutes = tz < 0 ? -tz : tz;
-	minutes = (minutes / 100)*60 + (minutes % 100);
-	minutes = tz < 0 ? -minutes : minutes;
+	int minutes = tz_to_minutes(tz);
 
 	if (minutes > 0) {
 		if (unsigned_add_overflows(time, minutes * 60))
@@ -103,8 +113,7 @@ static int local_time_tzoffset(time_t t, struct tm *tm)
 		offset = t_local - t;
 	}
 	offset /= 60; /* in minutes */
-	offset = (offset % 60) + ((offset / 60) * 100);
-	return offset * eastwest;
+	return minutes_to_tz(offset)  * eastwest;
 }
 
 /*
@@ -862,7 +871,7 @@ static int match_object_header_date(const char *date, timestamp_t *timestamp, in
 	ofs = strtol(date, &end, 10);
 	if ((*end != '\0' && (*end != '\n')) || end != date + 4)
 		return -1;
-	ofs = (ofs / 100) * 60 + (ofs % 100);
+	ofs = tz_to_minutes(ofs);
 	if (date[-1] == '-')
 		ofs = -ofs;
 	*timestamp = stamp;
diff --git a/date.h b/date.h
index 0747864..816df5b 100644
--- a/date.h
+++ b/date.h
@@ -70,4 +70,13 @@ void datestamp(struct strbuf *out);
 timestamp_t approxidate_careful(const char *, int *);
 int date_overflows(timestamp_t date);
 time_t tm_to_time_t(const struct tm *tm);
+
+/**
+ * Convert between the "[+-]HHMM" timezone format and minutes. This format is
+ * used for example as part of commit headers and reflogs. For example, the
+ * timezone -0100 is converted to -60 minutes.
+ */
+int tz_to_minutes(int tz);
+int minutes_to_tz(int minutes);
+
 #endif
diff --git a/diagnose.c b/diagnose.c
index 5092bf8..2c1bcfe 100644
--- a/diagnose.c
+++ b/diagnose.c
@@ -59,13 +59,13 @@ static void dir_file_stats_objects(const char *full_path,
 			    (uintmax_t)st.st_size);
 }
 
-static int dir_file_stats(struct odb_source *source, void *data)
+static int dir_file_stats(struct odb_files_dir *source, void *data)
 {
 	struct strbuf *buf = data;
 
-	strbuf_addf(buf, "Contents of %s:\n", source->path);
+	strbuf_addf(buf, "Contents of %s:\n", source->abspath);
 
-	for_each_file_in_pack_dir(source->path, dir_file_stats_objects,
+	for_each_file_in_pack_dir(source->abspath, dir_file_stats_objects,
 				  data);
 
 	return 0;
@@ -228,7 +228,7 @@ int create_diagnostics_archive(struct repository *r,
 
 	strbuf_reset(&buf);
 	strbuf_addstr(&buf, "--add-virtual-file=packs-local.txt:");
-	dir_file_stats(r->objects->sources, &buf);
+	dir_file_stats(odb_source_files_downcast(r->objects->source)->dirs, &buf);
 	odb_for_each_alternate(r->objects, dir_file_stats, &buf);
 	strvec_push(&archiver_args, buf.buf);
 
diff --git a/diff-no-index.c b/diff-no-index.c
index f320424..a9151cc 100644
--- a/diff-no-index.c
+++ b/diff-no-index.c
@@ -188,6 +188,8 @@ static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop,
 			mode1 = 0;
 		}
 		/* emit that file */
+		if (o->flags.reverse_diff)
+			SWAP(d1, d2);
 		diff_queue(&diff_queued_diff, d1, d2);
 
 		/* and then let the entire directory be created or deleted */
diff --git a/diff.c b/diff.c
index 414532d..b4ac17f 100644
--- a/diff.c
+++ b/diff.c
@@ -3646,7 +3646,7 @@ static void emit_binary_diff_body(struct diff_options *o,
 		data = delta;
 		data_size = delta_size;
 	} else {
-		char *s = xstrfmt("%lu", two->size);
+		char *s = xstrfmt("%"PRIuMAX, (uintmax_t)two->size);
 		emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_HEADER_LITERAL,
 				 s, strlen(s), 0);
 		free(s);
diff --git a/dir.c b/dir.c
index d896e7b..ac246ce 100644
--- a/dir.c
+++ b/dir.c
@@ -1935,6 +1935,16 @@ static enum exist_status directory_exists_in_index(struct index_state *istate,
 	return index_nonexistent;
 }
 
+static int dir_match(struct index_state *istate,
+		     const struct pathspec *pathspec,
+		     const char *dirname, int len)
+{
+	return match_pathspec_with_flags(istate, pathspec, dirname, len,
+					 0 /* prefix */,
+					 NULL /* seen */,
+					 DO_MATCH_LEADING_PATHSPEC);
+}
+
 /*
  * When we find a directory when traversing the filesystem, we
  * have three distinct cases:
@@ -2001,11 +2011,7 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
 	 * for matching patterns.
 	 */
 	if (pathspec && !excluded) {
-		matches_how = match_pathspec_with_flags(istate, pathspec,
-							dirname, len,
-							0 /* prefix */,
-							NULL /* seen */,
-							DO_MATCH_LEADING_PATHSPEC);
+		matches_how = dir_match(istate, pathspec, dirname, len);
 		if (!matches_how)
 			return path_none;
 	}
@@ -2039,8 +2045,15 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
 		strbuf_release(&sb);
 
 		if (nested_repo) {
-			if ((dir->flags & DIR_SKIP_NESTED_GIT) ||
-				(matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC))
+			if (dir->flags & DIR_SKIP_NESTED_GIT)
+				return path_none;
+			if (pathspec && !matches_how) {
+				matches_how = dir_match(istate, pathspec,
+							dirname, len);
+				if (!matches_how)
+					return path_none;
+			}
+			if (matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC)
 				return path_none;
 			return excluded ? path_excluded : path_untracked;
 		}
diff --git a/environment.c b/environment.c
index 76ee65e..c83cf44 100644
--- a/environment.c
+++ b/environment.c
@@ -571,6 +571,13 @@ int git_default_core_config(const char *var, const char *value,
 		return 0;
 	}
 
+#ifndef NO_NSEC
+	if (!strcmp(var, "core.usenanosec")) {
+		cfg->use_nanosec = git_config_bool(var, value);
+		return 0;
+	}
+#endif
+
 	/* Add other config variables here and to Documentation/config.adoc. */
 	return platform_core_config(var, value, ctx, cb);
 }
@@ -745,6 +752,7 @@ int git_default_config(const char *var, const char *value,
 
 void repo_config_values_init(struct repo_config_values *cfg)
 {
+	/* section "core" config values */
 	cfg->attributes_file = NULL;
 	cfg->excludes_file = NULL;
 	cfg->editor_program = NULL;
@@ -756,20 +764,25 @@ void repo_config_values_init(struct repo_config_values *cfg)
 	cfg->autorebase = AUTOREBASE_NEVER;
 	cfg->object_creation_mode = OBJECT_CREATION_MODE;
 	cfg->apply_sparse_checkout = 0;
-	cfg->protect_hfs = PROTECT_HFS_DEFAULT;
-	cfg->protect_ntfs = PROTECT_NTFS_DEFAULT;
-	cfg->ignore_case = 0;
-	cfg->trust_executable_bit = 1;
-	cfg->has_symlinks = platform_has_symlinks();
-	cfg->branch_track = BRANCH_TRACK_REMOTE;
 	cfg->trust_ctime = 1;
 	cfg->check_stat = 1;
 	cfg->zlib_compression_level = Z_BEST_SPEED;
 	cfg->pack_compression_level = Z_DEFAULT_COMPRESSION;
 	cfg->precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
 	cfg->core_sparse_checkout_cone = 0;
-	cfg->sparse_expect_files_outside_of_patterns = 0;
 	cfg->warn_on_object_refname_ambiguity = 1;
+	cfg->protect_hfs = PROTECT_HFS_DEFAULT;
+	cfg->protect_ntfs = PROTECT_NTFS_DEFAULT;
+	cfg->ignore_case = 0;
+	cfg->trust_executable_bit = 1;
+	cfg->has_symlinks = platform_has_symlinks();
+	cfg->use_nanosec = 0;
+
+	/* section "sparse" config values */
+	cfg->sparse_expect_files_outside_of_patterns = 0;
+
+	/* section "branch" config values */
+	cfg->branch_track = BRANCH_TRACK_REMOTE;
 }
 
 void repo_config_values_clear(struct repo_config_values *cfg)
diff --git a/environment.h b/environment.h
index e7ec5b0..49c86e8 100644
--- a/environment.h
+++ b/environment.h
@@ -44,6 +44,7 @@
 #define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT "GIT_TEXTDOMAINDIR"
 #define GIT_ATTR_SOURCE_ENVIRONMENT "GIT_ATTR_SOURCE"
 #define GIT_REFERENCE_BACKEND_ENVIRONMENT "GIT_REFERENCE_BACKEND"
+#define GIT_REF_STORAGE_FORMAT_ENVIRONMENT "GIT_REF_STORAGE_FORMAT"
 
 /*
  * Environment variable used to propagate the --no-advice global option to the
@@ -53,6 +54,14 @@
 #define GIT_ADVICE_ENVIRONMENT "GIT_ADVICE"
 
 /*
+ * Environment variable used to detect that a lazy fetch is already in
+ * progress in a parent process, to prevent infinite recursion when a
+ * promisor remote resolves back to the repository being served.
+ * This is an internal variable that should not be set by the user.
+ */
+#define LAZY_FETCH_DEPTH_ENVIRONMENT "GIT_INTERNAL_LAZY_FETCH_DEPTH"
+
+/*
  * Environment variable used in handshaking the wire protocol.
  * Contains a colon ':' separated list of keys with optional values
  * 'key[=value]'.  Presence of unknown keys and values must be
@@ -139,6 +148,7 @@ struct repo_config_values {
 	int ignore_case;
 	int trust_executable_bit;
 	int has_symlinks;
+	int use_nanosec;
 
 	/* section "sparse" config values */
 	int sparse_expect_files_outside_of_patterns;
diff --git a/fetch-pack.c b/fetch-pack.c
index aad07b1..0300cf7 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -1075,7 +1075,7 @@ static int get_pack(struct fetch_pack_args *args,
 		die(_("fetch-pack: unable to fork off %s"), cmd_name);
 	if (do_keep && (pack_lockfiles || fsck_objects)) {
 		int is_well_formed;
-		char *pack_lockfile = index_pack_lockfile(the_repository->objects->sources,
+		char *pack_lockfile = index_pack_lockfile(the_repository->objects->source,
 							  cmd.out,
 							  &is_well_formed);
 
@@ -2311,8 +2311,8 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,
 			die(_("unexpected 'ready' from remote"));
 		else
 			do_check_stateless_delimiter(stateless_rpc, &reader);
-		if (can_all_from_reach_with_flag(&nt_object_array, COMMON,
-						 REACH_SCRATCH, 0,
+		if (can_all_from_reach_with_flag(the_repository, &nt_object_array,
+						 COMMON, REACH_SCRATCH, 0,
 						 min_generation))
 			last_iteration = 1;
 		trace2_region_leave_printf("negotiation", "round",
diff --git a/git-curl-compat.h b/git-curl-compat.h
index 032aaf7..25678b5 100644
--- a/git-curl-compat.h
+++ b/git-curl-compat.h
@@ -29,6 +29,13 @@
  */
 
 /**
+ * curl_url() interface added in 7.62.0 (October 2018)
+ */
+#if LIBCURL_VERSION_NUM >= 0x073e00
+#define GIT_CURL_HAVE_CURL_URL
+#endif
+
+/**
  * Versions before curl 7.66.0 (September 2019) required manually setting the
  * transfer-encoding for a streaming POST; after that this is handled
  * automatically.
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index 24fa317..9aa07be 100755
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -302,7 +302,9 @@
 		do
 			case "$line" in
 			*:*)
-				echo "${line%:*}" >../map/"${line#*:}";;
+				from_commit=${line%:*}
+				to_commit=${line#*:}
+				echo "$to_commit" >../map/"$from_commit";;
 			*)
 				die "Unable to load state from $state_branch:filter.map";;
 			esac
diff --git a/git-submodule.sh b/git-submodule.sh
index 2999b31..8632194 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -95,13 +95,13 @@
 		--reference=*)
 			reference="$1"
 			;;
-		--ref-format)
+		--ref-format|--ref-storage-format)
 			case "$2" in '') usage ;; esac
-			ref_format="--ref-format=$2"
+			ref_storage_format="--ref-storage-format=$2"
 			shift
 			;;
-		--ref-format=*)
-			ref_format="$1"
+		--ref-format=*|--ref-storage-format=*)
+			ref_storage_format="$1"
 			;;
 		--dissociate)
 			dissociate=$1
@@ -147,7 +147,7 @@
 		$progress \
 		${branch:+"$branch"} \
 		${reference:+"$reference"} \
-		${ref_format:+"$ref_format"} \
+		${ref_storage_format:+"$ref_storage_format"} \
 		$dissociate \
 		${name:+"$name"} \
 		${depth:+"$depth"} \
@@ -302,13 +302,13 @@
 		-r|--rebase)
 			rebase=$1
 			;;
-		--ref-format)
+		--ref-format|--ref-storage-format)
 			case "$2" in '') usage ;; esac
-			ref_format="--ref-format=$2"
+			ref_storage_format="--ref-storage-format=$2"
 			shift
 			;;
-		--ref-format=*)
-			ref_format="$1"
+		--ref-format=*|--ref-storage-format=*)
+			ref_storage_format="$1"
 			;;
 		--reference)
 			case "$2" in '') usage ;; esac
@@ -385,7 +385,7 @@
 		$rebase \
 		$merge \
 		$checkout \
-		${ref_format:+"$ref_format"} \
+		${ref_storage_format:+"$ref_storage_format"} \
 		${reference:+"$reference"} \
 		$dissociate \
 		${depth:+"$depth"} \
diff --git a/gpg-interface.c b/gpg-interface.c
index 95abf1e..60c315f 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -133,20 +133,20 @@ static struct gpg_format *get_format_by_name(const char *str)
 	return NULL;
 }
 
-static struct gpg_format *get_format_by_sig(const char *sig)
+static struct gpg_format *get_format_by_sig(const char *sig, size_t len)
 {
 	int j;
 
 	for (size_t i = 0; i < ARRAY_SIZE(gpg_format); i++)
 		for (j = 0; gpg_format[i].sigs[j]; j++)
-			if (starts_with(sig, gpg_format[i].sigs[j]))
+			if (starts_with_mem(sig, len, gpg_format[i].sigs[j]))
 				return gpg_format + i;
 	return NULL;
 }
 
 const char *get_signature_format(const char *buf)
 {
-	struct gpg_format *format = get_format_by_sig(buf);
+	struct gpg_format *format = get_format_by_sig(buf, strlen(buf));
 	return format ? format->name : "unknown";
 }
 
@@ -669,7 +669,7 @@ int check_signature(struct signature_check *sigc,
 	sigc->result = 'N';
 	sigc->trust_level = TRUST_UNDEFINED;
 
-	fmt = get_format_by_sig(signature);
+	fmt = get_format_by_sig(signature, slen);
 	if (!fmt)
 		die(_("bad/incompatible signature '%s'"), signature);
 
@@ -706,7 +706,7 @@ size_t parse_signed_buffer(const char *buf, size_t size)
 	while (len < size) {
 		const char *eol;
 
-		if (get_format_by_sig(buf + len))
+		if (get_format_by_sig(buf + len, size - len))
 			match = len;
 
 		eol = memchr(buf + len, '\n', size - len);
diff --git a/help.c b/help.c
index 4624149..1dd8391 100644
--- a/help.c
+++ b/help.c
@@ -824,7 +824,7 @@ void get_version_info(struct strbuf *buf, int show_build_options)
 			    SHA1_UNSAFE_BACKEND);
 #endif
 		strbuf_addf(buf, "SHA-256: %s\n", SHA256_BACKEND);
-		strbuf_addf(buf, "default-ref-format: %s\n",
+		strbuf_addf(buf, "default-ref-storage-format: %s\n",
 			    ref_storage_format_to_name(REF_STORAGE_FORMAT_DEFAULT));
 		strbuf_addf(buf, "default-hash: %s\n", hash_algos[GIT_HASH_DEFAULT].name);
 	}
diff --git a/http-walker.c b/http-walker.c
index abafca8..4f50dd9 100644
--- a/http-walker.c
+++ b/http-walker.c
@@ -540,9 +540,9 @@ static int fetch_object(struct walker *walker, const struct object_id *oid)
 	} else if (!oideq(&obj_req->oid, &req->real_oid)) {
 		ret = error("File %s has bad hash", hex);
 	} else if (req->rename < 0) {
-		struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->sources);
+		struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->source);
 		struct strbuf buf = STRBUF_INIT;
-		odb_loose_path(files->loose, &buf, &req->oid);
+		odb_loose_path(files->dirs->loose, &buf, &req->oid);
 		ret = error("unable to write sha1 filename %s", buf.buf);
 		strbuf_release(&buf);
 	}
diff --git a/http.c b/http.c
index c8fcfd7..d79678d 100644
--- a/http.c
+++ b/http.c
@@ -2315,6 +2315,45 @@ static int http_request(const char *url,
 	return ret;
 }
 
+#ifndef GIT_CURL_HAVE_CURL_URL
+#define strip_url_credential(in) NULL
+#else
+static char *strip_url_credential(const char *in)
+{
+	char *ret = NULL;
+	CURLU *url;
+
+	url = curl_url();
+	if (!url)
+		goto out;
+
+	if (curl_url_set(url, CURLUPART_URL, in, 0))
+		goto out;
+
+	curl_url_set(url, CURLUPART_USER, NULL, 0);
+	curl_url_set(url, CURLUPART_PASSWORD, NULL, 0);
+	curl_url_get(url, CURLUPART_URL, &ret, 0);
+
+out:
+	curl_url_cleanup(url);
+	return ret;
+}
+#endif
+
+static int match_effective_url(const char *asked, const char *got)
+{
+	char *stripped;
+	int ret;
+
+	if (!strcmp(asked, got))
+		return 1;
+
+	stripped = strip_url_credential(asked);
+	ret = stripped && !strcmp(stripped, got);
+	curl_free(stripped);
+	return ret;
+}
+
 /*
  * Update the "base" url to a more appropriate value, as deduced by
  * redirects seen when requesting a URL starting with "url".
@@ -2347,7 +2386,7 @@ static int update_url_from_redirect(struct strbuf *base,
 	const char *tail;
 	size_t new_len;
 
-	if (!strcmp(asked, got->buf))
+	if (match_effective_url(asked, got->buf))
 		return 0;
 
 	if (!skip_prefix(asked, base->buf, &tail))
@@ -2718,9 +2757,9 @@ int finish_http_pack_request(struct http_pack_request *preq)
 void http_install_packfile(struct packed_git *p,
 			   struct packfile_list *list_to_remove_from)
 {
-	struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->sources);
+	struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->source);
 	packfile_list_remove(list_to_remove_from, p);
-	packfile_store_add_pack(files->packed, p);
+	packfile_store_add_pack(files->dirs->packed, p);
 }
 
 struct http_pack_request *new_http_pack_request(
@@ -2846,7 +2885,7 @@ static size_t fwrite_sha1_file(char *ptr, size_t eltsize, size_t nmemb,
 struct http_object_request *new_http_object_request(const char *base_url,
 						    const struct object_id *oid)
 {
-	struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->sources);
+	struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->source);
 	char *hex = oid_to_hex(oid);
 	struct strbuf filename = STRBUF_INIT;
 	struct strbuf prevfile = STRBUF_INIT;
@@ -2861,7 +2900,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
 	oidcpy(&freq->oid, oid);
 	freq->localfile = -1;
 
-	odb_loose_path(files->loose, &filename, oid);
+	odb_loose_path(files->dirs->loose, &filename, oid);
 	strbuf_addf(&freq->tmpfile, "%s.temp", filename.buf);
 
 	strbuf_addf(&prevfile, "%s.prev", filename.buf);
@@ -2987,7 +3026,7 @@ void process_http_object_request(struct http_object_request *freq)
 
 int finish_http_object_request(struct http_object_request *freq)
 {
-	struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->sources);
+	struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->source);
 	struct stat st;
 	struct strbuf filename = STRBUF_INIT;
 
@@ -3014,7 +3053,7 @@ int finish_http_object_request(struct http_object_request *freq)
 		unlink_or_warn(freq->tmpfile.buf);
 		return -1;
 	}
-	odb_loose_path(files->loose, &filename, &freq->oid);
+	odb_loose_path(files->dirs->loose, &filename, &freq->oid);
 	freq->rename = finalize_object_file(the_repository, freq->tmpfile.buf, filename.buf);
 	strbuf_release(&filename);
 
diff --git a/log-tree.c b/log-tree.c
index 83a3c4b..361dfad 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -1043,10 +1043,9 @@ static int do_remerge_diff(struct rev_info *opt,
 	 * into the alternative object store list as the primary.
 	 */
 	if (opt->remerge_diff && !opt->remerge_objdir) {
-		opt->remerge_objdir = tmp_objdir_create(the_repository, "remerge-diff");
+		opt->remerge_objdir = tmp_objdir_create(the_repository, "remerge-diff", 1);
 		if (!opt->remerge_objdir)
 			return error(_("unable to create temporary object directory"));
-		tmp_objdir_replace_primary_odb(opt->remerge_objdir, 1);
 	}
 
 	/* Setup merge options */
diff --git a/loose.c b/loose.c
index c159d29..d6a2bb5 100644
--- a/loose.c
+++ b/loose.c
@@ -113,11 +113,10 @@ int loose_object_map_load(struct odb_source_loose *loose)
 
 int repo_read_loose_object_map(struct repository *repo)
 {
-	struct odb_source *source;
+	struct odb_source_files *files = odb_source_files_downcast(repo->objects->source);
 
-	for (source = repo->objects->sources; source; source = source->next) {
-		struct odb_source_files *files = odb_source_files_downcast(source);
-		if (loose_object_map_load(files->loose) < 0)
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		if (loose_object_map_load(dir->loose) < 0)
 			return -1;
 	}
 
@@ -126,8 +125,8 @@ int repo_read_loose_object_map(struct repository *repo)
 
 int repo_write_loose_object_map(struct repository *repo)
 {
-	struct odb_source_files *files = odb_source_files_downcast(repo->objects->sources);
-	kh_oid_map_t *map = files->loose->map->to_compat;
+	struct odb_source_files *files = odb_source_files_downcast(repo->objects->source);
+	kh_oid_map_t *map = files->dirs->loose->map->to_compat;
 	struct lock_file lock;
 	int fd;
 	khiter_t iter;
@@ -231,13 +230,12 @@ int repo_loose_object_map_oid(struct repository *repo,
 			      const struct git_hash_algo *to,
 			      struct object_id *dest)
 {
-	struct odb_source *source;
+	struct odb_source_files *files = odb_source_files_downcast(repo->objects->source);
 	kh_oid_map_t *map;
 	khiter_t pos;
 
-	for (source = repo->objects->sources; source; source = source->next) {
-		struct odb_source_files *files = odb_source_files_downcast(source);
-		struct loose_object_map *loose_map = files->loose->map;
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		struct loose_object_map *loose_map = dir->loose->map;
 		if (!loose_map)
 			continue;
 		map = (to == repo->compat_hash_algo) ?
diff --git a/merge-blobs.c b/merge-blobs.c
index 16a75bd..49dbec9 100644
--- a/merge-blobs.c
+++ b/merge-blobs.c
@@ -38,7 +38,7 @@ static void *three_way_filemerge(struct index_state *istate,
 				 size_t *size)
 {
 	enum ll_merge_result merge_status;
-	mmbuffer_t res;
+	mmfile_t res;
 
 	/*
 	 * This function is only used by cmd_merge_tree, which
diff --git a/merge-ll.c b/merge-ll.c
index ef5287d..996a381 100644
--- a/merge-ll.c
+++ b/merge-ll.c
@@ -17,11 +17,12 @@
 #include "quote.h"
 #include "strbuf.h"
 #include "gettext.h"
+#include "tempfile.h"
 
 struct ll_merge_driver;
 
 typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,
-			   mmbuffer_t *result,
+			   mmfile_t *result,
 			   const char *path,
 			   mmfile_t *orig, const char *orig_name,
 			   mmfile_t *src1, const char *name1,
@@ -56,7 +57,7 @@ void reset_merge_attributes(void)
  * Built-in low-levels
  */
 static enum ll_merge_result ll_binary_merge(const struct ll_merge_driver *drv UNUSED,
-			   mmbuffer_t *result,
+			   mmfile_t *result,
 			   const char *path UNUSED,
 			   mmfile_t *orig, const char *orig_name UNUSED,
 			   mmfile_t *src1, const char *name1 UNUSED,
@@ -101,7 +102,7 @@ static enum ll_merge_result ll_binary_merge(const struct ll_merge_driver *drv UN
 }
 
 static enum ll_merge_result ll_xdl_merge(const struct ll_merge_driver *drv_unused,
-			mmbuffer_t *result,
+			mmfile_t *result,
 			const char *path,
 			mmfile_t *orig, const char *orig_name,
 			mmfile_t *src1, const char *name1,
@@ -147,7 +148,7 @@ static enum ll_merge_result ll_xdl_merge(const struct ll_merge_driver *drv_unuse
 }
 
 static enum ll_merge_result ll_union_merge(const struct ll_merge_driver *drv_unused,
-			  mmbuffer_t *result,
+			  mmfile_t *result,
 			  const char *path,
 			  mmfile_t *orig, const char *orig_name,
 			  mmfile_t *src1, const char *name1,
@@ -174,22 +175,35 @@ static struct ll_merge_driver ll_merge_drv[] = {
 	{ "union", "built-in union merge", ll_union_merge },
 };
 
-static void create_temp(mmfile_t *src, char *path, size_t len)
+static struct tempfile *create_temp(mmfile_t *src)
 {
-	int fd;
+	struct tempfile *t = xmks_tempfile(".merge_file_XXXXXX");
+	if (write_in_full(t->fd, src->ptr, src->size) < 0)
+		die_errno(_("unable to write %s"), get_tempfile_path(t));
+	if (close_tempfile_gently(t) < 0)
+		die_errno(_("unable to close %s"), get_tempfile_path(t));
+	return t;
+}
 
-	xsnprintf(path, len, ".merge_file_XXXXXX");
-	fd = xmkstemp(path);
-	if (write_in_full(fd, src->ptr, src->size) < 0)
-		die_errno("unable to write temp-file");
-	close(fd);
+static const char *temp_path_basename(struct tempfile *t)
+{
+	/*
+	 * basename() takes a non-const pointer because it can
+	 * modify the input string to remove trailing directory
+	 * separators. We know that we don't have any because
+	 * this is a clean path generated from our vanilla
+	 * tempfile template.
+	 *
+	 * So casting away the const here is safe, albeit gross.
+	 */
+	return basename((char *)get_tempfile_path(t));
 }
 
 /*
  * User defined low-level merge driver support.
  */
 static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
-			mmbuffer_t *result,
+			mmfile_t *result,
 			const char *path,
 			mmfile_t *orig, const char *orig_name,
 			mmfile_t *src1, const char *name1,
@@ -197,12 +211,11 @@ static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
 			const struct ll_merge_options *opts,
 			int marker_size)
 {
-	char temp[3][50];
+	struct tempfile *tmp_o, *tmp_a, *tmp_b;
 	struct strbuf cmd = STRBUF_INIT;
 	const char *format = fn->cmdline;
 	struct child_process child = CHILD_PROCESS_INIT;
-	int status, fd, i;
-	struct stat st;
+	int status;
 	enum ll_merge_result ret;
 	assert(opts);
 
@@ -211,19 +224,19 @@ static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
 
 	result->ptr = NULL;
 	result->size = 0;
-	create_temp(orig, temp[0], sizeof(temp[0]));
-	create_temp(src1, temp[1], sizeof(temp[1]));
-	create_temp(src2, temp[2], sizeof(temp[2]));
+	tmp_o = create_temp(orig);
+	tmp_a = create_temp(src1);
+	tmp_b = create_temp(src2);
 
 	while (strbuf_expand_step(&cmd, &format)) {
 		if (skip_prefix(format, "%", &format))
 			strbuf_addch(&cmd, '%');
 		else if (skip_prefix(format, "O", &format))
-			strbuf_addstr(&cmd, temp[0]);
+			strbuf_addstr(&cmd, temp_path_basename(tmp_o));
 		else if (skip_prefix(format, "A", &format))
-			strbuf_addstr(&cmd, temp[1]);
+			strbuf_addstr(&cmd, temp_path_basename(tmp_a));
 		else if (skip_prefix(format, "B", &format))
-			strbuf_addstr(&cmd, temp[2]);
+			strbuf_addstr(&cmd, temp_path_basename(tmp_b));
 		else if (skip_prefix(format, "L", &format))
 			strbuf_addf(&cmd, "%d", marker_size);
 		else if (skip_prefix(format, "P", &format))
@@ -241,23 +254,6 @@ static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
 	child.use_shell = 1;
 	strvec_push(&child.args, cmd.buf);
 	status = run_command(&child);
-	fd = open(temp[1], O_RDONLY);
-	if (fd < 0)
-		goto bad;
-	if (fstat(fd, &st))
-		goto close_bad;
-	result->size = st.st_size;
-	result->ptr = xmallocz(result->size);
-	if (read_in_full(fd, result->ptr, result->size) != result->size) {
-		FREE_AND_NULL(result->ptr);
-		result->size = 0;
-	}
- close_bad:
-	close(fd);
- bad:
-	for (i = 0; i < 3; i++)
-		unlink_or_warn(temp[i]);
-	strbuf_release(&cmd);
 	if (!status)
 		ret = LL_MERGE_OK;
 	else if (status <= 128)
@@ -265,6 +261,14 @@ static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
 	else
 		/* died due to a signal: WTERMSIG(status) + 128 */
 		ret = LL_MERGE_ERROR;
+
+	if (read_mmfile(result, temp_path_basename(tmp_a)) < 0)
+		ret = LL_MERGE_ERROR;
+
+	delete_tempfile(&tmp_o);
+	delete_tempfile(&tmp_a);
+	delete_tempfile(&tmp_b);
+	strbuf_release(&cmd);
 	return ret;
 }
 
@@ -403,7 +407,7 @@ static void normalize_file(mmfile_t *mm, const char *path, struct index_state *i
 	}
 }
 
-enum ll_merge_result ll_merge(mmbuffer_t *result_buf,
+enum ll_merge_result ll_merge(mmfile_t *result_buf,
 	     const char *path,
 	     mmfile_t *ancestor, const char *ancestor_label,
 	     mmfile_t *ours, const char *our_label,
diff --git a/merge-ll.h b/merge-ll.h
index f26aef2..f95332c 100644
--- a/merge-ll.h
+++ b/merge-ll.h
@@ -16,7 +16,7 @@
  *   If you have no special requests, skip this and pass `NULL`
  *   as the `opts` parameter to use the default options.
  *
- * - Allocate an mmbuffer_t variable for the result.
+ * - Allocate an mmfile_t variable for the result.
  *
  * - Allocate and fill variables with the file's original content
  *   and two modified versions (using `read_mmfile`, for example).
@@ -100,7 +100,7 @@ enum ll_merge_result {
  * `.gitattributes` or `.git/info/attributes` into account.
  * Returns 0 for a clean merge.
  */
-enum ll_merge_result ll_merge(mmbuffer_t *result_buf,
+enum ll_merge_result ll_merge(mmfile_t *result_buf,
 	     const char *path,
 	     mmfile_t *ancestor, const char *ancestor_label,
 	     mmfile_t *ours, const char *our_label,
diff --git a/merge-ort.c b/merge-ort.c
index c410a5d..1d3d193 100644
--- a/merge-ort.c
+++ b/merge-ort.c
@@ -2111,7 +2111,7 @@ static int merge_3way(struct merge_options *opt,
 		      const struct object_id *b,
 		      const char *pathnames[3],
 		      const int extra_marker_size,
-		      mmbuffer_t *result_buf)
+		      mmfile_t *result_buf)
 {
 	mmfile_t orig, src1, src2;
 	struct ll_merge_options ll_opts = LL_MERGE_OPTIONS_INIT;
@@ -2247,7 +2247,7 @@ static int handle_content_merge(struct merge_options *opt,
 
 	/* Remaining rules depend on file vs. submodule vs. symlink. */
 	else if (S_ISREG(a->mode)) {
-		mmbuffer_t result_buf;
+		mmfile_t result_buf;
 		int ret = 0, merge_status;
 		int two_way;
 
diff --git a/meson.build b/meson.build
index 0a95d90..fa104a3 100644
--- a/meson.build
+++ b/meson.build
@@ -1925,19 +1925,23 @@
 
 if curl.found()
   libgit_curl = declare_dependency(
-    sources: [
-      'http.c',
-      'http-walker.c',
-    ],
+    link_with: static_library('git-curl',
+      sources: [
+        'http.c',
+        'http-walker.c',
+      ],
+      dependencies: [libgit_commonmain, curl],
+    ),
     dependencies: [libgit_commonmain, curl],
   )
 
-  test_dependencies += executable('git-remote-http',
+  git_remote_http = executable('git-remote-http',
     sources: 'remote-curl.c',
     dependencies: [libgit_curl],
     install: true,
     install_dir: git_exec_path,
   )
+  test_dependencies += git_remote_http
 
   test_dependencies += executable('git-http-fetch',
     sources: 'http-fetch.c',
@@ -1957,7 +1961,7 @@
 
   foreach alias : [ 'git-remote-https', 'git-remote-ftp', 'git-remote-ftps' ]
     test_dependencies += executable(alias,
-      sources: 'remote-curl.c',
+      objects: git_remote_http.extract_all_objects(recursive: false),
       dependencies: [libgit_curl],
     )
 
diff --git a/meson_options.txt b/meson_options.txt
index becf468..5fecb97 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -115,6 +115,8 @@
   description: 'Default format used when executing git-help(1).')
 option('docs_backend', type: 'combo', choices: ['asciidoc', 'asciidoctor', 'auto'], value: 'auto',
   description: 'Which backend to use to generate documentation.')
+option('man_base_url', type: 'string', value: '',
+  description: 'The base URL to use for relative links in manuals')
 
 # Testing.
 option('benchmarks', type: 'feature', value: 'auto',
diff --git a/midx.c b/midx.c
index 6d1c548..dc0c728 100644
--- a/midx.c
+++ b/midx.c
@@ -583,10 +583,16 @@ off_t nth_midxed_offset(struct multi_pack_index *m, uint32_t pos)
 
 uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos)
 {
-	pos = midx_for_object(&m, pos);
+	uint32_t pack_int_id;
 
-	return m->num_packs_in_base + get_be32(m->chunk_object_offsets +
-					       (off_t)pos * MIDX_CHUNK_OFFSET_WIDTH);
+	pos = midx_for_object(&m, pos);
+	pack_int_id = get_be32(m->chunk_object_offsets +
+			       (off_t)pos * MIDX_CHUNK_OFFSET_WIDTH);
+	if (pack_int_id >= m->num_packs)
+		die(_("bad pack-int-id: %"PRIu32" (%"PRIu32" total packs)"),
+		    pack_int_id, m->num_packs);
+
+	return m->num_packs_in_base + pack_int_id;
 }
 
 enum midx_fill_result midx_fill_entry(struct multi_pack_index *m,
@@ -606,7 +612,7 @@ enum midx_fill_result midx_fill_entry(struct multi_pack_index *m,
 
 	if (prepare_midx_pack(m, pack_int_id))
 		return MIDX_FILL_OWNER_UNAVAILABLE;
-	p = m->packs[pack_int_id - m->num_packs_in_base];
+	p = nth_midxed_pack(m, pack_int_id);
 
 	/*
 	* We are about to tell the caller where they can locate the
@@ -829,28 +835,22 @@ void clear_incremental_midx_files_ext(struct odb_source_packed *source, const ch
 
 void clear_midx_file(struct repository *r)
 {
-	struct odb_source_files *files;
+	struct odb_source_files *files = odb_source_files_downcast(r->objects->source);
 	struct strbuf midx = STRBUF_INIT;
 
-	if (r->objects) {
-		struct odb_source *source;
-
-		for (source = r->objects->sources; source; source = source->next) {
-			files = odb_source_files_downcast(source);
-			if (files->packed->midx)
-				close_midx(files->packed->midx);
-			files->packed->midx = NULL;
-		}
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		if (dir->packed->midx)
+			close_midx(dir->packed->midx);
+		dir->packed->midx = NULL;
 	}
 
-	files = odb_source_files_downcast(r->objects->sources);
-	get_midx_filename(files->packed, &midx);
+	get_midx_filename(files->dirs->packed, &midx);
 
 	if (remove_path(midx.buf))
 		die(_("failed to clear multi-pack-index at %s"), midx.buf);
 
-	clear_midx_files_ext(files->packed, MIDX_EXT_BITMAP, NULL);
-	clear_midx_files_ext(files->packed, MIDX_EXT_REV, NULL);
+	clear_midx_files_ext(files->dirs->packed, MIDX_EXT_BITMAP, NULL);
+	clear_midx_files_ext(files->dirs->packed, MIDX_EXT_REV, NULL);
 
 	strbuf_release(&midx);
 }
@@ -858,27 +858,24 @@ void clear_midx_file(struct repository *r)
 void clear_incremental_midx_files(struct repository *r,
 				  const struct strvec *keep_hashes)
 {
-	struct odb_source_files *files;
-	struct odb_source *source;
+	struct odb_source_files *files = odb_source_files_downcast(r->objects->source);
 	struct strbuf chain = STRBUF_INIT;
 
-	for (source = r->objects->sources; source; source = source->next) {
-		files = odb_source_files_downcast(source);
-		if (files->packed->midx)
-			close_midx(files->packed->midx);
-		files->packed->midx = NULL;
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		if (dir->packed->midx)
+			close_midx(dir->packed->midx);
+		dir->packed->midx = NULL;
 	}
 
-	files = odb_source_files_downcast(r->objects->sources);
-	get_midx_chain_filename(files->packed, &chain);
+	get_midx_chain_filename(files->dirs->packed, &chain);
 
 	if (!keep_hashes && remove_path(chain.buf))
 		die(_("failed to clear multi-pack-index chain at %s"),
 		    chain.buf);
 
-	clear_incremental_midx_files_ext(files->packed, MIDX_EXT_BITMAP, keep_hashes);
-	clear_incremental_midx_files_ext(files->packed, MIDX_EXT_REV, keep_hashes);
-	clear_incremental_midx_files_ext(files->packed, MIDX_EXT_MIDX, keep_hashes);
+	clear_incremental_midx_files_ext(files->dirs->packed, MIDX_EXT_BITMAP, keep_hashes);
+	clear_incremental_midx_files_ext(files->dirs->packed, MIDX_EXT_REV, keep_hashes);
+	clear_incremental_midx_files_ext(files->dirs->packed, MIDX_EXT_MIDX, keep_hashes);
 
 	strbuf_release(&chain);
 }
diff --git a/notes-merge.c b/notes-merge.c
index 118cad2..d361e70 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -355,7 +355,7 @@ static void write_note_to_worktree(const struct object_id *obj,
 static int ll_merge_in_worktree(struct notes_merge_options *o,
 				struct notes_merge_pair *p)
 {
-	mmbuffer_t result_buf;
+	mmfile_t result_buf;
 	mmfile_t base, local, remote;
 	enum ll_merge_result status;
 
diff --git a/object-file.c b/object-file.c
index a4cbf8b..c049bf1 100644
--- a/object-file.c
+++ b/object-file.c
@@ -488,7 +488,6 @@ struct odb_transaction_files {
 	enum odb_transaction_flags flags;
 
 	struct tmp_objdir *objdir;
-	struct odb_source *quarantine;
 	struct transaction_packfile packfile;
 	const char *prefix;
 
@@ -511,12 +510,10 @@ int odb_transaction_files_prepare(struct odb_transaction *base)
 	if (!transaction || transaction->objdir)
 		return 0;
 
-	transaction->objdir = tmp_objdir_create(base->source->odb->repo, transaction->prefix);
+	transaction->objdir = tmp_objdir_create(base->source->odb->repo, transaction->prefix, 0);
 	if (!transaction->objdir)
 		return error(_("unable to create temporary object directory"));
 
-	transaction->quarantine = tmp_objdir_replace_primary_odb(transaction->objdir, 0);
-
 	return 0;
 }
 
@@ -857,8 +854,6 @@ static void flush_packfile_transaction(struct odb_transaction_files *transaction
 	memset(state, 0, sizeof(*state));
 
 	strbuf_release(&packname);
-	/* Make objects we just wrote available to ourselves */
-	odb_reprepare(repo->objects);
 }
 
 /*
@@ -909,8 +904,10 @@ static int odb_transaction_files_write_object_stream(struct odb_transaction *bas
 	 * to zlib compression and is sufficient for this check.
 	 */
 	if (state->nr_written && pack_size_limit_cfg &&
-	    pack_size_limit_cfg < state->offset + stream->size)
+	    pack_size_limit_cfg < state->offset + stream->size) {
 		flush_packfile_transaction(transaction);
+		odb_reprepare(transaction->base.source->odb);
+	}
 
 	CALLOC_ARRAY(idx, 1);
 	prepare_packfile_transaction(transaction);
@@ -1260,6 +1257,9 @@ static int odb_transaction_files_commit(struct odb_transaction *base)
 {
 	struct odb_transaction_files *transaction =
 		container_of(base, struct odb_transaction_files, base);
+	int have_packfile = !!transaction->packfile.f;
+
+	flush_packfile_transaction(transaction);
 
 	if (transaction->objdir) {
 		struct strbuf temp_path = STRBUF_INIT;
@@ -1291,7 +1291,8 @@ static int odb_transaction_files_commit(struct odb_transaction *base)
 		transaction->objdir = NULL;
 	}
 
-	flush_packfile_transaction(transaction);
+	if (have_packfile)
+		odb_reprepare(transaction->base.source->odb);
 
 	return 0;
 }
@@ -1443,8 +1444,7 @@ static int odb_transaction_files_write_pack(struct odb_transaction *base,
 			return -1;
 		}
 
-		odb_source_prepare(transaction->quarantine,
-				   ODB_PREPARE_FLUSH_CACHES);
+		odb_source_prepare(base->source, ODB_PREPARE_FLUSH_CACHES);
 	}
 
 	return 0;
diff --git a/object-name.c b/object-name.c
index 4eda8c8..6546685 100644
--- a/object-name.c
+++ b/object-name.c
@@ -657,7 +657,7 @@ static inline int upstream_mark(const char *string, int len)
 
 static inline int push_mark(const char *string, int len)
 {
-	const char *suffix[] = { "@{push}" };
+	const char *suffix[] = { "@{push}", "@{p}" };
 	return at_mark(string, len, suffix, ARRAY_SIZE(suffix));
 }
 
diff --git a/object.h b/object.h
index 8fb03ff..46cade3 100644
--- a/object.h
+++ b/object.h
@@ -74,6 +74,7 @@ void object_array_init(struct object_array *array);
  * bisect.c:                                        16
  * bundle.c:                                        16
  * http-push.c:                          11-----14
+ * builtin/history.c:                    11---13
  * commit-graph.c:                                15
  * commit-reach.c:                                  16-------20
  * builtin/last-modified.c:                         1617
diff --git a/odb.c b/odb.c
index 32fef21..98c5c86 100644
--- a/odb.c
+++ b/odb.c
@@ -13,10 +13,10 @@
 #include "object-file.h"
 #include "object-name.h"
 #include "odb.h"
+#include "odb/source-files.h"
 #include "odb/source-inmemory.h"
 #include "path.h"
 #include "promisor-remote.h"
-#include "quote.h"
 #include "replace-object.h"
 #include "run-command.h"
 #include "setup.h"
@@ -27,48 +27,6 @@
 #include "trace2.h"
 #include "write-or-die.h"
 
-/*
- * NEEDSWORK: we're using "core.ignoreCase" to deduplicate alternates that
- * _may_ be the same. This requires quite a bit of boilerplate for dubious
- * benefit:
- *
- *   - Duplicating alternates should really only lead to regressed performance.
- *
- *   - We don't properly resolve symlinks or mointpoints, so we may still end
- *     up duplicating alternates.
- *
- *   - The value may be lying, in which case we might deduplicate alternates
- *     that are in fact not mapping to the same directory.
- *
- * We should investigate whether we can remove this whole mechanism outright.
- */
-static int odb_source_paths_cmp(struct object_database *o,
-				const char *a, const char *b)
-{
-	if (o->source_paths_icase < 0) {
-		int icase = 0;
-		repo_config_get_bool(o->repo, "core.ignorecase", &icase);
-		o->source_paths_icase = icase;
-	}
-
-	return o->source_paths_icase ? strcasecmp(a, b) : strcmp(a, b);
-}
-
-static int odb_source_by_path_cmp(const void *cb_data,
-				  const struct hashmap_entry *entry,
-				  const struct hashmap_entry *entry_or_key,
-				  const void *keydata)
-{
-	struct object_database *o = (struct object_database *)cb_data;
-	const struct odb_source *source = container_of(entry, const struct odb_source, by_path_entry);
-	const char *path = keydata;
-
-	if (!path)
-		path = container_of(entry_or_key, const struct odb_source, by_path_entry)->path;
-
-	return odb_source_paths_cmp(o, source->path, path);
-}
-
 int odb_mkstemp(struct object_database *odb,
 		struct strbuf *temp_filename, const char *pattern)
 {
@@ -90,208 +48,6 @@ int odb_mkstemp(struct object_database *odb,
 	return xmkstemp_mode(temp_filename->buf, mode);
 }
 
-/*
- * Return non-zero iff the path is usable as an alternate object database.
- */
-static bool odb_is_source_usable(struct object_database *o, const char *path)
-{
-	struct strbuf normalized_objdir = STRBUF_INIT;
-	struct hashmap_entry key;
-	bool usable = false;
-
-	strbuf_realpath(&normalized_objdir, o->sources->path, 1);
-
-	/* Detect cases where alternate disappeared */
-	if (!is_directory(path)) {
-		error(_("object directory %s does not exist; "
-			"check .git/objects/info/alternates"),
-		      path);
-		goto out;
-	}
-
-	/*
-	 * Prevent the common mistake of listing the same
-	 * thing twice, or object directory itself.
-	 */
-	if (!hashmap_get_size(&o->source_by_path)) {
-		assert(!o->sources->next);
-		hashmap_entry_init(&o->sources->by_path_entry,
-				   strihash(o->sources->path));
-		hashmap_add(&o->source_by_path, &o->sources->by_path_entry);
-	}
-
-	if (!odb_source_paths_cmp(o, path, normalized_objdir.buf))
-		goto out;
-
-	hashmap_entry_init(&key, strihash(path));
-	if (hashmap_get(&o->source_by_path, &key, path))
-		goto out;
-
-	usable = true;
-
-out:
-	strbuf_release(&normalized_objdir);
-	return usable;
-}
-
-void parse_alternates(const char *string,
-		      int sep,
-		      const char *relative_base,
-		      struct strvec *out)
-{
-	struct strbuf pathbuf = STRBUF_INIT;
-	struct strbuf buf = STRBUF_INIT;
-
-	if (!string || !*string)
-		return;
-
-	while (*string) {
-		const char *end;
-
-		strbuf_reset(&buf);
-		strbuf_reset(&pathbuf);
-
-		if (*string == '#') {
-			/* comment; consume up to next separator */
-			end = strchrnul(string, sep);
-		} else if (*string == '"' && !unquote_c_style(&buf, string, &end)) {
-			/*
-			 * quoted path; unquote_c_style has copied the
-			 * data for us and set "end". Broken quoting (e.g.,
-			 * an entry that doesn't end with a quote) falls
-			 * back to the unquoted case below.
-			 */
-		} else {
-			/* normal, unquoted path */
-			end = strchrnul(string, sep);
-			strbuf_add(&buf, string, end - string);
-		}
-
-		if (*end)
-			end++;
-		string = end;
-
-		if (!buf.len)
-			continue;
-
-		if (!is_absolute_path(buf.buf) && relative_base) {
-			strbuf_realpath(&pathbuf, relative_base, 1);
-			strbuf_addch(&pathbuf, '/');
-		}
-		strbuf_addbuf(&pathbuf, &buf);
-
-		strbuf_reset(&buf);
-		if (!strbuf_realpath(&buf, pathbuf.buf, 0)) {
-			error(_("unable to normalize alternate object path: %s"),
-			      pathbuf.buf);
-			continue;
-		}
-
-		/*
-		 * The trailing slash after the directory name is given by
-		 * this function at the end. Remove duplicates.
-		 */
-		while (buf.len && buf.buf[buf.len - 1] == '/')
-			strbuf_setlen(&buf, buf.len - 1);
-
-		strvec_push(out, buf.buf);
-	}
-
-	strbuf_release(&pathbuf);
-	strbuf_release(&buf);
-}
-
-static struct odb_source *odb_add_alternate_recursively(struct object_database *odb,
-							const char *source,
-							int depth)
-{
-	struct odb_source *alternate = NULL;
-	struct strvec sources = STRVEC_INIT;
-
-	if (!odb_is_source_usable(odb, source))
-		goto error;
-
-	alternate = odb_source_new(odb, source, false);
-
-	/* add the alternate entry */
-	*odb->sources_tail = alternate;
-	odb->sources_tail = &(alternate->next);
-
-	hashmap_entry_init(&alternate->by_path_entry, strihash(alternate->path));
-	if (hashmap_get(&odb->source_by_path, &alternate->by_path_entry,
-			alternate->path))
-		BUG("source must not yet exist");
-	hashmap_add(&odb->source_by_path, &alternate->by_path_entry);
-
-	/* recursively add alternates */
-	odb_source_read_alternates(alternate, &sources);
-	if (sources.nr && depth + 1 > 5) {
-		error(_("%s: ignoring alternate object stores, nesting too deep"),
-		      source);
-	} else {
-		for (size_t i = 0; i < sources.nr; i++)
-			odb_add_alternate_recursively(odb, sources.v[i], depth + 1);
-	}
-
- error:
-	strvec_clear(&sources);
-	return alternate;
-}
-
-void odb_add_to_alternates_file(struct object_database *odb,
-				const char *dir)
-{
-	int ret = odb_source_write_alternate(odb->sources, dir);
-	if (ret < 0)
-		die(NULL);
-	odb_add_alternate_recursively(odb, dir, 0);
-}
-
-struct odb_source *odb_set_temporary_primary_source(struct object_database *odb,
-						    const char *dir, int will_destroy,
-						    struct odb_source **prev_source)
-{
-	struct odb_source *source;
-
-	/*
-	 * Make a new primary odb and link the old primary ODB in as an
-	 * alternate
-	 */
-	source = odb_source_new(odb, dir, false);
-
-	/*
-	 * Disable ref updates while a temporary odb is active, since
-	 * the objects in the database may roll back.
-	 */
-	odb->repo->disable_ref_updates = true;
-	source->will_destroy = will_destroy;
-	source->next = odb->sources;
-	odb->sources = source;
-
-	if (prev_source)
-		*prev_source = source->next;
-
-	return source;
-}
-
-void odb_restore_primary_source(struct object_database *odb,
-				struct odb_source *restore_source,
-				const char *old_path)
-{
-	struct odb_source *cur_source = odb->sources;
-
-	if (strcmp(old_path, cur_source->path))
-		BUG("expected %s as primary object store; found %s",
-		    old_path, cur_source->path);
-
-	if (cur_source->next != restore_source)
-		BUG("we expect the old primary object store to be the first alternate");
-
-	odb->repo->disable_ref_updates = false;
-	odb->sources = restore_source;
-	odb_source_free(cur_source);
-}
-
 char *compute_alternate_path(const char *path, struct strbuf *err)
 {
 	char *ref_git = NULL;
@@ -356,32 +112,6 @@ char *compute_alternate_path(const char *path, struct strbuf *err)
 	return ref_git;
 }
 
-struct odb_source *odb_find_source(struct object_database *odb, const char *obj_dir)
-{
-	struct odb_source *source;
-	char *obj_dir_real = real_pathdup(obj_dir, 1);
-	struct strbuf odb_path_real = STRBUF_INIT;
-
-	for (source = odb->sources; source; source = source->next) {
-		strbuf_realpath(&odb_path_real, source->path, 1);
-		if (!strcmp(obj_dir_real, odb_path_real.buf))
-			break;
-	}
-
-	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;
-}
-
 static void fill_alternate_refs_command(struct repository *repo,
 					struct child_process *cmd,
 					const char *repo_path)
@@ -444,18 +174,19 @@ static void read_alternate_refs(struct repository *repo,
 }
 
 struct alternate_refs_data {
+	struct repository *repo;
 	odb_for_each_alternate_ref_fn *fn;
 	void *payload;
 };
 
-static int refs_from_alternate_cb(struct odb_source *alternate,
+static int refs_from_alternate_cb(struct odb_files_dir *alternate,
 				  void *payload)
 {
 	struct strbuf path = STRBUF_INIT;
 	size_t base_len;
 	struct alternate_refs_data *cb = payload;
 
-	if (!strbuf_realpath(&path, alternate->path, 0))
+	if (!strbuf_realpath(&path, alternate->abspath, 0))
 		goto out;
 	if (!strbuf_strip_suffix(&path, "/objects"))
 		goto out;
@@ -467,7 +198,7 @@ static int refs_from_alternate_cb(struct odb_source *alternate,
 		goto out;
 	strbuf_setlen(&path, base_len);
 
-	read_alternate_refs(alternate->odb->repo, path.buf, cb->fn, cb->payload);
+	read_alternate_refs(cb->repo, path.buf, cb->fn, cb->payload);
 
 out:
 	strbuf_release(&path);
@@ -477,43 +208,33 @@ static int refs_from_alternate_cb(struct odb_source *alternate,
 void odb_for_each_alternate_ref(struct object_database *odb,
 				odb_for_each_alternate_ref_fn cb, void *payload)
 {
-	struct alternate_refs_data data;
-	data.fn = cb;
-	data.payload = payload;
+	struct alternate_refs_data data = {
+		.fn = cb,
+		.payload = payload,
+		.repo = odb->repo,
+	};
 	odb_for_each_alternate(odb, refs_from_alternate_cb, &data);
 }
 
 int odb_for_each_alternate(struct object_database *odb,
 			 odb_for_each_alternate_fn cb, void *payload)
 {
-	struct odb_source *alternate;
+	struct odb_source_files *files = odb_source_files_downcast(odb->source);
 	int r = 0;
 
-	for (alternate = odb->sources->next; alternate; alternate = alternate->next) {
-		r = cb(alternate, payload);
+	for (struct odb_files_dir *dir = files->dirs->next; dir; dir = dir->next) {
+		r = cb(dir, payload);
 		if (r)
 			break;
 	}
 	return r;
 }
 
-static void odb_prepare_alternates(struct object_database *odb,
-				   const char *alternate_db)
-{
-	struct strvec sources = STRVEC_INIT;
-
-	parse_alternates(alternate_db, PATH_SEP, NULL, &sources);
-	odb_source_read_alternates(odb->sources, &sources);
-
-	for (size_t i = 0; i < sources.nr; i++)
-		odb_add_alternate_recursively(odb, sources.v[i], 0);
-
-	strvec_clear(&sources);
-}
-
 int odb_has_alternates(struct object_database *odb)
 {
-	return !!odb->sources->next;
+	if (odb->source->type != ODB_SOURCE_FILES)
+		return 0;
+	return !!odb_source_files_downcast(odb->source)->dirs->next;
 }
 
 int obj_read_use_lock = 0;
@@ -557,16 +278,12 @@ static enum odb_read_status do_oid_object_info_extended(struct object_database *
 		return 0;
 
 	while (1) {
-		struct odb_source *source;
-
-		for (source = odb->sources; source; source = source->next) {
-			ret = odb_source_read_object_info(source, real, oi, flags,
-							  corrupt_err.len ? NULL : &corrupt_err);
-			if (!ret)
-				goto out;
-			if (ret != ODB_READ_NOT_FOUND)
-				corrupt = true;
-		}
+		ret = odb_source_read_object_info(odb->source, real, oi, flags,
+						  corrupt_err.len ? NULL : &corrupt_err);
+		if (!ret)
+			goto out;
+		if (ret != ODB_READ_NOT_FOUND)
+			corrupt = true;
 
 		/*
 		 * When the object hasn't been found we try a second read and
@@ -574,15 +291,13 @@ static enum odb_read_status do_oid_object_info_extended(struct object_database *
 		 * caches or reload on-disk state.
 		 */
 		if (!(flags & OBJECT_INFO_QUICK)) {
-			for (source = odb->sources; source; source = source->next) {
-				ret = odb_source_read_object_info(source, real, oi,
-								  flags | OBJECT_INFO_SECOND_READ,
-								  corrupt_err.len ? NULL : &corrupt_err);
-				if (!ret)
-					goto out;
-				if (ret != ODB_READ_NOT_FOUND)
-					corrupt = true;
-			}
+			ret = odb_source_read_object_info(odb->source, real, oi,
+							  flags | OBJECT_INFO_SECOND_READ,
+							  corrupt_err.len ? NULL : &corrupt_err);
+			if (!ret)
+				goto out;
+			if (ret != ODB_READ_NOT_FOUND)
+				corrupt = true;
 		}
 
 		/* Check if it is a missing object */
@@ -824,11 +539,7 @@ int odb_has_object(struct object_database *odb, const struct object_id *oid,
 int odb_freshen_object(struct object_database *odb,
 		       const struct object_id *oid)
 {
-	struct odb_source *source;
-	for (source = odb->sources; source; source = source->next)
-		if (odb_source_freshen_object(source, oid, NULL))
-			return 1;
-	return 0;
+	return odb_source_freshen_object(odb->source, oid, NULL);
 }
 
 int odb_for_each_object_ext(struct object_database *odb,
@@ -837,18 +548,7 @@ int odb_for_each_object_ext(struct object_database *odb,
 			    void *cb_data,
 			    const struct odb_for_each_object_options *opts)
 {
-	int ret;
-
-	for (struct odb_source *source = odb->sources; source; source = source->next) {
-		if (opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY && !source->local)
-			continue;
-
-		ret = odb_source_for_each_object(source, request, cb, cb_data, opts);
-		if (ret)
-			return ret;
-	}
-
-	return 0;
+	return odb_source_for_each_object(odb->source, request, cb, cb_data, opts);
 }
 
 int odb_for_each_object(struct object_database *odb,
@@ -867,7 +567,6 @@ int odb_count_objects(struct object_database *odb,
 		      enum odb_count_objects_flags flags,
 		      unsigned long *out)
 {
-	struct odb_source *source;
 	unsigned long count = 0;
 	int ret;
 
@@ -876,15 +575,9 @@ int odb_count_objects(struct object_database *odb,
 		return 0;
 	}
 
-	for (source = odb->sources; source; source = source->next) {
-		unsigned long c;
-
-		ret = odb_source_count_objects(source, flags, &c);
-		if (ret < 0)
-			goto out;
-
-		count += c;
-	}
+	ret = odb_source_count_objects(odb->source, flags, &count);
+	if (ret < 0)
+		goto out;
 
 	odb->object_count = count;
 	odb->object_count_valid = 1;
@@ -955,13 +648,7 @@ int odb_find_abbrev_len(struct object_database *odb,
 		goto out;
 	}
 
-	for (struct odb_source *source = odb->sources; source; source = source->next) {
-		ret = odb_source_find_abbrev_len(source, oid, len, &len);
-		if (ret)
-			goto out;
-	}
-
-	ret = 0;
+	ret = odb_source_find_abbrev_len(odb->source, oid, len, &len);
 	*out = len;
 
 out:
@@ -1017,7 +704,7 @@ int odb_write_object_ext(struct object_database *odb,
 		compat_oid_p = &compat_oid;
 	}
 
-	return odb_source_write_object(odb->sources, buf, len, type,
+	return odb_source_write_object(odb->source, buf, len, type,
 				       oid, compat_oid_p, NULL, flags);
 }
 
@@ -1025,19 +712,19 @@ int odb_write_object_stream(struct object_database *odb,
 			    struct odb_stream *stream,
 			    struct object_id *oid)
 {
-	return odb_source_write_object_stream(odb->sources, stream, oid);
+	return odb_source_write_object_stream(odb->source, stream, oid);
 }
 
 int odb_optimize(struct object_database *odb,
 		 const struct odb_optimize_options *opts)
 {
-	return odb_source_optimize(odb->sources, opts);
+	return odb_source_optimize(odb->source, opts);
 }
 
 bool odb_optimize_required(struct object_database *odb,
 			   const struct odb_optimize_options *opts)
 {
-	return odb_source_optimize_required(odb->sources, opts);
+	return odb_source_optimize_required(odb->source, opts);
 }
 
 void odb_generate_pack_options_release(struct odb_generate_pack_options *opts)
@@ -1051,9 +738,9 @@ int odb_generate_pack(struct object_database *odb,
 		      struct odb_pack_generator **out,
 		      const struct odb_generate_pack_options *opts)
 {
-	if (!odb->sources->generate_pack)
+	if (!odb->source->generate_pack)
 		return error(_("primary object source does not support generating packfiles"));
-	return odb_source_generate_pack(odb->sources, out, opts);
+	return odb_source_generate_pack(odb->source, out, opts);
 }
 
 int odb_pack_generator_finish(struct odb_pack_generator *generator)
@@ -1064,55 +751,29 @@ int odb_pack_generator_finish(struct odb_pack_generator *generator)
 struct object_database *odb_new(struct repository *repo,
 				enum odb_new_flags flags)
 {
-	char *primary_source = NULL, *secondary_sources = NULL;
 	struct object_database *o;
 
 	CALLOC_ARRAY(o, 1);
 	o->repo = repo;
 	pthread_mutex_init(&o->replace_mutex, NULL);
-	hashmap_init(&o->source_by_path, odb_source_by_path_cmp, o, 0);
-	o->source_paths_icase = -1;
 
-	if (flags & ODB_NEW_HONOR_ENV) {
-		primary_source = xstrdup_or_null(getenv(DB_ENVIRONMENT));
-		secondary_sources = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT));
-	}
-	if (!primary_source)
-		primary_source = xstrfmt("%s/objects", repo->commondir);
-
-	o->sources = odb_source_new(o, primary_source, true);
-	o->sources_tail = &o->sources->next;
+	o->source = odb_source_new(o, flags);
 	o->inmemory_objects = &odb_source_inmemory_new(o)->base;
 
-	odb_prepare_alternates(o, secondary_sources);
-
-	free(secondary_sources);
-	free(primary_source);
 	return o;
 }
 
 void odb_close(struct object_database *o)
 {
-	struct odb_source *source;
-	for (source = o->sources; source; source = source->next)
-		odb_source_close(source);
+	odb_source_close(o->source);
 	close_commit_graph(o);
 }
 
 static void odb_free_sources(struct object_database *o)
 {
-	while (o->sources) {
-		struct odb_source *next;
-
-		next = o->sources->next;
-		odb_source_free(o->sources);
-		o->sources = next;
-	}
-
+	odb_source_free(o->source);
 	odb_source_free(o->inmemory_objects);
 	o->inmemory_objects = NULL;
-
-	hashmap_clear(&o->source_by_path);
 }
 
 void odb_free(struct object_database *o)
@@ -1131,24 +792,12 @@ void odb_free(struct object_database *o)
 
 void odb_prepare(struct object_database *o, enum odb_prepare_flags flags)
 {
-	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. Consequently, we don't have to
-	 * reprocess GIT_ALTERNATE_OBJECT_DIRECTORIES here.
-	 */
-	if (flags & ODB_PREPARE_FLUSH_CACHES) {
-		odb_prepare_alternates(o, NULL);
+	if (flags & ODB_PREPARE_FLUSH_CACHES)
 		o->object_count_valid = 0;
-	}
 
-	for (source = o->sources; source; source = source->next)
-		odb_source_prepare(source, flags);
+	odb_source_prepare(o->source, flags);
 
 	obj_read_unlock();
 }
@@ -1160,8 +809,5 @@ void odb_reprepare(struct object_database *o)
 
 int odb_fsck(struct object_database *odb, struct odb_fsck_options *options)
 {
-	int ret = 0;
-	for (struct odb_source *source = odb->sources; source; source = source->next)
-		ret |= odb_source_fsck(source, options);
-	return ret;
+	return odb_source_fsck(odb->source, options);
 }
diff --git a/odb.h b/odb.h
index 85d45b7..0ccc47e 100644
--- a/odb.h
+++ b/odb.h
@@ -12,6 +12,7 @@
 struct cached_object_entry;
 struct list_objects_filter_options;
 struct odb_source_inmemory;
+struct odb_files_dir;
 struct packed_git;
 struct repository;
 struct strbuf;
@@ -27,13 +28,16 @@ char *compute_alternate_path(const char *path, struct strbuf *err);
 
 /*
  * The object database encapsulates access to objects in a repository. It
- * manages one or more sources that store the actual objects which are
- * configured via alternates.
+ * manages the object source as well as auxiliary data structures required to
+ * manage objects.
  */
 struct object_database {
 	/* Repository that owns this database. */
 	struct repository *repo;
 
+	/* The source backing this object database. */
+	struct odb_source *source;
+
 	/*
 	 * State of current object database transaction. Only one
 	 * transaction may be pending at a time. Is NULL when no transaction is
@@ -42,27 +46,6 @@ struct object_database {
 	struct odb_transaction *transaction;
 
 	/*
-	 * Set of all object directories; the main directory is first (and
-	 * cannot be NULL after initialization). Subsequent directories are
-	 * alternates.
-	 */
-	struct odb_source *sources;
-	struct odb_source **sources_tail;
-
-	/*
-	 * Map of object database sources, keyed by their respective paths.
-	 * This map is used to detect the case where the same source is
-	 * registered multiple times.
-	 */
-	struct hashmap source_by_path;
-
-	/*
-	 * Whether source paths shall be compared case-insensitively, as
-	 * determined by "core.ignoreCase".
-	 */
-	int source_paths_icase;
-
-	/*
 	 * Objects that should be substituted by other objects
 	 * (see git-replace(1)).
 	 */
@@ -226,38 +209,12 @@ struct odb_fsck_options {
 int odb_fsck(struct object_database *odb, struct odb_fsck_options *opts);
 
 /*
- * 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 and return the newly installed primary source. The former
- * primary source is reported via `prev_source` when non-NULL.
- */
-struct odb_source *odb_set_temporary_primary_source(struct object_database *odb,
-						    const char *dir, int will_destroy,
-						    struct odb_source **prev_source);
-
-/*
- * Restore the primary source that was previously replaced by
- * `odb_set_temporary_primary_source()`.
- */
-void odb_restore_primary_source(struct object_database *odb,
-				struct odb_source *restore_source,
-				const char *old_path);
-
-/*
  * Iterate through all alternates of the database and execute the provided
  * callback function for each of them. Stop iterating once the callback
  * function returns a non-zero value, in which case the value is bubbled up
  * from the callback.
  */
-typedef int odb_for_each_alternate_fn(struct odb_source *, void *);
+typedef int odb_for_each_alternate_fn(struct odb_files_dir *, void *);
 int odb_for_each_alternate(struct object_database *odb,
 			   odb_for_each_alternate_fn cb, void *payload);
 
@@ -285,13 +242,6 @@ int odb_mkstemp(struct object_database *odb,
 int odb_has_alternates(struct object_database *odb);
 
 /*
- * Add the directory to the on-disk alternates file; the new entry will also
- * take effect in the current process.
- */
-void odb_add_to_alternates_file(struct object_database *odb,
-				const char *dir);
-
-/*
  * Read an object from the database. Returns the object data and assigns object
  * type and size to the `type` and `size` pointers, if these pointers are
  * non-NULL. Returns a `NULL` pointer in case the object does not exist.
@@ -854,9 +804,4 @@ int odb_generate_pack(struct object_database *odb,
  */
 int odb_pack_generator_finish(struct odb_pack_generator *generator);
 
-void parse_alternates(const char *string,
-		      int sep,
-		      const char *relative_base,
-		      struct strvec *out);
-
 #endif /* ODB_H */
diff --git a/odb/source-files.c b/odb/source-files.c
index 109c416..455f409 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -15,14 +15,40 @@
 #include "packfile.h"
 #include "path.h"
 #include "promisor-remote.h"
+#include "quote.h"
 #include "repack.h"
 #include "run-command.h"
 #include "strbuf.h"
 #include "string-list.h"
+#include "strmap.h"
 #include "strvec.h"
 #include "tree.h"
 #include "write-or-die.h"
 
+struct odb_files_dir *odb_files_dir_new(struct object_database *odb,
+					const char *path, bool local)
+{
+	struct odb_files_dir *dir;
+
+	CALLOC_ARRAY(dir, 1);
+	dir->abspath = absolute_pathdup(path);
+	dir->local = local;
+	dir->loose = odb_source_loose_new(odb, path, local);
+	dir->packed = odb_source_packed_new(odb, path, local);
+
+	return dir;
+}
+
+void odb_files_dir_free(struct odb_files_dir *dir)
+{
+	if (!dir)
+		return;
+	odb_source_free(&dir->loose->base);
+	odb_source_free(&dir->packed->base);
+	free(dir->abspath);
+	free(dir);
+}
+
 static void odb_source_files_reparent(const char *old_cwd,
 				      const char *new_cwd,
 				      void *cb_data)
@@ -30,6 +56,7 @@ static void odb_source_files_reparent(const char *old_cwd,
 	struct odb_source_files *files = cb_data;
 	char *path = reparent_relative_path(old_cwd, new_cwd,
 					    files->base.path);
+
 	free(files->base.path);
 	files->base.path = path;
 }
@@ -37,9 +64,16 @@ static void odb_source_files_reparent(const char *old_cwd,
 static void odb_source_files_free(struct odb_source *source)
 {
 	struct odb_source_files *files = odb_source_files_downcast(source);
+
 	chdir_notify_unregister(odb_source_files_reparent, files);
-	odb_source_free(&files->loose->base);
-	odb_source_free(&files->packed->base);
+
+	while (files->dirs) {
+		struct odb_files_dir *next = files->dirs->next;
+		odb_files_dir_free(files->dirs);
+		files->dirs = next;
+	}
+	hashmap_clear(&files->dirs_by_path);
+
 	odb_source_release(&files->base);
 	free(files);
 }
@@ -47,13 +81,21 @@ static void odb_source_files_free(struct odb_source *source)
 static void odb_source_files_close(struct odb_source *source)
 {
 	struct odb_source_files *files = odb_source_files_downcast(source);
-	odb_source_close(&files->loose->base);
-	odb_source_close(&files->packed->base);
+
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		odb_source_close(&dir->loose->base);
+		odb_source_close(&dir->packed->base);
+	}
 }
 
-static int odb_source_files_create_on_disk(struct odb_source *source)
+static int odb_source_files_create_on_disk(struct odb_source *source,
+					   const struct odb_create_on_disk_options *opts)
 {
+	struct lock_file alternates_lock = LOCK_INIT;
 	struct strbuf path = STRBUF_INIT;
+	struct strset seen = STRSET_INIT;
+	struct strbuf line = STRBUF_INIT;
+	int ret;
 
 	safe_create_dir(source->odb->repo, source->path, 1);
 
@@ -64,16 +106,316 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
 	strbuf_addf(&path, "%s/info", source->path);
 	safe_create_dir(source->odb->repo, path.buf, 1);
 
+	if (opts->alternates && opts->alternates->nr) {
+		FILE *alternates, *orig;
+
+		strbuf_reset(&path);
+		strbuf_addf(&path, "%s/info/alternates", source->path);
+
+		repo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,
+					       path.buf, LOCK_DIE_ON_ERROR);
+
+		alternates = fdopen_lock_file(&alternates_lock, "w");
+		if (!alternates) {
+			ret = error_errno(_("unable to fdopen alternates lockfile"));
+			goto out;
+		}
+
+		/*
+		 * The alternates file may already exist, e.g. when it has been
+		 * seeded from a template directory. Read any preexisting
+		 * entries so that we don't end up writing duplicates.
+		 */
+		orig = fopen(path.buf, "r");
+		if (orig) {
+			while (strbuf_getline(&line, orig) != EOF) {
+				strset_add(&seen, line.buf);
+				fprintf(alternates, "%s\n", line.buf);
+			}
+
+			if (ferror(orig)) {
+				ret = error_errno(_("unable to read alternates file"));
+				fclose(orig);
+				goto out;
+			}
+
+			fclose(orig);
+		} else if (errno != ENOENT) {
+			ret = error_errno(_("unable to read alternates file"));
+			goto out;
+		}
+
+		for (size_t i = 0; i < opts->alternates->nr; i++) {
+			const char *alternate = opts->alternates->v[i];
+			if (!strset_add(&seen, alternate))
+				continue;
+			fprintf(alternates, "%s\n", alternate);
+		}
+
+		if (ferror(alternates)) {
+			ret = error_errno(_("unable to write alternates file"));
+			goto out;
+		}
+
+		if (commit_lock_file(&alternates_lock)) {
+			ret = error_errno(_("unable to commit alternates file"));
+			goto out;
+		}
+	}
+
+	/* Reprepare the object database to activate alternates. */
+	odb_reprepare(source->odb);
+
+	ret = 0;
+
+out:
+	rollback_lock_file(&alternates_lock);
+	strbuf_release(&line);
 	strbuf_release(&path);
+	strset_clear(&seen);
+	return ret;
+}
+
+/*
+ * NEEDSWORK: we're using "core.ignoreCase" to deduplicate alternates that
+ * _may_ be the same. This requires quite a bit of boilerplate for dubious
+ * benefit:
+ *
+ *   - Duplicating alternates should really only lead to regressed performance.
+ *
+ *   - We don't properly resolve symlinks or mointpoints, so we may still end
+ *     up duplicating alternates.
+ *
+ *   - The value may be lying, in which case we might deduplicate alternates
+ *     that are in fact not mapping to the same directory.
+ *
+ * We should investigate whether we can remove this whole mechanism outright.
+ */
+static int odb_files_dir_paths_cmp(struct odb_source_files *files,
+				   const char *a, const char *b)
+{
+	if (files->dirs_paths_icase < 0) {
+		int icase = 0;
+		repo_config_get_bool(files->base.odb->repo, "core.ignorecase", &icase);
+		files->dirs_paths_icase = icase;
+	}
+
+	return files->dirs_paths_icase ? strcasecmp(a, b) : strcmp(a, b);
+}
+
+static int odb_files_dir_by_path_cmp(const void *cb_data,
+				     const struct hashmap_entry *entry,
+				     const struct hashmap_entry *entry_or_key,
+				     const void *keydata)
+{
+	struct odb_source_files *files = (struct odb_source_files *)cb_data;
+	const struct odb_files_dir *dir = container_of(entry, const struct odb_files_dir, by_path_entry);
+	const char *path = keydata;
+
+	if (!path)
+		path = container_of(entry_or_key, const struct odb_files_dir, by_path_entry)->abspath;
+
+	return odb_files_dir_paths_cmp(files, dir->abspath, path);
+}
+
+/*
+ * Return non-zero iff the path is usable as an alternate object directory.
+ */
+static bool odb_files_dir_is_usable(struct odb_source_files *files,
+				    const char *path)
+{
+	struct strbuf normalized_objdir = STRBUF_INIT;
+	struct hashmap_entry key;
+	bool usable = false;
+
+	strbuf_realpath(&normalized_objdir, files->dirs->abspath, 1);
+
+	/* Detect cases where alternate disappeared */
+	if (!is_directory(path)) {
+		error(_("object directory %s does not exist; "
+			"check .git/objects/info/alternates"),
+		      path);
+		goto out;
+	}
+
+	/*
+	 * Prevent the common mistake of listing the same
+	 * thing twice, or object directory itself.
+	 */
+	if (!hashmap_get_size(&files->dirs_by_path)) {
+		assert(!files->dirs->next);
+		hashmap_entry_init(&files->dirs->by_path_entry,
+				   strihash(files->dirs->abspath));
+		hashmap_add(&files->dirs_by_path, &files->dirs->by_path_entry);
+	}
+
+	if (!odb_files_dir_paths_cmp(files, path, normalized_objdir.buf))
+		goto out;
+
+	hashmap_entry_init(&key, strihash(path));
+	if (hashmap_get(&files->dirs_by_path, &key, path))
+		goto out;
+
+	usable = true;
+
+out:
+	strbuf_release(&normalized_objdir);
+	return usable;
+}
+
+static void parse_alternates(const char *string,
+			     int sep,
+			     const char *relative_base,
+			     struct strvec *out)
+{
+	struct strbuf pathbuf = STRBUF_INIT;
+	struct strbuf buf = STRBUF_INIT;
+
+	if (!string || !*string)
+		return;
+
+	while (*string) {
+		const char *end;
+
+		strbuf_reset(&buf);
+		strbuf_reset(&pathbuf);
+
+		if (*string == '#') {
+			/* comment; consume up to next separator */
+			end = strchrnul(string, sep);
+		} else if (*string == '"' && !unquote_c_style(&buf, string, &end)) {
+			/*
+			 * quoted path; unquote_c_style has copied the
+			 * data for us and set "end". Broken quoting (e.g.,
+			 * an entry that doesn't end with a quote) falls
+			 * back to the unquoted case below.
+			 */
+		} else {
+			/* normal, unquoted path */
+			end = strchrnul(string, sep);
+			strbuf_add(&buf, string, end - string);
+		}
+
+		if (*end)
+			end++;
+		string = end;
+
+		if (!buf.len)
+			continue;
+
+		if (!is_absolute_path(buf.buf) && relative_base) {
+			strbuf_realpath(&pathbuf, relative_base, 1);
+			strbuf_addch(&pathbuf, '/');
+		}
+		strbuf_addbuf(&pathbuf, &buf);
+
+		strbuf_reset(&buf);
+		if (!strbuf_realpath(&buf, pathbuf.buf, 0)) {
+			error(_("unable to normalize alternate object path: %s"),
+			      pathbuf.buf);
+			continue;
+		}
+
+		/*
+		 * The trailing slash after the directory name is given by
+		 * this function at the end. Remove duplicates.
+		 */
+		while (buf.len && buf.buf[buf.len - 1] == '/')
+			strbuf_setlen(&buf, buf.len - 1);
+
+		strvec_push(out, buf.buf);
+	}
+
+	strbuf_release(&pathbuf);
+	strbuf_release(&buf);
+}
+
+static int read_alternates(const char *object_dir, struct strvec *out)
+{
+	struct strbuf buf = STRBUF_INIT;
+	char *path;
+
+	path = xstrfmt("%s/info/alternates", object_dir);
+	if (strbuf_read_file(&buf, path, 1024) < 0) {
+		warn_on_fopen_errors(path);
+		free(path);
+		return 0;
+	}
+	parse_alternates(buf.buf, '\n', object_dir, out);
+
+	strbuf_release(&buf);
+	free(path);
 	return 0;
 }
 
+static void odb_add_alternate_recursively(struct odb_source_files *files,
+					  const char *path,
+					  int depth)
+{
+	struct odb_files_dir *alternate;
+	struct strvec alternates = STRVEC_INIT;
+
+	if (!odb_files_dir_is_usable(files, path))
+		goto out;
+
+	alternate = odb_files_dir_new(files->base.odb, path, false);
+
+	/* add the alternate entry */
+	*files->dirs_tail = alternate;
+	files->dirs_tail = &(alternate->next);
+
+	hashmap_entry_init(&alternate->by_path_entry, strihash(alternate->abspath));
+	if (hashmap_get(&files->dirs_by_path, &alternate->by_path_entry,
+			alternate->abspath))
+		BUG("object directory must not yet exist");
+	hashmap_add(&files->dirs_by_path, &alternate->by_path_entry);
+
+	/* recursively add alternates */
+	read_alternates(alternate->abspath, &alternates);
+	if (alternates.nr && depth + 1 > 5) {
+		error(_("%s: ignoring alternate object stores, nesting too deep"),
+		      path);
+	} else {
+		for (size_t i = 0; i < alternates.nr; i++)
+			odb_add_alternate_recursively(files, alternates.v[i], depth + 1);
+	}
+
+ out:
+	strvec_clear(&alternates);
+}
+
+static void odb_prepare_alternates(struct odb_source_files *files,
+				   const char *alternate_db)
+{
+	struct strvec alternates = STRVEC_INIT;
+
+	parse_alternates(alternate_db, PATH_SEP, NULL, &alternates);
+	read_alternates(files->dirs->abspath, &alternates);
+
+	for (size_t i = 0; i < alternates.nr; i++)
+		odb_add_alternate_recursively(files, alternates.v[i], 0);
+
+	strvec_clear(&alternates);
+}
+
 static void odb_source_files_prepare(struct odb_source *source,
 				     enum odb_prepare_flags flags)
 {
 	struct odb_source_files *files = odb_source_files_downcast(source);
-	odb_source_prepare(&files->loose->base, flags);
-	odb_source_prepare(&files->packed->base, flags);
+
+	/*
+	 * Reprepare alternates, in case the alternates file was modified
+	 * during the course of this process. This only _adds_ directories to
+	 * the linked list, so existing directories will continue to exist
+	 * for the lifetime of the process.
+	 */
+	if (flags & ODB_PREPARE_FLUSH_CACHES)
+		odb_prepare_alternates(files, NULL);
+
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		odb_source_prepare(&dir->loose->base, flags);
+		odb_source_prepare(&dir->packed->base, flags);
+	}
 }
 
 static enum odb_read_status odb_source_files_read_object_info(struct odb_source *source,
@@ -83,28 +425,33 @@ static enum odb_read_status odb_source_files_read_object_info(struct odb_source
 							      struct strbuf *errmsg)
 {
 	struct odb_source_files *files = odb_source_files_downcast(source);
-	enum odb_read_status ret_packed, ret_loose;
-
-	ret_packed = odb_source_read_object_info(&files->packed->base, oid, oi,
-						 flags, errmsg);
-	if (!ret_packed)
-		return 0;
-
-	ret_loose = odb_source_read_object_info(&files->loose->base, oid, oi, flags,
-						ret_packed == ODB_READ_NOT_FOUND ? errmsg : NULL);
-	if (!ret_loose)
-		return 0;
+	enum odb_read_status status = ODB_READ_NOT_FOUND;
 
 	/*
-	 * Reading the packed object may have failed even though the object
-	 * exists, for example because it is corrupt. Report this failure to
-	 * the caller in case neither of the sources was able to read the
-	 * object, and prefer the error of the packed source in case both
-	 * reads have failed.
+	 * Reading an object may fail even though the object exists, for
+	 * example because it is corrupt. Report this failure to the caller in
+	 * case none of the directories was able to read the object, and
+	 * prefer the first such error in case multiple reads have failed.
 	 */
-	if (ret_packed != ODB_READ_NOT_FOUND)
-		return ret_packed;
-	return ret_loose;
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		enum odb_read_status ret;
+
+		ret = odb_source_read_object_info(&dir->packed->base, oid, oi, flags,
+						  status == ODB_READ_NOT_FOUND ? errmsg : NULL);
+		if (!ret)
+			return 0;
+		if (ret != ODB_READ_NOT_FOUND && status == ODB_READ_NOT_FOUND)
+			status = ret;
+
+		ret = odb_source_read_object_info(&dir->loose->base, oid, oi, flags,
+						  status == ODB_READ_NOT_FOUND ? errmsg : NULL);
+		if (!ret)
+			return 0;
+		if (ret != ODB_READ_NOT_FOUND && status == ODB_READ_NOT_FOUND)
+			status = ret;
+	}
+
+	return status;
 }
 
 static int odb_source_files_read_object_stream(struct odb_stream **out,
@@ -112,9 +459,12 @@ static int odb_source_files_read_object_stream(struct odb_stream **out,
 					       const struct object_id *oid)
 {
 	struct odb_source_files *files = odb_source_files_downcast(source);
-	if (!odb_source_read_object_stream(out, &files->packed->base, oid) ||
-	    !odb_source_read_object_stream(out, &files->loose->base, oid))
-		return 0;
+
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next)
+		if (!odb_source_read_object_stream(out, &dir->packed->base, oid) ||
+		    !odb_source_read_object_stream(out, &dir->loose->base, oid))
+			return 0;
+
 	return -1;
 }
 
@@ -127,16 +477,21 @@ static int odb_source_files_for_each_object(struct odb_source *source,
 	struct odb_source_files *files = odb_source_files_downcast(source);
 	int ret;
 
-	if (!(opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) {
-		ret = odb_source_for_each_object(&files->loose->base, request, cb, cb_data, opts);
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		if (opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY && !dir->local)
+			continue;
+
+		if (!(opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) {
+			ret = odb_source_for_each_object(&dir->loose->base, request, cb, cb_data, opts);
+			if (ret)
+				return ret;
+		}
+
+		ret = odb_source_for_each_object(&dir->packed->base, request, cb, cb_data, opts);
 		if (ret)
 			return ret;
 	}
 
-	ret = odb_source_for_each_object(&files->packed->base, request, cb, cb_data, opts);
-	if (ret)
-		return ret;
-
 	return 0;
 }
 
@@ -145,21 +500,23 @@ static int odb_source_files_count_objects(struct odb_source *source,
 					  unsigned long *out)
 {
 	struct odb_source_files *files = odb_source_files_downcast(source);
-	unsigned long count;
+	unsigned long count = 0;
 	int ret;
 
-	ret = odb_source_count_objects(&files->packed->base, flags, &count);
-	if (ret < 0)
-		goto out;
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		unsigned long dir_count;
 
-	if (!(flags & ODB_COUNT_OBJECTS_APPROXIMATE)) {
-		unsigned long loose_count;
-
-		ret = odb_source_count_objects(&files->loose->base, flags, &loose_count);
+		ret = odb_source_count_objects(&dir->packed->base, flags, &dir_count);
 		if (ret < 0)
 			goto out;
+		count += dir_count;
 
-		count += loose_count;
+		if (!(flags & ODB_COUNT_OBJECTS_APPROXIMATE)) {
+			ret = odb_source_count_objects(&dir->loose->base, flags, &dir_count);
+			if (ret < 0)
+				goto out;
+			count += dir_count;
+		}
 	}
 
 	*out = count;
@@ -176,15 +533,17 @@ static int odb_source_files_find_abbrev_len(struct odb_source *source,
 {
 	struct odb_source_files *files = odb_source_files_downcast(source);
 	unsigned len = min_len;
-	int ret;
+	int ret = 0;
 
-	ret = odb_source_find_abbrev_len(&files->packed->base, oid, len, &len);
-	if (ret < 0)
-		goto out;
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		ret = odb_source_find_abbrev_len(&dir->packed->base, oid, len, &len);
+		if (ret < 0)
+			goto out;
 
-	ret = odb_source_find_abbrev_len(&files->loose->base, oid, len, &len);
-	if (ret < 0)
-		goto out;
+		ret = odb_source_find_abbrev_len(&dir->loose->base, oid, len, &len);
+		if (ret < 0)
+			goto out;
+	}
 
 	*out = len;
 	ret = 0;
@@ -198,9 +557,12 @@ static int odb_source_files_freshen_object(struct odb_source *source,
 					   const time_t *mtime)
 {
 	struct odb_source_files *files = odb_source_files_downcast(source);
-	if (odb_source_freshen_object(&files->packed->base, oid, mtime) ||
-	    odb_source_freshen_object(&files->loose->base, oid, mtime))
-		return 1;
+
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next)
+		if (odb_source_freshen_object(&dir->packed->base, oid, mtime) ||
+		    odb_source_freshen_object(&dir->loose->base, oid, mtime))
+			return 1;
+
 	return 0;
 }
 
@@ -213,7 +575,7 @@ static int odb_source_files_write_object(struct odb_source *source,
 					 enum odb_write_object_flags flags)
 {
 	struct odb_source_files *files = odb_source_files_downcast(source);
-	return odb_source_write_object(&files->loose->base, buf, len, type,
+	return odb_source_write_object(&files->dirs->loose->base, buf, len, type,
 				       oid, compat_oid, mtime, flags);
 }
 
@@ -222,7 +584,7 @@ static int odb_source_files_write_object_stream(struct odb_source *source,
 						struct object_id *oid)
 {
 	struct odb_source_files *files = odb_source_files_downcast(source);
-	return odb_source_write_object_stream(&files->loose->base, stream, oid);
+	return odb_source_write_object_stream(&files->dirs->loose->base, stream, oid);
 }
 
 static int odb_source_files_begin_transaction(struct odb_source *source,
@@ -232,78 +594,6 @@ static int odb_source_files_begin_transaction(struct odb_source *source,
 	return odb_transaction_files_begin(source, out, flags);
 }
 
-static int odb_source_files_read_alternates(struct odb_source *source,
-					    struct strvec *out)
-{
-	struct strbuf buf = STRBUF_INIT;
-	char *path;
-
-	path = xstrfmt("%s/info/alternates", source->path);
-	if (strbuf_read_file(&buf, path, 1024) < 0) {
-		warn_on_fopen_errors(path);
-		free(path);
-		return 0;
-	}
-	parse_alternates(buf.buf, '\n', source->path, out);
-
-	strbuf_release(&buf);
-	free(path);
-	return 0;
-}
-
-static int odb_source_files_write_alternate(struct odb_source *source,
-					    const char *alternate)
-{
-	struct lock_file lock = LOCK_INIT;
-	char *path = xstrfmt("%s/%s", source->path, "info/alternates");
-	FILE *in, *out;
-	int found = 0;
-	int ret;
-
-	repo_hold_lock_file_for_update(source->odb->repo, &lock, path,
-				       LOCK_DIE_ON_ERROR);
-	out = fdopen_lock_file(&lock, "w");
-	if (!out) {
-		ret = error_errno(_("unable to fdopen alternates lockfile"));
-		goto out;
-	}
-
-	in = fopen(path, "r");
-	if (in) {
-		struct strbuf line = STRBUF_INIT;
-
-		while (strbuf_getline(&line, in) != EOF) {
-			if (!strcmp(alternate, line.buf)) {
-				found = 1;
-				break;
-			}
-			fprintf_or_die(out, "%s\n", line.buf);
-		}
-
-		strbuf_release(&line);
-		fclose(in);
-	} else if (errno != ENOENT) {
-		ret = error_errno(_("unable to read alternates file"));
-		goto out;
-	}
-
-	if (found) {
-		rollback_lock_file(&lock);
-	} else {
-		fprintf_or_die(out, "%s\n", alternate);
-		if (commit_lock_file(&lock)) {
-			ret = error_errno(_("unable to move new alternates file into place"));
-			goto out;
-		}
-	}
-
-	ret = 0;
-
-out:
-	free(path);
-	return ret;
-}
-
 static int too_many_loose_objects(struct odb_source_files *files, int limit)
 {
 	unsigned long loose_count;
@@ -311,7 +601,7 @@ static int too_many_loose_objects(struct odb_source_files *files, int limit)
 	if (limit <= 0)
 		return 0;
 
-	if (odb_source_count_objects(&files->loose->base, ODB_COUNT_OBJECTS_APPROXIMATE,
+	if (odb_source_count_objects(&files->dirs->loose->base, ODB_COUNT_OBJECTS_APPROXIMATE,
 				     &loose_count) < 0)
 		return 0;
 
@@ -330,7 +620,7 @@ static struct packed_git *find_base_packs(struct odb_source_files *files,
 	struct packfile_list_entry *e;
 	struct packed_git *base = NULL;
 
-	for (e = packfile_store_get_packs(files->packed); e; e = e->next) {
+	for (e = packfile_store_get_packs(files->dirs->packed); e; e = e->next) {
 		if (e->pack->is_cruft)
 			continue;
 		if (limit) {
@@ -355,7 +645,7 @@ static int too_many_packs(struct odb_source_files *files, int gc_auto_pack_limit
 	if (gc_auto_pack_limit <= 0)
 		return 0;
 
-	for (e = packfile_store_get_packs(files->packed); e; e = e->next) {
+	for (e = packfile_store_get_packs(files->dirs->packed); e; e = e->next) {
 		if (e->pack->pack_keep)
 			continue;
 		/*
@@ -915,25 +1205,57 @@ static int odb_source_files_fsck(struct odb_source *source,
 	struct odb_source_files *files = odb_source_files_downcast(source);
 	int ret = 0;
 
-	if (!(opts->flags & ODB_FSCK_FULL) && !source->local)
-		return 0;
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		if (!(opts->flags & ODB_FSCK_FULL) && !dir->local)
+			continue;
 
-	ret |= odb_source_fsck(&files->loose->base, opts);
-	ret |= odb_source_fsck(&files->packed->base, opts);
+		ret |= odb_source_fsck(&dir->loose->base, opts);
+		ret |= odb_source_fsck(&dir->packed->base, opts);
+	}
 
 	return ret;
 }
 
+struct odb_files_dir *odb_source_files_find_dir(struct object_database *odb, const char *obj_dir)
+{
+	struct odb_source_files *files = odb_source_files_downcast(odb->source);
+	char *obj_dir_real = real_pathdup(obj_dir, 1);
+	struct strbuf odb_path_real = STRBUF_INIT;
+	struct odb_files_dir *dir;
+
+	for (dir = files->dirs; dir; dir = dir->next) {
+		strbuf_realpath(&odb_path_real, dir->abspath, 1);
+		if (!strcmp(obj_dir_real, odb_path_real.buf))
+			break;
+	}
+
+	free(obj_dir_real);
+	strbuf_release(&odb_path_real);
+	return dir;
+}
+
 struct odb_source_files *odb_source_files_new(struct object_database *odb,
-					      const char *path,
-					      bool local)
+					      enum odb_new_flags flags)
 {
 	struct odb_source_files *files;
+	char *object_dir = NULL;
+	char *alternates = NULL;
+
+	if (flags & ODB_NEW_HONOR_ENV) {
+		object_dir = xstrdup_or_null(getenv(DB_ENVIRONMENT));
+		alternates = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT));
+	}
+	if (!object_dir)
+		object_dir = xstrfmt("%s/objects", odb->repo->commondir);
 
 	CALLOC_ARRAY(files, 1);
-	odb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local);
-	files->loose = odb_source_loose_new(odb, path, local);
-	files->packed = odb_source_packed_new(odb, path, local);
+	odb_source_init(&files->base, odb, ODB_SOURCE_FILES, object_dir, true);
+
+	hashmap_init(&files->dirs_by_path, odb_files_dir_by_path_cmp, files, 0);
+	files->dirs_paths_icase = -1;
+
+	files->dirs = odb_files_dir_new(odb, object_dir, true);
+	files->dirs_tail = &files->dirs->next;
 
 	files->base.free = odb_source_files_free;
 	files->base.close = odb_source_files_close;
@@ -949,8 +1271,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
 	files->base.write_object = odb_source_files_write_object;
 	files->base.write_object_stream = odb_source_files_write_object_stream;
 	files->base.begin_transaction = odb_source_files_begin_transaction;
-	files->base.read_alternates = odb_source_files_read_alternates;
-	files->base.write_alternate = odb_source_files_write_alternate;
 	files->base.optimize = odb_source_files_optimize;
 	files->base.optimize_required = odb_source_files_optimize_required;
 	files->base.generate_pack = odb_source_files_generate_pack;
@@ -960,8 +1280,12 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
 	 * is not (yet) possible though because we access and assume relative
 	 * paths in the primary ODB source in some user-facing functionality.
 	 */
-	if (!is_absolute_path(path))
+	if (!is_absolute_path(object_dir))
 		chdir_notify_register(odb_source_files_reparent, files);
 
+	odb_prepare_alternates(files, alternates);
+
+	free(object_dir);
+	free(alternates);
 	return files;
 }
diff --git a/odb/source-files.h b/odb/source-files.h
index 9630b5f..837e890 100644
--- a/odb/source-files.h
+++ b/odb/source-files.h
@@ -7,19 +7,70 @@ struct odb_source_loose;
 struct odb_source_packed;
 
 /*
+ * A single object directory that encapsulates access to both the loose and
+ * packed backend. This can either be the primary or an alternate object
+ * directory.
+ */
+struct odb_files_dir {
+	/* Absolute path to the object directory. */
+	char *abspath;
+
+	/* List of alternate object directories. */
+	struct odb_files_dir *next;
+
+	/*
+	 * Entry in the files source's map of directories, keyed by this
+	 * directory's path.
+	 */
+	struct hashmap_entry by_path_entry;
+
+	/* The two sources derived from this object directory. */
+	struct odb_source_loose *loose;
+	struct odb_source_packed *packed;
+
+	/*
+	 * Whether this is the local object directory of the owning
+	 * repository. Directories added via alternates are not local.
+	 */
+	bool local;
+};
+
+struct odb_files_dir *odb_files_dir_new(struct object_database *odb,
+					const char *path, bool local);
+void odb_files_dir_free(struct odb_files_dir *dir);
+
+/*
  * The files object database source uses a combination of loose objects and
  * packfiles. It is the default backend used by Git to store objects.
  */
 struct odb_source_files {
 	struct odb_source base;
-	struct odb_source_loose *loose;
-	struct odb_source_packed *packed;
+
+	/*
+	 * List of all object directories; the main directory is first (and
+	 * cannot be NULL after initialization). Subsequent directories are
+	 * alternates.
+	 */
+	struct odb_files_dir *dirs;
+	struct odb_files_dir **dirs_tail;
+
+	/*
+	 * Map of object directories, keyed by their respective paths. This
+	 * map is used to detect the case where the same directory is
+	 * registered multiple times.
+	 */
+	struct hashmap dirs_by_path;
+
+	/*
+	 * Whether directory paths shall be compared case-insensitively, as
+	 * determined by "core.ignoreCase".
+	 */
+	int dirs_paths_icase;
 };
 
 /* Allocate and initialize a new object source. */
 struct odb_source_files *odb_source_files_new(struct object_database *odb,
-					      const char *path,
-					      bool local);
+					      enum odb_new_flags flags);
 
 /*
  * Optimize the files object database source by repacking loose objects and
@@ -49,4 +100,10 @@ static inline struct odb_source_files *odb_source_files_downcast(struct odb_sour
 	return container_of(source, struct odb_source_files, base);
 }
 
+/*
+ * Find "files" directory by its object directory path. Returns a `NULL`
+ * pointer in case the object directory could not be found.
+ */
+struct odb_files_dir *odb_source_files_find_dir(struct object_database *odb, const char *obj_dir);
+
 #endif
diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c
index 473085f..868e723 100644
--- a/odb/source-inmemory.c
+++ b/odb/source-inmemory.c
@@ -322,18 +322,6 @@ static int odb_source_inmemory_begin_transaction(struct odb_source *source UNUSE
 	return error("in-memory source does not support transactions");
 }
 
-static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,
-					       struct strvec *out UNUSED)
-{
-	return 0;
-}
-
-static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,
-					       const char *alternate UNUSED)
-{
-	return error("in-memory source does not support alternates");
-}
-
 static void odb_source_inmemory_close(struct odb_source *source UNUSED)
 {
 }
@@ -396,8 +384,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)
 	source->base.write_object_stream = odb_source_inmemory_write_object_stream;
 	source->base.freshen_object = odb_source_inmemory_freshen_object;
 	source->base.begin_transaction = odb_source_inmemory_begin_transaction;
-	source->base.read_alternates = odb_source_inmemory_read_alternates;
-	source->base.write_alternate = odb_source_inmemory_write_alternate;
 
 	return source;
 }
diff --git a/odb/source-loose.c b/odb/source-loose.c
index a7e1625..0664634 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -599,7 +599,7 @@ static int odb_source_loose_freshen_object(struct odb_source *source,
 static void close_loose_object(struct odb_source_loose *loose,
 			       int fd, const char *filename)
 {
-	if (loose->base.will_destroy)
+	if (loose->will_destroy)
 		goto out;
 
 	if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
@@ -985,18 +985,6 @@ static int odb_source_loose_begin_transaction(struct odb_source *source UNUSED,
 	return error("loose source does not support transactions");
 }
 
-static int odb_source_loose_read_alternates(struct odb_source *source UNUSED,
-					    struct strvec *out UNUSED)
-{
-	return 0;
-}
-
-static int odb_source_loose_write_alternate(struct odb_source *source UNUSED,
-					    const char *alternate UNUSED)
-{
-	return error("loose source does not support alternates");
-}
-
 static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
 {
 	oidtree_clear(loose->cache);
@@ -1151,8 +1139,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
 	loose->base.write_object = odb_source_loose_write_object;
 	loose->base.write_object_stream = odb_source_loose_write_object_stream;
 	loose->base.begin_transaction = odb_source_loose_begin_transaction;
-	loose->base.read_alternates = odb_source_loose_read_alternates;
-	loose->base.write_alternate = odb_source_loose_write_alternate;
 
 	if (!is_absolute_path(loose->base.path))
 		chdir_notify_register(odb_source_loose_reparent, loose);
diff --git a/odb/source-loose.h b/odb/source-loose.h
index 3cf2e1f..7c7e845 100644
--- a/odb/source-loose.h
+++ b/odb/source-loose.h
@@ -28,6 +28,9 @@ struct odb_source_loose {
 
 	/* Map between object IDs for loose objects. */
 	struct loose_object_map *map;
+
+	/* Whether this is a source that will never be committed to disk. */
+	int will_destroy;
 };
 
 struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
diff --git a/odb/source-packed.c b/odb/source-packed.c
index a26c323..2226fbd 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -687,18 +687,6 @@ static int odb_source_packed_begin_transaction(struct odb_source *source UNUSED,
 	return error("packed backend cannot begin transactions");
 }
 
-static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,
-					     struct strvec *out UNUSED)
-{
-	return 0;
-}
-
-static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,
-					     const char *alternate UNUSED)
-{
-	return error("packed backend cannot write alternates");
-}
-
 void (*report_garbage)(unsigned seen_bits, const char *path);
 
 static void report_helper(const struct string_list *list,
@@ -1025,8 +1013,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,
 	packed->base.write_object = odb_source_packed_write_object;
 	packed->base.write_object_stream = odb_source_packed_write_object_stream;
 	packed->base.begin_transaction = odb_source_packed_begin_transaction;
-	packed->base.read_alternates = odb_source_packed_read_alternates;
-	packed->base.write_alternate = odb_source_packed_write_alternate;
 
 	if (!is_absolute_path(path))
 		chdir_notify_register(odb_source_packed_reparent, packed);
diff --git a/odb/source.c b/odb/source.c
index 30188b8..b25ef14 100644
--- a/odb/source.c
+++ b/odb/source.c
@@ -24,10 +24,9 @@ const char *odb_source_type_to_name(enum odb_source_type type)
 }
 
 struct odb_source *odb_source_new(struct object_database *odb,
-				  const char *path,
-				  bool local)
+				  enum odb_new_flags flags)
 {
-	return &odb_source_files_new(odb, path, local)->base;
+	return &odb_source_files_new(odb, flags)->base;
 }
 
 void odb_source_init(struct odb_source *source,
diff --git a/odb/source.h b/odb/source.h
index 072e771..1867347 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -37,27 +37,21 @@ struct odb_stream;
 struct strbuf;
 struct strvec;
 
+struct odb_create_on_disk_options {
+	/*
+	 * Alternates that shall be written into the newly created object
+	 * database. Whether or not this option can be handled is specific to
+	 * the backend.
+	 */
+	const struct strvec *alternates;
+};
+
 /*
  * The source is the part of the object database that stores the actual
  * objects. It thus encapsulates the logic to read and write the specific
- * on-disk format. An object database can have multiple sources:
- *
- *   - The primary source, which is typically located in "$GIT_DIR/objects".
- *     This is where new objects are usually written to.
- *
- *   - Alternate sources, which are configured via "objects/info/alternates" or
- *     via the GIT_ALTERNATE_OBJECT_DIRECTORIES environment variable. These
- *     alternate sources are only used to read objects.
+ * on-disk format.
  */
 struct odb_source {
-	struct odb_source *next;
-
-	/*
-	 * Entry in the object database's map of sources, keyed by this
-	 * source's path.
-	 */
-	struct hashmap_entry by_path_entry;
-
 	/* Object database that owns this object source. */
 	struct object_database *odb;
 
@@ -73,11 +67,6 @@ struct odb_source {
 	bool local;
 
 	/*
-	 * This object store is ephemeral, so there is no need to fsync.
-	 */
-	int will_destroy;
-
-	/*
 	 * Path to the source. If this is a relative path, it is relative to
 	 * the current working directory.
 	 */
@@ -107,7 +96,8 @@ struct odb_source {
 	 * This callback may be NULL in case the source does not need any
 	 * on-disk setup.
 	 */
-	int (*create_on_disk)(struct odb_source *source);
+	int (*create_on_disk)(struct odb_source *source,
+			      const struct odb_create_on_disk_options *opts);
 
 	/*
 	 * This callback is expected to prepare the source so that it becomes
@@ -269,32 +259,6 @@ struct odb_source {
 				 enum odb_transaction_flags flags);
 
 	/*
-	 * This callback is expected to read the list of alternate object
-	 * database sources connected to it and write them into the `strvec`.
-	 *
-	 * The result is expected to be paths to the alternates. All paths must
-	 * be resolved to absolute paths.
-	 *
-	 * The callback is expected to return 0 on success, a negative error
-	 * code otherwise.
-	 */
-	int (*read_alternates)(struct odb_source *source,
-			       struct strvec *out);
-
-	/*
-	 * This callback is expected to persist the singular alternate passed
-	 * to it into its list of alternates. Any pre-existing alternates are
-	 * expected to remain active. Subsequent calls to `read_alternates` are
-	 * thus expected to yield the pre-existing list of alternates plus the
-	 * newly added alternate appended to its end.
-	 *
-	 * The callback is expected to return 0 on success, a negative error
-	 * code otherwise.
-	 */
-	int (*write_alternate)(struct odb_source *source,
-			       const char *alternate);
-
-	/*
 	 * This callback is expected to optimize the object database source.
 	 * Returns 0 on success, a negative error code otherwise.
 	 */
@@ -339,13 +303,11 @@ struct odb_source {
 };
 
 /*
- * Allocate and initialize a new source for the given object database located
- * at `path`. `local` indicates whether or not the source is the local and thus
- * primary object source of the object database.
+ * Allocate and initialize a new source for the given object database. The path
+ * of the source is derived from repository paths.
  */
 struct odb_source *odb_source_new(struct object_database *odb,
-				  const char *path,
-				  bool local);
+				  enum odb_new_flags flags);
 
 /*
  * Initialize the source for the given object database located at `path`.
@@ -389,11 +351,12 @@ static inline void odb_source_close(struct odb_source *source)
  * Create on-disk data structures that are required for this source to operate
  * correctly. Returns 0 on success, a negative error code otherwise.
  */
-static inline int odb_source_create_on_disk(struct odb_source *source)
+static inline int odb_source_create_on_disk(struct odb_source *source,
+					    const struct odb_create_on_disk_options *opts)
 {
 	if (!source->create_on_disk)
 		return 0;
-	return source->create_on_disk(source);
+	return source->create_on_disk(source, opts);
 }
 
 /*
@@ -532,33 +495,6 @@ static inline int odb_source_write_object_stream(struct odb_source *source,
 }
 
 /*
- * Read the list of alternative object database sources from the given backend
- * and populate the `strvec` with them. The listing is not recursive -- that
- * is, if any of the yielded alternate sources has alternates itself, those
- * will not be yielded as part of this function call.
- *
- * Return 0 on success, a negative error code otherwise.
- */
-static inline int odb_source_read_alternates(struct odb_source *source,
-					     struct strvec *out)
-{
-	return source->read_alternates(source, out);
-}
-
-/*
- * Write and persist a new alternate object database source for the given
- * source. Any preexisting alternates are expected to stay valid, and the new
- * alternate shall be appended to the end of the list.
- *
- * Returns 0 on success, a negative error code otherwise.
- */
-static inline int odb_source_write_alternate(struct odb_source *source,
-					      const char *alternate)
-{
-	return source->write_alternate(source, alternate);
-}
-
-/*
  * Create a new transaction that can be used to write objects into a temporary
  * staging area. The objects will only be persisted when the transaction is
  * committed.
diff --git a/odb/streaming.c b/odb/streaming.c
index 8f2143c..5ac172b 100644
--- a/odb/streaming.c
+++ b/odb/streaming.c
@@ -182,12 +182,8 @@ static int istream_source(struct odb_stream **out,
 			  struct object_database *odb,
 			  const struct object_id *oid)
 {
-	struct odb_source *source;
-
-	for (source = odb->sources; source; source = source->next)
-		if (!odb_source_read_object_stream(out, source, oid))
-			return 0;
-
+	if (!odb_source_read_object_stream(out, odb->source, oid))
+		return 0;
 	return open_istream_incore(out, odb, oid);
 }
 
diff --git a/odb/transaction.c b/odb/transaction.c
index f6f2008..69824a3 100644
--- a/odb/transaction.c
+++ b/odb/transaction.c
@@ -12,7 +12,7 @@ int odb_transaction_begin(struct object_database *odb,
 	if (odb->transaction)
 		return error(_("object database transaction already pending"));
 
-	ret = odb_source_begin_transaction(odb->sources, out, flags);
+	ret = odb_source_begin_transaction(odb->source, out, flags);
 	if (!ret)
 		odb->transaction = *out;
 
diff --git a/oss-fuzz/fuzz-reftable.c b/oss-fuzz/fuzz-reftable.c
index c46eac2..75b8ad0 100644
--- a/oss-fuzz/fuzz-reftable.c
+++ b/oss-fuzz/fuzz-reftable.c
@@ -33,10 +33,11 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
 		struct reftable_ref_record ref = { 0 };
 		struct reftable_iterator it = { 0 };
 
-		reftable_table_init_ref_iterator(table, &it);
-		if (!reftable_iterator_seek_ref(&it, ""))
-			while (!reftable_iterator_next_ref(&it, &ref))
-				;
+		if (!reftable_table_init_ref_iterator(table, &it)) {
+			if (!reftable_iterator_seek_ref(&it, ""))
+				while (!reftable_iterator_next_ref(&it, &ref))
+					;
+		}
 
 		reftable_ref_record_release(&ref);
 		reftable_iterator_destroy(&it);
@@ -46,10 +47,11 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
 		struct reftable_log_record log = { 0 };
 		struct reftable_iterator it = { 0 };
 
-		reftable_table_init_log_iterator(table, &it);
-		if (!reftable_iterator_seek_log(&it, ""))
-			while (!reftable_iterator_next_log(&it, &log))
-				;
+		if (!reftable_table_init_log_iterator(table, &it)) {
+			if (!reftable_iterator_seek_log(&it, ""))
+				while (!reftable_iterator_next_log(&it, &log))
+					;
+		}
 
 		reftable_log_record_release(&log);
 		reftable_iterator_destroy(&it);
diff --git a/pack-bitmap.c b/pack-bitmap.c
index 3de8e95..d3c7997 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -712,15 +712,13 @@ static int open_bitmap_for_source(struct odb_source_packed *source,
 static int open_bitmap(struct repository *r,
 		       struct bitmap_index *bitmap_git)
 {
-	struct odb_source *source;
+	struct odb_source_files *files = odb_source_files_downcast(r->objects->source);
 	bool found = false;
 
 	assert(!bitmap_git->map);
 
-	for (source = r->objects->sources; source; source = source->next) {
-		struct odb_source_files *files = odb_source_files_downcast(source);
-
-		if (!open_bitmap_for_source(files->packed, bitmap_git))
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		if (!open_bitmap_for_source(dir->packed, bitmap_git))
 			found = true;
 
 		/*
diff --git a/packfile.c b/packfile.c
index 4fa5fd6..b32b4cd 100644
--- a/packfile.c
+++ b/packfile.c
@@ -273,14 +273,13 @@ static void scan_windows(struct packed_git *p,
 
 static int unuse_one_window(struct object_database *odb)
 {
-	struct odb_source *source;
+	struct odb_source_files *files = odb_source_files_downcast(odb->source);
 	struct packfile_list_entry *e;
 	struct packed_git *lru_p = NULL;
 	struct pack_window *lru_w = NULL, *lru_l = NULL;
 
-	for (source = odb->sources; source; source = source->next) {
-		struct odb_source_files *files = odb_source_files_downcast(source);
-		for (e = files->packed->packs.head; e; e = e->next)
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		for (e = dir->packed->packs.head; e; e = e->next)
 			scan_windows(e->pack, &lru_p, &lru_w, &lru_l);
 	}
 
@@ -355,16 +354,6 @@ static void close_pack_mtimes(struct packed_git *p)
 	p->mtimes_map = NULL;
 }
 
-void close_pack(struct packed_git *p)
-{
-	close_pack_windows(p);
-	close_pack_fd(p);
-	close_pack_index(p);
-	close_pack_revindex(p);
-	close_pack_mtimes(p);
-	oidset_clear(&p->bad_objects);
-}
-
 void unlink_pack_path(const char *pack_name, int force_delete)
 {
 	static const char *exts[] = {".idx", ".pack", ".rev", ".keep", ".bitmap", ".promisor", ".mtimes"};
@@ -450,15 +439,14 @@ static void find_lru_pack(struct packed_git *p, struct packed_git **lru_p, struc
 
 static int close_one_pack(struct repository *r)
 {
-	struct odb_source *source;
+	struct odb_source_files *files = odb_source_files_downcast(r->objects->source);
 	struct packfile_list_entry *e;
 	struct packed_git *lru_p = NULL;
 	struct pack_window *mru_w = NULL;
 	int accept_windows_inuse = 1;
 
-	for (source = r->objects->sources; source; source = source->next) {
-		struct odb_source_files *files = odb_source_files_downcast(source);
-		for (e = files->packed->packs.head; e; e = e->next) {
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		for (e = dir->packed->packs.head; e; e = e->next) {
 			if (e->pack->pack_fd == -1)
 				continue;
 			find_lru_pack(e->pack, &lru_p, &mru_w, &accept_windows_inuse);
@@ -1263,6 +1251,29 @@ void clear_delta_base_cache(void)
 	}
 }
 
+static void delta_base_cache_evict_entry(struct packed_git *p)
+{
+	struct list_head *lru, *tmp;
+
+	list_for_each_safe(lru, tmp, &delta_base_cache_lru) {
+		struct delta_base_cache_entry *entry =
+			list_entry(lru, struct delta_base_cache_entry, lru);
+		if (entry->key.p == p)
+			release_delta_base_cache(entry);
+	}
+}
+
+void close_pack(struct packed_git *p)
+{
+	close_pack_windows(p);
+	close_pack_fd(p);
+	close_pack_index(p);
+	close_pack_revindex(p);
+	close_pack_mtimes(p);
+	oidset_clear(&p->bad_objects);
+	delta_base_cache_evict_entry(p);
+}
+
 static void add_delta_base_cache(struct packed_git *p, off_t base_offset,
 				 void *base, size_t base_size,
 				 size_t delta_base_cache_limit,
@@ -1921,11 +1932,10 @@ struct packed_git **packfile_store_get_kept_pack_cache(struct odb_source_packed
 
 int has_object_pack(struct repository *r, const struct object_id *oid)
 {
-	struct odb_source *source;
+	struct odb_source_files *files = odb_source_files_downcast(r->objects->source);
 
-	for (source = r->objects->sources; source; source = source->next) {
-		struct odb_source_files *files = odb_source_files_downcast(source);
-		if (!odb_source_read_object_info(&files->packed->base, oid, NULL, 0, NULL))
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		if (!odb_source_read_object_info(&dir->packed->base, oid, NULL, 0, NULL))
 			return 1;
 	}
 
@@ -1935,14 +1945,13 @@ int has_object_pack(struct repository *r, const struct object_id *oid)
 int has_object_kept_pack(struct repository *r, const struct object_id *oid,
 			 unsigned flags)
 {
-	struct odb_source *source;
+	struct odb_source_files *files = odb_source_files_downcast(r->objects->source);
 	struct pack_entry e;
 
-	for (source = r->objects->sources; source; source = source->next) {
-		struct odb_source_files *files = odb_source_files_downcast(source);
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
 		struct packed_git **cache;
 
-		cache = packfile_store_get_kept_pack_cache(files->packed, flags);
+		cache = packfile_store_get_kept_pack_cache(dir->packed, flags);
 
 		for (; *cache; cache++) {
 			struct packed_git *p = *cache;
diff --git a/packfile.h b/packfile.h
index 6d30d15..ed3378c 100644
--- a/packfile.h
+++ b/packfile.h
@@ -69,20 +69,20 @@ void packfile_store_add_pack(struct odb_source_packed *store,
 struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *store);
 
 struct repo_for_each_pack_data {
-	struct odb_source *source;
+	struct odb_files_dir *dir;
 	struct packfile_list_entry *entry;
 };
 
 static inline struct repo_for_each_pack_data repo_for_eack_pack_data_init(struct repository *repo)
 {
 	struct repo_for_each_pack_data data = { 0 };
+	struct odb_source_files *files = odb_source_files_downcast(repo->objects->source);
 
-	for (struct odb_source *source = repo->objects->sources; source; source = source->next) {
-		struct odb_source_files *files = odb_source_files_downcast(source);
-		struct packfile_list_entry *entry = packfile_store_get_packs(files->packed);
+	for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
+		struct packfile_list_entry *entry = packfile_store_get_packs(dir->packed);
 		if (!entry)
 			continue;
-		data.source = source;
+		data.dir = dir;
 		data.entry = entry;
 		break;
 	}
@@ -92,23 +92,22 @@ static inline struct repo_for_each_pack_data repo_for_eack_pack_data_init(struct
 
 static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data *data)
 {
-	struct odb_source *source;
+	struct odb_files_dir *dir;
 
 	data->entry = data->entry->next;
 	if (data->entry)
 		return;
 
-	for (source = data->source->next; source; source = source->next) {
-		struct odb_source_files *files = odb_source_files_downcast(source);
-		struct packfile_list_entry *entry = packfile_store_get_packs(files->packed);
+	for (dir = data->dir->next; dir; dir = dir->next) {
+		struct packfile_list_entry *entry = packfile_store_get_packs(dir->packed);
 		if (!entry)
 			continue;
-		data->source = source;
+		data->dir = dir;
 		data->entry = entry;
 		return;
 	}
 
-	data->source = NULL;
+	data->dir = NULL;
 	data->entry = NULL;
 }
 
diff --git a/parse-options.c b/parse-options.c
index 4519ead..283a0f1 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -845,6 +845,7 @@ static int show_gitcomp(const struct option *opts, int show_all)
 {
 	const struct option *original_opts = opts;
 	int nr_noopts = 0;
+	bool shown = false;
 
 	for (; opts->type != OPTION_END; opts++) {
 		const char *prefix = "--";
@@ -882,8 +883,9 @@ static int show_gitcomp(const struct option *opts, int show_all)
 			suffix = "=";
 		if (starts_with(opts->long_name, "no-"))
 			nr_noopts++;
-		printf("%s%s%s%s", opts == original_opts ? "" : " ",
+		printf("%s%s%s%s", shown ? " " : "",
 		       prefix, opts->long_name, suffix);
+		shown = true;
 	}
 	show_negated_gitcomp(original_opts, show_all, -1);
 	show_negated_gitcomp(original_opts, show_all, nr_noopts);
@@ -925,6 +927,7 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx,
 		const char *long_name;
 		const char *source;
 		struct strbuf help = STRBUF_INIT;
+		enum parse_opt_option_flags flags;
 		int j;
 
 		if (newopt[i].type != OPTION_ALIAS)
@@ -933,6 +936,7 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx,
 		short_name = newopt[i].short_name;
 		long_name = newopt[i].long_name;
 		source = newopt[i].value;
+		flags = newopt[i].flags;
 
 		if (!long_name)
 			BUG("An alias must have long option name");
@@ -951,7 +955,7 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx,
 			newopt[i].short_name = short_name;
 			newopt[i].long_name = long_name;
 			newopt[i].help = strbuf_detach(&help, NULL);
-			newopt[i].flags |= PARSE_OPT_FROM_ALIAS;
+			newopt[i].flags |= flags | PARSE_OPT_FROM_ALIAS;
 			break;
 		}
 
@@ -1321,6 +1325,100 @@ static const struct option *find_option_by_long_name(const struct option *opts,
 	return NULL;
 }
 
+static int usage_print_flag(const struct option *opt,
+			    FILE *outfile,
+			    const char **positive_name)
+{
+	int off = 0;
+
+	if (opt->short_name) {
+		if (opt->flags & PARSE_OPT_NODASH)
+			off += fprintf(outfile, "%c", opt->short_name);
+		else
+			off += fprintf(outfile, "-%c", opt->short_name);
+	}
+	if (opt->long_name && opt->short_name)
+		off += fprintf(outfile, ", ");
+	if (opt->long_name) {
+		const char *long_name = opt->long_name;
+		if ((opt->flags & PARSE_OPT_NONEG) ||
+		    skip_prefix(long_name, "no-", positive_name))
+			off += fprintf(outfile, "--%s", long_name);
+		else
+			off += fprintf(outfile, "--[no-]%s", long_name);
+	}
+
+	if (opt->type == OPTION_NUMBER)
+		off += utf8_fprintf(outfile, _("-NUM"));
+
+	if ((opt->flags & PARSE_OPT_LITERAL_ARGHELP) ||
+	    !(opt->flags & PARSE_OPT_NOARG))
+		off += usage_argh(opt, outfile);
+
+	return off;
+}
+
+static void usage_print_option(const struct option *opt,
+			       const struct option *all_opts,
+			       int full,
+			       int *need_newline,
+			       FILE *outfile)
+{
+	const char *positive_name = NULL;
+	const char *cp, *np;
+	size_t pos;
+
+	if (opt->type == OPTION_SUBCOMMAND && !opt->help)
+		return;
+	if (!full && (opt->flags & PARSE_OPT_HIDDEN))
+		return;
+	if (opt->type == OPTION_GROUP) {
+		fputc('\n', outfile);
+		*need_newline = 0;
+		if (*opt->help)
+			fprintf(outfile, "%s\n", _(opt->help));
+		return;
+	}
+
+	if (*need_newline) {
+		fputc('\n', outfile);
+		*need_newline = 0;
+	}
+
+	pos = usage_indent(outfile);
+	if (opt->type == OPTION_SUBCOMMAND)
+		pos += fprintf(outfile, "%s", opt->long_name);
+	else
+		pos += usage_print_flag(opt, outfile, &positive_name);
+
+	if (opt->type == OPTION_ALIAS) {
+		usage_padding(outfile, pos);
+		fprintf_ln(outfile, _("alias of --%s"),
+			   (const char *)opt->value);
+		return;
+	}
+
+	for (cp = opt->help ? _(opt->help) : ""; *cp; cp = np) {
+		np = strchrnul(cp, '\n');
+		if (*np)
+			np++;
+		usage_padding(outfile, pos);
+		fwrite(cp, 1, np - cp, outfile);
+		pos = 0;
+	}
+	fputc('\n', outfile);
+
+	if (positive_name) {
+		if (find_option_by_long_name(all_opts, positive_name))
+			return;
+		pos = usage_indent(outfile);
+		pos += fprintf(outfile, "--%s", positive_name);
+		usage_padding(outfile, pos);
+		fprintf_ln(outfile, _("opposite of --no-%s"),
+			   positive_name);
+	}
+}
+
 static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t *ctx,
 							 const char * const *usagestr,
 							 const struct option *opts,
@@ -1406,81 +1504,9 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t
 	}
 
 	need_newline = 1;
-
-	for (; opts->type != OPTION_END; opts++) {
-		size_t pos;
-		const char *cp, *np;
-		const char *positive_name = NULL;
-
-		if (opts->type == OPTION_SUBCOMMAND)
-			continue;
-		if (!full && (opts->flags & PARSE_OPT_HIDDEN))
-			continue;
-		if (opts->type == OPTION_GROUP) {
-			fputc('\n', outfile);
-			need_newline = 0;
-			if (*opts->help)
-				fprintf(outfile, "%s\n", _(opts->help));
-			continue;
-		}
-
-		if (need_newline) {
-			fputc('\n', outfile);
-			need_newline = 0;
-		}
-
-		pos = usage_indent(outfile);
-		if (opts->short_name) {
-			if (opts->flags & PARSE_OPT_NODASH)
-				pos += fprintf(outfile, "%c", opts->short_name);
-			else
-				pos += fprintf(outfile, "-%c", opts->short_name);
-		}
-		if (opts->long_name && opts->short_name)
-			pos += fprintf(outfile, ", ");
-		if (opts->long_name) {
-			const char *long_name = opts->long_name;
-			if ((opts->flags & PARSE_OPT_NONEG) ||
-			    skip_prefix(long_name, "no-", &positive_name))
-				pos += fprintf(outfile, "--%s", long_name);
-			else
-				pos += fprintf(outfile, "--[no-]%s", long_name);
-		}
-
-		if (opts->type == OPTION_NUMBER)
-			pos += utf8_fprintf(outfile, _("-NUM"));
-
-		if ((opts->flags & PARSE_OPT_LITERAL_ARGHELP) ||
-		    !(opts->flags & PARSE_OPT_NOARG))
-			pos += usage_argh(opts, outfile);
-
-		if (opts->type == OPTION_ALIAS) {
-			usage_padding(outfile, pos);
-			fprintf_ln(outfile, _("alias of --%s"),
-				   (const char *)opts->value);
-			continue;
-		}
-
-		for (cp = opts->help ? _(opts->help) : ""; *cp; cp = np) {
-			np = strchrnul(cp, '\n');
-			if (*np)
-				np++;
-			usage_padding(outfile, pos);
-			fwrite(cp, 1, np - cp, outfile);
-			pos = 0;
-		}
-		fputc('\n', outfile);
-
-		if (positive_name) {
-			if (find_option_by_long_name(all_opts, positive_name))
-				continue;
-			pos = usage_indent(outfile);
-			pos += fprintf(outfile, "--%s", positive_name);
-			usage_padding(outfile, pos);
-			fprintf_ln(outfile, _("opposite of --no-%s"),
-				   positive_name);
-		}
-	}
+	for (; opts->type != OPTION_END; opts++)
+		usage_print_option(opts, all_opts, full,
+				   &need_newline, outfile);
 	fputc('\n', outfile);
 
 	if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
diff --git a/parse-options.h b/parse-options.h
index d7f896a..f68b51c 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -386,21 +386,25 @@ static char *parse_options_noop_ignored_value MAYBE_UNUSED;
 	.callback = parse_opt_noop_cb, \
 }
 
-#define OPT_ALIAS(s, l, source_long_name) { \
+#define OPT_ALIAS_F(s, l, source_long_name, f) { \
 	.type = OPTION_ALIAS, \
 	.short_name = (s), \
 	.long_name = (l), \
 	.value = (char *)(source_long_name), \
+	.flags = (f), \
 }
 
-#define OPT_SUBCOMMAND_F(l, v, fn, f) { \
+#define OPT_ALIAS(s, l, source_long_name) OPT_ALIAS_F(s, l, source_long_name, 0)
+
+#define OPT_SUBCOMMAND_F(l, v, fn, h, f) { \
 	.type = OPTION_SUBCOMMAND, \
 	.long_name = (l), \
 	.value = (v), \
+	.help = (h), \
 	.flags = (f), \
 	.subcommand_fn = (fn), \
 }
-#define OPT_SUBCOMMAND(l, v, fn)    OPT_SUBCOMMAND_F((l), (v), (fn), 0)
+#define OPT_SUBCOMMAND(l, v, fn)    OPT_SUBCOMMAND_F((l), (v), (fn), NULL, 0)
 
 /*
  * parse_options() will filter out the processed options and leave the
diff --git a/path.c b/path.c
index c3a709a..4608a56 100644
--- a/path.c
+++ b/path.c
@@ -396,7 +396,7 @@ static void adjust_git_path(struct repository *repo,
 		strbuf_splice(buf, 0, buf->len,
 			      repo->index_file, strlen(repo->index_file));
 	else if (dir_prefix(base, "objects"))
-		replace_dir(buf, git_dir_len + 7, repo->objects->sources->path);
+		replace_dir(buf, git_dir_len + 7, repo->objects->source->path);
 	else if (repo_settings_get_hooks_path(repo) && dir_prefix(base, "hooks"))
 		replace_dir(buf, git_dir_len + 5, repo_settings_get_hooks_path(repo));
 	else if (repo->different_commondir)
diff --git a/promisor-remote.c b/promisor-remote.c
index 43505d1..316d995 100644
--- a/promisor-remote.c
+++ b/promisor-remote.c
@@ -24,22 +24,13 @@ struct promisor_remote_config {
 static int fetch_objects(struct repository *repo,
 			 const char *remote_name,
 			 const struct object_id *oids,
-			 int oid_nr)
+			 int oid_nr, int depth)
 {
 	struct child_process child = CHILD_PROCESS_INIT;
 	int i;
 	FILE *child_in;
 	int quiet;
 
-	if (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0)) {
-		static int warning_shown;
-		if (!warning_shown) {
-			warning_shown = 1;
-			warning(_("lazy fetching disabled; some objects may not be available"));
-		}
-		return -1;
-	}
-
 	child.git_cmd = 1;
 	child.in = -1;
 	if (repo != the_repository)
@@ -50,6 +41,7 @@ static int fetch_objects(struct repository *repo,
 		     "--filter=blob:none", "--stdin", NULL);
 	if (!repo_config_get_bool(repo, "promisor.quiet", &quiet) && quiet)
 		strvec_push(&child.args, "--quiet");
+	strvec_pushf(&child.env, "%s=%d", LAZY_FETCH_DEPTH_ENVIRONMENT, depth + 1);
 	if (start_command(&child))
 		die(_("promisor-remote: unable to fork off fetch subprocess"));
 	child_in = xfdopen(child.in, "w");
@@ -270,9 +262,28 @@ static int remove_fetched_oids(struct repository *repo,
 	return remaining_nr;
 }
 
+/*
+ * Fetch the remaining objects (given in '*remaining_oids', which
+ * contains '*remaining_nr' object ids) from the known promisor
+ * remotes. If 'accepted_only' is true, ignore promisor remotes with
+ * their 'accepted' member unset.
+ *
+ * When a fetch from a remote fails, the objects that are still
+ * missing are computed, and '*remaining_oids' and '*remaining_nr' are
+ * updated accordingly before trying the next remote. In that case
+ * '*remaining_oids' points to a new array that this function
+ * allocated, and '*to_free' is set to 1 to tell the caller that it
+ * owns that array and should free it. '*to_free' should be 0 on the
+ * first call.
+ *
+ * Return 1 when all the requested objects have been fetched, 0
+ * otherwise.
+ */
 static int try_promisor_remotes(struct repository *repo,
 				struct object_id **remaining_oids,
-				int *remaining_nr, int *to_free,
+				int *remaining_nr,
+				int *to_free,
+				int depth,
 				bool accepted_only)
 {
 	struct promisor_remote *r = repo->promisor_remote_config->promisors;
@@ -280,7 +291,8 @@ static int try_promisor_remotes(struct repository *repo,
 	for (; r; r = r->next) {
 		if (accepted_only != r->accepted)
 			continue;
-		if (fetch_objects(repo, r->name, *remaining_oids, *remaining_nr) < 0) {
+		if (fetch_objects(repo, r->name,
+				  *remaining_oids, *remaining_nr, depth) < 0) {
 			if (*remaining_nr == 1)
 				continue;
 			*remaining_nr = remove_fetched_oids(repo, remaining_oids,
@@ -295,6 +307,53 @@ static int try_promisor_remotes(struct repository *repo,
 	return 0;
 }
 
+#define MAX_LAZY_FETCH_DEPTH 5
+
+/*
+ * Lazily fetch the objects given in '*remaining_oids' from the
+ * promisor remotes, trying the accepted ones first. See
+ * try_promisor_remotes() above for how '*remaining_oids',
+ * '*remaining_nr' and '*to_free' are used.
+ *
+ * Return 1 when all the requested objects have been fetched, 0
+ * otherwise.
+ */
+static int lazy_fetch_objects(struct repository *repo,
+			      struct object_id **remaining_oids,
+			      int *remaining_nr,
+			      int *to_free)
+{
+	int depth = (int)git_env_ulong(LAZY_FETCH_DEPTH_ENVIRONMENT, 0);
+
+	if (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0)) {
+		static int warning_shown;
+		if (!warning_shown) {
+			warning_shown = 1;
+			warning(_("lazy fetching disabled; some objects may not be available"));
+		}
+		return 0;
+	}
+
+	if (depth >= MAX_LAZY_FETCH_DEPTH) {
+		static int warning_shown;
+		if (!warning_shown) {
+			warning_shown = 1;
+			warning(_("too many nested lazy fetches (%d); "
+				  "is a promisor remote pointing at the repository itself?"),
+				depth);
+		}
+		return 0;
+	}
+
+	promisor_remote_init(repo);
+
+	/* Try accepted remotes first (those the server told us to use) */
+	return try_promisor_remotes(repo, remaining_oids, remaining_nr,
+				    to_free, depth, true) ||
+		try_promisor_remotes(repo, remaining_oids, remaining_nr,
+				     to_free, depth, false);
+}
+
 void promisor_remote_get_direct(struct repository *repo,
 				const struct object_id *oids,
 				int oid_nr)
@@ -302,28 +361,18 @@ void promisor_remote_get_direct(struct repository *repo,
 	struct object_id *remaining_oids = (struct object_id *)oids;
 	int remaining_nr = oid_nr;
 	int to_free = 0;
-	int i;
 
 	if (oid_nr == 0)
 		return;
 
-	promisor_remote_init(repo);
-
-	/* Try accepted remotes first (those the server told us to use) */
-	if (try_promisor_remotes(repo, &remaining_oids, &remaining_nr,
-				 &to_free, true))
-		goto all_fetched;
-	if (try_promisor_remotes(repo, &remaining_oids, &remaining_nr,
-				 &to_free, false))
-		goto all_fetched;
-
-	for (i = 0; i < remaining_nr; i++) {
-		if (is_promisor_object(repo, &remaining_oids[i]))
-			die(_("could not fetch %s from promisor remote"),
-			    oid_to_hex(&remaining_oids[i]));
+	if (!lazy_fetch_objects(repo, &remaining_oids, &remaining_nr, &to_free)) {
+		for (int i = 0; i < remaining_nr; i++) {
+			if (is_promisor_object(repo, &remaining_oids[i]))
+				die(_("could not fetch %s from promisor remote"),
+				    oid_to_hex(&remaining_oids[i]));
+		}
 	}
 
-all_fetched:
 	if (to_free)
 		free(remaining_oids);
 }
diff --git a/prune-packed.c b/prune-packed.c
index d49dc11..b6749b8 100644
--- a/prune-packed.c
+++ b/prune-packed.c
@@ -40,7 +40,7 @@ void prune_packed_objects(int opts)
 		progress = start_delayed_progress(the_repository,
 						  _("Removing duplicate objects"), 256);
 
-	for_each_loose_file_in_source(the_repository->objects->sources,
+	for_each_loose_file_in_source(the_repository->objects->source,
 				      prune_object, NULL, prune_subdir, &opts);
 
 	/* Ensure we show 100% before finishing progress */
diff --git a/read-cache.c b/read-cache.c
index e40f290..c4cf08a 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -354,15 +354,12 @@ static int is_racy_stat(const struct index_state *istate,
 			const struct stat_data *sd)
 {
 	return (istate->timestamp.sec &&
-#ifdef USE_NSEC
-		 /* nanosecond timestamped files can also be racy! */
-		(istate->timestamp.sec < sd->sd_mtime.sec ||
-		 (istate->timestamp.sec == sd->sd_mtime.sec &&
-		  istate->timestamp.nsec <= sd->sd_mtime.nsec))
-#else
-		istate->timestamp.sec <= sd->sd_mtime.sec
-#endif
-		);
+		/* nanosecond timestamped files can also be racy! */
+		(repo_config_values(istate->repo)->use_nanosec
+		 ? (istate->timestamp.sec < sd->sd_mtime.sec ||
+		    (istate->timestamp.sec == sd->sd_mtime.sec &&
+		     istate->timestamp.nsec <= sd->sd_mtime.nsec))
+		 : istate->timestamp.sec <= sd->sd_mtime.sec));
 }
 
 int is_racy_timestamp(const struct index_state *istate,
diff --git a/reflog.h b/reflog.h
index b996712..3bdd1ca 100644
--- a/reflog.h
+++ b/reflog.h
@@ -23,8 +23,8 @@ struct reflog_expire_options {
 	int recno;
 };
 #define REFLOG_EXPIRE_OPTIONS_INIT(now) { \
-	.default_expire_total = now - 30 * 24 * 3600, \
-	.default_expire_unreachable = now - 90 * 24 * 3600, \
+	.default_expire_total = now - 90 * 24 * 3600, \
+	.default_expire_unreachable = now - 30 * 24 * 3600, \
 }
 
 /*
diff --git a/refs.c b/refs.c
index 92d5df5..951db56 100644
--- a/refs.c
+++ b/refs.c
@@ -54,6 +54,29 @@ enum ref_storage_format ref_storage_format_by_name(const char *name)
 	return REF_STORAGE_FORMAT_UNKNOWN;
 }
 
+enum ref_storage_format ref_storage_format_by_uri(const char *uri,
+						  char **payload)
+{
+	enum ref_storage_format format;
+	const char *schema_end;
+	char *name;
+
+	schema_end = strstr(uri, "://");
+	if (!schema_end) {
+		name = xstrdup(uri);
+		if (payload)
+			*payload = NULL;
+	} else {
+		name = xstrndup(uri, schema_end - uri);
+		if (payload)
+			*payload = xstrdup(schema_end + 3);
+	}
+
+	format = ref_storage_format_by_name(name);
+	free(name);
+	return format;
+}
+
 const char *ref_storage_format_to_name(enum ref_storage_format ref_storage_format)
 {
 	const struct ref_storage_be *be = find_ref_storage_backend(ref_storage_format);
diff --git a/refs.h b/refs.h
index 9979446..ee3b8a6 100644
--- a/refs.h
+++ b/refs.h
@@ -17,6 +17,10 @@ struct worktree;
 enum ref_storage_format ref_storage_format_by_name(const char *name);
 const char *ref_storage_format_to_name(enum ref_storage_format ref_storage_format);
 
+/* Parse a reference storage URI in the format "<format>[://<payload>]". */
+enum ref_storage_format ref_storage_format_by_uri(const char *uri,
+						  char **payload);
+
 enum ref_transaction_error {
 	/* Default error code */
 	REF_TRANSACTION_ERROR_GENERIC = -1,
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 7162855..7ddf0be 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -3034,10 +3034,12 @@ static int files_transaction_prepare(struct ref_store *ref_store,
 
 		if (update->flags & REF_DELETING &&
 		    !(update->flags & REF_LOG_ONLY) &&
-		    !(update->flags & REF_IS_PRUNING)) {
+		    !(update->flags & REF_IS_PRUNING) &&
+		    !is_root_ref(update->refname)) {
 			/*
-			 * This reference has to be deleted from
-			 * packed-refs if it exists there.
+			 * This reference has to be deleted from packed-refs if it
+			 * exists there. Root refs are never packed, so we do not
+			 * have to delete them from packed-refs.
 			 */
 			if (!packed_transaction) {
 				packed_transaction = ref_store_transaction_begin(
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index a73fc6a..53511f2 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -879,6 +879,9 @@ struct packed_ref_iterator {
 	/* The current position in the snapshot's buffer: */
 	const char *pos;
 
+	/* The starting position of the current ref record. */
+	const char *record_start;
+
 	/* The end of the part of the buffer that will be iterated over: */
 	const char *eof;
 
@@ -933,6 +936,7 @@ static int next_record(struct packed_ref_iterator *iter)
 	if (iter->pos == iter->eof)
 		return ITER_DONE;
 
+	iter->record_start = iter->pos;
 	iter->base.ref.flags = REF_ISPACKED;
 	p = iter->pos;
 
@@ -1530,9 +1534,15 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
 		}
 
 		if (cmp < 0) {
-			/* Pass the old reference through. */
-			if (write_packed_entry(out, iter->ref.name,
-					       iter->ref.oid, iter->ref.peeled_oid))
+			const struct packed_ref_iterator *packed_iter =
+				(const struct packed_ref_iterator *)iter;
+			size_t len = packed_iter->pos - packed_iter->record_start;
+
+			/*
+			 * Skip any formatting and directly write to the packed-refs file
+			 * when deleting references and writing the remaining refs verbatim.
+			 */
+			if (fwrite(packed_iter->record_start, len, 1, out) != 1)
 				goto write_error;
 
 			if ((ok = ref_iterator_advance(iter)) != ITER_OK) {
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 10db039..d0de066 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -2,6 +2,7 @@
 #include "../abspath.h"
 #include "../chdir-notify.h"
 #include "../config.h"
+#include "../date.h"
 #include "../dir.h"
 #include "../environment.h"
 #include "../fsck.h"
@@ -317,7 +318,7 @@ static void fill_reftable_log_record(struct reftable_log_record *log, const stru
 		tz_begin++;
 	}
 
-	log->value.update.tz_offset = sign * atoi(tz_begin);
+	log->value.update.tz_offset = tz_to_minutes(sign * atoi(tz_begin));
 }
 
 static int reftable_be_config(const char *var, const char *value,
@@ -2186,7 +2187,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(log->refname, &old_oid, &new_oid, full_committer,
-		  log->value.update.time, log->value.update.tz_offset,
+		  log->value.update.time, minutes_to_tz(log->value.update.tz_offset),
 		  log->value.update.message, cb_data);
 }
 
@@ -2690,7 +2691,7 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store,
 
 		if (should_prune_fn(&old_oid, &new_oid, logs[i].value.update.email,
 				    (timestamp_t)logs[i].value.update.time,
-				    logs[i].value.update.tz_offset,
+				    minutes_to_tz(logs[i].value.update.tz_offset),
 				    logs[i].value.update.message,
 				    policy_cb_data)) {
 			dest->value_type = REFTABLE_LOG_DELETION;
diff --git a/remote.c b/remote.c
index fe62068..49ee2ea 100644
--- a/remote.c
+++ b/remote.c
@@ -582,6 +582,13 @@ static int handle_config(const char *key, const char *value,
 					      &remote->negotiation_include);
 	} else if (!strcmp(subkey, "followremotehead")) {
 		const char *no_warn_branch;
+		/*
+		 * NEEDSWORK: this is validated/warned about here, during config
+		 * parsing, regardless of whether the fetch that triggered this
+		 * parse will ever consult it for this particular remote. See
+		 * fetch.c's deferred handling of fetch.followRemoteHEAD for the
+		 * pattern this should likely follow.
+		 */
 		if (!strcmp(value, "never"))
 			remote->follow_remote_head = FOLLOW_REMOTE_NEVER;
 		else if (!strcmp(value, "create"))
@@ -2844,7 +2851,7 @@ static int check_and_collect_until(const char *refname UNUSED,
  */
 static int is_reachable_in_reflog(const char *local, const struct ref *remote)
 {
-	timestamp_t date;
+	timestamp_t date = 0;
 	struct commit *commit;
 	struct commit **chunk;
 	struct check_and_collect_until_cb_data cb;
diff --git a/repack-geometry.c b/repack-geometry.c
index 15b3412..b541c34 100644
--- a/repack-geometry.c
+++ b/repack-geometry.c
@@ -33,7 +33,7 @@ void pack_geometry_init(struct pack_geometry *geometry,
 	struct packed_git *p;
 	struct strbuf buf = STRBUF_INIT;
 	struct odb_source_files *files = odb_source_files_downcast(existing->source);
-	struct multi_pack_index *m = get_multi_pack_index(files->packed);
+	struct multi_pack_index *m = get_multi_pack_index(files->dirs->packed);
 
 	repo_for_each_pack(existing->repo, p) {
 		if (geometry->midx_layer_threshold_set && m &&
diff --git a/repack-midx.c b/repack-midx.c
index 64c7f8d..2514056 100644
--- a/repack-midx.c
+++ b/repack-midx.c
@@ -564,7 +564,7 @@ static void repack_make_midx_append_plan(struct repack_write_midx_opts *opts,
 	size_t steps_nr = 0, steps_alloc = 0;
 
 	odb_reprepare(opts->existing->repo->objects);
-	m = get_multi_pack_index(files->packed);
+	m = get_multi_pack_index(files->dirs->packed);
 
 	if (opts->names->nr) {
 		struct strbuf buf = STRBUF_INIT;
@@ -620,7 +620,7 @@ static int repack_make_midx_compaction_plan(struct repack_write_midx_opts *opts,
 			    opts->existing->repo);
 
 	odb_reprepare(opts->existing->repo->objects);
-	m = get_multi_pack_index(files->packed);
+	m = get_multi_pack_index(files->dirs->packed);
 
 	for (i = 0; m && i < m->num_packs + m->num_packs_in_base; i++) {
 		if (prepare_midx_pack(m, i)) {
@@ -949,7 +949,7 @@ static int write_midx_incremental(struct repack_write_midx_opts *opts)
 	size_t i;
 	int ret = 0;
 
-	get_midx_chain_filename(files->packed, &lock_name);
+	get_midx_chain_filename(files->dirs->packed, &lock_name);
 	if (safe_create_leading_directories(opts->existing->repo,
 					    lock_name.buf))
 		die_errno(_("unable to create leading directories of %s"),
diff --git a/repack.c b/repack.c
index d2aa58e..53c3419 100644
--- a/repack.c
+++ b/repack.c
@@ -59,8 +59,8 @@ void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,
 				  bool wrote_incremental_midx)
 {
 	struct strbuf buf = STRBUF_INIT;
-	struct odb_source_files *files = odb_source_files_downcast(repo->objects->sources);
-	struct multi_pack_index *m = get_multi_pack_index(files->packed);
+	struct odb_source_files *files = odb_source_files_downcast(repo->objects->source);
+	struct multi_pack_index *m = get_multi_pack_index(files->dirs->packed);
 	strbuf_addf(&buf, "%s.pack", base_name);
 	if (m && files->base.local && midx_contains_pack(m, buf.buf)) {
 		clear_midx_file(repo);
@@ -158,7 +158,7 @@ void existing_packs_collect(struct existing_packs *existing,
 			string_list_append(&existing->non_kept_packs, buf.buf);
 	}
 
-	existing->source = existing->repo->objects->sources;
+	existing->source = existing->repo->objects->source;
 
 	string_list_sort(&existing->kept_packs);
 	string_list_sort(&existing->non_kept_packs);
diff --git a/repository.c b/repository.c
index b857e1c..fa4214f 100644
--- a/repository.c
+++ b/repository.c
@@ -126,9 +126,9 @@ const char *repo_get_common_dir(struct repository *repo)
 
 const char *repo_get_object_directory(struct repository *repo)
 {
-	if (!repo->objects->sources)
+	if (!repo->objects->source)
 		BUG("repository hasn't been set up");
-	return repo->objects->sources->path;
+	return repo->objects->source->path;
 }
 
 const char *repo_get_index_file(struct repository *repo)
diff --git a/rerere.c b/rerere.c
index 1c3745d..9b1aa93 100644
--- a/rerere.c
+++ b/rerere.c
@@ -476,8 +476,11 @@ static int handle_file(struct index_state *istate,
 			unlink_or_warn(output);
 		return error(_("could not parse conflict hunks in '%s'"), path);
 	}
-	if (io.io.wrerror)
+	if (io.io.wrerror) {
+		if (output)
+			unlink_or_warn(output);
 		return -1;
+	}
 	return has_conflicts;
 }
 
@@ -597,7 +600,7 @@ int rerere_remaining(struct repository *r, struct string_list *merge_rr)
  */
 static int try_merge(struct index_state *istate,
 		     const struct rerere_id *id, const char *path,
-		     mmfile_t *cur, mmbuffer_t *result)
+		     mmfile_t *cur, mmfile_t *result)
 {
 	enum ll_merge_result ret;
 	mmfile_t base = {NULL, 0}, other = {NULL, 0};
@@ -638,7 +641,7 @@ static int merge(struct index_state *istate, const struct rerere_id *id, const c
 	int ret;
 	struct strbuf buf = STRBUF_INIT;
 	mmfile_t cur = {NULL, 0};
-	mmbuffer_t result = {NULL, 0};
+	mmfile_t result = {NULL, 0};
 
 	/*
 	 * Normalize the conflicts in path and write it out to
@@ -729,8 +732,25 @@ static void do_rerere_one_path(struct index_state *istate,
 
 	/* Has the user resolved it already? */
 	if (variant >= 0) {
-		if (!handle_file(istate, path, NULL, NULL)) {
-			copy_file(the_repository, rerere_path(&buf, id, "postimage"), path, 0666);
+		int ret = handle_file(istate, path, NULL, NULL);
+
+		if (ret < 0)
+			goto out;
+		if (!ret) {
+			const int had_postimage =
+				id->collection->status[variant] & RR_HAS_POSTIMAGE;
+			const char *postimage =
+				rerere_path(&buf, id, "postimage");
+
+			if (copy_file(the_repository,
+				      postimage,
+				      path, 0666)) {
+				if (!had_postimage)
+					unlink_or_warn(postimage);
+				error_errno(_("could not copy resolution for '%s'"),
+					    path);
+				goto out;
+			}
 			id->collection->status[variant] |= RR_HAS_POSTIMAGE;
 			fprintf_ln(stderr, _("Recorded resolution for '%s'."), path);
 			free_rerere_id(rr_item);
@@ -778,7 +798,9 @@ static void do_rerere_one_path(struct index_state *istate,
 	assign_variant(id);
 
 	variant = id->variant;
-	handle_file(istate, path, NULL, rerere_path(&buf, id, "preimage"));
+	if (handle_file(istate, path, NULL,
+			rerere_path(&buf, id, "preimage")) < 0)
+		goto out;
 	if (id->collection->status[variant] & RR_HAS_POSTIMAGE) {
 		const char *path = rerere_path(&buf, id, "postimage");
 		if (unlink(path))
@@ -947,7 +969,7 @@ static int handle_cache(struct index_state *istate,
 			const char *path, unsigned char *hash, const char *output)
 {
 	mmfile_t mmfile[3] = {{NULL}};
-	mmbuffer_t result = {NULL, 0};
+	mmfile_t result = {NULL, 0};
 	const struct cache_entry *ce;
 	int pos, len, i, has_conflicts;
 	struct rerere_io_mem io;
@@ -1039,8 +1061,8 @@ static int rerere_forget_one_path(struct index_state *istate,
 	for (id->variant = 0;
 	     id->variant < id->collection->status_nr;
 	     id->variant++) {
-		mmfile_t cur = { NULL, 0 };
-		mmbuffer_t result = {NULL, 0};
+		mmfile_t cur;
+		mmfile_t result = {NULL, 0};
 		int cleanly_resolved;
 
 		if (!has_rerere_resolution(id))
@@ -1048,7 +1070,6 @@ static int rerere_forget_one_path(struct index_state *istate,
 
 		handle_cache(istate, path, hash, rerere_path(&buf, id, "thisimage"));
 		if (read_mmfile(&cur, rerere_path(&buf, id, "thisimage"))) {
-			free(cur.ptr);
 			error(_("failed to update conflicted state in '%s'"), path);
 			goto fail_exit;
 		}
diff --git a/revision.c b/revision.c
index ee1df92..a83e499 100644
--- a/revision.c
+++ b/revision.c
@@ -2771,8 +2771,11 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
 {
 	int n = handle_revision_opt(revs, ctx->argc, ctx->argv,
 				    &ctx->cpidx, ctx->out, NULL);
-	if (n <= 0) {
-		error("unknown option `%s'", ctx->argv[0]);
+	if (n < 0) {
+		/* handle_revision_opt() has already reported the error. */
+		usage_with_options(usagestr, options);
+	} else if (!n) {
+		error("unknown option `%s'", ctx->out[ctx->cpidx - 1]);
 		usage_with_options(usagestr, options);
 	}
 	ctx->argv += n;
diff --git a/sequencer.c b/sequencer.c
index 6dae43e..efbfd13 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -235,6 +235,11 @@ struct replay_ctx {
 	 * Whether message contains a commit message.
 	 */
 	unsigned have_message :1;
+	/*
+	 * GIT_CONFIG_PARAMETERS for the commands we spawn, with auto
+	 * maintenance turned off. Built on first use.
+	 */
+	char *config_parameters;
 };
 
 struct replay_ctx* replay_ctx_new(void)
@@ -408,6 +413,7 @@ static void replay_ctx_release(struct replay_ctx *ctx)
 {
 	strbuf_release(&ctx->current_fixups);
 	strbuf_release(&ctx->message);
+	free(ctx->config_parameters);
 }
 
 void replay_opts_release(struct replay_opts *opts)
@@ -1109,6 +1115,27 @@ static int run_command_silent_on_success(struct child_process *cmd)
 }
 
 /*
+ * Don't let the commands we spawn run auto maintenance. It would race
+ * us for MERGE_RR.lock or delete packs we still have open. Our caller
+ * runs it once the sequence is done.
+ */
+static void disable_auto_maintenance(struct replay_opts *opts,
+				     struct child_process *cmd)
+{
+	if (!opts->ctx->config_parameters) {
+		const char *old = getenv(CONFIG_DATA_ENVIRONMENT);
+		struct strbuf buf = STRBUF_INIT;
+
+		if (old && *old)
+			strbuf_addstr(&buf, old);
+		git_config_append_parameter(&buf, "maintenance.auto", "false");
+		opts->ctx->config_parameters = strbuf_detach(&buf, NULL);
+	}
+	strvec_pushf(&cmd->env, "%s=%s", CONFIG_DATA_ENVIRONMENT,
+		     opts->ctx->config_parameters);
+}
+
+/*
  * If we are cherry-pick, and if the merge did not result in
  * hand-editing, we will hit this commit and inherit the original
  * author date and name.
@@ -1149,6 +1176,7 @@ static int run_git_commit(const char *defmsg,
 			     author_date_from_env(&cmd.env));
 	if (opts->ignore_date)
 		strvec_push(&cmd.env, "GIT_AUTHOR_DATE=");
+	disable_auto_maintenance(opts, &cmd);
 
 	strvec_push(&cmd.args, "commit");
 
@@ -1882,18 +1910,38 @@ static int is_pick_or_similar(enum todo_command command)
 	}
 }
 
-enum todo_item_flags {
-	TODO_EDIT_MERGE_MSG    = (1 << 0),
-	TODO_REPLACE_FIXUP_MSG = (1 << 1),
-	TODO_EDIT_FIXUP_MSG    = (1 << 2),
-};
-
 static const char first_commit_msg_str[] = N_("This is the 1st commit message:");
 static const char nth_commit_msg_fmt[] = N_("This is the commit message #%d:");
 static const char skip_first_commit_msg_str[] = N_("The 1st commit message will be skipped:");
 static const char skip_nth_commit_msg_fmt[] = N_("The commit message #%d will be skipped:");
 static const char combined_commit_msg_fmt[] = N_("This is a combination of %d commits.");
 
+void add_squash_combination_header(struct strbuf *buf, int n)
+{
+	strbuf_addf(buf, "%s ", comment_line_str);
+	strbuf_addf(buf, _(combined_commit_msg_fmt), n);
+}
+
+void add_squash_message_header(struct strbuf *buf, int n, int skip)
+{
+	strbuf_addf(buf, "%s ", comment_line_str);
+	if (n == 1)
+		strbuf_addstr(buf, skip ? _(skip_first_commit_msg_str) :
+				   _(first_commit_msg_str));
+	else
+		strbuf_addf(buf, skip ? _(skip_nth_commit_msg_fmt) :
+			    _(nth_commit_msg_fmt), n);
+}
+
+size_t squash_subject_comment_len(const char *body, int squashing)
+{
+	if (starts_with(body, "amend!") ||
+	    (squashing && (starts_with(body, "squash!") ||
+			   starts_with(body, "fixup!"))))
+		return commit_subject_length(body);
+	return 0;
+}
+
 static int is_fixup_flag(enum todo_command command, unsigned flag)
 {
 	return command == TODO_FIXUP && ((flag & TODO_REPLACE_FIXUP_MSG) ||
@@ -2014,20 +2062,13 @@ static int append_squash_message(struct strbuf *buf, const char *body,
 {
 	struct replay_ctx *ctx = opts->ctx;
 	const char *fixup_msg;
-	size_t commented_len = 0, fixup_off;
-	/*
-	 * amend is non-interactive and not normally used with fixup!
-	 * or squash! commits, so only comment out those subjects when
-	 * squashing commit messages.
-	 */
-	if (starts_with(body, "amend!") ||
-	    ((command == TODO_SQUASH || seen_squash(ctx)) &&
-	     (starts_with(body, "squash!") || starts_with(body, "fixup!"))))
-		commented_len = commit_subject_length(body);
+	size_t commented_len, fixup_off;
 
-	strbuf_addf(buf, "\n%s ", comment_line_str);
-	strbuf_addf(buf, _(nth_commit_msg_fmt),
-		    ++ctx->current_fixup_count + 1);
+	commented_len = squash_subject_comment_len(body,
+				command == TODO_SQUASH || seen_squash(ctx));
+
+	strbuf_addch(buf, '\n');
+	add_squash_message_header(buf, ++ctx->current_fixup_count + 1, 0);
 	strbuf_addstr(buf, "\n\n");
 	strbuf_add_commented_lines(buf, body, commented_len, comment_line_str);
 	/* buf->buf may be reallocated so store an offset into the buffer */
@@ -2092,9 +2133,8 @@ static int update_squash_messages(struct repository *r,
 		eol = !starts_with(buf.buf, comment_line_str) ?
 			buf.buf : strchrnul(buf.buf, '\n');
 
-		strbuf_addf(&header, "%s ", comment_line_str);
-		strbuf_addf(&header, _(combined_commit_msg_fmt),
-			    ctx->current_fixup_count + 2);
+		add_squash_combination_header(&header,
+					      ctx->current_fixup_count + 2);
 		strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
 		strbuf_release(&header);
 		if (is_fixup_flag(command, flag) && !seen_squash(ctx))
@@ -2118,12 +2158,9 @@ static int update_squash_messages(struct repository *r,
 			repo_unuse_commit_buffer(r, head_commit, head_message);
 			return error(_("cannot write '%s'"), rebase_path_fixup_msg());
 		}
-		strbuf_addf(&buf, "%s ", comment_line_str);
-		strbuf_addf(&buf, _(combined_commit_msg_fmt), 2);
-		strbuf_addf(&buf, "\n%s ", comment_line_str);
-		strbuf_addstr(&buf, is_fixup_flag(command, flag) ?
-			      _(skip_first_commit_msg_str) :
-			      _(first_commit_msg_str));
+		add_squash_combination_header(&buf, 2);
+		strbuf_addch(&buf, '\n');
+		add_squash_message_header(&buf, 1, is_fixup_flag(command, flag));
 		strbuf_addstr(&buf, "\n\n");
 		if (is_fixup_flag(command, flag))
 			strbuf_add_commented_lines(&buf, body, strlen(body),
@@ -2142,9 +2179,8 @@ static int update_squash_messages(struct repository *r,
 	if (command == TODO_SQUASH || is_fixup_flag(command, flag)) {
 		res = append_squash_message(&buf, body, command, opts, flag);
 	} else if (command == TODO_FIXUP) {
-		strbuf_addf(&buf, "\n%s ", comment_line_str);
-		strbuf_addf(&buf, _(skip_nth_commit_msg_fmt),
-			    ++ctx->current_fixup_count + 1);
+		strbuf_addch(&buf, '\n');
+		add_squash_message_header(&buf, ++ctx->current_fixup_count + 1, 1);
 		strbuf_addstr(&buf, "\n\n");
 		strbuf_add_commented_lines(&buf, body, strlen(body),
 					   comment_line_str);
@@ -3925,16 +3961,18 @@ static int error_failed_squash(struct repository *r,
 	return error_with_patch(r, commit, subject, subject_len, opts, 1, 1);
 }
 
-static int do_exec(struct repository *r, const char *command_line, int quiet)
+static int do_exec(struct repository *r, const char *command_line,
+		   struct replay_opts *opts)
 {
 	struct child_process cmd = CHILD_PROCESS_INIT;
 	int dirty, status;
 
-	if (!quiet)
+	if (!opts->quiet)
 		fprintf(stderr, _("Executing: %s\n"), command_line);
 	cmd.use_shell = 1;
 	strvec_push(&cmd.args, command_line);
 	strvec_push(&cmd.env, "GIT_CHERRY_PICK_HELP");
+	disable_auto_maintenance(opts, &cmd);
 	status = run_command(&cmd);
 
 	/* force re-reading of the cache */
@@ -4343,6 +4381,7 @@ static int do_merge(struct repository *r,
 				     author_date_from_env(&cmd.env));
 		if (opts->ignore_date)
 			strvec_push(&cmd.env, "GIT_AUTHOR_DATE=");
+		disable_auto_maintenance(opts, &cmd);
 
 		cmd.git_cmd = 1;
 		strvec_push(&cmd.args, "merge");
@@ -5189,7 +5228,7 @@ static int pick_commits(struct repository *r,
 			if (!opts->verbose)
 				term_clear_line();
 			*end_of_arg = '\0';
-			res = do_exec(r, arg, opts->quiet);
+			res = do_exec(r, arg, opts);
 			*end_of_arg = saved;
 
 			if (res) {
@@ -5360,6 +5399,7 @@ static int continue_single_pick(struct repository *r, struct replay_opts *opts)
 		return error(_("no cherry-pick or revert in progress"));
 
 	cmd.git_cmd = 1;
+	disable_auto_maintenance(opts, &cmd);
 	strvec_push(&cmd.args, "commit");
 
 	/*
diff --git a/sequencer.h b/sequencer.h
index 804501b..a5d12a1 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -120,6 +120,13 @@ enum todo_command {
 	TODO_COMMENT
 };
 
+/* Bits for the "flags" member of struct todo_item */
+enum todo_item_flags {
+	TODO_EDIT_MERGE_MSG    = (1 << 0),
+	TODO_REPLACE_FIXUP_MSG = (1 << 1),
+	TODO_EDIT_FIXUP_MSG    = (1 << 2),
+};
+
 struct todo_item {
 	enum todo_command command;
 	struct commit *commit;
@@ -209,6 +216,29 @@ int todo_list_rearrange_squash(struct todo_list *todo_list);
  */
 void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag);
 
+/*
+ * Append the "This is a combination of N commits." banner that "git rebase
+ * -i" writes at the top of a squashed commit's message, commented out with
+ * the comment character.
+ */
+void add_squash_combination_header(struct strbuf *buf, int n);
+
+/*
+ * Append the header (1-based N) that "git rebase -i" writes above each message
+ * when squashing, commented out with the comment character. With SKIP it reads
+ * "The ... commit message will be skipped" for a message that is dropped (a
+ * fixup), otherwise "This is the ... commit message".
+ */
+void add_squash_message_header(struct strbuf *buf, int n, int skip);
+
+/*
+ * Return the length of the leading subject of BODY when it should be commented
+ * out in a squash message, or 0 otherwise. An "amend!" subject always
+ * qualifies; "squash!" and "fixup!" subjects only when SQUASHING, since a
+ * plain fixup chain keeps them.
+ */
+size_t squash_subject_comment_len(const char *body, int squashing);
+
 void append_conflicts_hint(struct index_state *istate,
 		struct strbuf *msgbuf, enum commit_msg_cleanup_mode cleanup_mode);
 enum commit_msg_cleanup_mode get_cleanup_mode(const char *cleanup_arg,
diff --git a/setup.c b/setup.c
index 0d157ac..c64dcf4 100644
--- a/setup.c
+++ b/setup.c
@@ -632,21 +632,6 @@ static enum extension_result handle_extension_v0(const char *var,
 		return EXTENSION_UNKNOWN;
 }
 
-static void parse_reference_uri(const char *value, char **format,
-				char **payload)
-{
-	const char *schema_end;
-
-	schema_end = strstr(value, "://");
-	if (!schema_end) {
-		*format = xstrdup(value);
-		*payload = NULL;
-	} else {
-		*format = xstrndup(value, schema_end - value);
-		*payload = xstrdup_or_null(schema_end + 3);
-	}
-}
-
 /*
  * Record any new extensions in this function.
  */
@@ -689,16 +674,13 @@ static enum extension_result handle_extension(const char *var,
 		return EXTENSION_OK;
 	} else if (!strcmp(ext, "refstorage")) {
 		unsigned int format;
-		char *format_str;
 
 		if (!value)
 			return config_error_nonbool(var);
 
-		parse_reference_uri(value, &format_str,
-				    &data->ref_storage_payload);
-
-		format = ref_storage_format_by_name(format_str);
-		free(format_str);
+		FREE_AND_NULL(data->ref_storage_payload);
+		format = ref_storage_format_by_uri(value,
+						   &data->ref_storage_payload);
 
 		if (format == REF_STORAGE_FORMAT_UNKNOWN)
 			return error(_("invalid value for '%s': '%s'"),
@@ -1355,67 +1337,111 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,
 	}
 }
 
+void path_allowlist_apply(const char *allowed, const char *target_path,
+			  bool *matches,
+			  bool (*allow_path)(const char *path, void *cbdata),
+			  void *allow_path_cbdata)
+{
+	char *normalized = NULL;
+
+	if (!allowed || !*allowed) {
+		*matches = false;
+		return;
+	}
+
+	if (!strcmp(allowed, "*")) {
+		*matches = true;
+		return;
+	}
+
+	if (!allow_path(allowed, allow_path_cbdata))
+		return;
+
+	/*
+	 * A .gitconfig in $HOME may be shared across different
+	 * machines and the config variable entries may or may not
+	 * exist as paths on all of these machines.  In other words,
+	 * it is not a warning worthy event when there is no such path
+	 * on this machine---the entry may be useful elsewhere.
+	 *
+	 * NEEDSWORK: this also silently ignores misspelled paths. We
+	 * may want to warn about a missing path unless it is marked
+	 * as allowed to be missing, e.g., with an ":(optional)"
+	 * prefix like pathname-typed configuration values, and hint
+	 * about that prefix in the warning.
+	 */
+	normalized = real_pathdup(allowed, 0);
+	if (!normalized)
+		return;
+
+	if (ends_with(normalized, "/*")) {
+		size_t len = strlen(normalized);
+		if (!fspathncmp(normalized, target_path, len - 1))
+			*matches = true;
+	} else if (!fspathcmp(target_path, normalized)) {
+		*matches = true;
+	}
+
+	free(normalized);
+}
+
+void path_allowlist_config_apply(const char *key, const char *value,
+				 const char *target_path, bool *matches,
+				 bool (*allow_path)(const char *path, void *cbdata),
+				 void *allow_path_cbdata)
+{
+	char *allowed = NULL;
+
+	if (!value || !*value || !strcmp(value, "*")) {
+		path_allowlist_apply(value, target_path, matches,
+				     allow_path, allow_path_cbdata);
+		return;
+	}
+
+	if (git_config_pathname(&allowed, key, value) || !allowed)
+		return;
+
+	path_allowlist_apply(allowed, target_path, matches,
+			     allow_path, allow_path_cbdata);
+
+	free(allowed);
+}
+
+/*
+ * Setting the config variable to a non-absolute path makes
+ * little sense---it won't be relative to the configuration
+ * file the item is defined in.  Except for ".", which means
+ * "if we are at the top level of a repository, then it is
+ * OK", which is slightly tighter than "*" that allows
+ * discovery.
+ */
+static bool allow_safe_dir(const char *path, void *cbdata_)
+{
+	struct path_allowlist_cb_data *cbdata = cbdata_;
+
+	if (is_absolute_path(path) || !strcmp(path, "."))
+		return true;
+
+	warning(_("%s '%s' not absolute"), cbdata->key, path);
+	return false;
+}
+
 struct safe_directory_data {
 	char *path;
-	int is_safe;
+	bool is_safe;
 };
 
 static int safe_directory_cb(const char *key, const char *value,
 			     const struct config_context *ctx UNUSED, void *d)
 {
 	struct safe_directory_data *data = d;
+	struct path_allowlist_cb_data cbdata = { .key = key };
 
 	if (strcmp(key, "safe.directory"))
 		return 0;
 
-	if (!value || !*value) {
-		data->is_safe = 0;
-	} else if (!strcmp(value, "*")) {
-		data->is_safe = 1;
-	} else {
-		char *allowed = NULL;
-
-		if (!git_config_pathname(&allowed, key, value) && allowed) {
-			char *normalized = NULL;
-
-			/*
-			 * Setting safe.directory to a non-absolute path
-			 * makes little sense---it won't be relative to
-			 * the configuration file the item is defined in.
-			 * Except for ".", which means "if we are at the top
-			 * level of a repository, then it is OK", which is
-			 * slightly tighter than "*" that allows discovery.
-			 */
-			if (!is_absolute_path(allowed) && strcmp(allowed, ".")) {
-				warning(_("safe.directory '%s' not absolute"),
-					allowed);
-				goto next;
-			}
-
-			/*
-			 * A .gitconfig in $HOME may be shared across
-			 * different machines and safe.directory entries
-			 * may or may not exist as paths on all of these
-			 * machines.  In other words, it is not a warning
-			 * worthy event when there is no such path on this
-			 * machine---the entry may be useful elsewhere.
-			 */
-			normalized = real_pathdup(allowed, 0);
-			if (!normalized)
-				goto next;
-
-			if (ends_with(normalized, "/*")) {
-				size_t len = strlen(normalized);
-				if (!fspathncmp(normalized, data->path, len - 1))
-					data->is_safe = 1;
-			} else if (!fspathcmp(data->path, normalized)) {
-				data->is_safe = 1;
-			}
-		next:
-			free(normalized);
-			free(allowed);
-		}
-	}
+	path_allowlist_config_apply(key, value, data->path, &data->is_safe,
+				    allow_safe_dir, &cbdata);
 
 	return 0;
 }
@@ -2084,18 +2110,16 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
 			 * The env variable should override the repository config
 			 * for 'extensions.refStorage'.
 			 */
-			ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT);
+			ref_backend_uri = getenv(GIT_REF_STORAGE_FORMAT_ENVIRONMENT);
+			if (!ref_backend_uri)
+				ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT);
 			if (ref_backend_uri) {
-				char *format;
-
-				free(discovery.format.ref_storage_payload);
-
-				parse_reference_uri(ref_backend_uri, &format, &discovery.format.ref_storage_payload);
-				discovery.format.ref_storage_format = ref_storage_format_by_name(format);
+				FREE_AND_NULL(discovery.format.ref_storage_payload);
+				discovery.format.ref_storage_format =
+					ref_storage_format_by_uri(ref_backend_uri,
+								  &discovery.format.ref_storage_payload);
 				if (discovery.format.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
-					die(_("unknown ref storage format: '%s'"), format);
-
-				free(format);
+					die(_("unknown ref storage format: '%s'"), ref_backend_uri);
 			}
 
 			if (apply_repository_format(repo, &discovery.format,
@@ -2663,8 +2687,13 @@ static int create_default_files(struct repository *repo,
 	return reinit;
 }
 
-static void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo,
+			    const struct strvec *alternates)
 {
+	struct odb_create_on_disk_options opts = {
+		.alternates = alternates,
+	};
+
 	/*
 	 * Create the "objects" directory in the common directory. This is done
 	 * so that the repository can be discovered regardless of the backend
@@ -2684,7 +2713,7 @@ static void create_object_database(struct repository *repo)
 
 	repo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);
 
-	if (odb_source_create_on_disk(repo->objects->sources) < 0)
+	if (odb_source_create_on_disk(repo->objects->source, &opts) < 0)
 		die(_("failed creating object database"));
 }
 
@@ -2713,7 +2742,7 @@ static void separate_git_dir(struct repository *repo,
 
 struct default_format_config {
 	int hash;
-	enum ref_storage_format ref_format;
+	enum ref_storage_format ref_storage_format;
 };
 
 static int read_default_format_config(const char *key, const char *value,
@@ -2734,24 +2763,25 @@ static int read_default_format_config(const char *key, const char *value,
 		goto out;
 	}
 
-	if (!strcmp(key, "init.defaultrefformat")) {
+	if (!strcmp(key, "init.defaultrefstorageformat") ||
+	    !strcmp(key, "init.defaultrefformat")) {
 		ret = git_config_string(&str, key, value);
 		if (ret)
 			goto out;
-		cfg->ref_format = ref_storage_format_by_name(str);
-		if (cfg->ref_format == REF_STORAGE_FORMAT_UNKNOWN)
+		cfg->ref_storage_format = ref_storage_format_by_name(str);
+		if (cfg->ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
 			warning(_("unknown ref storage format '%s'"), str);
 		goto out;
 	}
 
 	/*
 	 * Enable the reftable format when "features.experimental" is enabled.
-	 * "init.defaultRefFormat" takes precedence over this setting.
+	 * "init.defaultRefStorageFormat" takes precedence over this setting.
 	 */
 	if (!strcmp(key, "feature.experimental") &&
-	    cfg->ref_format == REF_STORAGE_FORMAT_UNKNOWN &&
+	    cfg->ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN &&
 	    git_config_bool(key, value)) {
-		cfg->ref_format = REF_STORAGE_FORMAT_REFTABLE;
+		cfg->ref_storage_format = REF_STORAGE_FORMAT_REFTABLE;
 		ret = 0;
 		goto out;
 	}
@@ -2763,18 +2793,20 @@ static int read_default_format_config(const char *key, const char *value,
 }
 
 static void repository_format_configure(struct repository_format *repo_fmt,
-					int hash, enum ref_storage_format ref_format)
+					int hash,
+					const char *ref_storage_format_uri)
 {
 	struct default_format_config cfg = {
 		.hash = GIT_HASH_UNKNOWN,
-		.ref_format = REF_STORAGE_FORMAT_UNKNOWN,
+		.ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN,
 	};
 	struct config_options opts = {
 		.respect_includes = 1,
 		.ignore_repo = 1,
 		.ignore_worktree = 1,
 	};
-	const char *ref_backend_uri;
+	enum ref_storage_format ref_storage_format;
+	char *ref_storage_payload = NULL;
 	const char *env;
 
 	config_with_options(read_default_format_config, &cfg, NULL, NULL, &opts);
@@ -2800,55 +2832,97 @@ static void repository_format_configure(struct repository_format *repo_fmt,
 		repo_fmt->hash_algo = cfg.hash;
 	}
 
-	env = getenv("GIT_DEFAULT_REF_FORMAT");
-	if (repo_fmt->version >= 0 &&
-	    ref_format != REF_STORAGE_FORMAT_UNKNOWN &&
-	    ref_format != repo_fmt->ref_storage_format) {
-		die(_("attempt to reinitialize repository with different reference storage format"));
-	} else if (ref_format != REF_STORAGE_FORMAT_UNKNOWN) {
-		repo_fmt->ref_storage_format = ref_format;
-	} else if (env) {
-		ref_format = ref_storage_format_by_name(env);
-		if (ref_format == REF_STORAGE_FORMAT_UNKNOWN)
-			die(_("unknown ref storage format '%s'"), env);
-		if (repo_fmt->version < 0 ||
-		    repo_fmt->ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
-			repo_fmt->ref_storage_format = ref_format;
-	} else if (cfg.ref_format != REF_STORAGE_FORMAT_UNKNOWN) {
-		repo_fmt->ref_storage_format = cfg.ref_format;
+	/*
+	 * We have the following order of preference when configuring the ref
+	 * storage format:
+	 *
+	 *   1. Explicit override via the command line, like in `git init
+	 *      --ref-storage-format=`.
+	 *
+	 *   2. Explicit override via the environment with
+	 *      "GIT_REF_STORAGE_FORMAT".
+	 *
+	 *   3. Its deprecated equivalent "GIT_REFERENCE_BACKEND".
+	 *
+	 *   4. Existing repository format. All the subsequent sources only
+	 *      kick in when there is no repository yet.
+	 *
+	 *   4. The default ref storage format for new repositories as
+	 *      configured via "GIT_DEFAULT_REF_STORAGE_FORMAT".
+	 *
+	 *   5. Its deprecated equivalent "GIT_DEFAULT_REF_FORMAT".
+	 *
+	 *   6. The default ref storage format for new repositories as
+	 *      configured via "init.defaultRefStorageFormat" or its deprecated
+	 *      equivalent "init.defaultRefFormat".
+	 *
+	 *   7. Otherwise, we fall back to the default ref storage format
+	 *      compiled into Git.
+	 */
+	if (ref_storage_format_uri) {
+		ref_storage_format = ref_storage_format_by_uri(ref_storage_format_uri,
+							       &ref_storage_payload);
+		if (ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
+			die(_("unknown ref storage format specified via command line: '%s'"),
+			    ref_storage_format_uri);
+	} else if ((env = getenv(GIT_REF_STORAGE_FORMAT_ENVIRONMENT))) {
+		ref_storage_format = ref_storage_format_by_uri(env, &ref_storage_payload);
+		if (ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
+			die(_("unknown ref storage format specified via %s: '%s'"),
+			    GIT_REF_STORAGE_FORMAT_ENVIRONMENT, env);
+	} else if ((env = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT))) {
+		ref_storage_format = ref_storage_format_by_uri(env, &ref_storage_payload);
+		if (ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
+			die(_("unknown ref storage format specified via %s: '%s'"),
+			    GIT_REFERENCE_BACKEND_ENVIRONMENT, env);
+	} else if (repo_fmt->version >= 0) {
+		ref_storage_format = repo_fmt->ref_storage_format;
+		ref_storage_payload = xstrdup_or_null(repo_fmt->ref_storage_payload);
+	} else if ((env = getenv("GIT_DEFAULT_REF_STORAGE_FORMAT"))) {
+		ref_storage_format = ref_storage_format_by_name(env);
+		if (ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
+			die(_("unknown ref storage format specified via %s: '%s'"),
+			    "GIT_DEFAULT_REF_STORAGE_FORMAT", env);
+	} else if ((env = getenv("GIT_DEFAULT_REF_FORMAT"))) {
+		ref_storage_format = ref_storage_format_by_name(env);
+		if (ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
+			die(_("unknown ref storage format specified via %s: '%s'"),
+			    "GIT_DEFAULT_REF_FORMAT", env);
+	} else if (cfg.ref_storage_format != REF_STORAGE_FORMAT_UNKNOWN) {
+		ref_storage_format = cfg.ref_storage_format;
 	} else {
-		repo_fmt->ref_storage_format = REF_STORAGE_FORMAT_DEFAULT;
+		ref_storage_format = REF_STORAGE_FORMAT_DEFAULT;
 	}
 
-
-	ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT);
-	if (ref_backend_uri) {
-		char *backend, *payload;
-		enum ref_storage_format format;
-
-		parse_reference_uri(ref_backend_uri, &backend, &payload);
-		format = ref_storage_format_by_name(backend);
-		if (format == REF_STORAGE_FORMAT_UNKNOWN)
-			die(_("unknown ref storage format: '%s'"), backend);
-
-		repo_fmt->ref_storage_format = format;
-		repo_fmt->ref_storage_payload = payload;
-
-		free(backend);
+	/*
+	 * If we have a preexisting repository we need to verify that its
+	 * current ref storage format does not change.
+	 */
+	if (repo_fmt->version >= 0) {
+		if (ref_storage_format != repo_fmt->ref_storage_format)
+			die(_("attempt to reinitialize repository with different reference storage format"));
+		if ((ref_storage_payload || repo_fmt->ref_storage_payload) &&
+		    strcmp(ref_storage_payload ? ref_storage_payload : "",
+			   repo_fmt->ref_storage_payload ? repo_fmt->ref_storage_payload : ""))
+			die(_("attempt to reinitialize repository with different reference storage payload"));
 	}
+
+	free(repo_fmt->ref_storage_payload);
+	repo_fmt->ref_storage_format = ref_storage_format;
+	repo_fmt->ref_storage_payload = ref_storage_payload;
 }
 
-int init_db(struct repository *repo,
-	    const char *git_dir,
-	    const char *real_git_dir,
-	    const char *worktree,
-	    const char *template_dir, int hash,
-	    enum ref_storage_format ref_storage_format,
-	    const char *initial_branch,
-	    int init_shared_repository, unsigned int flags)
+void create_repository(struct repository *repo,
+		       const char *git_dir,
+		       const char *real_git_dir,
+		       const char *worktree,
+		       const char *template_dir,
+		       int hash,
+		       const char *ref_storage_format_uri,
+		       int init_shared_repository,
+		       int *reinit_ok)
 {
-	int reinit;
-	int exist_ok = flags & INIT_DB_EXIST_OK;
+	int reinit_ignored;
 	char *original_git_dir = real_pathdup(git_dir, 1);
 	struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
 	struct strbuf err = STRBUF_INIT;
@@ -2856,10 +2930,10 @@ int init_db(struct repository *repo,
 	if (real_git_dir) {
 		struct stat st;
 
-		if (!exist_ok && !stat(git_dir, &st))
+		if (!reinit_ok && !stat(git_dir, &st))
 			die(_("%s already exists"), git_dir);
 
-		if (!exist_ok && !stat(real_git_dir, &st))
+		if (!reinit_ok && !stat(real_git_dir, &st))
 			die(_("%s already exists"), real_git_dir);
 
 		apply_and_export_relative_gitdir(repo, real_git_dir, 1);
@@ -2880,7 +2954,7 @@ int init_db(struct repository *repo,
 	 * is an attempt to reinitialize new repository with an old tool.
 	 */
 	read_and_verify_repository_format(&repo_fmt, repo_get_git_dir(repo), NULL);
-	repository_format_configure(&repo_fmt, hash, ref_storage_format);
+	repository_format_configure(&repo_fmt, hash, ref_storage_format_uri);
 	if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)
 		die("%s", err.buf);
 
@@ -2893,8 +2967,10 @@ int init_db(struct repository *repo,
 
 	safe_create_dir(repo, git_dir, 0);
 
-	reinit = create_default_files(repo, template_dir, original_git_dir,
-				      &repo_fmt, init_shared_repository);
+	if (!reinit_ok)
+		reinit_ok = &reinit_ignored;
+	*reinit_ok = create_default_files(repo, template_dir, original_git_dir,
+					  &repo_fmt, init_shared_repository);
 
 	if (repo_settings_get_shared_repository(repo)) {
 		char buf[10];
@@ -2917,29 +2993,7 @@ int init_db(struct repository *repo,
 		repo_config_set(repo, "receive.denyNonFastforwards", "true");
 	}
 
-	if (!(flags & INIT_DB_SKIP_REFDB))
-		create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
-	create_object_database(repo);
-
-	startup_info->have_repository = 1;
-
-	if (!(flags & INIT_DB_QUIET)) {
-		int len = strlen(git_dir);
-
-		if (reinit)
-			printf(repo_settings_get_shared_repository(repo)
-			       ? _("Reinitialized existing shared Git repository in %s%s\n")
-			       : _("Reinitialized existing Git repository in %s%s\n"),
-			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
-		else
-			printf(repo_settings_get_shared_repository(repo)
-			       ? _("Initialized empty shared Git repository in %s%s\n")
-			       : _("Initialized empty Git repository in %s%s\n"),
-			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
-	}
-
 	clear_repository_format(&repo_fmt);
 	strbuf_release(&err);
 	free(original_git_dir);
-	return 0;
 }
diff --git a/setup.h b/setup.h
index 7394473..871c5d0 100644
--- a/setup.h
+++ b/setup.h
@@ -257,25 +257,48 @@ int apply_repository_format(struct repository *repo,
 
 const char *get_template_dir(const char *option_template);
 
-#define INIT_DB_QUIET      (1 << 0)
-#define INIT_DB_EXIST_OK   (1 << 1)
-#define INIT_DB_SKIP_REFDB (1 << 2)
+/*
+ * Create the repository by creating the necessary directory structures,
+ * setting up the configuration and configuring the repository's format. If
+ * `template_dir` is set, copy over templates from that directory. Furthermore,
+ * if and only if `reinit_ok` is a non-NULL pointer, then the function may
+ * reinitialize a preexisting repository. In that case, the pointer will be set
+ * to `1` in case the repo was reinitialized and `0` if it didn't exist yet.
+ *
+ * Note that this function does not create the reference and object databases.
+ */
+void create_repository(struct repository *repo,
+		       const char *git_dir,
+		       const char *real_git_dir,
+		       const char *worktree,
+		       const char *template_dir,
+		       int hash_algo,
+		       const char *ref_storage_format_uri,
+		       int init_shared_repository,
+		       int *reinit_ok);
 
-int init_db(struct repository *repo,
-	    const char *git_dir,
-	    const char *real_git_dir,
-	    const char *worktree,
-	    const char *template_dir, int hash_algo,
-	    enum ref_storage_format ref_storage_format,
-	    const char *initial_branch, int init_shared_repository,
-	    unsigned int flags);
 void initialize_repository_version(struct repository *repo,
 				   int hash_algo,
 				   enum ref_storage_format ref_storage_format,
 				   int reinit);
+
+/*
+ * Create the reference database for the repository. The repository and its ref
+ * storage format must have already been configured properly before calling
+ * this function. When set, `initial_branch` overrides the default branch that
+ * HEAD will point to.
+ */
 void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
 
 /*
+ * Create the object database for the repository. The repository must have
+ * already been configured properly before calling this function. When set,
+ * `alternates` is the list of alternates that should be written into the
+ * object database.
+ */
+void create_object_database(struct repository *repo, const struct strvec *alternates);
+
+/*
  * NOTE NOTE NOTE!!
  *
  * PERM_UMASK, OLD_PERM_GROUP and OLD_PERM_EVERYBODY enumerations must
@@ -305,4 +328,54 @@ struct startup_info {
 extern struct startup_info *startup_info;
 extern const char *tmp_original_cwd;
 
+/* Path allowlist */
+
+struct path_allowlist_cb_data {
+	const char *key;
+};
+
+/*
+ * Check the allowlist entry in `allowed` against `target_path`,
+ * updating `*matches` accordingly.
+ *
+ * `allowed` is a single entry of an allowlist of paths, typically one
+ * value of a multi-valued config variable, already expanded by
+ * git_config_pathname(). `target_path` is the (normalized) path being
+ * tested. `*matches` is updated in place:
+ *
+ *   - an empty `allowed` resets it to 'false' (so a later, more
+ *     specific config scope can clear entries from a broader one),
+ *   - "*" sets it to 'true' (allow everything),
+ *   - "<path>" sets it to 'true' if <path> equals `target_path`,
+ *   - "<path>" + "/" + "*" sets it to 'true' if <path> is a leading
+ *     directory of `target_path`,
+ *   - anything else leaves `*matches` unchanged.
+ *
+ * `allow_path` is called with `allowed` and `allow_path_cbdata`, and
+ * should return 'true' if the entry is acceptable to the caller. It
+ * lets each caller decide which paths it is willing to consider, and
+ * whether to warn about the ones it rejects. Returning 'false' leaves
+ * `*matches` unchanged.
+ *
+ * Callers are expected to invoke this once per allowlist entry,
+ * typically from a protected-config callback, so that untrusted
+ * repository config cannot influence the decision.
+ */
+void path_allowlist_apply(const char *allowed, const char *target_path,
+			  bool *matches,
+			  bool (*allow_path)(const char *path, void *cbdata),
+			  void *allow_path_cbdata);
+
+/*
+ * Apply one value of a multi-valued config variable holding an
+ * allowlist of paths, expanding it with git_config_pathname() before
+ * checking it against `target_path`. Empty and "*" values are passed
+ * through without expansion, as interpolating them is not
+ * meaningful. See path_allowlist_apply().
+ */
+void path_allowlist_config_apply(const char *key, const char *value,
+				 const char *target_path, bool *matches,
+				 bool (*allow_path)(const char *path, void *cbdata),
+				 void *allow_path_cbdata);
+
 #endif /* SETUP_H */
diff --git a/statinfo.c b/statinfo.c
index 5e00af1..d9ddcf9 100644
--- a/statinfo.c
+++ b/statinfo.c
@@ -72,13 +72,13 @@ int match_stat_data(const struct stat_data *sd, struct stat *st)
 	    sd->sd_ctime.sec != (unsigned int)st->st_ctime)
 		changed |= CTIME_CHANGED;
 
-#ifdef USE_NSEC
-	if (cfg->check_stat && sd->sd_mtime.nsec != ST_MTIME_NSEC(*st))
-		changed |= MTIME_CHANGED;
-	if (cfg->trust_ctime && cfg->check_stat &&
-	    sd->sd_ctime.nsec != ST_CTIME_NSEC(*st))
-		changed |= CTIME_CHANGED;
-#endif
+	if (cfg->use_nanosec) {
+		if (cfg->check_stat && sd->sd_mtime.nsec != ST_MTIME_NSEC(*st))
+			changed |= MTIME_CHANGED;
+		if (cfg->trust_ctime && cfg->check_stat &&
+		    sd->sd_ctime.nsec != ST_CTIME_NSEC(*st))
+			changed |= CTIME_CHANGED;
+	}
 
 	if (cfg->check_stat) {
 		if (sd->sd_uid != (unsigned int) st->st_uid ||
diff --git a/strbuf.c b/strbuf.c
index 4495566..c3baa47 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -1023,8 +1023,7 @@ void strbuf_addftime(struct strbuf *sb, const char *fmt, const struct tm *tm,
 		else if (skip_prefix(fmt, "s", &fmt))
 			strbuf_addf(&munged_fmt, "%"PRItime,
 				    (timestamp_t)tm_to_time_t(tm) -
-				    3600 * (tz_offset / 100) -
-				    60 * (tz_offset % 100));
+				    60 * tz_to_minutes(tz_offset));
 		else if (skip_prefix(fmt, "z", &fmt))
 			strbuf_addf(&munged_fmt, "%+05d", tz_offset);
 		else if (suppress_tz_name && skip_prefix(fmt, "Z", &fmt))
diff --git a/subprojects/curl.wrap b/subprojects/curl.wrap
index f7e384b..d73b88b 100644
--- a/subprojects/curl.wrap
+++ b/subprojects/curl.wrap
@@ -1,13 +1,14 @@
 [wrap-file]
-directory = curl-8.10.1
-source_url = https://github.com/curl/curl/releases/download/curl-8_10_1/curl-8.10.1.tar.xz
-source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/curl_8.10.1-1/curl-8.10.1.tar.xz
-source_filename = curl-8.10.1.tar.xz
-source_hash = 73a4b0e99596a09fa5924a4fb7e4b995a85fda0d18a2c02ab9cf134bebce04ee
-patch_filename = curl_8.10.1-1_patch.zip
-patch_url = https://wrapdb.mesonbuild.com/v2/curl_8.10.1-1/get_patch
-patch_hash = 707c28f35fc9b0e8d68c0c2800712007612f922a31da9637ce706a2159f3ddd8
-wrapdb_version = 8.10.1-1
+directory = curl-8.12.1
+source_url = https://github.com/curl/curl/releases/download/curl-8_12_1/curl-8.12.1.tar.xz
+source_fallback_url = https://wrapdb.mesonbuild.com/v2/curl_8.12.1-2/get_source/curl-8.12.1.tar.xz
+source_filename = curl-8.12.1.tar.xz
+source_hash = 0341f1ed97a26c811abaebd37d62b833956792b7607ea3f15d001613c76de202
+patch_filename = curl_8.12.1-2_patch.zip
+patch_url = https://wrapdb.mesonbuild.com/v2/curl_8.12.1-2/get_patch
+patch_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/curl_8.12.1-2/curl_8.12.1-2_patch.zip
+patch_hash = bfd8886cc76ccfab52b1b0472e6c682cdf5374a4c30be2427326f2f495de4eba
+wrapdb_version = 8.12.1-2
 
 [provide]
 dependency_names = libcurl
diff --git a/subprojects/expat.wrap b/subprojects/expat.wrap
index 0e9292f..67c4ae8 100644
--- a/subprojects/expat.wrap
+++ b/subprojects/expat.wrap
@@ -1,13 +1,14 @@
 [wrap-file]
-directory = expat-2.7.1
-source_url = https://github.com/libexpat/libexpat/releases/download/R_2_7_1/expat-2.7.1.tar.xz
-source_filename = expat-2.7.1.tar.bz2
-source_hash = 354552544b8f99012e5062f7d570ec77f14b412a3ff5c7d8d0dae62c0d217c30
-patch_filename = expat_2.7.1-1_patch.zip
-patch_url = https://wrapdb.mesonbuild.com/v2/expat_2.7.1-1/get_patch
-patch_hash = fe28cbbc427a7c9787d08b969ad54d19f59d8dd18294b4a18651cecfc789d4ef
-source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/expat_2.7.1-1/expat-2.7.1.tar.bz2
-wrapdb_version = 2.7.1-1
+directory = expat-2.8.4
+source_url = https://github.com/libexpat/libexpat/releases/download/R_2_8_4/expat-2.8.4.tar.xz
+source_filename = expat-2.8.4.tar.xz
+source_hash = 656ae1cc8da3b4ea513bb4e254f33e6243938084c0ec6239da873376b09985a7
+source_fallback_url = https://wrapdb.mesonbuild.com/v2/expat_2.8.4-1/get_source/expat-2.8.4.tar.xz
+patch_filename = expat_2.8.4-1_patch.zip
+patch_url = https://wrapdb.mesonbuild.com/v2/expat_2.8.4-1/get_patch
+patch_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/expat_2.8.4-1/expat_2.8.4-1_patch.zip
+patch_hash = 221c537a6cfd8d55ea2d41600ee7f32d8c0659cf6e41511e9e7a2f294b21cb92
+wrapdb_version = 2.8.4-1
 
 [provide]
-expat = expat_dep
+dependency_names = expat
diff --git a/subprojects/openssl.wrap b/subprojects/openssl.wrap
index 873d551..e775bb1 100644
--- a/subprojects/openssl.wrap
+++ b/subprojects/openssl.wrap
@@ -1,15 +1,14 @@
 [wrap-file]
-directory = openssl-3.0.8
-source_url = https://www.openssl.org/source/openssl-3.0.8.tar.gz
-source_filename = openssl-3.0.8.tar.gz
-source_hash = 6c13d2bf38fdf31eac3ce2a347073673f5d63263398f1f69d0df4a41253e4b3e
-patch_filename = openssl_3.0.8-3_patch.zip
-patch_url = https://wrapdb.mesonbuild.com/v2/openssl_3.0.8-3/get_patch
-patch_hash = 300da189e106942347d61a4a4295aa2edbcf06184f8d13b4cee0bed9fb936963
-source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/openssl_3.0.8-3/openssl-3.0.8.tar.gz
-wrapdb_version = 3.0.8-3
+directory = openssl-3.0.10
+source_url = https://www.openssl.org/source/openssl-3.0.10.tar.gz
+source_filename = openssl-3.0.10.tar.gz
+source_hash = 1761d4f5b13a1028b9b6f3d4b8e17feb0cedc9370f6afe61d7193d2cdce83323
+source_fallback_url = https://wrapdb.mesonbuild.com/v2/openssl_3.0.10-1/get_source/openssl-3.0.10.tar.gz
+patch_filename = openssl_3.0.10-1_patch.zip
+patch_url = https://wrapdb.mesonbuild.com/v2/openssl_3.0.10-1/get_patch
+patch_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/openssl_3.0.10-1/openssl_3.0.10-1_patch.zip
+patch_hash = 2d142b7e3b1ac092cf67cb4891594c4a2d044aa92624c617a8dcbfe4f056d907
+wrapdb_version = 3.0.10-1
 
 [provide]
-libcrypto = libcrypto_dep
-libssl = libssl_dep
-openssl = openssl_dep
+dependency_names = libcrypto, libssl, openssl
diff --git a/subprojects/pcre2.wrap b/subprojects/pcre2.wrap
index f45c968..a2ff626 100644
--- a/subprojects/pcre2.wrap
+++ b/subprojects/pcre2.wrap
@@ -1,16 +1,14 @@
 [wrap-file]
-directory = pcre2-10.45
-source_url = https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.45/pcre2-10.45.tar.bz2
-source_filename = pcre2-10.45.tar.bz2
-source_hash = 21547f3516120c75597e5b30a992e27a592a31950b5140e7b8bfde3f192033c4
-patch_filename = pcre2_10.45-2_patch.zip
-patch_url = https://wrapdb.mesonbuild.com/v2/pcre2_10.45-2/get_patch
-patch_hash = 7c6f34b703708652a404f9dc2769c67658c437b6043573295fa3428a9b7a6807
-source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/pcre2_10.45-2/pcre2-10.45.tar.bz2
-wrapdb_version = 10.45-2
+directory = pcre2-10.48
+source_url = https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.48/pcre2-10.48.tar.bz2
+source_filename = pcre2-10.48.tar.bz2
+source_hash = b6c68fdf6f3ac31388b50aa89ff0fc49c00c987c16e7b5146491d12003f2c8ed
+source_fallback_url = https://wrapdb.mesonbuild.com/v2/pcre2_10.48-1/get_source/pcre2-10.48.tar.bz2
+patch_filename = pcre2_10.48-1_patch.zip
+patch_url = https://wrapdb.mesonbuild.com/v2/pcre2_10.48-1/get_patch
+patch_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/pcre2_10.48-1/pcre2_10.48-1_patch.zip
+patch_hash = fbcc964804a921b02ea78fc0ff1558d5999b224db8361087828e30012023525c
+wrapdb_version = 10.48-1
 
 [provide]
-libpcre2-8 = libpcre2_8
-libpcre2-16 = libpcre2_16
-libpcre2-32 = libpcre2_32
-libpcre2-posix = libpcre2_posix
+dependency_names = libpcre2-8, libpcre2-16, libpcre2-32, libpcre2-posix
diff --git a/subprojects/zlib.wrap b/subprojects/zlib.wrap
index aa14de1..0626401 100644
--- a/subprojects/zlib.wrap
+++ b/subprojects/zlib.wrap
@@ -1,13 +1,14 @@
 [wrap-file]
-directory = zlib-1.3.1
-source_url = http://zlib.net/fossils/zlib-1.3.1.tar.gz
-source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/zlib_1.3.1-1/zlib-1.3.1.tar.gz
-source_filename = zlib-1.3.1.tar.gz
-source_hash = 9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23
-patch_filename = zlib_1.3.1-1_patch.zip
-patch_url = https://wrapdb.mesonbuild.com/v2/zlib_1.3.1-1/get_patch
-patch_hash = e79b98eb24a75392009cec6f99ca5cdca9881ff20bfa174e8b8926d5c7a47095
-wrapdb_version = 1.3.1-1
+directory = zlib-1.3.2
+source_url = https://zlib.net/zlib-1.3.2.tar.xz
+source_fallback_url = https://wrapdb.mesonbuild.com/v2/zlib_1.3.2-1/get_source/zlib-1.3.2.tar.xz
+source_filename = zlib-1.3.2.tar.xz
+source_hash = d7a0654783a4da529d1bb793b7ad9c3318020af77667bcae35f95d0e42a792f3
+patch_filename = zlib_1.3.2-1_patch.zip
+patch_url = https://wrapdb.mesonbuild.com/v2/zlib_1.3.2-1/get_patch
+patch_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/zlib_1.3.2-1/zlib_1.3.2-1_patch.zip
+patch_hash = 5ae7a2e92f823df118cfb8c1b23d94e3117864392b3446581d669049b2fba6dd
+wrapdb_version = 1.3.2-1
 
 [provide]
-zlib = zlib_dep
+dependency_names = zlib
diff --git a/t/README b/t/README
index f831c53..fd7e850 100644
--- a/t/README
+++ b/t/README
@@ -448,8 +448,8 @@
 use in the test scripts. Recognized values for <hash-algo> are "sha1"
 and "sha256".
 
-GIT_TEST_DEFAULT_REF_FORMAT=<format> specifies which ref storage format to use
-in the test scripts. Recognized values for <format> are "files" and
+GIT_TEST_DEFAULT_REF_STORAGE_FORMAT=<format> specifies which ref storage format
+to use in the test scripts. Recognized values for <format> are "files" and
 "reftable".
 
 GIT_TEST_NO_WRITE_REV_INDEX=<boolean>, when true disables the
diff --git a/t/helper/meson.build b/t/helper/meson.build
index 3235f10..ae513b4 100644
--- a/t/helper/meson.build
+++ b/t/helper/meson.build
@@ -83,6 +83,7 @@
 
 test_tool = executable('test-tool',
   sources: test_tool_sources,
+  c_pch: '../../tools/precompiled.h',
   dependencies: [libgit_commonmain],
 )
 bin_wrappers += test_tool
diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c
index f181f0c..950db78 100644
--- a/t/helper/test-parse-options.c
+++ b/t/helper/test-parse-options.c
@@ -351,8 +351,10 @@ static int parse_subcommand__cmd(int argc, const char **argv,
 	parse_opt_subcommand_fn *fn = NULL;
 	int opt = 0;
 	struct option options[] = {
-		OPT_SUBCOMMAND("subcmd-one", &fn, subcmd_one),
+		OPT_GROUP("Subcommands"),
+		OPT_SUBCOMMAND_F("subcmd-one", &fn, subcmd_one, "the first subcommand", 0),
 		OPT_SUBCOMMAND("subcmd-two", &fn, subcmd_two),
+		OPT_GROUP("Options"),
 		OPT_INTEGER('o', "opt", &opt, "an integer option"),
 		OPT_END()
 	};
diff --git a/t/helper/test-reach.c b/t/helper/test-reach.c
index 5d86a96..8ea77a7 100644
--- a/t/helper/test-reach.c
+++ b/t/helper/test-reach.c
@@ -127,7 +127,7 @@ int cmd__reach(int ac, const char **av)
 		print_sorted_commit_ids(list);
 		commit_list_free(list);
 	} else if (!strcmp(av[1], "can_all_from_reach")) {
-		printf("%s(X,Y):%d\n", av[1], can_all_from_reach(X, Y, 1));
+		printf("%s(X,Y):%d\n", av[1], can_all_from_reach(r, X, Y, 1));
 	} else if (!strcmp(av[1], "can_all_from_reach_with_flag")) {
 		struct commit_list *iter = Y;
 
@@ -136,7 +136,8 @@ int cmd__reach(int ac, const char **av)
 			iter = iter->next;
 		}
 
-		printf("%s(X,_,_,0,0):%d\n", av[1], can_all_from_reach_with_flag(&X_obj, 2, 4, 0, 0));
+		printf("%s(X,_,_,0,0):%d\n", av[1],
+		       can_all_from_reach_with_flag(r, &X_obj, 2, 4, 0, 0));
 	} else if (!strcmp(av[1], "commit_contains")) {
 		struct ref_filter filter = REF_FILTER_INIT;
 		struct contains_cache cache;
diff --git a/t/helper/test-read-graph.c b/t/helper/test-read-graph.c
index 9f07b9c..aceac4f 100644
--- a/t/helper/test-read-graph.c
+++ b/t/helper/test-read-graph.c
@@ -2,8 +2,12 @@
 
 #include "test-tool.h"
 #include "commit-graph.h"
+#include "commit.h"
+#include "hex.h"
+#include "object-name.h"
 #include "repository.h"
 #include "odb.h"
+#include "odb/source.h"
 #include "bloom.h"
 #include "setup.h"
 
@@ -77,11 +81,11 @@ int cmd__read_graph(int argc, const char **argv)
 	int ret = 0;
 
 	setup_git_directory(the_repository);
-	source = the_repository->objects->sources;
+	source = the_repository->objects->source;
 
 	prepare_repo_settings(the_repository);
 
-	graph = read_commit_graph_one(source);
+	graph = read_commit_graph_one(the_repository, source->path);
 	if (!graph) {
 		ret = 1;
 		goto done;
@@ -91,7 +95,25 @@ int cmd__read_graph(int argc, const char **argv)
 		dump_graph_info(graph);
 	else if (!strcmp(argv[1], "bloom-filters"))
 		dump_graph_bloom_filters(graph);
-	else {
+	else if (!strcmp(argv[1], "commit-info")) {
+		int i;
+		for (i = 2; i < argc; i++) {
+			struct object_id oid;
+			struct commit *c;
+
+			if (repo_get_oid(the_repository, argv[i], &oid))
+				die("not a valid object name: '%s'", argv[i]);
+			c = lookup_commit_in_graph(the_repository, &oid);
+			if (!c) {
+				fprintf(stderr, "%s: not in graph\n", argv[i]);
+				ret = 1;
+				continue;
+			}
+			printf("%s generation %"PRIuMAX"\n",
+			       oid_to_hex(&oid),
+			       (uintmax_t)commit_graph_generation(c));
+		}
+	} else {
 		fprintf(stderr, "unknown sub-command: '%s'\n", argv[1]);
 		ret = 1;
 	}
diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c
index 83b07c6..a80d815 100644
--- a/t/helper/test-read-midx.c
+++ b/t/helper/test-read-midx.c
@@ -16,13 +16,13 @@ static struct multi_pack_index *setup_midx(const char *object_dir,
 					   struct odb_source_packed **out)
 {
 	struct odb_source_packed *packed;
-	struct odb_source *source;
+	struct odb_files_dir *dir;
 
 	setup_git_directory(the_repository);
 
-	source = odb_find_source(the_repository->objects, object_dir);
-	if (source) {
-		packed = odb_source_files_downcast(source)->packed;
+	dir = odb_source_files_find_dir(the_repository->objects, object_dir);
+	if (dir) {
+		packed = dir->packed;
 	} else {
 		packed = odb_source_packed_new(the_repository->objects,
 					       object_dir, false);
@@ -90,7 +90,11 @@ 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);
-			midx_fill_entry(m, &oid, &e, NULL);
+			if (midx_fill_entry(m, &oid, &e, NULL) !=
+			    MIDX_FILL_HIT) {
+				ret = error(_("failed to load pack entry"));
+				goto out;
+			}
 
 			printf("%s %"PRIu64"\t%s\n",
 			       oid_to_hex(&oid), e.offset, e.p->pack_name);
diff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c
index fc49faf..d9f2ca1 100644
--- a/t/helper/test-reftable.c
+++ b/t/helper/test-reftable.c
@@ -103,7 +103,16 @@ static int dump_table(struct reftable_merged_table *mt)
 	if (err < 0)
 		return err;
 
-	algop = &hash_algos[hash_algo_by_id(reftable_merged_table_hash_id(mt))];
+	switch (reftable_merged_table_hash_id(mt)) {
+	case REFTABLE_HASH_SHA1:
+		algop = &hash_algos[GIT_HASH_SHA1];
+		break;
+	case REFTABLE_HASH_SHA256:
+		algop = &hash_algos[GIT_HASH_SHA256];
+		break;
+	default:
+		die("unknown reftable hash function: %d", reftable_merged_table_hash_id(mt));
+	}
 
 	while (1) {
 		err = reftable_iterator_next_ref(&it, &ref);
@@ -154,7 +163,7 @@ static int dump_table(struct reftable_merged_table *mt)
 			       log.update_index);
 			break;
 		case REFTABLE_LOG_UPDATE:
-			printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n",
+			printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %d\n",
 			       log.refname, log.update_index,
 			       log.value.update.name ? log.value.update.name : "",
 			       log.value.update.email ? log.value.update.email : "",
diff --git a/t/meson.build b/t/meson.build
index 3ca7b27..11b3752 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -30,7 +30,6 @@
 ]
 
 clar_sources = [
-  'unit-tests/clar/clar.c',
   'unit-tests/unit-test.c',
   'unit-tests/lib-oid.c',
   'unit-tests/lib-reftable.c'
@@ -49,7 +48,7 @@
 )
 clar_sources += clar_decls_h
 
-clar_sources += custom_target(
+clar_suite_h = custom_target(
   input: clar_decls_h,
   output: 'clar.suite',
   command : [
@@ -66,6 +65,13 @@
   c_args: [
     '-DGIT_CLAR_DECLS_H="' + clar_decls_h.full_path() + '"',
   ],
+  c_pch: '../tools/precompiled.h',
+  link_with: static_library('clar',
+    sources: [
+      'unit-tests/clar/clar.c',
+      clar_suite_h,
+    ],
+  ),
   dependencies: [libgit_commonmain],
 )
 test('unit-tests', clar_unit_tests, kwargs: test_kwargs)
@@ -407,6 +413,7 @@
   't3452-history-split.sh',
   't3453-history-fixup.sh',
   't3454-history-drop.sh',
+  't3455-history-squash.sh',
   't3500-cherry.sh',
   't3501-revert-cherry-pick.sh',
   't3502-cherry-pick-merge.sh',
@@ -652,6 +659,7 @@
   't5409-colorize-remote-messages.sh',
   't5410-receive-pack.sh',
   't5411-proc-receive-hook.sh',
+  't5412-receive-report-hook.sh',
   't5500-fetch-pack.sh',
   't5501-fetch-push-alternates.sh',
   't5502-quickfetch.sh',
diff --git a/t/perf/p1401-ref-store-tombstones.sh b/t/perf/p1401-ref-store-tombstones.sh
index 9e3d803..37ffe0c 100755
--- a/t/perf/p1401-ref-store-tombstones.sh
+++ b/t/perf/p1401-ref-store-tombstones.sh
@@ -5,7 +5,7 @@
 . ./perf-lib.sh
 
 test_expect_success "setup" '
-	git init --ref-format=reftable repo &&
+	git init --ref-storage-format=reftable repo &&
 	blob=$(echo foo | git -C repo hash-object -w --stdin) &&
 	for i in $(test_seq 8000)
 	do
@@ -24,7 +24,7 @@
 '
 
 test_expect_success "setup asymmetric" '
-	git init --ref-format=reftable repo2 &&
+	git init --ref-storage-format=reftable repo2 &&
 	blob=$(echo foo | git -C repo2 hash-object -w --stdin) &&
 	for i in $(test_seq 8000)
 	do
diff --git a/t/perf/p5551-fetch-rescan.sh b/t/perf/p5551-fetch-rescan.sh
index b99dc23..0b3ff63 100755
--- a/t/perf/p5551-fetch-rescan.sh
+++ b/t/perf/p5551-fetch-rescan.sh
@@ -45,7 +45,7 @@
 	(
 		cd child &&
 		$MODERN_GIT for-each-ref --format="delete %(refname)" refs/remotes |
-		$MODERN_GIT update-ref --stdin &&
+		$MODERN_GIT update-ref --stdin --no-deref &&
 		rm -vf .git/objects/$(echo $obj | sed "s|^..|&/|") &&
 
 		git fetch
diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh
index 2ac0078..846ecc3 100644
--- a/t/perf/perf-lib.sh
+++ b/t/perf/perf-lib.sh
@@ -135,7 +135,7 @@
 	source_git="$("$MODERN_GIT" -C "$source" rev-parse --git-dir)"
 	objects_dir="$("$MODERN_GIT" -C "$source" rev-parse --git-path objects)"
 	common_dir="$("$MODERN_GIT" -C "$source" rev-parse --git-common-dir)"
-	refformat="$("$MODERN_GIT" -C "$source" rev-parse --show-ref-format)"
+	ref_storage_format="$("$MODERN_GIT" -C "$source" rev-parse --show-ref-storage-format)"
 	objectformat="$("$MODERN_GIT" -C "$source" rev-parse --show-object-format)"
 	mkdir -p "$repo/.git"
 	(
@@ -153,7 +153,7 @@
 	) &&
 	(
 		cd "$repo" &&
-		"$MODERN_GIT" init -q --ref-format="$refformat" --object-format="$objectformat" &&
+		"$MODERN_GIT" init -q --ref-storage-format="$ref_storage_format" --object-format="$objectformat" &&
 		test_perf_do_repo_symlink_config_ &&
 		mv .git/hooks .git/hooks-disabled 2>/dev/null &&
 		if test -f .git/index.lock
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index 5cf2e5a..f1d227f 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -640,45 +640,55 @@
 	test_grep "invalid value for ${SQ}extensions.refstorage${SQ}: ${SQ}garbage${SQ}" err
 '
 
-test_expect_success 'init with GIT_DEFAULT_REF_FORMAT=garbage' '
+test_expect_success 'init with GIT_DEFAULT_REF_STORAGE_FORMAT=garbage' '
 	test_when_finished "rm -rf refformat" &&
 	cat >expect <<-EOF &&
-	fatal: unknown ref storage format ${SQ}garbage${SQ}
+	fatal: unknown ref storage format specified via GIT_DEFAULT_REF_STORAGE_FORMAT: ${SQ}garbage${SQ}
 	EOF
-	test_must_fail env GIT_DEFAULT_REF_FORMAT=garbage git init refformat 2>err &&
+	test_must_fail env GIT_DEFAULT_REF_STORAGE_FORMAT=garbage git init refformat 2>err &&
 	test_cmp expect err
 '
 
-test_expect_success 'init warns about invalid init.defaultRefFormat' '
+test_expect_success 'GIT_REF_STORAGE_FORMAT refuses to reinitialize with different storage format' '
+	test_when_finished "rm -rf refbackend" &&
+	git init --ref-storage-format=files refbackend &&
+	cat >expect <<-EOF &&
+	fatal: attempt to reinitialize repository with different reference storage format
+	EOF
+	test_must_fail env GIT_REF_STORAGE_FORMAT=reftable git init refbackend 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 'init warns about invalid init.defaultRefStorageFormat' '
 	test_when_finished "rm -rf repo" &&
-	test_config_global init.defaultRefFormat garbage &&
+	test_config_global init.defaultRefStorageFormat garbage &&
 
 	echo "warning: unknown ref storage format ${SQ}garbage${SQ}" >expect &&
 	git init repo 2>err &&
 	test_cmp expect err &&
 
-	git -C repo rev-parse --show-ref-format >actual &&
-	echo $GIT_DEFAULT_REF_FORMAT >expected &&
+	git -C repo rev-parse --show-ref-storage-format >actual &&
+	echo $GIT_DEFAULT_REF_STORAGE_FORMAT >expected &&
 	test_cmp expected actual
 '
 
 test_expect_success 'default ref format' '
 	test_when_finished "rm -rf refformat" &&
 	(
-		sane_unset GIT_DEFAULT_REF_FORMAT &&
+		sane_unset GIT_DEFAULT_REF_STORAGE_FORMAT &&
 		git init refformat
 	) &&
-	git version --build-options | sed -ne "s/^default-ref-format: //p" >expect &&
-	git -C refformat rev-parse --show-ref-format >actual &&
+	git version --build-options | sed -ne "s/^default-ref-storage-format: //p" >expect &&
+	git -C refformat rev-parse --show-ref-storage-format >actual &&
 	test_cmp expect actual
 '
 
 backends="files reftable"
 for format in $backends
 do
-	test_expect_success DEFAULT_REPO_FORMAT "init with GIT_DEFAULT_REF_FORMAT=$format" '
+	test_expect_success DEFAULT_REPO_FORMAT "init with GIT_DEFAULT_REF_STORAGE_FORMAT=$format" '
 		test_when_finished "rm -rf refformat" &&
-		GIT_DEFAULT_REF_FORMAT=$format git init refformat &&
+		GIT_DEFAULT_REF_STORAGE_FORMAT=$format git init refformat &&
 
 		if test $format = files
 		then
@@ -692,64 +702,64 @@
 		test_cmp expect actual &&
 
 		echo $format >expect &&
-		git -C refformat rev-parse --show-ref-format >actual &&
+		git -C refformat rev-parse --show-ref-storage-format >actual &&
 		test_cmp expect actual
 	'
 
-	test_expect_success "init with --ref-format=$format" '
+	test_expect_success "init with --ref-storage-format=$format" '
 		test_when_finished "rm -rf refformat" &&
-		git init --ref-format=$format refformat &&
+		git init --ref-storage-format=$format refformat &&
 		echo $format >expect &&
-		git -C refformat rev-parse --show-ref-format >actual &&
+		git -C refformat rev-parse --show-ref-storage-format >actual &&
 		test_cmp expect actual
 	'
 
-	test_expect_success "init with init.defaultRefFormat=$format" '
+	test_expect_success "init with init.defaultRefStorageFormat=$format" '
 		test_when_finished "rm -rf refformat" &&
-		test_config_global init.defaultRefFormat $format &&
+		test_config_global init.defaultRefStorageFormat $format &&
 		(
-			sane_unset GIT_DEFAULT_REF_FORMAT &&
+			sane_unset GIT_DEFAULT_REF_STORAGE_FORMAT &&
 			git init refformat
 		) &&
 
 		echo $format >expect &&
-		git -C refformat rev-parse --show-ref-format >actual &&
+		git -C refformat rev-parse --show-ref-storage-format >actual &&
 		test_cmp expect actual
 	'
 
-	test_expect_success "--ref-format=$format overrides GIT_DEFAULT_REF_FORMAT" '
+	test_expect_success "--ref-storage-format=$format overrides GIT_DEFAULT_REF_STORAGE_FORMAT" '
 		test_when_finished "rm -rf refformat" &&
-		GIT_DEFAULT_REF_FORMAT=garbage git init --ref-format=$format refformat &&
+		GIT_DEFAULT_REF_STORAGE_FORMAT=garbage git init --ref-storage-format=$format refformat &&
 		echo $format >expect &&
-		git -C refformat rev-parse --show-ref-format >actual &&
+		git -C refformat rev-parse --show-ref-storage-format >actual &&
 		test_cmp expect actual
 	'
 
-	test_expect_success "reinit repository with GIT_DEFAULT_REF_FORMAT=$format does not change format" '
+	test_expect_success "reinit repository with GIT_DEFAULT_REF_STORAGE_FORMAT=$format does not change format" '
 		test_when_finished "rm -rf refformat" &&
 		git init refformat &&
-		git -C refformat rev-parse --show-ref-format >expect &&
-		GIT_DEFAULT_REF_FORMAT=$format git init refformat &&
-		git -C refformat rev-parse --show-ref-format >actual &&
+		git -C refformat rev-parse --show-ref-storage-format >expect &&
+		GIT_DEFAULT_REF_STORAGE_FORMAT=$format git init refformat &&
+		git -C refformat rev-parse --show-ref-storage-format >actual &&
 		test_cmp expect actual
 	'
 done
 
-test_expect_success "--ref-format= overrides GIT_DEFAULT_REF_FORMAT" '
+test_expect_success "--ref-storage-format= overrides GIT_DEFAULT_REF_STORAGE_FOMAT" '
 	test_when_finished "rm -rf refformat" &&
-	GIT_DEFAULT_REF_FORMAT=files git init --ref-format=reftable refformat &&
+	GIT_DEFAULT_REF_STORAGE_FORMAT=files git init --ref-storage-format=reftable refformat &&
 	echo reftable >expect &&
-	git -C refformat rev-parse --show-ref-format >actual &&
+	git -C refformat rev-parse --show-ref-storage-format >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success "GIT_DEFAULT_REF_FORMAT= overrides init.defaultRefFormat" '
+test_expect_success "GIT_DEFAULT_REF_STORAGE_FORMAT= overrides init.defaultRefStorageFormat" '
 	test_when_finished "rm -rf refformat" &&
-	test_config_global init.defaultRefFormat files &&
+	test_config_global init.defaultRefStorageFormat files &&
 
-	GIT_DEFAULT_REF_FORMAT=reftable git init refformat &&
+	GIT_DEFAULT_REF_STORAGE_FORMAT=reftable git init refformat &&
 	echo reftable >expect &&
-	git -C refformat rev-parse --show-ref-format >actual &&
+	git -C refformat rev-parse --show-ref-storage-format >actual &&
 	test_cmp expect actual
 '
 
@@ -757,33 +767,33 @@
 	test_when_finished "rm -rf refformat" &&
 	test_config_global feature.experimental true &&
 	(
-		sane_unset GIT_DEFAULT_REF_FORMAT &&
+		sane_unset GIT_DEFAULT_REF_STORAGE_FORMAT &&
 		git init refformat
 	) &&
 	echo reftable >expect &&
-	git -C refformat rev-parse --show-ref-format >actual &&
+	git -C refformat rev-parse --show-ref-storage-format >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success "init.defaultRefFormat overrides feature.experimental=true" '
+test_expect_success "init.defaultRefStorageFormat overrides feature.experimental=true" '
 	test_when_finished "rm -rf refformat" &&
 	test_config_global feature.experimental true &&
-	test_config_global init.defaultRefFormat files &&
+	test_config_global init.defaultRefStorageFormat files &&
 	(
-		sane_unset GIT_DEFAULT_REF_FORMAT &&
+		sane_unset GIT_DEFAULT_REF_STORAGE_FORMAT &&
 		git init refformat
 	) &&
 	echo files >expect &&
-	git -C refformat rev-parse --show-ref-format >actual &&
+	git -C refformat rev-parse --show-ref-storage-format >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success "GIT_DEFAULT_REF_FORMAT= overrides feature.experimental=true" '
+test_expect_success "GIT_DEFAULT_REF_STORAGE_FORMAT= overrides feature.experimental=true" '
 	test_when_finished "rm -rf refformat" &&
 	test_config_global feature.experimental true &&
-	GIT_DEFAULT_REF_FORMAT=files git init refformat &&
+	GIT_DEFAULT_REF_STORAGE_FORMAT=files git init refformat &&
 	echo files >expect &&
-	git -C refformat rev-parse --show-ref-format >actual &&
+	git -C refformat rev-parse --show-ref-storage-format >actual &&
 	test_cmp expect actual
 '
 
@@ -791,10 +801,10 @@
 do
 	test_expect_success "re-init with same format ($from_format)" '
 		test_when_finished "rm -rf refformat" &&
-		git init --ref-format=$from_format refformat &&
-		git init --ref-format=$from_format refformat &&
+		git init --ref-storage-format=$from_format refformat &&
+		git init --ref-storage-format=$from_format refformat &&
 		echo $from_format >expect &&
-		git -C refformat rev-parse --show-ref-format >actual &&
+		git -C refformat rev-parse --show-ref-storage-format >actual &&
 		test_cmp expect actual
 	'
 
@@ -807,25 +817,25 @@
 
 		test_expect_success "re-init with different format fails ($from_format -> $to_format)" '
 			test_when_finished "rm -rf refformat" &&
-			git init --ref-format=$from_format refformat &&
+			git init --ref-storage-format=$from_format refformat &&
 			cat >expect <<-EOF &&
 			fatal: attempt to reinitialize repository with different reference storage format
 			EOF
-			test_must_fail git init --ref-format=$to_format refformat 2>err &&
+			test_must_fail git init --ref-storage-format=$to_format refformat 2>err &&
 			test_cmp expect err &&
 			echo $from_format >expect &&
-			git -C refformat rev-parse --show-ref-format >actual &&
+			git -C refformat rev-parse --show-ref-storage-format >actual &&
 			test_cmp expect actual
 		'
 	done
 done
 
-test_expect_success 'init with --ref-format=garbage' '
+test_expect_success 'init with --ref-storage-format=garbage' '
 	test_when_finished "rm -rf refformat" &&
 	cat >expect <<-EOF &&
-	fatal: unknown ref storage format ${SQ}garbage${SQ}
+	fatal: unknown ref storage format specified via command line: ${SQ}garbage${SQ}
 	EOF
-	test_must_fail git init --ref-format=garbage refformat 2>err &&
+	test_must_fail git init --ref-storage-format=garbage refformat 2>err &&
 	test_cmp expect err
 '
 
@@ -932,8 +942,8 @@
 test_expect_success 'init with includeIf.onbranch condition' '
 	test_when_finished "rm -rf repo" &&
 	git -c includeIf.onbranch:main.path=nonexistent init repo &&
-	echo $GIT_DEFAULT_REF_FORMAT >expect &&
-	git -C repo rev-parse --show-ref-format >actual &&
+	echo $GIT_DEFAULT_REF_STORAGE_FORMAT >expect &&
+	git -C repo rev-parse --show-ref-storage-format >actual &&
 	test_cmp expect actual
 '
 
@@ -941,8 +951,8 @@
 	test_when_finished "rm -rf repo" &&
 	mkdir repo &&
 	git -c includeIf.onbranch:nonexistent.path=/does/not/exist init repo &&
-	echo $GIT_DEFAULT_REF_FORMAT >expect &&
-	git -C repo rev-parse --show-ref-format >actual &&
+	echo $GIT_DEFAULT_REF_STORAGE_FORMAT >expect &&
+	git -C repo rev-parse --show-ref-storage-format >actual &&
 	test_cmp expect actual
 '
 
@@ -950,8 +960,8 @@
 	test_when_finished "rm -rf repo" &&
 	git init repo &&
 	git -c includeIf.onbranch:nonexistent.path=/does/not/exist init repo &&
-	echo $GIT_DEFAULT_REF_FORMAT >expect &&
-	git -C repo rev-parse --show-ref-format >actual &&
+	echo $GIT_DEFAULT_REF_STORAGE_FORMAT >expect &&
+	git -C repo rev-parse --show-ref-storage-format >actual &&
 	test_cmp expect actual
 '
 
diff --git a/t/t0007-git-var.sh b/t/t0007-git-var.sh
index 2b60317..e43becf 100755
--- a/t/t0007-git-var.sh
+++ b/t/t0007-git-var.sh
@@ -276,4 +276,157 @@
 	)
 '
 
+test_expect_success 'get a variable with -z' '
+	printf "%sQ" "$(git var GIT_AUTHOR_IDENT)" >expect &&
+	git var -z GIT_AUTHOR_IDENT >actual.raw &&
+	nul_to_q <actual.raw >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'get multi-valued variable with -z' '
+	TRASHDIR="$(test-tool path-utils normalize_path_copy "$(pwd)")" &&
+	HOME="$TRASHDIR" XDG_CONFIG_HOME="$TRASHDIR/foo" git var -z GIT_CONFIG_GLOBAL >actual.raw &&
+	printf "%sQ%sQ" "$TRASHDIR/foo/git/config" "$TRASHDIR/.gitconfig" >expect &&
+	nul_to_q <actual.raw >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git var -l -z' '
+	git var -l -z >actual &&
+	tr "\0" "\n" <actual >actual.lines &&
+	git var GIT_AUTHOR_IDENT >expect &&
+	sed -n "/^GIT_AUTHOR_IDENT$/{n;p;}" actual.lines >actual.author &&
+	test_cmp expect actual.author &&
+	echo false >expect &&
+	sed -n "/^core\.bare$/{n;p;}" actual.lines >actual.bare &&
+	test_cmp expect actual.bare
+'
+
+test_expect_success 'options must precede variable arguments' '
+	test_must_fail git var GIT_AUTHOR_IDENT -z
+'
+
+test_expect_success 'get multiple variables' '
+	test_tick &&
+	cat >expect <<-EOF &&
+	GIT_AUTHOR_IDENT=$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	GIT_COMMITTER_IDENT=$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	EOF
+	git var GIT_AUTHOR_IDENT GIT_COMMITTER_IDENT >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'get multiple variables with -z' '
+	test_tick &&
+	printf "GIT_AUTHOR_IDENT\n%sQGIT_COMMITTER_IDENT\n%sQ" \
+		"$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" \
+		"$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE" >expect &&
+	git var -z GIT_AUTHOR_IDENT GIT_COMMITTER_IDENT >actual.raw &&
+	nul_to_q <actual.raw >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'get multiple variables including multi-valued variable' '
+	test_tick &&
+	TRASHDIR="$(test-tool path-utils normalize_path_copy "$(pwd)")" &&
+	cat >expect <<-EOF &&
+	GIT_AUTHOR_IDENT=$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	GIT_CONFIG_GLOBAL=$TRASHDIR/foo/git/config
+	GIT_CONFIG_GLOBAL=$TRASHDIR/.gitconfig
+	GIT_COMMITTER_IDENT=$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	EOF
+	HOME="$TRASHDIR" XDG_CONFIG_HOME="$TRASHDIR/foo" \
+		git var GIT_AUTHOR_IDENT GIT_CONFIG_GLOBAL GIT_COMMITTER_IDENT >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'get multiple variables including multi-valued variable with -z' '
+	test_tick &&
+	TRASHDIR="$(test-tool path-utils normalize_path_copy "$(pwd)")" &&
+	printf "GIT_AUTHOR_IDENT\n%sQGIT_CONFIG_GLOBAL\n%sQGIT_CONFIG_GLOBAL\n%sQ" \
+		"$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" \
+		"$TRASHDIR/foo/git/config" "$TRASHDIR/.gitconfig" >expect &&
+	HOME="$TRASHDIR" XDG_CONFIG_HOME="$TRASHDIR/foo" \
+		git var -z GIT_AUTHOR_IDENT GIT_CONFIG_GLOBAL >actual.raw &&
+	nul_to_q <actual.raw >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'variable without a value is omitted but is not an error' '
+	test_tick &&
+	cat >expect <<-EOF &&
+	GIT_AUTHOR_IDENT=$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	GIT_COMMITTER_IDENT=$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	EOF
+	test_env GIT_CONFIG_GLOBAL= \
+		git var GIT_AUTHOR_IDENT GIT_CONFIG_GLOBAL GIT_COMMITTER_IDENT >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'a single variable without a value still exits with 1' '
+	test_env GIT_CONFIG_GLOBAL= test_expect_code 1 git var GIT_CONFIG_GLOBAL >out &&
+	test_must_be_empty out
+'
+
+test_expect_success 'unknown variable is a usage error' '
+	test_must_fail git var GIT_AUTHOR_IDENT NO_SUCH_VARIABLE 2>err &&
+	test_grep usage err
+'
+
+test_expect_success 'get author identity components' '
+	test_tick &&
+	echo "$GIT_AUTHOR_NAME" >expect.name &&
+	echo "$GIT_AUTHOR_EMAIL" >expect.email &&
+	echo "$GIT_AUTHOR_DATE" >expect.date &&
+	git var GIT_AUTHOR_NAME >actual.name &&
+	git var GIT_AUTHOR_EMAIL >actual.email &&
+	git var GIT_AUTHOR_DATE >actual.date &&
+	test_cmp expect.name actual.name &&
+	test_cmp expect.email actual.email &&
+	test_cmp expect.date actual.date
+'
+
+test_expect_success 'get committer identity components' '
+	test_tick &&
+	echo "$GIT_COMMITTER_NAME" >expect.name &&
+	echo "$GIT_COMMITTER_EMAIL" >expect.email &&
+	echo "$GIT_COMMITTER_DATE" >expect.date &&
+	git var GIT_COMMITTER_NAME >actual.name &&
+	git var GIT_COMMITTER_EMAIL >actual.email &&
+	git var GIT_COMMITTER_DATE >actual.date &&
+	test_cmp expect.name actual.name &&
+	test_cmp expect.email actual.email &&
+	test_cmp expect.date actual.date
+'
+
+test_expect_success !FAIL_PREREQS,!AUTOIDENT 'identity components are strict' '
+	(
+		sane_unset GIT_COMMITTER_NAME &&
+		sane_unset GIT_COMMITTER_EMAIL &&
+		test_must_fail git var GIT_COMMITTER_NAME
+	)
+'
+
+test_expect_success 'get several identity components at once' '
+	test_tick &&
+	cat >expect <<-EOF &&
+	GIT_AUTHOR_NAME=$GIT_AUTHOR_NAME
+	GIT_AUTHOR_EMAIL=$GIT_AUTHOR_EMAIL
+	GIT_COMMITTER_NAME=$GIT_COMMITTER_NAME
+	GIT_COMMITTER_EMAIL=$GIT_COMMITTER_EMAIL
+	EOF
+	git var GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git var -l lists the identity components' '
+	git var -l >actual &&
+	test_grep "^GIT_AUTHOR_NAME=" actual &&
+	test_grep "^GIT_AUTHOR_EMAIL=" actual &&
+	test_grep "^GIT_AUTHOR_DATE=" actual &&
+	test_grep "^GIT_COMMITTER_NAME=" actual &&
+	test_grep "^GIT_COMMITTER_EMAIL=" actual &&
+	test_grep "^GIT_COMMITTER_DATE=" actual
+'
+
 test_done
diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh
index 449fff4..ec55bb1 100755
--- a/t/t0040-parse-options.sh
+++ b/t/t0040-parse-options.sh
@@ -629,6 +629,22 @@
 	test_cmp expect actual
 '
 
+test_expect_success 'subcommand - usage lists subcommands with help text under their group' '
+	test-tool parse-subcommand cmd -h >actual &&
+	cat >expect <<-\EOF &&
+	usage: <...> cmd subcmd-one
+	   or: <...> cmd subcmd-two
+
+	Subcommands
+	    subcmd-one            the first subcommand
+
+	Options
+	    -o, --[no-]opt <n>    an integer option
+
+	EOF
+	test_cmp expect actual
+'
+
 test_expect_success 'subcommand - no subcommand shows error and usage' '
 	test_expect_code 129 test-tool parse-subcommand cmd 2>err &&
 	test_grep "^error: need a subcommand" err &&
diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh
index 788e9a1..a54685e 100755
--- a/t/t0410-partial-clone.sh
+++ b/t/t0410-partial-clone.sh
@@ -709,6 +709,39 @@
 	test_grep ! "[?]$FILE_HASH" out
 '
 
+test_expect_success 'lazy-fetch does not recurse infinitely between two promisor remotes' '
+	rm -rf full partial1.git partial2.git &&
+
+	# Create a repo with a blob
+	test_create_repo full &&
+	test_config -C full uploadpack.allowfilter 1 &&
+	test_config -C full uploadpack.allowanysha1inwant 1 &&
+	test_commit -C full create-a-file file.txt &&
+	FILE_HASH=$(git -C full rev-parse HEAD:file.txt) &&
+
+	# Create partial clone repos without blobs
+	git clone --filter=blob:none --bare "file://$(pwd)/full" partial1.git &&
+	git clone --filter=blob:none --bare "file://$(pwd)/full" partial2.git &&
+	test_config -C partial1.git uploadpack.allowfilter 1 &&
+	test_config -C partial1.git uploadpack.allowanysha1inwant 1 &&
+	test_config -C partial2.git uploadpack.allowfilter 1 &&
+	test_config -C partial2.git uploadpack.allowanysha1inwant 1 &&
+
+	# Configure the partial repos as remotes of each other
+	git -C partial2.git remote set-url origin "file://$(pwd)/partial1.git" &&
+	git -C partial1.git remote set-url origin "file://$(pwd)/partial2.git" &&
+
+	# Make sure lazy fetching fails
+	test_must_fail env GIT_TRACE="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 \
+		git -C partial1.git cat-file -e "$FILE_HASH" 2>err &&
+	test_grep "too many nested lazy fetches" err &&
+
+	# Make sure the recursion was bounded, i.e. that only
+	# MAX_LAZY_FETCH_DEPTH "git fetch" subprocesses were spawned
+	grep "run_command: GIT_INTERNAL_LAZY_FETCH_DEPTH" trace >fetches &&
+	test_line_count = 5 fetches
+'
+
 test_expect_success 'push should not fetch new commit objects' '
 	rm -rf server client &&
 	test_create_repo server &&
diff --git a/t/t0450-txt-doc-vs-help.sh b/t/t0450-txt-doc-vs-help.sh
index 55c0fb3..a2da2b0 100755
--- a/t/t0450-txt-doc-vs-help.sh
+++ b/t/t0450-txt-doc-vs-help.sh
@@ -116,13 +116,13 @@
 	if grep -q "^$builtin$" "$TEST_DIRECTORY"/t0450/adoc-missing
 	then
 		test_expect_success "$builtin appropriately marked as not having .adoc" '
-			! test -f "$adoc"
+			test_path_is_missing "$adoc"
 		'
 	else
 		test_set_prereq "$preq"
 
 		test_expect_success "$builtin appropriately marked as having .adoc" '
-			test -f "$adoc"
+			test_path_is_file "$adoc"
 		'
 	fi
 
diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh
index bbbf6fa..62b0851 100755
--- a/t/t0600-reffiles-backend.sh
+++ b/t/t0600-reffiles-backend.sh
@@ -4,8 +4,8 @@
 
 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
+GIT_TEST_DEFAULT_REF_STORAGE_FORMAT=files
+export GIT_TEST_DEFAULT_REF_STORAGE_FORMAT
 
 . ./test-lib.sh
 
@@ -540,4 +540,44 @@
 	test "$(test_readlink .git/TEST_SYMREF_HEAD)" = refs/heads/new
 '
 
+test_expect_success 'deleting a root ref does not lock packed-refs' '
+	test_when_finished "rm -rf root-ref" &&
+	git init root-ref &&
+	(
+		cd root-ref &&
+		test_commit initial &&
+		git update-ref AUTO_MERGE HEAD &&
+		git pack-refs --all &&
+		test_path_is_file .git/AUTO_MERGE &&
+		cp .git/packed-refs expect &&
+		: >.git/packed-refs.lock &&
+		git update-ref --no-deref -d AUTO_MERGE &&
+		test_path_is_missing .git/AUTO_MERGE &&
+		test_path_is_file .git/packed-refs.lock &&
+		test_cmp expect .git/packed-refs
+	)
+'
+
+test_expect_success 'deleting root and packed refs in one transaction requires packed-refs lock' '
+	test_when_finished "rm -rf root-ref" &&
+	git init root-ref &&
+	(
+		cd root-ref &&
+		test_commit initial &&
+		git update-ref refs/heads/packed-branch HEAD &&
+		git pack-refs --all &&
+		test_path_is_missing .git/refs/heads/packed-branch &&
+		git update-ref AUTO_MERGE HEAD &&
+		git refs list --include-root-refs >expect &&
+		: >.git/packed-refs.lock &&
+		test_must_fail git update-ref --no-deref --stdin 2>err <<-EOF &&
+		delete AUTO_MERGE
+		delete refs/heads/packed-branch
+		EOF
+		test_grep "Unable to create .*packed-refs.lock" err &&
+		git refs list --include-root-refs >actual &&
+		test_cmp expect actual
+	)
+'
+
 test_done
diff --git a/t/t0601-reffiles-pack-refs.sh b/t/t0601-reffiles-pack-refs.sh
index 3c70697..17283f8 100755
--- a/t/t0601-reffiles-pack-refs.sh
+++ b/t/t0601-reffiles-pack-refs.sh
@@ -12,8 +12,8 @@
 
 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
+GIT_TEST_DEFAULT_REF_STORAGE_FORMAT=files
+export GIT_TEST_DEFAULT_REF_STORAGE_FORMAT
 
 . ./test-lib.sh
 
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 1325982..1078141 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -4,8 +4,8 @@
 
 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
+GIT_TEST_DEFAULT_REF_STORAGE_FORMAT=files
+export GIT_TEST_DEFAULT_REF_STORAGE_FORMAT
 
 . ./test-lib.sh
 
diff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh
index 35e98b4..a702745 100755
--- a/t/t0610-reftable-basics.sh
+++ b/t/t0610-reftable-basics.sh
@@ -7,8 +7,8 @@
 
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
-GIT_TEST_DEFAULT_REF_FORMAT=reftable
-export GIT_TEST_DEFAULT_REF_FORMAT
+GIT_TEST_DEFAULT_REF_STORAGE_FORMAT=reftable
+export GIT_TEST_DEFAULT_REF_STORAGE_FORMAT
 
 . ./test-lib.sh
 
@@ -27,7 +27,7 @@
 	test_path_is_dir repo/.git/reftable &&
 	test_path_is_file repo/.git/reftable/tables.list &&
 	echo reftable >expect &&
-	git -C repo rev-parse --show-ref-format >actual &&
+	git -C repo rev-parse --show-ref-storage-format >actual &&
 	test_cmp expect actual
 '
 
@@ -38,7 +38,7 @@
 	sha256
 	reftable
 	EOF
-	git -C repo rev-parse --show-object-format --show-ref-format >actual &&
+	git -C repo rev-parse --show-object-format --show-ref-storage-format >actual &&
 	test_cmp expect actual
 '
 
@@ -49,7 +49,7 @@
 	sha256
 	reftable
 	EOF
-	git -C repo rev-parse --show-object-format --show-ref-format >actual &&
+	git -C repo rev-parse --show-object-format --show-ref-storage-format >actual &&
 	test_cmp expect actual
 '
 
@@ -59,28 +59,28 @@
 	test_commit -C repo A &&
 
 	git -C repo for-each-ref >expect &&
-	git init --ref-format=reftable repo &&
+	git init --ref-storage-format=reftable repo &&
 	git -C repo for-each-ref >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'init: reinitializing files with reftable backend fails' '
 	test_when_finished "rm -rf repo" &&
-	git init --ref-format=files repo &&
+	git init --ref-storage-format=files repo &&
 	test_commit -C repo file &&
 
 	cp repo/.git/HEAD expect &&
-	test_must_fail git init --ref-format=reftable repo &&
+	test_must_fail git init --ref-storage-format=reftable repo &&
 	test_cmp expect repo/.git/HEAD
 '
 
 test_expect_success 'init: reinitializing reftable with files backend fails' '
 	test_when_finished "rm -rf repo" &&
-	git init --ref-format=reftable repo &&
+	git init --ref-storage-format=reftable repo &&
 	test_commit -C repo file &&
 
 	cp repo/.git/HEAD expect &&
-	test_must_fail git init --ref-format=files repo &&
+	test_must_fail git init --ref-storage-format=files repo &&
 	test_cmp expect repo/.git/HEAD
 '
 
@@ -156,45 +156,45 @@
 
 	git clone repo cloned &&
 	echo reftable >expect &&
-	git -C cloned rev-parse --show-ref-format >actual &&
+	git -C cloned rev-parse --show-ref-storage-format >actual &&
 	test_cmp expect actual &&
 	test_path_is_file cloned/file1
 '
 
 test_expect_success 'clone: can clone reffiles into reftable repository' '
 	test_when_finished "rm -rf reffiles reftable" &&
-	git init --ref-format=files reffiles &&
+	git init --ref-storage-format=files reffiles &&
 	test_commit -C reffiles A &&
-	git clone --ref-format=reftable ./reffiles reftable &&
+	git clone --ref-storage-format=reftable ./reffiles reftable &&
 
 	git -C reffiles rev-parse HEAD >expect &&
 	git -C reftable rev-parse HEAD >actual &&
 	test_cmp expect actual &&
 
-	git -C reftable rev-parse --show-ref-format >actual &&
+	git -C reftable rev-parse --show-ref-storage-format >actual &&
 	echo reftable >expect &&
 	test_cmp expect actual &&
 
-	git -C reffiles rev-parse --show-ref-format >actual &&
+	git -C reffiles rev-parse --show-ref-storage-format >actual &&
 	echo files >expect &&
 	test_cmp expect actual
 '
 
 test_expect_success 'clone: can clone reftable into reffiles repository' '
 	test_when_finished "rm -rf reffiles reftable" &&
-	git init --ref-format=reftable reftable &&
+	git init --ref-storage-format=reftable reftable &&
 	test_commit -C reftable A &&
-	git clone --ref-format=files ./reftable reffiles &&
+	git clone --ref-storage-format=files ./reftable reffiles &&
 
 	git -C reftable rev-parse HEAD >expect &&
 	git -C reffiles rev-parse HEAD >actual &&
 	test_cmp expect actual &&
 
-	git -C reftable rev-parse --show-ref-format >actual &&
+	git -C reftable rev-parse --show-ref-storage-format >actual &&
 	echo reftable >expect &&
 	test_cmp expect actual &&
 
-	git -C reffiles rev-parse --show-ref-format >actual &&
+	git -C reffiles rev-parse --show-ref-storage-format >actual &&
 	echo files >expect &&
 	test_cmp expect actual
 '
@@ -837,6 +837,41 @@
 	)
 '
 
+test_expect_success 'reflog: timezone offset is stored in minutes' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		GIT_COMMITTER_DATE="1234567890 -1200" git commit --allow-empty -m min &&
+		GIT_COMMITTER_DATE="1234567890 +0530" git commit --allow-empty -m east &&
+		GIT_COMMITTER_DATE="1234567890 -0830" git commit --allow-empty -m west &&
+		GIT_COMMITTER_DATE="1234567890 +1400" git commit --allow-empty -m max &&
+
+		# The reftable format specifies the timezone as the offset from
+		# UTC in minutes, whereas Git uses the parsed form of "+HHMM"
+		# internally. Verify that we do the conversion when writing.
+		for table in .git/reftable/*.ref
+		do
+			test-tool dump-reftable -t "$table" || return 1
+		done >dump &&
+		sed -n "s/^log{refs\/heads\/main([0-9]*) .* 1234567890 //p" dump >actual &&
+		cat >expect <<-\EOF &&
+		840
+		-510
+		330
+		-720
+		EOF
+		test_cmp expect actual &&
+
+		# And verify that we convert back when reading.
+		test-tool ref-store main for-each-reflog-ent refs/heads/main >entries &&
+		test_grep "1234567890 -1200	commit (initial): min" entries &&
+		test_grep "1234567890 +0530	commit: east" entries &&
+		test_grep "1234567890 -0830	commit: west" entries &&
+		test_grep "1234567890 +1400	commit: max" entries
+	)
+'
+
 test_expect_success 'reflog: can store empty logs' '
 	test_when_finished "rm -rf repo" &&
 	git init repo &&
diff --git a/t/t0611-reftable-httpd.sh b/t/t0611-reftable-httpd.sh
index 5e05b9c..b030814 100755
--- a/t/t0611-reftable-httpd.sh
+++ b/t/t0611-reftable-httpd.sh
@@ -10,7 +10,7 @@
 REPO="$HTTPD_DOCUMENT_ROOT_PATH/repo"
 
 test_expect_success 'serving ls-remote' '
-	git init --ref-format=reftable -b main "$REPO" &&
+	git init --ref-storage-format=reftable -b main "$REPO" &&
 	cd "$REPO" &&
 	test_commit m1 &&
 	>.git/git-daemon-export-ok &&
diff --git a/t/t0612-reftable-jgit-compatibility.sh b/t/t0612-reftable-jgit-compatibility.sh
index 7df2ad5..eddfd9a 100755
--- a/t/t0612-reftable-jgit-compatibility.sh
+++ b/t/t0612-reftable-jgit-compatibility.sh
@@ -4,8 +4,8 @@
 
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
-GIT_TEST_DEFAULT_REF_FORMAT=reftable
-export GIT_TEST_DEFAULT_REF_FORMAT
+GIT_TEST_DEFAULT_REF_STORAGE_FORMAT=reftable
+export GIT_TEST_DEFAULT_REF_STORAGE_FORMAT
 
 # JGit does not support the 'link' DIRC extension.
 GIT_TEST_SPLIT_INDEX=0
diff --git a/t/t0613-reftable-write-options.sh b/t/t0613-reftable-write-options.sh
index a65960d..0b27d75 100755
--- a/t/t0613-reftable-write-options.sh
+++ b/t/t0613-reftable-write-options.sh
@@ -2,8 +2,8 @@
 
 test_description='reftable write options'
 
-GIT_TEST_DEFAULT_REF_FORMAT=reftable
-export GIT_TEST_DEFAULT_REF_FORMAT
+GIT_TEST_DEFAULT_REF_STORAGE_FORMAT=reftable
+export GIT_TEST_DEFAULT_REF_STORAGE_FORMAT
 # Disable auto-compaction for all tests as we explicitly control repacking of
 # refs.
 GIT_TEST_REFTABLE_AUTOCOMPACTION=false
diff --git a/t/t0614-reftable-fsck.sh b/t/t0614-reftable-fsck.sh
index d24b87f..9576a82 100755
--- a/t/t0614-reftable-fsck.sh
+++ b/t/t0614-reftable-fsck.sh
@@ -2,8 +2,8 @@
 
 test_description='Test reftable backend consistency check'
 
-GIT_TEST_DEFAULT_REF_FORMAT=reftable
-export GIT_TEST_DEFAULT_REF_FORMAT
+GIT_TEST_DEFAULT_REF_STORAGE_FORMAT=reftable
+export GIT_TEST_DEFAULT_REF_STORAGE_FORMAT
 
 . ./test-lib.sh
 
diff --git a/t/t1050-large.sh b/t/t1050-large.sh
index d295c26..9523345 100755
--- a/t/t1050-large.sh
+++ b/t/t1050-large.sh
@@ -87,6 +87,23 @@
 	test $count = 1
 '
 
+test_expect_success 'add large file with loose object in batch fsync' '
+	test_when_finished "rm -rf batch" &&
+	git init batch &&
+	(
+		cd batch &&
+		git config core.bigFileThreshold 5 &&
+		echo foo >1-small &&
+		echo foobar >2-large &&
+
+		git -c core.fsync=loose-object -c core.fsyncMethod=batch \
+			add 1-small 2-large &&
+
+		git cat-file -e :1-small &&
+		git cat-file -e :2-large
+	)
+'
+
 test_expect_success 'checkout a large file' '
 	large1=$(git rev-parse :large1) &&
 	git update-index --add --cacheinfo 100644 $large1 another &&
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index 269fdaa..1a164c9 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -2361,7 +2361,7 @@
 	'
 
 	test_expect_success CASE_INSENSITIVE_FS "stdin $type batch-updates existing reference" '
-		git init --ref-format=reftable repo &&
+		git init --ref-storage-format=reftable repo &&
 		test_when_finished "rm -fr repo" &&
 		(
 			cd repo &&
diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
index 8f78cf4..93b5b49 100755
--- a/t/t1410-reflog.sh
+++ b/t/t1410-reflog.sh
@@ -153,6 +153,72 @@
 	test_grep ! "error: [Oo]bject .* not a commit" err
 '
 
+test_expect_success 'reflog expire keeps reachable entries for 90 days' '
+	test_when_finished "rm -rf reachable-keep" &&
+	git init reachable-keep &&
+	(
+		cd reachable-keep &&
+		timestamp=$(test-tool date timestamp "60.days.ago") &&
+		timestamp=${timestamp#* -> } &&
+		test_commit --no-tag --date "$timestamp +0000" old &&
+		git reflog expire --all &&
+		test_stdout_line_count = 1 git reflog refs/heads/main
+	)
+'
+
+test_expect_success 'reflog expire removes reachable entries after 90 days' '
+	test_when_finished "rm -rf reachable-expire" &&
+	git init reachable-expire &&
+	(
+		cd reachable-expire &&
+		timestamp=$(test-tool date timestamp "100.days.ago") &&
+		timestamp=${timestamp#* -> } &&
+		test_commit --no-tag --date "$timestamp +0000" old &&
+		git reflog expire --all &&
+		test_stdout_line_count = 0 git reflog refs/heads/main
+	)
+'
+
+test_expect_success 'reflog expire keeps unreachable entries for 30 days' '
+	test_when_finished "rm -rf unreachable-keep" &&
+	git init unreachable-keep &&
+	(
+		cd unreachable-keep &&
+		test_commit --no-tag base &&
+		base=$(git rev-parse HEAD) &&
+		timestamp=$(test-tool date timestamp "20.days.ago") &&
+		timestamp=${timestamp#* -> } &&
+		test_commit --no-tag --date "$timestamp +0000" old &&
+		old=$(git rev-parse HEAD) &&
+		git update-ref refs/heads/main "$base" &&
+		git rev-list --all --objects >reachable &&
+		test_grep ! "$old" reachable &&
+		git reflog expire --all &&
+		git reflog --format='%H' refs/heads/main >actual &&
+		test_grep "$old" actual
+	)
+'
+
+test_expect_success 'reflog expire removes unreachable entries after 30 days' '
+	test_when_finished "rm -rf unreachable-expire" &&
+	git init unreachable-expire &&
+	(
+		cd unreachable-expire &&
+		test_commit --no-tag base &&
+		base=$(git rev-parse HEAD) &&
+		timestamp=$(test-tool date timestamp "40.days.ago") &&
+		timestamp=${timestamp#* -> } &&
+		test_commit --no-tag --date "$timestamp +0000" old &&
+		old=$(git rev-parse HEAD) &&
+		git update-ref refs/heads/main "$base" &&
+		git rev-list --all --objects >reachable &&
+		test_grep ! "$old" reachable &&
+		git reflog expire --all &&
+		git reflog --format='%H' refs/heads/main >actual &&
+		test_grep ! "$old" actual
+	)
+'
+
 test_expect_success 'corrupt and check' '
 
 	corrupt $F &&
diff --git a/t/t1419-exclude-refs.sh b/t/t1419-exclude-refs.sh
index 04797ae..50237a5 100755
--- a/t/t1419-exclude-refs.sh
+++ b/t/t1419-exclude-refs.sh
@@ -21,13 +21,13 @@
 	local nr="$1"
 	local trace="$2"
 
-	case "$GIT_DEFAULT_REF_FORMAT" in
+	case "$GIT_DEFAULT_REF_STORAGE_FORMAT" in
 	files)
 		grep -q "name:jumps_made value:$nr$" $trace;;
 	reftable)
 		grep -q "name:reseeks_made value:$nr$" $trace;;
 	*)
-		BUG "unhandled ref format $GIT_DEFAULT_REF_FORMAT";;
+		BUG "unhandled ref storage format $GIT_DEFAULT_REF_STORAGE_FORMAT";;
 	esac
 }
 
@@ -93,13 +93,13 @@
 	for_each_ref refs/heads/foo refs/heads/quux >expect &&
 
 	test_cmp expect actual &&
-	case "$GIT_DEFAULT_REF_FORMAT" in
+	case "$GIT_DEFAULT_REF_STORAGE_FORMAT" in
 	files)
 		assert_jumps 1 perf;;
 	reftable)
 		assert_jumps 2 perf;;
 	*)
-		BUG "unhandled ref format $GIT_DEFAULT_REF_FORMAT";;
+		BUG "unhandled ref storage format $GIT_DEFAULT_REF_STORAGE_FORMAT";;
 	esac
 '
 
@@ -125,13 +125,13 @@
 	for_each_ref refs/heads/quux >expect &&
 
 	test_cmp expect actual &&
-	case "$GIT_DEFAULT_REF_FORMAT" in
+	case "$GIT_DEFAULT_REF_STORAGE_FORMAT" in
 	files)
 		assert_jumps 1 perf;;
 	reftable)
 		assert_jumps 3 perf;;
 	*)
-		BUG "unhandled ref format $GIT_DEFAULT_REF_FORMAT";;
+		BUG "unhandled ref storage format $GIT_DEFAULT_REF_STORAGE_FORMAT";;
 	esac
 '
 
@@ -141,13 +141,13 @@
 	for_each_ref refs/heads/bar refs/heads/quux >expect &&
 
 	test_cmp expect actual &&
-	case "$GIT_DEFAULT_REF_FORMAT" in
+	case "$GIT_DEFAULT_REF_STORAGE_FORMAT" in
 	files)
 		assert_jumps 1 perf;;
 	reftable)
 		assert_jumps 2 perf;;
 	*)
-		BUG "unhandled ref format $GIT_DEFAULT_REF_FORMAT";;
+		BUG "unhandled ref storage format $GIT_DEFAULT_REF_STORAGE_FORMAT";;
 	esac
 '
 
diff --git a/t/t1423-ref-backend.sh b/t/t1423-ref-backend.sh
index fd47d77..ee2bb66 100755
--- a/t/t1423-ref-backend.sh
+++ b/t/t1423-ref-backend.sh
@@ -12,7 +12,7 @@
 #   <uri> is the new URI to be set for the ref storage.
 #   <cmd> is the git subcommand to be run in the repository.
 #   <via> if 'config', set the backend via the 'extensions.refStorage' config.
-#         if 'env', set the backend via the 'GIT_REFERENCE_BACKEND' env.
+#         if 'env', set the backend via the 'GIT_REF_STORAGE_FORMAT' env.
 run_with_uri () {
 	repo=$1 &&
 	backend=$2 &&
@@ -23,7 +23,7 @@
 	git -C "$repo" config set core.repositoryformatversion 1 &&
 	if test "$via" = "env"
 	then
-		test_env GIT_REFERENCE_BACKEND="$uri" git -C "$repo" $cmd
+		test_env GIT_REF_STORAGE_FORMAT="$uri" git -C "$repo" $cmd
 	elif test "$via" = "config"
 	then
 		git -C "$repo" config set extensions.refStorage "$uri" &&
@@ -40,7 +40,7 @@
 #   <backend> is the original ref storage of the repo.
 #   <uri> is the new URI to be set for the ref storage.
 #   <via> if 'config', set the backend via the 'extensions.refStorage' config.
-#         if 'env', set the backend via the 'GIT_REFERENCE_BACKEND' env.
+#         if 'env', set the backend via the 'GIT_REF_STORAGE_FORMAT' env.
 #   <err_msg> (optional) if set, check if 'git-refs(1)' failed with the provided msg.
 test_refs_backend () {
 	repo=$1 &&
@@ -54,7 +54,7 @@
 	then
 		if test "$via" = "env"
 		then
-			test_env GIT_REFERENCE_BACKEND="$uri" test_must_fail git -C "$repo" refs list 2>err
+			test_env GIT_REF_STORAGE_FORMAT="$uri" test_must_fail git -C "$repo" refs list 2>err
 		elif test "$via" = "config"
 		then
 			git -C "$repo" config set extensions.refStorage "$uri" &&
@@ -83,7 +83,7 @@
 	test_cmp expect $gitdir/HEAD
 
 	# verify that backend specific files exist.
-	case "$GIT_DEFAULT_REF_FORMAT" in
+	case "$GIT_DEFAULT_REF_STORAGE_FORMAT" in
 	files)
 		test_path_is_dir $refdir/refs/heads &&
 		test_path_is_file $refdir/HEAD;;
@@ -91,7 +91,7 @@
 		test_path_is_dir $refdir/reftable &&
 		test_path_is_file $refdir/reftable/tables.list;;
 	*)
-		BUG "unhandled ref format $GIT_DEFAULT_REF_FORMAT";;
+		BUG "unhandled ref storage format $GIT_DEFAULT_REF_STORAGE_FORMAT";;
 	esac
 }
 
@@ -137,14 +137,14 @@
 
 		test_expect_success "$method: read from $to_format backend, $dir dir" '
 			test_when_finished "rm -rf repo" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			(
 				cd repo &&
 				test_commit 1 &&
 				test_commit 2 &&
 				test_commit 3 &&
 
-				git refs migrate --dry-run --ref-format=$to_format >out &&
+				git refs migrate --dry-run --ref-storage-format=$to_format >out &&
 				BACKEND_PATH="$dir/$(sed "s/.* ${SQ}.git\/\(.*\)${SQ}/\1/" out)" &&
 				test_refs_backend . $from_format "$to_format://$BACKEND_PATH" "$method"
 			)
@@ -152,14 +152,14 @@
 
 		test_expect_success "$method: write to $to_format backend, $dir dir" '
 			test_when_finished "rm -rf repo" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			(
 				cd repo &&
 				test_commit 1 &&
 				test_commit 2 &&
 				test_commit 3 &&
 
-				git refs migrate --dry-run --ref-format=$to_format >out &&
+				git refs migrate --dry-run --ref-storage-format=$to_format >out &&
 				BACKEND_PATH="$dir/$(sed "s/.* ${SQ}.git\/\(.*\)${SQ}/\1/" out)" &&
 
 				test_refs_backend . $from_format "$to_format://$BACKEND_PATH" "$method" &&
@@ -179,14 +179,14 @@
 
 		test_expect_success "$method: with worktree and $to_format backend, $dir dir" '
 			test_when_finished "rm -rf repo wt" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			(
 				cd repo &&
 				test_commit 1 &&
 				test_commit 2 &&
 				test_commit 3 &&
 
-				git refs migrate --dry-run --ref-format=$to_format >out &&
+				git refs migrate --dry-run --ref-storage-format=$to_format >out &&
 				BACKEND_PATH="$dir/$(sed "s/.* ${SQ}.git\/\(.*\)${SQ}/\1/" out)" &&
 
 				run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \
@@ -210,7 +210,7 @@
 	test_expect_success "migrating repository to $to_format with alternate refs directory" '
 		test_when_finished "rm -rf repo refdir" &&
 		mkdir refdir &&
-		GIT_REFERENCE_BACKEND="${from_format}://$(pwd)/refdir" git init repo &&
+		GIT_REF_STORAGE_FORMAT="${from_format}://$(pwd)/refdir" git init repo &&
 		(
 			cd repo &&
 
@@ -218,7 +218,7 @@
 			test_commit 2 &&
 			test_commit 3 &&
 
-			git refs migrate --ref-format=$to_format &&
+			git refs migrate --ref-storage-format=$to_format &&
 			git refs list >out &&
 			test_grep "refs/tags/1"	out &&
 			test_grep "refs/tags/2"	out &&
@@ -235,7 +235,7 @@
 	test_when_finished "rm -rf repo refdir" &&
 	mkdir refdir &&
 	BACKEND="$(test_detect_ref_format)://$(pwd)/refdir" &&
-	GIT_REFERENCE_BACKEND=$BACKEND git init repo &&
+	GIT_REF_STORAGE_FORMAT=$BACKEND git init repo &&
 	verify_files_exist repo/.git refdir &&
 	(
 		cd repo &&
@@ -254,6 +254,36 @@
 	)
 '
 
+test_expect_success 'initializing repository with --ref-storage-format and payload' '
+	test_when_finished "rm -rf repo refdir" &&
+	mkdir refdir &&
+	BACKEND="$(test_detect_ref_format)://$(pwd)/refdir" &&
+	git init --ref-storage-format="$BACKEND" repo &&
+	verify_files_exist repo/.git refdir &&
+
+	git -C repo config get extensions.refstorage >actual &&
+	echo $BACKEND >expect &&
+	test_cmp expect actual &&
+
+	test_commit -C repo 1 &&
+	git -C repo refs list >out &&
+	test_grep "refs/tags/1" out &&
+
+	# Reinitializing the repository is fine when not specifying any format.
+	git -C repo init &&
+	# Reinitializing with the same backend is fine, too.
+	git -C repo init --ref-storage-format="$BACKEND" &&
+	# Reinitializing without a payload should fail.
+	test_must_fail git -C repo init --ref-storage-format="$(test_detect_ref_format)" 2>err &&
+	test_grep "attempt to reinitialize repository with different reference storage payload" err &&
+	# Reinitializing with a different payload should fail, too.
+	test_must_fail git -C repo init --ref-storage-format="$(test_detect_ref_format)://$(pwd)/other" 2>err &&
+	test_grep "attempt to reinitialize repository with different reference storage payload" err &&
+
+	git -C repo config get extensions.refstorage >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'cloning repository with alt ref directory' '
 	test_when_finished "rm -rf source repo refdir" &&
 	mkdir refdir &&
@@ -264,7 +294,7 @@
 	test_commit -C source 3 &&
 
 	BACKEND="$(test_detect_ref_format)://$(pwd)/refdir" &&
-	GIT_REFERENCE_BACKEND=$BACKEND git clone source repo &&
+	GIT_REF_STORAGE_FORMAT=$BACKEND git clone source repo &&
 
 	git -C repo config get extensions.refstorage >actual &&
 	echo $BACKEND >expect &&
diff --git a/t/t1460-refs-migrate.sh b/t/t1460-refs-migrate.sh
index 8f42697..ecf6411 100755
--- a/t/t1460-refs-migrate.sh
+++ b/t/t1460-refs-migrate.sh
@@ -42,7 +42,7 @@
 		print_all_reflog_entries "$repo" >expect_logs
 	fi &&
 
-	git -C "$repo" refs migrate --ref-format="$format" "$@" &&
+	git -C "$repo" refs migrate --ref-storage-format="$format" "$@" &&
 
 	git -C "$repo" for-each-ref --include-root-refs \
 		--format='%(refname) %(objectname) %(symref)' >actual &&
@@ -53,7 +53,7 @@
 		test_cmp expect_logs actual_logs
 	fi &&
 
-	git -C "$repo" rev-parse --show-ref-format >actual &&
+	git -C "$repo" rev-parse --show-ref-storage-format >actual &&
 	echo "$format" >expect &&
 	test_cmp expect actual
 }
@@ -77,7 +77,7 @@
 	git init repo &&
 	test_must_fail git -C repo refs migrate 2>err &&
 	cat >expect <<-EOF &&
-	usage: missing --ref-format=<format>
+	usage: missing --ref-storage-format=<format>
 	EOF
 	test_cmp expect err
 '
@@ -86,7 +86,7 @@
 	test_when_finished "rm -rf repo" &&
 	git init repo &&
 	test_must_fail git -C repo refs migrate \
-		--ref-format=unknown 2>err &&
+		--ref-storage-format=unknown 2>err &&
 	cat >expect <<-EOF &&
 	error: unknown ref storage format ${SQ}unknown${SQ}
 	EOF
@@ -105,9 +105,9 @@
 
 		test_expect_success "$from_format: migration to same format fails" '
 			test_when_finished "rm -rf repo" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			test_must_fail git -C repo refs migrate \
-				--ref-format=$from_format 2>err &&
+				--ref-storage-format=$from_format 2>err &&
 			cat >expect <<-EOF &&
 			error: repository already uses ${SQ}$from_format${SQ} format
 			EOF
@@ -116,10 +116,10 @@
 
 		test_expect_success "$from_format -> $to_format: migration with worktree fails" '
 			test_when_finished "rm -rf repo" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			git -C repo worktree add wt &&
 			test_must_fail git -C repo refs migrate \
-				--ref-format=$to_format 2>err &&
+				--ref-storage-format=$to_format 2>err &&
 			cat >expect <<-EOF &&
 			error: migrating repositories with worktrees is not supported yet
 			EOF
@@ -128,28 +128,28 @@
 
 		test_expect_success "$from_format -> $to_format: unborn HEAD" '
 			test_when_finished "rm -rf repo" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			test_migration repo "$to_format"
 		'
 
 		test_expect_success "$from_format -> $to_format: single ref" '
 			test_when_finished "rm -rf repo" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			test_commit -C repo initial &&
 			test_migration repo "$to_format"
 		'
 
 		test_expect_success "$from_format -> $to_format: bare repository" '
 			test_when_finished "rm -rf repo repo.git" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			test_commit -C repo initial &&
-			git clone --ref-format=$from_format --mirror repo repo.git &&
+			git clone --ref-storage-format=$from_format --mirror repo repo.git &&
 			test_migration repo.git "$to_format"
 		'
 
 		test_expect_success "$from_format -> $to_format: dangling symref" '
 			test_when_finished "rm -rf repo" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			test_commit -C repo initial &&
 			git -C repo symbolic-ref BROKEN_HEAD refs/heads/nonexistent &&
 			test_migration repo "$to_format" &&
@@ -160,7 +160,7 @@
 
 		test_expect_success "$from_format -> $to_format: broken ref" '
 			test_when_finished "rm -rf repo" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			test_commit -C repo initial &&
 			test-tool -C repo ref-store main update-ref "" refs/heads/broken \
 				"$(test_oid 001)" "$ZERO_OID" REF_SKIP_CREATE_REFLOG,REF_SKIP_OID_VERIFICATION &&
@@ -172,7 +172,7 @@
 
 		test_expect_success "$from_format -> $to_format: pseudo-refs" '
 			test_when_finished "rm -rf repo" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			test_commit -C repo initial &&
 			git -C repo update-ref FOO_HEAD HEAD &&
 			test_migration repo "$to_format"
@@ -180,7 +180,7 @@
 
 		test_expect_success "$from_format -> $to_format: special refs are left alone" '
 			test_when_finished "rm -rf repo" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			test_commit -C repo initial &&
 			git -C repo rev-parse HEAD >repo/.git/MERGE_HEAD &&
 			git -C repo rev-parse MERGE_HEAD &&
@@ -190,7 +190,7 @@
 
 		test_expect_success "$from_format -> $to_format: a bunch of refs" '
 			test_when_finished "rm -rf repo" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 
 			test_commit -C repo initial &&
 			cat >input <<-EOF &&
@@ -208,20 +208,20 @@
 
 		test_expect_success "$from_format -> $to_format: dry-run migration does not modify repository" '
 			test_when_finished "rm -rf repo" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			test_commit -C repo initial &&
 			git -C repo refs migrate --dry-run \
-				--ref-format=$to_format >output &&
+				--ref-storage-format=$to_format >output &&
 			test_grep "Finished dry-run migration of refs" output &&
 			test_path_is_dir repo/.git/ref_migration.* &&
 			echo $from_format >expect &&
-			git -C repo rev-parse --show-ref-format >actual &&
+			git -C repo rev-parse --show-ref-storage-format >actual &&
 			test_cmp expect actual
 		'
 
 		test_expect_success "$from_format -> $to_format: reflogs of symrefs with target deleted" '
 			test_when_finished "rm -rf repo" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			test_commit -C repo initial &&
 			git -C repo branch branch-1 HEAD &&
 			git -C repo symbolic-ref refs/heads/symref refs/heads/branch-1 &&
@@ -234,7 +234,7 @@
 
 		test_expect_success "$from_format -> $to_format: reflogs order is retained" '
 			test_when_finished "rm -rf repo" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			test_commit --date "100005000 +0700" --no-tag -C repo initial &&
 			test_commit --date "100003000 +0700" --no-tag -C repo second &&
 			test_migration repo "$to_format"
@@ -242,7 +242,7 @@
 
 		test_expect_success "$from_format -> $to_format: stash is retained" '
 			test_when_finished "rm -rf repo" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			(
 				cd repo &&
 				test_commit initial A &&
@@ -259,7 +259,7 @@
 
 		test_expect_success "$from_format -> $to_format: skip reflog with --skip-reflog" '
 			test_when_finished "rm -rf repo" &&
-			git init --ref-format=$from_format repo &&
+			git init --ref-storage-format=$from_format repo &&
 			test_commit -C repo initial &&
 			# we see that the repository contains reflogs.
 			git -C repo reflog --all >reflogs &&
@@ -274,7 +274,7 @@
 
 test_expect_success 'multiple reftable blocks with multiple entries' '
 	test_when_finished "rm -rf repo" &&
-	git init --ref-format=files repo &&
+	git init --ref-storage-format=files repo &&
 	test_commit -C repo first &&
 	test_seq -f "create refs/heads/ref-%d HEAD" 5000 |
 	git -C repo update-ref --stdin &&
@@ -286,7 +286,7 @@
 
 test_expect_success 'migrating from files format deletes backend files' '
 	test_when_finished "rm -rf repo" &&
-	git init --ref-format=files repo &&
+	git init --ref-storage-format=files repo &&
 	test_commit -C repo first &&
 	git -C repo pack-refs --all &&
 	test_commit -C repo second &&
@@ -313,7 +313,7 @@
 
 test_expect_success 'migrating from reftable format deletes backend files' '
 	test_when_finished "rm -rf repo" &&
-	git init --ref-format=reftable repo &&
+	git init --ref-storage-format=reftable repo &&
 	test_commit -C repo first &&
 
 	test_path_is_dir repo/.git/reftable &&
diff --git a/t/t1463-refs-optimize.sh b/t/t1463-refs-optimize.sh
index 9afe3c1..b4b9735 100755
--- a/t/t1463-refs-optimize.sh
+++ b/t/t1463-refs-optimize.sh
@@ -8,8 +8,8 @@
 
 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
+GIT_TEST_DEFAULT_REF_STORAGE_FORMAT=files
+export GIT_TEST_DEFAULT_REF_STORAGE_FORMAT
 
 . ./test-lib.sh
 
diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh
index 4174ca4..30bf8a4 100755
--- a/t/t1500-rev-parse.sh
+++ b/t/t1500-rev-parse.sh
@@ -241,19 +241,19 @@
 	)
 '
 
-test_expect_success 'rev-parse --show-ref-format' '
+test_expect_success 'rev-parse --show-ref-storage-format' '
 	test_detect_ref_format >expect &&
-	git rev-parse --show-ref-format >actual &&
+	git rev-parse --show-ref-storage-format >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success 'rev-parse --show-ref-format with invalid storage' '
+test_expect_success 'rev-parse --show-ref-storage-format with invalid storage' '
 	test_when_finished "rm -rf repo" &&
 	git init repo &&
 	(
 		cd repo &&
 		git config extensions.refstorage broken &&
-		test_must_fail git rev-parse --show-ref-format 2>err &&
+		test_must_fail git rev-parse --show-ref-storage-format 2>err &&
 		test_grep "error: invalid value for ${SQ}extensions.refstorage${SQ}: ${SQ}broken${SQ}" err
 	)
 '
diff --git a/t/t1514-rev-parse-push.sh b/t/t1514-rev-parse-push.sh
index d868a08..5a4f168 100755
--- a/t/t1514-rev-parse-push.sh
+++ b/t/t1514-rev-parse-push.sh
@@ -60,6 +60,13 @@
 	resolve topic@{push} refs/remotes/other/topic
 '
 
+test_expect_success '@{p} is short for @{push}' '
+	test_config push.default current &&
+	test_config branch.topic.pushremote other &&
+	resolve topic@{p} refs/remotes/other/topic &&
+	resolve topic@{P} refs/remotes/other/topic
+'
+
 test_expect_success '@{push} with push refspecs' '
 	test_config push.default nothing &&
 	test_config remote.origin.push refs/heads/*:refs/heads/magic/* &&
diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
index c85d390..d115d2d 100755
--- a/t/t1900-repo-info.sh
+++ b/t/t1900-repo-info.sh
@@ -39,10 +39,10 @@
 }
 
 test_repo_info 'ref format files is retrieved correctly' \
-	'git init --ref-format=files' 'format-files' 'references.format' 'files'
+	'git init --ref-storage-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'
+	'git init --ref-storage-format=reftable' 'format-reftable' 'references.format' 'reftable'
 
 test_repo_info 'bare repository = false is retrieved correctly' \
 	'git init' 'nonbare' 'layout.bare' 'false'
@@ -75,7 +75,7 @@
 	references.format=files
 	layout.bare=false
 	EOF
-	git init --ref-format=files ordered &&
+	git init --ref-storage-format=files ordered &&
 	git -C ordered repo info layout.bare references.format layout.bare >actual &&
 	test_cmp expect actual
 '
diff --git a/t/t2021-checkout-overwrite.sh b/t/t2021-checkout-overwrite.sh
index 38c41ae..2b7fff5 100755
--- a/t/t2021-checkout-overwrite.sh
+++ b/t/t2021-checkout-overwrite.sh
@@ -79,4 +79,11 @@
 	test_path_is_file some_dir
 '
 
+test_expect_success 'checkout must not overwrite untracked nested repo' '
+	git checkout -f start &&
+	rm -rf some_dir &&
+	git init some_dir &&
+	test_must_fail git checkout df_conflict
+'
+
 test_done
diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
index ef92704..679a707 100755
--- a/t/t3206-range-diff.sh
+++ b/t/t3206-range-diff.sh
@@ -845,6 +845,92 @@
 	test_cmp expect actual
 '
 
+# Unlike '--notes', '--range-diff-notes' requires a value
+test_expect_success 'format-patch --range-diff-notes requires a value' '
+	cat >expect <<-EOF &&
+	error: option \`range-diff-notes${SQ} requires a value
+	EOF
+	test_must_fail git format-patch --range-diff=main..topic \
+		--cover-letter --range-diff-notes 2>actual &&
+	test_cmp expect actual
+'
+
+# The '--range-diff-notes' has no effect but is allowed
+test_expect_success 'format-patch --range-diff-notes=not-a-note (no --range-diff)' '
+	test_when_finished "rm -f 000?-*" &&
+	git format-patch --range-diff-notes=not-a-note --cover-letter \
+		main..unmodified &&
+	test_file_not_empty 0000-cover-letter* &&
+	test_grep ! "^Range-diff:" 0000-cover-letter* &&
+	test_grep ! "## Notes " 0000-cover-letter*
+'
+
+test_expect_success 'format-patch --range-diff --notes=custom --no-range-diff-notes' '
+	test_when_finished "git notes --ref=custom remove topic unmodified || :" &&
+	git notes --ref=custom add -m "topic note1" topic &&
+	git notes --ref=custom add -m "unmodified note1" unmodified &&
+	test_when_finished "rm -f 000?-*" &&
+	git format-patch --range-diff=main..topic --notes=custom \
+		--no-range-diff-notes --cover-letter \
+		main..unmodified &&
+	test_grep "^Notes (custom):" 0004-* &&
+	test_grep "^Range-diff:" 0000-cover-letter* &&
+	test_grep ! "## Notes (custom) ##" 0000-cover-letter*
+'
+
+test_expect_success 'format-patch --range-diff --no-notes --range-diff-notes=custom' '
+	test_when_finished "git notes --ref=custom remove topic unmodified || :" &&
+	git notes --ref=custom add -m "topic note1" topic &&
+	git notes --ref=custom add -m "unmodified note1" unmodified &&
+	test_when_finished "rm -f 000?-*" &&
+	git format-patch --range-diff=main..topic --no-notes \
+		--range-diff-notes=custom --cover-letter \
+		main..unmodified &&
+	test_grep ! "^Notes (custom):" 0004-* &&
+	test_grep "^Range-diff:" 0000-cover-letter* &&
+	test_grep "## Notes (custom) ##" 0000-cover-letter*
+'
+
+test_expect_success 'format-patch --range-diff --notes=patch --range-diff-notes=rdiff' '
+	test_when_finished "git notes --ref=patch remove topic unmodified || :" &&
+	git notes --ref=patch add -m "only for patch 1" topic &&
+	git notes --ref=patch add -m "only for patch 2" unmodified &&
+	test_when_finished "git notes --ref=rdiff remove topic unmodified || :" &&
+	git notes --ref=rdiff add -m "only for range diff 1" topic &&
+	git notes --ref=rdiff add -m "only for range diff 2" unmodified &&
+	test_when_finished "rm -f 000?-*" &&
+	git format-patch --range-diff=main..topic --notes=patch \
+		--range-diff-notes=rdiff --cover-letter \
+		main..unmodified &&
+	test_grep "^Notes (patch):" 0004-* &&
+	test_grep ! "^Notes (rdiff):" 0004-* &&
+	test_grep "^Range-diff:" 0000-cover-letter* &&
+	test_grep "## Notes (rdiff) ##" 0000-cover-letter* &&
+	test_grep ! "## Notes (patch) ##" 0000-cover-letter*
+'
+
+test_expect_success 'format-patch --range-diff --no-range-diff-notes on single patch' '
+	test_when_finished "git notes --ref=custom remove HEAD 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=main..topic \
+		--no-range-diff-notes -1 --stdout >actual &&
+	test_grep "Notes (custom):" actual &&
+	test_grep "^Range-diff:" actual &&
+	test_grep ! "## Notes (custom) ##" actual
+'
+
+test_expect_success 'format-patch --range-diff --range-diff-notes=custom on single patch' '
+	test_when_finished "git notes --ref=custom remove HEAD unmodified || :" &&
+	git notes --ref=custom add -m "topic note (custom)" HEAD &&
+	git notes --ref=custom add -m "unmodified note (custom)" unmodified &&
+	git format-patch --range-diff=main..topic \
+		--range-diff-notes=custom -1 --stdout >actual &&
+	test_grep ! "Notes (custom):" actual &&
+	test_grep "^Range-diff:" actual &&
+	test_grep "## Notes (custom) ##" actual
+'
+
 test_expect_success '--left-only/--right-only' '
 	git switch --orphan left-right &&
 	test_commit first &&
diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh
index cb5c3a1..8c43ef6 100755
--- a/t/t3418-rebase-continue.sh
+++ b/t/t3418-rebase-continue.sh
@@ -395,4 +395,24 @@
 test_orig_head --apply
 test_orig_head --merge
 
+test_expect_success 'rebase runs auto maintenance once it is done' '
+	# topic and main both add F2, so the pick conflicts and the rebase
+	# stops before the exec runs. "--continue" commits the resolution
+	# first, then runs the exec, which fails and stops it again.
+	git checkout -b auto-maintenance topic &&
+	test_must_fail env GIT_TRACE2_EVENT="$(pwd)/stop.txt" \
+		git rebase -x "git commit --allow-empty -m exec && false" main &&
+	test_subcommand_flex ! git maintenance run --auto <stop.txt &&
+	echo resolved >F2 &&
+	git add F2 &&
+	test_must_fail env GIT_TRACE2_EVENT="$(pwd)/mid.txt" \
+		git rebase --continue &&
+	test_subcommand_flex git commit <mid.txt &&
+	test_subcommand_flex ! git maintenance run --auto <mid.txt &&
+	GIT_TRACE2_EVENT="$(pwd)/end.txt" git rebase --continue &&
+	test_subcommand_flex git maintenance run --auto <end.txt &&
+	grep "\"child_start\".*\"maintenance\"" end.txt >maintenance &&
+	test_line_count = 1 maintenance
+'
+
 test_done
diff --git a/t/t3455-history-squash.sh b/t/t3455-history-squash.sh
new file mode 100755
index 0000000..d21e9d9
--- /dev/null
+++ b/t/t3455-history-squash.sh
@@ -0,0 +1,886 @@
+#!/bin/sh
+
+test_description='tests for git-history squash subcommand'
+
+. ./test-lib.sh
+
+stage_file () {
+	printf "%s\n" "$1" >file &&
+	git add file
+}
+
+commit_with_message () {
+	printf "%b" "$1" >msg &&
+	git commit --allow-empty -qF msg
+}
+
+check_commit_count () {
+	git rev-list --count "$1" >actual &&
+	echo "$2" >expect &&
+	test_cmp expect actual
+}
+
+check_log_subjects () {
+	git log --format="%s" "$1" >actual &&
+	cat >expect &&
+	test_cmp expect actual
+}
+
+check_log_messages () {
+	git log --format="%B" "$1" >actual &&
+	cat >expect &&
+	test_cmp expect actual
+}
+
+# Checks that the author data of two commits matches
+# Usage: check_commit_author <rev1> <rev2>
+check_commit_author () {
+	git show -s --format="%an <%ae> %ad" "$1" >expect &&
+	git show -s --format="%an <%ae> %ad" "$2" >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success 'setup linear history touching two files' '
+	test_commit base file a start &&
+	GIT_AUTHOR_NAME=One GIT_AUTHOR_EMAIL=one@example.com \
+		test_commit one other x &&
+	GIT_AUTHOR_NAME=Two GIT_AUTHOR_EMAIL=two@example.com \
+		test_commit two file c &&
+	GIT_AUTHOR_NAME=Three GIT_AUTHOR_EMAIL=three@example.com \
+		test_commit three file d
+'
+
+test_expect_success 'errors on missing range argument' '
+	test_must_fail git history squash 2>err &&
+	test_grep "expects a revision range" err
+'
+
+test_expect_success 'errors on an empty range' '
+	test_must_fail git history squash HEAD..HEAD 2>err &&
+	test_grep "the revision range is empty" err
+'
+
+test_expect_success 'errors on a single revision that is not a range' '
+	test_must_fail git history squash HEAD 2>err &&
+	test_grep "not a .*range" err &&
+	test_must_fail git history squash HEAD~1 2>err &&
+	test_grep "not a .*range" err
+'
+
+test_expect_success 'errors on a range holding a single commit' '
+	git reset --hard three &&
+	head_before=$(git rev-parse HEAD) &&
+
+	test_must_fail git history squash "HEAD^!" 2>err &&
+	test_grep "single commit; nothing to squash" err &&
+	test_cmp_rev "$head_before" HEAD
+'
+
+test_expect_success 'rejects root commit' '
+	# create a disconnected root commit
+	oid=$(git commit-tree -m root three^{tree}) &&
+	# because we pass --ancestry-path when calling setup_revs() it the
+	# revision walk will only include commits decended from $oid so
+	# we need to give it another --ancestry-path commit to actually walk
+	# any commits.
+	test_must_fail git history squash --ancestry-path=start $oid..three 2>err &&
+	echo "error: cannot squash down to root commit" >expect &&
+	test_cmp expect err
+'
+
+test_expect_success 'rejects multiple tips' '
+	oid=$(git commit-tree -m tip -p start^0 three^{tree}) &&
+	test_must_fail git history squash ^start $oid three~1 2>err &&
+	echo "error: the revision range contains more than one tip commit" >expect &&
+	test_cmp expect err &&
+
+	git reset --hard three &&
+	git history squash --no-edit ^start three~1 three &&
+	test_cmp_rev HEAD~1 start^0 &&
+	test_cmp_rev HEAD^{tree} three^{tree}
+'
+
+test_expect_success 'accepts multiple revision arguments with an exclusion' '
+	git reset --hard three &&
+	git branch -f keep HEAD~2 &&
+	tip_tree=$(git rev-parse HEAD^{tree}) &&
+
+	git history squash --no-edit start..HEAD ^keep &&
+
+	git reflog -1 --format=%gs >actual &&
+	echo "squash: updating start..HEAD ^keep" >expect &&
+	test_cmp expect actual &&
+
+	check_log_subjects start..HEAD <<-\EOF &&
+	two
+	one
+	EOF
+	test_cmp_rev keep HEAD~1 &&
+	test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
+
+	git branch -D keep
+'
+
+test_expect_success 'squashes a branch the current branch is not on' '
+	git reset --hard three &&
+	main=$(git symbolic-ref --short HEAD) &&
+	head_before=$(git rev-parse HEAD) &&
+	git checkout -b off-history start &&
+	test_commit --no-tag off-one off a &&
+	test_commit --no-tag off-two off b &&
+	git checkout "$main" &&
+
+	git history squash --no-edit start..off-history &&
+
+	check_commit_count start..off-history 1 &&
+	test_cmp_rev "$head_before" HEAD &&
+
+	git branch -D off-history
+'
+
+test_expect_success 'squashes a range into a single commit without changing the tree' '
+	git reset --hard three &&
+	head_before=$(git rev-parse HEAD) &&
+	tip_tree=$(git rev-parse HEAD^{tree}) &&
+
+	git history squash --no-edit --dry-run start.. >out &&
+	predicted=$(awk "/^update refs\/heads\// {print \$3}" out) &&
+	test_cmp_rev "$head_before" HEAD &&
+
+	git history squash --no-edit start.. &&
+
+	test "$predicted" = "$(git rev-parse HEAD)" &&
+	check_commit_count start..HEAD 1 &&
+	test_cmp_rev start HEAD^ &&
+	test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
+	check_log_subjects -1 <<-\EOF &&
+	one
+	EOF
+	git reflog >reflog &&
+	test_grep "squash: updating" reflog
+'
+
+test_expect_success 'sanitizes rev-list walk options, before and after --' '
+	git reset --hard three &&
+	tip_tree=$(git rev-parse HEAD^{tree}) &&
+
+	git history squash --no-edit --date-order start.. 2>err &&
+	test_grep "ignoring rev-list options" err &&
+	test_cmp_rev start HEAD^ &&
+	test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
+
+	git reset --hard three &&
+	git history squash --no-edit -- --reverse start.. 2>err &&
+	test_grep "ignoring rev-list options" err &&
+	test_cmp_rev start HEAD^ &&
+	test "$tip_tree" = "$(git rev-parse HEAD^{tree})"
+'
+
+test_expect_success 'squashes an interior range and replays descendants verbatim' '
+	git reset --hard three &&
+	final_tree=$(git rev-parse HEAD^{tree}) &&
+
+	git history squash --no-edit start..@~1 &&
+
+	check_log_subjects start..HEAD <<-\EOF &&
+	three
+	one
+	EOF
+
+	test_cmp_rev start HEAD~2 &&
+	test "$final_tree" = "$(git rev-parse HEAD^{tree})"
+'
+
+test_expect_success 'squashes when the base is the root commit' '
+	git reset --hard three &&
+	root=$(git rev-list --max-parents=0 HEAD) &&
+	tip_tree=$(git rev-parse HEAD^{tree}) &&
+
+	git history squash --no-edit "$root.." &&
+
+	check_commit_count "$root..HEAD" 1 &&
+	test_cmp_rev "$root" HEAD^ &&
+	test "$tip_tree" = "$(git rev-parse HEAD^{tree})"
+'
+
+test_expect_success 'squashing a mix of fixups' '
+	git reset --hard three &&
+	echo fix >file &&
+	git commit --fixup=two -a &&
+	echo really fix >file &&
+	git commit --fixup=one -a &&
+	echo really really fix >file &&
+	git commit --fixup=HEAD~1 -a && # fixup! two
+	echo really really really fix >file &&
+	git commit --fixup=HEAD~1 -a && # fixup! one
+
+	# squashing fixup! with a target that is not being squashed fails
+	test_must_fail git history squash one.. 2>err &&
+	test_grep "^error: cannot squash .* (fixup! one): its target is not being squashed" err &&
+
+	# squashing fixup! into fixup! with a different target fails
+	test_must_fail git history squash HEAD~4.. 2>err && # HEAD~4 is fixup! two
+	test_grep "^error: cannot squash .* (fixup! one): its target is not being squashed" err &&
+
+	# squashing a sequence of fixup! commits into their targets
+	git history squash --no-edit start..HEAD~1 &&
+	test_cmp_rev start HEAD~2 &&
+	check_commit_author one HEAD~1 &&
+	test_commit_message HEAD~1 -m one &&
+
+	# squashing "fixup! fixup! <target>" into "<target>"
+	git history squash --no-edit start.. &&
+	test_cmp_rev start HEAD~1 &&
+	check_commit_author one HEAD &&
+	test_commit_message HEAD -m one
+'
+
+test_expect_success 'squashing "squash!" messages' '
+	git reset --hard two &&
+	echo fix >file &&
+	git commit --fixup=HEAD -a &&
+	oldest=$(git rev-parse HEAD) &&
+	echo better fix >file &&
+	git commit -a -F - <<-EOF &&
+	squash! $(git rev-parse two)
+
+	Append this
+	EOF
+
+	echo an even better fix >file &&
+	git commit -a -F - <<-EOF &&
+	squash! squash! two
+
+	Append this as well
+	EOF
+
+	# must edit when squashing "squash!" into its target
+	test_must_fail git history squash --no-edit two^.. 2>err &&
+	test_grep "^error: squashing .* (squash! [a-f0-9]*) would discard its message" err &&
+
+	# squashing "squash!" into "fixup!" appends messages and changes
+	# subject prefix
+	git history squash --no-edit two.. &&
+	test_cmp_rev HEAD^ two &&
+	test_commit_message HEAD <<-\EOF &&
+	squash! two
+
+	Append this
+
+	Append this as well
+	EOF
+	check_commit_author "$oldest" HEAD &&
+
+	git commit --allow-empty -F - <<-\EOF &&
+	amend! two
+
+	A new message
+	EOF
+
+	# "amend!" does not replace "squash!"
+	test_must_fail git history squash --no-edit HEAD~2.. 2>err &&
+	test_grep "^error: squashing .* (amend! two) would overwrite .squash!. message" err
+'
+
+test_expect_success '--no-edit uses last "amend!" message without an editor' '
+	git reset --hard three &&
+	write_script editor <<-\EOF &&
+	exit 1
+	EOF
+	test_set_editor "$(pwd)/editor" &&
+	echo fix >file &&
+	git commit --author="Fix Me <fix.me@example.com>" --fixup=HEAD -a &&
+	git commit --allow-empty -F - <<-EOF &&
+	amend! $(git rev-parse --short HEAD)
+
+	The first reword
+
+	More detail
+	EOF
+
+	git commit --allow-empty -F - <<-\EOF &&
+	amend! three
+
+	The second reword
+
+	Extra detail
+	EOF
+
+	test_commit WIP &&
+
+	cat >msg <<-EOF &&
+	amend! $(git rev-parse --short HEAD^)
+
+	The third reword
+
+	Excruciating detail
+	EOF
+
+	git commit --author="Someone Else <s.else@example.com>" --allow-empty \
+		-F msg &&
+
+	# squashing amend! updates the commit message
+	git history squash --no-edit three^.. &&
+	sed -e 1,2d msg | test_commit_message HEAD &&
+	check_commit_author three HEAD &&
+	test_cmp_rev HEAD^ three^ &&
+
+	# squashing amend! into fixup! updates subject prefix
+	git reset --hard HEAD@{1} &&
+	git history squash --no-edit three.. &&
+	sed "1s/.*/amend! three/" msg | test_commit_message HEAD &&
+	check_commit_author HEAD@{1}~4 HEAD &&
+	test_cmp_rev HEAD^ three &&
+
+	# squashing amend! into amend! keeps original subject line
+	git reset --hard HEAD@{1} &&
+	git history squash --no-edit HEAD~3.. &&
+	sed "1s/.*/amend! three/" msg | test_commit_message HEAD &&
+	test_cmp_rev HEAD~3 three &&
+
+	# all amend! messages must target the first commit
+	git reset --hard HEAD@{1} &&
+	git commit --allow-empty -F - <<-\EOF &&
+	amend! WIP
+
+	The real message
+	EOF
+
+	test_must_fail git history squash --no-edit HEAD~4.. 2>err &&
+	test_grep "^error: cannot squash .* that does not target" err &&
+
+	# amend! message that targets commit that is not in range is rejected
+	test_must_fail git history squash --no-edit HEAD~3.. 2>err &&
+	test_grep "^error: cannot squash .* target is not being squashed" err &&
+	test_set_editor :
+'
+
+test_expect_success 'squashing fixups into a merge' '
+	test_when_finished \
+		"git switch -f $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME; \
+		 git branch -D feature" &&
+	git checkout -f start &&
+	test_commit F1 &&
+	git checkout -b feature start &&
+	test_commit F2 &&
+	git merge F1 &&
+	echo fixed >F1.t &&
+	cat >msg <<-EOF &&
+	amend! $(git rev-parse HEAD)
+
+	merge F1 and F2
+
+	reworded
+	EOF
+
+	git commit -a -F msg &&
+	git history squash --no-edit HEAD^^! HEAD &&
+	test_cmp_rev HEAD^1 F2 &&
+	test_cmp_rev HEAD^2 F1 &&
+	test_cmp_rev HEAD@{1}^{tree} HEAD^{tree} &&
+	sed 1,2d msg | test_commit_message HEAD
+'
+
+test_expect_success 'edits every message and aborts on an empty result' '
+	git reset --hard start &&
+	stage_file b &&
+	git commit -m "re-one subject" -m "re-one body line" &&
+	test_commit --no-tag re-two file c &&
+	test_commit re-three file d &&
+	head_before=$(git rev-parse HEAD) &&
+
+	write_script empty-editor <<-\EOF &&
+	>"$1"
+	EOF
+	test_set_editor "$(pwd)/empty-editor" &&
+	test_must_fail git history squash start.. 2>err &&
+	test_grep "Aborting commit due to empty commit message" err &&
+	test_cmp_rev "$head_before" HEAD &&
+
+	write_script editor <<-\EOF &&
+	cat "$1" >edited &&
+	echo combined >"$1"
+	EOF
+	test_set_editor "$(pwd)/editor" &&
+	git history squash start.. &&
+
+	cat >expect <<-EOF &&
+	# This is a combination of 3 commits.
+	# This is the 1st commit message:
+
+	re-one subject
+
+	re-one body line
+
+	# This is the commit message #2:
+
+	re-two
+
+	# This is the commit message #3:
+
+	re-three
+
+	# Please enter the commit message for the squash changes. Lines starting
+	# with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
+	# Changes to be committed:
+	#	modified:   file
+	#
+	EOF
+	test_cmp expect edited &&
+	check_log_subjects -1 <<-\EOF
+	combined
+	EOF
+'
+
+test_expect_success 'handles fixup!, squash! and amend! like rebase' '
+	git reset --hard start &&
+	test_commit --no-tag mark-base file b &&
+	stage_file c &&
+	commit_with_message "fixup! mark-base\n\nfixup body\n" &&
+	stage_file d &&
+	commit_with_message "squash! mark-base\n\nsquash remark\n" &&
+	stage_file e &&
+	commit_with_message "amend! mark-base\n\namended message\n" &&
+
+	write_script editor <<-\EOF &&
+	cat "$1" >edited
+	EOF
+	test_set_editor "$(pwd)/editor" &&
+	git history squash start.. &&
+
+	cat >expect <<-EOF &&
+	# This is a combination of 4 commits.
+	# This is the 1st commit message:
+
+	mark-base
+
+	# The commit message #2 will be skipped:
+
+	# fixup! mark-base
+	#
+	# fixup body
+
+	# This is the commit message #3:
+
+	# squash! mark-base
+
+	squash remark
+
+	# This is the commit message #4:
+
+	# amend! mark-base
+
+	amended message
+
+	# Please enter the commit message for the squash changes. Lines starting
+	# with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
+	# Changes to be committed:
+	#	modified:   file
+	#
+	EOF
+	test_cmp expect edited &&
+	check_log_messages -1 <<-\EOF
+	mark-base
+
+	squash remark
+
+	amended message
+
+	EOF
+'
+
+test_expect_success 'groups fixups under their targets in the editor' '
+	git reset --hard start &&
+	test_commit --no-tag alpha file a1 &&
+	test_commit --no-tag beta file b1 &&
+	stage_file a2 &&
+	commit_with_message "fixup! alpha\n" &&
+	stage_file b2 &&
+	commit_with_message "fixup! beta\n" &&
+
+	write_script editor <<-\EOF &&
+	cat "$1" >edited
+	EOF
+	test_set_editor "$(pwd)/editor" &&
+	git history squash start.. &&
+
+	cat >expect <<-EOF &&
+	# This is a combination of 4 commits.
+	# This is the 1st commit message:
+
+	alpha
+
+	# The commit message #2 will be skipped:
+
+	# fixup! alpha
+
+	# This is the commit message #3:
+
+	beta
+
+	# The commit message #4 will be skipped:
+
+	# fixup! beta
+
+	# Please enter the commit message for the squash changes. Lines starting
+	# with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
+	# Changes to be committed:
+	#	modified:   file
+	#
+	EOF
+	test_cmp expect edited
+'
+
+test_expect_success 'lets amend! replace its target message in the editor' '
+	git reset --hard start &&
+	test_commit --no-tag mark-base file b &&
+	stage_file c &&
+	commit_with_message "amend! mark-base\n\namended message\n" &&
+	stage_file d &&
+	commit_with_message "squash! mark-base\n\nsquash remark\n" &&
+
+	write_script editor <<-\EOF &&
+	cat "$1" >edited
+	EOF
+	test_set_editor "$(pwd)/editor" &&
+	git history squash start.. &&
+
+	cat >expect <<-EOF &&
+	# This is a combination of 3 commits.
+	# The 1st commit message will be skipped:
+
+	# mark-base
+
+	# This is the commit message #2:
+
+	# amend! mark-base
+
+	amended message
+
+	# This is the commit message #3:
+
+	# squash! mark-base
+
+	squash remark
+
+	# Please enter the commit message for the squash changes. Lines starting
+	# with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
+	# Changes to be committed:
+	#	modified:   file
+	#
+	EOF
+	test_cmp expect edited &&
+	check_log_messages -1 <<-\EOF
+	amended message
+
+	squash remark
+
+	EOF
+'
+
+test_expect_success '--update-refs=head only moves HEAD' '
+	git reset --hard three &&
+	git branch -f other HEAD &&
+	other_before=$(git rev-parse other) &&
+
+	git history squash --no-edit --update-refs=head start.. &&
+
+	check_commit_count start..HEAD 1 &&
+	test_cmp_rev "$other_before" other
+'
+
+test_expect_success 'refuses to fold a range a branch points into' '
+	test_when_finished \
+		"git switch -f $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME; \
+		 git branch -D feature" &&
+	git checkout -f -b feature start &&
+	test_commit C1 &&
+	test_commit C2 &&
+	git checkout -b topic-1 start &&
+	test_commit C3 &&
+	test_commit C4 &&
+	git checkout C3 &&
+	test_commit C5 &&
+	git checkout feature &&
+	git merge C5 &&
+	test_commit C6 &&
+	git checkout -b topic-2 C2 &&
+	test_commit C7 &&
+	git checkout feature &&
+
+	test_must_fail git history squash start.. 2>err &&
+	test_grep "^error: the following branches cannot be rewritten" err &&
+	test_grep "^  topic-1$" err &&
+	test_grep "^  topic-2$" err &&
+	test_grep "^hint: .* --update-refs=head" err &&
+	test_cmp_rev C6 HEAD &&
+
+	# squash succeeds with --update-refs=head
+	git history squash --no-edit --update-refs=head start.. &&
+	test_cmp_rev start HEAD^ &&
+	test_cmp_rev C6^{tree} HEAD^{tree} &&
+	test_cmp_rev C6 HEAD@{1}
+'
+
+test_expect_success 'advice.historyUpdateRefs silences the hint' '
+	git reset --hard three &&
+	git branch -f mid HEAD~1 &&
+	head_before=$(git rev-parse HEAD) &&
+
+	test_must_fail git -c advice.historyUpdateRefs=false \
+		history squash start.. 2>err &&
+	test_grep "^error: the following branches cannot be rewritten" err &&
+	test_grep "^  mid$" err &&
+	test_grep ! "hint:" err &&
+	test_cmp_rev "$head_before" HEAD &&
+
+	git branch -D mid
+'
+
+test_expect_success 'leaves tags and remote-tracking refs unchanged' '
+	git reset --hard three &&
+	git tag -f mark HEAD~1 &&
+	git update-ref refs/remotes/origin/mark HEAD~1 &&
+	mark_before=$(git rev-parse mark) &&
+
+	git history squash --no-edit start.. &&
+
+	test_cmp_rev "$mark_before" mark &&
+	test_cmp_rev "$mark_before" refs/remotes/origin/mark &&
+
+	git tag -d mark &&
+	git update-ref -d refs/remotes/origin/mark
+'
+
+test_expect_success 'squashes a range whose internal merge has a single base' '
+	git reset --hard start &&
+	main=$(git symbolic-ref --short HEAD) &&
+	test_commit --no-tag before-side file b &&
+	git checkout -b inner-side &&
+	test_commit --no-tag on-inner-side inner x &&
+	git checkout "$main" &&
+	test_commit --no-tag after-side file c &&
+	git merge --no-ff -m merge inner-side &&
+	git branch -D inner-side &&
+	test_commit --no-tag after-merge file d &&
+	tip_tree=$(git rev-parse HEAD^{tree}) &&
+
+	git history squash --no-edit start.. &&
+
+	check_commit_count start..HEAD 1 &&
+	check_log_subjects -1 <<-\EOF &&
+	before-side
+	EOF
+	test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
+	test_path_is_file inner
+'
+
+test_expect_success 'folds a merge of a branch that forked at the base' '
+	git reset --hard start &&
+	main=$(git symbolic-ref --short HEAD) &&
+	git checkout -b base-fork-side &&
+	test_commit --no-tag base-fork-side side x &&
+	git checkout "$main" &&
+	test_commit --no-tag base-fork-main file b &&
+	git merge --no-ff -m "merge base-fork-side" base-fork-side &&
+	git branch -D base-fork-side &&
+	test_commit --no-tag base-fork-tail file c &&
+	tip_tree=$(git rev-parse HEAD^{tree}) &&
+
+	git history squash --no-edit start.. &&
+
+	check_commit_count start..HEAD 1 &&
+	test_cmp_rev start HEAD^ &&
+	test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
+	test_path_is_file side
+'
+
+test_expect_success 'refuses a merge whose other parent is outside the range' '
+	git reset --hard start &&
+	main=$(git symbolic-ref --short HEAD) &&
+	git checkout -b outside-parent &&
+	test_commit --no-tag outside-parent outside x &&
+	git checkout "$main" &&
+	test_commit --no-tag outside-main file b &&
+	base=$(git rev-parse HEAD) &&
+	test_commit --no-tag outside-mid file c &&
+	git merge --no-ff -m "merge outside-parent" outside-parent &&
+	git branch -D outside-parent &&
+	merged=$(git rev-parse HEAD) &&
+
+	test_must_fail git history squash "$base.." 2>err &&
+	test_grep "parent .* of commit .* is outside the revision range" err &&
+	test_cmp_rev "$merged" HEAD
+'
+
+test_expect_success 'folds a range whose tip is a merge commit' '
+	git reset --hard start &&
+	main=$(git symbolic-ref --short HEAD) &&
+	test_commit --no-tag tipmerge-base file b &&
+	git checkout -b tipmerge-side &&
+	test_commit --no-tag tipmerge-side side x &&
+	git checkout "$main" &&
+	test_commit --no-tag tipmerge-main file c &&
+	git merge --no-ff -m "merge tipmerge-side" tipmerge-side &&
+	git branch -D tipmerge-side &&
+	tip_tree=$(git rev-parse HEAD^{tree}) &&
+
+	git history squash --no-edit start.. &&
+
+	check_commit_count start..HEAD 1 &&
+	test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
+	test_path_is_file side
+'
+
+test_expect_success 'folds a range whose base is a merge commit' '
+	git reset --hard start &&
+	main=$(git symbolic-ref --short HEAD) &&
+	git checkout -b basemerge-side &&
+	test_commit --no-tag basemerge-side side x &&
+	git checkout "$main" &&
+	test_commit --no-tag basemerge-main file b &&
+	git merge --no-ff -m "merge basemerge-side" basemerge-side &&
+	git branch -D basemerge-side &&
+	base=$(git rev-parse HEAD) &&
+	test_commit --no-tag basemerge-one file c &&
+	test_commit --no-tag basemerge-two file d &&
+	tip_tree=$(git rev-parse HEAD^{tree}) &&
+
+	git history squash --no-edit "$base.." &&
+
+	check_commit_count "$base..HEAD" 1 &&
+	test_cmp_rev "$base" HEAD^ &&
+	test "$tip_tree" = "$(git rev-parse HEAD^{tree})"
+'
+
+test_expect_success 'folds a range with two interior merges' '
+	git reset --hard start &&
+	main=$(git symbolic-ref --short HEAD) &&
+	test_commit --no-tag two-merge-a file a1 &&
+	git checkout -b two-merge-s1 &&
+	test_commit --no-tag two-merge-s1 s1 x &&
+	git checkout "$main" &&
+	git merge --no-ff -m "merge s1" two-merge-s1 &&
+	test_commit --no-tag two-merge-b file b1 &&
+	git checkout -b two-merge-s2 &&
+	test_commit --no-tag two-merge-s2 s2 y &&
+	git checkout "$main" &&
+	git merge --no-ff -m "merge s2" two-merge-s2 &&
+	git branch -D two-merge-s1 two-merge-s2 &&
+	tip_tree=$(git rev-parse HEAD^{tree}) &&
+
+	git history squash --no-edit start.. &&
+
+	check_commit_count start..HEAD 1 &&
+	test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
+	test_path_is_file s1 &&
+	test_path_is_file s2
+'
+
+test_expect_success 'folds a range with a nested merge' '
+	git reset --hard start &&
+	main=$(git symbolic-ref --short HEAD) &&
+	git checkout -b nested-outer &&
+	test_commit --no-tag nested-outer outer x &&
+	git checkout -b nested-inner &&
+	test_commit --no-tag nested-inner inner y &&
+	git checkout nested-outer &&
+	git merge --no-ff -m "merge inner" nested-inner &&
+	git checkout "$main" &&
+	test_commit --no-tag nested-main file b1 &&
+	git merge --no-ff -m "merge outer" nested-outer &&
+	git branch -D nested-outer nested-inner &&
+	tip_tree=$(git rev-parse HEAD^{tree}) &&
+
+	git history squash --no-edit start.. &&
+
+	check_commit_count start..HEAD 1 &&
+	test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
+	test_path_is_file outer &&
+	test_path_is_file inner
+'
+
+test_expect_success 'folds a range with an octopus merge' '
+	git reset --hard start &&
+	main=$(git symbolic-ref --short HEAD) &&
+	test_commit --no-tag octo-base file a1 &&
+	git checkout -b octo-1 &&
+	test_commit --no-tag octo-1 o1 x &&
+	git checkout "$main" &&
+	git checkout -b octo-2 &&
+	test_commit --no-tag octo-2 o2 y &&
+	git checkout "$main" &&
+	git merge --no-ff -m octopus octo-1 octo-2 &&
+	git branch -D octo-1 octo-2 &&
+	tip_tree=$(git rev-parse HEAD^{tree}) &&
+
+	git history squash --no-edit start.. &&
+
+	check_commit_count start..HEAD 1 &&
+	test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
+	test_path_is_file o1 &&
+	test_path_is_file o2
+'
+
+test_expect_success 'refuses an octopus merge with an arm forked before the base' '
+	git reset --hard start &&
+	main=$(git symbolic-ref --short HEAD) &&
+	git checkout -b octo-pre &&
+	test_commit octo-pre-side pside x &&
+	git checkout "$main" &&
+	test_commit octo-pre-main file b1 &&
+	octo_base=$(git rev-parse HEAD) &&
+	git checkout -b octo-within &&
+	test_commit --no-tag octo-within wside y &&
+	git checkout "$main" &&
+	git merge --no-ff -m octopus octo-pre octo-within &&
+	merged=$(git rev-parse HEAD) &&
+	git branch -D octo-pre octo-within &&
+
+	test_must_fail git history squash "$octo_base.." 2>err &&
+	test_grep "parent .* of commit .* is outside the revision range" err &&
+	test_cmp_rev "$merged" HEAD
+'
+
+test_expect_success 'refuses when a descendant above the range is a merge' '
+	git reset --hard start &&
+	main=$(git symbolic-ref --short HEAD) &&
+	test_commit --no-tag desc-one file b &&
+	test_commit --no-tag desc-two file c &&
+	git tag desc-tip &&
+	git checkout -b desc-above &&
+	test_commit --no-tag desc-above above x &&
+	git checkout "$main" &&
+	test_commit --no-tag desc-main file d &&
+	git merge --no-ff -m "merge desc-above" desc-above &&
+	git branch -D desc-above &&
+	head_before=$(git rev-parse HEAD) &&
+
+	test_must_fail git history squash --no-edit start..desc-tip 2>err &&
+	test_grep "merge commits is not supported" err &&
+	test_cmp_rev "$head_before" HEAD
+'
+
+test_expect_success 'refuses to fold a range a ref points into at a merge' '
+	git reset --hard start &&
+	main=$(git symbolic-ref --short HEAD) &&
+	test_commit --no-tag refmerge-base file b &&
+	git checkout -b refmerge-side &&
+	test_commit --no-tag refmerge-side side x &&
+	git checkout "$main" &&
+	test_commit --no-tag refmerge-main file c &&
+	git merge --no-ff -m "interior merge" refmerge-side &&
+	git branch -D refmerge-side &&
+	git branch at-merge HEAD &&
+	test_commit --no-tag refmerge-tail file d &&
+	head_before=$(git rev-parse HEAD) &&
+
+	test_must_fail git history squash start.. 2>err &&
+	test_grep "^error: the following branches cannot be rewritten" err &&
+	test_grep "^  at-merge$" err &&
+	test_cmp_rev "$head_before" HEAD &&
+
+	git branch -D at-merge
+'
+
+test_done
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index 5777dff..9400ddf 100755
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -721,4 +721,37 @@
 	test_line_count = 4 commits
 '
 
+test_expect_success 'cherry-pick runs auto maintenance once it is done' '
+	pristine_detach base &&
+	GIT_TRACE2_EVENT="$(pwd)/single.txt" git cherry-pick --edit picked &&
+	test_subcommand_flex git commit <single.txt &&
+	test_subcommand_flex git maintenance run --auto <single.txt &&
+	grep "\"child_start\".*\"maintenance\"" single.txt >maintenance &&
+	test_line_count = 1 maintenance &&
+	GIT_TRACE2_EVENT="$(pwd)/sequence.txt" \
+		git cherry-pick anotherpick yetanotherpick &&
+	test_subcommand_flex git maintenance run --auto <sequence.txt &&
+	grep "\"child_start\".*\"maintenance\"" sequence.txt >maintenance &&
+	test_line_count = 1 maintenance
+'
+
+test_expect_success 'cherry-pick runs auto maintenance once a stopped sequence is done' '
+	# both picked and anotherpick conflict on foo, so "--continue" stops
+	# once more before "--skip" ends the sequence
+	pristine_detach initial &&
+	test_must_fail env GIT_TRACE2_EVENT="$(pwd)/stop.txt" \
+		git cherry-pick base..anotherpick &&
+	test_subcommand_flex ! git maintenance run --auto <stop.txt &&
+	echo resolved >foo &&
+	git add foo &&
+	test_must_fail env GIT_TRACE2_EVENT="$(pwd)/mid.txt" \
+		git cherry-pick --continue &&
+	test_subcommand_flex git commit <mid.txt &&
+	test_subcommand_flex ! git maintenance run --auto <mid.txt &&
+	GIT_TRACE2_EVENT="$(pwd)/end.txt" git cherry-pick --skip &&
+	test_subcommand_flex git maintenance run --auto <end.txt &&
+	grep "\"child_start\".*\"maintenance\"" end.txt >maintenance &&
+	test_line_count = 1 maintenance
+'
+
 test_done
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 7211586..70c6031 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -374,6 +374,48 @@
 	test_cmp expect actual
 '
 
+test_expect_success 'stash apply --index leaves everything untouched on failure' '
+	git reset --hard &&
+	echo test >other-file &&
+	git add other-file &&
+	git stash &&
+	echo unrelated >file &&
+	echo unrelated >another-file &&
+	git add another-file &&
+	echo conflict >other-file &&
+	git add other-file &&
+	git diff-files -p >expect &&
+	git diff-index --cached HEAD >expect-index &&
+
+	test_must_fail git stash apply --index 2>err &&
+	test_grep "conflicts in index. Try without --index" err &&
+	git diff-files -p >actual &&
+	test_cmp expect actual &&
+	git diff-index --cached HEAD >actual-index &&
+	test_cmp expect-index actual-index
+'
+
+# the later "stash -k" test is not expecting us to muck with file so much, so
+# reset when finished
+test_expect_success 'stash apply --index merges the correct trees' '
+	head=$(git rev-parse HEAD) &&
+	test_when_finished "git reset --hard $head" &&
+	test_write_lines A B C >file &&
+	git commit -m setup file &&
+	test_write_lines A B staged >file &&
+	git add file &&
+	test_write_lines A B unstaged >file &&
+	git stash &&
+	test_write_lines committed B C >file &&
+	git commit -m to-be-merged file &&
+	git stash pop --index &&
+	git show :file >actual &&
+	test_write_lines committed B staged >expect &&
+	test_cmp expect actual &&
+	test_write_lines committed B unstaged >expect &&
+	test_cmp expect file
+'
+
 test_expect_success 'stash -k' '
 	echo bar3 >file &&
 	echo bar4 >file2 &&
diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh
index 8e0394c..c3c703f 100755
--- a/t/t4053-diff-no-index.sh
+++ b/t/t4053-diff-no-index.sh
@@ -120,6 +120,24 @@
 	)
 '
 
+test_expect_success 'reverse diff when turning a file into a directory' '
+	(
+		cd non/git &&
+		printf "A\td/sub\nD\te/sub/file\n" >expect &&
+		test_expect_code 1 git diff --no-index -R --name-status d e >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'reverse diff when turning a directory into a file' '
+	(
+		cd non/git &&
+		printf "D\td/sub\nA\te/sub/file\n" >expect &&
+		test_expect_code 1 git diff --no-index -R --name-status e d >actual &&
+		test_cmp expect actual
+	)
+'
+
 test_expect_success 'diff from repo subdir shows real paths (explicit)' '
 	echo "diff --git a/../../non/git/a b/../../non/git/b" >expect &&
 	test_expect_code 1 \
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
index 7bb601e..5be3f05 100755
--- a/t/t4200-rerere.sh
+++ b/t/t4200-rerere.sh
@@ -734,4 +734,55 @@
 	test_must_fail git rebase --continue
 '
 
+test_expect_success 'rerere preserves conflicts when driver output is unreadable' '
+	test_create_repo unreadable-output &&
+	(
+		cd unreadable-output &&
+		git config rerere.enabled true &&
+		git config rerere.autoupdate true &&
+		write_script merge-driver <<-\EOF &&
+		git merge-file "$@"
+		status=$?
+		if test -f fail-read
+		then
+			rm "$1" || exit 1
+		fi
+		exit "$status"
+		EOF
+		git config merge.unreadable.driver "./merge-driver %A %O %B" &&
+		echo "file merge=unreadable" >.gitattributes &&
+		test_commit base file base &&
+		git checkout -b one &&
+		test_commit --no-tag one file one &&
+		git checkout -b two base &&
+		test_commit --no-tag two file two &&
+
+		# Teach rerere a resolution while the driver works normally.
+		test_must_fail git merge one &&
+		echo resolved >file &&
+		git rerere &&
+		git merge --abort &&
+
+		# Recreate the conflict without replaying the resolution yet.
+		test_must_fail git -c rerere.enabled=false merge one &&
+
+		# We will expect the same conflicted content after rerere fails
+		# below.
+		cp file expect &&
+		git ls-files -u >expect-index &&
+		test_file_not_empty expect-index &&
+
+		# Now we try rerere again, but the merge driver will cause the
+		# read to fail.
+		>fail-read &&
+		git rerere 2>err &&
+		test_grep "Could not stat" err &&
+
+		# And we expect the conflicted state.
+		test_cmp expect file &&
+		git ls-files -u >actual-index &&
+		test_cmp expect-index actual-index
+	)
+'
+
 test_done
diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh
index 023fbff..10c43e6 100755
--- a/t/t4201-shortlog.sh
+++ b/t/t4201-shortlog.sh
@@ -436,4 +436,15 @@
 	test_must_fail git shortlog --group=author --group=committer <log
 '
 
+test_expect_success 'invalid revision options are not reported as unknown' '
+	test_must_fail git shortlog --default 2>err &&
+	test_grep "bad --default argument" err &&
+	test_grep ! "unknown option" err
+'
+
+test_expect_success 'unknown revision options are reported correctly' '
+	test_must_fail git shortlog -n --no-such-option 2>err &&
+	test_grep "unknown option .*--no-such-option" err
+'
+
 test_done
diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh
index 4be5c51..01b97c8 100755
--- a/t/t4205-log-pretty-formats.sh
+++ b/t/t4205-log-pretty-formats.sh
@@ -1189,7 +1189,7 @@
 test_expect_success EXPENSIVE,SIZE_T_IS_64BIT 'log --pretty with huge commit message' '
 	git log -1 --format="%B%<(1)%x30" $huge_commit >actual &&
 	echo 0 >>expect &&
-	test_cmp expect actual
+	test_cmp_bin expect actual
 '
 
 test_expect_success EXPENSIVE,SIZE_T_IS_64BIT 'log --pretty with huge commit message does not cause allocation failure' '
diff --git a/t/t4216-log-bloom.sh b/t/t4216-log-bloom.sh
index ad26866..f57a3d6 100755
--- a/t/t4216-log-bloom.sh
+++ b/t/t4216-log-bloom.sh
@@ -755,7 +755,7 @@
 test_expect_success PERL_TEST_HELPERS 'Bloom reader notices out-of-bounds filter offsets' '
 	check_corrupt_graph BIDX 12 FFFFFFFF &&
 	# use grep to avoid depending on exact chunk size
-	test_grep "warning: ignoring out-of-range offset (4294967295) for changed-path filter at pos 3 of .git/objects/info/commit-graph" err
+	test_grep "warning: ignoring out-of-range offset (4294967295) for changed-path filter at pos 3 of $(pwd)/.git/objects/info/commit-graph" err
 '
 
 test_expect_success PERL_TEST_HELPERS 'Bloom reader notices too-small index chunk' '
@@ -773,7 +773,7 @@
 	# actually reading from the bogus offsets anyway.
 	corrupt_graph BIDX 4 0000000c00000005 &&
 	echo "warning: ignoring decreasing changed-path index offsets" \
-		"(12 > 5) for positions 1 and 2 of .git/objects/info/commit-graph" >expect.err &&
+		"(12 > 5) for positions 1 and 2 of $(pwd)/.git/objects/info/commit-graph" >expect.err &&
 	git -c core.commitGraph=false log -- A/B/file2 >expect.out &&
 	git -c core.commitGraph=true log -- A/B/file2 >out 2>err &&
 	test_cmp expect.out out &&
diff --git a/t/t5412-receive-report-hook.sh b/t/t5412-receive-report-hook.sh
new file mode 100755
index 0000000..2f6515b
--- /dev/null
+++ b/t/t5412-receive-report-hook.sh
@@ -0,0 +1,257 @@
+#!/bin/sh
+
+test_description='test receive-report hook'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+
+. "$TEST_DIRECTORY"/t5411/common-functions.sh
+
+URL_PREFIX="\.\."
+
+test_expect_success "setup workbench" '
+	git init workbench &&
+	create_commits_in workbench A B
+'
+
+test_expect_success "no report hook, push succeeds" '
+	test_when_finished "rm -rf upstream" &&
+	test_when_finished "git -C workbench remote remove origin" &&
+	git init --bare upstream &&
+
+	git -C workbench remote add origin ../upstream &&
+	git -C workbench push origin $A:refs/heads/main &&
+	git -C workbench push origin $B:refs/heads/main >out 2>&1 &&
+
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-\EOF &&
+	To ../upstream
+	   <COMMIT-A>..<COMMIT-B>  <COMMIT-B> -> main
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "passthrough does not alter report" '
+	test_when_finished "rm -rf upstream" &&
+	test_when_finished "git -C workbench remote remove origin" &&
+	git init --bare upstream &&
+
+	test_hook -C upstream --setup receive-report <<-\EOF &&
+	cat
+	EOF
+
+	git -C workbench remote add origin ../upstream &&
+	git -C workbench push origin $A:refs/heads/main &&
+	git -C workbench push origin $B:refs/heads/main >out 2>&1 &&
+
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-\EOF &&
+	To ../upstream
+	   <COMMIT-A>..<COMMIT-B>  <COMMIT-B> -> main
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "non-zero exit reports as hook failed" '
+	test_when_finished "rm -rf upstream" &&
+	test_when_finished "git -C workbench remote remove origin" &&
+
+	git init --bare upstream &&
+	git -C workbench remote add origin ../upstream &&
+	git -C workbench push origin $A:refs/heads/main &&
+
+	test_hook -C upstream --setup receive-report <<-\EOF &&
+	exit 1
+	EOF
+
+	test_must_fail git -C workbench push origin $B:refs/heads/main >out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-\EOF &&
+	To ../upstream
+	 ! [remote rejected] <COMMIT-B> -> main (receive-report hook failed)
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "hook is invoked and receives report on stdin" '
+	test_when_finished "rm -rf upstream" &&
+	test_when_finished "git -C workbench remote remove origin" &&
+
+	git init --bare upstream &&
+	test_hook -C upstream --setup receive-report <<-EOF &&
+	tee raw
+	EOF
+
+	git -C workbench remote add origin ../upstream &&
+	git -C workbench push origin $A:refs/heads/main &&
+	git -C workbench push origin $B:refs/heads/main >out 2>&1 &&
+
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	To ../upstream
+	   <COMMIT-A>..<COMMIT-B>  <COMMIT-B> -> main
+	EOF
+	test_cmp expect actual &&
+
+	test-tool pkt-line unpack <upstream/raw >actual-report &&
+	cat >expect-report <<-EOF &&
+	unpack ok
+	ok refs/heads/main
+	0000
+	EOF
+	test_cmp expect-report actual-report
+'
+
+test_expect_success "hook can modify the report sent to client" '
+	test_when_finished "rm -rf upstream" &&
+	test_when_finished "git -C workbench remote remove origin" &&
+
+	git init --bare upstream &&
+	git -C workbench remote add origin ../upstream &&
+	git -C workbench push origin $A:refs/heads/main &&
+
+	test_hook -C upstream --setup receive-report <<-\EOF &&
+	test-tool pkt-line unpack |
+	sed "s/^ok /ng /" |
+	test-tool pkt-line pack
+	EOF
+
+	test_must_fail git -C workbench push origin $B:refs/heads/main >out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-\EOF &&
+	To ../upstream
+	 ! [remote rejected] <COMMIT-B> -> main (failed)
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "hook can modify the unpack status" '
+	test_when_finished "rm -rf upstream" &&
+	test_when_finished "git -C workbench remote remove origin" &&
+
+	git init --bare upstream &&
+	git -C workbench remote add origin ../upstream &&
+	git -C workbench push origin $A:refs/heads/main &&
+
+	test_hook -C upstream --setup receive-report <<-\EOF &&
+	test-tool pkt-line unpack |
+	sed "s/^unpack ok$/unpack push failed due to server error/" |
+	test-tool pkt-line pack
+	EOF
+
+	test_must_fail git -C workbench push origin $B:refs/heads/main >out 2>&1 &&
+	test_grep "error: remote unpack failed: push failed due to server error" out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-\EOF &&
+	To ../upstream
+	   <COMMIT-A>..<COMMIT-B>  <COMMIT-B> -> main
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "hook can report a custom failure message" '
+	test_when_finished "rm -rf upstream" &&
+	test_when_finished "git -C workbench remote remove origin" &&
+
+	git init --bare upstream &&
+	git -C workbench remote add origin ../upstream &&
+	git -C workbench push origin $A:refs/heads/main &&
+
+	test_hook -C upstream --setup receive-report <<-\EOF &&
+	echo "push rejected: service X is down" >&2
+	test-tool pkt-line unpack |
+	sed "s/^ok \(.*\)/ng \1 service-x-is-down/" |
+	test-tool pkt-line pack |
+	tee raw
+	EOF
+
+	test_must_fail git -C workbench push origin $B:refs/heads/main >out 2>&1 &&
+	test_grep "push rejected: service X is down" out &&
+
+	test-tool pkt-line unpack <upstream/raw >actual-report &&
+	cat >expect-report <<-\EOF &&
+	unpack ok
+	ng refs/heads/main service-x-is-down
+	0000
+	EOF
+	test_cmp expect-report actual-report
+'
+
+test_expect_success "hook stderr with zero exit status code" '
+	test_when_finished "rm -rf upstream" &&
+	test_when_finished "git -C workbench remote remove origin" &&
+
+	git init --bare upstream &&
+	git -C workbench remote add origin ../upstream &&
+	git -C workbench push origin $A:refs/heads/main &&
+
+	test_hook -C upstream --setup receive-report <<-\EOF &&
+	echo "push rejected: service X is down" >&2
+	tee raw
+	EOF
+
+	git -C workbench push origin $B:refs/heads/main >out 2>&1 &&
+	test_grep "push rejected: service X is down" out &&
+
+	test-tool pkt-line unpack <upstream/raw >actual-report &&
+	cat >expect-report <<-\EOF &&
+	unpack ok
+	ok refs/heads/main
+	0000
+	EOF
+	test_cmp expect-report actual-report
+'
+
+test_expect_success "non-zero exit with pre-existing ng from proc-receive" '
+	test_when_finished "rm -rf upstream" &&
+	test_when_finished "git -C workbench remote remove origin" &&
+
+	git init --bare upstream &&
+	git -C upstream config receive.procReceiveRefs refs/for &&
+	git -C workbench remote add origin ../upstream &&
+	git -C workbench push origin $A:refs/heads/main &&
+
+	# Use a proc-receive hook to generate a dynamic error string.
+	# This is used to capture any leaks stemming from overriding the
+	# error message via the receive-report.
+	test_hook -C upstream --setup proc-receive <<-\EOF &&
+	test-tool proc-receive -r "ng refs/for/main/topic push-rejected-by-service-x"
+	EOF
+
+	test_hook -C upstream --setup receive-report <<-\EOF &&
+	tee raw
+	exit 1
+	EOF
+
+	test_must_fail git -C workbench push origin HEAD:refs/for/main/topic >out 2>&1 &&
+	test_grep "receive-report hook failed" out &&
+
+	test-tool pkt-line unpack <upstream/raw >actual-report &&
+	cat >expect-report <<-\EOF &&
+	unpack ok
+	ng refs/for/main/topic push-rejected-by-service-x
+	0000
+	EOF
+	test_cmp expect-report actual-report
+'
+
+test_expect_success "hook stderr is relayed to client via sideband" '
+	test_when_finished "rm -rf upstream" &&
+	test_when_finished "git -C workbench remote remove origin" &&
+
+	git init --bare upstream &&
+	git -C workbench remote add origin ../upstream &&
+	git -C workbench push origin $A:refs/heads/main &&
+
+	test_hook -C upstream --setup receive-report <<-\EOF &&
+	echo "hook-stderr-message" >&2
+	exit 1
+	EOF
+
+	test_must_fail git -C workbench push origin $B:refs/heads/main >out 2>&1 &&
+	test_grep "remote: hook-stderr-message" out
+'
+
+test_done
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index a8d38d9..17c0be4 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -46,19 +46,19 @@
 	) &&
 	git clone . bundle &&
 	git clone . seven &&
-	git clone --ref-format=reftable . case_sensitive &&
+	git clone --ref-storage-format=reftable . case_sensitive &&
 	(
 		cd case_sensitive &&
 		git branch branch1 &&
 		git branch bRanch1
 	) &&
-	git clone --ref-format=reftable . case_sensitive_fd &&
+	git clone --ref-storage-format=reftable . case_sensitive_fd &&
 	(
 		cd case_sensitive_fd &&
 		git branch foo/bar &&
 		git branch Foo
 	) &&
-	git clone --ref-format=reftable . case_sensitive_df &&
+	git clone --ref-storage-format=reftable . case_sensitive_df &&
 	(
 		cd case_sensitive_df &&
 		git branch Foo/bar &&
@@ -1087,6 +1087,65 @@
 	)
 '
 
+test_expect_success 'fetch.writeCommitGraph adds fetched commits incrementally' '
+	git init incremental-source &&
+	test_commit -C incremental-source one &&
+	git clone incremental-source incremental-dest &&
+	test_commit -C incremental-dest local &&
+	git -C incremental-dest commit-graph write --reachable --split &&
+	test_commit -C incremental-source two &&
+	test_commit -C incremental-source three &&
+	(
+		cd incremental-dest &&
+		git -c fetch.writeCommitGraph=true fetch origin &&
+		test-tool read-graph commit-info three two local
+	)
+'
+
+test_expect_success 'fetch.writeCommitGraph does not add unrelated commits' '
+	git init unrelated-source &&
+	test_commit -C unrelated-source initial &&
+	git clone unrelated-source unrelated-dest &&
+	git -C unrelated-dest commit-graph write --reachable --split &&
+	test_commit -C unrelated-source fetched &&
+	(
+		cd unrelated-dest &&
+		test_env GIT_TEST_COMMIT_GRAPH=0 test_commit local-only &&
+		git -c fetch.writeCommitGraph=true fetch origin &&
+		test-tool read-graph commit-info fetched &&
+		test_expect_code 1 \
+			test-tool read-graph commit-info local-only 2>/dev/null
+	)
+'
+
+test_expect_success 'fetch.writeCommitGraph skips write on no-op fetch' '
+	git init noop-source &&
+	test_commit -C noop-source one &&
+	git clone noop-source noop-dest &&
+	git -C noop-dest commit-graph write --reachable --split &&
+	(
+		cd noop-dest &&
+		GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
+			git -c fetch.writeCommitGraph=true fetch origin &&
+		test_region ! fetch write-commit-graph trace2.txt
+	)
+'
+
+test_expect_success 'fetch.writeCommitGraph falls back to reachable scan without existing graph' '
+	git init first-graph-source &&
+	test_commit -C first-graph-source base &&
+	git clone first-graph-source first-graph-dest &&
+	test_commit -C first-graph-source fetched &&
+	(
+		cd first-graph-dest &&
+		test_commit local &&
+		rm -rf .git/objects/info/commit-graphs &&
+		rm -f .git/objects/info/commit-graph &&
+		git -c fetch.writeCommitGraph=true fetch origin &&
+		test-tool read-graph commit-info fetched local base
+	)
+'
+
 test_expect_success 'fetch.writeCommitGraph with submodules' '
 	test_config_global protocol.file.allow always &&
 	git clone dups super &&
@@ -1803,7 +1862,7 @@
 test_expect_success REFFILES 'existing reference lock in repo' '
 	test_when_finished rm -rf base repo &&
 	(
-		git init --ref-format=reftable base &&
+		git init --ref-storage-format=reftable base &&
 		cd base &&
 		echo >file update &&
 		git add . &&
@@ -1814,7 +1873,7 @@
 		git update-ref refs/heads/branch @ &&
 		cd .. &&
 
-		git init --ref-format=files --bare repo &&
+		git init --ref-storage-format=files --bare repo &&
 		cd repo &&
 		git remote add origin ../base &&
 		touch refs/heads/foo.lock &&
@@ -1857,7 +1916,7 @@
 test_expect_success REFFILES 'D/F conflict on case sensitive filesystem with lock' '
 	test_when_finished rm -rf base repo &&
 	(
-		git init --ref-format=reftable base &&
+		git init --ref-storage-format=reftable base &&
 		cd base &&
 		echo >file update &&
 		git add . &&
@@ -1868,7 +1927,7 @@
 		git update-ref refs/heads/branch @ &&
 		cd .. &&
 
-		git init --ref-format=files --bare repo &&
+		git init --ref-storage-format=files --bare repo &&
 		cd repo &&
 		git remote add origin ../base &&
 		mkdir refs/heads/foo &&
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 27f38ab..087ce46 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -35,6 +35,12 @@
 }
 
 test_expect_success setup '
+	# Commit dates are hardcoded to 2005, and the reflog entries will have
+	# a matching timestamp. Maintenance may thus immediately expire
+	# reflogs if it was running.
+	git config set gc.reflogExpire never &&
+	git config set gc.reflogExpireUnreachable never &&
+
 	echo file >file &&
 	git add file &&
 	git commit -a -m original
@@ -888,4 +894,31 @@
 	test_cmp expect file2
 '
 
+test_expect_success 'pull does not crash when a merge head does not resolve' '
+	test_when_finished "rm -rf up dn" &&
+	git init up &&
+	(
+		cd up &&
+		test_commit base &&
+		git switch -c sideA &&
+		test_commit a &&
+		git switch -c sideB base &&
+		test_commit b
+	) &&
+	git clone up dn &&
+	(
+		cd dn &&
+		git commit-graph write --reachable &&
+		oid=$(git rev-parse refs/remotes/origin/sideA) &&
+		obj=.git/objects/$(test_oid_to_path "$oid") &&
+
+		# Corrupt the object instead of removing it: a missing
+		# object that is still in the commit-graph is caught by
+		# fetch before pull ever reaches the fast-forward check.
+		rm -f "$obj" &&
+		echo garbage >"$obj" &&
+		test_must_fail git pull --no-rebase origin sideA sideB
+	)
+'
+
 test_done
diff --git a/t/t5533-push-cas.sh b/t/t5533-push-cas.sh
index 2e14aec..e6e1360 100755
--- a/t/t5533-push-cas.sh
+++ b/t/t5533-push-cas.sh
@@ -396,4 +396,20 @@
 	)
 '
 
+test_expect_success '"--force-if-includes" should allow forced update when remote-tracking ref has no reflog' '
+	setup_src_dup_dst &&
+	test_when_finished "rm -fr dst src dup" &&
+	(
+		cd src &&
+		git switch branch &&
+		git pull --rebase origin branch &&
+		# the bug needs a remote-tracking ref with no reflog, and
+		# the fetch above wrote one
+		git reflog expire --expire=all refs/remotes/origin/branch &&
+		git reset --hard HEAD^ &&
+		test_commit I &&
+		git push --force-if-includes --force-with-lease="branch"
+	)
+'
+
 test_done
diff --git a/t/t5537-fetch-shallow.sh b/t/t5537-fetch-shallow.sh
index f323cee..16bfaba 100755
--- a/t/t5537-fetch-shallow.sh
+++ b/t/t5537-fetch-shallow.sh
@@ -135,6 +135,28 @@
 	)
 '
 
+test_expect_success 'fetch.writeCommitGraph skips refs that require changes in .git/shallow' '
+	git clone --no-local --depth=2 .git shallow-graph &&
+	git -C shallow-graph checkout --orphan no-shallow &&
+	test_commit -C shallow-graph --no-tag no-shallow &&
+	git init notshallow-graph &&
+	git -C notshallow-graph -c fetch.writeCommitGraph=true \
+		fetch ../shallow-graph/.git "refs/heads/*:refs/remotes/shallow/*" &&
+	test_commit -C shallow-graph --no-tag no-shallow-2 &&
+	rejected=$(git -C shallow-graph rev-parse main) &&
+	(
+		cd notshallow-graph &&
+		git -c fetch.writeCommitGraph=true \
+			fetch ../shallow-graph/.git "refs/heads/*:refs/remotes/shallow/*" &&
+		git for-each-ref --format="%(refname)" >actual.refs &&
+		echo refs/remotes/shallow/no-shallow >expect.refs &&
+		test_cmp expect.refs actual.refs &&
+		test-tool read-graph commit-info shallow/no-shallow &&
+		test_expect_code 1 \
+			test-tool read-graph commit-info $rejected 2>/dev/null
+	)
+'
+
 test_expect_success 'fetch --update-shallow' '
 	(
 	cd shallow &&
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
index 805bec0..1ec971c 100755
--- a/t/t5551-http-fetch-smart.sh
+++ b/t/t5551-http-fetch-smart.sh
@@ -17,6 +17,19 @@
 test "$HTTP_PROTO" = "HTTP/2" && enable_http2
 start_httpd
 
+# The cURL version which Debian 12 ships (v7.88.1) can fail to retry
+# authentication after an early HTTP/2 response. This bug was introduced
+# in cURL v7.88.0 (8c762f5998 (http2: minor buffer and error path fixes,
+# 2023-02-08)) and fixed in v8.3.0 (https://github.com/curl/curl/pull/11756).
+test_lazy_prereq HAVE_CURL_HTTP2_BUG '
+	test_have_prereq HTTP2 &&
+	build_option libcurl |
+	awk -F. '\''
+		($1 == 7 && $2 >= 88) || ($1 == 8 && $2 < 3) { broken = 1 }
+		END { exit !broken }
+	'\''
+'
+
 test_expect_success HTTP2 'enable client-side http/2' '
 	git config --global http.version HTTP/2
 '
@@ -224,7 +237,8 @@
 	test_cmp expect actual
 '
 
-test_expect_success 'clone from auth-only-for-objects repository' '
+test_expect_success !HAVE_CURL_HTTP2_BUG \
+	'clone from auth-only-for-objects repository' '
 	echo two >expect &&
 	set_askpass user@host pass@host &&
 	git clone --bare "$HTTPD_URL/auth-fetch/smart/repo.git" half-auth &&
@@ -233,7 +247,8 @@
 	test_cmp expect actual
 '
 
-test_expect_success 'no-op half-auth fetch does not require a password' '
+test_expect_success !HAVE_CURL_HTTP2_BUG \
+	'no-op half-auth fetch does not require a password' '
 	set_askpass wrong &&
 
 	# NEEDSWORK: When using HTTP(S), protocol v0 supports a "half-auth"
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index b616758..06f6121 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -166,9 +166,9 @@
 
 test_expect_success 'clone with files ref format' '
 	test_when_finished "rm -rf ref-storage" &&
-	git clone --ref-format=files --mirror src ref-storage &&
+	git clone --ref-storage-format=files --mirror src ref-storage &&
 	echo files >expect &&
-	git -C ref-storage rev-parse --show-ref-format >actual &&
+	git -C ref-storage rev-parse --show-ref-storage-format >actual &&
 	test_cmp expect actual
 '
 
@@ -176,7 +176,7 @@
 	cat >expect <<-EOF &&
 	fatal: unknown ref storage format ${SQ}garbage${SQ}
 	EOF
-	test_must_fail git clone --ref-format=garbage --mirror src ref-storage 2>err &&
+	test_must_fail git clone --ref-storage-format=garbage --mirror src ref-storage 2>err &&
 	test_cmp expect err &&
 	test_path_is_missing ref-storage
 '
diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh
index 39a0c31..9e4b98f 100755
--- a/t/t5604-clone-reference.sh
+++ b/t/t5604-clone-reference.sh
@@ -383,4 +383,29 @@
 	git clone --no-local --reference graph.git --dissociate orig clone
 '
 
+test_expect_success 'local clone from linked worktree carries over alternates' '
+	rm -fr base derived derived-wt dst expect &&
+	git init base &&
+	test_commit -C base one &&
+	git clone --shared base derived &&
+	git -C derived worktree add ../derived-wt &&
+	git clone derived-wt dst &&
+	echo "$(pwd)/base/.git/objects" >expect &&
+	test_cmp expect dst/.git/objects/info/alternates &&
+	git -C dst fsck
+'
+
+test_expect_success 'local clone from linked worktree resolves relative alternates' '
+	rm -fr base derived derived-wt dst expect &&
+	git init base &&
+	test_commit -C base one &&
+	git clone --shared base derived &&
+	echo "../../../base/.git/objects" >derived/.git/objects/info/alternates &&
+	git -C derived worktree add ../derived-wt &&
+	git clone derived-wt dst &&
+	echo "$(pwd)/base/.git/objects" >expect &&
+	test_cmp expect dst/.git/objects/info/alternates &&
+	git -C dst fsck
+'
+
 test_done
diff --git a/t/t5703-upload-pack-ref-in-want.sh b/t/t5703-upload-pack-ref-in-want.sh
index 330d049..4348966 100755
--- a/t/t5703-upload-pack-ref-in-want.sh
+++ b/t/t5703-upload-pack-ref-in-want.sh
@@ -295,6 +295,24 @@
 	test_grep "want-ref refs/heads/o/bar" log
 '
 
+test_expect_success 'shallow clone with ref-in-want' '
+       rm -rf local &&
+       GIT_TEST_PROTOCOL_VERSION=2 git clone --depth=1 "file://$REPO" local &&
+       git -C "$REPO" rev-parse main >expected &&
+       git -C local rev-parse refs/remotes/origin/main >actual &&
+       test_cmp expected actual &&
+       git -C local log --oneline refs/remotes/origin/main >log &&
+       test_line_count = 1 log
+'
+
+test_expect_success 'incremental shallow fetch with ref-in-want' '
+       rm -rf local &&
+       GIT_TEST_PROTOCOL_VERSION=2 git clone --depth=1 "file://$REPO" local &&
+       GIT_TEST_PROTOCOL_VERSION=2 git -C local fetch --depth=2 origin main &&
+       git -C local log --oneline refs/remotes/origin/main >log &&
+       test_line_count = 2 log
+'
+
 REPO="$(pwd)/repo-ns"
 
 test_expect_success 'setup namespaced repo' '
diff --git a/t/t5710-promisor-remote-capability.sh b/t/t5710-promisor-remote-capability.sh
index 549acff..463e9e0 100755
--- a/t/t5710-promisor-remote-capability.sh
+++ b/t/t5710-promisor-remote-capability.sh
@@ -173,6 +173,236 @@
 	initialize_server 1 "$oid"
 '
 
+test_expect_success "clone with uploadpack.lazyFetchTrusted" '
+	# No promisors are advertised
+	git -C server config promisor.advertise false &&
+	test_when_finished "rm -rf client" &&
+
+	# The served repo is trusted for lazy fetching
+	test_config_global uploadpack.lazyFetchTrusted "$(pwd)/server" &&
+
+	# Clone without GIT_NO_LAZY_FETCH=0
+	git clone --no-local --filter="blob:limit=5k" server client &&
+
+	# Check that the largest object is not missing on the server
+	# This means the server lazy fetched it
+	check_missing_objects server 0 "" &&
+
+	# Reinitialize server so that the largest object is missing again
+	initialize_server 1 "$oid"
+'
+
+test_expect_success "clone without uploadpack.lazyFetchTrusted fails" '
+	# No promisors are advertised
+	git -C server config promisor.advertise false &&
+	test_when_finished "rm -rf client" &&
+
+	# Note: no uploadpack.lazyFetchTrusted config is set here, so
+	# the served repo is NOT trusted for lazy fetching.
+
+	# Clone without GIT_NO_LAZY_FETCH=0 fails
+	test_must_fail git clone --no-local --filter="blob:limit=5k" server client 2>err &&
+	test_grep "lazy fetching disabled" err &&
+
+	# Check that the largest object is still missing on the server
+	check_missing_objects server 1 "$oid"
+'
+
+test_expect_success "uploadpack.lazyFetchTrusted is ignored in repo config" '
+	# No promisors are advertised
+	git -C server config promisor.advertise false &&
+	test_when_finished "rm -rf client" &&
+
+	# The served repo is trusted for lazy fetching, but this is
+	# done in the repo config, not in protected config, so this is
+	# ignored.
+	test_config -C server uploadpack.lazyFetchTrusted "$(pwd)/server" &&
+
+	# Clone without GIT_NO_LAZY_FETCH=0 fails
+	test_must_fail git clone --no-local --filter="blob:limit=5k" server client 2>err &&
+	test_grep "lazy fetching disabled" err &&
+
+	# Check that the largest object is still missing on the server
+	check_missing_objects server 1 "$oid"
+'
+
+test_expect_success "explicit GIT_NO_LAZY_FETCH overrides uploadpack.lazyFetchTrusted" '
+	# No promisors are advertised
+	git -C server config promisor.advertise false &&
+	test_when_finished "rm -rf client" &&
+
+	# The served repo is trusted for lazy fetching
+	test_config_global uploadpack.lazyFetchTrusted "$(pwd)/server" &&
+
+	# But GIT_NO_LAZY_FETCH=1 disables lazy fetching, so clone fails
+	test_must_fail env GIT_NO_LAZY_FETCH=1 git clone --no-local \
+		--filter="blob:limit=5k" server client 2>err &&
+	test_grep "lazy fetching disabled" err &&
+
+	# Check that the largest object is still missing on the server
+	check_missing_objects server 1 "$oid"
+'
+
+test_expect_success "trusted repo as its own promisor remote does not recurse" '
+	# No promisors are advertised
+	git -C server config promisor.advertise false &&
+	test_when_finished "rm -rf client" &&
+
+	# Add itself as its own remote
+	git -C server remote add self "$TRASH_DIRECTORY_URL/server" &&
+	git -C server config remote.self.promisor true &&
+	test_when_finished "git -C server remote remove self" &&
+
+	# Make "self" the only promisor remote of the server, so that it
+	# cannot get the missing object from "lop". Note that
+	# "remote.lop.partialCloneFilter" also makes "lop" a promisor
+	# remote, so it has to be unset too.
+	git -C server config --unset remote.lop.promisor &&
+	test_when_finished "git -C server config remote.lop.promisor true" &&
+	lop_filter="$(git -C server config remote.lop.partialCloneFilter)" &&
+	git -C server config --unset remote.lop.partialCloneFilter &&
+	test_when_finished "git -C server config remote.lop.partialCloneFilter \"$lop_filter\"" &&
+
+	# Allow lazy fetching from itself
+	test_config_global uploadpack.lazyFetchTrusted "$(pwd)/server" &&
+
+	# Check that lazy fetching fails
+	test_must_fail git clone --no-local --filter="blob:limit=5k" server client 2>err &&
+	test_grep "too many nested lazy fetches" err &&
+
+	# Check that the largest object is still missing on the server
+	check_missing_objects server 1 "$oid"
+'
+
+test_expect_success "uploadpack.lazyFetchTrusted needs the git dir of a non-bare repo" '
+	test_when_finished "rm -rf nonbare nonbare-pack-* client client2" &&
+
+	# Create a non-bare repo, without any worktree content, so that
+	# its largest object can be filtered out below
+	git init nonbare &&
+	git -C nonbare remote add origin "$TRASH_DIRECTORY_URL/template" &&
+	git -C nonbare fetch origin &&
+	git -C nonbare update-ref HEAD FETCH_HEAD &&
+
+	git -C nonbare remote add lop "$TRASH_DIRECTORY_URL/lop" &&
+	git -C nonbare config remote.lop.promisor true &&
+	git -C nonbare config uploadpack.allowFilter true &&
+	git -C nonbare config uploadpack.allowAnySHA1InWant true &&
+	git -C nonbare config promisor.advertise false &&
+
+	# Repack everything, then repack without the largest object and
+	# create a promisor pack, like initialize_server() does
+	git -C nonbare -c repack.writebitmaps=false repack -a -d &&
+	rm -f nonbare/.git/objects/pack/*.promisor &&
+	git -C nonbare -c repack.writebitmaps=false repack -a -d \
+		--filter=blob:limit=5k --filter-to="$(pwd)/nonbare-pack" &&
+	promisor_file=$(ls nonbare/.git/objects/pack/*.pack | sed "s/\.pack/.promisor/") &&
+	>"$promisor_file" &&
+	check_missing_objects nonbare 1 "$oid" &&
+
+	# The worktree path does not identify the repo, so it is not
+	# trusted and the clone fails
+	test_config_global uploadpack.lazyFetchTrusted "$(pwd)/nonbare" &&
+	test_must_fail git clone --no-local --filter="blob:limit=1k" \
+		nonbare client 2>err &&
+	test_grep "lazy fetching disabled" err &&
+	check_missing_objects nonbare 1 "$oid" &&
+
+	# The git dir identifies the repo, so it is trusted and the
+	# clone succeeds
+	test_config_global uploadpack.lazyFetchTrusted "$(pwd)/nonbare/.git" &&
+	git clone --no-local --filter="blob:limit=1k" nonbare client2 &&
+	check_missing_objects nonbare 0 ""
+'
+
+test_expect_success "uploadpack.lazyFetchTrusted needs the git dir a .git file points to" '
+	test_when_finished "rm -rf sepwt sepgit sep-pack-* client client2" &&
+
+	# Create a non-bare repo with a ".git" file pointing to a
+	# separate git dir, without any worktree content, so that its
+	# largest object can be filtered out below
+	git init --separate-git-dir="$(pwd)/sepgit" sepwt &&
+	test_path_is_file sepwt/.git &&
+	git -C sepwt remote add origin "$TRASH_DIRECTORY_URL/template" &&
+	git -C sepwt fetch origin &&
+	git -C sepwt update-ref HEAD FETCH_HEAD &&
+
+	git -C sepwt remote add lop "$TRASH_DIRECTORY_URL/lop" &&
+	git -C sepwt config remote.lop.promisor true &&
+	git -C sepwt config uploadpack.allowFilter true &&
+	git -C sepwt config uploadpack.allowAnySHA1InWant true &&
+	git -C sepwt config promisor.advertise false &&
+
+	# Repack everything, then repack without the largest object and
+	# create a promisor pack, like initialize_server() does
+	git -C sepwt -c repack.writebitmaps=false repack -a -d &&
+	rm -f sepgit/objects/pack/*.promisor &&
+	git -C sepwt -c repack.writebitmaps=false repack -a -d \
+		--filter=blob:limit=5k --filter-to="$(pwd)/sep-pack" &&
+	promisor_file=$(ls sepgit/objects/pack/*.pack | sed "s/\.pack/.promisor/") &&
+	>"$promisor_file" &&
+	check_missing_objects sepwt 1 "$oid" &&
+
+	# The ".git" file does not identify the repo, so it is not
+	# trusted and the clone fails
+	test_config_global uploadpack.lazyFetchTrusted "$(pwd)/sepwt/.git" &&
+	test_must_fail git clone --no-local --filter="blob:limit=1k" \
+		sepwt client 2>err &&
+	test_grep "lazy fetching disabled" err &&
+	check_missing_objects sepwt 1 "$oid" &&
+
+	# The git dir the ".git" file points to identifies the repo, so
+	# it is trusted and the clone succeeds
+	test_config_global uploadpack.lazyFetchTrusted "$(pwd)/sepgit" &&
+	git clone --no-local --filter="blob:limit=1k" sepwt client2 &&
+	check_missing_objects sepwt 0 ""
+'
+
+test_expect_success "uploadpack.lazyFetchTrusted trust does not propagate to promisor remotes" '
+	# No promisors are advertised
+	git -C server config promisor.advertise false &&
+	test_when_finished "rm -rf client lop2" &&
+
+	# Create "lop2", a partial clone that is also missing the
+	# largest object, and that can lazily fetch it from "lop"
+	test_config -C template uploadpack.allowFilter true &&
+	git clone --bare --no-local --filter="blob:limit=5k" \
+		"$TRASH_DIRECTORY_URL/template" lop2 &&
+	git -C lop2 remote set-url origin "$TRASH_DIRECTORY_URL/lop" &&
+	git -C lop2 config uploadpack.allowFilter true &&
+	git -C lop2 config uploadpack.allowAnySHA1InWant true &&
+	check_missing_objects lop2 1 "$oid" &&
+
+	# Make "lop2" the only promisor remote of the server. Note that
+	# "remote.lop.partialCloneFilter" also makes "lop" a promisor
+	# remote, so it has to be unset too.
+	git -C server remote add lop2 "$TRASH_DIRECTORY_URL/lop2" &&
+	git -C server config remote.lop2.promisor true &&
+	test_when_finished "git -C server remote remove lop2" &&
+	git -C server config --unset remote.lop.promisor &&
+	test_when_finished "git -C server config remote.lop.promisor true" &&
+	lop_filter="$(git -C server config remote.lop.partialCloneFilter)" &&
+	git -C server config --unset remote.lop.partialCloneFilter &&
+	test_when_finished "git -C server config remote.lop.partialCloneFilter \"$lop_filter\"" &&
+
+	# Only the server is trusted, not "lop2", so the upload-pack
+	# serving "lop2" to the server refuses to lazily fetch from "lop"
+	test_config_global uploadpack.lazyFetchTrusted "$(pwd)/server" &&
+	test_must_fail git clone --no-local --filter="blob:limit=5k" \
+		server client 2>err &&
+	test_grep "lazy fetching disabled" err &&
+	check_missing_objects server 1 "$oid" &&
+	check_missing_objects lop2 1 "$oid" &&
+
+	# Once "lop2" is also trusted, the clone succeeds
+	git config --global --add uploadpack.lazyFetchTrusted "$(pwd)/lop2" &&
+	git clone --no-local --filter="blob:limit=5k" server client &&
+	check_missing_objects server 0 "" &&
+
+	# Reinitialize server so that the largest object is missing again
+	initialize_server 1 "$oid"
+'
+
 test_expect_success "init + fetch with promisor.advertise set to 'true'" '
 	git -C server config promisor.advertise true &&
 	test_when_finished "rm -rf client" &&
diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh
index 1546d5f..7ea491c 100755
--- a/t/t6437-submodule-merge.sh
+++ b/t/t6437-submodule-merge.sh
@@ -514,4 +514,90 @@
 	)
 '
 
+test_expect_success 'setup for commit-graphs in superproject and submodule' '
+	git init commit-graph &&
+	(cd commit-graph &&
+	git init sub &&
+	for i in 1 2 3 4 5 6 7 8 9 10
+	do
+		test_commit -C sub "sub-$i" || return 1
+	done &&
+
+	git -C sub checkout --detach sub-1 &&
+	git add sub &&
+	git commit -m base &&
+
+	# Write the commit-graph of the superproject while it only
+	# contains a single commit. Any commit-graph position of the
+	# submodule is thus out of bounds for the superproject.
+	git commit-graph write --reachable &&
+
+	git checkout -b side &&
+	git -C sub checkout --detach sub-5 &&
+	git add sub &&
+	git commit -m side &&
+
+	git checkout main &&
+	git -C sub checkout --detach sub-10 &&
+	git add sub &&
+	git commit -m main &&
+
+	git -C sub commit-graph write --reachable)
+'
+
+test_expect_success 'merge does not mix up superproject and submodule commit-graphs' '
+	(cd commit-graph &&
+	git merge side &&
+	git rev-parse HEAD:sub >actual &&
+	git -C sub rev-parse sub-10^{commit} >expect &&
+	test_cmp expect actual)
+'
+
+test_expect_success 'merge with many packed submodules reports conflicts' '
+	test_config_global protocol.file.allow always &&
+
+	# Create 16 submodules with two divergent branches each.
+	submodules="A B C D E F G H I J K L M N O P" &&
+	for name in $submodules
+	do
+		git init source-$name &&
+		test_commit -C source-$name $name-main &&
+		git -C source-$name switch --create branch-a main &&
+		git -C source-$name commit --allow-empty --message $name-branch-a &&
+		git -C source-$name switch --create branch-b main &&
+		git -C source-$name commit --allow-empty --message $name-branch-b || return 1
+	done &&
+
+	# Create the superproject and add all submodules.
+	git init many-packed &&
+	for name in $submodules
+	do
+		git -C many-packed submodule add --branch main "file://$PWD/source-$name" $name || return 1
+	done &&
+	git -C many-packed commit --message main &&
+
+	# Create two divergent commits in the superproject that update all
+	# submodules to the divergent branches.
+	for branch in branch-a branch-b
+	do
+		git -C many-packed switch -c $branch main &&
+		for name in $submodules
+		do
+			git -C many-packed/$name switch $branch || return 1
+		done &&
+		git -C many-packed add $submodules &&
+		git -C many-packed commit --message $branch || return 1
+	done &&
+
+	# Clone the superproject to ensure that everything is well-packed and
+	# then merge the two branches, creating conflicts for every submodule.
+	git clone many-packed many-packed-clone &&
+	git -C many-packed-clone submodule update --init &&
+	git -C many-packed-clone switch branch-a &&
+	test_expect_code 1 git -C many-packed-clone -c advice.submoduleMergeConflict=false merge branch-b >out 2>err &&
+	grep "^CONFLICT (submodule)" out >conflicts &&
+	test_line_count = 16 conflicts &&
+	test_must_be_empty err
+'
+
 test_done
diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh
index 86011e7..801cc83 100755
--- a/t/t7003-filter-branch.sh
+++ b/t/t7003-filter-branch.sh
@@ -121,10 +121,33 @@
 test_expect_success 'using --state-branch to skip already rewritten commits' '
 	test_when_finished git reset --hard $V &&
 	git reset --hard $V &&
-	git filter-branch --state-branch state -f --tree-filter "touch file || :" HEAD &&
+	git filter-branch --state-branch state -f --tree-filter "exit 1" HEAD &&
 	test_cmp_rev $W HEAD
 '
 
+test_expect_success '--state-branch incremental rewrite uses the rewritten parents' '
+	test_when_finished "rm -fr incremental" &&
+	git init incremental &&
+	(
+		cd incremental &&
+		mkdir sub &&
+		test_commit first sub/file &&
+		test_commit outside root-file &&
+		git filter-branch --state-branch refs/state \
+			--prune-empty --subdirectory-filter sub -- HEAD &&
+		rewritten_first=$(git rev-parse HEAD) &&
+		git reset --hard outside &&
+		test_commit second sub/file &&
+		git filter-branch -f --state-branch refs/state \
+			--prune-empty --subdirectory-filter sub -- outside..HEAD &&
+		test_cmp_rev $rewritten_first HEAD^ &&
+		git show refs/state:filter.map >map &&
+		echo "$(git rev-parse second):$(git rev-parse HEAD)" >expect &&
+		grep "^$(git rev-parse second):" map >actual &&
+		test_cmp expect actual
+	)
+'
+
 git tag oldD HEAD~4
 test_expect_success 'rewrite one branch, keeping a side branch' '
 	git branch modD oldD &&
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 8c795d7..a0c2c9a 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -11,6 +11,7 @@
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
 . ./test-lib.sh
+GNUPGHOME_NOT_USED=$GNUPGHOME
 . "$TEST_DIRECTORY"/lib-gpg.sh
 . "$TEST_DIRECTORY"/lib-terminal.sh
 
@@ -1525,8 +1526,7 @@
 # try to verify without gpg:
 
 test_expect_success GPG 'verify signed tag fails when public key is not present' '
-	rm -rf gpghome &&
-	test_must_fail git tag -v signed-tag
+	test_must_fail env GNUPGHOME="$GNUPGHOME_NOT_USED" git tag -v signed-tag
 '
 
 test_expect_success 'git tag -a fails if tag annotation is empty' '
diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh
index 942ddbb..a9b435b 100755
--- a/t/t7060-wtstatus.sh
+++ b/t/t7060-wtstatus.sh
@@ -37,7 +37,7 @@
 	cat >expect <<EOF &&
 On branch side
 You have unmerged paths.
-  (fix conflicts and run "git commit")
+  (fix conflicts and run "git merge --continue")
   (use "git merge --abort" to abort the merge)
 
 Unmerged paths:
@@ -141,7 +141,7 @@
 	cat >expected <<\EOF &&
 On branch main
 You have unmerged paths.
-  (fix conflicts and run "git commit")
+  (fix conflicts and run "git merge --continue")
   (use "git merge --abort" to abort the merge)
 
 Unmerged paths:
@@ -174,7 +174,7 @@
 	cat >expected <<\EOF &&
 On branch conflict_second
 You have unmerged paths.
-  (fix conflicts and run "git commit")
+  (fix conflicts and run "git merge --continue")
   (use "git merge --abort" to abort the merge)
 
 Unmerged paths:
@@ -198,7 +198,7 @@
 	cat >expected <<\EOF &&
 On branch conflict_second
 You have unmerged paths.
-  (fix conflicts and run "git commit")
+  (fix conflicts and run "git merge --continue")
   (use "git merge --abort" to abort the merge)
 
 Changes to be committed:
diff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh
index 14ddaba..1829334 100755
--- a/t/t7061-wtstatus-ignore.sh
+++ b/t/t7061-wtstatus-ignore.sh
@@ -340,4 +340,9 @@
 	test_cmp expected actual
 '
 
+test_expect_success 'status omits excluded directory with submodule on prefix match' '
+	git status --porcelain --ignored -u tracke >actual &&
+	test_must_be_empty actual
+'
+
 test_done
diff --git a/t/t7424-submodule-mixed-ref-formats.sh b/t/t7424-submodule-mixed-ref-formats.sh
index 559713b..197c02b 100755
--- a/t/t7424-submodule-mixed-ref-formats.sh
+++ b/t/t7424-submodule-mixed-ref-formats.sh
@@ -6,13 +6,13 @@
 
 test_ref_format () {
 	echo "$2" >expect &&
-	git -C "$1" rev-parse --show-ref-format >actual &&
+	git -C "$1" rev-parse --show-ref-storage-format >actual &&
 	test_cmp expect actual
 }
 
 for OTHER_FORMAT in files reftable
 do
-	if test "$OTHER_FORMAT" = "$GIT_DEFAULT_REF_FORMAT"
+	if test "$OTHER_FORMAT" = "$GIT_DEFAULT_REF_STORAGE_FORMAT"
 	then
 		continue
 	fi
@@ -31,7 +31,7 @@
 	(
 		cd parent &&
 		test_commit parent &&
-		git init --ref-format=$OTHER_FORMAT submodule &&
+		git init --ref-storage-format=$OTHER_FORMAT submodule &&
 		test_commit -C submodule submodule &&
 		git submodule add ./submodule
 	)
@@ -43,8 +43,8 @@
 	git init submodule &&
 	test_commit -C submodule submodule-initial &&
 	git init upstream &&
-	test_ref_format upstream "$GIT_DEFAULT_REF_FORMAT" &&
-	git -C upstream submodule add --ref-format="$OTHER_FORMAT" "file://$(pwd)/submodule" &&
+	test_ref_format upstream "$GIT_DEFAULT_REF_STORAGE_FORMAT" &&
+	git -C upstream submodule add --ref-storage-format="$OTHER_FORMAT" "file://$(pwd)/submodule" &&
 	test_ref_format upstream/submodule "$OTHER_FORMAT"
 '
 
@@ -59,13 +59,13 @@
 
 	# The upstream repository and its submodule should be using the default
 	# ref format.
-	test_ref_format upstream "$GIT_DEFAULT_REF_FORMAT" &&
-	test_ref_format upstream/submodule "$GIT_DEFAULT_REF_FORMAT" &&
+	test_ref_format upstream "$GIT_DEFAULT_REF_STORAGE_FORMAT" &&
+	test_ref_format upstream/submodule "$GIT_DEFAULT_REF_STORAGE_FORMAT" &&
 
 	# The cloned repositories should use the other ref format that we have
-	# specified via `--ref-format`. The option should propagate to cloned
+	# specified via `--ref-storage-format`. The option should propagate to cloned
 	# submodules.
-	git clone --ref-format=$OTHER_FORMAT --recurse-submodules \
+	git clone --ref-storage-format=$OTHER_FORMAT --recurse-submodules \
 		upstream downstream &&
 	test_ref_format downstream "$OTHER_FORMAT" &&
 	test_ref_format downstream/submodule "$OTHER_FORMAT"
@@ -81,8 +81,8 @@
 	git -C upstream commit -m "upstream submodule" &&
 
 	git clone --no-recurse-submodules "file://$(pwd)/upstream" downstream &&
-	test_ref_format downstream "$GIT_DEFAULT_REF_FORMAT" &&
-	git -C downstream submodule update --init --ref-format=$OTHER_FORMAT &&
+	test_ref_format downstream "$GIT_DEFAULT_REF_STORAGE_FORMAT" &&
+	git -C downstream submodule update --init --ref-storage-format=$OTHER_FORMAT &&
 	test_ref_format downstream/submodule "$OTHER_FORMAT"
 '
 
@@ -94,11 +94,11 @@
 	git init main &&
 	git -C main submodule add "file://$(pwd)/submodule" &&
 	git -C main commit -m "add submodule" &&
-	git -C main/submodule refs migrate --ref-format=$OTHER_FORMAT &&
+	git -C main/submodule refs migrate --ref-storage-format=$OTHER_FORMAT &&
 
 	# The main repository should use the default ref format now, whereas
 	# the submodule should use the other format.
-	test_ref_format main "$GIT_DEFAULT_REF_FORMAT" &&
+	test_ref_format main "$GIT_DEFAULT_REF_STORAGE_FORMAT" &&
 	test_ref_format main/submodule "$OTHER_FORMAT" &&
 
 	cat >expect <<-EOF &&
@@ -122,8 +122,8 @@
 	# Clone the upstream repository such that the main repo and its
 	# submodules have different formats.
 	git clone --no-recurse-submodules "file://$(pwd)/upstream" downstream &&
-	git -C downstream submodule update --init --ref-format=$OTHER_FORMAT &&
-	test_ref_format downstream "$GIT_DEFAULT_REF_FORMAT" &&
+	git -C downstream submodule update --init --ref-storage-format=$OTHER_FORMAT &&
+	test_ref_format downstream "$GIT_DEFAULT_REF_STORAGE_FORMAT" &&
 	test_ref_format downstream/submodule "$OTHER_FORMAT" &&
 
 	# Update the upstream submodule as well as the owning repository such
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index aca4b6d..f2e712a 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -31,7 +31,7 @@
 	cat >expected <<\EOF &&
 On branch conflicts
 You have unmerged paths.
-  (fix conflicts and run "git commit")
+  (fix conflicts and run "git merge --continue")
   (use "git merge --abort" to abort the merge)
 
 Unmerged paths:
@@ -53,7 +53,7 @@
 	cat >expected <<\EOF &&
 On branch conflicts
 All conflicts fixed but you are still merging.
-  (use "git commit" to conclude merge)
+  (use "git merge --continue" to conclude the merge)
 
 Changes to be committed:
 	modified:   main.txt
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index 64fe217..8f6109f 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -801,6 +801,15 @@
 	test_cmp result.1-5 file
 '
 
+test_expect_success 'fast-forward merge with --autostash, stash.index' '
+	git reset --hard c0 &&
+	git stash clear &&
+	echo staged >>z && git add z &&
+	git -c stash.index=true merge --autostash c1 2>err &&
+	test_grep "Applied autostash." err &&
+	test_stdout_line_count = 0 git stash list
+'
+
 test_expect_success 'failed fast-forward merge with --autostash' '
 	git reset --hard c0 &&
 	git merge-file file file.orig file.5 &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 38cf221..978c42c 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -3227,6 +3227,7 @@
 	fixup Z
 	reword Z
 	split Z
+	squash Z
 	EOF
 	test_completion "git history --" ""
 '
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index ed9779b..09a41d5 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -1737,7 +1737,7 @@
 
 # Detect the ref format in use.
 test_detect_ref_format () {
-	echo "${GIT_TEST_DEFAULT_REF_FORMAT:-files}"
+	echo "${GIT_TEST_DEFAULT_REF_STORAGE_FORMAT:-files}"
 }
 
 # Load common hash metadata and common placeholder object IDs for use with
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 1f0505e..321c2ba 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -575,8 +575,8 @@
 GIT_TEST_BUILTIN_HASH=$("$GIT_BINARY" version --build-options | sed -ne 's/^default-hash: //p')
 GIT_DEFAULT_HASH="${GIT_TEST_DEFAULT_HASH:-$GIT_TEST_BUILTIN_HASH}"
 export GIT_DEFAULT_HASH
-GIT_DEFAULT_REF_FORMAT="${GIT_TEST_DEFAULT_REF_FORMAT:-files}"
-export GIT_DEFAULT_REF_FORMAT
+GIT_DEFAULT_REF_STORAGE_FORMAT="${GIT_TEST_DEFAULT_REF_STORAGE_FORMAT:-files}"
+export GIT_DEFAULT_REF_STORAGE_FORMAT
 
 # Tests using GIT_TRACE typically don't want <timestamp> <file>:<line> output
 GIT_TRACE_BARE=1
@@ -1752,13 +1752,13 @@
 	;;
 esac
 
-case "$GIT_DEFAULT_REF_FORMAT" in
+case "$GIT_DEFAULT_REF_STORAGE_FORMAT" in
 files)
 	test_set_prereq REFFILES;;
 reftable)
 	test_set_prereq REFTABLE;;
 *)
-	echo 2>&1 "error: unknown ref format $GIT_DEFAULT_REF_FORMAT"
+	echo 2>&1 "error: unknown ref storage format $GIT_DEFAULT_REF_STORAGE_FORMAT"
 	exit 1
 	;;
 esac
diff --git a/t/unit-tests/u-reftable-table.c b/t/unit-tests/u-reftable-table.c
index bd04b47..1e4378b 100644
--- a/t/unit-tests/u-reftable-table.c
+++ b/t/unit-tests/u-reftable-table.c
@@ -257,7 +257,7 @@ void test_reftable_table__seek_invalid_log_offset(void)
 	 * know that the table is corrupt, so the seek must report a format
 	 * error instead of pretending that the section is empty.
 	 */
-	reftable_table_init_log_iterator(table, &it);
+	cl_assert_equal_i(reftable_table_init_log_iterator(table, &it), 0);
 	cl_assert_equal_i(reftable_iterator_seek_log(&it, ""),
 			  REFTABLE_FORMAT_ERROR);
 
diff --git a/tmp-objdir.c b/tmp-objdir.c
index deaaf6b..3debb60 100644
--- a/tmp-objdir.c
+++ b/tmp-objdir.c
@@ -12,13 +12,16 @@
 #include "quote.h"
 #include "odb.h"
 #include "odb/source.h"
+#include "odb/source-files.h"
+#include "odb/source-loose.h"
 #include "repository.h"
 
 struct tmp_objdir {
 	struct repository *repo;
 	struct strbuf path;
 	struct strvec env;
-	struct odb_source *prev_source;
+	struct odb_files_dir *temp_dir;
+	struct odb_files_dir *orig_dir;
 	int will_destroy;
 };
 
@@ -51,6 +54,27 @@ static void tmp_objdir_reparent(const char *old_cwd,
 	free(path);
 }
 
+/*
+ * Restore the primary source that was previously replaced by
+ * `tmp_objdir_create()`.
+ */
+static void tmp_objdir_restore_source(struct tmp_objdir *t)
+{
+	struct odb_source_files *files = odb_source_files_downcast(t->repo->objects->source);
+	struct odb_files_dir *cur_dir = files->dirs;
+
+	if (t->temp_dir != files->dirs)
+		BUG("expected %s as primary object store; found %s",
+		    t->temp_dir->abspath, cur_dir->abspath);
+
+	if (t->temp_dir->next != t->orig_dir)
+		BUG("we expect the old primary object store to be the first alternate");
+
+	t->repo->disable_ref_updates = false;
+	files->dirs = t->orig_dir;
+	odb_files_dir_free(cur_dir);
+}
+
 int tmp_objdir_destroy(struct tmp_objdir *t)
 {
 	int err;
@@ -61,8 +85,8 @@ int tmp_objdir_destroy(struct tmp_objdir *t)
 	if (t == the_tmp_objdir)
 		the_tmp_objdir = NULL;
 
-	if (t->prev_source)
-		odb_restore_primary_source(t->repo->objects, t->prev_source, t->path.buf);
+	if (t->orig_dir)
+		tmp_objdir_restore_source(t);
 
 	err = remove_dir_recursively(&t->path, 0);
 
@@ -132,8 +156,10 @@ static int setup_tmp_objdir(const char *root)
 }
 
 struct tmp_objdir *tmp_objdir_create(struct repository *r,
-				     const char *prefix)
+				     const char *prefix,
+				     int will_destroy)
 {
+	struct odb_source_files *files = odb_source_files_downcast(r->objects->source);
 	static int installed_handlers;
 	struct tmp_objdir *t;
 
@@ -142,6 +168,7 @@ struct tmp_objdir *tmp_objdir_create(struct repository *r,
 
 	t = xcalloc(1, sizeof(*t));
 	t->repo = r;
+	t->will_destroy = will_destroy;
 	strbuf_init(&t->path, 0);
 	strvec_init(&t->env);
 
@@ -179,6 +206,19 @@ struct tmp_objdir *tmp_objdir_create(struct repository *r,
 	env_replace(&t->env, GIT_QUARANTINE_ENVIRONMENT,
 		    absolute_path(t->path.buf));
 
+	/*
+	 * Make a new primary source and link the old primary source in as an
+	 * alternate. Disable ref updates while a temporary source is active,
+	 * since the objects in the database may roll back.
+	 */
+	t->temp_dir = odb_files_dir_new(t->repo->objects, t->path.buf, false);
+	t->temp_dir->loose->will_destroy = will_destroy;
+	t->temp_dir->next = files->dirs;
+
+	t->orig_dir = files->dirs;
+	files->dirs = t->temp_dir;
+	t->repo->disable_ref_updates = true;
+
 	return t;
 }
 
@@ -295,11 +335,11 @@ int tmp_objdir_migrate(struct tmp_objdir *t)
 	if (!t)
 		return 0;
 
-	if (t->prev_source) {
-		if (t->repo->objects->sources->will_destroy)
+	if (t->orig_dir) {
+		if (t->will_destroy)
 			BUG("migrating an ODB that was marked for destruction");
-		odb_restore_primary_source(t->repo->objects, t->prev_source, t->path.buf);
-		t->prev_source = NULL;
+		tmp_objdir_restore_source(t);
+		t->orig_dir = NULL;
 	}
 
 	strbuf_addbuf(&src, &t->path);
@@ -320,14 +360,3 @@ const char **tmp_objdir_env(const struct tmp_objdir *t)
 		return NULL;
 	return t->env.v;
 }
-
-struct odb_source *tmp_objdir_replace_primary_odb(struct tmp_objdir *t,
-						  int will_destroy)
-{
-	if (t->prev_source)
-		BUG("the primary object database is already replaced");
-	t->will_destroy = will_destroy;
-
-	return odb_set_temporary_primary_source(t->repo->objects, t->path.buf,
-						will_destroy, &t->prev_source);
-}
diff --git a/tmp-objdir.h b/tmp-objdir.h
index 05f0d08..a301b01 100644
--- a/tmp-objdir.h
+++ b/tmp-objdir.h
@@ -11,7 +11,7 @@
  * Example:
  *
  *	struct child_process child = CHILD_PROCESS_INIT;
- *	struct tmp_objdir *t = tmp_objdir_create(repo, "incoming");
+ *	struct tmp_objdir *t = tmp_objdir_create(repo, "incoming", 0);
  *	strvec_push(&child.args, cmd);
  *	strvec_pushv(&child.env, tmp_objdir_env(t));
  *	if (!run_command(&child)) && !tmp_objdir_migrate(t))
@@ -25,10 +25,11 @@ struct repository;
 struct tmp_objdir;
 
 /*
- * Create a new temporary object directory with the specified prefix;
- * returns NULL on failure.
+ * Create a new temporary object directory with the specified prefix and
+ * install the directory as the primary write target; returns NULL on failure.
  */
-struct tmp_objdir *tmp_objdir_create(struct repository *r, const char *prefix);
+struct tmp_objdir *tmp_objdir_create(struct repository *r, const char *prefix,
+				     int will_destroy);
 
 /*
  * Return a list of environment strings, suitable for use with
@@ -55,13 +56,4 @@ int tmp_objdir_destroy(struct tmp_objdir *);
  */
 void tmp_objdir_discard_objects(struct tmp_objdir *);
 
-/*
- * Replaces the writable object store in the current process with the temporary
- * object directory and makes the former main object store an alternate.
- * If will_destroy is nonzero, the object directory may not be migrated. Returns
- * the newly installed primary source.
- */
-struct odb_source *tmp_objdir_replace_primary_odb(struct tmp_objdir *,
-						  int will_destroy);
-
 #endif /* TMP_OBJDIR_H */
diff --git a/tools/coccinelle/free.cocci b/tools/coccinelle/free.cocci
index 03799e1..f2af140 100644
--- a/tools/coccinelle/free.cocci
+++ b/tools/coccinelle/free.cocci
@@ -6,16 +6,8 @@
   free(E);
 |
   commit_list_free(E);
-)
-
-@@
-expression E;
-@@
-- if (!E)
-(
-  free(E);
 |
-  commit_list_free(E);
+  FREE_AND_NULL(E);
 )
 
 @@
diff --git a/upload-pack.c b/upload-pack.c
index 22573ad..11a8073 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -34,6 +34,8 @@
 #include "json-writer.h"
 #include "strmap.h"
 #include "promisor-remote.h"
+#include "setup.h"
+#include "abspath.h"
 
 /* Remember to update object flag allocation in object.h */
 #define THEY_HAVE	(1u << 11)
@@ -530,9 +532,9 @@ static int ok_to_give_up(struct upload_pack_data *data)
 	if (!data->have_obj.nr)
 		return 0;
 
-	return can_all_from_reach_with_flag(&data->want_obj, THEY_HAVE,
-					    COMMON_KNOWN, data->oldest_have,
-					    min_generation);
+	return can_all_from_reach_with_flag(the_repository, &data->want_obj,
+					    THEY_HAVE, COMMON_KNOWN,
+					    data->oldest_have, min_generation);
 }
 
 static int get_common_commits(struct upload_pack_data *data,
@@ -1343,6 +1345,63 @@ static int upload_pack_config(const char *var, const char *value,
 	return parse_hide_refs_config(var, value, "uploadpack", &data->hidden_refs);
 }
 
+/*
+ * Only absolute paths make sense here. Unlike 'safe.directory', "."
+ * is not accepted, as the served repository is always identified by
+ * an absolute path.
+ */
+static bool allow_trusted_path(const char *path, void *cbdata_)
+{
+	struct path_allowlist_cb_data *cbdata = cbdata_;
+
+	if (is_absolute_path(path))
+		return true;
+
+	warning(_("%s '%s' not absolute"), cbdata->key, path);
+	return false;
+}
+
+struct lazy_fetch_trusted {
+	char *repo_path;
+	bool trusted;
+};
+
+static int upload_pack_protected_lazy_fetch_config(const char *var, const char *value,
+						   const struct config_context *ctx UNUSED,
+						   void *cb_data)
+{
+	struct lazy_fetch_trusted *data = cb_data;
+	struct path_allowlist_cb_data cbdata = { .key = var };
+
+	if (strcmp("uploadpack.lazyfetchtrusted", var))
+		return 0;
+
+	path_allowlist_config_apply(var, value, data->repo_path, &data->trusted,
+				    allow_trusted_path, &cbdata);
+
+	return 0;
+}
+
+bool upload_pack_lazy_fetch_trusted(struct repository *r)
+{
+	struct lazy_fetch_trusted data = { 0 };
+
+	/*
+	 * A served repository is identified by its git directory, as
+	 * `upload-pack` uses enter_repo() instead of the usual repository
+	 * discovery, so its worktree, if any, is never known here.
+	 */
+	data.repo_path = real_pathdup(r->gitdir, 0);
+	if (!data.repo_path)
+		return false;
+
+	git_protected_config(upload_pack_protected_lazy_fetch_config, &data);
+
+	free(data.repo_path);
+
+	return !!data.trusted;
+}
+
 static int upload_pack_protected_config(const char *var, const char *value,
 					const struct config_context *ctx UNUSED,
 					void *cb_data)
@@ -1777,8 +1836,8 @@ int upload_pack_v2(struct repository *r, struct packet_reader *request)
 				state = UPLOAD_DONE;
 			break;
 		case UPLOAD_SEND_PACK:
-			send_wanted_ref_info(&data);
 			send_shallow_info(&data);
+			send_wanted_ref_info(&data);
 
 			if (data.uri_protocols.nr) {
 				create_pack_file(&data, &data.uri_protocols);
diff --git a/upload-pack.h b/upload-pack.h
index d6ee25e..b221299 100644
--- a/upload-pack.h
+++ b/upload-pack.h
@@ -12,4 +12,7 @@ struct strbuf;
 int upload_pack_advertise(struct repository *r,
 			  struct strbuf *value);
 
+/* Is this repo trusted for lazy fetching? */
+bool upload_pack_lazy_fetch_trusted(struct repository *r);
+
 #endif /* UPLOAD_PACK_H */
diff --git a/wrapper.c b/wrapper.c
index 561f9ee..05a9cd3 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -376,6 +376,10 @@ ssize_t writev_in_full(int fd, struct iovec *iov, int iovcnt)
 			return -1;
 		}
 
+		if (signed_add_overflows(total_written, bytes_written)) {
+			errno = EOVERFLOW;
+			return -1;
+		}
 		total_written += bytes_written;
 
 		/*
diff --git a/wt-status.c b/wt-status.c
index 57772c7..238bb48 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -1273,7 +1273,7 @@ static void show_merge_in_progress(struct wt_status *s,
 		status_printf_ln(s, color, _("You have unmerged paths."));
 		if (s->hints) {
 			status_printf_ln(s, color,
-					 _("  (fix conflicts and run \"git commit\")"));
+					 _("  (fix conflicts and run \"git merge --continue\")"));
 			status_printf_ln(s, color,
 					 _("  (use \"git merge --abort\" to abort the merge)"));
 		}
@@ -1282,7 +1282,7 @@ static void show_merge_in_progress(struct wt_status *s,
 			_("All conflicts fixed but you are still merging."));
 		if (s->hints)
 			status_printf_ln(s, color,
-				_("  (use \"git commit\" to conclude merge)"));
+				_("  (use \"git merge --continue\" to conclude the merge)"));
 	}
 	wt_longstatus_print_trailer(s);
 }
diff --git a/xdiff-interface.c b/xdiff-interface.c
index db69386..b3e9f19 100644
--- a/xdiff-interface.c
+++ b/xdiff-interface.c
@@ -53,7 +53,7 @@ static int consume_one(void *priv_, char *s, unsigned long size)
 	return 0;
 }
 
-static int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf)
+static int xdiff_outf(void *priv_, mmfile_t *mb, int nbuf)
 {
 	struct xdiff_emit_state *priv = priv_;
 	int i;
@@ -166,8 +166,9 @@ int read_mmfile(mmfile_t *ptr, const char *filename)
 	if (!(f = fopen(filename, "rb")))
 		return error_errno("Could not open %s", filename);
 	sz = xsize_t(st.st_size);
-	ptr->ptr = xmalloc(sz ? sz : 1);
+	ptr->ptr = xmallocz(sz);
 	if (sz && fread(ptr->ptr, sz, 1, f) != 1) {
+		FREE_AND_NULL(ptr->ptr);
 		fclose(f);
 		return error("Could not read %s", filename);
 	}
diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h
index dc37071..8fa513f 100644
--- a/xdiff/xdiff.h
+++ b/xdiff/xdiff.h
@@ -70,14 +70,9 @@ extern "C" {
 
 typedef struct s_mmfile {
 	char *ptr;
-	long size;
+	size_t size;
 } mmfile_t;
 
-typedef struct s_mmbuffer {
-	char *ptr;
-	long size;
-} mmbuffer_t;
-
 typedef struct s_xpparam {
 	unsigned long flags;
 
@@ -96,7 +91,7 @@ typedef struct s_xdemitcb {
 			long old_begin, long old_nr,
 			long new_begin, long new_nr,
 			const char *func, long funclen);
-	int (*out_line)(void *, mmbuffer_t *, int);
+	int (*out_line)(void *, mmfile_t *, int);
 } xdemitcb_t;
 
 typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv);
@@ -144,7 +139,7 @@ typedef struct s_xmparam {
 #define DEFAULT_CONFLICT_MARKER_SIZE 7
 
 int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2,
-		xmparam_t const *xmp, mmbuffer_t *result);
+		xmparam_t const *xmp, mmfile_t *result);
 
 #ifdef __cplusplus
 }
diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c
index 659ad4e..7b37968 100644
--- a/xdiff/xmerge.c
+++ b/xdiff/xmerge.c
@@ -504,7 +504,7 @@ static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m,
  */
 static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1,
 		xdfenv_t *xe2, xdchange_t *xscr2,
-		xmparam_t const *xmp, mmbuffer_t *result)
+		xmparam_t const *xmp, mmfile_t *result)
 {
 	xdmerge_t *changes, *c;
 	xpparam_t const *xpp = &xmp->xpp;
@@ -682,7 +682,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1,
 }
 
 int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2,
-		xmparam_t const *xmp, mmbuffer_t *result)
+		xmparam_t const *xmp, mmfile_t *result)
 {
 	xdchange_t *xscr1 = NULL, *xscr2 = NULL;
 	xdfenv_t xe1, xe2;
diff --git a/xdiff/xutils.c b/xdiff/xutils.c
index 9a999ac..4215f64 100644
--- a/xdiff/xutils.c
+++ b/xdiff/xutils.c
@@ -39,7 +39,7 @@ uint64_t xdl_bogosqrt(uint64_t n) {
 int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize,
 		     xdemitcb_t *ecb) {
 	int i = 2;
-	mmbuffer_t mb[3];
+	mmfile_t mb[3];
 
 	mb[0].ptr = (char *) pre;
 	mb[0].size = psize;
@@ -392,7 +392,7 @@ static int xdl_format_hunk_hdr(long s1, long c1, long s2, long c2,
 			       const char *func, long funclen,
 			       xdemitcb_t *ecb) {
 	int nb = 0;
-	mmbuffer_t mb;
+	mmfile_t mb;
 	char buf[128];
 
 	memcpy(buf, "@@ -", 4);