Merge branch 'ma/test-libcurl-prereq' into maint-2.46

Test portability fix.

* ma/test-libcurl-prereq:
  t0211: add missing LIBCURL prereq
  t1517: add missing LIBCURL prereq
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 1ee0433..916a64b 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -339,8 +339,8 @@
           image: alpine
           distro: alpine-latest
         - jobname: linux32
-          image: daald/ubuntu32:xenial
-          distro: ubuntu32-16.04
+          image: i386/ubuntu:focal
+          distro: ubuntu32-20.04
         - jobname: pedantic
           image: fedora
           distro: fedora-latest
@@ -350,27 +350,21 @@
     runs-on: ubuntu-latest
     container: ${{matrix.vector.image}}
     steps:
-    - uses: actions/checkout@v4
-      if: matrix.vector.jobname != 'linux32'
-    - uses: actions/checkout@v1 # cannot be upgraded because Node.js Actions aren't supported in this container
+    - name: prepare libc6 for actions
       if: matrix.vector.jobname == 'linux32'
+      run: apt -q update && apt -q -y install libc6-amd64 lib64stdc++6
+    - uses: actions/checkout@v4
     - run: ci/install-dependencies.sh
     - run: ci/run-build-and-tests.sh
     - name: print test failures
       if: failure() && env.FAILED_TEST_ARTIFACTS != ''
       run: ci/print-test-failures.sh
     - name: Upload failed tests' directories
-      if: failure() && env.FAILED_TEST_ARTIFACTS != '' && matrix.vector.jobname != 'linux32'
+      if: failure() && env.FAILED_TEST_ARTIFACTS != ''
       uses: actions/upload-artifact@v4
       with:
         name: failed-tests-${{matrix.vector.jobname}}
         path: ${{env.FAILED_TEST_ARTIFACTS}}
-    - name: Upload failed tests' directories
-      if: failure() && env.FAILED_TEST_ARTIFACTS != '' && matrix.vector.jobname == 'linux32'
-      uses: actions/upload-artifact@v1 # cannot be upgraded because Node.js Actions aren't supported in this container
-      with:
-        name: failed-tests-${{matrix.vector.jobname}}
-        path: ${{env.FAILED_TEST_ARTIFACTS}}
   static-analysis:
     needs: ci-config
     if: needs.ci-config.outputs.enabled == 'yes'
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 37b991e..c4c45da 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -25,6 +25,9 @@
       fi
   parallel:
     matrix:
+      - jobname: linux-old
+        image: ubuntu:16.04
+        CC: gcc
       - jobname: linux-sha256
         image: ubuntu:latest
         CC: clang
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
index 8fb8733..5edd3a0 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -293,7 +293,9 @@
      v12.01, 2022-03-28).
 
  - Variables have to be declared at the beginning of the block, before
-   the first statement (i.e. -Wdeclaration-after-statement).
+   the first statement (i.e. -Wdeclaration-after-statement).  It is
+   encouraged to have a blank line between the end of the declarations
+   and the first statement in the block.
 
  - NULL pointers shall be written as NULL, not as 0.
 
@@ -313,6 +315,13 @@
         while( condition )
 		func (bar+1);
 
+ - A binary operator (other than ",") and ternary conditional "?:"
+   have a space on each side of the operator to separate it from its
+   operands.  E.g. "A + 1", not "A+1".
+
+ - A unary operator (other than "." and "->") have no space between it
+   and its operand.  E.g. "(char *)ptr", not "(char *) ptr".
+
  - Do not explicitly compare an integral value with constant 0 or '\0',
    or a pointer value with constant NULL.  For instance, to validate that
    counted array <ptr, cnt> is initialized but has no elements, write:
diff --git a/Documentation/RelNotes/2.46.1.txt b/Documentation/RelNotes/2.46.1.txt
index 3b4c443..e55c2c4 100644
--- a/Documentation/RelNotes/2.46.1.txt
+++ b/Documentation/RelNotes/2.46.1.txt
@@ -56,4 +56,20 @@
    behave more or less like "git log -p --remerge-diff" but instead it
    crashed, forgetting to prepare a temporary object store needed.
 
+ * The patch parser in "git patch-id" has been tightened to avoid
+   getting confused by lines that look like a patch header in the log
+   message.
+
+ * "git bundle unbundle" outside a repository triggered a BUG()
+   unnecessarily, which has been corrected.
+
+ * The code forgot to discard unnecessary in-core commit buffer data
+   for commits that "git log --skip=<number>" traversed but omitted
+   from the output, which has been corrected.
+
+ * "git verify-pack" and "git index-pack" started dying outside a
+   repository, which has been corrected.
+
+ * A corner case bug in "git stash" was fixed.
+
 Also contains minor documentation updates and code clean-ups.
diff --git a/Documentation/RelNotes/2.46.2.txt b/Documentation/RelNotes/2.46.2.txt
new file mode 100644
index 0000000..51471e5
--- /dev/null
+++ b/Documentation/RelNotes/2.46.2.txt
@@ -0,0 +1,11 @@
+Git 2.46.2 Release Notes
+========================
+
+This release is primarily to merge changes to unbreak the 32-bit
+GitHub actions jobs we use for CI testing, so that we can release
+real fixes for the 2.46.x track after they pass CI.
+
+It also reverts the "git patch-id" change that went into 2.46.1,
+as it seems to have got a regression reported (I haven't verified,
+but it is better to keep a known breakage than adding an unintended
+regression).
diff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt
index 8efc53e..36e7715 100644
--- a/Documentation/config/remote.txt
+++ b/Documentation/config/remote.txt
@@ -42,14 +42,15 @@
 	as if the `--mirror` option was given on the command line.
 
 remote.<name>.skipDefaultUpdate::
-	If true, this remote will be skipped by default when updating
-	using linkgit:git-fetch[1] or the `update` subcommand of
-	linkgit:git-remote[1].
+	A deprecated synonym to `remote.<name>.skipFetchAll` (if
+	both are set in the configuration files with different
+	values, the value of the last occurrence will be used).
 
 remote.<name>.skipFetchAll::
-	If true, this remote will be skipped by default when updating
-	using linkgit:git-fetch[1] or the `update` subcommand of
-	linkgit:git-remote[1].
+	If true, this remote will be skipped when updating
+	using linkgit:git-fetch[1], the `update` subcommand of
+	linkgit:git-remote[1], and ignored by the prefetch task
+	of `git maitenance`.
 
 remote.<name>.receivepack::
 	The default program to execute on the remote side when pushing.  See
diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
index e22b217..80838fe 100644
--- a/Documentation/fetch-options.txt
+++ b/Documentation/fetch-options.txt
@@ -1,6 +1,7 @@
 --[no-]all::
-	Fetch all remotes. This overrides the configuration variable
-	`fetch.all`.
+	Fetch all remotes, except for the ones that has the
+	`remote.<name>.skipFetchAll` configuration variable set.
+	This overrides the configuration variable fetch.all`.
 
 -a::
 --append::
diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt
index bd95a6c..d5890ae 100644
--- a/Documentation/git-cat-file.txt
+++ b/Documentation/git-cat-file.txt
@@ -270,9 +270,9 @@
 ------------
 
 If `--batch` or `--batch-check` is given, `cat-file` will read objects
-from stdin, one per line, and print information about them. By default,
-the whole line is considered as an object, as if it were fed to
-linkgit:git-rev-parse[1].
+from stdin, one per line, and print information about them in the same
+order as they have been read. By default, the whole line is
+considered as an object, as if it were fed to linkgit:git-rev-parse[1].
 
 When `--batch-command` is given, `cat-file` will read commands from stdin,
 one per line, and print information based on the command given. With
diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index 65c645d..7f81fbb 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -10,7 +10,7 @@
 --------
 [verse]
 'git config list' [<file-option>] [<display-option>] [--includes]
-'git config get' [<file-option>] [<display-option>] [--includes] [--all] [--regexp=<regexp>] [--value=<value>] [--fixed-value] [--default=<default>] <name>
+'git config get' [<file-option>] [<display-option>] [--includes] [--all] [--regexp] [--value=<value>] [--fixed-value] [--default=<default>] <name>
 'git config set' [<file-option>] [--type=<type>] [--all] [--value=<value>] [--fixed-value] <name> <value>
 'git config unset' [<file-option>] [--all] [--value=<value>] [--fixed-value] <name> <value>
 'git config rename-section' [<file-option>] <old-name> <new-name>
@@ -130,7 +130,7 @@
 --all::
 	With `get`, return all values for a multi-valued key.
 
----regexp::
+--regexp::
 	With `get`, interpret the name as a regular expression. Regular
 	expression matching is currently case-sensitive and done against a
 	canonicalized version of the key in which section and variable names
@@ -309,7 +309,7 @@
 	Replaced by `git config get [--value=<pattern>] <name>`.
 
 --get-all <name> [<value-pattern>]::
-	Replaced by `git config get [--value=<pattern>] --all --show-names <name>`.
+	Replaced by `git config get [--value=<pattern>] --all <name>`.
 
 --get-regexp <name-regexp>::
 	Replaced by `git config get --all --show-names --regexp <name-regexp>`.
diff --git a/Documentation/git-diff-tree.txt b/Documentation/git-diff-tree.txt
index 143318c..09286a8 100644
--- a/Documentation/git-diff-tree.txt
+++ b/Documentation/git-diff-tree.txt
@@ -88,7 +88,7 @@
 
 --no-commit-id::
 	'git diff-tree' outputs a line with the commit ID when
-	applicable.  This flag suppressed the commit ID output.
+	applicable.  This flag suppresses the commit ID output.
 
 -c::
 	This flag changes the way a merge commit is displayed
diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt
index 51d0f7e..9d96819 100644
--- a/Documentation/git-maintenance.txt
+++ b/Documentation/git-maintenance.txt
@@ -107,6 +107,9 @@
 would already be obtained, making the real fetch faster.  In the ideal case,
 it will just become an update to a bunch of remote-tracking branches without
 any object transfer.
++
+The `remote.<name>.skipFetchAll` configuration can be used to
+exclude a particular remote from getting prefetched.
 
 gc::
 	Clean up unnecessary files and optimize the local repository. "GC"
diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt
index 4759408..f89ad30 100644
--- a/Documentation/gittutorial.txt
+++ b/Documentation/gittutorial.txt
@@ -360,7 +360,7 @@
 This means "show everything that is reachable from either one, but
 exclude anything that is reachable from both of them".
 
-Please note that these range notation can be used with both `gitk`
+Please note that these range notations can be used with both `gitk`
 and `git log`.
 
 After inspecting what Bob did, if there is nothing urgent, Alice may
diff --git a/Documentation/howto/maintain-git.txt b/Documentation/howto/maintain-git.txt
index 41f5405..da31332 100644
--- a/Documentation/howto/maintain-git.txt
+++ b/Documentation/howto/maintain-git.txt
@@ -181,6 +181,10 @@
      $ git diff ORIG_HEAD..   ;# final review
      $ make test              ;# final review
 
+   If the tip of 'master' is updated, also generate the preformatted
+   documentation and push the out result to git-htmldocs and
+   git-manpages repositories.
+
  - Handle the remaining patches:
 
    - Anything unobvious that is applicable to 'master' (in other
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 91a06ca..efe61fd 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.46.1-pre
+DEF_VER=v2.46.2-pre
 
 LF='
 '
diff --git a/RelNotes b/RelNotes
index 89b65f2..0dfe046 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.46.1.txt
\ No newline at end of file
+Documentation/RelNotes/2.46.2.txt
\ No newline at end of file
diff --git a/apply.c b/apply.c
index 0f2f5da..6e1060a 100644
--- a/apply.c
+++ b/apply.c
@@ -995,6 +995,7 @@ static int parse_mode_line(const char *line, int linenr, unsigned int *mode)
 	*mode = strtoul(line, &end, 8);
 	if (end == line || !isspace(*end))
 		return error(_("invalid mode on line %d: %s"), linenr, line);
+	*mode = canon_mode(*mode);
 	return 0;
 }
 
diff --git a/builtin/bundle.c b/builtin/bundle.c
index d5d41a8..86d0ed7 100644
--- a/builtin/bundle.c
+++ b/builtin/bundle.c
@@ -207,12 +207,13 @@ static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix)
 			builtin_bundle_unbundle_usage, options, &bundle_file);
 	/* bundle internals use argv[1] as further parameters */
 
+	if (!startup_info->have_repository)
+		die(_("Need a repository to unbundle."));
+
 	if ((bundle_fd = open_bundle(bundle_file, &header, NULL)) < 0) {
 		ret = 1;
 		goto cleanup;
 	}
-	if (!startup_info->have_repository)
-		die(_("Need a repository to unbundle."));
 	if (progress)
 		strvec_pushl(&extra_index_pack_args, "-v", "--progress-title",
 			     _("Unbundling objects"), NULL);
diff --git a/builtin/config.c b/builtin/config.c
index 20a0b64..97e4d5f 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -17,7 +17,7 @@
 
 static const char *const builtin_config_usage[] = {
 	N_("git config list [<file-option>] [<display-option>] [--includes]"),
-	N_("git config get [<file-option>] [<display-option>] [--includes] [--all] [--regexp=<regexp>] [--value=<value>] [--fixed-value] [--default=<default>] <name>"),
+	N_("git config get [<file-option>] [<display-option>] [--includes] [--all] [--regexp] [--value=<value>] [--fixed-value] [--default=<default>] <name>"),
 	N_("git config set [<file-option>] [--type=<type>] [--all] [--value=<value>] [--fixed-value] <name> <value>"),
 	N_("git config unset [<file-option>] [--all] [--value=<value>] [--fixed-value] <name> <value>"),
 	N_("git config rename-section [<file-option>] <old-name> <new-name>"),
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index fd968d6..763b013 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -1868,6 +1868,15 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
 	if (!index_name && pack_name)
 		index_name = derive_filename(pack_name, "pack", "idx", &index_name_buf);
 
+	/*
+	 * Packfiles and indices do not carry enough information to be able to
+	 * identify their object hash. So when we are neither in a repository
+	 * nor has the user told us which object hash to use we have no other
+	 * choice but to guess the object hash.
+	 */
+	if (!the_repository->hash_algo)
+		repo_set_hash_algo(the_repository, GIT_HASH_SHA1);
+
 	opts.flags &= ~(WRITE_REV | WRITE_REV_VERIFY);
 	if (rev_index) {
 		opts.flags |= verify ? WRITE_REV_VERIFY : WRITE_REV;
diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c
index 1d96949..e6f2245 100644
--- a/builtin/interpret-trailers.c
+++ b/builtin/interpret-trailers.c
@@ -132,6 +132,7 @@ static void read_input_file(struct strbuf *sb, const char *file)
 		if (strbuf_read(sb, fileno(stdin), 0) < 0)
 			die_errno(_("could not read from stdin"));
 	}
+	strbuf_complete_line(sb);
 }
 
 static void interpret_trailers(const struct process_trailer_options *opts,
diff --git a/builtin/stash.c b/builtin/stash.c
index 46b981c..80ccfc7 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -1671,7 +1671,28 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
 			}
 		}
 
-		if (keep_index == 1 && !is_null_oid(&info.i_tree)) {
+		/*
+		 * When keeping staged entries, we need to reset the working
+		 * directory to match the state of our index. This can be
+		 * skipped when the index is the empty tree, because there is
+		 * nothing to reset in that case:
+		 *
+		 *   - When the index has any file, regardless of whether
+		 *     staged or not, the tree cannot be empty by definition
+		 *     and thus we enter the condition.
+		 *
+		 *   - When the index has no files, the only thing we need to
+		 *     care about is untracked files when `--include-untracked`
+		 *     is given. But as we already execute git-clean(1) further
+		 *     up to delete such untracked files we don't have to do
+		 *     anything here, either.
+		 *
+		 * We thus skip calling git-checkout(1) in this case, also
+		 * because running it on an empty tree will cause it to fail
+		 * due to the pathspec not matching anything.
+		 */
+		if (keep_index == 1 && !is_null_oid(&info.i_tree) &&
+		    !is_empty_tree_oid(&info.i_tree, the_repository->hash_algo)) {
 			struct child_process cp = CHILD_PROCESS_INIT;
 
 			cp.git_cmd = 1;
diff --git a/bundle.c b/bundle.c
index ce164c3..b0a8a92 100644
--- a/bundle.c
+++ b/bundle.c
@@ -89,7 +89,12 @@ int read_bundle_header_fd(int fd, struct bundle_header *header,
 		goto abort;
 	}
 
-	header->hash_algo = the_hash_algo;
+	/*
+	 * The default hash format for bundles is SHA1, unless told otherwise
+	 * by an "object-format=" capability, which is being handled in
+	 * `parse_capability()`.
+	 */
+	header->hash_algo = &hash_algos[GIT_HASH_SHA1];
 
 	/* The bundle header ends with an empty line */
 	while (!strbuf_getwholeline_fd(&buf, fd, '\n') &&
diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh
index b59fd7c..e2c6ef0 100755
--- a/ci/install-dependencies.sh
+++ b/ci/install-dependencies.sh
@@ -33,37 +33,49 @@
 	dnf -yq update >/dev/null &&
 	dnf -yq install make gcc findutils diffutils perl python3 gettext zlib-devel expat-devel openssl-devel curl-devel pcre2-devel >/dev/null
 	;;
-ubuntu-*)
+ubuntu-*|ubuntu32-*)
 	# Required so that apt doesn't wait for user input on certain packages.
 	export DEBIAN_FRONTEND=noninteractive
 
+	case "$distro" in
+	ubuntu-*)
+		SVN='libsvn-perl subversion'
+		;;
+	*)
+		SVN=
+		;;
+	esac
+
 	sudo apt-get -q update
 	sudo apt-get -q -y install \
-		language-pack-is libsvn-perl apache2 cvs cvsps git gnupg subversion \
+		language-pack-is apache2 cvs cvsps git gnupg $SVN \
 		make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \
 		tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \
 		libemail-valid-perl libio-pty-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl \
 		${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE
 
-	mkdir --parents "$CUSTOM_PATH"
-	wget --quiet --directory-prefix="$CUSTOM_PATH" \
-		"$P4WHENCE/bin.linux26x86_64/p4d" "$P4WHENCE/bin.linux26x86_64/p4"
-	chmod a+x "$CUSTOM_PATH/p4d" "$CUSTOM_PATH/p4"
+	case "$distro" in
+	ubuntu-16.04)
+		# Does not support JGit, but we also don't really care about
+		# the others. We rather care whether Git still compiles and
+		# runs fine overall.
+		;;
+	ubuntu-*)
+		mkdir --parents "$CUSTOM_PATH"
 
-	wget --quiet "$LFSWHENCE/git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz"
-	tar -xzf "git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz" \
-		-C "$CUSTOM_PATH" --strip-components=1 "git-lfs-$LINUX_GIT_LFS_VERSION/git-lfs"
-	rm "git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz"
+		wget --quiet --directory-prefix="$CUSTOM_PATH" \
+			"$P4WHENCE/bin.linux26x86_64/p4d" "$P4WHENCE/bin.linux26x86_64/p4"
+		chmod a+x "$CUSTOM_PATH/p4d" "$CUSTOM_PATH/p4"
 
-	wget --quiet "$JGITWHENCE" --output-document="$CUSTOM_PATH/jgit"
-	chmod a+x "$CUSTOM_PATH/jgit"
-	;;
-ubuntu32-*)
-	sudo linux32 --32bit i386 sh -c '
-		apt update >/dev/null &&
-		apt install -y build-essential libcurl4-openssl-dev \
-			libssl-dev libexpat-dev gettext python >/dev/null
-	'
+		wget --quiet "$LFSWHENCE/git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz"
+		tar -xzf "git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz" \
+			-C "$CUSTOM_PATH" --strip-components=1 "git-lfs-$LINUX_GIT_LFS_VERSION/git-lfs"
+		rm "git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz"
+
+		wget --quiet "$JGITWHENCE" --output-document="$CUSTOM_PATH/jgit"
+		chmod a+x "$CUSTOM_PATH/jgit"
+		;;
+	esac
 	;;
 macos-*)
 	export HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1
diff --git a/ci/lib.sh b/ci/lib.sh
index 51f8f59..74b430b 100755
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -336,7 +336,14 @@
 	fi
 	MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=/usr/bin/$PYTHON_PACKAGE"
 
-	export GIT_TEST_HTTPD=true
+	case "$distro" in
+	ubuntu-16.04)
+		# Apache is too old for HTTP/2.
+		;;
+	*)
+		export GIT_TEST_HTTPD=true
+		;;
+	esac
 
 	# The Linux build installs the defined dependency versions below.
 	# The OS X build installs much more recent versions, whichever
diff --git a/ci/run-docker-build.sh b/ci/run-docker-build.sh
deleted file mode 100755
index 6cd832e..0000000
--- a/ci/run-docker-build.sh
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/bin/sh
-#
-# Build and test Git inside container
-#
-# Usage:
-#   run-docker-build.sh <host-user-id>
-#
-
-set -ex
-
-if test $# -ne 1 || test -z "$1"
-then
-	echo >&2 "usage: run-docker-build.sh <host-user-id>"
-	exit 1
-fi
-
-case "$jobname" in
-linux32)
-	switch_cmd="linux32 --32bit i386"
-	;;
-linux-musl)
-	switch_cmd=
-	useradd () { adduser -D "$@"; }
-	;;
-*)
-	exit 1
-	;;
-esac
-
-"${0%/*}/install-docker-dependencies.sh"
-
-# If this script runs inside a docker container, then all commands are
-# usually executed as root. Consequently, the host user might not be
-# able to access the test output files.
-# If a non 0 host user id is given, then create a user "ci" with that
-# user id to make everything accessible to the host user.
-HOST_UID=$1
-if test $HOST_UID -eq 0
-then
-	# Just in case someone does want to run the test suite as root.
-	CI_USER=root
-else
-	CI_USER=ci
-	if test "$(id -u $CI_USER 2>/dev/null)" = $HOST_UID
-	then
-		echo "user '$CI_USER' already exists with the requested ID $HOST_UID"
-	else
-		useradd -u $HOST_UID $CI_USER
-	fi
-fi
-
-# Build and test
-command $switch_cmd su -m -l $CI_USER -c "
-	set -ex
-	export DEVELOPER='$DEVELOPER'
-	export DEFAULT_TEST_TARGET='$DEFAULT_TEST_TARGET'
-	export GIT_PROVE_OPTS='$GIT_PROVE_OPTS'
-	export GIT_TEST_OPTS='$GIT_TEST_OPTS'
-	export GIT_TEST_CLONE_2GB='$GIT_TEST_CLONE_2GB'
-	export MAKEFLAGS='$MAKEFLAGS'
-	export cache_dir='$cache_dir'
-	cd /usr/src/git
-	test -n '$cache_dir' && ln -s '$cache_dir/.prove' t/.prove
-	make
-	make test
-"
diff --git a/ci/run-docker.sh b/ci/run-docker.sh
deleted file mode 100755
index af89d16..0000000
--- a/ci/run-docker.sh
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/bin/sh
-#
-# Download and run Docker image to build and test Git
-#
-
-. ${0%/*}/lib.sh
-
-case "$jobname" in
-linux32)
-	CI_CONTAINER="daald/ubuntu32:xenial"
-	;;
-linux-musl)
-	CI_CONTAINER=alpine
-	;;
-*)
-	exit 1
-	;;
-esac
-
-docker pull "$CI_CONTAINER"
-
-# Use the following command to debug the docker build locally:
-# <host-user-id> must be 0 if podman is used as drop-in replacement for docker
-# $ docker run -itv "${PWD}:/usr/src/git" --entrypoint /bin/sh "$CI_CONTAINER"
-# root@container:/# export jobname=<jobname>
-# root@container:/# /usr/src/git/ci/run-docker-build.sh <host-user-id>
-
-container_cache_dir=/tmp/container-cache
-
-docker run \
-	--interactive \
-	--env DEVELOPER \
-	--env DEFAULT_TEST_TARGET \
-	--env GIT_PROVE_OPTS \
-	--env GIT_TEST_OPTS \
-	--env GIT_TEST_CLONE_2GB \
-	--env MAKEFLAGS \
-	--env jobname \
-	--env cache_dir="$container_cache_dir" \
-	--volume "${PWD}:/usr/src/git" \
-	--volume "$cache_dir:$container_cache_dir" \
-	"$CI_CONTAINER" \
-	/usr/src/git/ci/run-docker-build.sh $(id -u $USER)
-
-check_unignored_build_artifacts
-
-save_good_tree
diff --git a/compat/terminal.c b/compat/terminal.c
index 0afda73..d54efa1 100644
--- a/compat/terminal.c
+++ b/compat/terminal.c
@@ -594,7 +594,7 @@ void restore_term(void)
 {
 }
 
-char *git_terminal_prompt(const char *prompt, int echo)
+char *git_terminal_prompt(const char *prompt, int echo UNUSED)
 {
 	return getpass(prompt);
 }
diff --git a/config.mak.uname b/config.mak.uname
index 85d6382..bbf7744 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -249,6 +249,7 @@
         else
 		NO_REGEX = UnfortunatelyYes
         endif
+	HAVE_DEV_TTY = YesPlease
 	HAVE_ALLOCA_H = YesPlease
 	NEEDS_LIBICONV = YesPlease
 	NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes
diff --git a/diff.c b/diff.c
index ebb7538..a834099 100644
--- a/diff.c
+++ b/diff.c
@@ -3565,6 +3565,7 @@ static void builtin_diff(const char *name_a,
 		show_submodule_diff_summary(o, one->path ? one->path : two->path,
 				&one->oid, &two->oid,
 				two->dirty_submodule);
+		o->found_changes = 1;
 		return;
 	} else if (o->submodule_format == DIFF_SUBMODULE_INLINE_DIFF &&
 		   (!one->mode || S_ISGITLINK(one->mode)) &&
@@ -3573,6 +3574,7 @@ static void builtin_diff(const char *name_a,
 		show_submodule_inline_diff(o, one->path ? one->path : two->path,
 				&one->oid, &two->oid,
 				two->dirty_submodule);
+		o->found_changes = 1;
 		return;
 	}
 
@@ -4587,6 +4589,9 @@ static void run_diff_cmd(const struct external_diff *pgm,
 		builtin_diff(name, other ? other : name,
 			     one, two, xfrm_msg, must_show_header,
 			     o, complete_rewrite);
+		if (p->status == DIFF_STATUS_COPIED ||
+		    p->status == DIFF_STATUS_RENAMED)
+			o->found_changes = 1;
 	} else {
 		fprintf(o->file, "* Unmerged path %s\n", name);
 		o->found_changes = 1;
diff --git a/revision.c b/revision.c
index 1c0192f..5fecd7e 100644
--- a/revision.c
+++ b/revision.c
@@ -4407,6 +4407,7 @@ static struct commit *get_revision_internal(struct rev_info *revs)
 				c = get_revision_1(revs);
 				if (!c)
 					break;
+				free_commit_buffer(revs->repo->parsed_objects, c);
 			}
 		}
 
diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh
index 88c524f..48a1550 100755
--- a/t/t1001-read-tree-m-2way.sh
+++ b/t/t1001-read-tree-m-2way.sh
@@ -397,7 +397,7 @@
 
 test_expect_success 'a/b vs a, plus c/d case test.' '
 	read_tree_u_must_succeed -u -m "$treeH" "$treeM" &&
-	git ls-files --stage | tee >treeMcheck.out &&
+	git ls-files --stage >treeMcheck.out &&
 	test_cmp treeM.out treeMcheck.out
 '
 
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index a7f71f8..74666ff 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -1397,6 +1397,21 @@
 	test_path_is_missing to-remove
 '
 
+test_expect_success 'stash --keep-index --include-untracked with empty tree' '
+	test_when_finished "rm -rf empty" &&
+	git init empty &&
+	(
+		cd empty &&
+		git commit --allow-empty --message "empty" &&
+		echo content >file &&
+		git stash push --keep-index --include-untracked &&
+		test_path_is_missing file &&
+		git stash pop &&
+		echo content >expect &&
+		test_cmp expect file
+	)
+'
+
 test_expect_success 'stash apply should succeed with unmodified file' '
 	echo base >file &&
 	git add file &&
diff --git a/t/t4017-diff-retval.sh b/t/t4017-diff-retval.sh
index f439f46..d644310 100755
--- a/t/t4017-diff-retval.sh
+++ b/t/t4017-diff-retval.sh
@@ -143,4 +143,41 @@
 	grep '^usage:' err
 '
 
+for option in --exit-code --quiet
+do
+	test_expect_success "git diff $option returns 1 for copied file" "
+		git reset --hard &&
+		cp a copy &&
+		git add copy &&
+		test_expect_code 1 git diff $option --cached --find-copies-harder
+	"
+
+	test_expect_success "git diff $option returns 1 for renamed file" "
+		git reset --hard &&
+		git mv a renamed &&
+		test_expect_code 1 git diff $option --cached
+	"
+done
+
+test_expect_success 'setup dirty subrepo' '
+	git reset --hard &&
+	test_create_repo subrepo &&
+	test_commit -C subrepo subrepo-file &&
+	test_tick &&
+	git add subrepo &&
+	git commit -m subrepo &&
+	test_commit -C subrepo another-subrepo-file
+'
+
+for option in --exit-code --quiet
+do
+	for submodule_format in diff log short
+	do
+		opts="$option --submodule=$submodule_format" &&
+		test_expect_success "git diff $opts returns 1 for dirty subrepo" "
+			test_expect_code 1 git diff $opts
+		"
+	done
+done
+
 test_done
diff --git a/t/t4129-apply-samemode.sh b/t/t4129-apply-samemode.sh
index 4eb8444..87ffd2b 100755
--- a/t/t4129-apply-samemode.sh
+++ b/t/t4129-apply-samemode.sh
@@ -130,4 +130,66 @@
 	test_grep ! "has type 100644, expected 100755" err
 '
 
+test_expect_success POSIXPERM 'patch mode for new file is canonicalized' '
+	cat >patch <<-\EOF &&
+	diff --git a/non-canon b/non-canon
+	new file mode 100660
+	--- /dev/null
+	+++ b/non-canon
+	+content
+	EOF
+	test_when_finished "git reset --hard" &&
+	(
+		umask 0 &&
+		git apply --index patch 2>err
+	) &&
+	test_must_be_empty err &&
+	git ls-files -s -- non-canon >staged &&
+	test_grep "^100644" staged &&
+	ls -l non-canon >worktree &&
+	test_grep "^-rw-rw-rw" worktree
+'
+
+test_expect_success POSIXPERM 'patch mode for deleted file is canonicalized' '
+	test_when_finished "git reset --hard" &&
+	echo content >non-canon &&
+	chmod 666 non-canon &&
+	git add non-canon &&
+
+	cat >patch <<-\EOF &&
+	diff --git a/non-canon b/non-canon
+	deleted file mode 100660
+	--- a/non-canon
+	+++ /dev/null
+	@@ -1 +0,0 @@
+	-content
+	EOF
+	git apply --index patch 2>err &&
+	test_must_be_empty err &&
+	git ls-files -- non-canon >staged &&
+	test_must_be_empty staged &&
+	test_path_is_missing non-canon
+'
+
+test_expect_success POSIXPERM 'patch mode for mode change is canonicalized' '
+	test_when_finished "git reset --hard" &&
+	echo content >non-canon &&
+	git add non-canon &&
+
+	cat >patch <<-\EOF &&
+	diff --git a/non-canon b/non-canon
+	old mode 100660
+	new mode 100770
+	EOF
+	(
+		umask 0 &&
+		git apply --index patch 2>err
+	) &&
+	test_must_be_empty err &&
+	git ls-files -s -- non-canon >staged &&
+	test_grep "^100755" staged &&
+	ls -l non-canon >worktree &&
+	test_grep "^-rwxrwxrwx" worktree
+'
+
 test_done
diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh
index 4ad023c..3b9dae3 100755
--- a/t/t5300-pack-object.sh
+++ b/t/t5300-pack-object.sh
@@ -635,4 +635,43 @@
 	check_deltas stderr = 0
 '
 
+for hash in sha1 sha256
+do
+	test_expect_success "verify-pack with $hash packfile" '
+		test_when_finished "rm -rf repo" &&
+		git init --object-format=$hash repo &&
+		test_commit -C repo initial &&
+		git -C repo repack -ad &&
+		git -C repo verify-pack "$(pwd)"/repo/.git/objects/pack/*.idx &&
+		if test $hash = sha1
+		then
+			nongit git verify-pack "$(pwd)"/repo/.git/objects/pack/*.idx
+		else
+			# We have no way to identify the hash used by packfiles
+			# or indices, so we always fall back to SHA1.
+			nongit test_must_fail git verify-pack "$(pwd)"/repo/.git/objects/pack/*.idx &&
+			# But with an explicit object format we should succeed.
+			nongit git verify-pack --object-format=$hash "$(pwd)"/repo/.git/objects/pack/*.idx
+		fi
+	'
+
+	test_expect_success "index-pack outside of a $hash repository" '
+		test_when_finished "rm -rf repo" &&
+		git init --object-format=$hash repo &&
+		test_commit -C repo initial &&
+		git -C repo repack -ad &&
+		git -C repo index-pack --verify "$(pwd)"/repo/.git/objects/pack/*.pack &&
+		if test $hash = sha1
+		then
+			nongit git index-pack --verify "$(pwd)"/repo/.git/objects/pack/*.pack
+		else
+			# We have no way to identify the hash used by packfiles
+			# or indices, so we always fall back to SHA1.
+			nongit test_must_fail git index-pack --verify "$(pwd)"/repo/.git/objects/pack/*.pack 2>err &&
+			# But with an explicit object format we should succeed.
+			nongit git index-pack --object-format=$hash --verify "$(pwd)"/repo/.git/objects/pack/*.pack
+		fi
+	'
+done
+
 test_done
diff --git a/t/t5523-push-upstream.sh b/t/t5523-push-upstream.sh
index 1f859ad..4ad36a3 100755
--- a/t/t5523-push-upstream.sh
+++ b/t/t5523-push-upstream.sh
@@ -124,14 +124,14 @@
 test_expect_success TTY 'quiet push' '
 	ensure_fresh_upstream &&
 
-	test_terminal git push --quiet --no-progress upstream main 2>&1 | tee output &&
+	test_terminal git push --quiet --no-progress upstream main >output 2>&1 &&
 	test_must_be_empty output
 '
 
 test_expect_success TTY 'quiet push -u' '
 	ensure_fresh_upstream &&
 
-	test_terminal git push --quiet -u --no-progress upstream main 2>&1 | tee output &&
+	test_terminal git push --quiet -u --no-progress upstream main >output 2>&1 &&
 	test_must_be_empty output
 '
 
diff --git a/t/t6020-bundle-misc.sh b/t/t6020-bundle-misc.sh
index fe75a06..34b5cd6 100755
--- a/t/t6020-bundle-misc.sh
+++ b/t/t6020-bundle-misc.sh
@@ -652,4 +652,36 @@
 	test_cmp expect actual
 '
 
+test_expect_success 'unbundle outside of a repository' '
+	git bundle create some.bundle HEAD &&
+	echo "fatal: Need a repository to unbundle." >expect &&
+	nongit test_must_fail git bundle unbundle "$(pwd)/some.bundle" 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 'list-heads outside of a repository' '
+	git bundle create some.bundle HEAD &&
+	cat >expect <<-EOF &&
+	$(git rev-parse HEAD) HEAD
+	EOF
+	nongit git bundle list-heads "$(pwd)/some.bundle" >actual &&
+	test_cmp expect actual
+'
+
+for hash in sha1 sha256
+do
+	test_expect_success "list-heads with bundle using $hash" '
+		test_when_finished "rm -rf hash" &&
+		git init --object-format=$hash hash &&
+		test_commit -C hash initial &&
+		git -C hash bundle create hash.bundle HEAD &&
+
+		cat >expect <<-EOF &&
+		$(git -C hash rev-parse HEAD) HEAD
+		EOF
+		git bundle list-heads hash/hash.bundle >actual &&
+		test_cmp expect actual
+	'
+done
+
 test_done
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 3d3e13c..0f7d893 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -175,6 +175,46 @@
 	test_cmp expected actual
 '
 
+test_expect_success 'with a bodiless message that lacks a trailing newline after the subject' '
+	cat >expected <<-\EOF &&
+		area: change
+
+		Reviewed-by: Peff
+		Acked-by: Johan
+	EOF
+	printf "area: change" |
+	git interpret-trailers --trailer "Reviewed-by: Peff" \
+		--trailer "Acked-by: Johan" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'with a bodied message that lacks a trailing newline after the body' '
+	cat >expected <<-\EOF &&
+		area: change
+
+		details about the change.
+
+		Reviewed-by: Peff
+		Acked-by: Johan
+	EOF
+	printf "area: change\n\ndetails about the change." |
+	git interpret-trailers --trailer "Reviewed-by: Peff" \
+		--trailer "Acked-by: Johan" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'with a message that lacks a trailing newline after the trailers' '
+	cat >expected <<-\EOF &&
+		area: change
+
+		Reviewed-by: Peff
+		Acked-by: Johan
+	EOF
+	printf "area: change\n\nReviewed-by: Peff" |
+	git interpret-trailers --trailer "Acked-by: Johan" >actual &&
+	test_cmp expected actual
+'
+
 test_expect_success 'with multiline title in the message' '
 	cat >expected <<-\EOF &&
 		place of