From af15f84da731e59197af147497868f684b8c5650 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-No=C3=ABl=20Avila?= <jn.avila@free.fr>
Date: Mon, 11 Apr 2022 19:23:30 +0000
Subject: [PATCH 001/265] i18n: fix some badly formatted i18n strings
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

String in submodule--helper is not correctly formatting
placeholders. The string in git-send-email is partial.

Signed-off-by: Jean-Noël Avila <jn.avila@free.fr>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/submodule--helper.c | 2 +-
 git-send-email.perl         | 7 +++----
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 0b8b226c8ff9e6..2c87ef9364fa6d 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -3082,7 +3082,7 @@ static int module_create_branch(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 	const char *const usage[] = {
-		N_("git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start_oid> <start_name>"),
+		N_("git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start-oid> <start-name>"),
 		NULL
 	};
 
diff --git a/git-send-email.perl b/git-send-email.perl
index a98460bdb96580..5861e99a6eb2a1 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -2096,10 +2096,9 @@ sub validate_patch {
 			chdir($cwd_save) or die("chdir: $!");
 		}
 		if ($hook_error) {
-			$hook_error = sprintf(__("fatal: %s: rejected by %s hook\n" .
-						 $hook_error . "\n" .
-						 "warning: no patches were sent\n"),
-					      $fn, $hook_name);
+			$hook_error = sprintf(
+			    __("fatal: %s: rejected by %s hook\n%s\nwarning: no patches were sent\n"),
+			    $fn, $hook_name, $hook_error);
 			die $hook_error;
 		}
 	}

From 11cfe552610386954886543f5de87dcc49ad5735 Mon Sep 17 00:00:00 2001
From: Junio C Hamano <gitster@pobox.com>
Date: Mon, 11 Apr 2022 21:27:02 -0700
Subject: [PATCH 002/265] Git 2.36-rc2

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/RelNotes/2.36.0.txt | 2 ++
 GIT-VERSION-GEN                   | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/Documentation/RelNotes/2.36.0.txt b/Documentation/RelNotes/2.36.0.txt
index 8da5b2e1e7e650..d61ade21e2be92 100644
--- a/Documentation/RelNotes/2.36.0.txt
+++ b/Documentation/RelNotes/2.36.0.txt
@@ -397,6 +397,8 @@ Fixes since v2.35
    entry it moved.
    (merge b7f9130a06 vd/mv-refresh-stat later to maint).
 
+ * Fix for CVE-2022-24765 has been merged up from 2.35.2 and others.
+
  * Other code cleanup, docfix, build fix, etc.
    (merge cfc5cf428b jc/find-header later to maint).
    (merge 40e7cfdd46 jh/p4-fix-use-of-process-error-exception later to maint).
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 1c5f6451673f2c..9252e0430d4611 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.36.0-rc1
+DEF_VER=v2.36.0-rc2
 
 LF='
 '

From d994e1531060eb6404efe08063eb8064de2417be Mon Sep 17 00:00:00 2001
From: Sverre Rabbelier <srabbelier@gmail.com>
Date: Sun, 24 Jul 2011 15:54:04 +0200
Subject: [PATCH 003/265] t9350: point out that refs are not updated correctly

This happens only when the corresponding commits are not exported in
the current fast-export run. This can happen either when the relevant
commit is already marked, or when the commit is explicitly marked
as UNINTERESTING with a negative ref by another argument.

This breaks fast-export basec remote helpers.

Signed-off-by: Sverre Rabbelier <srabbelier@gmail.com>
---
 t/t9350-fast-export.sh | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh
index 7b7a18dd2c1e07..938e8cb59db538 100755
--- a/t/t9350-fast-export.sh
+++ b/t/t9350-fast-export.sh
@@ -782,4 +782,15 @@ test_expect_success 'fast-export --first-parent outputs all revisions output by
 	)
 '
 
+cat > expected << EOF
+reset refs/heads/master
+from $(git rev-parse master)
+
+EOF
+
+test_expect_failure 'refs are updated even if no commits need to be exported' '
+	git fast-export master..master > actual &&
+	test_cmp expected actual
+'
+
 test_done

From 836fe72b27706471f6694238446c8354e0fec6ed Mon Sep 17 00:00:00 2001
From: Sverre Rabbelier <srabbelier@gmail.com>
Date: Sat, 28 Aug 2010 20:49:01 -0500
Subject: [PATCH 004/265] transport-helper: add trailing --

[PT: ensure we add an additional element to the argv array]

Signed-off-by: Sverre Rabbelier <srabbelier@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 transport-helper.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/transport-helper.c b/transport-helper.c
index b4dbbabb0c2ef6..b843ba411181fd 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -478,6 +478,8 @@ static int get_exporter(struct transport *transport,
 	for (i = 0; i < revlist_args->nr; i++)
 		strvec_push(&fastexport->args, revlist_args->items[i].string);
 
+	strvec_push(&fastexport->args, "--");
+
 	fastexport->git_cmd = 1;
 	return start_command(fastexport);
 }

From 7c40254062d45986c7504f0bda34da69845a97c4 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 31 Jan 2020 12:02:47 +0100
Subject: [PATCH 005/265] mingw: demonstrate a `git add` issue with NTFS
 junctions

NTFS junctions are somewhat similar in spirit to Unix bind mounts: they
point to a different directory and are resolved by the filesystem
driver. As such, they appear to `lstat()` as if they are directories,
not as if they are symbolic links.

_Any_ user can create junctions, while symbolic links can only be
created by non-administrators in Developer Mode on Windows 10. Hence
NTFS junctions are much more common "in the wild" than NTFS symbolic
links.

It was reported in https://github.com/git-for-windows/git/issues/2481
that adding files via an absolute path that traverses an NTFS junction:
since 1e64d18 (mingw: do resolve symlinks in `getcwd()`), we resolve not
only symbolic links but also NTFS junctions when determining the
absolute path of the current directory. The same is not true for `git
add <file>`, where symbolic links are resolved in `<file>`, but not NTFS
junctions.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t3700-add.sh | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/t/t3700-add.sh b/t/t3700-add.sh
index b1f90ba3250fa3..f4506fff8d2512 100755
--- a/t/t3700-add.sh
+++ b/t/t3700-add.sh
@@ -470,4 +470,15 @@ test_expect_success CASE_INSENSITIVE_FS 'path is case-insensitive' '
 	git add "$downcased"
 '
 
+test_expect_failure MINGW 'can add files via NTFS junctions' '
+	test_when_finished "cmd //c rmdir junction && rm -rf target" &&
+	test_create_repo target &&
+	cmd //c "mklink /j junction target" &&
+	>target/via-junction &&
+	git -C junction add "$(pwd)/junction/via-junction" &&
+	echo via-junction >expect &&
+	git -C target diff --cached --name-only >actual &&
+	test_cmp expect actual
+'
+
 test_done

From ac1b6dbcd784543fa3c025edf6a64a88d859970b Mon Sep 17 00:00:00 2001
From: Sverre Rabbelier <srabbelier@gmail.com>
Date: Sun, 24 Jul 2011 00:06:00 +0200
Subject: [PATCH 006/265] remote-helper: check helper status after
 import/export

Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Sverre Rabbelier <srabbelier@gmail.com>
---
 t/t5801-remote-helpers.sh |  2 +-
 transport-helper.c        | 15 +++++++++++++++
 2 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/t/t5801-remote-helpers.sh b/t/t5801-remote-helpers.sh
index d386076dbd3f8d..b5d69b0c50e060 100755
--- a/t/t5801-remote-helpers.sh
+++ b/t/t5801-remote-helpers.sh
@@ -239,7 +239,7 @@ test_expect_success 'push update refs failure' '
 	echo "update fail" >>file &&
 	git commit -a -m "update fail" &&
 	git rev-parse --verify testgit/origin/heads/update >expect &&
-	test_expect_code 1 env GIT_REMOTE_TESTGIT_FAILURE="non-fast forward" \
+	test_must_fail env GIT_REMOTE_TESTGIT_FAILURE="non-fast forward" \
 		git push origin update &&
 	git rev-parse --verify testgit/origin/heads/update >actual &&
 	test_cmp expect actual
diff --git a/transport-helper.c b/transport-helper.c
index b843ba411181fd..76c667fd29aa96 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -484,6 +484,19 @@ static int get_exporter(struct transport *transport,
 	return start_command(fastexport);
 }
 
+static void check_helper_status(struct helper_data *data)
+{
+	int pid, status;
+
+	pid = waitpid(data->helper->pid, &status, WNOHANG);
+	if (pid < 0)
+		die("Could not retrieve status of remote helper '%s'",
+		    data->name);
+	if (pid > 0 && WIFEXITED(status))
+		die("Remote helper '%s' died with %d",
+		    data->name, WEXITSTATUS(status));
+}
+
 static int fetch_with_import(struct transport *transport,
 			     int nr_heads, struct ref **to_fetch)
 {
@@ -520,6 +533,7 @@ static int fetch_with_import(struct transport *transport,
 
 	if (finish_command(&fastimport))
 		die(_("error while running fast-import"));
+	check_helper_status(data);
 
 	/*
 	 * The fast-import stream of a remote helper that advertises
@@ -1124,6 +1138,7 @@ static int push_refs_with_export(struct transport *transport,
 
 	if (finish_command(&exporter))
 		die(_("error while running fast-export"));
+	check_helper_status(data);
 	if (push_update_refs_status(data, remote_refs, flags))
 		return 1;
 

From 6d994a0c0f8998d65cac5d177514c1a748132ffe Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 18 Apr 2017 12:09:08 +0200
Subject: [PATCH 007/265] mingw: demonstrate a problem with certain absolute
 paths

On Windows, there are several categories of absolute paths. One such
category starts with a backslash and is implicitly relative to the
drive associated with the current working directory. Example:

	c:
	git clone https://github.com/git-for-windows/git \G4W

should clone into C:\G4W.

There is currently a problem with that, in that mingw_mktemp() does not
expect the _wmktemp() function to prefix the absolute path with the
drive prefix, and as a consequence, the resulting path does not fit into
the originally-passed string buffer. The symptom is a "Result too large"
error.

Reported by Juan Carlos Arevalo Baeza.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t5580-unc-paths.sh | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/t/t5580-unc-paths.sh b/t/t5580-unc-paths.sh
index cd7604fff93a55..116204b59d919e 100755
--- a/t/t5580-unc-paths.sh
+++ b/t/t5580-unc-paths.sh
@@ -21,14 +21,11 @@ fi
 UNCPATH="$(winpwd)"
 case "$UNCPATH" in
 [A-Z]:*)
+	WITHOUTDRIVE="${UNCPATH#?:}"
 	# Use administrative share e.g. \\localhost\C$\git-sdk-64\usr\src\git
 	# (we use forward slashes here because MSYS2 and Git accept them, and
 	# they are easier on the eyes)
-	UNCPATH="//localhost/${UNCPATH%%:*}\$/${UNCPATH#?:}"
-	test -d "$UNCPATH" || {
-		skip_all='could not access administrative share; skipping'
-		test_done
-	}
+	UNCPATH="//localhost/${UNCPATH%%:*}\$$WITHOUTDRIVE"
 	;;
 *)
 	skip_all='skipping UNC path tests, cannot determine current path as UNC'
@@ -36,6 +33,18 @@ case "$UNCPATH" in
 	;;
 esac
 
+test_expect_failure 'clone into absolute path lacking a drive prefix' '
+	USINGBACKSLASHES="$(echo "$WITHOUTDRIVE"/without-drive-prefix |
+		tr / \\\\)" &&
+	git clone . "$USINGBACKSLASHES" &&
+	test -f without-drive-prefix/.git/HEAD
+'
+
+test -d "$UNCPATH" || {
+	skip_all='could not access administrative share; skipping'
+	test_done
+}
+
 test_expect_success setup '
 	test_commit initial
 '

From cfa0ed70cb0d744c3d38489b7f8e33284a3e1ccc Mon Sep 17 00:00:00 2001
From: Rohit Ashiwal <rohit.ashiwal265@gmail.com>
Date: Fri, 15 Feb 2019 19:03:57 +0530
Subject: [PATCH 008/265] archive: replace write_or_die() calls with
 write_block_or_die()

MinGit for Windows comes without `gzip` bundled inside, git-archive uses
`gzip -cn` to compress tar files but for this to work, gzip needs to be
present on the host system.

In the next commit, we will change the gzip compression so that we no
longer spawn `gzip` but let zlib perform the compression in the same
process instead.

In preparation for this, we consolidate all the block writes into a
single function.

This closes https://github.com/git-for-windows/git/issues/1970

Signed-off-by: Rohit Ashiwal <rohit.ashiwal265@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 archive-tar.c | 20 ++++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/archive-tar.c b/archive-tar.c
index 042feb66d28732..74dfc635b86425 100644
--- a/archive-tar.c
+++ b/archive-tar.c
@@ -17,6 +17,8 @@ static unsigned long offset;
 
 static int tar_umask = 002;
 
+static gzFile gzip;
+
 static int write_tar_filter_archive(const struct archiver *ar,
 				    struct archiver_args *args);
 
@@ -38,11 +40,21 @@ static int write_tar_filter_archive(const struct archiver *ar,
 #define USTAR_MAX_MTIME 077777777777ULL
 #endif
 
+/* writes out the whole block, or dies if fails */
+static void write_block_or_die(const char *block) {
+	if (gzip) {
+		if (gzwrite(gzip, block, (unsigned) BLOCKSIZE) != BLOCKSIZE)
+			die(_("gzwrite failed"));
+	} else {
+		write_or_die(1, block, BLOCKSIZE);
+	}
+}
+
 /* writes out the whole block, but only if it is full */
 static void write_if_needed(void)
 {
 	if (offset == BLOCKSIZE) {
-		write_or_die(1, block, BLOCKSIZE);
+		write_block_or_die(block);
 		offset = 0;
 	}
 }
@@ -66,7 +78,7 @@ static void do_write_blocked(const void *data, unsigned long size)
 		write_if_needed();
 	}
 	while (size >= BLOCKSIZE) {
-		write_or_die(1, buf, BLOCKSIZE);
+		write_block_or_die(buf);
 		size -= BLOCKSIZE;
 		buf += BLOCKSIZE;
 	}
@@ -101,10 +113,10 @@ static void write_trailer(void)
 {
 	int tail = BLOCKSIZE - offset;
 	memset(block + offset, 0, tail);
-	write_or_die(1, block, BLOCKSIZE);
+	write_block_or_die(block);
 	if (tail < 2 * RECORDSIZE) {
 		memset(block, 0, offset);
-		write_or_die(1, block, BLOCKSIZE);
+		write_block_or_die(block);
 	}
 }
 

From c83d352d9c4ee777c22f29742b7531a07235cfcc Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 25 Apr 2019 12:05:29 -0400
Subject: [PATCH 009/265] gitk: prevent overly long command lines

To avoid running into command line limitations, some of Git's commands
support the `--stdin` option.

Let's use exactly this option in the three rev-list/log invocations in
gitk that would otherwise possibly run the danger of trying to invoke a
too-long command line.

While it is easy to redirect either stdin or stdout in Tcl/Tk scripts,
what we need here is both. We need to capture the output, yet we also
need to pipe in the revs/files arguments via stdin (because stdin does
not have any limit, unlike the command line). To help this, we use the
neat Tcl feature where you can capture stdout and at the same time feed
a fixed string as stdin to the spawned process.

One non-obvious aspect about this change is that the `--stdin` option
allows to specify revs, the double-dash, and files, but *no* other
options such as `--not`. This is addressed by prefixing the "negative"
revs with `^` explicitly rather than relying on the `--not` option
(thanks for coming up with that idea, Max!).

This fixes https://github.com/git-for-windows/git/issues/1987

Analysis-and-initial-patch-by: Max Kirillov <max@max630.net>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 gitk-git/gitk | 24 +++++++++++++++++++-----
 1 file changed, 19 insertions(+), 5 deletions(-)

diff --git a/gitk-git/gitk b/gitk-git/gitk
index 23d9dd1fe0d024..42594da5617504 100755
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -405,14 +405,16 @@ proc start_rev_list {view} {
         if {$revs eq {}} {
             return 0
         }
-        set args [concat $vflags($view) $revs]
+        set args $vflags($view)
     } else {
+        set revs {}
         set args $vorigargs($view)
     }
 
     if {[catch {
         set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
-                        --parents --boundary $args "--" $files] r]
+                        --parents --boundary $args --stdin \
+                        "<<[join [concat $revs "--" $files] "\\n"]"] r]
     } err]} {
         error_popup "[mc "Error executing git log:"] $err"
         return 0
@@ -554,13 +556,19 @@ proc updatecommits {} {
             set revs $newrevs
             set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
         }
-        set args [concat $vflags($view) $revs --not $oldpos]
+        set args $vflags($view)
+        foreach r $oldpos {
+                lappend revs "^$r"
+        }
     } else {
+        set revs {}
         set args $vorigargs($view)
     }
     if {[catch {
         set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
-                        --parents --boundary $args "--" $vfilelimit($view)] r]
+                        --parents --boundary $args --stdin \
+                        "<<[join [concat $revs "--" \
+                                $vfilelimit($view)] "\\n"]"] r]
     } err]} {
         error_popup "[mc "Error executing git log:"] $err"
         return
@@ -10228,10 +10236,16 @@ proc getallcommits {} {
             foreach id $seeds {
                 lappend ids "^$id"
             }
+            lappend ids "--"
         }
     }
     if {$ids ne {}} {
-        set fd [open [concat $cmd $ids] r]
+        if {$ids eq "--all"} {
+            set cmd [concat $cmd "--all"]
+        } else {
+            set cmd [concat $cmd --stdin "<<[join $ids "\\n"]"]
+        }
+        set fd [open $cmd r]
         fconfigure $fd -blocking 0
         incr allcommits
         nowbusy allcommits

From 43a109486bce30277ded2716fe2fa4e6bdc8bfe2 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 7 Dec 2018 13:39:30 +0100
Subject: [PATCH 010/265] clean: do not traverse mount points

It seems to be not exactly rare on Windows to install NTFS junction
points (the equivalent of "bind mounts" on Linux/Unix) in worktrees,
e.g. to map some development tools into a subdirectory.

In such a scenario, it is pretty horrible if `git clean -dfx` traverses
into the mapped directory and starts to "clean up".

Let's just not do that. Let's make sure before we traverse into a
directory that it is not a mount point (or junction).

This addresses https://github.com/git-for-windows/git/issues/607

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/clean.c   | 14 ++++++++++++++
 cache.h           |  1 +
 compat/mingw.c    | 22 ++++++++++++++++++++++
 compat/mingw.h    |  3 +++
 git-compat-util.h |  4 ++++
 path.c            | 39 +++++++++++++++++++++++++++++++++++++++
 t/t7300-clean.sh  |  9 +++++++++
 7 files changed, 92 insertions(+)

diff --git a/builtin/clean.c b/builtin/clean.c
index 5466636e66604e..34d56426d79a74 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -34,6 +34,8 @@ static const char *msg_remove = N_("Removing %s\n");
 static const char *msg_would_remove = N_("Would remove %s\n");
 static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
 static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
+static const char *msg_skip_mount_point = N_("Skipping mount point %s\n");
+static const char *msg_would_skip_mount_point = N_("Would skip mount point %s\n");
 static const char *msg_warn_remove_failed = N_("failed to remove %s");
 static const char *msg_warn_lstat_failed = N_("could not lstat %s\n");
 static const char *msg_skip_cwd = N_("Refusing to remove current working directory\n");
@@ -175,6 +177,18 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
 		goto out;
 	}
 
+	if (is_mount_point(path)) {
+		if (!quiet) {
+			quote_path(path->buf, prefix, &quoted, 0);
+			printf(dry_run ?
+			       _(msg_would_skip_mount_point) :
+			       _(msg_skip_mount_point), quoted.buf);
+		}
+		*dir_gone = 0;
+
+		goto out;
+	}
+
 	dir = opendir(path->buf);
 	if (!dir) {
 		/* an empty dir could be removed even if it is unreadble */
diff --git a/cache.h b/cache.h
index 6226f6a8a53f65..2380d8229f509d 100644
--- a/cache.h
+++ b/cache.h
@@ -1284,6 +1284,7 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
 int normalize_path_copy(char *dst, const char *src);
 int longest_ancestor_length(const char *path, struct string_list *prefixes);
 char *strip_path_suffix(const char *path, const char *suffix);
+int is_mount_point_via_stat(struct strbuf *path);
 int daemon_avoid_alias(const char *path);
 
 /*
diff --git a/compat/mingw.c b/compat/mingw.c
index 6fe80fdf014021..cbf5eb441b43f0 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2508,6 +2508,28 @@ pid_t waitpid(pid_t pid, int *status, int options)
 	return -1;
 }
 
+int mingw_is_mount_point(struct strbuf *path)
+{
+	WIN32_FIND_DATAW findbuf = { 0 };
+	HANDLE handle;
+	wchar_t wfilename[MAX_PATH];
+	int wlen = xutftowcs_path(wfilename, path->buf);
+	if (wlen < 0)
+		die(_("could not get long path for '%s'"), path->buf);
+
+	/* remove trailing slash, if any */
+	if (wlen > 0 && wfilename[wlen - 1] == L'/')
+		wfilename[--wlen] = L'\0';
+
+	handle = FindFirstFileW(wfilename, &findbuf);
+	if (handle == INVALID_HANDLE_VALUE)
+		return 0;
+	FindClose(handle);
+
+	return (findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
+		(findbuf.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT);
+}
+
 int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
 {
 	int upos = 0, wpos = 0;
diff --git a/compat/mingw.h b/compat/mingw.h
index 494cc8de92221b..002493d5a5c165 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -446,6 +446,9 @@ static inline void convert_slashes(char *path)
 		if (*path == '\\')
 			*path = '/';
 }
+struct strbuf;
+int mingw_is_mount_point(struct strbuf *path);
+#define is_mount_point mingw_is_mount_point
 #define PATH_SEP ';'
 char *mingw_query_user_email(void);
 #define query_user_email mingw_query_user_email
diff --git a/git-compat-util.h b/git-compat-util.h
index 58fd813bd012f1..031033a8209f88 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -464,6 +464,10 @@ static inline int git_has_dir_sep(const char *path)
 #define has_dir_sep(path) git_has_dir_sep(path)
 #endif
 
+#ifndef is_mount_point
+#define is_mount_point is_mount_point_via_stat
+#endif
+
 #ifndef query_user_email
 #define query_user_email() NULL
 #endif
diff --git a/path.c b/path.c
index d73146b6cd266a..bd959e3e737f7e 100644
--- a/path.c
+++ b/path.c
@@ -1300,6 +1300,45 @@ char *strip_path_suffix(const char *path, const char *suffix)
 	return offset == -1 ? NULL : xstrndup(path, offset);
 }
 
+int is_mount_point_via_stat(struct strbuf *path)
+{
+	size_t len = path->len;
+	unsigned int current_dev;
+	struct stat st;
+
+	if (!strcmp("/", path->buf))
+		return 1;
+
+	strbuf_addstr(path, "/.");
+	if (lstat(path->buf, &st)) {
+		/*
+		 * If we cannot access the current directory, we cannot say
+		 * that it is a bind mount.
+		 */
+		strbuf_setlen(path, len);
+		return 0;
+	}
+	current_dev = st.st_dev;
+
+	/* Now look at the parent directory */
+	strbuf_addch(path, '.');
+	if (lstat(path->buf, &st)) {
+		/*
+		 * If we cannot access the parent directory, we cannot say
+		 * that it is a bind mount.
+		 */
+		strbuf_setlen(path, len);
+		return 0;
+	}
+	strbuf_setlen(path, len);
+
+	/*
+	 * If the device ID differs between current and parent directory,
+	 * then it is a bind mount.
+	 */
+	return current_dev != st.st_dev;
+}
+
 int daemon_avoid_alias(const char *p)
 {
 	int sl, ndot;
diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh
index 0399701e6276d6..6439e0f723db7c 100755
--- a/t/t7300-clean.sh
+++ b/t/t7300-clean.sh
@@ -788,4 +788,13 @@ test_expect_success 'traverse into directories that may have ignored entries' '
 	)
 '
 
+test_expect_success MINGW 'clean does not traverse mount points' '
+	mkdir target &&
+	>target/dont-clean-me &&
+	git init with-mountpoint &&
+	cmd //c "mklink /j with-mountpoint\\mountpoint target" &&
+	git -C with-mountpoint clean -dfx &&
+	test_path_is_file target/dont-clean-me
+'
+
 test_done

From 12da37a62a5a24f4e0f8d62dfec189046135b840 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 31 Jan 2020 11:44:31 +0100
Subject: [PATCH 011/265] strbuf_realpath(): use platform-dependent API if
 available

Some platforms (e.g. Windows) provide API functions to resolve paths
much quicker. Let's offer a way to short-cut `strbuf_realpath()` on
those platforms.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 abspath.c         | 3 +++
 git-compat-util.h | 4 ++++
 2 files changed, 7 insertions(+)

diff --git a/abspath.c b/abspath.c
index 39e06b58486e3e..7a5f114382f182 100644
--- a/abspath.c
+++ b/abspath.c
@@ -91,6 +91,9 @@ static char *strbuf_realpath_1(struct strbuf *resolved, const char *path,
 			goto error_out;
 	}
 
+	if (platform_strbuf_realpath(resolved, path))
+		return resolved->buf;
+
 	strbuf_addstr(&remaining, path);
 	get_root_part(resolved, &remaining);
 
diff --git a/git-compat-util.h b/git-compat-util.h
index 58fd813bd012f1..da9842167a61e3 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -468,6 +468,10 @@ static inline int git_has_dir_sep(const char *path)
 #define query_user_email() NULL
 #endif
 
+#ifndef platform_strbuf_realpath
+#define platform_strbuf_realpath(resolved, path) NULL
+#endif
+
 #ifdef __TANDEM
 #include <floss.h(floss_execl,floss_execlp,floss_execv,floss_execvp)>
 #include <floss.h(floss_getpwuid)>

From 41c0d896910223de6d9040cceb1c6ffda479191e Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Mon, 9 Apr 2012 13:04:35 -0500
Subject: [PATCH 012/265] Always auto-gc after calling a fast-import transport

After importing anything with fast-import, we should always let the
garbage collector do its job, since the objects are written to disk
inefficiently.

This brings down an initial import of http://selenic.com/hg from about
230 megabytes to about 14.

In the future, we may want to make this configurable on a per-remote
basis, or maybe teach fast-import about it in the first place.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 transport-helper.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/transport-helper.c b/transport-helper.c
index 76c667fd29aa96..5b498fac138a63 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -16,6 +16,8 @@
 #include "protocol.h"
 
 static int debug;
+/* TODO: put somewhere sensible, e.g. git_transport_options? */
+static int auto_gc = 1;
 
 struct helper_data {
 	const char *name;
@@ -567,6 +569,12 @@ static int fetch_with_import(struct transport *transport,
 		}
 	}
 	strbuf_release(&buf);
+	if (auto_gc) {
+		const char *argv_gc_auto[] = {
+			"gc", "--auto", "--quiet", NULL,
+		};
+		run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+	}
 	return 0;
 }
 

From cc5191c26d9ab39aae4a18de075ad87d12517402 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 18 Apr 2017 12:38:30 +0200
Subject: [PATCH 013/265] mingw: allow absolute paths without drive prefix

When specifying an absolute path without a drive prefix, we convert that
path internally. Let's make sure that we handle that case properly, too
;-)

This fixes the command

	git clone https://github.com/git-for-windows/git \G4W

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c       | 10 +++++++++-
 t/t5580-unc-paths.sh |  2 +-
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 6fe80fdf014021..fe898ed00d67d0 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1048,11 +1048,19 @@ unsigned int sleep (unsigned int seconds)
 char *mingw_mktemp(char *template)
 {
 	wchar_t wtemplate[MAX_PATH];
+	int offset = 0;
+
 	if (xutftowcs_path(wtemplate, template) < 0)
 		return NULL;
+
+	if (is_dir_sep(template[0]) && !is_dir_sep(template[1]) &&
+	    iswalpha(wtemplate[0]) && wtemplate[1] == L':') {
+		/* We have an absolute path missing the drive prefix */
+		offset = 2;
+	}
 	if (!_wmktemp(wtemplate))
 		return NULL;
-	if (xwcstoutf(template, wtemplate, strlen(template) + 1) < 0)
+	if (xwcstoutf(template, wtemplate + offset, strlen(template) + 1) < 0)
 		return NULL;
 	return template;
 }
diff --git a/t/t5580-unc-paths.sh b/t/t5580-unc-paths.sh
index 116204b59d919e..2c5b410048952d 100755
--- a/t/t5580-unc-paths.sh
+++ b/t/t5580-unc-paths.sh
@@ -33,7 +33,7 @@ case "$UNCPATH" in
 	;;
 esac
 
-test_expect_failure 'clone into absolute path lacking a drive prefix' '
+test_expect_success 'clone into absolute path lacking a drive prefix' '
 	USINGBACKSLASHES="$(echo "$WITHOUTDRIVE"/without-drive-prefix |
 		tr / \\\\)" &&
 	git clone . "$USINGBACKSLASHES" &&

From 1f12a1c1da9c5b90ef96e3e058838797e6710362 Mon Sep 17 00:00:00 2001
From: Rohit Ashiwal <rohit.ashiwal265@gmail.com>
Date: Tue, 19 Feb 2019 22:28:41 +0530
Subject: [PATCH 014/265] archive: avoid spawning `gzip`

As we already link to the zlib library, we can perform the compression
without even requiring gzip on the host machine.

Signed-off-by: Rohit Ashiwal <rohit.ashiwal265@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 archive-tar.c | 34 +++++++++++++++++++++++++---------
 1 file changed, 25 insertions(+), 9 deletions(-)

diff --git a/archive-tar.c b/archive-tar.c
index 74dfc635b86425..ae87fe646ecf58 100644
--- a/archive-tar.c
+++ b/archive-tar.c
@@ -455,18 +455,34 @@ static int write_tar_filter_archive(const struct archiver *ar,
 	filter.use_shell = 1;
 	filter.in = -1;
 
-	if (start_command(&filter) < 0)
-		die_errno(_("unable to start '%s' filter"), cmd.buf);
-	close(1);
-	if (dup2(filter.in, 1) < 0)
-		die_errno(_("unable to redirect descriptor"));
-	close(filter.in);
+	if (!strcmp("gzip -cn", ar->data)) {
+		char outmode[4] = "wb\0";
+
+		if (args->compression_level >= 0 && args->compression_level <= 9)
+			outmode[2] = '0' + args->compression_level;
+
+		gzip = gzdopen(fileno(stdout), outmode);
+		if (!gzip)
+			die(_("Could not gzdopen stdout"));
+	} else {
+		if (start_command(&filter) < 0)
+			die_errno(_("unable to start '%s' filter"), cmd.buf);
+		close(1);
+		if (dup2(filter.in, 1) < 0)
+			die_errno(_("unable to redirect descriptor"));
+		close(filter.in);
+	}
 
 	r = write_tar_archive(ar, args);
 
-	close(1);
-	if (finish_command(&filter) != 0)
-		die(_("'%s' filter reported error"), cmd.buf);
+	if (gzip) {
+		if (gzclose(gzip) != Z_OK)
+			die(_("gzclose failed"));
+	} else {
+		close(1);
+		if (finish_command(&filter) != 0)
+			die(_("'%s' filter reported error"), cmd.buf);
+	}
 
 	strbuf_release(&cmd);
 	return r;

From 52659dd3304937c651a0630473c81e06698358c2 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Mon, 4 Sep 2017 11:59:45 +0200
Subject: [PATCH 015/265] mingw: change core.fsyncObjectFiles = 1 by default
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

From the documentation of said setting:

	This boolean will enable fsync() when writing object files.

	This is a total waste of time and effort on a filesystem that
	orders data writes properly, but can be useful for filesystems
	that do not use journalling (traditional UNIX filesystems) or
	that only journal metadata and not file contents (OS X’s HFS+,
	or Linux ext3 with "data=writeback").

The most common file system on Windows (NTFS) does not guarantee that
order, therefore a sudden loss of power (or any other event causing an
unclean shutdown) would cause corrupt files (i.e. files filled with
NULs). Therefore we need to change the default.

Note that the documentation makes it sound as if this causes really bad
performance. In reality, writing loose objects is something that is done
only rarely, and only a handful of files at a time.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/compat/mingw.c b/compat/mingw.c
index 6fe80fdf014021..ea7496291dca3e 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2974,6 +2974,7 @@ int wmain(int argc, const wchar_t **wargv)
 #endif
 
 	maybe_redirect_std_handles();
+	fsync_object_files = 1;
 
 	/* determine size of argv and environ conversion buffer */
 	maxlen = wcslen(wargv[0]);

From 7f65d58957c3f1262d23f8fcfcd81b51dd67a29f Mon Sep 17 00:00:00 2001
From: Nico Rieck <nico.rieck@gmail.com>
Date: Mon, 19 Aug 2019 20:46:12 +0200
Subject: [PATCH 016/265] gitk: Escape file paths before piping to git log

Fixes https://github.com/git-for-windows/git/issues/2293

Signed-off-by: Nico Rieck <nico.rieck@gmail.com>
---
 gitk-git/gitk | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/gitk-git/gitk b/gitk-git/gitk
index 42594da5617504..64678c0aa75f06 100755
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -353,6 +353,16 @@ proc parseviewrevs {view revs} {
     return $ret
 }
 
+# Escapes a list of filter paths to be passed to git log via stdin. Note that
+# paths must not be quoted.
+proc escape_filter_paths {paths} {
+	set escaped [list]
+	foreach path $paths {
+		lappend escaped [string map {\\ \\\\ "\ " "\\\ "} $path]
+	}
+	return $escaped
+}
+
 # Start off a git log process and arrange to read its output
 proc start_rev_list {view} {
     global startmsecs commitidx viewcomplete curview
@@ -414,7 +424,8 @@ proc start_rev_list {view} {
     if {[catch {
         set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
                         --parents --boundary $args --stdin \
-                        "<<[join [concat $revs "--" $files] "\\n"]"] r]
+                        "<<[join [concat $revs "--" \
+                                [escape_filter_paths $files]] "\\n"]"] r]
     } err]} {
         error_popup "[mc "Error executing git log:"] $err"
         return 0
@@ -568,7 +579,8 @@ proc updatecommits {} {
         set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
                         --parents --boundary $args --stdin \
                         "<<[join [concat $revs "--" \
-                                $vfilelimit($view)] "\\n"]"] r]
+                                [escape_filter_paths \
+                                        $vfilelimit($view)]] "\\n"]"] r]
     } err]} {
         error_popup "[mc "Error executing git log:"] $err"
         return

From 3fcf02825c53ae3c177f63d651e01d938a79d550 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 11 Dec 2018 12:55:26 +0100
Subject: [PATCH 017/265] clean: remove mount points when possible

Windows' equivalent to "bind mounts", NTFS junction points, can be
unlinked without affecting the mount target. This is clearly what users
expect to happen when they call `git clean -dfx` in a worktree that
contains NTFS junction points: the junction should be removed, and the
target directory of said junction should be left alone (unless it is
inside the worktree).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/clean.c  | 13 +++++++++++++
 compat/mingw.h   |  1 +
 t/t7300-clean.sh |  1 +
 3 files changed, 15 insertions(+)

diff --git a/builtin/clean.c b/builtin/clean.c
index 34d56426d79a74..cb55e8956b0747 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -34,8 +34,10 @@ static const char *msg_remove = N_("Removing %s\n");
 static const char *msg_would_remove = N_("Would remove %s\n");
 static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
 static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
+#ifndef CAN_UNLINK_MOUNT_POINTS
 static const char *msg_skip_mount_point = N_("Skipping mount point %s\n");
 static const char *msg_would_skip_mount_point = N_("Would skip mount point %s\n");
+#endif
 static const char *msg_warn_remove_failed = N_("failed to remove %s");
 static const char *msg_warn_lstat_failed = N_("could not lstat %s\n");
 static const char *msg_skip_cwd = N_("Refusing to remove current working directory\n");
@@ -178,6 +180,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
 	}
 
 	if (is_mount_point(path)) {
+#ifndef CAN_UNLINK_MOUNT_POINTS
 		if (!quiet) {
 			quote_path(path->buf, prefix, &quoted, 0);
 			printf(dry_run ?
@@ -185,6 +188,16 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
 			       _(msg_skip_mount_point), quoted.buf);
 		}
 		*dir_gone = 0;
+#else
+		if (!dry_run && unlink(path->buf)) {
+			int saved_errno = errno;
+			quote_path(path->buf, prefix, &quoted, 0);
+			errno = saved_errno;
+			warning_errno(_(msg_warn_remove_failed), quoted.buf);
+			*dir_gone = 0;
+			ret = -1;
+		}
+#endif
 
 		goto out;
 	}
diff --git a/compat/mingw.h b/compat/mingw.h
index 002493d5a5c165..f7f8383e8d6c72 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -449,6 +449,7 @@ static inline void convert_slashes(char *path)
 struct strbuf;
 int mingw_is_mount_point(struct strbuf *path);
 #define is_mount_point mingw_is_mount_point
+#define CAN_UNLINK_MOUNT_POINTS 1
 #define PATH_SEP ';'
 char *mingw_query_user_email(void);
 #define query_user_email mingw_query_user_email
diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh
index 6439e0f723db7c..336f41c7426c87 100755
--- a/t/t7300-clean.sh
+++ b/t/t7300-clean.sh
@@ -794,6 +794,7 @@ test_expect_success MINGW 'clean does not traverse mount points' '
 	git init with-mountpoint &&
 	cmd //c "mklink /j with-mountpoint\\mountpoint target" &&
 	git -C with-mountpoint clean -dfx &&
+	test_path_is_missing with-mountpoint/mountpoint &&
 	test_path_is_file target/dont-clean-me
 '
 

From d8578961e3669307e59e772f1c061cb1ff3a5cf4 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Mon, 16 Feb 2015 14:06:59 +0100
Subject: [PATCH 018/265] mingw: include the Python parts in the build

While Git for Windows does not _ship_ Python (in order to save on
bandwidth), MSYS2 provides very fine Python interpreters that users can
easily take advantage of, by using Git for Windows within its SDK.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 config.mak.uname | 1 +
 1 file changed, 1 insertion(+)

diff --git a/config.mak.uname b/config.mak.uname
index 259d1511cafb76..02c8fad2d04bb5 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -717,6 +717,7 @@ else
 		USE_LIBPCRE = YesPlease
 		NO_CURL =
 		USE_NED_ALLOCATOR = YesPlease
+		NO_PYTHON =
 		ifeq (/mingw64,$(subst 32,64,$(prefix)))
 			# Move system config into top-level /etc/
 			ETC_GITCONFIG = ../etc/gitconfig

From 170f5136e709c773d76eb7e11f541bd833cfeef0 Mon Sep 17 00:00:00 2001
From: Thomas Braun <thomas.braun@byte-physics.de>
Date: Thu, 8 May 2014 21:43:24 +0200
Subject: [PATCH 019/265] Config option to disable side-band-64k for transport

Since commit 0c499ea60f the send-pack builtin uses the side-band-64k
capability if advertised by the server.

Unfortunately this breaks pushing over the dump git protocol if used
over a network connection.

The detailed reasons for this breakage are (by courtesy of Jeff Preshing,
quoted from ttps://groups.google.com/d/msg/msysgit/at8D7J-h7mw/eaLujILGUWoJ):
----------------------------------------------------------------------------
MinGW wraps Windows sockets in CRT file descriptors in order to mimic the
functionality of POSIX sockets. This causes msvcrt.dll to treat sockets as
Installable File System (IFS) handles, calling ReadFile, WriteFile,
DuplicateHandle and CloseHandle on them. This approach works well in simple
cases on recent versions of Windows, but does not support all usage patterns.
In particular, using this approach, any attempt to read & write concurrently
on the same socket (from one or more processes) will deadlock in a scenario
where the read waits for a response from the server which is only invoked after
the write. This is what send_pack currently attempts to do in the use_sideband
codepath.
----------------------------------------------------------------------------

The new config option "sendpack.sideband" allows to override the side-band-64k
capability of the server, and thus makes the dump git protocol work.

Other transportation methods like ssh and http/https still benefit from
the sideband channel, therefore the default value of "sendpack.sideband"
is still true.

[jes: split out the documentation into Documentation/config/]

Signed-off-by: Thomas Braun <thomas.braun@byte-physics.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Oliver Schneider <oliver@assarbad.net>
---
 Documentation/config.txt          |  2 ++
 Documentation/config/sendpack.txt |  5 +++++
 send-pack.c                       | 14 +++++++++++++-
 3 files changed, 20 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/config/sendpack.txt

diff --git a/Documentation/config.txt b/Documentation/config.txt
index e284b042f227f1..c662bf86e0cb73 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -499,6 +499,8 @@ include::config/safe.txt[]
 
 include::config/sendemail.txt[]
 
+include::config/sendpack.txt[]
+
 include::config/sequencer.txt[]
 
 include::config/showbranch.txt[]
diff --git a/Documentation/config/sendpack.txt b/Documentation/config/sendpack.txt
new file mode 100644
index 00000000000000..e306f657fba7dd
--- /dev/null
+++ b/Documentation/config/sendpack.txt
@@ -0,0 +1,5 @@
+sendpack.sideband::
+	Allows to disable the side-band-64k capability for send-pack even
+	when it is advertised by the server. Makes it possible to work
+	around a limitation in the git for windows implementation together
+	with the dump git protocol. Defaults to true.
diff --git a/send-pack.c b/send-pack.c
index bc0fcdbb000769..a5821515b30a69 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -39,6 +39,16 @@ int option_parse_push_signed(const struct option *opt,
 	die("bad %s argument: %s", opt->long_name, arg);
 }
 
+static int config_use_sideband = 1;
+
+static int send_pack_config(const char *var, const char *value, void *unused)
+{
+	if (!strcmp("sendpack.sideband", var))
+		config_use_sideband = git_config_bool(var, value);
+
+	return 0;
+}
+
 static void feed_object(const struct object_id *oid, FILE *fh, int negative)
 {
 	if (negative &&
@@ -494,6 +504,8 @@ int send_pack(struct send_pack_args *args,
 		return 0;
 	}
 
+	git_config(send_pack_config, NULL);
+
 	git_config_get_bool("push.negotiate", &push_negotiate);
 	if (push_negotiate)
 		get_commons_through_negotiation(args->url, remote_refs, &commons);
@@ -509,7 +521,7 @@ int send_pack(struct send_pack_args *args,
 		allow_deleting_refs = 1;
 	if (server_supports("ofs-delta"))
 		args->use_ofs_delta = 1;
-	if (server_supports("side-band-64k"))
+	if (config_use_sideband && server_supports("side-band-64k"))
 		use_sideband = 1;
 	if (server_supports("quiet"))
 		quiet_supported = 1;

From c7ed0b0be90182f2f3d763e2fe55f6b0004ea76b Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 14 Nov 2019 20:09:23 +0100
Subject: [PATCH 020/265] mingw: make sure `errno` is set correctly when socket
 operations fail

The winsock2 library provides functions that work on different data
types than file descriptors, therefore we wrap them.

But that is not the only difference: they also do not set `errno` but
expect the callers to enquire about errors via `WSAGetLastError()`.

Let's translate that into appropriate `errno` values whenever the socket
operations fail so that Git's code base does not have to change its
expectations.

This closes https://github.com/git-for-windows/git/issues/2404

Helped-by: Jeff Hostetler <jeffhost@microsoft.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 157 +++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 147 insertions(+), 10 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 6fe80fdf014021..ab81ccf3c9ee7b 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2020,18 +2020,150 @@ static void ensure_socket_initialization(void)
 	initialized = 1;
 }
 
+static int winsock_error_to_errno(DWORD err)
+{
+	switch (err) {
+	case WSAEINTR: return EINTR;
+	case WSAEBADF: return EBADF;
+	case WSAEACCES: return EACCES;
+	case WSAEFAULT: return EFAULT;
+	case WSAEINVAL: return EINVAL;
+	case WSAEMFILE: return EMFILE;
+	case WSAEWOULDBLOCK: return EWOULDBLOCK;
+	case WSAEINPROGRESS: return EINPROGRESS;
+	case WSAEALREADY: return EALREADY;
+	case WSAENOTSOCK: return ENOTSOCK;
+	case WSAEDESTADDRREQ: return EDESTADDRREQ;
+	case WSAEMSGSIZE: return EMSGSIZE;
+	case WSAEPROTOTYPE: return EPROTOTYPE;
+	case WSAENOPROTOOPT: return ENOPROTOOPT;
+	case WSAEPROTONOSUPPORT: return EPROTONOSUPPORT;
+	case WSAEOPNOTSUPP: return EOPNOTSUPP;
+	case WSAEAFNOSUPPORT: return EAFNOSUPPORT;
+	case WSAEADDRINUSE: return EADDRINUSE;
+	case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL;
+	case WSAENETDOWN: return ENETDOWN;
+	case WSAENETUNREACH: return ENETUNREACH;
+	case WSAENETRESET: return ENETRESET;
+	case WSAECONNABORTED: return ECONNABORTED;
+	case WSAECONNRESET: return ECONNRESET;
+	case WSAENOBUFS: return ENOBUFS;
+	case WSAEISCONN: return EISCONN;
+	case WSAENOTCONN: return ENOTCONN;
+	case WSAETIMEDOUT: return ETIMEDOUT;
+	case WSAECONNREFUSED: return ECONNREFUSED;
+	case WSAELOOP: return ELOOP;
+	case WSAENAMETOOLONG: return ENAMETOOLONG;
+	case WSAEHOSTUNREACH: return EHOSTUNREACH;
+	case WSAENOTEMPTY: return ENOTEMPTY;
+	/* No errno equivalent; default to EIO */
+	case WSAESOCKTNOSUPPORT:
+	case WSAEPFNOSUPPORT:
+	case WSAESHUTDOWN:
+	case WSAETOOMANYREFS:
+	case WSAEHOSTDOWN:
+	case WSAEPROCLIM:
+	case WSAEUSERS:
+	case WSAEDQUOT:
+	case WSAESTALE:
+	case WSAEREMOTE:
+	case WSASYSNOTREADY:
+	case WSAVERNOTSUPPORTED:
+	case WSANOTINITIALISED:
+	case WSAEDISCON:
+	case WSAENOMORE:
+	case WSAECANCELLED:
+	case WSAEINVALIDPROCTABLE:
+	case WSAEINVALIDPROVIDER:
+	case WSAEPROVIDERFAILEDINIT:
+	case WSASYSCALLFAILURE:
+	case WSASERVICE_NOT_FOUND:
+	case WSATYPE_NOT_FOUND:
+	case WSA_E_NO_MORE:
+	case WSA_E_CANCELLED:
+	case WSAEREFUSED:
+	case WSAHOST_NOT_FOUND:
+	case WSATRY_AGAIN:
+	case WSANO_RECOVERY:
+	case WSANO_DATA:
+	case WSA_QOS_RECEIVERS:
+	case WSA_QOS_SENDERS:
+	case WSA_QOS_NO_SENDERS:
+	case WSA_QOS_NO_RECEIVERS:
+	case WSA_QOS_REQUEST_CONFIRMED:
+	case WSA_QOS_ADMISSION_FAILURE:
+	case WSA_QOS_POLICY_FAILURE:
+	case WSA_QOS_BAD_STYLE:
+	case WSA_QOS_BAD_OBJECT:
+	case WSA_QOS_TRAFFIC_CTRL_ERROR:
+	case WSA_QOS_GENERIC_ERROR:
+	case WSA_QOS_ESERVICETYPE:
+	case WSA_QOS_EFLOWSPEC:
+	case WSA_QOS_EPROVSPECBUF:
+	case WSA_QOS_EFILTERSTYLE:
+	case WSA_QOS_EFILTERTYPE:
+	case WSA_QOS_EFILTERCOUNT:
+	case WSA_QOS_EOBJLENGTH:
+	case WSA_QOS_EFLOWCOUNT:
+#ifndef _MSC_VER
+	case WSA_QOS_EUNKNOWNPSOBJ:
+#endif
+	case WSA_QOS_EPOLICYOBJ:
+	case WSA_QOS_EFLOWDESC:
+	case WSA_QOS_EPSFLOWSPEC:
+	case WSA_QOS_EPSFILTERSPEC:
+	case WSA_QOS_ESDMODEOBJ:
+	case WSA_QOS_ESHAPERATEOBJ:
+	case WSA_QOS_RESERVED_PETYPE:
+	default: return EIO;
+	}
+}
+
+/*
+ * On Windows, `errno` is a global macro to a function call.
+ * This makes it difficult to debug and single-step our mappings.
+ */
+static inline void set_wsa_errno(void)
+{
+	DWORD wsa = WSAGetLastError();
+	int e = winsock_error_to_errno(wsa);
+	errno = e;
+
+#ifdef DEBUG_WSA_ERRNO
+	fprintf(stderr, "winsock error: %d -> %d\n", wsa, e);
+	fflush(stderr);
+#endif
+}
+
+static inline int winsock_return(int ret)
+{
+	if (ret < 0)
+		set_wsa_errno();
+
+	return ret;
+}
+
+#define WINSOCK_RETURN(x) do { return winsock_return(x); } while (0)
+
 #undef gethostname
 int mingw_gethostname(char *name, int namelen)
 {
-    ensure_socket_initialization();
-    return gethostname(name, namelen);
+	ensure_socket_initialization();
+	WINSOCK_RETURN(gethostname(name, namelen));
 }
 
 #undef gethostbyname
 struct hostent *mingw_gethostbyname(const char *host)
 {
+	struct hostent *ret;
+
 	ensure_socket_initialization();
-	return gethostbyname(host);
+
+	ret = gethostbyname(host);
+	if (!ret)
+		set_wsa_errno();
+
+	return ret;
 }
 
 #undef getaddrinfo
@@ -2039,7 +2171,7 @@ int mingw_getaddrinfo(const char *node, const char *service,
 		      const struct addrinfo *hints, struct addrinfo **res)
 {
 	ensure_socket_initialization();
-	return getaddrinfo(node, service, hints, res);
+	WINSOCK_RETURN(getaddrinfo(node, service, hints, res));
 }
 
 int mingw_socket(int domain, int type, int protocol)
@@ -2059,7 +2191,7 @@ int mingw_socket(int domain, int type, int protocol)
 		 * in errno so that _if_ someone looks up the code somewhere,
 		 * then it is at least the number that are usually listed.
 		 */
-		errno = WSAGetLastError();
+		set_wsa_errno();
 		return -1;
 	}
 	/* convert into a file descriptor */
@@ -2075,35 +2207,35 @@ int mingw_socket(int domain, int type, int protocol)
 int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz)
 {
 	SOCKET s = (SOCKET)_get_osfhandle(sockfd);
-	return connect(s, sa, sz);
+	WINSOCK_RETURN(connect(s, sa, sz));
 }
 
 #undef bind
 int mingw_bind(int sockfd, struct sockaddr *sa, size_t sz)
 {
 	SOCKET s = (SOCKET)_get_osfhandle(sockfd);
-	return bind(s, sa, sz);
+	WINSOCK_RETURN(bind(s, sa, sz));
 }
 
 #undef setsockopt
 int mingw_setsockopt(int sockfd, int lvl, int optname, void *optval, int optlen)
 {
 	SOCKET s = (SOCKET)_get_osfhandle(sockfd);
-	return setsockopt(s, lvl, optname, (const char*)optval, optlen);
+	WINSOCK_RETURN(setsockopt(s, lvl, optname, (const char*)optval, optlen));
 }
 
 #undef shutdown
 int mingw_shutdown(int sockfd, int how)
 {
 	SOCKET s = (SOCKET)_get_osfhandle(sockfd);
-	return shutdown(s, how);
+	WINSOCK_RETURN(shutdown(s, how));
 }
 
 #undef listen
 int mingw_listen(int sockfd, int backlog)
 {
 	SOCKET s = (SOCKET)_get_osfhandle(sockfd);
-	return listen(s, backlog);
+	WINSOCK_RETURN(listen(s, backlog));
 }
 
 #undef accept
@@ -2114,6 +2246,11 @@ int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz)
 	SOCKET s1 = (SOCKET)_get_osfhandle(sockfd1);
 	SOCKET s2 = accept(s1, sa, sz);
 
+	if (s2 == INVALID_SOCKET) {
+		set_wsa_errno();
+		return -1;
+	}
+
 	/* convert into a file descriptor */
 	if ((sockfd2 = _open_osfhandle(s2, O_RDWR|O_BINARY)) < 0) {
 		int err = errno;

From 6546137ae516d529a6bf9b7bc03a5d1b39ba9808 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 1 Jan 2020 21:07:22 +0100
Subject: [PATCH 021/265] mingw: do resolve symlinks in `getcwd()`

As pointed out in https://github.com/git-for-windows/git/issues/1676,
the `git rev-parse --is-inside-work-tree` command currently fails when
the current directory's path contains symbolic links.

The underlying reason for this bug is that `getcwd()` is supposed to
resolve symbolic links, but our `mingw_getcwd()` implementation did not.

We do have all the building blocks for that, though: the
`GetFinalPathByHandleW()` function will resolve symbolic links. However,
we only called that function if `GetLongPathNameW()` failed, for
historical reasons: the latter function was supported for a long time,
but the former API function was introduced only with Windows Vista, and
we used to support also Windows XP. With that support having been
dropped, we are free to call the symbolic link-resolving function right
away.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 18 +++++++-----------
 1 file changed, 7 insertions(+), 11 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 6fe80fdf014021..4eb88b13fdfcd5 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1121,18 +1121,16 @@ char *mingw_getcwd(char *pointer, int len)
 {
 	wchar_t cwd[MAX_PATH], wpointer[MAX_PATH];
 	DWORD ret = GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
+	HANDLE hnd;
 
 	if (!ret || ret >= ARRAY_SIZE(cwd)) {
 		errno = ret ? ENAMETOOLONG : err_win_to_posix(GetLastError());
 		return NULL;
 	}
-	ret = GetLongPathNameW(cwd, wpointer, ARRAY_SIZE(wpointer));
-	if (!ret && GetLastError() == ERROR_ACCESS_DENIED) {
-		HANDLE hnd = CreateFileW(cwd, 0,
-			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
-			OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
-		if (hnd == INVALID_HANDLE_VALUE)
-			return NULL;
+	hnd = CreateFileW(cwd, 0,
+			  FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+			  OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+	if (hnd != INVALID_HANDLE_VALUE) {
 		ret = GetFinalPathNameByHandleW(hnd, wpointer, ARRAY_SIZE(wpointer), 0);
 		CloseHandle(hnd);
 		if (!ret || ret >= ARRAY_SIZE(wpointer))
@@ -1141,13 +1139,11 @@ char *mingw_getcwd(char *pointer, int len)
 			return NULL;
 		return pointer;
 	}
-	if (!ret || ret >= ARRAY_SIZE(wpointer))
-		return NULL;
-	if (GetFileAttributesW(wpointer) == INVALID_FILE_ATTRIBUTES) {
+	if (GetFileAttributesW(cwd) == INVALID_FILE_ATTRIBUTES) {
 		errno = ENOENT;
 		return NULL;
 	}
-	if (xwcstoutf(pointer, wpointer, len) < 0)
+	if (xwcstoutf(pointer, cwd, len) < 0)
 		return NULL;
 	convert_slashes(pointer);
 	return pointer;

From fb652c5287706cbe40a5c5c6709c0b625c7427db Mon Sep 17 00:00:00 2001
From: Bjoern Mueller <bjoernm@gmx.de>
Date: Wed, 22 Jan 2020 13:49:13 +0100
Subject: [PATCH 022/265] mingw: fix fatal error working on mapped network
 drives on Windows

In 1e64d18 (mingw: do resolve symlinks in `getcwd()`) a problem was
introduced that causes git for Windows to stop working with certain
mapped network drives (in particular, drives that are mapped to
locations with long path names). Error message was "fatal: Unable to
read current working directory: No such file or directory". Present
change fixes this issue as discussed in
https://github.com/git-for-windows/git/issues/2480

Signed-off-by: Bjoern Mueller <bjoernm@gmx.de>
---
 compat/mingw.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 6fe80fdf014021..25d157cb579611 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1135,8 +1135,13 @@ char *mingw_getcwd(char *pointer, int len)
 			return NULL;
 		ret = GetFinalPathNameByHandleW(hnd, wpointer, ARRAY_SIZE(wpointer), 0);
 		CloseHandle(hnd);
-		if (!ret || ret >= ARRAY_SIZE(wpointer))
-			return NULL;
+		if (!ret || ret >= ARRAY_SIZE(wpointer)) {
+			ret = GetLongPathNameW(cwd, wpointer, ARRAY_SIZE(wpointer));
+			if (!ret || ret >= ARRAY_SIZE(wpointer)) {
+				errno = ret ? ENAMETOOLONG : err_win_to_posix(GetLastError());
+				return NULL;
+			}
+		}
 		if (xwcstoutf(pointer, normalize_ntpath(wpointer), len) < 0)
 			return NULL;
 		return pointer;

From a86423b4aef0a4d4dfa59869ef16dde6694d392b Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Thu, 30 Jan 2020 14:22:27 -0500
Subject: [PATCH 023/265] clink.pl: fix MSVC compile script to handle
 libcurl-d.lib

Update clink.pl to link with either libcurl.lib or libcurl-d.lib
depending on whether DEBUG=1 is set.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/vcbuild/scripts/clink.pl | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl
index 3bd824154be381..c4c99d1a11f18c 100755
--- a/compat/vcbuild/scripts/clink.pl
+++ b/compat/vcbuild/scripts/clink.pl
@@ -56,7 +56,8 @@
 		# need to use that instead?
 		foreach my $flag (@lflags) {
 			if ($flag =~ /^-LIBPATH:(.*)/) {
-				foreach my $l ("libcurl_imp.lib", "libcurl.lib") {
+				my $libcurl = $is_debug ? "libcurl-d.lib" : "libcurl.lib";
+				foreach my $l ("libcurl_imp.lib", $libcurl) {
 					if (-f "$1/$l") {
 						$lib = $l;
 						last;

From 550853f1dc7704900b008f344e752d2a5da7c325 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Tue, 30 Mar 2021 14:25:31 -0400
Subject: [PATCH 024/265] clink.pl: fix libexpatd.lib link error when using
 MSVC

When building with `make MSVC=1 DEBUG=1`, link to `libexpatd.lib`
rather than `libexpat.lib`.

It appears that the `vcpkg` package for "libexpat" has changed and now
creates `libexpatd.lib` for debug mode builds.  Previously, both debug
and release builds created a ".lib" with the same basename.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/vcbuild/scripts/clink.pl | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl
index 3bd824154be381..2768ae15f1879f 100755
--- a/compat/vcbuild/scripts/clink.pl
+++ b/compat/vcbuild/scripts/clink.pl
@@ -66,7 +66,11 @@
 		}
 		push(@args, $lib);
 	} elsif ("$arg" eq "-lexpat") {
+	    if ($is_debug) {
+		push(@args, "libexpatd.lib");
+	    } else {
 		push(@args, "libexpat.lib");
+	    }
 	} elsif ("$arg" =~ /^-L/ && "$arg" ne "-LTCG") {
 		$arg =~ s/^-L/-LIBPATH:/;
 		push(@lflags, $arg);

From 12156d69e1700c309cab3f051dc0123b81228cd4 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Mon, 5 Apr 2021 15:27:38 -0400
Subject: [PATCH 025/265] Makefile: clean up .ilk files when MSVC=1

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 Makefile | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/Makefile b/Makefile
index f8bccfab5e9c46..36642b36de07aa 100644
--- a/Makefile
+++ b/Makefile
@@ -3322,12 +3322,15 @@ endif
 ifdef MSVC
 	$(RM) $(patsubst %.o,%.o.pdb,$(OBJECTS))
 	$(RM) $(patsubst %.exe,%.pdb,$(OTHER_PROGRAMS))
+	$(RM) $(patsubst %.exe,%.ilk,$(OTHER_PROGRAMS))
 	$(RM) $(patsubst %.exe,%.iobj,$(OTHER_PROGRAMS))
 	$(RM) $(patsubst %.exe,%.ipdb,$(OTHER_PROGRAMS))
 	$(RM) $(patsubst %.exe,%.pdb,$(PROGRAMS))
+	$(RM) $(patsubst %.exe,%.ilk,$(PROGRAMS))
 	$(RM) $(patsubst %.exe,%.iobj,$(PROGRAMS))
 	$(RM) $(patsubst %.exe,%.ipdb,$(PROGRAMS))
 	$(RM) $(patsubst %.exe,%.pdb,$(TEST_PROGRAMS))
+	$(RM) $(patsubst %.exe,%.ilk,$(TEST_PROGRAMS))
 	$(RM) $(patsubst %.exe,%.iobj,$(TEST_PROGRAMS))
 	$(RM) $(patsubst %.exe,%.ipdb,$(TEST_PROGRAMS))
 	$(RM) compat/vcbuild/MSVC-DEFS-GEN

From 925b1bed6fb1472f1ad3e56a1317fdbc2b842436 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Mon, 5 Apr 2021 14:08:22 -0400
Subject: [PATCH 026/265] vcbuild: add support for compiling Windows resource
 files

Create a wrapper for the Windows Resource Compiler (RC.EXE)
for use by the MSVC=1 builds. This is similar to the CL.EXE
and LIB.EXE wrappers used for the MSVC=1 builds.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/vcbuild/find_vs_env.bat |  7 ++++++
 compat/vcbuild/scripts/rc.pl   | 46 ++++++++++++++++++++++++++++++++++
 config.mak.uname               |  3 ++-
 3 files changed, 55 insertions(+), 1 deletion(-)
 create mode 100644 compat/vcbuild/scripts/rc.pl

diff --git a/compat/vcbuild/find_vs_env.bat b/compat/vcbuild/find_vs_env.bat
index b35d264c0e6bed..379b16296e09c2 100644
--- a/compat/vcbuild/find_vs_env.bat
+++ b/compat/vcbuild/find_vs_env.bat
@@ -99,6 +99,7 @@ REM ================================================================
 
    SET sdk_dir=%WindowsSdkDir%
    SET sdk_ver=%WindowsSDKVersion%
+   SET sdk_ver_bin_dir=%WindowsSdkVerBinPath%%tgt%
    SET si=%sdk_dir%Include\%sdk_ver%
    SET sdk_includes=-I"%si%ucrt" -I"%si%um" -I"%si%shared"
    SET sl=%sdk_dir%lib\%sdk_ver%
@@ -130,6 +131,7 @@ REM ================================================================
 
    SET sdk_dir=%WindowsSdkDir%
    SET sdk_ver=%WindowsSDKVersion%
+   SET sdk_ver_bin_dir=%WindowsSdkVerBinPath%bin\amd64
    SET si=%sdk_dir%Include\%sdk_ver%
    SET sdk_includes=-I"%si%ucrt" -I"%si%um" -I"%si%shared" -I"%si%winrt"
    SET sl=%sdk_dir%lib\%sdk_ver%
@@ -160,6 +162,11 @@ REM ================================================================
    echo msvc_includes=%msvc_includes%
    echo msvc_libs=%msvc_libs%
 
+   echo sdk_ver_bin_dir=%sdk_ver_bin_dir%
+   SET X1=%sdk_ver_bin_dir:C:=/C%
+   SET X2=%X1:\=/%
+   echo sdk_ver_bin_dir_msys=%X2%
+
    echo sdk_includes=%sdk_includes%
    echo sdk_libs=%sdk_libs%
 
diff --git a/compat/vcbuild/scripts/rc.pl b/compat/vcbuild/scripts/rc.pl
new file mode 100644
index 00000000000000..7bca4cd81c6c63
--- /dev/null
+++ b/compat/vcbuild/scripts/rc.pl
@@ -0,0 +1,46 @@
+#!/usr/bin/perl -w
+######################################################################
+# Compile Resources on Windows
+#
+# This is a wrapper to facilitate the compilation of Git with MSVC
+# using GNU Make as the build system. So, instead of manipulating the
+# Makefile into something nasty, just to support non-space arguments
+# etc, we use this wrapper to fix the command line options
+#
+######################################################################
+use strict;
+my @args = ();
+my @input = ();
+
+while (@ARGV) {
+	my $arg = shift @ARGV;
+	if ("$arg" =~ /^-[dD]/) {
+		# GIT_VERSION gets passed with too many
+		# layers of dquote escaping.
+		$arg =~ s/\\"/"/g;
+
+		push(@args, $arg);
+
+	} elsif ("$arg" eq "-i") {
+		my $arg = shift @ARGV;
+		# TODO complain if NULL or is dashed ??
+		push(@input, $arg);
+
+	} elsif ("$arg" eq "-o") {
+		my $arg = shift @ARGV;
+		# TODO complain if NULL or is dashed ??
+		push(@args, "-fo$arg");
+
+	} else {
+		push(@args, $arg);
+	}
+}
+
+push(@args, "-nologo");
+push(@args, "-v");
+push(@args, @input);
+
+unshift(@args, "rc.exe");
+printf("**** @args\n");
+
+exit (system(@args) != 0);
diff --git a/config.mak.uname b/config.mak.uname
index 27229b2e0729ab..79607ac6619c5f 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -424,7 +424,7 @@ ifeq ($(uname_S),Windows)
 	# link.exe next to, and required by, cl.exe, we have to prepend this
 	# onto the existing $PATH.
 	#
-	SANE_TOOL_PATH ?= $(msvc_bin_dir_msys)
+	SANE_TOOL_PATH ?= $(msvc_bin_dir_msys):$(sdk_ver_bin_dir_msys)
 	HAVE_ALLOCA_H = YesPlease
 	NO_PREAD = YesPlease
 	NEEDS_CRYPTO_WITH_SSL = YesPlease
@@ -491,6 +491,7 @@ endif
 	# See https://msdn.microsoft.com/en-us/library/ms235330.aspx
 	EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib invalidcontinue.obj kernel32.lib ntdll.lib
 	PTHREAD_LIBS =
+	RC = compat/vcbuild/scripts/rc.pl
 	lib =
 	BASIC_CFLAGS += $(vcpkg_inc) $(sdk_includes) $(msvc_includes)
 ifndef DEBUG

From 65eddead816915337d33db400c5ebd2b2cb636b2 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Mon, 5 Apr 2021 14:12:14 -0400
Subject: [PATCH 027/265] config.mak.uname: add git.rc to MSVC builds

Teach MSVC=1 builds to depend on the `git.rc` file so that
the resulting executables have Windows-style resources and
version number information within them.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 config.mak.uname | 1 +
 1 file changed, 1 insertion(+)

diff --git a/config.mak.uname b/config.mak.uname
index 79607ac6619c5f..41bca503383dc6 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -490,6 +490,7 @@ endif
 	# handle twice, or to access the osfhandle of an already-closed stdout
 	# See https://msdn.microsoft.com/en-us/library/ms235330.aspx
 	EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib invalidcontinue.obj kernel32.lib ntdll.lib
+	GITLIBS += git.res
 	PTHREAD_LIBS =
 	RC = compat/vcbuild/scripts/rc.pl
 	lib =

From 5f848ebd65d45da2ffd50ddd0798d1475ee2da27 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Mon, 5 Apr 2021 14:24:52 -0400
Subject: [PATCH 028/265] clink.pl: ignore no-stack-protector arg on MSVC=1
 builds

Ignore the `-fno-stack-protector` compiler argument when building
with MSVC.  This will be used in a later commit that needs to build
a Win32 GUI app.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/vcbuild/scripts/clink.pl | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl
index 2768ae15f1879f..73c8a2b184f38b 100755
--- a/compat/vcbuild/scripts/clink.pl
+++ b/compat/vcbuild/scripts/clink.pl
@@ -122,6 +122,8 @@
 		push(@cflags, "-wd4996");
 	} elsif ("$arg" =~ /^-W[a-z]/) {
 		# let's ignore those
+	} elsif ("$arg" eq "-fno-stack-protector") {
+		# eat this
 	} else {
 		push(@args, $arg);
 	}

From 5709081d611c616f6af212806fde13cae27e4a41 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Mon, 5 Apr 2021 14:39:33 -0400
Subject: [PATCH 029/265] clink.pl: move default linker options for MSVC=1
 builds

Move the default `-ENTRY` and `-SUBSYSTEM` arguments for
MSVC=1 builds from `config.mak.uname` into `clink.pl`.
These args are constant for console-mode executables.

Add support to `clink.pl` for generating a Win32 GUI application
using the `-mwindows` argument (to match how GCC does it).  This
changes the `-ENTRY` and `-SUBSYSTEM` arguments accordingly.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/vcbuild/scripts/clink.pl | 11 +++++++++++
 config.mak.uname                |  2 +-
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl
index 73c8a2b184f38b..a38b360015ece9 100755
--- a/compat/vcbuild/scripts/clink.pl
+++ b/compat/vcbuild/scripts/clink.pl
@@ -15,6 +15,7 @@
 my @lflags = ();
 my $is_linking = 0;
 my $is_debug = 0;
+my $is_gui = 0;
 while (@ARGV) {
 	my $arg = shift @ARGV;
 	if ("$arg" eq "-DDEBUG") {
@@ -124,11 +125,21 @@
 		# let's ignore those
 	} elsif ("$arg" eq "-fno-stack-protector") {
 		# eat this
+	} elsif ("$arg" eq "-mwindows") {
+		$is_gui = 1;
 	} else {
 		push(@args, $arg);
 	}
 }
 if ($is_linking) {
+	if ($is_gui) {
+		push(@args, "-ENTRY:wWinMainCRTStartup");
+		push(@args, "-SUBSYSTEM:WINDOWS");
+	} else {
+		push(@args, "-ENTRY:wmainCRTStartup");
+		push(@args, "-SUBSYSTEM:CONSOLE");
+	}
+
 	push(@args, @lflags);
 	unshift(@args, "link.exe");
 } else {
diff --git a/config.mak.uname b/config.mak.uname
index 41bca503383dc6..00d778ff06938e 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -485,7 +485,7 @@ endif
 		compat/win32/trace2_win32_process_info.o \
 		compat/win32/dirent.o
 	COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DDETECT_MSYS_TTY -DENSURE_MSYSTEM_IS_SET -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
-	BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -ENTRY:wmainCRTStartup -SUBSYSTEM:CONSOLE
+	BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO
 	# invalidcontinue.obj allows Git's source code to close the same file
 	# handle twice, or to access the osfhandle of an already-closed stdout
 	# See https://msdn.microsoft.com/en-us/library/ms235330.aspx

From 52e50ffa9b0dccccdebdd7fdfd400a473a644d65 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 7 Apr 2021 15:29:21 +0200
Subject: [PATCH 030/265] buildsystems: remove duplicate clause

This seems to have been there since 259d87c35495 (Add scripts to
generate projects for other buildsystems (MSVC vcproj, QMake),
2009-09-16), i.e. since the beginning of that file.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 contrib/buildsystems/engine.pl | 1 -
 1 file changed, 1 deletion(-)

diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl
index ed6c45988a38b0..37b0b026f22430 100755
--- a/contrib/buildsystems/engine.pl
+++ b/contrib/buildsystems/engine.pl
@@ -265,7 +265,6 @@ sub handleCompileLine
             shift @parts;
         } elsif ("$part" eq "-c") {
             # ignore compile flag
-        } elsif ("$part" eq "-c") {
         } elsif ($part =~ /^.?-I/) {
             push(@incpaths, $part);
         } elsif ($part =~ /^.?-D/) {

From a6a0d9650c398f600e903f0b019515eb8c95ebc8 Mon Sep 17 00:00:00 2001
From: Ian Bearman <ianb@microsoft.com>
Date: Fri, 31 Jan 2020 15:37:27 -0800
Subject: [PATCH 031/265] vcxproj: support building Windows/ARM64 binaries

Signed-off-by: Ian Bearman <ianb@microsoft.com>
Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 contrib/buildsystems/Generators/Vcxproj.pm | 23 ++++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm
index 1a25789d28513b..548766fa9dbdb6 100644
--- a/contrib/buildsystems/Generators/Vcxproj.pm
+++ b/contrib/buildsystems/Generators/Vcxproj.pm
@@ -114,12 +114,21 @@ sub createProject {
       <Configuration>Release</Configuration>
       <Platform>x64</Platform>
     </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|ARM64">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM64">
+      <Configuration>Release</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
   </ItemGroup>
   <PropertyGroup Label="Globals">
     <ProjectGuid>$uuid</ProjectGuid>
     <Keyword>Win32Proj</Keyword>
     <VCPKGArch Condition="'\$(Platform)'=='Win32'">x86-windows</VCPKGArch>
-    <VCPKGArch Condition="'\$(Platform)'!='Win32'">x64-windows</VCPKGArch>
+    <VCPKGArch Condition="'\$(Platform)'=='x64'">x64-windows</VCPKGArch>
+    <VCPKGArch Condition="'\$(Platform)'=='ARM64'">arm64-windows</VCPKGArch>
     <VCPKGArchDirectory>$cdup\\compat\\vcbuild\\vcpkg\\installed\\\$(VCPKGArch)</VCPKGArchDirectory>
     <VCPKGBinDirectory Condition="'\$(Configuration)'=='Debug'">\$(VCPKGArchDirectory)\\debug\\bin</VCPKGBinDirectory>
     <VCPKGLibDirectory Condition="'\$(Configuration)'=='Debug'">\$(VCPKGArchDirectory)\\debug\\lib</VCPKGLibDirectory>
@@ -140,7 +149,7 @@ sub createProject {
   </PropertyGroup>
   <PropertyGroup>
     <ConfigurationType>$config_type</ConfigurationType>
-    <PlatformToolset>v140</PlatformToolset>
+    <PlatformToolset>v142</PlatformToolset>
     <!-- <CharacterSet>UTF-8</CharacterSet> -->
     <OutDir>..\\</OutDir>
     <!-- <IntDir>\$(ProjectDir)\$(Configuration)\\</IntDir> -->
@@ -355,8 +364,10 @@ sub createGlueProject {
     print F << "EOM";
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|ARM64 = Debug|ARM64
 		Debug|x64 = Debug|x64
 		Debug|x86 = Debug|x86
+		Release|ARM64 = Release|ARM64
 		Release|x64 = Release|x64
 		Release|x86 = Release|x86
 	EndGlobalSection
@@ -367,10 +378,14 @@ EOM
     foreach (@apps) {
         my $appname = $_;
         my $uuid = $build_structure{"APPS_${appname}_GUID"};
+        print F "\t\t${uuid}.Debug|ARM64.ActiveCfg = Debug|ARM64\n";
+        print F "\t\t${uuid}.Debug|ARM64.Build.0 = Debug|ARM64\n";
         print F "\t\t${uuid}.Debug|x64.ActiveCfg = Debug|x64\n";
         print F "\t\t${uuid}.Debug|x64.Build.0 = Debug|x64\n";
         print F "\t\t${uuid}.Debug|x86.ActiveCfg = Debug|Win32\n";
         print F "\t\t${uuid}.Debug|x86.Build.0 = Debug|Win32\n";
+        print F "\t\t${uuid}.Release|ARM64.ActiveCfg = Release|ARM64\n";
+        print F "\t\t${uuid}.Release|ARM64.Build.0 = Release|ARM64\n";
         print F "\t\t${uuid}.Release|x64.ActiveCfg = Release|x64\n";
         print F "\t\t${uuid}.Release|x64.Build.0 = Release|x64\n";
         print F "\t\t${uuid}.Release|x86.ActiveCfg = Release|Win32\n";
@@ -379,10 +394,14 @@ EOM
     foreach (@libs) {
         my $libname = $_;
         my $uuid = $build_structure{"LIBS_${libname}_GUID"};
+        print F "\t\t${uuid}.Debug|ARM64.ActiveCfg = Debug|ARM64\n";
+        print F "\t\t${uuid}.Debug|ARM64.Build.0 = Debug|ARM64\n";
         print F "\t\t${uuid}.Debug|x64.ActiveCfg = Debug|x64\n";
         print F "\t\t${uuid}.Debug|x64.Build.0 = Debug|x64\n";
         print F "\t\t${uuid}.Debug|x86.ActiveCfg = Debug|Win32\n";
         print F "\t\t${uuid}.Debug|x86.Build.0 = Debug|Win32\n";
+        print F "\t\t${uuid}.Release|ARM64.ActiveCfg = Release|ARM64\n";
+        print F "\t\t${uuid}.Release|ARM64.Build.0 = Release|ARM64\n";
         print F "\t\t${uuid}.Release|x64.ActiveCfg = Release|x64\n";
         print F "\t\t${uuid}.Release|x64.Build.0 = Release|x64\n";
         print F "\t\t${uuid}.Release|x86.ActiveCfg = Release|Win32\n";

From 7205ff9d790f06cce8f5c5d9f50616cd1919efe8 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 7 Apr 2021 15:15:08 +0200
Subject: [PATCH 032/265] vcxproj: handle resource files, too

On Windows, we also compile a "resource" file, which is similar to
source code, but contains metadata (such as the program version).

So far, we did not compile it in `MSVC` mode, only when compiling Git
for Windows with the GNU C Compiler.

In preparation for including it also when compiling with MS Visual C,
let's teach our `vcxproj` generator to handle those sort of files, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 contrib/buildsystems/Generators/Vcxproj.pm | 17 ++++++++++++++++-
 contrib/buildsystems/engine.pl             |  9 +++++----
 2 files changed, 21 insertions(+), 5 deletions(-)

diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm
index 81ee2f5a4a41a5..58a48233c59a76 100644
--- a/contrib/buildsystems/Generators/Vcxproj.pm
+++ b/contrib/buildsystems/Generators/Vcxproj.pm
@@ -89,6 +89,9 @@ sub createProject {
     $defines =~ s/>/&gt;/g;
     $defines =~ s/\'//g;
 
+    my $rcdefines = $defines;
+    $rcdefines =~ s/(?<!\\)"/\\$&/g;
+
     my $dir = $vcxproj;
     $dir =~ s/\/[^\/]*$//;
     die "Could not create the directory $dir for $label project!\n" unless (-d "$dir" || mkdir "$dir");
@@ -203,6 +206,9 @@ EOM
       <PreprocessorDefinitions>WIN32;_DEBUG;$defines;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
     </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>WIN32;_DEBUG;$rcdefines;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ResourceCompile>
     <Link>
       <GenerateDebugInformation>true</GenerateDebugInformation>
     </Link>
@@ -216,6 +222,9 @@ EOM
       <FunctionLevelLinking>true</FunctionLevelLinking>
       <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
     </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>WIN32;NDEBUG;$rcdefines;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ResourceCompile>
     <Link>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
@@ -225,9 +234,15 @@ EOM
   <ItemGroup>
 EOM
     foreach(@sources) {
-        print F << "EOM";
+        if (/\.rc$/) {
+            print F << "EOM";
+    <ResourceCompile Include="$_" />
+EOM
+        } else {
+            print F << "EOM";
     <ClCompile Include="$_" />
 EOM
+        }
     }
     print F << "EOM";
   </ItemGroup>
diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl
index 37b0b026f22430..441059a7370271 100755
--- a/contrib/buildsystems/engine.pl
+++ b/contrib/buildsystems/engine.pl
@@ -165,7 +165,7 @@ sub parseMakeOutput
             next;
         }
 
-        if($text =~ / -c /) {
+        if($text =~ / -c / || $text =~ / -i \S+\.rc /) {
             # compilation
             handleCompileLine($text, $line);
 
@@ -263,7 +263,7 @@ sub handleCompileLine
         if ("$part" eq "-o") {
             # ignore object file
             shift @parts;
-        } elsif ("$part" eq "-c") {
+        } elsif ("$part" eq "-c" || "$part" eq "-i") {
             # ignore compile flag
         } elsif ($part =~ /^.?-I/) {
             push(@incpaths, $part);
@@ -271,7 +271,7 @@ sub handleCompileLine
             push(@defines, $part);
         } elsif ($part =~ /^-/) {
             push(@cflags, $part);
-        } elsif ($part =~ /\.(c|cc|cpp)$/) {
+        } elsif ($part =~ /\.(c|cc|cpp|rc)$/) {
             $sourcefile = $part;
         } else {
             die "Unhandled compiler option @ line $lineno: $part";
@@ -358,7 +358,7 @@ sub handleLinkLine
             push(@libs, $part);
         } elsif ($part eq 'invalidcontinue.obj') {
             # ignore - known to MSVC
-        } elsif ($part =~ /\.o$/) {
+        } elsif ($part =~ /\.(o|res)$/) {
             push(@objfiles, $part);
         } elsif ($part =~ /\.obj$/) {
             # do nothing, 'make' should not be producing .obj, only .o files
@@ -371,6 +371,7 @@ sub handleLinkLine
     foreach (@objfiles) {
         my $sourcefile = $_;
         $sourcefile =~ s/\.o$/.c/;
+        $sourcefile =~ s/\.res$/.rc/;
         push(@sources, $sourcefile);
         push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}});
         push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}});

From 17dedfcb991aefbda496081638c6781792b287a0 Mon Sep 17 00:00:00 2001
From: Ian Bearman <ianb@microsoft.com>
Date: Fri, 31 Jan 2020 16:00:25 -0800
Subject: [PATCH 033/265] vcbuild: install ARM64 dependencies when building
 ARM64 binaries

Co-authored-by: Dennis Ameling <dennis@dennisameling.com>
Signed-off-by: Ian Bearman <ianb@microsoft.com>
Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/vcbuild/README                      | 6 +++++-
 compat/vcbuild/vcpkg_copy_dlls.bat         | 7 ++++++-
 compat/vcbuild/vcpkg_install.bat           | 9 +++++++--
 contrib/buildsystems/Generators/Vcxproj.pm | 2 +-
 4 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/compat/vcbuild/README b/compat/vcbuild/README
index 29ec1d0f104b80..1df1cabb1ebbbd 100644
--- a/compat/vcbuild/README
+++ b/compat/vcbuild/README
@@ -6,7 +6,11 @@ The Steps to Build Git with VS2015 or VS2017 from the command line.
    Prompt or from an SDK bash window:
 
    $ cd <repo_root>
-   $ ./compat/vcbuild/vcpkg_install.bat
+   $ ./compat/vcbuild/vcpkg_install.bat x64-windows
+
+   or
+
+   $ ./compat/vcbuild/vcpkg_install.bat arm64-windows
 
    The vcpkg tools and all of the third-party sources will be installed
    in this folder:
diff --git a/compat/vcbuild/vcpkg_copy_dlls.bat b/compat/vcbuild/vcpkg_copy_dlls.bat
index 13661c14f8705c..8bea0cbf83b6cf 100644
--- a/compat/vcbuild/vcpkg_copy_dlls.bat
+++ b/compat/vcbuild/vcpkg_copy_dlls.bat
@@ -15,7 +15,12 @@ REM ================================================================
 	@FOR /F "delims=" %%D IN ("%~dp0") DO @SET cwd=%%~fD
 	cd %cwd%
 
-	SET arch=x64-windows
+	SET arch=%2
+	IF NOT DEFINED arch (
+		echo defaulting to 'x64-windows`. Invoke %0 with 'x86-windows', 'x64-windows', or 'arm64-windows'
+		set arch=x64-windows
+	)
+
 	SET inst=%cwd%vcpkg\installed\%arch%
 
 	IF [%1]==[release] (
diff --git a/compat/vcbuild/vcpkg_install.bat b/compat/vcbuild/vcpkg_install.bat
index 8330d8120fb511..cacef18c11dc79 100644
--- a/compat/vcbuild/vcpkg_install.bat
+++ b/compat/vcbuild/vcpkg_install.bat
@@ -31,6 +31,12 @@ REM ================================================================
 
 	SETLOCAL EnableDelayedExpansion
 
+	SET arch=%1
+	IF NOT DEFINED arch (
+		echo defaulting to 'x64-windows`. Invoke %0 with 'x86-windows', 'x64-windows', or 'arm64-windows'
+		set arch=x64-windows
+	)
+
 	@FOR /F "delims=" %%D IN ("%~dp0") DO @SET cwd=%%~fD
 	cd %cwd%
 
@@ -55,9 +61,8 @@ REM ================================================================
 	echo Successfully installed %cwd%vcpkg\vcpkg.exe
 
 :install_libraries
-	SET arch=x64-windows
 
-	echo Installing third-party libraries...
+	echo Installing third-party libraries(%arch%)...
 	FOR %%i IN (zlib expat libiconv openssl libssh2 curl) DO (
 	    cd %cwd%vcpkg
 	    IF NOT EXIST "packages\%%i_%arch%" CALL :sub__install_one %%i
diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm
index 548766fa9dbdb6..7a92599946b097 100644
--- a/contrib/buildsystems/Generators/Vcxproj.pm
+++ b/contrib/buildsystems/Generators/Vcxproj.pm
@@ -193,7 +193,7 @@ EOM
     <PreBuildEvent Condition="!Exists('$cdup\\compat\\vcbuild\\vcpkg\\installed\\\$(VCPKGArch)\\include\\openssl\\ssl.h')">
       <Message>Initialize VCPKG</Message>
       <Command>del "$cdup\\compat\\vcbuild\\vcpkg"</Command>
-      <Command>call "$cdup\\compat\\vcbuild\\vcpkg_install.bat"</Command>
+      <Command>call "$cdup\\compat\\vcbuild\\vcpkg_install.bat" \$(VCPKGArch)</Command>
     </PreBuildEvent>
 EOM
     }

From 3f9cdd584782c8c0aa0986effb64471cfbe67aaa Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 7 Apr 2021 21:57:31 +0200
Subject: [PATCH 034/265] vcxproj: ignore -fno-stack-protector and -fno-common

An upcoming commit will introduce those compile options; MSVC does not
understand them, so let's suppress them when generating the Visual
Studio project files.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 contrib/buildsystems/engine.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl
index 441059a7370271..7453bc9697aa7f 100755
--- a/contrib/buildsystems/engine.pl
+++ b/contrib/buildsystems/engine.pl
@@ -263,7 +263,7 @@ sub handleCompileLine
         if ("$part" eq "-o") {
             # ignore object file
             shift @parts;
-        } elsif ("$part" eq "-c" || "$part" eq "-i") {
+        } elsif ("$part" eq "-c" || "$part" eq "-i" || "$part" =~ /^-fno-/) {
             # ignore compile flag
         } elsif ($part =~ /^.?-I/) {
             push(@incpaths, $part);

From 96b33684213fdffb6aeac3c0f2015825c006b4c0 Mon Sep 17 00:00:00 2001
From: Ian Bearman <ianb@microsoft.com>
Date: Tue, 4 Feb 2020 10:34:40 -0800
Subject: [PATCH 035/265] vcbuild: add an option to install individual
 'features'

In this context, a "feature" is a dependency combined with its own
dependencies.

Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/vcbuild/vcpkg_install.bat | 35 +++++++++++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)

diff --git a/compat/vcbuild/vcpkg_install.bat b/compat/vcbuild/vcpkg_install.bat
index cacef18c11dc79..8da212487ae97d 100644
--- a/compat/vcbuild/vcpkg_install.bat
+++ b/compat/vcbuild/vcpkg_install.bat
@@ -85,14 +85,47 @@ REM ================================================================
 :sub__install_one
 	echo     Installing package %1...
 
+	call :%1_features
+
 	REM vcpkg may not be reliable on slow, intermittent or proxy
 	REM connections, see e.g.
 	REM https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/4a8f7be5-5e15-4213-a7bb-ddf424a954e6/winhttpsendrequest-ends-with-12002-errorhttptimeout-after-21-seconds-no-matter-what-timeout?forum=windowssdk
 	REM which explains the hidden 21 second timeout
 	REM (last post by Dave : Microsoft - Windows Networking team)
 
-	.\vcpkg.exe install %1:%arch%
+	.\vcpkg.exe install %1%features%:%arch%
 	IF ERRORLEVEL 1 ( EXIT /B 1 )
 
 	echo     Finished %1
 	goto :EOF
+
+::
+:: features for each vcpkg to install
+:: there should be an entry here for each package to install
+:: 'set features=' means use the default otherwise
+:: 'set features=[comma-delimited-feature-set]' is the syntax
+::
+
+:zlib_features
+set features=
+goto :EOF
+
+:expat_features
+set features=
+goto :EOF
+
+:libiconv_features
+set features=
+goto :EOF
+
+:openssl_features
+set features=
+goto :EOF
+
+:libssh2_features
+set features=
+goto :EOF
+
+:curl_features
+set features=[core,openssl]
+goto :EOF

From 2ab3ae7cc1894ff616a436f63707715dd8572ee1 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 21 Feb 2017 13:28:58 +0100
Subject: [PATCH 036/265] mingw: ensure valid CTYPE

A change between versions 2.4.1 and 2.6.0 of the MSYS2 runtime modified
how Cygwin's runtime (and hence Git for Windows' MSYS2 runtime
derivative) handles locales: d16a56306d (Consolidate wctomb/mbtowc calls
for POSIX-1.2008, 2016-07-20).

An unintended side-effect is that "cold-calling" into the POSIX
emulation will start with a locale based on the current code page,
something that Git for Windows is very ill-prepared for, as it expects
to be able to pass a command-line containing non-ASCII characters to the
shell without having those characters munged.

One symptom of this behavior: when `git clone` or `git fetch` shell out
to call `git-upload-pack` with a path that contains non-ASCII
characters, the shell tried to interpret the entire command-line
(including command-line parameters) as executable path, which obviously
must fail.

This fixes https://github.com/git-for-windows/git/issues/1036

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/compat/mingw.c b/compat/mingw.c
index 6fe80fdf014021..3f987d711facf6 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2644,6 +2644,9 @@ static void setup_windows_environment(void)
 		if (!tmp && (tmp = getenv("USERPROFILE")))
 			setenv("HOME", tmp, 1);
 	}
+
+	if (!getenv("LC_ALL") && !getenv("LC_CTYPE") && !getenv("LANG"))
+		setenv("LC_CTYPE", "C.UTF-8", 1);
 }
 
 static PSID get_current_user_sid(void)

From 33aeaa5207d26237c0d270c4180817094c6e51f3 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Sat, 9 May 2020 14:08:36 +0200
Subject: [PATCH 037/265] vcxproj: unclash project directories with build
 outputs

It already caused problems with the test suite that the directory
containing `git.vcxproj` is called the same as the Git executable
without its file extension: `./git` is ambiguous, it could refer both to
the directory `git/` as well as to `git.exe`.

Now there is one more problem: when our GitHub workflow runs on the
`vs/master` branch, it fails in all but the Windows builds, as they want
to write the file `git` but there is already a directory in the way.

Let's just go ahead and append `.proj` to all of those directories, e.g.
`git.proj/` instead of `git/`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 config.mak.uname                           |  8 ++++----
 contrib/buildsystems/Generators/Vcxproj.pm | 18 ++++++++++--------
 2 files changed, 14 insertions(+), 12 deletions(-)

diff --git a/config.mak.uname b/config.mak.uname
index 259d1511cafb76..53c72e74c73600 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -751,7 +751,7 @@ vcxproj:
 
 	# Make .vcxproj files and add them
 	perl contrib/buildsystems/generate -g Vcxproj
-	git add -f git.sln {*,*/lib,t/helper/*}/*.vcxproj
+	git add -f git.sln {*,*/lib.proj,t/helper/*}/*.vcxproj
 
 	# Generate the LinkOrCopyBuiltins.targets and LinkOrCopyRemoteHttp.targets file
 	(echo '<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">' && \
@@ -761,7 +761,7 @@ vcxproj:
 	   echo '    <Copy SourceFiles="$$(OutDir)\git.exe" DestinationFiles="$$(OutDir)\'"$$name"'" SkipUnchangedFiles="true" UseHardlinksIfPossible="true" />'; \
 	 done && \
 	 echo '  </Target>' && \
-	 echo '</Project>') >git/LinkOrCopyBuiltins.targets
+	 echo '</Project>') >git.proj/LinkOrCopyBuiltins.targets
 	(echo '<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">' && \
 	 echo '  <Target Name="CopyBuiltins_AfterBuild" AfterTargets="AfterBuild">' && \
 	 for name in $(REMOTE_CURL_ALIASES); \
@@ -769,8 +769,8 @@ vcxproj:
 	   echo '    <Copy SourceFiles="$$(OutDir)\'"$(REMOTE_CURL_PRIMARY)"'" DestinationFiles="$$(OutDir)\'"$$name"'" SkipUnchangedFiles="true" UseHardlinksIfPossible="true" />'; \
 	 done && \
 	 echo '  </Target>' && \
-	 echo '</Project>') >git-remote-http/LinkOrCopyRemoteHttp.targets
-	git add -f git/LinkOrCopyBuiltins.targets git-remote-http/LinkOrCopyRemoteHttp.targets
+	 echo '</Project>') >git-remote-http.proj/LinkOrCopyRemoteHttp.targets
+	git add -f git.proj/LinkOrCopyBuiltins.targets git-remote-http.proj/LinkOrCopyRemoteHttp.targets
 
 	# Add generated headers
 	$(MAKE) MSVC=1 SKIP_VCPKG=1 prefix=/mingw64 $(GENERATED_H)
diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm
index 1a25789d28513b..81ee2f5a4a41a5 100644
--- a/contrib/buildsystems/Generators/Vcxproj.pm
+++ b/contrib/buildsystems/Generators/Vcxproj.pm
@@ -58,8 +58,8 @@ sub createProject {
     my $uuid = generate_guid($name);
     $$build_structure{"$prefix${target}_GUID"} = $uuid;
     my $vcxproj = $target;
-    $vcxproj =~ s/(.*\/)?(.*)/$&\/$2.vcxproj/;
-    $vcxproj =~ s/([^\/]*)(\/lib)\/(lib.vcxproj)/$1$2\/$1_$3/;
+    $vcxproj =~ s/(.*\/)?(.*)/$&.proj\/$2.vcxproj/;
+    $vcxproj =~ s/([^\/]*)(\/lib\.proj)\/(lib.vcxproj)/$1$2\/$1_$3/;
     $$build_structure{"$prefix${target}_VCXPROJ"} = $vcxproj;
 
     my @srcs = sort(map("$rel_dir\\$_", @{$$build_structure{"$prefix${name}_SOURCES"}}));
@@ -89,7 +89,9 @@ sub createProject {
     $defines =~ s/>/&gt;/g;
     $defines =~ s/\'//g;
 
-    die "Could not create the directory $target for $label project!\n" unless (-d "$target" || mkdir "$target");
+    my $dir = $vcxproj;
+    $dir =~ s/\/[^\/]*$//;
+    die "Could not create the directory $dir for $label project!\n" unless (-d "$dir" || mkdir "$dir");
 
     open F, ">$vcxproj" or die "Could not open $vcxproj for writing!\n";
     binmode F, ":crlf :utf8";
@@ -237,7 +239,7 @@ EOM
 
       print F << "EOM";
   <ItemGroup>
-    <ProjectReference Include="$cdup\\libgit\\libgit.vcxproj">
+    <ProjectReference Include="$cdup\\libgit.proj\\libgit.vcxproj">
       <Project>$uuid_libgit</Project>
       <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
     </ProjectReference>
@@ -252,7 +254,7 @@ EOM
       }
       if (!($name =~ 'xdiff')) {
         print F << "EOM";
-    <ProjectReference Include="$cdup\\xdiff\\lib\\xdiff_lib.vcxproj">
+    <ProjectReference Include="$cdup\\xdiff\\lib.proj\\xdiff_lib.vcxproj">
       <Project>$uuid_xdiff_lib</Project>
       <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
     </ProjectReference>
@@ -261,7 +263,7 @@ EOM
       if ($name =~ /(test-(line-buffer|svn-fe)|^git-remote-testsvn)\.exe$/) {
         my $uuid_vcs_svn_lib = $$build_structure{"LIBS_vcs-svn/lib_GUID"};
         print F << "EOM";
-    <ProjectReference Include="$cdup\\vcs-svn\\lib\\vcs-svn_lib.vcxproj">
+    <ProjectReference Include="$cdup\\vcs-svn\\lib.proj\\vcs-svn_lib.vcxproj">
       <Project>$uuid_vcs_svn_lib</Project>
       <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
     </ProjectReference>
@@ -338,7 +340,7 @@ sub createGlueProject {
 	my $vcxproj = $build_structure{"APPS_${appname}_VCXPROJ"};
 	$vcxproj =~ s/\//\\/g;
         $appname =~ s/.*\///;
-        print F "\"${appname}\", \"${vcxproj}\", \"${uuid}\"";
+        print F "\"${appname}.proj\", \"${vcxproj}\", \"${uuid}\"";
         print F "$SLN_POST";
     }
     foreach (@libs) {
@@ -348,7 +350,7 @@ sub createGlueProject {
         my $vcxproj = $build_structure{"LIBS_${libname}_VCXPROJ"};
 	$vcxproj =~ s/\//\\/g;
         $libname =~ s/\//_/g;
-        print F "\"${libname}\", \"${vcxproj}\", \"${uuid}\"";
+        print F "\"${libname}.proj\", \"${vcxproj}\", \"${uuid}\"";
         print F "$SLN_POST";
     }
 

From e1cac8ee7dacd21f9544b09ba982e5c20af78cc5 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 7 Apr 2021 15:48:50 +0200
Subject: [PATCH 038/265] vcxproj: handle GUI programs, too

So far, we only built Console programs, but we are about to introduce a
program that targets the Windows subsystem (i.e. it is a so-called "GUI"
program).

Let's handle this preemptively in the script that generates the Visual
Studio files.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 contrib/buildsystems/Generators/Vcxproj.pm | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm
index 58a48233c59a76..1cabe69bff68c7 100644
--- a/contrib/buildsystems/Generators/Vcxproj.pm
+++ b/contrib/buildsystems/Generators/Vcxproj.pm
@@ -92,6 +92,13 @@ sub createProject {
     my $rcdefines = $defines;
     $rcdefines =~ s/(?<!\\)"/\\$&/g;
 
+    my $entrypoint = 'wmainCRTStartup';
+    my $subsystem = 'Console';
+    if (grep /^-mwindows$/, @{$$build_structure{"$prefix${name}_LFLAGS"}}) {
+        $entrypoint = 'wWinMainCRTStartup';
+        $subsystem = 'Windows';
+    }
+
     my $dir = $vcxproj;
     $dir =~ s/\/[^\/]*$//;
     die "Could not create the directory $dir for $label project!\n" unless (-d "$dir" || mkdir "$dir");
@@ -179,9 +186,9 @@ sub createProject {
       <AdditionalLibraryDirectories>\$(VCPKGLibDirectory);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <AdditionalDependencies>\$(VCPKGLibs);\$(AdditionalDependencies)</AdditionalDependencies>
       <AdditionalOptions>invalidcontinue.obj %(AdditionalOptions)</AdditionalOptions>
-      <EntryPointSymbol>wmainCRTStartup</EntryPointSymbol>
+      <EntryPointSymbol>$entrypoint</EntryPointSymbol>
       <ManifestFile>$cdup\\compat\\win32\\git.manifest</ManifestFile>
-      <SubSystem>Console</SubSystem>
+      <SubSystem>$subsystem</SubSystem>
     </Link>
 EOM
     if ($target eq 'libgit') {

From e1dcdd23f65a52458ac1c9913b9fef415f474c2a Mon Sep 17 00:00:00 2001
From: Dennis Ameling <dennis@dennisameling.com>
Date: Fri, 4 Dec 2020 14:11:34 +0100
Subject: [PATCH 039/265] cmake: allow building for Windows/ARM64

Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 contrib/buildsystems/CMakeLists.txt | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index 185f56f414f4b7..402bc2f68c34aa 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -65,9 +65,9 @@ if(USE_VCPKG)
 	set(VCPKG_DIR "${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg")
 	if(NOT EXISTS ${VCPKG_DIR})
 		message("Initializing vcpkg and building the Git's dependencies (this will take a while...)")
-		execute_process(COMMAND ${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg_install.bat)
+		execute_process(COMMAND ${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg_install.bat ${VCPKG_ARCH})
 	endif()
-	list(APPEND CMAKE_PREFIX_PATH "${VCPKG_DIR}/installed/x64-windows")
+	list(APPEND CMAKE_PREFIX_PATH "${VCPKG_DIR}/installed/${VCPKG_ARCH}")
 
 	# In the vcpkg edition, we need this to be able to link to libcurl
 	set(CURL_NO_CURL_CMAKE ON)
@@ -1039,7 +1039,7 @@ file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "RUNTIME_PREFIX='${RUNTIME_PRE
 file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_PYTHON='${NO_PYTHON}'\n")
 file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "SUPPORTS_SIMPLE_IPC='${SUPPORTS_SIMPLE_IPC}'\n")
 if(USE_VCPKG)
-	file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "PATH=\"$PATH:$TEST_DIRECTORY/../compat/vcbuild/vcpkg/installed/x64-windows/bin\"\n")
+	file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "PATH=\"$PATH:$TEST_DIRECTORY/../compat/vcbuild/vcpkg/installed/${VCPKG_ARCH}/bin\"\n")
 endif()
 
 #Make the tests work when building out of the source tree

From 223a8c700e89332b5253681a16db2277f02cd6d2 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Sat, 1 Feb 2020 00:31:16 +0100
Subject: [PATCH 040/265] mingw: allow `git.exe` to be used instead of the "Git
 wrapper"

Git for Windows wants to add `git.exe` to the users' `PATH`, without
cluttering the latter with unnecessary executables such as `wish.exe`.
To that end, it invented the concept of its "Git wrapper", i.e. a tiny
executable located in `C:\Program Files\Git\cmd\git.exe` (originally a
CMD script) whose sole purpose is to set up a couple of environment
variables and then spawn the _actual_ `git.exe` (which nowadays lives in
`C:\Program Files\Git\mingw64\bin\git.exe` for 64-bit, and the obvious
equivalent for 32-bit installations).

Currently, the following environment variables are set unless already
initialized:

- `MSYSTEM`, to make sure that the MSYS2 Bash and the MSYS2 Perl
  interpreter behave as expected, and

- `PLINK_PROTOCOL`, to force PuTTY's `plink.exe` to use the SSH
  protocol instead of Telnet,

- `PATH`, to make sure that the `bin` folder in the user's home
  directory, as well as the `/mingw64/bin` and the `/usr/bin`
  directories are included. The trick here is that the `/mingw64/bin/`
  and `/usr/bin/` directories are relative to the top-level installation
  directory of Git for Windows (which the included Bash interprets as
  `/`, i.e. as the MSYS pseudo root directory).

Using the absence of `MSYSTEM` as a tell-tale, we can detect in
`git.exe` whether these environment variables have been initialized
properly. Therefore we can call `C:\Program Files\Git\mingw64\bin\git`
in-place after this change, without having to call Git through the Git
wrapper.

Obviously, above-mentioned directories must be _prepended_ to the `PATH`
variable, otherwise we risk picking up executables from unrelated Git
installations. We do that by constructing the new `PATH` value from
scratch, appending `$HOME/bin` (if `HOME` is set), then the MSYS2 system
directories, and then appending the original `PATH`.

Side note: this modification of the `PATH` variable is independent of
the modification necessary to reach the executables and scripts in
`/mingw64/libexec/git-core/`, i.e. the `GIT_EXEC_PATH`. That
modification is still performed by Git, elsewhere, long after making the
changes described above.

While we _still_ cannot simply hard-link `mingw64\bin\git.exe` to `cmd`
(because the former depends on a couple of `.dll` files that are only in
`mingw64\bin`, i.e. calling `...\cmd\git.exe` would fail to load due to
missing dependencies), at least we can now avoid that extra process of
running the Git wrapper (which then has to wait for the spawned
`git.exe` to finish) by calling `...\mingw64\bin\git.exe` directly, via
its absolute path.

Testing this is in Git's test suite tricky: we set up a "new" MSYS
pseudo-root and copy the `git.exe` file into the appropriate location,
then verify that `MSYSTEM` is set properly, and also that the `PATH` is
modified so that scripts can be found in `$HOME/bin`, `/mingw64/bin/`
and `/usr/bin/`.

This addresses https://github.com/git-for-windows/git/issues/2283

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c        | 69 +++++++++++++++++++++++++++++++++++++++++++
 config.mak.uname      |  4 +--
 t/t0060-path-utils.sh | 23 ++++++++++++++-
 3 files changed, 93 insertions(+), 3 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 3f987d711facf6..d27184f0c6c319 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2593,6 +2593,47 @@ int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen)
 	return -1;
 }
 
+#ifdef ENSURE_MSYSTEM_IS_SET
+static size_t append_system_bin_dirs(char *path, size_t size)
+{
+#if !defined(RUNTIME_PREFIX) || !defined(HAVE_WPGMPTR)
+	return 0;
+#else
+	char prefix[32768];
+	const char *slash;
+	size_t len = xwcstoutf(prefix, _wpgmptr, sizeof(prefix)), off = 0;
+
+	if (len == 0 || len >= sizeof(prefix) ||
+	    !(slash = find_last_dir_sep(prefix)))
+		return 0;
+	/* strip trailing `git.exe` */
+	len = slash - prefix;
+
+	/* strip trailing `cmd` or `mingw64\bin` or `mingw32\bin` or `bin` or `libexec\git-core` */
+	if (strip_suffix_mem(prefix, &len, "\\mingw64\\libexec\\git-core") ||
+	    strip_suffix_mem(prefix, &len, "\\mingw64\\bin"))
+		off += xsnprintf(path + off, size - off,
+				 "%.*s\\mingw64\\bin;", (int)len, prefix);
+	else if (strip_suffix_mem(prefix, &len, "\\mingw32\\libexec\\git-core") ||
+		 strip_suffix_mem(prefix, &len, "\\mingw32\\bin"))
+		off += xsnprintf(path + off, size - off,
+				 "%.*s\\mingw32\\bin;", (int)len, prefix);
+	else if (strip_suffix_mem(prefix, &len, "\\cmd") ||
+		 strip_suffix_mem(prefix, &len, "\\bin") ||
+		 strip_suffix_mem(prefix, &len, "\\libexec\\git-core"))
+		off += xsnprintf(path + off, size - off,
+				 "%.*s\\mingw%d\\bin;", (int)len, prefix,
+				 (int)(sizeof(void *) * 8));
+	else
+		return 0;
+
+	off += xsnprintf(path + off, size - off,
+			 "%.*s\\usr\\bin;", (int)len, prefix);
+	return off;
+#endif
+}
+#endif
+
 static void setup_windows_environment(void)
 {
 	char *tmp = getenv("TMPDIR");
@@ -2645,6 +2686,34 @@ static void setup_windows_environment(void)
 			setenv("HOME", tmp, 1);
 	}
 
+	if (!getenv("PLINK_PROTOCOL"))
+		setenv("PLINK_PROTOCOL", "ssh", 0);
+
+#ifdef ENSURE_MSYSTEM_IS_SET
+	if (!(tmp = getenv("MSYSTEM")) || !tmp[0]) {
+		const char *home = getenv("HOME"), *path = getenv("PATH");
+		char buf[32768];
+		size_t off = 0;
+
+		xsnprintf(buf, sizeof(buf),
+			  "MINGW%d", (int)(sizeof(void *) * 8));
+		setenv("MSYSTEM", buf, 1);
+
+		if (home)
+			off += xsnprintf(buf + off, sizeof(buf) - off,
+					 "%s\\bin;", home);
+		off += append_system_bin_dirs(buf + off, sizeof(buf) - off);
+		if (path)
+			off += xsnprintf(buf + off, sizeof(buf) - off,
+					 "%s", path);
+		else if (off > 0)
+			buf[off - 1] = '\0';
+		else
+			buf[0] = '\0';
+		setenv("PATH", buf, 1);
+	}
+#endif
+
 	if (!getenv("LC_ALL") && !getenv("LC_CTYPE") && !getenv("LANG"))
 		setenv("LC_CTYPE", "C.UTF-8", 1);
 }
diff --git a/config.mak.uname b/config.mak.uname
index 259d1511cafb76..c209cb3e79f0c2 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -484,7 +484,7 @@ endif
 		compat/win32/pthread.o compat/win32/syslog.o \
 		compat/win32/trace2_win32_process_info.o \
 		compat/win32/dirent.o
-	COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DDETECT_MSYS_TTY -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
+	COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DDETECT_MSYS_TTY -DENSURE_MSYSTEM_IS_SET -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
 	BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -ENTRY:wmainCRTStartup -SUBSYSTEM:CONSOLE
 	# invalidcontinue.obj allows Git's source code to close the same file
 	# handle twice, or to access the osfhandle of an already-closed stdout
@@ -707,7 +707,7 @@ else
 		endif
 		CC = gcc
 		COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \
-			-fstack-protector-strong
+			-DENSURE_MSYSTEM_IS_SET -fstack-protector-strong
 		EXTLIBS += -lntdll
 		INSTALL = /bin/install
 		INTERNAL_QSORT = YesPlease
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index 2fe6ae6a4e544c..f0272a8c0c00a1 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -548,7 +548,8 @@ test_expect_success RUNTIME_PREFIX,CAN_EXEC_IN_PWD 'RUNTIME_PREFIX works' '
 	cp "$GIT_EXEC_PATH"/git$X pretend/bin/ &&
 	GIT_EXEC_PATH= ./pretend/bin/git here >actual &&
 	echo HERE >expect &&
-	test_cmp expect actual'
+	test_cmp expect actual
+'
 
 test_expect_success RUNTIME_PREFIX,CAN_EXEC_IN_PWD '%(prefix)/ works' '
 	mkdir -p pretend/bin &&
@@ -559,4 +560,24 @@ test_expect_success RUNTIME_PREFIX,CAN_EXEC_IN_PWD '%(prefix)/ works' '
 	test_cmp expect actual
 '
 
+test_expect_success MINGW 'MSYSTEM/PATH is adjusted if necessary' '
+	mkdir -p "$HOME"/bin pretend/mingw64/bin \
+		pretend/mingw64/libexec/git-core pretend/usr/bin &&
+	cp "$GIT_EXEC_PATH"/git.exe pretend/mingw64/bin/ &&
+	cp "$GIT_EXEC_PATH"/git.exe pretend/mingw64/libexec/git-core/ &&
+	echo "env | grep MSYSTEM=" | write_script "$HOME"/bin/git-test-home &&
+	echo "echo mingw64" | write_script pretend/mingw64/bin/git-test-bin &&
+	echo "echo usr" | write_script pretend/usr/bin/git-test-bin2 &&
+
+	(
+		MSYSTEM= &&
+		GIT_EXEC_PATH= &&
+		pretend/mingw64/libexec/git-core/git.exe test-home >actual &&
+		pretend/mingw64/libexec/git-core/git.exe test-bin >>actual &&
+		pretend/mingw64/bin/git.exe test-bin2 >>actual
+	) &&
+	test_write_lines MSYSTEM=$MSYSTEM mingw64 usr >expect &&
+	test_cmp expect actual
+'
+
 test_done

From 90eb88c48ae3a7c4bbb0308e68fa9621cba516b6 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Sat, 9 May 2020 16:19:06 +0200
Subject: [PATCH 041/265] t5505/t5516: allow running without `.git/branches/`
 in the templates

When we commit the template directory as part of `make vcxproj`, the
`branches/` directory is not actually commited, as it is empty.

Two tests were not prepared for that situation.

This developer tried to get rid of the support for `.git/branches/` a
long time ago, but that effort did not bear fruit, so the best we can do
is work around in these here tests.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t5505-remote.sh     | 2 ++
 t/t5516-fetch-push.sh | 4 ++++
 2 files changed, 6 insertions(+)

diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index c90cf47acdb234..2accb3840d27b6 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -928,6 +928,7 @@ test_expect_success 'migrate a remote from named file in $GIT_DIR/branches' '
 	(
 		cd six &&
 		git remote rm origin &&
+		mkdir -p .git/branches &&
 		echo "$origin_url#main" >.git/branches/origin &&
 		git remote rename origin origin &&
 		test_path_is_missing .git/branches/origin &&
@@ -942,6 +943,7 @@ test_expect_success 'migrate a remote from named file in $GIT_DIR/branches (2)'
 	(
 		cd seven &&
 		git remote rm origin &&
+		mkdir -p .git/branches &&
 		echo "quux#foom" > .git/branches/origin &&
 		git remote rename origin origin &&
 		test_path_is_missing .git/branches/origin &&
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 4dfb080433e0a0..0b860a45ccc714 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -916,6 +916,7 @@ test_expect_success 'fetch with branches' '
 	mk_empty testrepo &&
 	git branch second $the_first_commit &&
 	git checkout second &&
+	mkdir -p testrepo/.git/branches &&
 	echo ".." > testrepo/.git/branches/branch1 &&
 	(
 		cd testrepo &&
@@ -929,6 +930,7 @@ test_expect_success 'fetch with branches' '
 
 test_expect_success 'fetch with branches containing #' '
 	mk_empty testrepo &&
+	mkdir -p testrepo/.git/branches &&
 	echo "..#second" > testrepo/.git/branches/branch2 &&
 	(
 		cd testrepo &&
@@ -943,6 +945,7 @@ test_expect_success 'fetch with branches containing #' '
 test_expect_success 'push with branches' '
 	mk_empty testrepo &&
 	git checkout second &&
+	mkdir -p .git/branches &&
 	echo "testrepo" > .git/branches/branch1 &&
 	git push branch1 &&
 	(
@@ -955,6 +958,7 @@ test_expect_success 'push with branches' '
 
 test_expect_success 'push with branches containing #' '
 	mk_empty testrepo &&
+	mkdir -p .git/branches &&
 	echo "testrepo#branch3" > .git/branches/branch2 &&
 	git push branch2 &&
 	(

From b611d9bfdc9ee78d5ed8456d3ce76639794d51af Mon Sep 17 00:00:00 2001
From: Kelly Heller <kkheller@cedrus.com>
Date: Wed, 27 May 2015 14:51:43 -0700
Subject: [PATCH 042/265] Allow `add -p` and `add -i` with a large number of
 files

This fixes https://github.com/msysgit/git/issues/182.

Inspired by Pull Request 218 using code from @PhilipDavis.

[jes: simplified code quite a bit]

Signed-off-by: Kelly Heller <kkheller@cedrus.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 git-add--interactive.perl | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index 95887fd8e52ae9..63dd89a6513bcd 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -174,6 +174,24 @@ sub run_cmd_pipe {
 		die "$^O does not support: @invalid\n" if @invalid;
 		my @args = map { m/ /o ? "\"$_\"": $_ } @_;
 		return qx{@args};
+	} elsif (($^O eq 'MSWin32' || $^O eq 'msys') && (scalar @_ > 200) &&
+			grep $_ eq '--', @_) {
+		use File::Temp qw(tempfile);
+		my ($fhargs, $filename) =
+			tempfile('git-args-XXXXXX', UNLINK => 1);
+
+		my $cmd = 'cat '.$filename.' | xargs -0 -s 20000 ';
+		while ($_[0] ne '--') {
+			$cmd = $cmd . shift(@_) . ' ';
+		}
+
+		shift(@_);
+		print $fhargs join("\0", @_);
+		close($fhargs);
+
+		my $fh = undef;
+		open($fh, '-|', $cmd) or die;
+		return <$fh>;
 	} else {
 		my $fh = undef;
 		open($fh, '-|', @_) or die;

From ee63a780a1d671760c7da253ed50ad676f5656e4 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 3 Dec 2020 22:44:24 +0100
Subject: [PATCH 043/265] win32: add a helper to run `git.exe` without a
 foreground window
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

On Windows, there are two kinds of executables, console ones and
non-console ones. Git's executables are all console ones.

When launching the former e.g. in a scheduled task, a CMD window pops
up. This is not what we want for the tasks installed via the `git
maintenance` command.

To work around this, let's introduce `headless-git.exe`, which is a
non-console program that does _not_ pop up any window. All it does is to
re-launch `git.exe`, suppressing that console window, passing through
all command-line arguments as-are.

Helped-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
Helped-by: Yuyi Wang <Strawberry_Str@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
---
 Makefile                                   |   9 ++
 compat/win32/headless.c                    | 115 +++++++++++++++++++++
 config.mak.uname                           |   3 +
 contrib/buildsystems/CMakeLists.txt        |   9 ++
 contrib/buildsystems/Generators/Vcxproj.pm |   4 +-
 contrib/buildsystems/engine.pl             |   1 +
 6 files changed, 139 insertions(+), 2 deletions(-)
 create mode 100644 compat/win32/headless.c

diff --git a/Makefile b/Makefile
index 36642b36de07aa..dad2b4f1e70012 100644
--- a/Makefile
+++ b/Makefile
@@ -2634,6 +2634,13 @@ compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \
 compat/nedmalloc/nedmalloc.sp: SP_EXTRA_FLAGS += -Wno-non-pointer-null
 endif
 
+headless-git.o: compat/win32/headless.c GIT-CFLAGS
+	$(QUIET_CC)$(CC) $(ALL_CFLAGS) $(COMPAT_CFLAGS) \
+		-fno-stack-protector -o $@ -c -Wall -Wwrite-strings $<
+
+headless-git$X: headless-git.o git.res GIT-LDFLAGS
+	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) -mwindows -o $@ $< git.res
+
 git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
@@ -3292,6 +3299,7 @@ cocciclean:
 clean: profile-clean coverage-clean cocciclean
 	$(RM) *.res
 	$(RM) $(OBJECTS)
+	$(RM) headless-git.o
 	$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(REFTABLE_TEST_LIB)
 	$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
 	$(RM) $(TEST_PROGRAMS)
@@ -3321,6 +3329,7 @@ endif
 	$(RM) GIT-SCRIPT-DEFINES GIT-PERL-DEFINES GIT-PERL-HEADER GIT-PYTHON-VARS
 ifdef MSVC
 	$(RM) $(patsubst %.o,%.o.pdb,$(OBJECTS))
+	$(RM) headless-git.o.pdb
 	$(RM) $(patsubst %.exe,%.pdb,$(OTHER_PROGRAMS))
 	$(RM) $(patsubst %.exe,%.ilk,$(OTHER_PROGRAMS))
 	$(RM) $(patsubst %.exe,%.iobj,$(OTHER_PROGRAMS))
diff --git a/compat/win32/headless.c b/compat/win32/headless.c
new file mode 100644
index 00000000000000..8b00dfe3bd5d00
--- /dev/null
+++ b/compat/win32/headless.c
@@ -0,0 +1,115 @@
+/*
+ * headless Git - run Git without opening a console window on Windows
+ */
+
+#define STRICT
+#define WIN32_LEAN_AND_MEAN
+#define UNICODE
+#define _UNICODE
+#include <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+/*
+ * If `dir` contains the path to a Git exec directory, extend `PATH` to
+ * include the corresponding `bin/` directory (which is where all those
+ * `.dll` files needed by `git.exe` are, on Windows).
+ */
+static int extend_path(wchar_t *dir, size_t dir_len)
+{
+	const wchar_t *suffix = L"\\libexec\\git-core";
+	size_t suffix_len = wcslen(suffix);
+	wchar_t *env;
+	DWORD len;
+
+	if (dir_len < suffix_len)
+		return 0;
+
+	dir_len -= suffix_len;
+	if (memcmp(dir + dir_len, suffix, suffix_len * sizeof(wchar_t)))
+		return 0;
+
+	len = GetEnvironmentVariableW(L"PATH", NULL, 0);
+	if (!len)
+		return 0;
+
+	env = _alloca((dir_len + 5 + len) * sizeof(wchar_t));
+	wcsncpy(env, dir, dir_len);
+	wcscpy(env + dir_len, L"\\bin;");
+	if (!GetEnvironmentVariableW(L"PATH", env + dir_len + 5, len))
+		return 0;
+
+	SetEnvironmentVariableW(L"PATH", env);
+	return 1;
+}
+
+int WINAPI wWinMain(_In_ HINSTANCE instance,
+		    _In_opt_ HINSTANCE previous_instance,
+		    _In_ LPWSTR command_line, _In_ int show)
+{
+	wchar_t git_command_line[32768];
+	size_t size = sizeof(git_command_line) / sizeof(wchar_t);
+	const wchar_t *needs_quotes = L"";
+	int slash = 0, i;
+
+	STARTUPINFO startup_info = {
+		.cb = sizeof(STARTUPINFO),
+		.dwFlags = STARTF_USESHOWWINDOW,
+		.wShowWindow = SW_HIDE,
+	};
+	PROCESS_INFORMATION process_info = { 0 };
+	DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT |
+		CREATE_NEW_CONSOLE | CREATE_NO_WINDOW;
+	DWORD exit_code;
+
+	/* First, determine the full path of argv[0] */
+	for (i = 0; _wpgmptr[i]; i++)
+		if (_wpgmptr[i] == L' ')
+			needs_quotes = L"\"";
+		else if (_wpgmptr[i] == L'\\')
+			slash = i;
+
+	if (slash >= size - 11)
+		return 127; /* Too long path */
+
+	/* If it is in Git's exec path, add the bin/ directory to the PATH */
+	extend_path(_wpgmptr, slash);
+
+	/* Then, add the full path of `git.exe` as argv[0] */
+	i = swprintf_s(git_command_line, size, L"%ls%.*ls\\git.exe%ls",
+		       needs_quotes, slash, _wpgmptr, needs_quotes);
+	if (i < 0)
+		return 127; /* Too long path */
+
+	if (*command_line) {
+		/* Now, append the command-line arguments */
+		i = swprintf_s(git_command_line + i, size - i,
+			       L" %ls", command_line);
+		if (i < 0)
+			return 127;
+	}
+
+	startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+	startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+	startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+
+	if (!CreateProcess(NULL, /* infer argv[0] from the command line */
+			   git_command_line, /* modified command line */
+			   NULL, /* inherit process handles? */
+			   NULL, /* inherit thread handles? */
+			   FALSE, /* handles inheritable? */
+			   creation_flags,
+			   NULL, /* use this process' environment */
+			   NULL, /* use this process' working directory */
+			   &startup_info, &process_info))
+		return 129; /* could not start */
+	WaitForSingleObject(process_info.hProcess, INFINITE);
+	if (!GetExitCodeProcess(process_info.hProcess, &exit_code))
+		exit_code = 130; /* Could not determine exit code? */
+
+	CloseHandle(process_info.hProcess);
+	CloseHandle(process_info.hThread);
+
+	return (int)exit_code;
+}
diff --git a/config.mak.uname b/config.mak.uname
index 00d778ff06938e..c1040505b49667 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -524,6 +524,8 @@ else
 endif
 	X = .exe
 
+	EXTRA_PROGRAMS += headless-git$X
+
 compat/msvc.o: compat/msvc.c compat/mingw.c GIT-CFLAGS
 endif
 ifeq ($(uname_S),Interix)
@@ -675,6 +677,7 @@ ifeq ($(uname_S),MINGW)
 	RC = windres -O coff
 	NATIVE_CRLF = YesPlease
 	X = .exe
+	EXTRA_PROGRAMS += headless-git$X
 ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
 	htmldir = doc/git/html/
 	prefix =
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index 185f56f414f4b7..73c86110faa37a 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -712,6 +712,15 @@ if(WIN32)
 	else()
 		message(FATAL_ERROR "Unhandled compiler: ${CMAKE_C_COMPILER_ID}")
 	endif()
+
+	add_executable(headless-git ${CMAKE_SOURCE_DIR}/compat/win32/headless.c)
+	if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
+		target_link_options(headless-git PUBLIC -municode -Wl,-subsystem,windows)
+	elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
+		target_link_options(headless-git PUBLIC /NOLOGO /ENTRY:wWinMainCRTStartup /SUBSYSTEM:WINDOWS)
+	else()
+		message(FATAL_ERROR "Unhandled compiler: ${CMAKE_C_COMPILER_ID}")
+	endif()
 elseif(UNIX)
 	target_link_libraries(common-main pthread rt)
 endif()
diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm
index 1cabe69bff68c7..a6d1c6b8d05682 100644
--- a/contrib/buildsystems/Generators/Vcxproj.pm
+++ b/contrib/buildsystems/Generators/Vcxproj.pm
@@ -76,7 +76,7 @@ sub createProject {
 
     my $libs_release = "\n    ";
     my $libs_debug = "\n    ";
-    if (!$static_library) {
+    if (!$static_library && $name ne 'headless-git') {
       $libs_release = join(";", sort(grep /^(?!libgit\.lib|xdiff\/lib\.lib|vcs-svn\/lib\.lib|reftable\/libreftable\.lib)/, @{$$build_structure{"$prefix${name}_LIBS"}}));
       $libs_debug = $libs_release;
       $libs_debug =~ s/zlib\.lib/zlibd\.lib/g;
@@ -254,7 +254,7 @@ EOM
     print F << "EOM";
   </ItemGroup>
 EOM
-    if (!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') {
+    if ((!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') && !($name =~ /headless-git/)) {
       my $uuid_libgit = $$build_structure{"LIBS_libgit_GUID"};
       my $uuid_libreftable = $$build_structure{"LIBS_reftable/libreftable_GUID"};
       my $uuid_xdiff_lib = $$build_structure{"LIBS_xdiff/lib_GUID"};
diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl
index 7453bc9697aa7f..417ae71d44ccab 100755
--- a/contrib/buildsystems/engine.pl
+++ b/contrib/buildsystems/engine.pl
@@ -370,6 +370,7 @@ sub handleLinkLine
 #    exit(1);
     foreach (@objfiles) {
         my $sourcefile = $_;
+        $sourcefile =~ s/^headless-git\.o$/compat\/win32\/headless.c/;
         $sourcefile =~ s/\.o$/.c/;
         $sourcefile =~ s/\.res$/.rc/;
         push(@sources, $sourcefile);

From f3f6c7968cfffc22263441a1d6d2a20e0f842d6a Mon Sep 17 00:00:00 2001
From: Philip Oakley <philipoakley@iee.email>
Date: Sun, 6 Oct 2019 18:40:55 +0100
Subject: [PATCH 044/265] vcpkg_install: detect lack of Git

The vcpkg_install batch file depends on the availability of a
working Git on the CMD path. This may not be present if the user
has selected the 'bash only' option during Git-for-Windows install.

Detect and tell the user about their lack of a working Git in the CMD
window.

Fixes #2348.
A separate PR https://github.com/git-for-windows/build-extra/pull/258
now highlights the recommended path setting during install.

Signed-off-by: Philip Oakley <philipoakley@iee.email>
---
 compat/vcbuild/vcpkg_install.bat | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/compat/vcbuild/vcpkg_install.bat b/compat/vcbuild/vcpkg_install.bat
index ebd0bad242a8ca..bcbbf536af3141 100644
--- a/compat/vcbuild/vcpkg_install.bat
+++ b/compat/vcbuild/vcpkg_install.bat
@@ -36,6 +36,13 @@ REM ================================================================
 
 	dir vcpkg\vcpkg.exe >nul 2>nul && GOTO :install_libraries
 
+	git.exe version 2>nul
+	IF ERRORLEVEL 1 (
+	echo "***"
+	echo "Git not found. Please adjust your CMD path or Git install option."
+	echo "***"
+	EXIT /B 1 )
+
 	echo Fetching vcpkg in %cwd%vcpkg
 	git.exe clone https://github.com/Microsoft/vcpkg vcpkg
 	IF ERRORLEVEL 1 ( EXIT /B 1 )

From 77c6fa1808382faad58e142b9f51f64450e45579 Mon Sep 17 00:00:00 2001
From: Dennis Ameling <dennis@dennisameling.com>
Date: Sun, 29 Nov 2020 00:12:26 +0100
Subject: [PATCH 045/265] ci(vs-build) also build Windows/ARM64 artifacts

There are no Windows/ARM64 agents in GitHub Actions yet, therefore we
just skip adjusting the `vs-test` job for now.

Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/main.yml | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index c35200defb9357..1a42e31cbe82c9 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -137,6 +137,9 @@ jobs:
       NO_PERL: 1
       GIT_CONFIG_PARAMETERS: "'user.name=CI' 'user.email=ci@git'"
     runs-on: windows-latest
+    strategy:
+      matrix:
+        arch: [x64, arm64]
     steps:
     - uses: actions/checkout@v2
     - uses: git-for-windows/setup-git-for-windows-sdk@v1
@@ -158,14 +161,14 @@ jobs:
       uses: microsoft/setup-msbuild@v1
     - name: copy dlls to root
       shell: cmd
-      run: compat\vcbuild\vcpkg_copy_dlls.bat release
+      run: compat\vcbuild\vcpkg_copy_dlls.bat release ${{ matrix.arch }}-windows
     - name: generate Visual Studio solution
       shell: bash
       run: |
-        cmake `pwd`/contrib/buildsystems/ -DCMAKE_PREFIX_PATH=`pwd`/compat/vcbuild/vcpkg/installed/x64-windows \
-        -DNO_GETTEXT=YesPlease -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON
+        cmake `pwd`/contrib/buildsystems/ -DCMAKE_PREFIX_PATH=`pwd`/compat/vcbuild/vcpkg/installed/${{ matrix.arch }}-windows \
+        -DNO_GETTEXT=YesPlease -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON -DCMAKE_GENERATOR_PLATFORM=${{ matrix.arch }} -DVCPKG_ARCH=${{ matrix.arch }}-windows
     - name: MSBuild
-      run: msbuild git.sln -property:Configuration=Release -property:Platform=x64 -maxCpuCount:4 -property:PlatformToolset=v142
+      run: msbuild git.sln -property:Configuration=Release -property:Platform=${{ matrix.arch }} -maxCpuCount:4 -property:PlatformToolset=v142
     - name: bundle artifact tar
       shell: bash
       env:
@@ -179,7 +182,7 @@ jobs:
     - name: upload tracked files and build artifacts
       uses: actions/upload-artifact@v2
       with:
-        name: vs-artifacts
+        name: vs-artifacts-${{ matrix.arch }}
         path: artifacts
   vs-test:
     name: win+VS test
@@ -194,7 +197,7 @@ jobs:
     - name: download tracked files and build artifacts
       uses: actions/download-artifact@v2
       with:
-        name: vs-artifacts
+        name: vs-artifacts-x64
         path: ${{github.workspace}}
     - name: extract tracked files and build artifacts
       shell: bash

From a13dfdb7f6826395417faf3343ad1ee81975678a Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 31 Jan 2020 11:49:04 +0100
Subject: [PATCH 046/265] mingw: implement a platform-specific
 `strbuf_realpath()`

There is a Win32 API function to resolve symbolic links, and we can use
that instead of resolving them manually. Even better, this function also
resolves NTFS junction points (which are somewhat similar to bind
mounts).

This fixes https://github.com/git-for-windows/git/issues/2481.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c   | 63 ++++++++++++++++++++++++++++++++++++++++++++++++
 compat/mingw.h   |  3 +++
 t/t3700-add.sh   |  2 +-
 t/t5601-clone.sh |  7 ++++++
 4 files changed, 74 insertions(+), 1 deletion(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 6fe80fdf014021..f988d56ef04a22 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1117,6 +1117,69 @@ struct tm *localtime_r(const time_t *timep, struct tm *result)
 }
 #endif
 
+char *mingw_strbuf_realpath(struct strbuf *resolved, const char *path)
+{
+	wchar_t wpath[MAX_PATH];
+	HANDLE h;
+	DWORD ret;
+	int len;
+	const char *last_component = NULL;
+
+	if (xutftowcs_path(wpath, path) < 0)
+		return NULL;
+
+	h = CreateFileW(wpath, 0,
+			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+			OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+	/*
+	 * strbuf_realpath() allows the last path component to not exist. If
+	 * that is the case, now it's time to try without last component.
+	 */
+	if (h == INVALID_HANDLE_VALUE &&
+	    GetLastError() == ERROR_FILE_NOT_FOUND) {
+		/* cut last component off of `wpath` */
+		wchar_t *p = wpath + wcslen(wpath);
+
+		while (p != wpath)
+			if (*(--p) == L'/' || *p == L'\\')
+				break; /* found start of last component */
+
+		if (p != wpath && (last_component = find_last_dir_sep(path))) {
+			last_component++; /* skip directory separator */
+			*p = L'\0';
+			h = CreateFileW(wpath, 0, FILE_SHARE_READ |
+					FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+					NULL, OPEN_EXISTING,
+					FILE_FLAG_BACKUP_SEMANTICS, NULL);
+		}
+	}
+
+	if (h == INVALID_HANDLE_VALUE)
+		return NULL;
+
+	ret = GetFinalPathNameByHandleW(h, wpath, ARRAY_SIZE(wpath), 0);
+	CloseHandle(h);
+	if (!ret || ret >= ARRAY_SIZE(wpath))
+		return NULL;
+
+	len = wcslen(wpath) * 3;
+	strbuf_grow(resolved, len);
+	len = xwcstoutf(resolved->buf, normalize_ntpath(wpath), len);
+	if (len < 0)
+		return NULL;
+	resolved->len = len;
+
+	if (last_component) {
+		/* Use forward-slash, like `normalize_ntpath()` */
+		strbuf_addch(resolved, '/');
+		strbuf_addstr(resolved, last_component);
+	}
+
+	return resolved->buf;
+
+}
+
 char *mingw_getcwd(char *pointer, int len)
 {
 	wchar_t cwd[MAX_PATH], wpointer[MAX_PATH];
diff --git a/compat/mingw.h b/compat/mingw.h
index 494cc8de92221b..05d99ef40c55b4 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -449,6 +449,9 @@ static inline void convert_slashes(char *path)
 #define PATH_SEP ';'
 char *mingw_query_user_email(void);
 #define query_user_email mingw_query_user_email
+struct strbuf;
+char *mingw_strbuf_realpath(struct strbuf *resolved, const char *path);
+#define platform_strbuf_realpath mingw_strbuf_realpath
 #if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800)
 #define PRIuMAX "I64u"
 #define PRId64 "I64d"
diff --git a/t/t3700-add.sh b/t/t3700-add.sh
index f4506fff8d2512..f5e8fb5ca21d49 100755
--- a/t/t3700-add.sh
+++ b/t/t3700-add.sh
@@ -470,7 +470,7 @@ test_expect_success CASE_INSENSITIVE_FS 'path is case-insensitive' '
 	git add "$downcased"
 '
 
-test_expect_failure MINGW 'can add files via NTFS junctions' '
+test_expect_success MINGW 'can add files via NTFS junctions' '
 	test_when_finished "cmd //c rmdir junction && rm -rf target" &&
 	test_create_repo target &&
 	cmd //c "mklink /j junction target" &&
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 4a61f2c901ea3d..92ca516ec296d4 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -71,6 +71,13 @@ test_expect_success 'clone respects GIT_WORK_TREE' '
 
 '
 
+test_expect_success CASE_INSENSITIVE_FS 'core.worktree is not added due to path case' '
+
+	mkdir UPPERCASE &&
+	git clone src "$(pwd)/uppercase" &&
+	test "unset" = "$(git -C UPPERCASE config --default unset core.worktree)"
+'
+
 test_expect_success 'clone from hooks' '
 
 	test_create_repo r0 &&

From 6a715ef263aeb68b6d6640aabce5aca5f00f4bfe Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 25 Aug 2020 12:13:26 +0200
Subject: [PATCH 047/265] mingw: ignore HOMEDRIVE/HOMEPATH if it points to
 Windows' system directory

Internally, Git expects the environment variable `HOME` to be set, and
to point to the current user's home directory.

This environment variable is not set by default on Windows, and
therefore Git tries its best to construct one if it finds `HOME` unset.

There are actually two different approaches Git tries: first, it looks
at `HOMEDRIVE`/`HOMEPATH` because this is widely used in corporate
environments with roaming profiles, and a user generally wants their
global Git settings to be in a roaming profile.

Only when `HOMEDRIVE`/`HOMEPATH` is either unset or does not point to a
valid location, Git will fall back to using `USERPROFILE` instead.

However, starting with Windows Vista, for secondary logons and services,
the environment variables `HOMEDRIVE`/`HOMEPATH` point to Windows'
system directory (usually `C:\Windows\system32`).

That is undesirable, and that location is usually write-protected anyway.

So let's verify that the `HOMEDRIVE`/`HOMEPATH` combo does not point to
Windows' system directory before using it, falling back to `USERPROFILE`
if it does.

This fixes git-for-windows#2709

Initial-Path-by: Ivan Pozdeev <vano@mail.mipt.ru>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index d27184f0c6c319..d2ef20d90c4ba0 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2634,6 +2634,18 @@ static size_t append_system_bin_dirs(char *path, size_t size)
 }
 #endif
 
+static int is_system32_path(const char *path)
+{
+	WCHAR system32[MAX_PATH], wpath[MAX_PATH];
+
+	if (xutftowcs_path(wpath, path) < 0 ||
+	    !GetSystemDirectoryW(system32, ARRAY_SIZE(system32)) ||
+	    _wcsicmp(system32, wpath))
+		return 0;
+
+	return 1;
+}
+
 static void setup_windows_environment(void)
 {
 	char *tmp = getenv("TMPDIR");
@@ -2674,7 +2686,8 @@ static void setup_windows_environment(void)
 			strbuf_addstr(&buf, tmp);
 			if ((tmp = getenv("HOMEPATH"))) {
 				strbuf_addstr(&buf, tmp);
-				if (is_directory(buf.buf))
+				if (!is_system32_path(buf.buf) &&
+				    is_directory(buf.buf))
 					setenv("HOME", buf.buf, 1);
 				else
 					tmp = NULL; /* use $USERPROFILE */

From b1d1c48ec8fa6e6e20a147c247393a3a73db2cf6 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 4 Mar 2020 21:55:28 +0100
Subject: [PATCH 048/265] http: use new "best effort" strategy for Secure
 Channel revoke checking
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The native Windows HTTPS backend is based on Secure Channel which lets
the caller decide how to handle revocation checking problems caused by
missing information in the certificate or offline CRL distribution
points.

Unfortunately, cURL chose to handle these problems differently than
OpenSSL by default: while OpenSSL happily ignores those problems
(essentially saying "¯\_(ツ)_/¯"), the Secure Channel backend will error
out instead.

As a remedy, the "no revoke" mode was introduced, which turns off
revocation checking altogether. This is a bit heavy-handed. We support
this via the `http.schannelCheckRevoke` setting.

In https://github.com/curl/curl/pull/4981, we contributed an opt-in
"best effort" strategy that emulates what OpenSSL seems to do.

In Git for Windows, we actually want this to be the default. This patch
makes it so, introducing it as a new value for the
`http.schannelCheckRevoke" setting, which now becmes a tristate: it
accepts the values "false", "true" or "best-effort" (defaulting to the
last one).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/config/http.txt | 12 +++++++-----
 http.c                        | 26 ++++++++++++++++++++++----
 2 files changed, 29 insertions(+), 9 deletions(-)

diff --git a/Documentation/config/http.txt b/Documentation/config/http.txt
index 7003661c0dba39..24f51879392040 100644
--- a/Documentation/config/http.txt
+++ b/Documentation/config/http.txt
@@ -173,11 +173,13 @@ http.sslBackend::
 
 http.schannelCheckRevoke::
 	Used to enforce or disable certificate revocation checks in cURL
-	when http.sslBackend is set to "schannel". Defaults to `true` if
-	unset. Only necessary to disable this if Git consistently errors
-	and the message is about checking the revocation status of a
-	certificate. This option is ignored if cURL lacks support for
-	setting the relevant SSL option at runtime.
+	when http.sslBackend is set to "schannel" via "true" and "false",
+	respectively. Another accepted value is "best-effort" (the default)
+	in which case revocation checks are performed, but errors due to
+	revocation list distribution points that are offline are silently
+	ignored, as well as errors due to certificates missing revocation
+	list distribution points. This option is ignored if cURL lacks
+	support for setting the relevant SSL option at runtime.
 
 http.schannelUseSSLCAInfo::
 	As of cURL v7.60.0, the Secure Channel backend can use the
diff --git a/http.c b/http.c
index 229da4d14882d9..b980f2f2e9d6bc 100644
--- a/http.c
+++ b/http.c
@@ -134,7 +134,13 @@ static char *cached_accept_language;
 
 static char *http_ssl_backend;
 
-static int http_schannel_check_revoke = 1;
+static int http_schannel_check_revoke_mode =
+#ifdef CURLSSLOPT_REVOKE_BEST_EFFORT
+	CURLSSLOPT_REVOKE_BEST_EFFORT;
+#else
+	CURLSSLOPT_NO_REVOKE;
+#endif
+
 /*
  * With the backend being set to `schannel`, setting sslCAinfo would override
  * the Certificate Store in cURL v7.60.0 and later, which is not what we want
@@ -285,7 +291,19 @@ static int http_options(const char *var, const char *value, void *cb)
 	}
 
 	if (!strcmp("http.schannelcheckrevoke", var)) {
-		http_schannel_check_revoke = git_config_bool(var, value);
+		if (value && !strcmp(value, "best-effort")) {
+			http_schannel_check_revoke_mode =
+#ifdef CURLSSLOPT_REVOKE_BEST_EFFORT
+				CURLSSLOPT_REVOKE_BEST_EFFORT;
+#else
+				CURLSSLOPT_NO_REVOKE;
+			warning(_("%s=%s unsupported by current cURL"),
+				var, value);
+#endif
+		} else
+			http_schannel_check_revoke_mode =
+				(git_config_bool(var, value) ?
+				 0 : CURLSSLOPT_NO_REVOKE);
 		return 0;
 	}
 
@@ -805,9 +823,9 @@ static CURL *get_curl_handle(void)
 #endif
 
 	if (http_ssl_backend && !strcmp("schannel", http_ssl_backend) &&
-	    !http_schannel_check_revoke) {
+	    http_schannel_check_revoke_mode) {
 #ifdef GIT_CURL_HAVE_CURLSSLOPT_NO_REVOKE
-		curl_easy_setopt(result, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE);
+		curl_easy_setopt(result, CURLOPT_SSL_OPTIONS, http_schannel_check_revoke_mode);
 #else
 		warning(_("CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0"));
 #endif

From b049a2b92151c9cef1369b74702a74de7a750580 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Sat, 9 May 2020 19:24:23 +0200
Subject: [PATCH 049/265] t5505/t5516: fix white-space around redirectors

The convention in Git project's shell scripts is to have white-space
_before_, but not _after_ the `>` (or `<`).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t5505-remote.sh     |  6 +++---
 t/t5516-fetch-push.sh | 10 +++++-----
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 2accb3840d27b6..6a6cdf04ef29b0 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -735,8 +735,8 @@ test_expect_success '"remote show" does not show symbolic refs' '
 	(
 		cd three &&
 		git remote show origin >output &&
-		! grep "^ *HEAD$" < output &&
-		! grep -i stale < output
+		! grep "^ *HEAD$" <output &&
+		! grep -i stale <output
 	)
 '
 
@@ -944,7 +944,7 @@ test_expect_success 'migrate a remote from named file in $GIT_DIR/branches (2)'
 		cd seven &&
 		git remote rm origin &&
 		mkdir -p .git/branches &&
-		echo "quux#foom" > .git/branches/origin &&
+		echo "quux#foom" >.git/branches/origin &&
 		git remote rename origin origin &&
 		test_path_is_missing .git/branches/origin &&
 		test "$(git config remote.origin.url)" = "quux" &&
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 0b860a45ccc714..b5af149426d9fc 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -917,7 +917,7 @@ test_expect_success 'fetch with branches' '
 	git branch second $the_first_commit &&
 	git checkout second &&
 	mkdir -p testrepo/.git/branches &&
-	echo ".." > testrepo/.git/branches/branch1 &&
+	echo ".." >testrepo/.git/branches/branch1 &&
 	(
 		cd testrepo &&
 		git fetch branch1 &&
@@ -931,7 +931,7 @@ test_expect_success 'fetch with branches' '
 test_expect_success 'fetch with branches containing #' '
 	mk_empty testrepo &&
 	mkdir -p testrepo/.git/branches &&
-	echo "..#second" > testrepo/.git/branches/branch2 &&
+	echo "..#second" >testrepo/.git/branches/branch2 &&
 	(
 		cd testrepo &&
 		git fetch branch2 &&
@@ -946,7 +946,7 @@ test_expect_success 'push with branches' '
 	mk_empty testrepo &&
 	git checkout second &&
 	mkdir -p .git/branches &&
-	echo "testrepo" > .git/branches/branch1 &&
+	echo "testrepo" >.git/branches/branch1 &&
 	git push branch1 &&
 	(
 		cd testrepo &&
@@ -959,7 +959,7 @@ test_expect_success 'push with branches' '
 test_expect_success 'push with branches containing #' '
 	mk_empty testrepo &&
 	mkdir -p .git/branches &&
-	echo "testrepo#branch3" > .git/branches/branch2 &&
+	echo "testrepo#branch3" >.git/branches/branch2 &&
 	git push branch2 &&
 	(
 		cd testrepo &&
@@ -1487,7 +1487,7 @@ EOF
 	git init no-thin &&
 	git --git-dir=no-thin/.git config receive.unpacklimit 0 &&
 	git push no-thin/.git refs/heads/main:refs/heads/foo &&
-	echo modified >> path1 &&
+	echo modified >>path1 &&
 	git commit -am modified &&
 	git repack -adf &&
 	rcvpck="git receive-pack --reject-thin-pack-for-testing" &&

From 692334669911785fced89a96af8415aac88c1c32 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Sat, 12 Sep 2015 12:25:47 +0200
Subject: [PATCH 050/265] t3701: verify that we can add *lots* of files
 interactively

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t3701-add-interactive.sh | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 94537a6b40a136..369203f7e0cfb3 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -991,6 +991,27 @@ test_expect_success 'checkout -p patch editing of added file' '
 	)
 '
 
+test_expect_success EXPENSIVE 'add -i with a lot of files' '
+	git reset --hard &&
+	x160=0123456789012345678901234567890123456789 &&
+	x160=$x160$x160$x160$x160 &&
+	y= &&
+	i=0 &&
+	while test $i -le 200
+	do
+		name=$(printf "%s%03d" $x160 $i) &&
+		echo $name >$name &&
+		git add -N $name &&
+		y="${y}y$LF" &&
+		i=$(($i+1)) ||
+		break
+	done &&
+	echo "$y" | git add -p -- . &&
+	git diff --cached >staged &&
+	test_line_count = 1407 staged &&
+	git reset --hard
+'
+
 test_expect_success 'show help from add--helper' '
 	git reset --hard &&
 	cat >expect <<-EOF &&

From 8d2f0b48b69e1c87f9993440756a0fc25f8f7d2b Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 2 Jul 2020 16:35:05 +0200
Subject: [PATCH 051/265] git add -i: handle CR/LF line endings in the
 interactive input

As of Git for Windows v2.27.0, there is an option to use Windows'
newly-introduced Pseudo Console support. When running an interactive add
operation with this support enabled, Git will receive CR/LF line
endings.

Therefore, let's not pretend that we are expecting Unix line endings.

This fixes https://github.com/git-for-windows/git/issues/2729

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 prompt.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/prompt.c b/prompt.c
index 50df17279d1d5b..c546e4373774d6 100644
--- a/prompt.c
+++ b/prompt.c
@@ -77,7 +77,7 @@ int git_read_line_interactively(struct strbuf *line)
 	int ret;
 
 	fflush(stdout);
-	ret = strbuf_getline_lf(line, stdin);
+	ret = strbuf_getline(line, stdin);
 	if (ret != EOF)
 		strbuf_trim_trailing_newline(line);
 

From 32fe68b3a50314402aaa6a3ef144fcf32378cb26 Mon Sep 17 00:00:00 2001
From: Luke Bonanomi <lbonanomi@gmail.com>
Date: Wed, 24 Jun 2020 07:45:52 -0400
Subject: [PATCH 052/265] commit: accept "scissors" with CR/LF line endings

This change enhances `git commit --cleanup=scissors` by detecting
scissors lines ending in either LF (UNIX-style) or CR/LF (DOS-style).

Regression tests are included to specifically test for trailing
comments after a CR/LF-terminated scissors line.

Signed-off-by: Luke Bonanomi <lbonanomi@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t7502-commit-porcelain.sh | 42 +++++++++++++++++++++++++++++++++++++
 wt-status.c                 | 13 +++++++++---
 2 files changed, 52 insertions(+), 3 deletions(-)

diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 38a532d81ccd06..b643375fa59da0 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -586,6 +586,48 @@ test_expect_success 'cleanup commit messages (scissors option,-F,-e, scissors on
 	test_must_be_empty actual
 '
 
+test_expect_success 'helper-editor' '
+
+	write_script lf-to-crlf.sh <<-\EOF
+	sed "s/\$/Q/" <"$1" | tr Q "\\015" >"$1".new &&
+	mv -f "$1".new "$1"
+	EOF
+'
+
+test_expect_success 'cleanup commit messages (scissors option,-F,-e, CR/LF line endings)' '
+
+	test_config core.editor "\"$PWD/lf-to-crlf.sh\"" &&
+	scissors="# ------------------------ >8 ------------------------" &&
+
+	test_write_lines >text \
+	"# Keep this comment" "" " $scissors" \
+	"# Keep this comment, too" "$scissors" \
+	"# Remove this comment" "$scissors" \
+	"Remove this comment, too" &&
+
+	test_write_lines >expect \
+	"# Keep this comment" "" " $scissors" \
+	"# Keep this comment, too" &&
+
+	git commit --cleanup=scissors -e -F text --allow-empty &&
+	git cat-file -p HEAD >raw &&
+	sed -e "1,/^\$/d" raw >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'cleanup commit messages (scissors option,-F,-e, scissors on first line, CR/LF line endings)' '
+
+	scissors="# ------------------------ >8 ------------------------" &&
+	test_write_lines >text \
+	"$scissors" \
+	"# Remove this comment and any following lines" &&
+	cp text /tmp/test2-text &&
+	git commit --cleanup=scissors -e -F text --allow-empty --allow-empty-message &&
+	git cat-file -p HEAD >raw &&
+	sed -e "1,/^\$/d" raw >actual &&
+	test_must_be_empty actual
+'
+
 test_expect_success 'cleanup commit messages (strip option,-F)' '
 
 	echo >>negative &&
diff --git a/wt-status.c b/wt-status.c
index d33f9272b724b5..24eb51da849258 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -22,7 +22,7 @@
 #define AB_DELAY_WARNING_IN_MS (2 * 1000)
 
 static const char cut_line[] =
-"------------------------ >8 ------------------------\n";
+"------------------------ >8 ------------------------";
 
 static char default_wt_status_colors[][COLOR_MAXLEN] = {
 	GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
@@ -1069,15 +1069,22 @@ static void wt_longstatus_print_other(struct wt_status *s,
 	status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
 }
 
+static inline int starts_with_newline(const char *p)
+{
+    return *p == '\n' || (*p == '\r' && p[1] == '\n');
+}
+
 size_t wt_status_locate_end(const char *s, size_t len)
 {
 	const char *p;
 	struct strbuf pattern = STRBUF_INIT;
 
 	strbuf_addf(&pattern, "\n%c %s", comment_line_char, cut_line);
-	if (starts_with(s, pattern.buf + 1))
+	if (starts_with(s, pattern.buf + 1) &&
+	    starts_with_newline(s + pattern.len - 1))
 		len = 0;
-	else if ((p = strstr(s, pattern.buf)))
+	else if ((p = strstr(s, pattern.buf)) &&
+		 starts_with_newline(p + pattern.len))
 		len = p - s + 1;
 	strbuf_release(&pattern);
 	return len;

From c34ed53fecb8931c06dcc10a81373a4895c83731 Mon Sep 17 00:00:00 2001
From: Jens Glathe <jens.glathe@oldschoolsolutions.biz>
Date: Tue, 2 Jun 2020 12:12:25 +0200
Subject: [PATCH 053/265] t0014: fix indentation

For some reason, this test case was indented with 4 spaces instead of 1
horizontal tab. The other test cases in the same test script are fine.

Signed-off-by: Jens Glathe <jens.glathe@oldschoolsolutions.biz>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t0014-alias.sh | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 8d3d9144c06d61..288e08299abc4e 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -38,10 +38,10 @@ test_expect_success 'looping aliases - internal execution' '
 #'
 
 test_expect_success 'run-command formats empty args properly' '
-    test_must_fail env GIT_TRACE=1 git frotz a "" b " " c 2>actual.raw &&
-    sed -ne "/run_command:/s/.*trace: run_command: //p" actual.raw >actual &&
-    echo "git-frotz a '\'''\'' b '\'' '\'' c" >expect &&
-    test_cmp expect actual
+	test_must_fail env GIT_TRACE=1 git frotz a "" b " " c 2>actual.raw &&
+	sed -ne "/run_command:/s/.*trace: run_command: //p" actual.raw >actual &&
+	echo "git-frotz a '\'''\'' b '\'' '\'' c" >expect &&
+	test_cmp expect actual
 '
 
 test_done

From 2394a29eed0759ead69480642284d8e42fb199d1 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 12 Aug 2020 15:06:17 +0000
Subject: [PATCH 054/265] git-gui: accommodate for intent-to-add files

As of Git v2.28.0, the diff for files staged via `git add -N` marks them
as new files. Git GUI was ill-prepared for that, and this patch teaches
Git GUI about them.

Please note that this will not even fix things with v2.28.0, as the
`rp/apply-cached-with-i-t-a` patches are required on Git's side, too.

This fixes https://github.com/git-for-windows/git/issues/2779

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Pratyush Yadav <me@yadavpratyush.com>
---
 git-gui/git-gui.sh   |  2 ++
 git-gui/lib/diff.tcl | 12 ++++++++----
 2 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 201524c34edac0..3fe9bd9f4798b8 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -2087,6 +2087,7 @@ set all_icons(U$ui_index)   file_merge
 set all_icons(T$ui_index)   file_statechange
 
 set all_icons(_$ui_workdir) file_plain
+set all_icons(A$ui_workdir) file_plain
 set all_icons(M$ui_workdir) file_mod
 set all_icons(D$ui_workdir) file_question
 set all_icons(U$ui_workdir) file_merge
@@ -2113,6 +2114,7 @@ foreach i {
 		{A_ {mc "Staged for commit"}}
 		{AM {mc "Portions staged for commit"}}
 		{AD {mc "Staged for commit, missing"}}
+		{AA {mc "Intended to be added"}}
 
 		{_D {mc "Missing"}}
 		{D_ {mc "Staged for removal"}}
diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl
index 871ad488c2a1c0..36d3715f7b25a2 100644
--- a/git-gui/lib/diff.tcl
+++ b/git-gui/lib/diff.tcl
@@ -582,7 +582,8 @@ proc apply_or_revert_hunk {x y revert} {
 	if {$current_diff_side eq $ui_index} {
 		set failed_msg [mc "Failed to unstage selected hunk."]
 		lappend apply_cmd --reverse --cached
-		if {[string index $mi 0] ne {M}} {
+		set file_state [string index $mi 0]
+		if {$file_state ne {M} && $file_state ne {A}} {
 			unlock_index
 			return
 		}
@@ -595,7 +596,8 @@ proc apply_or_revert_hunk {x y revert} {
 			lappend apply_cmd --cached
 		}
 
-		if {[string index $mi 1] ne {M}} {
+		set file_state [string index $mi 1]
+		if {$file_state ne {M} && $file_state ne {A}} {
 			unlock_index
 			return
 		}
@@ -687,7 +689,8 @@ proc apply_or_revert_range_or_line {x y revert} {
 		set failed_msg [mc "Failed to unstage selected line."]
 		set to_context {+}
 		lappend apply_cmd --reverse --cached
-		if {[string index $mi 0] ne {M}} {
+		set file_state [string index $mi 0]
+		if {$file_state ne {M} && $file_state ne {A}} {
 			unlock_index
 			return
 		}
@@ -702,7 +705,8 @@ proc apply_or_revert_range_or_line {x y revert} {
 			lappend apply_cmd --cached
 		}
 
-		if {[string index $mi 1] ne {M}} {
+		set file_state [string index $mi 1]
+		if {$file_state ne {M} && $file_state ne {A}} {
 			unlock_index
 			return
 		}

From 55a7ca0c52efc67c03ec5a44ab6c2139118463a8 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 3 Dec 2020 22:54:11 +0100
Subject: [PATCH 055/265] git maintenance: avoid console window in scheduled
 tasks on Windows

We just introduced a helper to avoid showing a console window when the
scheduled task runs `git.exe`. Let's actually use it.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
---
 builtin/gc.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/gc.c b/builtin/gc.c
index b335cffa33561f..08198f33223d0f 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -1961,7 +1961,7 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
 	      "</Settings>\n"
 	      "<Actions Context=\"Author\">\n"
 	      "<Exec>\n"
-	      "<Command>\"%s\\git.exe\"</Command>\n"
+	      "<Command>\"%s\\headless-git.exe\"</Command>\n"
 	      "<Arguments>--exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%s</Arguments>\n"
 	      "</Exec>\n"
 	      "</Actions>\n"

From 0efae65d8646abd43b3f70faa80089501af929b3 Mon Sep 17 00:00:00 2001
From: Philip Oakley <philipoakley@iee.email>
Date: Sun, 6 Oct 2019 18:43:57 +0100
Subject: [PATCH 056/265] vcpkg_install: add comment regarding slow network
 connections

The vcpkg downloads may not succeed. Warn careful readers of the time out.

A simple retry will usually resolve the issue.

Signed-off-by: Philip Oakley <philipoakley@iee.email>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/vcbuild/vcpkg_install.bat | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/compat/vcbuild/vcpkg_install.bat b/compat/vcbuild/vcpkg_install.bat
index bcbbf536af3141..8330d8120fb511 100644
--- a/compat/vcbuild/vcpkg_install.bat
+++ b/compat/vcbuild/vcpkg_install.bat
@@ -80,6 +80,12 @@ REM ================================================================
 :sub__install_one
 	echo     Installing package %1...
 
+	REM vcpkg may not be reliable on slow, intermittent or proxy
+	REM connections, see e.g.
+	REM https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/4a8f7be5-5e15-4213-a7bb-ddf424a954e6/winhttpsendrequest-ends-with-12002-errorhttptimeout-after-21-seconds-no-matter-what-timeout?forum=windowssdk
+	REM which explains the hidden 21 second timeout
+	REM (last post by Dave : Microsoft - Windows Networking team)
+
 	.\vcpkg.exe install %1:%arch%
 	IF ERRORLEVEL 1 ( EXIT /B 1 )
 

From f7dcb6cb263431e50b7868f754c53b34af52cddd Mon Sep 17 00:00:00 2001
From: Dennis Ameling <dennis@dennisameling.com>
Date: Sun, 6 Dec 2020 18:39:26 +0100
Subject: [PATCH 057/265] Add schannel to curl installation

Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
---
 compat/vcbuild/vcpkg_install.bat | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/compat/vcbuild/vcpkg_install.bat b/compat/vcbuild/vcpkg_install.bat
index 8da212487ae97d..575c65c20ba307 100644
--- a/compat/vcbuild/vcpkg_install.bat
+++ b/compat/vcbuild/vcpkg_install.bat
@@ -127,5 +127,5 @@ set features=
 goto :EOF
 
 :curl_features
-set features=[core,openssl]
+set features=[core,openssl,schannel]
 goto :EOF

From 3aed9c49be4d2201b06642aed8b7acde85103f4f Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 10 Apr 2020 23:39:40 +0200
Subject: [PATCH 058/265] Add a GitHub workflow to generate Git for Windows'
 Pacman package

Git for Windows uses MSYS2 as base system, and therefore the Git
binaries are bundled as Pacman package.

This workflow allows building the 64-bit version of this package (which
is called `mingw-w64-x86_64-git`).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 141 ++++++++++++++++++++++++++++
 1 file changed, 141 insertions(+)
 create mode 100644 .github/workflows/git-artifacts.yml

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
new file mode 100644
index 00000000000000..837015d7f7752e
--- /dev/null
+++ b/.github/workflows/git-artifacts.yml
@@ -0,0 +1,141 @@
+name: mingw-w64-x86_64-git
+
+on:
+  # This workflow can be triggered manually in the Actions tab, see
+  # https://github.blog/changelog/2020-07-06-github-actions-manual-triggers-with-workflow_dispatch/
+  - workflow_dispatch
+
+env:
+  HOME: "${{github.workspace}}\\home"
+  MSYSTEM: MINGW64
+  USERPROFILE: "${{github.workspace}}\\home"
+
+jobs:
+  bundle-artifacts:
+    runs-on: windows-latest
+    steps:
+      - name: Configure user
+        shell: bash
+        run:
+          USER_NAME="${{github.actor}}" &&
+          USER_EMAIL="${{github.actor}}@users.noreply.github.com" &&
+          mkdir "$HOME" &&
+          git config --global user.name "$USER_NAME" &&
+          git config --global user.email "$USER_EMAIL" &&
+          echo "PACKAGER=$USER_NAME <$USER_EMAIL>" >>$GITHUB_ENV
+      - name: Download git-sdk-64-build-installers
+        shell: bash
+        run: |
+          # Use Git Bash to download and unpack the artifact
+
+          ## Get artifact
+          urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
+          id=$(curl "$urlbase?definitions=29&statusFilter=completed&resultFilter=succeeded&\$top=1" |
+            jq -r '.value[0].id')
+          download_url=$(curl "$urlbase/$id/artifacts" |
+            jq -r '.value[] | select(.name == "git-sdk-64-build-installers").resource.downloadUrl')
+
+          curl -o artifacts.zip "$download_url"
+
+          ## Unpack artifact
+          unzip artifacts.zip
+      - name: Clone build-extra
+        shell: bash
+        run: |
+          d=git-sdk-64-build-installers/usr/src/build-extra &&
+          git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d
+      - name: Generate bundle artifacts
+        shell: powershell
+        run: |
+          & .\git-sdk-64-build-installers\git-cmd.exe --command=usr\bin\bash.exe -lc @"
+            printf '#!/bin/sh\n\nexec /mingw64/bin/git.exe "`$@"\n' >/usr/bin/git &&
+            mkdir -p bundle-artifacts &&
+
+            git -c init.defaultBranch=main init --bare &&
+            git remote add -f origin https://github.com/git-for-windows/git &&
+            git fetch https://github.com/${{github.repository}} ${{github.ref}}:${{github.ref}} &&
+
+            tag_name=\"`$(git describe --match 'v[0-9]*' FETCH_HEAD)-`$(date +%Y%m%d%H%M%S)\" &&
+            echo \"prerelease-`${tag_name#v}\" >bundle-artifacts/ver &&
+            echo \"`${tag_name#v}\" >bundle-artifacts/display_version &&
+            echo \"`$tag_name\" >bundle-artifacts/next_version &&
+            git tag -m \"Snapshot build\" \"`$tag_name\" FETCH_HEAD &&
+            git bundle create bundle-artifacts/git.bundle origin/main..\"`$tag_name\" &&
+
+            sh -x /usr/src/build-extra/please.sh mention feature \"Snapshot of `$(git show -s  --pretty='tformat:%h (%s, %ad)' --date=short FETCH_HEAD)\" &&
+            git -C /usr/src/build-extra bundle create \"`$PWD/bundle-artifacts/build-extra.bundle\" origin/main..main
+          "@
+      - name: 'Publish Pipeline Artifact: bundle-artifacts'
+        uses: actions/upload-artifact@v1
+        with:
+          name: bundle-artifacts
+          path: bundle-artifacts
+  pkg-x86_64:
+    runs-on: windows-latest
+    needs: bundle-artifacts
+    steps:
+      - name: Configure user
+        shell: bash
+        run:
+          USER_NAME="${{github.actor}}" &&
+          USER_EMAIL="${{github.actor}}@users.noreply.github.com" &&
+          mkdir "$HOME" &&
+          git config --global user.name "$USER_NAME" &&
+          git config --global user.email "$USER_EMAIL" &&
+          echo "PACKAGER=$USER_NAME <$USER_EMAIL>" >>$GITHUB_ENV
+      - name: Download git-sdk-64-makepkg-git
+        shell: bash
+        run: |
+          # Use Git Bash to download and unpack the artifact
+
+          ## Get artifact
+          urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
+          id=${{ needs.bundle-artifacts.outputs.latest-sdk64-extra-build-id }}
+          download_url="$(curl "$urlbase/$id/artifacts" |
+            jq -r '.value[] | select(.name == "git-sdk-64-makepkg-git").resource.downloadUrl')"
+
+          curl -o artifacts.zip "$download_url"
+
+          ## Unpack artifact
+          unzip artifacts.zip
+      - name: Download bundle-artifacts
+        uses: actions/download-artifact@v1
+        with:
+          name: bundle-artifacts
+          path: bundle-artifacts
+      - name: Clone and update build-extra
+        shell: bash
+        run: |
+          d=git-sdk-64-makepkg-git/usr/src/build-extra &&
+          git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d &&
+          git -C $d pull "$PWD"/bundle-artifacts/build-extra.bundle main
+      - name: Check out git/git
+        shell: bash
+        run: |
+          git -c init.defaultBranch=main init &&
+          git remote add -f origin https://github.com/git-for-windows/git &&
+          git fetch --tags bundle-artifacts/git.bundle $(cat bundle-artifacts/next_version) &&
+          git reset --hard $(cat bundle-artifacts/next_version)
+      - name: Build mingw-w64-x86_64-git
+        shell: powershell
+        run: |
+          & git-sdk-64-makepkg-git\usr\bin\sh.exe -lc @"
+            set -x
+            # Let `cv2pdb` find the DLLs
+            PATH=\"`$PATH:/c/Program Files/Microsoft Visual Studio/2022/Enterprise/Common7/IDE/:/C/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64\"
+            type -p mspdb140.dll || exit 1
+            sh -x /usr/src/build-extra/please.sh build-mingw-w64-git --only-64-bit --build-src-pkg -o artifacts HEAD &&
+            cp bundle-artifacts/ver artifacts/ &&
+
+            b=`$PWD/artifacts &&
+            version=`$(cat bundle-artifacts/next_version) &&
+            (cd /usr/src/MINGW-packages/mingw-w64-git &&
+            cp PKGBUILD.`$version PKGBUILD &&
+            git commit -s -m \"mingw-w64-git: new version (`$version)\" PKGBUILD &&
+            git bundle create \"`$b\"/MINGW-packages.bundle origin/main..main)
+          "@
+      - name: Publish mingw-w64-x86_64-git
+        uses: actions/upload-artifact@v1
+        with:
+          name: pkg-x86_64
+          path: artifacts

From 053521a1dcd40c9f46fad90e798177ebc365dff9 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Sun, 12 Apr 2020 00:24:49 +0200
Subject: [PATCH 059/265] git-artifacts: if GPG secrets are available, use them

This expects the `GPGKEY` and `PRIVGPGKEY` secrets to be set in the
respective GitHub repository.

The `GPGKEY` value should be of the form

	<short-key> --passphrase <pass> --yes --batch --no-tty --pinentry-mode loopback --digest-algo SHA256

and the `PRIVGPGKEY` should be generated via

	gpg --export-secret-keys | base64 | tr '\n' %

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 44 ++++++++++++++++++++++++++++-
 1 file changed, 43 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index 837015d7f7752e..b5dbf6c4bcf5a4 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -6,6 +6,7 @@ on:
   - workflow_dispatch
 
 env:
+  GPG_OPTIONS: "--batch --yes --no-tty --list-options no-show-photos --verify-options no-show-photos --pinentry-mode loopback"
   HOME: "${{github.workspace}}\\home"
   MSYSTEM: MINGW64
   USERPROFILE: "${{github.workspace}}\\home"
@@ -44,7 +45,21 @@ jobs:
         run: |
           d=git-sdk-64-build-installers/usr/src/build-extra &&
           git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d
+      - name: Prepare home directory for GPG signing
+        if: env.GPGKEY != ''
+        shell: bash
+        run: |
+          echo '${{secrets.PRIVGPGKEY}}' | tr % '\n' | gpg $GPG_OPTIONS --import &&
+          mkdir -p home &&
+          git config --global gpg.program "$PWD/git-sdk-64-build-installers/usr/src/build-extra/gnupg-with-gpgkey.sh" &&
+          info="$(gpg --list-keys --with-colons "${GPGKEY%% *}" | cut -d : -f 1,10 | sed -n '/^uid/{s|uid:||p;q}')" &&
+          git config --global user.name "${info% <*}" &&
+          git config --global user.email "<${info#*<}"
+        env:
+          GPGKEY: ${{secrets.GPGKEY}}
       - name: Generate bundle artifacts
+        env:
+          GPGKEY: ${{secrets.GPGKEY}}
         shell: powershell
         run: |
           & .\git-sdk-64-build-installers\git-cmd.exe --command=usr\bin\bash.exe -lc @"
@@ -59,12 +74,16 @@ jobs:
             echo \"prerelease-`${tag_name#v}\" >bundle-artifacts/ver &&
             echo \"`${tag_name#v}\" >bundle-artifacts/display_version &&
             echo \"`$tag_name\" >bundle-artifacts/next_version &&
-            git tag -m \"Snapshot build\" \"`$tag_name\" FETCH_HEAD &&
+            git tag `$(test -z \"`$GPGKEY\" || echo \" -s\") -m \"Snapshot build\" \"`$tag_name\" FETCH_HEAD &&
             git bundle create bundle-artifacts/git.bundle origin/main..\"`$tag_name\" &&
 
             sh -x /usr/src/build-extra/please.sh mention feature \"Snapshot of `$(git show -s  --pretty='tformat:%h (%s, %ad)' --date=short FETCH_HEAD)\" &&
             git -C /usr/src/build-extra bundle create \"`$PWD/bundle-artifacts/build-extra.bundle\" origin/main..main
           "@
+      - name: Clean up temporary files
+        if: always()
+        shell: bash
+        run: rm -rf home
       - name: 'Publish Pipeline Artifact: bundle-artifacts'
         uses: actions/upload-artifact@v1
         with:
@@ -116,7 +135,19 @@ jobs:
           git remote add -f origin https://github.com/git-for-windows/git &&
           git fetch --tags bundle-artifacts/git.bundle $(cat bundle-artifacts/next_version) &&
           git reset --hard $(cat bundle-artifacts/next_version)
+      - name: Prepare home directory for GPG signing
+        if: env.GPGKEY != ''
+        shell: bash
+        run: |
+          echo '${{secrets.PRIVGPGKEY}}' | tr % '\n' | gpg $GPG_OPTIONS --import &&
+          info="$(gpg --list-keys --with-colons "${GPGKEY%% *}" | cut -d : -f 1,10 | sed -n '/^uid/{s|uid:||p;q}')" &&
+          git config --global user.name "${info% <*}" &&
+          git config --global user.email "<${info#*<}"
+        env:
+          GPGKEY: ${{secrets.GPGKEY}}
       - name: Build mingw-w64-x86_64-git
+        env:
+          GPGKEY: "${{secrets.GPGKEY}}"
         shell: powershell
         run: |
           & git-sdk-64-makepkg-git\usr\bin\sh.exe -lc @"
@@ -126,6 +157,13 @@ jobs:
             type -p mspdb140.dll || exit 1
             sh -x /usr/src/build-extra/please.sh build-mingw-w64-git --only-64-bit --build-src-pkg -o artifacts HEAD &&
             cp bundle-artifacts/ver artifacts/ &&
+            if test -n \"`$GPGKEY\"
+            then
+              for tar in artifacts/*.tar*
+              do
+                /usr/src/build-extra/gnupg-with-gpgkey.sh --detach-sign --no-armor `$tar
+              done
+            fi &&
 
             b=`$PWD/artifacts &&
             version=`$(cat bundle-artifacts/next_version) &&
@@ -134,6 +172,10 @@ jobs:
             git commit -s -m \"mingw-w64-git: new version (`$version)\" PKGBUILD &&
             git bundle create \"`$b\"/MINGW-packages.bundle origin/main..main)
           "@
+      - name: Clean up temporary files
+        if: always()
+        shell: bash
+        run: rm -rf home
       - name: Publish mingw-w64-x86_64-git
         uses: actions/upload-artifact@v1
         with:

From f632ed40e70f725eb5be87c9c04959b44f249d10 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Fri, 25 Jun 2021 18:04:56 -0400
Subject: [PATCH 060/265] fsm-listen-win32: handle shortnames

Teach FSMonitor daemon on Windows to recognize shortname paths as
aliases of normal longname paths.  FSMonitor clients, such as `git
status`, should receive the longname spelling of changed files (when
possible).

Sometimes we receive FS events using the shortname, such as when a CMD
shell runs "RENAME GIT~1 FOO" or "RMDIR GIT~1".  The FS notification
arrives using whatever combination of long and shortnames were used by
the other process.  (Shortnames do seem to be case normalized,
however.)

Use Windows GetLongPathNameW() to try to map the pathname spelling in
the notification event into the normalized longname spelling.  (This
can fail if the file/directory is deleted, moved, or renamed, because
we are asking the FS for the mapping in response to the event and
after it has already happened, but we try.)

Special case the shortname spelling of ".git" to avoid under-reporting
these events.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/fsmonitor/fsm-listen-win32.c | 363 +++++++++++++++++++++++-----
 t/t7527-builtin-fsmonitor.sh        |  65 +++++
 2 files changed, 374 insertions(+), 54 deletions(-)

diff --git a/compat/fsmonitor/fsm-listen-win32.c b/compat/fsmonitor/fsm-listen-win32.c
index 5b928ab66e5e71..3f1b68267bdc0b 100644
--- a/compat/fsmonitor/fsm-listen-win32.c
+++ b/compat/fsmonitor/fsm-listen-win32.c
@@ -25,6 +25,9 @@ struct one_watch
 	DWORD count;
 
 	struct strbuf path;
+	wchar_t wpath_longname[MAX_PATH + 1];
+	DWORD wpath_longname_len;
+
 	HANDLE hDir;
 	HANDLE hEvent;
 	OVERLAPPED overlapped;
@@ -34,6 +37,21 @@ struct one_watch
 	 * need to later call GetOverlappedResult() and possibly CancelIoEx().
 	 */
 	BOOL is_active;
+
+	/*
+	 * Are shortnames enabled on the containing drive?  This is
+	 * always true for "C:/" drives and usually never true for
+	 * other drives.
+	 *
+	 * We only set this for the worktree because we only need to
+	 * convert shortname paths to longname paths for items we send
+	 * to clients.  (We don't care about shortname expansion for
+	 * paths inside a GITDIR because we never send them to
+	 * clients.)
+	 */
+	BOOL has_shortnames;
+	BOOL has_tilda;
+	wchar_t dotgit_shortname[16]; /* for 8.3 name */
 };
 
 struct fsmonitor_daemon_backend_data
@@ -51,17 +69,18 @@ struct fsmonitor_daemon_backend_data
 };
 
 /*
- * Convert the WCHAR path from the notification into UTF8 and
- * then normalize it.
+ * Convert the WCHAR path from the event into UTF8 and normalize it.
+ *
+ * `wpath_len` is in WCHARS not bytes.
  */
-static int normalize_path_in_utf8(FILE_NOTIFY_INFORMATION *info,
+static int normalize_path_in_utf8(wchar_t *wpath, DWORD wpath_len,
 				  struct strbuf *normalized_path)
 {
 	int reserve;
 	int len = 0;
 
 	strbuf_reset(normalized_path);
-	if (!info->FileNameLength)
+	if (!wpath_len)
 		goto normalize;
 
 	/*
@@ -70,12 +89,12 @@ static int normalize_path_in_utf8(FILE_NOTIFY_INFORMATION *info,
 	 * sequence of 2 UTF8 characters.  That should let us
 	 * avoid ERROR_INSUFFICIENT_BUFFER 99.9+% of the time.
 	 */
-	reserve = info->FileNameLength + 1;
+	reserve = 2 * wpath_len + 1;
 	strbuf_grow(normalized_path, reserve);
 
 	for (;;) {
-		len = WideCharToMultiByte(CP_UTF8, 0, info->FileName,
-					  info->FileNameLength / sizeof(WCHAR),
+		len = WideCharToMultiByte(CP_UTF8, 0,
+					  wpath, wpath_len,
 					  normalized_path->buf,
 					  strbuf_avail(normalized_path) - 1,
 					  NULL, NULL);
@@ -83,9 +102,7 @@ static int normalize_path_in_utf8(FILE_NOTIFY_INFORMATION *info,
 			goto normalize;
 		if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
 			error(_("[GLE %ld] could not convert path to UTF-8: '%.*ls'"),
-			      GetLastError(),
-			      (int)(info->FileNameLength / sizeof(WCHAR)),
-			      info->FileName);
+			      GetLastError(), (int)wpath_len, wpath);
 			return -1;
 		}
 
@@ -98,6 +115,152 @@ static int normalize_path_in_utf8(FILE_NOTIFY_INFORMATION *info,
 	return strbuf_normalize_path(normalized_path);
 }
 
+/*
+ * See if the worktree root directory has shortnames enabled.
+ * This will help us decide if we need to do an expensive shortname
+ * to longname conversion on every notification event.
+ *
+ * We do not want to create a file to test this, so we assume that the
+ * root directory contains a ".git" file or directory.  (Our caller
+ * only calls us for the worktree root, so this should be fine.)
+ *
+ * Remember the spelling of the shortname for ".git" if it exists.
+ */
+static void check_for_shortnames(struct one_watch *watch)
+{
+	wchar_t buf_in[MAX_PATH + 1];
+	wchar_t buf_out[MAX_PATH + 1];
+	wchar_t *last_slash = NULL;
+	wchar_t *last_bslash = NULL;
+	wchar_t *last;
+
+	/* build L"<wt-root-path>/.git" */
+	wcscpy(buf_in, watch->wpath_longname);
+	wcscpy(buf_in + watch->wpath_longname_len, L".git");
+
+	if (!GetShortPathNameW(buf_in, buf_out, MAX_PATH))
+		return;
+
+	last_slash = wcsrchr(buf_out, L'/');
+	last_bslash = wcsrchr(buf_out, L'\\');
+	if (last_slash > last_bslash)
+		last = last_slash + 1;
+	else if (last_bslash)
+		last = last_bslash + 1;
+	else
+		last = buf_out;
+
+	if (!wcscmp(last, L".git"))
+		return;
+
+	watch->has_shortnames = 1;
+	wcsncpy(watch->dotgit_shortname, last,
+		ARRAY_SIZE(watch->dotgit_shortname));
+
+	/*
+	 * The shortname for ".git" is usually of the form "GIT~1", so
+	 * we should be able to avoid shortname to longname mapping on
+	 * every notification event if the source string does not
+	 * contain a "~".
+	 *
+	 * However, the documentation for GetLongPathNameW() says
+	 * that there are filesystems that don't follow that pattern
+	 * and warns against this optimization.
+	 *
+	 * Lets test this.
+	 */
+	if (wcschr(watch->dotgit_shortname, L'~'))
+		watch->has_tilda = 1;
+}
+
+enum get_relative_result {
+	GRR_NO_CONVERSION_NEEDED,
+	GRR_HAVE_CONVERSION,
+	GRR_SHUTDOWN,
+};
+
+/*
+ * Info notification paths are relative to the root of the watch.
+ * If our CWD is still at the root, then we can use relative paths
+ * to convert from shortnames to longnames.  If our process has a
+ * different CWD, then we need to construct an absolute path, do
+ * the conversion, and then return the root-relative portion.
+ *
+ * We use the longname form of the root as our basis and assume that
+ * it already has a trailing slash.
+ *
+ * `wpath_len` is in WCHARS not bytes.
+ */
+static enum get_relative_result get_relative_longname(
+	struct one_watch *watch,
+	const wchar_t *wpath, DWORD wpath_len,
+	wchar_t *wpath_longname)
+{
+	wchar_t buf_in[2 * MAX_PATH + 1];
+	wchar_t buf_out[MAX_PATH + 1];
+	DWORD root_len;
+
+	/* Build L"<wt-root-path>/<event-rel-path>" */
+	root_len = watch->wpath_longname_len;
+	wcsncpy(buf_in, watch->wpath_longname, root_len);
+	wcsncpy(buf_in + root_len, wpath, wpath_len);
+	buf_in[root_len + wpath_len] = 0;
+
+	/*
+	 * We don't actually know if the source pathname is a
+	 * shortname or a longname.  This routine allows either to be
+	 * given as input.
+	 */
+	if (!GetLongPathNameW(buf_in, buf_out, MAX_PATH)) {
+		/*
+		 * The shortname to longname conversion can fail for
+		 * various reasons, for example if the file has been
+		 * deleted.  (That is, if we just received a
+		 * delete-file notification event and the file is
+		 * already gone, we can't ask the file system to
+		 * lookup the longname for it.  Likewise, for moves
+		 * and renames where we are given the old name.)
+		 *
+		 * Since deleting or moving a file or directory by its
+		 * shortname is rather obscure, I'm going ignore the
+		 * failure and ask the caller to report the original
+		 * relative path.  This seems kinder than failing here
+		 * and forcing a resync.  Besides, forcing a resync on
+		 * every file/directory delete would effectively
+		 * cripple monitoring.
+		 *
+		 * We might revisit this in the future.
+		 */
+		return GRR_NO_CONVERSION_NEEDED;
+	}
+
+	if (!wcscmp(buf_in, buf_out)) {
+		/*
+		 * The path does not have a shortname alias.
+		 */
+		return GRR_NO_CONVERSION_NEEDED;
+	}
+
+	if (wcsncmp(buf_in, buf_out, root_len)) {
+		/*
+		 * The spelling of the root directory portion of the computed
+		 * longname has changed.  This should not happen.  Basically,
+		 * it means that we don't know where (without recomputing the
+		 * longname of just the root directory) to split out the
+		 * relative path.  Since this should not happen, I'm just
+		 * going to let this fail and force a shutdown (because all
+		 * subsequent events are probably going to see the same
+		 * mismatch).
+		 */
+		return GRR_SHUTDOWN;
+	}
+
+	/* Return the worktree root-relative portion of the longname. */
+
+	wcscpy(wpath_longname, buf_out + root_len);
+	return GRR_HAVE_CONVERSION;
+}
+
 void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
 {
 	SetEvent(state->backend_data->hListener[LISTENER_SHUTDOWN]);
@@ -111,7 +274,9 @@ static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,
 	DWORD share_mode =
 		FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
 	HANDLE hDir;
-	wchar_t wpath[MAX_PATH];
+	DWORD len_longname;
+	wchar_t wpath[MAX_PATH + 1];
+	wchar_t wpath_longname[MAX_PATH + 1];
 
 	if (xutftowcs_path(wpath, path) < 0) {
 		error(_("could not convert to wide characters: '%s'"), path);
@@ -128,6 +293,20 @@ static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,
 		return NULL;
 	}
 
+	if (!GetLongPathNameW(wpath, wpath_longname, MAX_PATH)) {
+		error(_("[GLE %ld] could not get longname of '%s'"),
+		      GetLastError(), path);
+		CloseHandle(hDir);
+		return NULL;
+	}
+
+	len_longname = wcslen(wpath_longname);
+	if (wpath_longname[len_longname - 1] != L'/' &&
+	    wpath_longname[len_longname - 1] != L'\\') {
+		wpath_longname[len_longname++] = L'/';
+		wpath_longname[len_longname] = 0;
+	}
+
 	CALLOC_ARRAY(watch, 1);
 
 	watch->buf_len = sizeof(watch->buffer); /* assume full MAX_RDCW_BUF */
@@ -135,6 +314,9 @@ static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,
 	strbuf_init(&watch->path, 0);
 	strbuf_addstr(&watch->path, path);
 
+	wcscpy(watch->wpath_longname, wpath_longname);
+	watch->wpath_longname_len = len_longname;
+
 	watch->hDir = hDir;
 	watch->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
 
@@ -258,6 +440,62 @@ static void cancel_rdcw_watch(struct one_watch *watch)
 	watch->is_active = FALSE;
 }
 
+/*
+ * Process a single relative pathname event.
+ * Return 1 if we should shutdown.
+ */
+static int process_1_worktree_event(
+	struct string_list *cookie_list,
+	struct fsmonitor_batch **batch,
+	const struct strbuf *path,
+	enum fsmonitor_path_type t,
+	DWORD info_action)
+{
+	const char *slash;
+
+	switch (t) {
+	case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
+		/* special case cookie files within .git */
+
+		/* Use just the filename of the cookie file. */
+		slash = find_last_dir_sep(path->buf);
+		string_list_append(cookie_list,
+				   slash ? slash + 1 : path->buf);
+		break;
+
+	case IS_INSIDE_DOT_GIT:
+		/* ignore everything inside of "<worktree>/.git/" */
+		break;
+
+	case IS_DOT_GIT:
+		/* "<worktree>/.git" was deleted (or renamed away) */
+		if ((info_action == FILE_ACTION_REMOVED) ||
+		    (info_action == FILE_ACTION_RENAMED_OLD_NAME)) {
+			trace2_data_string("fsmonitor", NULL,
+					   "fsm-listen/dotgit",
+					   "removed");
+			return 1;
+		}
+		break;
+
+	case IS_WORKDIR_PATH:
+		/* queue normal pathname */
+		if (!*batch)
+			*batch = fsmonitor_batch__new();
+		fsmonitor_batch__add_path(*batch, path->buf);
+		break;
+
+	case IS_GITDIR:
+	case IS_INSIDE_GITDIR:
+	case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
+	default:
+		BUG("unexpected path classification '%d' for '%s'",
+		    t, path->buf);
+	}
+
+	return 0;
+}
+
 /*
  * Process filesystem events that happen anywhere (recursively) under the
  * <worktree> root directory.  For a normal working directory, this includes
@@ -274,6 +512,7 @@ static int process_worktree_events(struct fsmonitor_daemon_state *state)
 	struct string_list cookie_list = STRING_LIST_INIT_DUP;
 	struct fsmonitor_batch *batch = NULL;
 	const char *p = watch->buffer;
+	wchar_t wpath_longname[MAX_PATH + 1];
 
 	/*
 	 * If the kernel gets more events than will fit in the kernel
@@ -306,54 +545,63 @@ static int process_worktree_events(struct fsmonitor_daemon_state *state)
 	 */
 	for (;;) {
 		FILE_NOTIFY_INFORMATION *info = (void *)p;
-		const char *slash;
+		wchar_t *wpath = info->FileName;
+		DWORD wpath_len = info->FileNameLength / sizeof(WCHAR);
 		enum fsmonitor_path_type t;
+		enum get_relative_result grr;
+
+		if (watch->has_shortnames) {
+			if (!wcscmp(wpath, watch->dotgit_shortname)) {
+				/*
+				 * This event exactly matches the
+				 * spelling of the shortname of
+				 * ".git", so we can skip some steps.
+				 *
+				 * (This case is odd because the user
+				 * can "rm -rf GIT~1" and we cannot
+				 * use the filesystem to map it back
+				 * to ".git".)
+				 */
+				strbuf_reset(&path);
+				strbuf_addstr(&path, ".git");
+				t = IS_DOT_GIT;
+				goto process_it;
+			}
 
-		strbuf_reset(&path);
-		if (normalize_path_in_utf8(info, &path) == -1)
-			goto skip_this_path;
-
-		t = fsmonitor_classify_path_workdir_relative(path.buf);
-
-		switch (t) {
-		case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
-			/* special case cookie files within .git */
-
-			/* Use just the filename of the cookie file. */
-			slash = find_last_dir_sep(path.buf);
-			string_list_append(&cookie_list,
-					   slash ? slash + 1 : path.buf);
-			break;
-
-		case IS_INSIDE_DOT_GIT:
-			/* ignore everything inside of "<worktree>/.git/" */
-			break;
+			if (watch->has_tilda && !wcschr(wpath, L'~')) {
+				/*
+				 * Shortnames on this filesystem have tildas
+				 * and the notification path does not have
+				 * one, so we assume that it is a longname.
+				 */
+				goto normalize_it;
+			}
 
-		case IS_DOT_GIT:
-			/* "<worktree>/.git" was deleted (or renamed away) */
-			if ((info->Action == FILE_ACTION_REMOVED) ||
-			    (info->Action == FILE_ACTION_RENAMED_OLD_NAME)) {
-				trace2_data_string("fsmonitor", NULL,
-						   "fsm-listen/dotgit",
-						   "removed");
+			grr = get_relative_longname(watch, wpath, wpath_len,
+						    wpath_longname);
+			switch (grr) {
+			case GRR_NO_CONVERSION_NEEDED: /* use info buffer as is */
+				break;
+			case GRR_HAVE_CONVERSION:
+				wpath = wpath_longname;
+				wpath_len = wcslen(wpath);
+				break;
+			default:
+			case GRR_SHUTDOWN:
 				goto force_shutdown;
 			}
-			break;
+		}
 
-		case IS_WORKDIR_PATH:
-			/* queue normal pathname */
-			if (!batch)
-				batch = fsmonitor_batch__new();
-			fsmonitor_batch__add_path(batch, path.buf);
-			break;
+normalize_it:
+		if (normalize_path_in_utf8(wpath, wpath_len, &path) == -1)
+			goto skip_this_path;
 
-		case IS_GITDIR:
-		case IS_INSIDE_GITDIR:
-		case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
-		default:
-			BUG("unexpected path classification '%d' for '%s'",
-			    t, path.buf);
-		}
+		t = fsmonitor_classify_path_workdir_relative(path.buf);
+
+process_it:
+		if (process_1_worktree_event(&cookie_list, &batch, &path, t,
+					     info->Action))
+			goto force_shutdown;
 
 skip_this_path:
 		if (!info->NextEntryOffset)
@@ -382,6 +630,9 @@ static int process_worktree_events(struct fsmonitor_daemon_state *state)
  * Note that we DO NOT get filesystem events on the external <gitdir>
  * itself (it is not inside something that we are watching).  In particular,
  * we do not get an event if the external <gitdir> is deleted.
+ *
+ * Also, we do not care about shortnames within the external <gitdir>, since
+ * we never send these paths to clients.
  */
 static int process_gitdir_events(struct fsmonitor_daemon_state *state)
 {
@@ -403,8 +654,10 @@ static int process_gitdir_events(struct fsmonitor_daemon_state *state)
 		const char *slash;
 		enum fsmonitor_path_type t;
 
-		strbuf_reset(&path);
-		if (normalize_path_in_utf8(info, &path) == -1)
+		if (normalize_path_in_utf8(
+			    info->FileName,
+			    info->FileNameLength / sizeof(WCHAR),
+			    &path) == -1)
 			goto skip_this_path;
 
 		t = fsmonitor_classify_path_gitdir_relative(path.buf);
@@ -538,6 +791,8 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
 	if (!data->watch_worktree)
 		goto failed;
 
+	check_for_shortnames(data->watch_worktree);
+
 	if (state->nr_paths_watching > 1) {
 		data->watch_gitdir = create_watch(state,
 						  state->path_gitdir_watch.buf);
diff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh
index bd0c952a11659c..1be21785162d0b 100755
--- a/t/t7527-builtin-fsmonitor.sh
+++ b/t/t7527-builtin-fsmonitor.sh
@@ -166,6 +166,71 @@ test_expect_success 'implicit daemon stop (rename .git)' '
 	test_must_fail git -C test_implicit_2 fsmonitor--daemon status
 '
 
+# File systems on Windows may or may not have shortnames.
+# This is a volume-specific setting on modern systems.
+# "C:/" drives are required to have them enabled.  Other
+# hard drives default to disabled.
+#
+# This is a crude test to see if shortnames are enabled
+# on the volume containing the test directory.  It is
+# crude, but it does not require elevation like `fsutil`.
+#
+test_lazy_prereq SHORTNAMES '
+	mkdir .foo &&
+	test -d "FOO~1"
+'
+
+# Here we assume that the shortname of ".git" is "GIT~1".
+test_expect_success MINGW,SHORTNAMES 'implicit daemon stop (rename GIT~1)' '
+	test_when_finished "stop_daemon_delete_repo test_implicit_1s" &&
+
+	git init test_implicit_1s &&
+
+	start_daemon -C test_implicit_1s &&
+
+	# renaming the .git directory will implicitly stop the daemon.
+	# this moves {.git, GIT~1} to {.gitxyz, GITXYZ~1}.
+	# the rename-from FS Event will contain the shortname.
+	#
+	mv test_implicit_1s/GIT~1 test_implicit_1s/.gitxyz &&
+
+	sleep 1 &&
+	# put it back so that our status will not crawl out to our
+	# parent directory.
+	# this moves {.gitxyz, GITXYZ~1} to {.git, GIT~1}.
+	mv test_implicit_1s/.gitxyz test_implicit_1s/.git &&
+
+	test_must_fail git -C test_implicit_1s fsmonitor--daemon status
+'
+
+# Here we first create a file with LONGNAME of "GIT~1" before
+# we create the repo.  This will cause the shortname of ".git"
+# to be "GIT~2".
+test_expect_success MINGW,SHORTNAMES 'implicit daemon stop (rename GIT~2)' '
+	test_when_finished "stop_daemon_delete_repo test_implicit_1s2" &&
+
+	mkdir test_implicit_1s2 &&
+	echo HELLO >test_implicit_1s2/GIT~1 &&
+	git init test_implicit_1s2 &&
+
+	test_path_is_file test_implicit_1s2/GIT~1 &&
+	test_path_is_dir  test_implicit_1s2/GIT~2 &&
+
+	start_daemon -C test_implicit_1s2 &&
+
+	# renaming the .git directory will implicitly stop the daemon.
+	# the rename-from FS Event will contain the shortname.
+	#
+	mv test_implicit_1s2/GIT~2 test_implicit_1s2/.gitxyz &&
+
+	sleep 1 &&
+	# put it back so that our status will not crawl out to our
+	# parent directory.
+	mv test_implicit_1s2/.gitxyz test_implicit_1s2/.git &&
+
+	test_must_fail git -C test_implicit_1s2 fsmonitor--daemon status
+'
+
 test_expect_success 'cannot start multiple daemons' '
 	test_when_finished "stop_daemon_delete_repo test_multiple" &&
 

From 5419a4273ca5541be646d71c3928eb88a7c31f0f Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Sun, 12 Apr 2020 00:34:56 +0200
Subject: [PATCH 061/265] git-artifacts: also code-sign, if configured via the
 secrets

When the secrets `CODESIGN_P12` and `CODESIGN_PASS` are set, the
workflow will now code-sign the `.exe` files contained in the package.

This should help with a few anti-malware programs, at least when the
certificate saw some action and gained trust.

Note: `CODESIGN_P12` needs to be generated via

	cat <certificate>.p12 | base64 | tr '\n' %

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index b5dbf6c4bcf5a4..7faeac0fcca436 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -135,6 +135,18 @@ jobs:
           git remote add -f origin https://github.com/git-for-windows/git &&
           git fetch --tags bundle-artifacts/git.bundle $(cat bundle-artifacts/next_version) &&
           git reset --hard $(cat bundle-artifacts/next_version)
+      - name: Prepare home directory for code-signing
+        env:
+          CODESIGN_P12: ${{secrets.CODESIGN_P12}}
+          CODESIGN_PASS: ${{secrets.CODESIGN_PASS}}
+        if: env.CODESIGN_P12 != '' && env.CODESIGN_PASS != ''
+        shell: bash
+        run: |
+          cd home &&
+          mkdir -p .sig &&
+          echo -n "$CODESIGN_P12" | tr % '\n' | base64 -d >.sig/codesign.p12 &&
+          echo -n "$CODESIGN_PASS" >.sig/codesign.pass
+          git config --global alias.signtool '!sh "/usr/src/build-extra/signtool.sh"'
       - name: Prepare home directory for GPG signing
         if: env.GPGKEY != ''
         shell: bash

From 0a51e33067b45768426825f182946f18da8f7ca5 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Mon, 12 Jul 2021 12:50:31 -0400
Subject: [PATCH 062/265] t7527: test FSMonitor on repos with Unicode root
 paths

Create some test repos with UTF8 characters in the pathname of the
root directory and verify that the builtin FSMonitor can watch them.

This test is mainly for Windows where we need to avoid `*A()`
routines.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 t/t7527-builtin-fsmonitor.sh | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh
index 1be21785162d0b..c9c7dd77e3cc18 100755
--- a/t/t7527-builtin-fsmonitor.sh
+++ b/t/t7527-builtin-fsmonitor.sh
@@ -671,4 +671,27 @@ do
 	done
 done
 
+# Test Unicode UTF-8 characters in the pathname of the working
+# directory root.  Use of "*A()" routines rather than "*W()" routines
+# on Windows can sometimes lead to odd failures.
+#
+u1=$(printf "u_c3_a6__\xC3\xA6")
+u2=$(printf "u_e2_99_ab__\xE2\x99\xAB")
+u_values="$u1 $u2"
+for u in $u_values
+do
+	test_expect_success "Unicode in repo root path: $u" '
+		test_when_finished "stop_daemon_delete_repo $u" &&
+
+		git init "$u" &&
+		echo 1 >"$u"/file1 &&
+		git -C "$u" add file1 &&
+		git -C "$u" config core.fsmonitor true &&
+
+		start_daemon -C "$u" &&
+		git -C "$u" status >actual &&
+		grep "new file:   file1" actual
+	'
+done
+
 test_done

From 4b08857913b05eb00e65b2f668409582def3564e Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Sun, 12 Apr 2020 00:43:19 +0200
Subject: [PATCH 063/265] git-artifacts: also build the installer

While at it, we might just as well build the Git for Windows installer
;-)

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 75 ++++++++++++++++++++++++++++-
 1 file changed, 74 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index 7faeac0fcca436..380dc00b86da80 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -1,4 +1,4 @@
-name: mingw-w64-x86_64-git
+name: git-artifacts
 
 on:
   # This workflow can be triggered manually in the Actions tab, see
@@ -193,3 +193,76 @@ jobs:
         with:
           name: pkg-x86_64
           path: artifacts
+  installer-x86_64:
+    runs-on: windows-latest
+    needs: pkg-x86_64
+    env:
+      MSYSTEM: MINGW64
+    steps:
+      - name: Download pkg-x86_64
+        uses: actions/download-artifact@v1
+        with:
+          name: pkg-x86_64
+          path: pkg-x86_64
+      - name: Download bundle-artifacts
+        uses: actions/download-artifact@v1
+        with:
+          name: bundle-artifacts
+          path: bundle-artifacts
+      - name: Download git-sdk-64-build-installers
+        shell: bash
+        run: |
+          # Use Git Bash to download and unpack the artifact
+
+          ## Get artifact
+          urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
+          id=${{ needs.pkg.outputs.latest-sdk64-extra-build-id }}
+          download_url="$(curl "$urlbase/$id/artifacts" |
+            jq -r '.value[] | select(.name == "git-sdk-64-build-installers").resource.downloadUrl')"
+
+          curl -o artifacts.zip "$download_url"
+
+          ## Unpack artifact
+          unzip artifacts.zip
+      - name: Clone and update build-extra
+        shell: bash
+        run: |
+          d=git-sdk-64-build-installers/usr/src/build-extra &&
+          git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d &&
+          git -C $d pull "$PWD"/bundle-artifacts/build-extra.bundle main
+      - name: Prepare home directory for code-signing
+        env:
+          CODESIGN_P12: ${{secrets.CODESIGN_P12}}
+          CODESIGN_PASS: ${{secrets.CODESIGN_PASS}}
+        if: (matrix.artifact.name == 'installer' || matrix.artifact.name == 'portable') && env.CODESIGN_P12 != '' && env.CODESIGN_PASS != ''
+        shell: bash
+        run: |
+          mkdir -p home/.sig &&
+          echo -n "$CODESIGN_P12" | tr % '\n' | base64 -d >home/.sig/codesign.p12 &&
+          echo -n "$CODESIGN_PASS" >home/.sig/codesign.pass &&
+          git config --global alias.signtool '!sh "/usr/src/build-extra/signtool.sh"'
+      - name: Build 64-bit installer
+        shell: powershell
+        run: |
+          & .\git-sdk-64-build-installers\usr\bin\bash.exe -lc @"
+            set -x
+            /usr/src/build-extra/please.sh make_installers_from_mingw_w64_git --version=`$(cat pkg-x86_64/ver) -o artifacts --installer --pkg=pkg-x86_64/mingw-w64-x86_64-git-[0-9]*.tar.xz --pkg=pkg-x86_64/mingw-w64-x86_64-git-doc-html-[0-9]*.tar.xz &&
+            openssl dgst -sha256 artifacts/Git-*.exe | sed \"s/.* //\" >artifacts/sha-256.txt &&
+            cp /usr/src/build-extra/installer/package-versions.txt artifacts/ &&
+
+            a=`$PWD/artifacts &&
+            p=`$PWD/pkg-x86_64 &&
+            (cd /usr/src/build-extra &&
+            mkdir -p cached-source-packages &&
+            cp \"`$p\"/*-pdb* cached-source-packages/ &&
+            GIT_CONFIG_PARAMETERS=\"'windows.sdk64.path='\" ./please.sh bundle_pdbs --arch=x86_64 --directory=\"`$a\" installer/package-versions.txt)
+          "@
+      - name: Clean up temporary files
+        if: always()
+        shell: bash
+        run: rm -rf home
+      - name: Publish installer-x86_64
+        uses: actions/upload-artifact@v1
+        with:
+          name: installer-x86_64
+          path: artifacts

From 1efbb5b9c448947c6fcf1be682ce3ce84076a893 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Tue, 27 Jul 2021 14:06:33 -0400
Subject: [PATCH 064/265] t/helper/fsmonitor-client: create stress test

Create a stress test to hammer on the fsmonitor daemon.
Create a client-side thread pool of n threads and have
each of them make m requests as fast as they can.

We do not currently inspect the contents of the response.
We're only interested in placing a heavy request load on
the daemon.

This test is useful for interactive testing and various
experimentation.  For example, to place additional load
on the daemon while another test is running.  We currently
do not have a test script that actually uses this helper.
We might add such a test in the future.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 t/helper/test-fsmonitor-client.c | 114 +++++++++++++++++++++++++++++--
 1 file changed, 110 insertions(+), 4 deletions(-)

diff --git a/t/helper/test-fsmonitor-client.c b/t/helper/test-fsmonitor-client.c
index 3062c8a3c2b31f..f7dc1690ef145d 100644
--- a/t/helper/test-fsmonitor-client.c
+++ b/t/helper/test-fsmonitor-client.c
@@ -7,6 +7,8 @@
 #include "cache.h"
 #include "parse-options.h"
 #include "fsmonitor-ipc.h"
+#include "thread-utils.h"
+#include "trace2.h"
 
 #ifndef HAVE_FSMONITOR_DAEMON_BACKEND
 int cmd__fsmonitor_client(int argc, const char **argv)
@@ -79,20 +81,121 @@ static int do_send_flush(void)
 	return 0;
 }
 
+struct hammer_thread_data
+{
+	pthread_t pthread_id;
+	int thread_nr;
+
+	int nr_requests;
+	const char *token;
+
+	int sum_successful;
+	int sum_errors;
+};
+
+static void *hammer_thread_proc(void *_hammer_thread_data)
+{
+	struct hammer_thread_data *data = _hammer_thread_data;
+	struct strbuf answer = STRBUF_INIT;
+	int k;
+	int ret;
+
+	trace2_thread_start("hammer");
+
+	for (k = 0; k < data->nr_requests; k++) {
+		strbuf_reset(&answer);
+
+		ret = fsmonitor_ipc__send_query(data->token, &answer);
+		if (ret < 0)
+			data->sum_errors++;
+		else
+			data->sum_successful++;
+	}
+
+	strbuf_release(&answer);
+	trace2_thread_exit();
+	return NULL;
+}
+
+/*
+ * Start a pool of client threads that will each send a series of
+ * commands to the daemon.
+ *
+ * The goal is to overload the daemon with a sustained series of
+ * concurrent requests.
+ */
+static int do_hammer(const char *token, int nr_threads, int nr_requests)
+{
+	struct hammer_thread_data *data = NULL;
+	int k;
+	int sum_join_errors = 0;
+	int sum_commands = 0;
+	int sum_errors = 0;
+
+	if (!token || !*token)
+		token = get_token_from_index();
+	if (nr_threads < 1)
+		nr_threads = 1;
+	if (nr_requests < 1)
+		nr_requests = 1;
+
+	CALLOC_ARRAY(data, nr_threads);
+
+	for (k = 0; k < nr_threads; k++) {
+		struct hammer_thread_data *p = &data[k];
+		p->thread_nr = k;
+		p->nr_requests = nr_requests;
+		p->token = token;
+
+		if (pthread_create(&p->pthread_id, NULL, hammer_thread_proc, p)) {
+			warning("failed to create thread[%d] skipping remainder", k);
+			nr_threads = k;
+			break;
+		}
+	}
+
+	for (k = 0; k < nr_threads; k++) {
+		struct hammer_thread_data *p = &data[k];
+
+		if (pthread_join(p->pthread_id, NULL))
+			sum_join_errors++;
+		sum_commands += p->sum_successful;
+		sum_errors += p->sum_errors;
+	}
+
+	fprintf(stderr, "HAMMER: [threads %d][requests %d] [ok %d][err %d][join %d]\n",
+		nr_threads, nr_requests, sum_commands, sum_errors, sum_join_errors);
+
+	free(data);
+
+	/*
+	 * Return an error if any of the _send_query requests failed.
+	 * We don't care about thread create/join errors.
+	 */
+	return sum_errors > 0;
+}
+
 int cmd__fsmonitor_client(int argc, const char **argv)
 {
 	const char *subcmd;
 	const char *token = NULL;
+	int nr_threads = 1;
+	int nr_requests = 1;
 
 	const char * const fsmonitor_client_usage[] = {
-		"test-tool fsmonitor-client query [<token>]",
-		"test-tool fsmonitor-client flush",
+		N_("test-tool fsmonitor-client query [<token>]"),
+		N_("test-tool fsmonitor-client flush"),
+		N_("test-tool fsmonitor-client hammer [<token>] [<threads>] [<requests>]"),
 		NULL,
 	};
 
 	struct option options[] = {
-		OPT_STRING(0, "token", &token, "token",
-			   "command token to send to the server"),
+		OPT_STRING(0, "token", &token, N_("token"),
+			   N_("command token to send to the server")),
+
+		OPT_INTEGER(0, "threads", &nr_threads, N_("number of client threads")),
+		OPT_INTEGER(0, "requests", &nr_requests, N_("number of requests per thread")),
+
 		OPT_END()
 	};
 
@@ -111,6 +214,9 @@ int cmd__fsmonitor_client(int argc, const char **argv)
 	if (!strcmp(subcmd, "flush"))
 		return !!do_send_flush();
 
+	if (!strcmp(subcmd, "hammer"))
+		return !!do_hammer(token, nr_threads, nr_requests);
+
 	die("Unhandled subcommand: '%s'", subcmd);
 }
 #endif

From 9710470f0bdb3fd309d912caf5e1644cefda2b50 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Sun, 12 Apr 2020 00:51:00 +0200
Subject: [PATCH 065/265] git-artifacts: also build portable, mingit and
 mingit-busybox

... because we can.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 43 ++++++++++++++++++++++++-----
 1 file changed, 36 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index 380dc00b86da80..ed7c7e4a261d2f 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -193,9 +193,28 @@ jobs:
         with:
           name: pkg-x86_64
           path: artifacts
-  installer-x86_64:
+  artifacts:
     runs-on: windows-latest
-    needs: pkg-x86_64
+    needs: pkg
+    strategy:
+      matrix:
+        artifact:
+          - name: installer
+            fileprefix: Git
+            fileextension: exe
+          - name: portable
+            fileprefix: PortableGit
+            fileextension: exe
+          - name: archive
+            fileprefix: Git
+            fileextension: tar.bz2
+          - name: mingit
+            fileprefix: MinGit
+            fileextension: zip
+          - name: mingit-busybox
+            fileprefix: MinGit
+            fileextension: zip
+      fail-fast: false
     env:
       MSYSTEM: MINGW64
     steps:
@@ -241,13 +260,23 @@ jobs:
           echo -n "$CODESIGN_P12" | tr % '\n' | base64 -d >home/.sig/codesign.p12 &&
           echo -n "$CODESIGN_PASS" >home/.sig/codesign.pass &&
           git config --global alias.signtool '!sh "/usr/src/build-extra/signtool.sh"'
-      - name: Build 64-bit installer
+      - name: Build 64-bit ${{matrix.artifact.name}}
         shell: powershell
         run: |
           & .\git-sdk-64-build-installers\usr\bin\bash.exe -lc @"
             set -x
-            /usr/src/build-extra/please.sh make_installers_from_mingw_w64_git --version=`$(cat pkg-x86_64/ver) -o artifacts --installer --pkg=pkg-x86_64/mingw-w64-x86_64-git-[0-9]*.tar.xz --pkg=pkg-x86_64/mingw-w64-x86_64-git-doc-html-[0-9]*.tar.xz &&
-            openssl dgst -sha256 artifacts/Git-*.exe | sed \"s/.* //\" >artifacts/sha-256.txt &&
+            /usr/src/build-extra/please.sh make_installers_from_mingw_w64_git --version=`$(cat pkg-x86_64/ver) -o artifacts --${{matrix.artifact.name}} --pkg=pkg-x86_64/mingw-w64-x86_64-git-[0-9]*.tar.xz --pkg=pkg-x86_64/mingw-w64-x86_64-git-doc-html-[0-9]*.tar.xz &&
+            if test portable = '${{matrix.artifact.name}}' && test -n \"`$(git config alias.signtool)\"
+            then
+              git signtool artifacts/PortableGit-*.exe
+            fi &&
+            openssl dgst -sha256 artifacts/${{matrix.artifact.fileprefix}}-*.${{matrix.artifact.fileextension}} | sed \"s/.* //\" >artifacts/sha-256.txt
+          "@
+      - name: Copy package-versions and pdbs
+        if: matrix.artifact.name == 'installer'
+        shell: powershell
+        run: |
+          & .\git-sdk-64-build-installers\usr\bin\bash.exe -lc @"
             cp /usr/src/build-extra/installer/package-versions.txt artifacts/ &&
 
             a=`$PWD/artifacts &&
@@ -261,8 +290,8 @@ jobs:
         if: always()
         shell: bash
         run: rm -rf home
-      - name: Publish installer-x86_64
+      - name: Publish ${{matrix.artifact.name}}-x86_64
         uses: actions/upload-artifact@v1
         with:
-          name: installer-x86_64
+          name: ${{matrix.artifact.name}}-x86_64
           path: artifacts

From b0144836d53d8d564143e7dbb52f40644ffe00e0 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Wed, 23 Jun 2021 10:18:17 -0400
Subject: [PATCH 066/265] fsmonitor-settings: bare repos are incompatible with
 FSMonitor

Bare repos do not have a worktree, so there is nothing for the
daemon watch.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 builtin/fsmonitor--daemon.c |  6 ++++
 builtin/update-index.c      |  4 +++
 fsmonitor-settings.c        | 60 +++++++++++++++++++++++++++++++++++++
 fsmonitor-settings.h        | 12 ++++++++
 t/t7519-status-fsmonitor.sh | 23 ++++++++++++++
 5 files changed, 105 insertions(+)

diff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c
index 46be55a4618740..50ae3cca575be9 100644
--- a/builtin/fsmonitor--daemon.c
+++ b/builtin/fsmonitor--daemon.c
@@ -1449,6 +1449,12 @@ int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
 		die(_("invalid 'ipc-threads' value (%d)"),
 		    fsmonitor__ipc_threads);
 
+	prepare_repo_settings(the_repository);
+	fsm_settings__set_ipc(the_repository);
+
+	if (fsm_settings__error_if_incompatible(the_repository))
+		return 1;
+
 	if (!strcmp(subcmd, "start"))
 		return !!try_to_start_background_daemon();
 
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 876112abb21a4d..d29048f16f2183 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -1237,6 +1237,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 
 	if (fsmonitor > 0) {
 		enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(r);
+
+		if (fsm_settings__error_if_incompatible(the_repository))
+			return 1;
+
 		if (fsm_mode == FSMONITOR_MODE_DISABLED) {
 			warning(_("core.fsmonitor is unset; "
 				"set it if you really want to "
diff --git a/fsmonitor-settings.c b/fsmonitor-settings.c
index 757d230d538056..86c09bd35fe72f 100644
--- a/fsmonitor-settings.c
+++ b/fsmonitor-settings.c
@@ -9,9 +9,33 @@
  */
 struct fsmonitor_settings {
 	enum fsmonitor_mode mode;
+	enum fsmonitor_reason reason;
 	char *hook_path;
 };
 
+static void set_incompatible(struct repository *r,
+			     enum fsmonitor_reason reason)
+{
+	struct fsmonitor_settings *s = r->settings.fsmonitor;
+
+	s->mode = FSMONITOR_MODE_INCOMPATIBLE;
+	s->reason = reason;
+}
+
+static int check_for_incompatible(struct repository *r)
+{
+	if (!r->worktree) {
+		/*
+		 * Bare repositories don't have a working directory and
+		 * therefore have nothing to watch.
+		 */
+		set_incompatible(r, FSMONITOR_REASON_BARE);
+		return 1;
+	}
+
+	return 0;
+}
+
 static void lookup_fsmonitor_settings(struct repository *r)
 {
 	struct fsmonitor_settings *s;
@@ -23,6 +47,7 @@ static void lookup_fsmonitor_settings(struct repository *r)
 
 	CALLOC_ARRAY(s, 1);
 	s->mode = FSMONITOR_MODE_DISABLED;
+	s->reason = FSMONITOR_REASON_OK;
 
 	r->settings.fsmonitor = s;
 
@@ -86,6 +111,9 @@ void fsm_settings__set_ipc(struct repository *r)
 
 	lookup_fsmonitor_settings(r);
 
+	if (check_for_incompatible(r))
+		return;
+
 	r->settings.fsmonitor->mode = FSMONITOR_MODE_IPC;
 	FREE_AND_NULL(r->settings.fsmonitor->hook_path);
 }
@@ -97,6 +125,9 @@ void fsm_settings__set_hook(struct repository *r, const char *path)
 
 	lookup_fsmonitor_settings(r);
 
+	if (check_for_incompatible(r))
+		return;
+
 	r->settings.fsmonitor->mode = FSMONITOR_MODE_HOOK;
 	FREE_AND_NULL(r->settings.fsmonitor->hook_path);
 	r->settings.fsmonitor->hook_path = strdup(path);
@@ -110,5 +141,34 @@ void fsm_settings__set_disabled(struct repository *r)
 	lookup_fsmonitor_settings(r);
 
 	r->settings.fsmonitor->mode = FSMONITOR_MODE_DISABLED;
+	r->settings.fsmonitor->reason = FSMONITOR_REASON_OK;
 	FREE_AND_NULL(r->settings.fsmonitor->hook_path);
 }
+
+enum fsmonitor_reason fsm_settings__get_reason(struct repository *r)
+{
+	if (!r)
+		r = the_repository;
+
+	lookup_fsmonitor_settings(r);
+
+	return r->settings.fsmonitor->reason;
+}
+
+int fsm_settings__error_if_incompatible(struct repository *r)
+{
+	enum fsmonitor_reason reason = fsm_settings__get_reason(r);
+
+	switch (reason) {
+	case FSMONITOR_REASON_OK:
+		return 0;
+
+	case FSMONITOR_REASON_BARE:
+		error(_("bare repository '%s' is incompatible with fsmonitor"),
+		      xgetcwd());
+		return 1;
+	}
+
+	BUG("Unhandled case in fsm_settings__error_if_incompatible: '%d'",
+	    reason);
+}
diff --git a/fsmonitor-settings.h b/fsmonitor-settings.h
index a4c5d7b48891fe..8654edf33d80d9 100644
--- a/fsmonitor-settings.h
+++ b/fsmonitor-settings.h
@@ -4,11 +4,20 @@
 struct repository;
 
 enum fsmonitor_mode {
+	FSMONITOR_MODE_INCOMPATIBLE = -1, /* see _reason */
 	FSMONITOR_MODE_DISABLED = 0,
 	FSMONITOR_MODE_HOOK = 1, /* core.fsmonitor=<hook_path> */
 	FSMONITOR_MODE_IPC = 2,  /* core.fsmonitor=<true> */
 };
 
+/*
+ * Incompatibility reasons.
+ */
+enum fsmonitor_reason {
+	FSMONITOR_REASON_OK = 0, /* no incompatibility or when disbled */
+	FSMONITOR_REASON_BARE,
+};
+
 void fsm_settings__set_ipc(struct repository *r);
 void fsm_settings__set_hook(struct repository *r, const char *path);
 void fsm_settings__set_disabled(struct repository *r);
@@ -16,6 +25,9 @@ void fsm_settings__set_disabled(struct repository *r);
 enum fsmonitor_mode fsm_settings__get_mode(struct repository *r);
 const char *fsm_settings__get_hook_path(struct repository *r);
 
+enum fsmonitor_reason fsm_settings__get_reason(struct repository *r);
+int fsm_settings__error_if_incompatible(struct repository *r);
+
 struct fsmonitor_settings;
 
 #endif /* FSMONITOR_SETTINGS_H */
diff --git a/t/t7519-status-fsmonitor.sh b/t/t7519-status-fsmonitor.sh
index d4f9c6a837b180..844e482d3c4a58 100755
--- a/t/t7519-status-fsmonitor.sh
+++ b/t/t7519-status-fsmonitor.sh
@@ -55,6 +55,29 @@ test_lazy_prereq UNTRACKED_CACHE '
 	test $ret -ne 1
 '
 
+# Test that we detect and disallow repos that are incompatible with FSMonitor.
+test_expect_success 'incompatible bare repo' '
+	test_when_finished "rm -rf ./bare-clone actual expect" &&
+	git init --bare bare-clone &&
+
+	test_must_fail \
+		git -C ./bare-clone -c core.fsmonitor=foo \
+			update-index --fsmonitor 2>actual &&
+	grep "bare repository .* is incompatible with fsmonitor" actual &&
+
+	test_must_fail \
+		git -C ./bare-clone -c core.fsmonitor=true \
+			update-index --fsmonitor 2>actual &&
+	grep "bare repository .* is incompatible with fsmonitor" actual
+'
+
+test_expect_success FSMONITOR_DAEMON 'run fsmonitor-daemon in bare repo' '
+	test_when_finished "rm -rf ./bare-clone actual" &&
+	git init --bare bare-clone &&
+	test_must_fail git -C ./bare-clone fsmonitor--daemon run 2>actual &&
+	grep "bare repository .* is incompatible with fsmonitor" actual
+'
+
 test_expect_success 'setup' '
 	: >tracked &&
 	: >modified &&

From c062b182108b89c14ee65a0439b39ab943a159b3 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Sun, 12 Apr 2020 00:57:23 +0200
Subject: [PATCH 067/265] git-artifacts: also build 32-bit versions

Just in case that we need to generate those real quick.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 70 +++++++++++++++++++++--------
 1 file changed, 51 insertions(+), 19 deletions(-)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index ed7c7e4a261d2f..2b2ef6ebab234d 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -89,9 +89,18 @@ jobs:
         with:
           name: bundle-artifacts
           path: bundle-artifacts
-  pkg-x86_64:
+  pkg:
     runs-on: windows-latest
     needs: bundle-artifacts
+    strategy:
+      matrix:
+        arch:
+          - name: x86_64
+            bitness: 64
+            bin: /amd64
+          - name: i686
+            bitness: 32
+            bin: ''
     steps:
       - name: Configure user
         shell: bash
@@ -157,7 +166,7 @@ jobs:
           git config --global user.email "<${info#*<}"
         env:
           GPGKEY: ${{secrets.GPGKEY}}
-      - name: Build mingw-w64-x86_64-git
+      - name: Build mingw-w64-${{matrix.arch.name}}-git
         env:
           GPGKEY: "${{secrets.GPGKEY}}"
         shell: powershell
@@ -165,9 +174,9 @@ jobs:
           & git-sdk-64-makepkg-git\usr\bin\sh.exe -lc @"
             set -x
             # Let `cv2pdb` find the DLLs
-            PATH=\"`$PATH:/c/Program Files/Microsoft Visual Studio/2022/Enterprise/Common7/IDE/:/C/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64\"
+            PATH=\"`$PATH:/c/Program Files/Microsoft Visual Studio/2022/Enterprise/Common7/IDE/:/C/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin${{matrix.arch.bin}}\"
             type -p mspdb140.dll || exit 1
-            sh -x /usr/src/build-extra/please.sh build-mingw-w64-git --only-64-bit --build-src-pkg -o artifacts HEAD &&
+            sh -x /usr/src/build-extra/please.sh build-mingw-w64-git --only-${{matrix.arch.bitness}}-bit --build-src-pkg -o artifacts HEAD &&
             cp bundle-artifacts/ver artifacts/ &&
             if test -n \"`$GPGKEY\"
             then
@@ -188,10 +197,10 @@ jobs:
         if: always()
         shell: bash
         run: rm -rf home
-      - name: Publish mingw-w64-x86_64-git
+      - name: Publish mingw-w64-${{matrix.arch.name}}-git
         uses: actions/upload-artifact@v1
         with:
-          name: pkg-x86_64
+          name: pkg-${{matrix.arch.name}}
           path: artifacts
   artifacts:
     runs-on: windows-latest
@@ -214,21 +223,27 @@ jobs:
           - name: mingit-busybox
             fileprefix: MinGit
             fileextension: zip
+        arch:
+          - name: x86_64
+            bitness: 64
+          - name: i686
+            bitness: 32
       fail-fast: false
     env:
-      MSYSTEM: MINGW64
+      MSYSTEM: MINGW${{matrix.arch.bitness}}
     steps:
-      - name: Download pkg-x86_64
+      - name: Download pkg-${{matrix.arch.name}}
         uses: actions/download-artifact@v1
         with:
-          name: pkg-x86_64
-          path: pkg-x86_64
+          name: pkg-${{matrix.arch.name}}
+          path: pkg-${{matrix.arch.name}}
       - name: Download bundle-artifacts
         uses: actions/download-artifact@v1
         with:
           name: bundle-artifacts
           path: bundle-artifacts
       - name: Download git-sdk-64-build-installers
+        if: matrix.arch.bitness == '64'
         shell: bash
         run: |
           # Use Git Bash to download and unpack the artifact
@@ -241,12 +256,29 @@ jobs:
 
           curl -o artifacts.zip "$download_url"
 
+          ## Unpack artifact
+          unzip artifacts.zip
+      - name: Download git-sdk-32-build-installers
+        if: matrix.arch.bitness == '32'
+        shell: bash
+        run: |
+          # Use Git Bash to download and unpack the artifact
+
+          ## Get artifact
+          urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
+          id=$(curl "$urlbase?definitions=30&statusFilter=completed&resultFilter=succeeded&\$top=1" |
+            jq -r '.value[0].id')
+          download_url=$(curl "$urlbase/$id/artifacts" |
+            jq -r '.value[] | select(.name == "git-sdk-32-build-installers").resource.downloadUrl')
+
+          curl -o artifacts.zip "$download_url"
+
           ## Unpack artifact
           unzip artifacts.zip
       - name: Clone and update build-extra
         shell: bash
         run: |
-          d=git-sdk-64-build-installers/usr/src/build-extra &&
+          d=git-sdk-${{matrix.arch.bitness}}-build-installers/usr/src/build-extra &&
           git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d &&
           git -C $d pull "$PWD"/bundle-artifacts/build-extra.bundle main
       - name: Prepare home directory for code-signing
@@ -260,12 +292,12 @@ jobs:
           echo -n "$CODESIGN_P12" | tr % '\n' | base64 -d >home/.sig/codesign.p12 &&
           echo -n "$CODESIGN_PASS" >home/.sig/codesign.pass &&
           git config --global alias.signtool '!sh "/usr/src/build-extra/signtool.sh"'
-      - name: Build 64-bit ${{matrix.artifact.name}}
+      - name: Build ${{matrix.arch.bitness}}-bit ${{matrix.artifact.name}}
         shell: powershell
         run: |
-          & .\git-sdk-64-build-installers\usr\bin\bash.exe -lc @"
+          & .\git-sdk-${{matrix.arch.bitness}}-build-installers\usr\bin\bash.exe -lc @"
             set -x
-            /usr/src/build-extra/please.sh make_installers_from_mingw_w64_git --version=`$(cat pkg-x86_64/ver) -o artifacts --${{matrix.artifact.name}} --pkg=pkg-x86_64/mingw-w64-x86_64-git-[0-9]*.tar.xz --pkg=pkg-x86_64/mingw-w64-x86_64-git-doc-html-[0-9]*.tar.xz &&
+            /usr/src/build-extra/please.sh make_installers_from_mingw_w64_git --version=`$(cat pkg-${{matrix.arch.name}}/ver) -o artifacts --${{matrix.artifact.name}} --pkg=pkg-${{matrix.arch.name}}/mingw-w64-${{matrix.arch.name}}-git-[0-9]*.tar.xz --pkg=pkg-${{matrix.arch.name}}/mingw-w64-${{matrix.arch.name}}-git-doc-html-[0-9]*.tar.xz &&
             if test portable = '${{matrix.artifact.name}}' && test -n \"`$(git config alias.signtool)\"
             then
               git signtool artifacts/PortableGit-*.exe
@@ -276,22 +308,22 @@ jobs:
         if: matrix.artifact.name == 'installer'
         shell: powershell
         run: |
-          & .\git-sdk-64-build-installers\usr\bin\bash.exe -lc @"
+          & .\git-sdk-${{matrix.arch.bitness}}-build-installers\usr\bin\bash.exe -lc @"
             cp /usr/src/build-extra/installer/package-versions.txt artifacts/ &&
 
             a=`$PWD/artifacts &&
-            p=`$PWD/pkg-x86_64 &&
+            p=`$PWD/pkg-${{matrix.arch.name}} &&
             (cd /usr/src/build-extra &&
             mkdir -p cached-source-packages &&
             cp \"`$p\"/*-pdb* cached-source-packages/ &&
-            GIT_CONFIG_PARAMETERS=\"'windows.sdk64.path='\" ./please.sh bundle_pdbs --arch=x86_64 --directory=\"`$a\" installer/package-versions.txt)
+            GIT_CONFIG_PARAMETERS=\"'windows.sdk${{matrix.arch.bitness}}.path='\" ./please.sh bundle_pdbs --arch=${{matrix.arch.name}} --directory=\"`$a\" installer/package-versions.txt)
           "@
       - name: Clean up temporary files
         if: always()
         shell: bash
         run: rm -rf home
-      - name: Publish ${{matrix.artifact.name}}-x86_64
+      - name: Publish ${{matrix.artifact.name}}-${{matrix.arch.name}}
         uses: actions/upload-artifact@v1
         with:
-          name: ${{matrix.artifact.name}}-x86_64
+          name: ${{matrix.artifact.name}}-${{matrix.arch.name}}
           path: artifacts

From 1e927c03beae56878e2169c008f2322f6706d230 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Thu, 29 Jul 2021 13:33:21 -0400
Subject: [PATCH 068/265] fsmonitor-settings: stub in Win32-specific
 incompatibility checking

Extend generic incompatibility checkout with platform-specific
mechanism.  Stub in Win32 version.

In the existing fsmonitor-settings code we have a way to mark
types of repos as incompatible with fsmonitor (whether via the
hook and IPC APIs).  For example, we do this for bare repos,
since there are no files to watch.

Extend this exclusion mechanism for platform-specific reasons.
This commit just creates the framework and adds a stub for Win32.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 Makefile                              | 13 +++++++++++++
 compat/fsmonitor/fsm-settings-win32.c |  9 +++++++++
 config.mak.uname                      |  4 ++++
 contrib/buildsystems/CMakeLists.txt   |  3 +++
 fsmonitor-settings.c                  | 12 ++++++++++++
 fsmonitor-settings.h                  | 13 +++++++++++++
 6 files changed, 54 insertions(+)
 create mode 100644 compat/fsmonitor/fsm-settings-win32.c

diff --git a/Makefile b/Makefile
index f8bccfab5e9c46..d7b6218a6134a1 100644
--- a/Makefile
+++ b/Makefile
@@ -480,6 +480,11 @@ include shared.mak
 # `compat/fsmonitor/fsm-listen-<name>.c` that implements the
 # `fsm_listen__*()` routines.
 #
+# If your platform has OS-specific ways to tell if a repo is incompatible with
+# fsmonitor (whether the hook or IPC daemon version), set FSMONITOR_OS_SETTINGS
+# to the "<name>" of the corresponding `compat/fsmonitor/fsm-settings-<name>.c`
+# that implements the `fsm_os_settings__*()` routines.
+#
 # Define DEVELOPER to enable more compiler warnings. Compiler version
 # and family are auto detected, but could be overridden by defining
 # COMPILER_FEATURES (see config.mak.dev). You can still set
@@ -2007,6 +2012,11 @@ ifdef FSMONITOR_DAEMON_BACKEND
 	COMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o
 endif
 
+ifdef FSMONITOR_OS_SETTINGS
+	COMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS
+	COMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_OS_SETTINGS).o
+endif
+
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK = NoThanks
 endif
@@ -2866,6 +2876,9 @@ GIT-BUILD-OPTIONS: FORCE
 ifdef FSMONITOR_DAEMON_BACKEND
 	@echo FSMONITOR_DAEMON_BACKEND=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_DAEMON_BACKEND)))'\' >>$@+
 endif
+ifdef FSMONITOR_OS_SETTINGS
+	@echo FSMONITOR_OS_SETTINGS=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_OS_SETTINGS)))'\' >>$@+
+endif
 ifdef TEST_OUTPUT_DIRECTORY
 	@echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@+
 endif
diff --git a/compat/fsmonitor/fsm-settings-win32.c b/compat/fsmonitor/fsm-settings-win32.c
new file mode 100644
index 00000000000000..7fce32a3c5ba0e
--- /dev/null
+++ b/compat/fsmonitor/fsm-settings-win32.c
@@ -0,0 +1,9 @@
+#include "cache.h"
+#include "config.h"
+#include "repository.h"
+#include "fsmonitor-settings.h"
+
+enum fsmonitor_reason fsm_os__incompatible(struct repository *r)
+{
+	return FSMONITOR_REASON_OK;
+}
diff --git a/config.mak.uname b/config.mak.uname
index 259d1511cafb76..50fb18ddcbd022 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -451,6 +451,8 @@ ifeq ($(uname_S),Windows)
 	# These are always available, so we do not have to conditionally
 	# support it.
 	FSMONITOR_DAEMON_BACKEND = win32
+	FSMONITOR_OS_SETTINGS = win32
+
 	NO_SVN_TESTS = YesPlease
 	RUNTIME_PREFIX = YesPlease
 	HAVE_WPGMPTR = YesWeDo
@@ -641,6 +643,8 @@ ifeq ($(uname_S),MINGW)
 	# These are always available, so we do not have to conditionally
 	# support it.
 	FSMONITOR_DAEMON_BACKEND = win32
+	FSMONITOR_OS_SETTINGS = win32
+
 	RUNTIME_PREFIX = YesPlease
 	HAVE_WPGMPTR = YesWeDo
 	NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index 185f56f414f4b7..be85cb2a8e1dc2 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -297,6 +297,9 @@ if(SUPPORTS_SIMPLE_IPC)
 	if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
 		add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)
 		list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)
+
+		add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)
+		list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c)
 	elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
 		add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)
 		list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)
diff --git a/fsmonitor-settings.c b/fsmonitor-settings.c
index 86c09bd35fe72f..8ff55f8c3fd923 100644
--- a/fsmonitor-settings.c
+++ b/fsmonitor-settings.c
@@ -33,6 +33,18 @@ static int check_for_incompatible(struct repository *r)
 		return 1;
 	}
 
+#ifdef HAVE_FSMONITOR_OS_SETTINGS
+	{
+		enum fsmonitor_reason reason;
+
+		reason = fsm_os__incompatible(r);
+		if (reason != FSMONITOR_REASON_OK) {
+			set_incompatible(r, reason);
+			return 1;
+		}
+	}
+#endif
+
 	return 0;
 }
 
diff --git a/fsmonitor-settings.h b/fsmonitor-settings.h
index 8654edf33d80d9..4b35f051fb1aa8 100644
--- a/fsmonitor-settings.h
+++ b/fsmonitor-settings.h
@@ -30,4 +30,17 @@ int fsm_settings__error_if_incompatible(struct repository *r);
 
 struct fsmonitor_settings;
 
+#ifdef HAVE_FSMONITOR_OS_SETTINGS
+/*
+ * Ask platform-specific code whether the repository is incompatible
+ * with fsmonitor (both hook and ipc modes).  For example, if the working
+ * directory is on a remote volume and mounted via a technology that does
+ * not support notification events, then we should not pretend to watch it.
+ *
+ * fsm_os__* routines should considered private to fsm_settings__
+ * routines.
+ */
+enum fsmonitor_reason fsm_os__incompatible(struct repository *r);
+#endif /* HAVE_FSMONITOR_OS_SETTINGS */
+
 #endif /* FSMONITOR_SETTINGS_H */

From 12c6c011089b9c51a9a4cefb67b748d4ec73ef1b Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Sun, 12 Apr 2020 00:52:22 +0200
Subject: [PATCH 069/265] git-artifacts: also build the nuget package

The two NuGet artifact exists only in the 64-bit version. So let's make
them in a separate, non-matrix job.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 49 +++++++++++++++++++++++++++++
 1 file changed, 49 insertions(+)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index 2b2ef6ebab234d..2f521175d20351 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -327,3 +327,52 @@ jobs:
         with:
           name: ${{matrix.artifact.name}}-${{matrix.arch.name}}
           path: artifacts
+  nuget:
+    runs-on: windows-latest
+    needs: pkg
+    steps:
+      - name: Download pkg-x86_64
+        uses: actions/download-artifact@v1
+        with:
+          name: pkg-x86_64
+          path: pkg-x86_64
+      - name: Download bundle-artifacts
+        uses: actions/download-artifact@v1
+        with:
+          name: bundle-artifacts
+          path: bundle-artifacts
+      - name: Download git-sdk-64-build-installers
+        shell: bash
+        run: |
+          # Use Git Bash to download and unpack the artifact
+
+          ## Get artifact
+          urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
+          id=${{ needs.pkg.outputs.latest-sdk64-extra-build-id }}
+          download_url=$(curl "$urlbase/$id/artifacts" |
+            jq -r '.value[] | select(.name == "git-sdk-64-build-installers").resource.downloadUrl')
+
+          curl -o artifacts.zip "$download_url"
+
+          ## Unpack artifact
+          unzip artifacts.zip
+      - name: Clone and update build-extra
+        shell: bash
+        run: |
+          d=git-sdk-64-build-installers/usr/src/build-extra &&
+          git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d &&
+          git -C $d pull "$PWD"/bundle-artifacts/build-extra.bundle main
+      - uses: nuget/setup-nuget@v1
+      - name: Build 64-bit NuGet packages
+        shell: powershell
+        run: |
+          & .\git-sdk-64-build-installers\usr\bin\bash.exe -lc @"
+            /usr/src/build-extra/please.sh make_installers_from_mingw_w64_git --version=`$(cat pkg-x86_64/ver) -o artifacts --nuget --pkg=pkg-x86_64/mingw-w64-x86_64-git-[0-9]*.tar.xz --pkg=pkg-x86_64/mingw-w64-x86_64-git-doc-html-[0-9]*.tar.xz &&
+            /usr/src/build-extra/please.sh make_installers_from_mingw_w64_git --version=`$(cat pkg-x86_64/ver) -o artifacts --nuget-mingit &&
+            openssl dgst -sha256 artifacts/Git*.nupkg | sed \"s/.* //\" >artifacts/sha-256.txt
+          "@
+      - name: Publish nuget-x86_64
+        uses: actions/upload-artifact@v1
+        with:
+          name: nuget-x86_64
+          path: artifacts

From e84e4d0de5e3cb41029973426a381e61f6c5f18c Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Mon, 2 Aug 2021 15:40:36 -0400
Subject: [PATCH 070/265] fsmonitor-settings: VFS for Git virtual repos are
 incompatible

VFS for Git virtual repositories are incompatible with FSMonitor.

VFS for Git is a downstream fork of Git.  It contains its own custom
file system watcher that is aware of the virtualization.  If a working
directory is being managed by VFS for Git, we should not try to watch
it because we may get incomplete results.

We do not know anything about how VFS for Git works, but we do
know that VFS for Git working directories contain a well-defined
config setting.  If it is set, mark the working directory as
incompatible.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/fsmonitor/fsm-settings-win32.c | 26 ++++++++++++++++++++++++++
 fsmonitor-settings.c                  |  5 +++++
 fsmonitor-settings.h                  |  1 +
 t/t7519-status-fsmonitor.sh           |  9 +++++++++
 4 files changed, 41 insertions(+)

diff --git a/compat/fsmonitor/fsm-settings-win32.c b/compat/fsmonitor/fsm-settings-win32.c
index 7fce32a3c5ba0e..ee78bba38e322d 100644
--- a/compat/fsmonitor/fsm-settings-win32.c
+++ b/compat/fsmonitor/fsm-settings-win32.c
@@ -3,7 +3,33 @@
 #include "repository.h"
 #include "fsmonitor-settings.h"
 
+/*
+ * VFS for Git is incompatible with FSMonitor.
+ *
+ * Granted, core Git does not know anything about VFS for Git and we
+ * shouldn't make assumptions about a downstream feature, but users
+ * can install both versions.  And this can lead to incorrect results
+ * from core Git commands.  So, without bringing in any of the VFS for
+ * Git code, do a simple config test for a published config setting.
+ * (We do not look at the various *_TEST_* environment variables.)
+ */
+static enum fsmonitor_reason check_vfs4git(struct repository *r)
+{
+	const char *const_str;
+
+	if (!repo_config_get_value(r, "core.virtualfilesystem", &const_str))
+		return FSMONITOR_REASON_VFS4GIT;
+
+	return FSMONITOR_REASON_OK;
+}
+
 enum fsmonitor_reason fsm_os__incompatible(struct repository *r)
 {
+	enum fsmonitor_reason reason;
+
+	reason = check_vfs4git(r);
+	if (reason != FSMONITOR_REASON_OK)
+		return reason;
+
 	return FSMONITOR_REASON_OK;
 }
diff --git a/fsmonitor-settings.c b/fsmonitor-settings.c
index 8ff55f8c3fd923..1efb6e17a20381 100644
--- a/fsmonitor-settings.c
+++ b/fsmonitor-settings.c
@@ -179,6 +179,11 @@ int fsm_settings__error_if_incompatible(struct repository *r)
 		error(_("bare repository '%s' is incompatible with fsmonitor"),
 		      xgetcwd());
 		return 1;
+
+	case FSMONITOR_REASON_VFS4GIT:
+		error(_("virtual repository '%s' is incompatible with fsmonitor"),
+		      r->worktree);
+		return 1;
 	}
 
 	BUG("Unhandled case in fsm_settings__error_if_incompatible: '%d'",
diff --git a/fsmonitor-settings.h b/fsmonitor-settings.h
index 4b35f051fb1aa8..6361fcbf6b0e5e 100644
--- a/fsmonitor-settings.h
+++ b/fsmonitor-settings.h
@@ -16,6 +16,7 @@ enum fsmonitor_mode {
 enum fsmonitor_reason {
 	FSMONITOR_REASON_OK = 0, /* no incompatibility or when disbled */
 	FSMONITOR_REASON_BARE,
+	FSMONITOR_REASON_VFS4GIT, /* VFS for Git virtualization */
 };
 
 void fsm_settings__set_ipc(struct repository *r);
diff --git a/t/t7519-status-fsmonitor.sh b/t/t7519-status-fsmonitor.sh
index 844e482d3c4a58..8348e3ae7db70d 100755
--- a/t/t7519-status-fsmonitor.sh
+++ b/t/t7519-status-fsmonitor.sh
@@ -78,6 +78,15 @@ test_expect_success FSMONITOR_DAEMON 'run fsmonitor-daemon in bare repo' '
 	grep "bare repository .* is incompatible with fsmonitor" actual
 '
 
+test_expect_success MINGW,FSMONITOR_DAEMON 'run fsmonitor-daemon in virtual repo' '
+	test_when_finished "rm -rf ./fake-virtual-clone actual" &&
+	git init fake-virtual-clone &&
+	test_must_fail git -C ./fake-virtual-clone \
+			   -c core.virtualfilesystem=true \
+			   fsmonitor--daemon run 2>actual &&
+	grep "virtual repository .* is incompatible with fsmonitor" actual
+'
+
 test_expect_success 'setup' '
 	: >tracked &&
 	: >modified &&

From 8ba209896b37d0b22ccf87ad5ab72f00288589a2 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 25 Feb 2021 00:06:41 +0100
Subject: [PATCH 071/265] git-artifacts(arm64): avoid hard-linking the dashed
 built-ins

The archive and MinGit variants really get bloated because they handle
those as straight copies instead of hard-links.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index 327683f35f8f2c..630a3e17b58273 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -288,7 +288,7 @@ jobs:
         run: |
           cmake `pwd`/contrib/buildsystems/ -DCMAKE_PREFIX_PATH=`pwd`/compat/vcbuild/vcpkg/installed/arm64-windows \
           -DNO_GETTEXT=YesPlease -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON -DCMAKE_GENERATOR_PLATFORM=arm64 -DVCPKG_ARCH=arm64-windows \
-          -DCMAKE_INSTALL_PREFIX="`pwd`/git-arm64"
+          -DCMAKE_INSTALL_PREFIX="`pwd`/git-arm64" -DSKIP_DASHED_BUILT_INS=ON
       - name: MSBuild
         run: msbuild git.sln -property:Configuration=Release
       - name: Link the Git executables

From 47a75934cf03d0f398a2fd65b5b47956c408755a Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Sat, 11 Apr 2020 23:43:55 +0200
Subject: [PATCH 072/265] git-artifacts: allow restricting which artifacts are
 built

Users can now specify which artifacts they want to build, via the
`build_only` input, which is a space-separated list of artifacts. For
example, `installer portable` will build `installer-x86_64`,
`installer-i686`, `portable-x86_64` and `portable-i686`, and an empty or
unset value will build all artifacts.

Please note that the `mingw-w64-git` packages are built always, as it
would be tricky to figure out when they need to be built (for example,
`build_only=portable-x86_64` technically does not need `pkg-i686` to be
built, while `build_only=portable` does).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 44 +++++++++++++++++++++++++----
 1 file changed, 38 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index 2f521175d20351..6700e7ef26cf04 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -3,13 +3,17 @@ name: git-artifacts
 on:
   # This workflow can be triggered manually in the Actions tab, see
   # https://github.blog/changelog/2020-07-06-github-actions-manual-triggers-with-workflow_dispatch/
-  - workflow_dispatch
+  workflow_dispatch:
+    inputs:
+      build_only:
+        description: 'Optionally restrict what artifacts to build'
 
 env:
   GPG_OPTIONS: "--batch --yes --no-tty --list-options no-show-photos --verify-options no-show-photos --pinentry-mode loopback"
   HOME: "${{github.workspace}}\\home"
   MSYSTEM: MINGW64
   USERPROFILE: "${{github.workspace}}\\home"
+  BUILD_ONLY: "${{github.event.inputs.build_only}}"
 
 jobs:
   bundle-artifacts:
@@ -232,18 +236,28 @@ jobs:
     env:
       MSYSTEM: MINGW${{matrix.arch.bitness}}
     steps:
+      - name: Determine whether this job should be skipped
+        shell: bash
+        run: |
+          case " $BUILD_ONLY " in
+          '  ') ;; # not set; build all
+          *" ${{matrix.artifact.name}} "*|*" ${{matrix.artifact.name}}-${{matrix.arch.name}} "*) ;; # build this artifact
+          *) echo "SKIP=true" >>$GITHUB_ENV;;
+          esac
       - name: Download pkg-${{matrix.arch.name}}
+        if: env.SKIP != 'true'
         uses: actions/download-artifact@v1
         with:
           name: pkg-${{matrix.arch.name}}
           path: pkg-${{matrix.arch.name}}
       - name: Download bundle-artifacts
+        if: env.SKIP != 'true'
         uses: actions/download-artifact@v1
         with:
           name: bundle-artifacts
           path: bundle-artifacts
       - name: Download git-sdk-64-build-installers
-        if: matrix.arch.bitness == '64'
+        if: env.SKIP != 'true' && matrix.arch.bitness == '64'
         shell: bash
         run: |
           # Use Git Bash to download and unpack the artifact
@@ -259,7 +273,7 @@ jobs:
           ## Unpack artifact
           unzip artifacts.zip
       - name: Download git-sdk-32-build-installers
-        if: matrix.arch.bitness == '32'
+        if: env.SKIP != 'true' && matrix.arch.bitness == '32'
         shell: bash
         run: |
           # Use Git Bash to download and unpack the artifact
@@ -276,6 +290,7 @@ jobs:
           ## Unpack artifact
           unzip artifacts.zip
       - name: Clone and update build-extra
+        if: env.SKIP != 'true'
         shell: bash
         run: |
           d=git-sdk-${{matrix.arch.bitness}}-build-installers/usr/src/build-extra &&
@@ -285,7 +300,7 @@ jobs:
         env:
           CODESIGN_P12: ${{secrets.CODESIGN_P12}}
           CODESIGN_PASS: ${{secrets.CODESIGN_PASS}}
-        if: (matrix.artifact.name == 'installer' || matrix.artifact.name == 'portable') && env.CODESIGN_P12 != '' && env.CODESIGN_PASS != ''
+        if: env.SKIP != 'true' && (matrix.artifact.name == 'installer' || matrix.artifact.name == 'portable') && env.CODESIGN_P12 != '' && env.CODESIGN_PASS != ''
         shell: bash
         run: |
           mkdir -p home/.sig &&
@@ -293,6 +308,7 @@ jobs:
           echo -n "$CODESIGN_PASS" >home/.sig/codesign.pass &&
           git config --global alias.signtool '!sh "/usr/src/build-extra/signtool.sh"'
       - name: Build ${{matrix.arch.bitness}}-bit ${{matrix.artifact.name}}
+        if: env.SKIP != 'true'
         shell: powershell
         run: |
           & .\git-sdk-${{matrix.arch.bitness}}-build-installers\usr\bin\bash.exe -lc @"
@@ -305,7 +321,7 @@ jobs:
             openssl dgst -sha256 artifacts/${{matrix.artifact.fileprefix}}-*.${{matrix.artifact.fileextension}} | sed \"s/.* //\" >artifacts/sha-256.txt
           "@
       - name: Copy package-versions and pdbs
-        if: matrix.artifact.name == 'installer'
+        if: env.SKIP != 'true' && matrix.artifact.name == 'installer'
         shell: powershell
         run: |
           & .\git-sdk-${{matrix.arch.bitness}}-build-installers\usr\bin\bash.exe -lc @"
@@ -319,10 +335,11 @@ jobs:
             GIT_CONFIG_PARAMETERS=\"'windows.sdk${{matrix.arch.bitness}}.path='\" ./please.sh bundle_pdbs --arch=${{matrix.arch.name}} --directory=\"`$a\" installer/package-versions.txt)
           "@
       - name: Clean up temporary files
-        if: always()
+        if: always() && env.SKIP != 'true'
         shell: bash
         run: rm -rf home
       - name: Publish ${{matrix.artifact.name}}-${{matrix.arch.name}}
+        if: env.SKIP != 'true'
         uses: actions/upload-artifact@v1
         with:
           name: ${{matrix.artifact.name}}-${{matrix.arch.name}}
@@ -331,17 +348,28 @@ jobs:
     runs-on: windows-latest
     needs: pkg
     steps:
+      - name: Determine whether this job should be skipped
+        shell: bash
+        run: |
+          case " $BUILD_ONLY " in
+          '  ') ;; # not set; build all
+          *" nuget "*) ;; # build this artifact
+          *) echo "SKIP=true" >>$GITHUB_ENV;;
+          esac
       - name: Download pkg-x86_64
+        if: env.SKIP != 'true'
         uses: actions/download-artifact@v1
         with:
           name: pkg-x86_64
           path: pkg-x86_64
       - name: Download bundle-artifacts
+        if: env.SKIP != 'true'
         uses: actions/download-artifact@v1
         with:
           name: bundle-artifacts
           path: bundle-artifacts
       - name: Download git-sdk-64-build-installers
+        if: env.SKIP != 'true'
         shell: bash
         run: |
           # Use Git Bash to download and unpack the artifact
@@ -357,13 +385,16 @@ jobs:
           ## Unpack artifact
           unzip artifacts.zip
       - name: Clone and update build-extra
+        if: env.SKIP != 'true'
         shell: bash
         run: |
           d=git-sdk-64-build-installers/usr/src/build-extra &&
           git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d &&
           git -C $d pull "$PWD"/bundle-artifacts/build-extra.bundle main
       - uses: nuget/setup-nuget@v1
+        if: env.SKIP != 'true'
       - name: Build 64-bit NuGet packages
+        if: env.SKIP != 'true'
         shell: powershell
         run: |
           & .\git-sdk-64-build-installers\usr\bin\bash.exe -lc @"
@@ -372,6 +403,7 @@ jobs:
             openssl dgst -sha256 artifacts/Git*.nupkg | sed \"s/.* //\" >artifacts/sha-256.txt
           "@
       - name: Publish nuget-x86_64
+        if: env.SKIP != 'true'
         uses: actions/upload-artifact@v1
         with:
           name: nuget-x86_64

From ff9543325df6303c2a9fd577e322fad3e047e790 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Wed, 4 Aug 2021 10:20:55 -0400
Subject: [PATCH 073/265] fsmonitor-settings: stub in macOS-specific
 incompatibility checking

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/fsmonitor/fsm-settings-darwin.c | 9 +++++++++
 config.mak.uname                       | 1 +
 contrib/buildsystems/CMakeLists.txt    | 3 +++
 3 files changed, 13 insertions(+)
 create mode 100644 compat/fsmonitor/fsm-settings-darwin.c

diff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-darwin.c
new file mode 100644
index 00000000000000..7fce32a3c5ba0e
--- /dev/null
+++ b/compat/fsmonitor/fsm-settings-darwin.c
@@ -0,0 +1,9 @@
+#include "cache.h"
+#include "config.h"
+#include "repository.h"
+#include "fsmonitor-settings.h"
+
+enum fsmonitor_reason fsm_os__incompatible(struct repository *r)
+{
+	return FSMONITOR_REASON_OK;
+}
diff --git a/config.mak.uname b/config.mak.uname
index 50fb18ddcbd022..ce83cad47a24f7 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -164,6 +164,7 @@ ifeq ($(uname_S),Darwin)
 	ifndef NO_PTHREADS
 	ifndef NO_UNIX_SOCKETS
 	FSMONITOR_DAEMON_BACKEND = darwin
+	FSMONITOR_OS_SETTINGS = darwin
 	endif
 	endif
 
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index be85cb2a8e1dc2..f04da4cfde6152 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -303,6 +303,9 @@ if(SUPPORTS_SIMPLE_IPC)
 	elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
 		add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)
 		list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)
+
+		add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)
+		list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)
 	endif()
 endif()
 

From 339b8eb75770c35a9dd39c0ed12d9255f7d9ab55 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 23 Feb 2021 11:39:15 +0100
Subject: [PATCH 074/265] git-artifacts: use a narrower `PATH`

GitHub workflows run in agents that have quite a bit of stuff in their
`PATH`, e.g. Chocolatey. To make sure that those bits and pieces are
_not_ used to build the artifacts, let's whittle down the `PATH` to
contain the bare minimum.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index 630a3e17b58273..f1b0426779e9b4 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -224,8 +224,10 @@ jobs:
         run: |
           & git-sdk-64-build-installers\usr\bin\sh.exe -lc @"
             set -x
-            # Let `cv2pdb` find the DLLs
-            PATH=\"`$PATH:/c/Program Files/Microsoft Visual Studio/2022/Enterprise/Common7/IDE/:/C/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin${{matrix.arch.bin}}\"
+
+            # Restrict `PATH` to MSYS2 and to Visual Studio (to let `cv2pdb` find the relevant DLLs)
+            PATH=\"`/mingw64/bin:/usr/bin:/c/Program Files/Microsoft Visual Studio/2022/Enterprise/Common7/IDE/:/C/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin${{matrix.arch.bin}}:/C/Windows/system32\"
+
             type -p mspdb140.dll || exit 1
             sh -x /usr/src/build-extra/please.sh build-mingw-w64-git --only-${{matrix.arch.bitness}}-bit --build-src-pkg -o artifacts HEAD &&
             cp bundle-artifacts/ver artifacts/ &&

From 10bfd6d7e801ec97d4078629d3f0d7bc3e1d3e74 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Sat, 11 Apr 2020 23:58:47 +0200
Subject: [PATCH 075/265] git-artifacts: allow specifying repo/ref via
 workflow_dispatch

With this change, users can specify the branch and repository from which
they want to build Git for Windows' artifacts, via the `ref` and
`repository` inputs.

This allows e.g. building `refs/heads/seen` of `git/git` (even if no
`git-artifacts` workflow is configured in that repository), or
`refs/pull/<number>/merge` for a given Pull Request.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index 6700e7ef26cf04..0090941ce0ac48 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -7,6 +7,10 @@ on:
     inputs:
       build_only:
         description: 'Optionally restrict what artifacts to build'
+      ref:
+        description: 'Optionally override which branch to build'
+      repository:
+        description: 'Optionally override from where to fetch the specified ref'
 
 env:
   GPG_OPTIONS: "--batch --yes --no-tty --list-options no-show-photos --verify-options no-show-photos --pinentry-mode loopback"
@@ -14,6 +18,8 @@ env:
   MSYSTEM: MINGW64
   USERPROFILE: "${{github.workspace}}\\home"
   BUILD_ONLY: "${{github.event.inputs.build_only}}"
+  REPOSITORY: "${{github.event.inputs.repository}}"
+  REF: "${{github.event.inputs.ref}}"
 
 jobs:
   bundle-artifacts:
@@ -70,9 +76,11 @@ jobs:
             printf '#!/bin/sh\n\nexec /mingw64/bin/git.exe "`$@"\n' >/usr/bin/git &&
             mkdir -p bundle-artifacts &&
 
+            { test -n \"`$REPOSITORY\" || REPOSITORY='${{github.repository}}'; } &&
+            { test -n \"`$REF\" || REF='${{github.ref}}'; } &&
             git -c init.defaultBranch=main init --bare &&
             git remote add -f origin https://github.com/git-for-windows/git &&
-            git fetch https://github.com/${{github.repository}} ${{github.ref}}:${{github.ref}} &&
+            git fetch \"https://github.com/`$REPOSITORY\" \"`$REF:`$REF\" &&
 
             tag_name=\"`$(git describe --match 'v[0-9]*' FETCH_HEAD)-`$(date +%Y%m%d%H%M%S)\" &&
             echo \"prerelease-`${tag_name#v}\" >bundle-artifacts/ver &&

From 72df76c4ac3cb047ba93254fd64db4b46a8b3996 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Wed, 4 Aug 2021 16:22:10 -0400
Subject: [PATCH 076/265] fsmonitor-settings: remote repos on macOS are
 incompatible

Teach Git to detect remote working directories on macOS and mark them as
incompatible with FSMonitor.

With this, `git fsmonitor--daemon run` will error out with a message
like it does for bare repos.

Client commands, like `git status`, will not attempt to start the daemon.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/fsmonitor/fsm-settings-darwin.c | 66 ++++++++++++++++++++++++++
 fsmonitor-settings.c                   | 10 ++++
 fsmonitor-settings.h                   |  2 +
 3 files changed, 78 insertions(+)

diff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-darwin.c
index 7fce32a3c5ba0e..fdd762bf79d125 100644
--- a/compat/fsmonitor/fsm-settings-darwin.c
+++ b/compat/fsmonitor/fsm-settings-darwin.c
@@ -2,8 +2,74 @@
 #include "config.h"
 #include "repository.h"
 #include "fsmonitor-settings.h"
+#include "fsmonitor.h"
+#include <sys/param.h>
+#include <sys/mount.h>
+
+/*
+ * Remote working directories are problematic for FSMonitor.
+ *
+ * The underlying file system on the server machine and/or the remote
+ * mount type (NFS, SAMBA, etc.) dictates whether notification events
+ * are available at all to remote client machines.
+ *
+ * Kernel differences between the server and client machines also
+ * dictate the how (buffering, frequency, de-dup) the events are
+ * delivered to client machine processes.
+ *
+ * A client machine (such as a laptop) may choose to suspend/resume
+ * and it is unclear (without lots of testing) whether the watcher can
+ * resync after a resume.  We might be able to treat this as a normal
+ * "events were dropped by the kernel" event and do our normal "flush
+ * and resync" --or-- we might need to close the existing (zombie?)
+ * notification fd and create a new one.
+ *
+ * In theory, the above issues need to be addressed whether we are
+ * using the Hook or IPC API.
+ *
+ * For the builtin FSMonitor, we create the Unix domain socket for the
+ * IPC in the .git directory.  If the working directory is remote,
+ * then the socket will be created on the remote file system.  This
+ * can fail if the remote file system does not support UDS file types
+ * (e.g. smbfs to a Windows server) or if the remote kernel does not
+ * allow a non-local process to bind() the socket.  (These problems
+ * could be fixed by moving the UDS out of the .git directory and to a
+ * well-known local directory on the client machine, but care should
+ * be taken to ensure that $HOME is actually local and not a managed
+ * file share.)
+ *
+ * So (for now at least), mark remote working directories as
+ * incompatible.
+ */
+static enum fsmonitor_reason check_remote(struct repository *r)
+{
+	struct statfs fs;
+
+	if (statfs(r->worktree, &fs) == -1) {
+		int saved_errno = errno;
+		trace_printf_key(&trace_fsmonitor, "statfs('%s') failed: %s",
+				 r->worktree, strerror(saved_errno));
+		errno = saved_errno;
+		return FSMONITOR_REASON_ERROR;
+	}
+
+	trace_printf_key(&trace_fsmonitor,
+			 "statfs('%s') [type 0x%08x][flags 0x%08x] '%s'",
+			 r->worktree, fs.f_type, fs.f_flags, fs.f_fstypename);
+
+	if (!(fs.f_flags & MNT_LOCAL))
+		return FSMONITOR_REASON_REMOTE;
+
+	return FSMONITOR_REASON_OK;
+}
 
 enum fsmonitor_reason fsm_os__incompatible(struct repository *r)
 {
+	enum fsmonitor_reason reason;
+
+	reason = check_remote(r);
+	if (reason != FSMONITOR_REASON_OK)
+		return reason;
+
 	return FSMONITOR_REASON_OK;
 }
diff --git a/fsmonitor-settings.c b/fsmonitor-settings.c
index 1efb6e17a20381..0a1811ff0043a8 100644
--- a/fsmonitor-settings.c
+++ b/fsmonitor-settings.c
@@ -180,6 +180,16 @@ int fsm_settings__error_if_incompatible(struct repository *r)
 		      xgetcwd());
 		return 1;
 
+	case FSMONITOR_REASON_ERROR:
+		error(_("repository '%s' is incompatible with fsmonitor due to errors"),
+		      r->worktree);
+		return 1;
+
+	case FSMONITOR_REASON_REMOTE:
+		error(_("remote repository '%s' is incompatible with fsmonitor"),
+		      r->worktree);
+		return 1;
+
 	case FSMONITOR_REASON_VFS4GIT:
 		error(_("virtual repository '%s' is incompatible with fsmonitor"),
 		      r->worktree);
diff --git a/fsmonitor-settings.h b/fsmonitor-settings.h
index 6361fcbf6b0e5e..34391b583b37ab 100644
--- a/fsmonitor-settings.h
+++ b/fsmonitor-settings.h
@@ -16,6 +16,8 @@ enum fsmonitor_mode {
 enum fsmonitor_reason {
 	FSMONITOR_REASON_OK = 0, /* no incompatibility or when disbled */
 	FSMONITOR_REASON_BARE,
+	FSMONITOR_REASON_ERROR, /* FS error probing for compatibility */
+	FSMONITOR_REASON_REMOTE,
 	FSMONITOR_REASON_VFS4GIT, /* VFS for Git virtualization */
 };
 

From ce3ae5f01fe78a27ffb489b8fae27399a9147f32 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 23 Feb 2021 12:25:34 +0100
Subject: [PATCH 077/265] git-artifacts: fix BUILD_ONLY handling for ARM64

The workflow allows users to restrict what parts are being built. For
example, `installer-i686` will build only the 32-bit installer, not the
64-bit one nor any MinGit flavor.

However, this logic was not extended when introducing support for ARM64:
Instead, we _also_ built the ARM64 installer when the user asked for
`installer-i686`.

Let's allow restricting to `installer-i686` _without_ building the ARM64
version, and allow restricting to `installer-arm64` _just_ for the ARM64
version.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index f1b0426779e9b4..8b47fa4c3ef569 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -338,9 +338,14 @@ jobs:
       - name: Determine whether this job should be skipped
         shell: bash
         run: |
+          suffix=${{matrix.arch.name}}
+          if test true = ${{matrix.arch.arm64}}
+          then
+            suffix=arm64
+          fi
           case " $BUILD_ONLY " in
           '  ') ;; # not set; build all
-          *" ${{matrix.artifact.name}} "*|*" ${{matrix.artifact.name}}-${{matrix.arch.name}} "*) ;; # build this artifact
+          *" ${{matrix.artifact.name}} "*|*" ${{matrix.artifact.name}}-$suffix "*) ;; # build this artifact
           *) echo "SKIP=true" >>$GITHUB_ENV;;
           esac
       - name: Download pkg-${{matrix.arch.name}}

From 2b790f298e16ee78818838bf17bb728fa60f4053 Mon Sep 17 00:00:00 2001
From: Dennis Ameling <dennis@dennisameling.com>
Date: Thu, 28 Jan 2021 21:31:05 +0100
Subject: [PATCH 078/265] git-artifacts: cache the build-installers artifact

It is a bit expensive to fetch just the git-sdk-64-build-installers
artifact from Azure Pipelines and then to unpack it (takes some 6-7
minutes, typically). Let's cache it if possible.

Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 89 +++++++++++++++++++++++++----
 1 file changed, 79 insertions(+), 10 deletions(-)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index 0090941ce0ac48..c2767fe26693b1 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -24,6 +24,8 @@ env:
 jobs:
   bundle-artifacts:
     runs-on: windows-latest
+    outputs:
+      latest-sdk64-extra-build-id: ${{ steps.determine-latest-sdk64-extra-build-id.outputs.id }}
     steps:
       - name: Configure user
         shell: bash
@@ -34,15 +36,31 @@ jobs:
           git config --global user.name "$USER_NAME" &&
           git config --global user.email "$USER_EMAIL" &&
           echo "PACKAGER=$USER_NAME <$USER_EMAIL>" >>$GITHUB_ENV
+      - name: Determine latest git-sdk-64-extra-artifacts build ID
+        id: determine-latest-sdk64-extra-build-id
+        shell: bash
+        run: |
+          urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
+          id=$(curl "$urlbase?definitions=29&statusFilter=completed&resultFilter=succeeded&\$top=1" |
+            jq -r '.value[0].id')
+
+          echo "Latest ID is ${id}"
+          echo "::set-output name=id::$id"
+      - name: Cache git-sdk-64-build-installers
+        id: cache-sdk-build-installers
+        uses: actions/cache@v2
+        with:
+          path: git-sdk-64-build-installers
+          key: build-installers-64-${{ steps.determine-latest-sdk64-extra-build-id.outputs.id }}
       - name: Download git-sdk-64-build-installers
+        if: steps.cache-sdk-build-installers.outputs.cache-hit != 'true'
         shell: bash
         run: |
           # Use Git Bash to download and unpack the artifact
 
           ## Get artifact
           urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
-          id=$(curl "$urlbase?definitions=29&statusFilter=completed&resultFilter=succeeded&\$top=1" |
-            jq -r '.value[0].id')
+          id=${{ steps.determine-latest-sdk64-extra-build-id.outputs.id }}
           download_url=$(curl "$urlbase/$id/artifacts" |
             jq -r '.value[] | select(.name == "git-sdk-64-build-installers").resource.downloadUrl')
 
@@ -54,7 +72,13 @@ jobs:
         shell: bash
         run: |
           d=git-sdk-64-build-installers/usr/src/build-extra &&
-          git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d
+          if test ! -d $d/.git
+          then
+            git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d
+          else
+            git -C $d fetch https://github.com/git-for-windows/build-extra main &&
+            git -C $d switch -C main FETCH_HEAD
+          fi
       - name: Prepare home directory for GPG signing
         if: env.GPGKEY != ''
         shell: bash
@@ -104,6 +128,8 @@ jobs:
   pkg:
     runs-on: windows-latest
     needs: bundle-artifacts
+    outputs:
+      latest-sdk64-extra-build-id: ${{ needs.bundle-artifacts.outputs.latest-sdk64-extra-build-id }}
     strategy:
       matrix:
         arch:
@@ -264,8 +290,15 @@ jobs:
         with:
           name: bundle-artifacts
           path: bundle-artifacts
-      - name: Download git-sdk-64-build-installers
+      - name: Cache git-sdk-64-build-installers
         if: env.SKIP != 'true' && matrix.arch.bitness == '64'
+        id: cache-sdk64-build-installers
+        uses: actions/cache@v2
+        with:
+          path: git-sdk-64-build-installers
+          key: build-installers-64-${{ needs.pkg.outputs.latest-sdk64-extra-build-id }}
+      - name: Download git-sdk-64-build-installers
+        if: env.SKIP != 'true' && matrix.arch.bitness == '64' && steps.cache-sdk64-build-installers.outputs.cache-hit != 'true'
         shell: bash
         run: |
           # Use Git Bash to download and unpack the artifact
@@ -280,16 +313,33 @@ jobs:
 
           ## Unpack artifact
           unzip artifacts.zip
-      - name: Download git-sdk-32-build-installers
+      - name: Determine latest git-sdk-32-extra-artifacts build ID
         if: env.SKIP != 'true' && matrix.arch.bitness == '32'
+        id: determine-latest-sdk32-extra-build-id
+        shell: bash
+        run: |
+          urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
+          id=$(curl "$urlbase?definitions=30&statusFilter=completed&resultFilter=succeeded&\$top=1" |
+            jq -r '.value[0].id')
+
+          echo "Latest ID is ${id}"
+          echo "::set-output name=id::$id"
+      - name: Cache git-sdk-32-build-installers
+        if: env.SKIP != 'true' && matrix.arch.bitness == '32'
+        id: cache-sdk32-build-installers
+        uses: actions/cache@v2
+        with:
+          path: git-sdk-32-build-installers
+          key: build-installers-32-${{ steps.determine-latest-sdk32-extra-build-id.outputs.id }}
+      - name: Download git-sdk-32-build-installers
+        if: env.SKIP != 'true' && matrix.arch.bitness == '32' && steps.cache-sdk32-build-installers.outputs.cache-hit != 'true'
         shell: bash
         run: |
           # Use Git Bash to download and unpack the artifact
 
           ## Get artifact
           urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
-          id=$(curl "$urlbase?definitions=30&statusFilter=completed&resultFilter=succeeded&\$top=1" |
-            jq -r '.value[0].id')
+          id=${{ steps.determine-latest-sdk32-extra-build-id.outputs.id }}
           download_url=$(curl "$urlbase/$id/artifacts" |
             jq -r '.value[] | select(.name == "git-sdk-32-build-installers").resource.downloadUrl')
 
@@ -302,7 +352,13 @@ jobs:
         shell: bash
         run: |
           d=git-sdk-${{matrix.arch.bitness}}-build-installers/usr/src/build-extra &&
-          git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d &&
+          if test ! -d $d/.git
+          then
+            git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d
+          else
+            git -C $d fetch https://github.com/git-for-windows/build-extra main &&
+            git -C $d switch -C main FETCH_HEAD
+          fi &&
           git -C $d pull "$PWD"/bundle-artifacts/build-extra.bundle main
       - name: Prepare home directory for code-signing
         env:
@@ -376,8 +432,15 @@ jobs:
         with:
           name: bundle-artifacts
           path: bundle-artifacts
-      - name: Download git-sdk-64-build-installers
+      - name: Cache git-sdk-64-build-installers
         if: env.SKIP != 'true'
+        id: cache-sdk-build-installers
+        uses: actions/cache@v2
+        with:
+          path: git-sdk-64-build-installers
+          key: build-installers-64-${{ needs.pkg.outputs.latest-sdk64-extra-build-id }}
+      - name: Download git-sdk-64-build-installers
+        if: env.SKIP != 'true' && steps.cache-sdk-build-installers.outputs.cache-hit != 'true'
         shell: bash
         run: |
           # Use Git Bash to download and unpack the artifact
@@ -397,7 +460,13 @@ jobs:
         shell: bash
         run: |
           d=git-sdk-64-build-installers/usr/src/build-extra &&
-          git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d &&
+          if test ! -d $d/.git
+          then
+            git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d
+          else
+            git -C $d fetch https://github.com/git-for-windows/build-extra main &&
+            git -C $d switch -C main FETCH_HEAD
+          fi &&
           git -C $d pull "$PWD"/bundle-artifacts/build-extra.bundle main
       - uses: nuget/setup-nuget@v1
         if: env.SKIP != 'true'

From 2afd0f864e291ac9638c2e799970f8021f65cef6 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Thu, 5 Aug 2021 09:07:48 -0400
Subject: [PATCH 079/265] fsmonitor-settings: remote repos on Windows are
 incompatible

Teach Git to detect remote working directories on Windows and mark them as
incompatible with FSMonitor.

With this `git fsmonitor--daemon run` will error out with a message like it
does for bare repos.

Client commands, such as `git status`, will not attempt to start the daemon.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/fsmonitor/fsm-settings-win32.c | 102 ++++++++++++++++++++++++++
 1 file changed, 102 insertions(+)

diff --git a/compat/fsmonitor/fsm-settings-win32.c b/compat/fsmonitor/fsm-settings-win32.c
index ee78bba38e322d..907655720bb41e 100644
--- a/compat/fsmonitor/fsm-settings-win32.c
+++ b/compat/fsmonitor/fsm-settings-win32.c
@@ -2,6 +2,7 @@
 #include "config.h"
 #include "repository.h"
 #include "fsmonitor-settings.h"
+#include "fsmonitor.h"
 
 /*
  * VFS for Git is incompatible with FSMonitor.
@@ -23,6 +24,103 @@ static enum fsmonitor_reason check_vfs4git(struct repository *r)
 	return FSMONITOR_REASON_OK;
 }
 
+/*
+ * Remote working directories are problematic for FSMonitor.
+ *
+ * The underlying file system on the server machine and/or the remote
+ * mount type dictates whether notification events are available at
+ * all to remote client machines.
+ *
+ * Kernel differences between the server and client machines also
+ * dictate the how (buffering, frequency, de-dup) the events are
+ * delivered to client machine processes.
+ *
+ * A client machine (such as a laptop) may choose to suspend/resume
+ * and it is unclear (without lots of testing) whether the watcher can
+ * resync after a resume.  We might be able to treat this as a normal
+ * "events were dropped by the kernel" event and do our normal "flush
+ * and resync" --or-- we might need to close the existing (zombie?)
+ * notification fd and create a new one.
+ *
+ * In theory, the above issues need to be addressed whether we are
+ * using the Hook or IPC API.
+ *
+ * So (for now at least), mark remote working directories as
+ * incompatible.
+ *
+ * Notes for testing:
+ *
+ * (a) Windows allows a network share to be mapped to a drive letter.
+ *     (This is the normal method to access it.)
+ *
+ *     $ NET USE Z: \\server\share
+ *     $ git -C Z:/repo status
+ *
+ * (b) Windows allows a network share to be referenced WITHOUT mapping
+ *     it to drive letter.
+ *
+ *     $ NET USE \\server\share\dir
+ *     $ git -C //server/share/repo status
+ *
+ * (c) Windows allows "SUBST" to create a fake drive mapping to an
+ *     arbitrary path (which may be remote)
+ *
+ *     $ SUBST Q: Z:\repo
+ *     $ git -C Q:/ status
+ *
+ * (d) Windows allows a directory symlink to be created on a local
+ *     file system that points to a remote repo.
+ *
+ *     $ mklink /d ./link //server/share/repo
+ *     $ git -C ./link status
+ */
+static enum fsmonitor_reason check_remote(struct repository *r)
+{
+	wchar_t wpath[MAX_PATH];
+	wchar_t wfullpath[MAX_PATH];
+	size_t wlen;
+	UINT driveType;
+
+	/*
+	 * Do everything in wide chars because the drive letter might be
+	 * a multi-byte sequence.  See win32_has_dos_drive_prefix().
+	 */
+	if (xutftowcs_path(wpath, r->worktree) < 0)
+		return FSMONITOR_REASON_ERROR;
+
+	/*
+	 * GetDriveTypeW() requires a final slash.  We assume that the
+	 * worktree pathname points to an actual directory.
+	 */
+	wlen = wcslen(wpath);
+	if (wpath[wlen - 1] != L'\\' && wpath[wlen - 1] != L'/') {
+		wpath[wlen++] = L'\\';
+		wpath[wlen] = 0;
+	}
+
+	/*
+	 * Normalize the path.  If nothing else, this converts forward
+	 * slashes to backslashes.  This is essential to get GetDriveTypeW()
+	 * correctly handle some UNC "\\server\share\..." paths.
+	 */
+	if (!GetFullPathNameW(wpath, MAX_PATH, wfullpath, NULL))
+		return FSMONITOR_REASON_ERROR;
+
+	driveType = GetDriveTypeW(wfullpath);
+	trace_printf_key(&trace_fsmonitor,
+			 "DriveType '%s' L'%ls' (%u)",
+			 r->worktree, wfullpath, driveType);
+
+	if (driveType == DRIVE_REMOTE) {
+		trace_printf_key(&trace_fsmonitor,
+				 "check_remote('%s') true",
+				 r->worktree);
+		return FSMONITOR_REASON_REMOTE;
+	}
+
+	return FSMONITOR_REASON_OK;
+}
+
 enum fsmonitor_reason fsm_os__incompatible(struct repository *r)
 {
 	enum fsmonitor_reason reason;
@@ -31,5 +129,9 @@ enum fsmonitor_reason fsm_os__incompatible(struct repository *r)
 	if (reason != FSMONITOR_REASON_OK)
 		return reason;
 
+	reason = check_remote(r);
+	if (reason != FSMONITOR_REASON_OK)
+		return reason;
+
 	return FSMONITOR_REASON_OK;
 }

From 37796238ee44296498ba384fef921cdc099cc20b Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 23 Feb 2021 12:31:24 +0100
Subject: [PATCH 080/265] git-artifacts: extend the `SKIP` logic to handle
 `pkg` and `build-arm64`

When the user asked for `installer-x86_64`, there is no point in
building `pkg-i686` or `build-arm64`; Let's be a bit smarter about this.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 50 ++++++++++++++++++++++++++---
 1 file changed, 46 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index 8b47fa4c3ef569..f6537f6c5acd3c 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -140,7 +140,21 @@ jobs:
             bitness: 32
             bin: ''
     steps:
+      - name: Determine whether this job should be skipped
+        shell: bash
+        run: |
+          for e in ${BUILD_ONLY:-pkg}
+          do
+            case $e in
+            *-${{matrix.arch.name}}) exit 0;; # build this artifact
+            *-arm64) test i686 != ${{matrix.arch.name}} || exit 0;; # pkg-i686 is required for the ARM64 version
+            *-*) ;; # not this build artifact, keep looking
+            *) exit 0;; # build this artifact
+            esac
+          done
+          echo "SKIP=true" >>$GITHUB_ENV
       - name: Configure user
+        if: env.SKIP != 'true'
         shell: bash
         run:
           USER_NAME="${{github.actor}}" &&
@@ -150,13 +164,14 @@ jobs:
           git config --global user.email "$USER_EMAIL" &&
           echo "PACKAGER=$USER_NAME <$USER_EMAIL>" >>$GITHUB_ENV
       - name: Cache git-sdk-64-build-installers
+        if: env.SKIP != 'true'
         id: cache-sdk-build-installers
         uses: actions/cache@v2
         with:
           path: git-sdk-64-build-installers
           key: build-installers-64-${{ needs.bundle-artifacts.outputs.latest-sdk64-extra-build-id }}
       - name: Download git-sdk-64-build-installers
-        if: steps.cache-sdk-build-installers.outputs.cache-hit != 'true'
+        if: env.SKIP != 'true' && steps.cache-sdk-build-installers.outputs.cache-hit != 'true'
         shell: bash
         run: |
           # Use Git Bash to download and unpack the artifact
@@ -172,11 +187,13 @@ jobs:
           ## Unpack artifact
           unzip artifacts.zip
       - name: Download bundle-artifacts
+        if: env.SKIP != 'true'
         uses: actions/download-artifact@v1
         with:
           name: bundle-artifacts
           path: bundle-artifacts
       - name: Clone and update build-extra
+        if: env.SKIP != 'true'
         shell: bash
         run: |
           d=git-sdk-64-build-installers/usr/src/build-extra &&
@@ -189,6 +206,7 @@ jobs:
           fi &&
           git -C $d pull "$PWD"/bundle-artifacts/build-extra.bundle main
       - name: Check out git/git
+        if: env.SKIP != 'true'
         shell: bash
         run: |
           git -c init.defaultBranch=main init &&
@@ -199,7 +217,7 @@ jobs:
         env:
           CODESIGN_P12: ${{secrets.CODESIGN_P12}}
           CODESIGN_PASS: ${{secrets.CODESIGN_PASS}}
-        if: env.CODESIGN_P12 != '' && env.CODESIGN_PASS != ''
+        if: env.SKIP != 'true' && env.CODESIGN_P12 != '' && env.CODESIGN_PASS != ''
         shell: bash
         run: |
           cd home &&
@@ -208,7 +226,7 @@ jobs:
           echo -n "$CODESIGN_PASS" >.sig/codesign.pass
           git config --global alias.signtool '!sh "/usr/src/build-extra/signtool.sh"'
       - name: Prepare home directory for GPG signing
-        if: env.GPGKEY != ''
+        if: env.SKIP != 'true' && env.GPGKEY != ''
         shell: bash
         run: |
           echo '${{secrets.PRIVGPGKEY}}' | tr % '\n' | gpg $GPG_OPTIONS --import &&
@@ -218,6 +236,7 @@ jobs:
         env:
           GPGKEY: ${{secrets.GPGKEY}}
       - name: Build mingw-w64-${{matrix.arch.name}}-git
+        if: env.SKIP != 'true'
         env:
           GPGKEY: "${{secrets.GPGKEY}}"
         shell: powershell
@@ -247,10 +266,11 @@ jobs:
             git bundle create \"`$b\"/MINGW-packages.bundle origin/main..main)
           "@
       - name: Clean up temporary files
-        if: always()
+        if: always() && env.SKIP != 'true'
         shell: bash
         run: rm -rf home
       - name: Publish mingw-w64-${{matrix.arch.name}}-git
+        if: env.SKIP != 'true'
         uses: actions/upload-artifact@v1
         with:
           name: pkg-${{matrix.arch.name}}
@@ -259,7 +279,20 @@ jobs:
     needs: bundle-artifacts
     runs-on: windows-latest
     steps:
+      - name: Determine whether this job should be skipped
+        shell: bash
+        run: |
+          for e in ${BUILD_ONLY:-pkg}
+          do
+            case $e in
+            *-arm64) exit 0;; # build this artifact
+            *-*) ;; # not this build artifact, keep looking
+            *) exit 0;; # build this artifact
+            esac
+          done
+          echo "SKIP=true" >>$GITHUB_ENV
       - name: Configure user
+        if: env.SKIP != 'true'
         shell: bash
         run:
           USER_NAME="${{github.actor}}" &&
@@ -268,34 +301,43 @@ jobs:
           git config --global user.name "$USER_NAME" &&
           git config --global user.email "$USER_EMAIL"
       - uses: actions/checkout@v2
+        if: env.SKIP != 'true'
       - name: initialize vcpkg
+        if: env.SKIP != 'true'
         uses: actions/checkout@v2
         with:
           repository: 'microsoft/vcpkg'
           path: 'compat/vcbuild/vcpkg'
       - name: download vcpkg artifacts
+        if: env.SKIP != 'true'
         uses: git-for-windows/get-azure-pipelines-artifact@v0
         with:
           repository: git/git
           definitionId: 9
       - name: add msbuild to PATH
+        if: env.SKIP != 'true'
         uses: microsoft/setup-msbuild@v1
       - name: copy dlls to root
+        if: env.SKIP != 'true'
         shell: powershell
         run: |
           & compat\vcbuild\vcpkg_copy_dlls.bat release arm64-windows
           if (!$?) { exit(1) }
       - name: generate Visual Studio solution
+        if: env.SKIP != 'true'
         shell: bash
         run: |
           cmake `pwd`/contrib/buildsystems/ -DCMAKE_PREFIX_PATH=`pwd`/compat/vcbuild/vcpkg/installed/arm64-windows \
           -DNO_GETTEXT=YesPlease -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON -DCMAKE_GENERATOR_PLATFORM=arm64 -DVCPKG_ARCH=arm64-windows \
           -DCMAKE_INSTALL_PREFIX="`pwd`/git-arm64" -DSKIP_DASHED_BUILT_INS=ON
       - name: MSBuild
+        if: env.SKIP != 'true'
         run: msbuild git.sln -property:Configuration=Release
       - name: Link the Git executables
+        if: env.SKIP != 'true'
         run: msbuild INSTALL.vcxproj -property:Configuration=Release
       - name: upload build artifacts
+        if: env.SKIP != 'true'
         uses: actions/upload-artifact@v1
         with:
           name: arm64-artifacts

From dfc7bdc1ab30bed087581f592870e3a347ffc7b1 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 3 Feb 2021 15:52:58 +0100
Subject: [PATCH 081/265] git-artifacts: use the cached build-installers
 instead of makepkg-git

When building the Pacman packages, we technically do not need the full
`build-installers` artifact (which is substantially larger than the
`makepkg-git` artifact). However, the former is already cached and
includes the latter's files. And it is _so_ much faster to download the
cached (larger) artifact than to download the smaller `makepkg-git`
artifact from Azure Pipelines.

Suggested-by: Dennis Ameling <dennis@dennisameling.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 27 ++++++++++++++++++++-------
 1 file changed, 20 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index c2767fe26693b1..ba954605845d09 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -149,16 +149,23 @@ jobs:
           git config --global user.name "$USER_NAME" &&
           git config --global user.email "$USER_EMAIL" &&
           echo "PACKAGER=$USER_NAME <$USER_EMAIL>" >>$GITHUB_ENV
-      - name: Download git-sdk-64-makepkg-git
+      - name: Cache git-sdk-64-build-installers
+        id: cache-sdk-build-installers
+        uses: actions/cache@v2
+        with:
+          path: git-sdk-64-build-installers
+          key: build-installers-64-${{ needs.bundle-artifacts.outputs.latest-sdk64-extra-build-id }}
+      - name: Download git-sdk-64-build-installers
+        if: steps.cache-sdk-build-installers.outputs.cache-hit != 'true'
         shell: bash
         run: |
           # Use Git Bash to download and unpack the artifact
 
           ## Get artifact
           urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
-          id=${{ needs.bundle-artifacts.outputs.latest-sdk64-extra-build-id }}
-          download_url="$(curl "$urlbase/$id/artifacts" |
-            jq -r '.value[] | select(.name == "git-sdk-64-makepkg-git").resource.downloadUrl')"
+          id=${{ needs.pkg.outputs.latest-sdk64-extra-build-id }}
+          download_url=$(curl "$urlbase/$id/artifacts" |
+            jq -r '.value[] | select(.name == "git-sdk-64-build-installers").resource.downloadUrl')
 
           curl -o artifacts.zip "$download_url"
 
@@ -172,8 +179,14 @@ jobs:
       - name: Clone and update build-extra
         shell: bash
         run: |
-          d=git-sdk-64-makepkg-git/usr/src/build-extra &&
-          git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d &&
+          d=git-sdk-64-build-installers/usr/src/build-extra &&
+          if test ! -d $d/.git
+          then
+            git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d
+          else
+            git -C $d fetch https://github.com/git-for-windows/build-extra main &&
+            git -C $d switch -C main FETCH_HEAD
+          fi &&
           git -C $d pull "$PWD"/bundle-artifacts/build-extra.bundle main
       - name: Check out git/git
         shell: bash
@@ -209,7 +222,7 @@ jobs:
           GPGKEY: "${{secrets.GPGKEY}}"
         shell: powershell
         run: |
-          & git-sdk-64-makepkg-git\usr\bin\sh.exe -lc @"
+          & git-sdk-64-build-installers\usr\bin\sh.exe -lc @"
             set -x
             # Let `cv2pdb` find the DLLs
             PATH=\"`$PATH:/c/Program Files/Microsoft Visual Studio/2022/Enterprise/Common7/IDE/:/C/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin${{matrix.arch.bin}}\"

From dd71ec28cb6d406095a9558afd4e0f9f2e22b989 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Tue, 8 Mar 2022 14:30:49 -0500
Subject: [PATCH 082/265] fsmonitor-settings: NTFS and FAT32 on MacOS are
 incompatible

On MacOS mark repos on NTFS or FAT32 volumes as incompatible.

The builtin FSMonitor used Unix domain sockets on MacOS for IPC
with clients.  These sockets are kept in the .git directory.
Unix sockets are not supported by NTFS and FAT32, so the daemon
cannot start up.

Test for this during our compatibility checking so that client
commands do not keep trying to start the daemon.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/fsmonitor/fsm-settings-darwin.c | 20 +++++++++++++++++---
 fsmonitor-settings.c                   |  5 +++++
 fsmonitor-settings.h                   |  1 +
 3 files changed, 23 insertions(+), 3 deletions(-)

diff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-darwin.c
index fdd762bf79d125..efc732c0f317fc 100644
--- a/compat/fsmonitor/fsm-settings-darwin.c
+++ b/compat/fsmonitor/fsm-settings-darwin.c
@@ -7,7 +7,7 @@
 #include <sys/mount.h>
 
 /*
- * Remote working directories are problematic for FSMonitor.
+ * [1] Remote working directories are problematic for FSMonitor.
  *
  * The underlying file system on the server machine and/or the remote
  * mount type (NFS, SAMBA, etc.) dictates whether notification events
@@ -40,8 +40,16 @@
  *
  * So (for now at least), mark remote working directories as
  * incompatible.
+ *
+ *
+ * [2] FAT32 and NTFS working directories are problematic too.
+ *
+ * The builtin FSMonitor uses a Unix domain socket in the .git
+ * directory for IPC.  These Windows drive formats do not support
+ * Unix domain sockets, so mark them as incompatible for the daemon.
+ *
  */
-static enum fsmonitor_reason check_remote(struct repository *r)
+static enum fsmonitor_reason check_volume(struct repository *r)
 {
 	struct statfs fs;
 
@@ -60,6 +68,12 @@ static enum fsmonitor_reason check_remote(struct repository *r)
 	if (!(fs.f_flags & MNT_LOCAL))
 		return FSMONITOR_REASON_REMOTE;
 
+	if (!strcmp(fs.f_fstypename, "msdos")) /* aka FAT32 */
+		return FSMONITOR_REASON_NOSOCKETS;
+
+	if (!strcmp(fs.f_fstypename, "ntfs"))
+		return FSMONITOR_REASON_NOSOCKETS;
+
 	return FSMONITOR_REASON_OK;
 }
 
@@ -67,7 +81,7 @@ enum fsmonitor_reason fsm_os__incompatible(struct repository *r)
 {
 	enum fsmonitor_reason reason;
 
-	reason = check_remote(r);
+	reason = check_volume(r);
 	if (reason != FSMONITOR_REASON_OK)
 		return reason;
 
diff --git a/fsmonitor-settings.c b/fsmonitor-settings.c
index 0a1811ff0043a8..60d5eaee497f3d 100644
--- a/fsmonitor-settings.c
+++ b/fsmonitor-settings.c
@@ -194,6 +194,11 @@ int fsm_settings__error_if_incompatible(struct repository *r)
 		error(_("virtual repository '%s' is incompatible with fsmonitor"),
 		      r->worktree);
 		return 1;
+
+	case FSMONITOR_REASON_NOSOCKETS:
+		error(_("repository '%s' is incompatible with fsmonitor due to lack of Unix sockets"),
+		      r->worktree);
+		return 1;
 	}
 
 	BUG("Unhandled case in fsm_settings__error_if_incompatible: '%d'",
diff --git a/fsmonitor-settings.h b/fsmonitor-settings.h
index 34391b583b37ab..23d5676c8c8fa4 100644
--- a/fsmonitor-settings.h
+++ b/fsmonitor-settings.h
@@ -19,6 +19,7 @@ enum fsmonitor_reason {
 	FSMONITOR_REASON_ERROR, /* FS error probing for compatibility */
 	FSMONITOR_REASON_REMOTE,
 	FSMONITOR_REASON_VFS4GIT, /* VFS for Git virtualization */
+	FSMONITOR_REASON_NOSOCKETS, /* NTFS,FAT32 do not support Unix sockets */
 };
 
 void fsm_settings__set_ipc(struct repository *r);

From 8f5518c4d03240f95a84df97088bf1ce877d0735 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 23 Feb 2021 12:44:29 +0100
Subject: [PATCH 083/265] git-artifacts: mark all inputs as "not required"

This workflow needs to be triggered manually, and it offers to specify a
couple input parameters. But none of them are required. Make that
explicit.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index f6537f6c5acd3c..04ce23498887d3 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -7,10 +7,13 @@ on:
     inputs:
       build_only:
         description: 'Optionally restrict what artifacts to build'
+        required: false
       ref:
         description: 'Optionally override which branch to build'
+        required: false
       repository:
         description: 'Optionally override from where to fetch the specified ref'
+        required: false
 
 env:
   GPG_OPTIONS: "--batch --yes --no-tty --list-options no-show-photos --verify-options no-show-photos --pinentry-mode loopback"

From 82ab0d62285fabfe9b84b3198c4704915ed97a5c Mon Sep 17 00:00:00 2001
From: Dennis Ameling <dennis@dennisameling.com>
Date: Thu, 4 Feb 2021 16:49:01 +0100
Subject: [PATCH 084/265] git-artifacts: add ARM64 artifacts

Adds ARM64 artifacts to the git-artifacts GitHub Action workflow.

Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 75 +++++++++++++++++++++++++++--
 1 file changed, 72 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index ba954605845d09..327683f35f8f2c 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -253,9 +253,54 @@ jobs:
         with:
           name: pkg-${{matrix.arch.name}}
           path: artifacts
+  build-arm64:
+    needs: bundle-artifacts
+    runs-on: windows-latest
+    steps:
+      - name: Configure user
+        shell: bash
+        run:
+          USER_NAME="${{github.actor}}" &&
+          USER_EMAIL="${{github.actor}}@users.noreply.github.com" &&
+          mkdir -p "$HOME" &&
+          git config --global user.name "$USER_NAME" &&
+          git config --global user.email "$USER_EMAIL"
+      - uses: actions/checkout@v2
+      - name: initialize vcpkg
+        uses: actions/checkout@v2
+        with:
+          repository: 'microsoft/vcpkg'
+          path: 'compat/vcbuild/vcpkg'
+      - name: download vcpkg artifacts
+        uses: git-for-windows/get-azure-pipelines-artifact@v0
+        with:
+          repository: git/git
+          definitionId: 9
+      - name: add msbuild to PATH
+        uses: microsoft/setup-msbuild@v1
+      - name: copy dlls to root
+        shell: powershell
+        run: |
+          & compat\vcbuild\vcpkg_copy_dlls.bat release arm64-windows
+          if (!$?) { exit(1) }
+      - name: generate Visual Studio solution
+        shell: bash
+        run: |
+          cmake `pwd`/contrib/buildsystems/ -DCMAKE_PREFIX_PATH=`pwd`/compat/vcbuild/vcpkg/installed/arm64-windows \
+          -DNO_GETTEXT=YesPlease -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON -DCMAKE_GENERATOR_PLATFORM=arm64 -DVCPKG_ARCH=arm64-windows \
+          -DCMAKE_INSTALL_PREFIX="`pwd`/git-arm64"
+      - name: MSBuild
+        run: msbuild git.sln -property:Configuration=Release
+      - name: Link the Git executables
+        run: msbuild INSTALL.vcxproj -property:Configuration=Release
+      - name: upload build artifacts
+        uses: actions/upload-artifact@v1
+        with:
+          name: arm64-artifacts
+          path: ./git-arm64
   artifacts:
     runs-on: windows-latest
-    needs: pkg
+    needs: [pkg, build-arm64]
     strategy:
       matrix:
         artifact:
@@ -277,8 +322,13 @@ jobs:
         arch:
           - name: x86_64
             bitness: 64
+            arm64: false
           - name: i686
             bitness: 32
+            arm64: false
+          - name: i686
+            bitness: 32
+            arm64: true
       fail-fast: false
     env:
       MSYSTEM: MINGW${{matrix.arch.bitness}}
@@ -360,6 +410,12 @@ jobs:
 
           ## Unpack artifact
           unzip artifacts.zip
+      - name: Download arm64 artifact
+        if: env.SKIP != 'true' && matrix.arch.arm64 == true
+        uses: actions/download-artifact@v1
+        with:
+          name: arm64-artifacts
+          path: ${{github.workspace}}/arm64
       - name: Clone and update build-extra
         if: env.SKIP != 'true'
         shell: bash
@@ -390,7 +446,14 @@ jobs:
         run: |
           & .\git-sdk-${{matrix.arch.bitness}}-build-installers\usr\bin\bash.exe -lc @"
             set -x
-            /usr/src/build-extra/please.sh make_installers_from_mingw_w64_git --version=`$(cat pkg-${{matrix.arch.name}}/ver) -o artifacts --${{matrix.artifact.name}} --pkg=pkg-${{matrix.arch.name}}/mingw-w64-${{matrix.arch.name}}-git-[0-9]*.tar.xz --pkg=pkg-${{matrix.arch.name}}/mingw-w64-${{matrix.arch.name}}-git-doc-html-[0-9]*.tar.xz &&
+            if test "${{matrix.arch.arm64}}" = true
+            then
+              ARM64="--include-arm64-artifacts=\"$PWD/arm64\""
+            else
+              ARM64=
+            fi
+
+            eval /usr/src/build-extra/please.sh make_installers_from_mingw_w64_git `$ARM64 --version=`$(cat pkg-${{matrix.arch.name}}/ver) -o artifacts --${{matrix.artifact.name}} --pkg=pkg-${{matrix.arch.name}}/mingw-w64-${{matrix.arch.name}}-git-[0-9]*.tar.xz --pkg=pkg-${{matrix.arch.name}}/mingw-w64-${{matrix.arch.name}}-git-doc-html-[0-9]*.tar.xz &&
             if test portable = '${{matrix.artifact.name}}' && test -n \"`$(git config alias.signtool)\"
             then
               git signtool artifacts/PortableGit-*.exe
@@ -416,11 +479,17 @@ jobs:
         shell: bash
         run: rm -rf home
       - name: Publish ${{matrix.artifact.name}}-${{matrix.arch.name}}
-        if: env.SKIP != 'true'
+        if: env.SKIP != 'true' && matrix.arch.arm64 != true
         uses: actions/upload-artifact@v1
         with:
           name: ${{matrix.artifact.name}}-${{matrix.arch.name}}
           path: artifacts
+      - name: Publish ${{matrix.artifact.name}}-arm64
+        if: env.SKIP != 'true' && matrix.arch.arm64 == true
+        uses: actions/upload-artifact@v1
+        with:
+          name: ${{matrix.artifact.name}}-arm64
+          path: artifacts
   nuget:
     runs-on: windows-latest
     needs: pkg

From d31f3ed8d44d17766e932a597ee38e9064aeb2b2 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Mon, 23 Aug 2021 17:42:28 -0400
Subject: [PATCH 085/265] unpack-trees: initialize fsmonitor_has_run_once in
 o->result

Initialize `o->result.fsmonitor_has_run_once` based upon value
in `o->src_index->fsmonitor_has_run_once` to prevent a second
fsmonitor query during the tree traversal and possibly getting
a skewed view of the working directory.

The checkout code has already talked to the fsmonitor and the
traversal is updating the index as it traverses, so there is
no need to query the fsmonitor.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 unpack-trees.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/unpack-trees.c b/unpack-trees.c
index 7f528d35cc263a..bbbcf83fa1c981 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -1838,6 +1838,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
 
 	o->result.fsmonitor_last_update =
 		xstrdup_or_null(o->src_index->fsmonitor_last_update);
+	o->result.fsmonitor_has_run_once = o->src_index->fsmonitor_has_run_once;
 
 	/*
 	 * Sparse checkout loop #1: set NEW_SKIP_WORKTREE on existing entries

From 463ca5842d3703e9f1fea7f0792ff9945f1b9ce3 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 23 Feb 2021 13:32:31 +0100
Subject: [PATCH 086/265] git-artifacts(build-arm64): build artifacts using the
 intended Git revision

We cannot just check out the current revision: The user might have
overridden `REPOSITORY` and `REF` via the workflow dispatch.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index 04ce23498887d3..35dd92fc521457 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -303,8 +303,20 @@ jobs:
           mkdir -p "$HOME" &&
           git config --global user.name "$USER_NAME" &&
           git config --global user.email "$USER_EMAIL"
-      - uses: actions/checkout@v2
+      - name: Download bundle-artifacts
+        if: env.SKIP != 'true'
+        uses: actions/download-artifact@v1
+        with:
+          name: bundle-artifacts
+          path: bundle-artifacts
+      - name: Check out git/git
         if: env.SKIP != 'true'
+        shell: bash
+        run: |
+          git -c init.defaultBranch=main init &&
+          git remote add -f origin https://github.com/git-for-windows/git &&
+          git fetch --tags bundle-artifacts/git.bundle $(cat bundle-artifacts/next_version) &&
+          git reset --hard $(cat bundle-artifacts/next_version)
       - name: initialize vcpkg
         if: env.SKIP != 'true'
         uses: actions/checkout@v2

From 0bf0250014bf654c573e49f8d46eb9158d34748b Mon Sep 17 00:00:00 2001
From: Dennis Ameling <dennis@dennisameling.com>
Date: Wed, 3 Mar 2021 11:59:05 +0100
Subject: [PATCH 087/265] git-artifacts: add workaround for GCM Core on ARM64

Since there is no GCM Core for ARM64, let's just install a simple shell
script that calls the i686 version for now.

Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index 327683f35f8f2c..64dc1dbcef4b21 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -416,6 +416,13 @@ jobs:
         with:
           name: arm64-artifacts
           path: ${{github.workspace}}/arm64
+      # Workaround for Git Credential Manager Core on ARM64: https://github.com/git-for-windows/git/issues/3015
+      - name: Create git-credential-manager-core wrapper for ARM64
+        if: env.SKIP != 'true' && matrix.arch.arm64 == true
+        shell: bash
+        run: |
+          printf '%s\n' '#!/bin/sh' 'exec /mingw32/libexec/git-core/git-credential-manager-core.exe "$@"' > arm64/libexec/git-core/git-credential-manager-core
+          chmod +x arm64/libexec/git-core/git-credential-manager-core
       - name: Clone and update build-extra
         if: env.SKIP != 'true'
         shell: bash

From e887fa227ce6a498683d4d3d45d2da72a56ef179 Mon Sep 17 00:00:00 2001
From: Dennis Ameling <dennis@dennisameling.com>
Date: Mon, 19 Jul 2021 13:02:16 +0200
Subject: [PATCH 088/265] cmake(): allow setting HOST_CPU for cross-compilation

Git's regular Makefile mentions that HOST_CPU should be defined when cross-compiling Git: https://github.com/git-for-windows/git/blob/37796bca76ef4180c39ee508ca3e42c0777ba444/Makefile#L438-L439

This is then used to set the GIT_HOST_CPU variable when compiling Git: https://github.com/git-for-windows/git/blob/37796bca76ef4180c39ee508ca3e42c0777ba444/Makefile#L1337-L1341

Then, when the user runs `git version --build-options`, it returns that value: https://github.com/git-for-windows/git/blob/37796bca76ef4180c39ee508ca3e42c0777ba444/help.c#L658

This commit adds the same functionality to the CMake configuration. Users can now set -DHOST_CPU= to set the target architecture.

Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
---
 .github/workflows/main.yml          | 2 +-
 contrib/buildsystems/CMakeLists.txt | 9 ++++++++-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 1a42e31cbe82c9..f02c25fa27178c 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -166,7 +166,7 @@ jobs:
       shell: bash
       run: |
         cmake `pwd`/contrib/buildsystems/ -DCMAKE_PREFIX_PATH=`pwd`/compat/vcbuild/vcpkg/installed/${{ matrix.arch }}-windows \
-        -DNO_GETTEXT=YesPlease -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON -DCMAKE_GENERATOR_PLATFORM=${{ matrix.arch }} -DVCPKG_ARCH=${{ matrix.arch }}-windows
+        -DNO_GETTEXT=YesPlease -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON -DCMAKE_GENERATOR_PLATFORM=${{ matrix.arch }} -DVCPKG_ARCH=${{ matrix.arch }}-windows -DHOST_CPU=${{ matrix.arch }}
     - name: MSBuild
       run: msbuild git.sln -property:Configuration=Release -property:Platform=${{ matrix.arch }} -maxCpuCount:4 -property:PlatformToolset=v142
     - name: bundle artifact tar
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index 402bc2f68c34aa..5405f114e438cb 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -213,7 +213,14 @@ endif()
 
 #default behaviour
 include_directories(${CMAKE_SOURCE_DIR})
-add_compile_definitions(GIT_HOST_CPU="${CMAKE_SYSTEM_PROCESSOR}")
+
+# When cross-compiling, define HOST_CPU as the canonical name of the CPU on
+# which the built Git will run (for instance "x86_64").
+if(NOT HOST_CPU)
+	add_compile_definitions(GIT_HOST_CPU="${CMAKE_SYSTEM_PROCESSOR}")
+else()
+	add_compile_definitions(GIT_HOST_CPU="${HOST_CPU}")
+endif()
 add_compile_definitions(SHA256_BLK INTERNAL_QSORT RUNTIME_PREFIX)
 add_compile_definitions(NO_OPENSSL SHA1_DC SHA1DC_NO_STANDARD_INCLUDES
 			SHA1DC_INIT_SAFE_HASH_DEFAULT=0

From 4b4357b045906511a5cf5bdeb258d5804b01c055 Mon Sep 17 00:00:00 2001
From: Philip Oakley <philipoakley@iee.email>
Date: Fri, 2 Jul 2021 00:30:24 +0100
Subject: [PATCH 089/265] CMake: default Visual Studio generator has changed

Correct some wording and inform users regarding the Visual Studio
changes (from V16.6) to the default generator.

Subsequent commits ensure that Git for Windows can be directly
opened in modern Visual Studio without needing special configuration
of the CMakeLists settings.

It appeares that internally Visual Studio creates it's own version of the
.sln file (etc.) for extension tools that expect them.

The large number of references below document the shifting of Visual Studio
default and CMake setting options.

refs: https://docs.microsoft.com/en-us/search/?scope=C%2B%2B&view=msvc-150&terms=Ninja

1. https://docs.microsoft.com/en-us/cpp/linux/cmake-linux-configure?view=msvc-160
(note the linux bit)
 "In Visual Studio 2019 version 16.6 or later ***, Ninja is the default
generator for configurations targeting a remote system or WSL. For more
information, see this post on the C++ Team Blog
[https://devblogs.microsoft.com/cppblog/linux-development-with-visual-studio-first-class-support-for-gdbserver-improved-build-times-with-ninja-and-updates-to-the-connection-manager/].

For more information about these settings, see CMakeSettings.json reference
[https://docs.microsoft.com/en-us/cpp/build/cmakesettings-reference?view=msvc-160]."

2. https://docs.microsoft.com/en-us/cpp/build/cmake-presets-vs?view=msvc-160
"CMake supports two files that allow users to specify common configure,
build, and test options and share them with others: CMakePresets.json
and CMakeUserPresets.json."

" Both files are supported in Visual Studio 2019 version 16.10 or later.
***"
3. https://devblogs.microsoft.com/cppblog/linux-development-with-visual-studio-first-class-support-for-gdbserver-improved-build-times-with-ninja-and-updates-to-the-connection-manager/
" Ninja has been the default generator (underlying build system) for
CMake configurations targeting Windows for some time***, but in Visual
Studio 2019 version 16.6 Preview 3*** we added support for Ninja on Linux."

4. https://docs.microsoft.com/en-us/cpp/build/cmakesettings-reference?view=msvc-160
" `generator`: specifies CMake generator to use for this configuration.
May be one of:

    Visual Studio 2019 only:
        Visual Studio 16 2019
        Visual Studio 16 2019 Win64
        Visual Studio 16 2019 ARM

    Visual Studio 2017 and later:
        Visual Studio 15 2017
        Visual Studio 15 2017 Win64
        Visual Studio 15 2017 ARM
        Visual Studio 14 2015
        Visual Studio 14 2015 Win64
        Visual Studio 14 2015 ARM
        Unix Makefiles
        Ninja

Because Ninja is designed for fast build speeds instead of flexibility
and function, it is set as the default. However, some CMake projects may
be unable to correctly build using Ninja. If this occurs, you can
instruct CMake to generate Visual Studio projects instead.

To specify a Visual Studio generator in Visual Studio 2017, open the
settings editor from the main menu by choosing CMake | Change CMake
Settings. Delete "Ninja" and type "V". This activates IntelliSense,
which enables you to choose the generator you want."

"To specify a Visual Studio generator in Visual Studio 2019, right-click
on the CMakeLists.txt file in Solution Explorer and choose CMake
Settings for project > Show Advanced Settings > CMake Generator.

When the active configuration specifies a Visual Studio generator, by
default MSBuild.exe is invoked with` -m -v:minimal` arguments."

5. https://docs.microsoft.com/en-us/cpp/build/cmake-presets-vs?view=msvc-160#enable-cmakepresetsjson-integration-in-visual-studio-2019
"Enable CMakePresets.json integration in Visual Studio 2019

CMakePresets.json integration isn't enabled by default in Visual Studio
2019. You can enable it for all CMake projects in Tools > Options >
CMake > General: (tick a box)" ... see more.

6. https://docs.microsoft.com/en-us/cpp/build/cmakesettings-reference?view=msvc-140
(whichever v140 is..)
"CMake projects are supported in Visual Studio 2017 and later."

7. https://docs.microsoft.com/en-us/cpp/overview/what-s-new-for-cpp-2017?view=msvc-150
"Support added for the CMake Ninja generator."

8. https://docs.microsoft.com/en-us/cpp/overview/what-s-new-for-cpp-2017?view=msvc-150#cmake-support-via-open-folder
"CMake support via Open Folder
Visual Studio 2017 introduces support for using CMake projects without
converting to MSBuild project files (.vcxproj). For more information,
see CMake projects in Visual
Studio[https://docs.microsoft.com/en-us/cpp/build/cmake-projects-in-visual-studio?view=msvc-150].
Opening CMake projects with Open Folder automatically configures the
environment for C++ editing, building, and debugging." ... +more!

9. https://docs.microsoft.com/en-us/cpp/build/cmake-presets-vs?view=msvc-160#supported-cmake-and-cmakepresetsjson-versions
"Visual Studio reads and evaluates CMakePresets.json and
CMakeUserPresets.json itself and doesn't invoke CMake directly with the
--preset option. So, CMake version 3.20 or later isn't strictly required
when you're building with CMakePresets.json inside Visual Studio. We
recommend using CMake version 3.14 or later."

10. https://docs.microsoft.com/en-us/cpp/build/cmake-presets-vs?view=msvc-160#enable-cmakepresetsjson-integration-in-visual-studio-2019
"If you don't want to enable CMakePresets.json integration for all CMake
projects, you can enable CMakePresets.json integration for a single
CMake project by adding a CMakePresets.json file to the root of the open
folder. You must close and reopen the folder in Visual Studio to
activate the integration.

11. https://docs.microsoft.com/en-us/cpp/build/cmake-presets-vs?view=msvc-160#default-configure-presets
***(doesn't actually say which version..)
"Default Configure Presets
If no CMakePresets.json or CMakeUserPresets.json file exists, or if
CMakePresets.json or CMakeUserPresets.json is invalid, Visual Studio
will fall back*** on the following default Configure Presets:

Windows example
JSON
{
  "name": "windows-default",
  "displayName": "Windows x64 Debug",
  "description": "Sets Ninja generator, compilers, x64 architecture,
build and install directory, debug build type",
  "generator": "Ninja",
  "binaryDir": "${sourceDir}/out/build/${presetName}",
  "architecture": {
    "value": "x64",
    "strategy": "external"
  },
  "cacheVariables": {
    "CMAKE_BUILD_TYPE": "Debug",
    "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}"
  },
  "vendor": {
    "microsoft.com/VisualStudioSettings/CMake/1.0": {
      "hostOS": [ "Windows" ]
    }
  }
},
"

Signed-off-by: Philip Oakley <philipoakley@iee.email>
---
 contrib/buildsystems/CMakeLists.txt | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index 5405f114e438cb..a214bfc8953e4e 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -14,6 +14,11 @@ Note: Visual Studio also has the option of opening `CMakeLists.txt`
 directly; Using this option, Visual Studio will not find the source code,
 though, therefore the `File>Open>Folder...` option is preferred.
 
+Visual Studio does not produce a .sln solution file nor the .vcxproj files
+that may be required by VS extension tools.
+
+To generate the .sln/.vcxproj files run CMake manually, as described below.
+
 Instructions to run CMake manually:
 
     mkdir -p contrib/buildsystems/out
@@ -22,7 +27,7 @@ Instructions to run CMake manually:
 
 This will build the git binaries in contrib/buildsystems/out
 directory (our top-level .gitignore file knows to ignore contents of
-this directory).
+this directory). The project .sln and .vcxproj files are also generated.
 
 Possible build configurations(-DCMAKE_BUILD_TYPE) with corresponding
 compiler flags
@@ -35,17 +40,16 @@ empty(default) :
 NOTE: -DCMAKE_BUILD_TYPE is optional. For multi-config generators like Visual Studio
 this option is ignored
 
-This process generates a Makefile(Linux/*BSD/MacOS) , Visual Studio solution(Windows) by default.
+This process generates a Makefile(Linux/*BSD/MacOS), Visual Studio solution(Windows) by default.
 Run `make` to build Git on Linux/*BSD/MacOS.
 Open git.sln on Windows and build Git.
 
-NOTE: By default CMake uses Makefile as the build tool on Linux and Visual Studio in Windows,
-to use another tool say `ninja` add this to the command line when configuring.
-`-G Ninja`
-
 NOTE: By default CMake will install vcpkg locally to your source tree on configuration,
 to avoid this, add `-DNO_VCPKG=TRUE` to the command line when configuring.
 
+The Visual Studio default generator changed in v16.6 from its Visual Studio
+implemenation to `Ninja` This required changes to many CMake scripts.
+
 ]]
 cmake_minimum_required(VERSION 3.14)
 

From 0d126679d308d03c31dfbdba0ee0fa39e6069112 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Wed, 25 Aug 2021 15:11:11 -0400
Subject: [PATCH 090/265] fsm-listen-darwin: ignore FSEvents caused by xattr
 changes on macOS

Ignore FSEvents resulting from `xattr` changes.  Git does not care about
xattr's or changes to xattr's, so don't waste time collecting these
events in the daemon nor transmitting them to clients.

Various security tools add xattrs to files and/or directories, such as
to mark them as having been downloaded.  We should ignore these events
since it doesn't affect the content of the file/directory or the normal
meta-data that Git cares about.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/fsmonitor/fsm-listen-darwin.c | 34 +++++++++++++++++++++++++++-
 1 file changed, 33 insertions(+), 1 deletion(-)

diff --git a/compat/fsmonitor/fsm-listen-darwin.c b/compat/fsmonitor/fsm-listen-darwin.c
index 0741fe834c3168..14105f45c18fe4 100644
--- a/compat/fsmonitor/fsm-listen-darwin.c
+++ b/compat/fsmonitor/fsm-listen-darwin.c
@@ -100,7 +100,7 @@ static void log_flags_set(const char *path, const FSEventStreamEventFlags flag)
 	if (flag & kFSEventStreamEventFlagItemCloned)
 		strbuf_addstr(&msg, "ItemCloned|");
 
-	trace_printf_key(&trace_fsmonitor, "fsevent: '%s', flags=%u %s",
+	trace_printf_key(&trace_fsmonitor, "fsevent: '%s', flags=0x%x %s",
 			 path, flag, msg.buf);
 
 	strbuf_release(&msg);
@@ -125,6 +125,31 @@ static int ef_is_dropped(const FSEventStreamEventFlags ef)
 		ef & kFSEventStreamEventFlagUserDropped);
 }
 
+/*
+ * If an `xattr` change is the only reason we received this event,
+ * then silently ignore it.  Git doesn't care about xattr's.  We
+ * have to be careful here because the kernel can combine multiple
+ * events for a single path.  And because events always have certain
+ * bits set, such as `ItemIsFile` or `ItemIsDir`.
+ *
+ * Return 1 if we should ignore it.
+ */
+static int ef_ignore_xattr(const FSEventStreamEventFlags ef)
+{
+	static const FSEventStreamEventFlags mask =
+		kFSEventStreamEventFlagItemChangeOwner |
+		kFSEventStreamEventFlagItemCreated |
+		kFSEventStreamEventFlagItemFinderInfoMod |
+		kFSEventStreamEventFlagItemInodeMetaMod |
+		kFSEventStreamEventFlagItemModified |
+		kFSEventStreamEventFlagItemRemoved |
+		kFSEventStreamEventFlagItemRenamed |
+		kFSEventStreamEventFlagItemXattrMod |
+		kFSEventStreamEventFlagItemCloned;
+
+	return ((ef & mask) == kFSEventStreamEventFlagItemXattrMod);
+}
+
 static void fsevent_callback(ConstFSEventStreamRef streamRef,
 			     void *ctx,
 			     size_t num_of_events,
@@ -190,6 +215,13 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,
 			continue;
 		}
 
+		if (ef_ignore_xattr(event_flags[k])) {
+			trace_printf_key(&trace_fsmonitor,
+					 "ignore-xattr: '%s', flags=0x%x",
+					 path_k, event_flags[k]);
+			continue;
+		}
+
 		switch (fsmonitor_classify_path_absolute(state, path_k)) {
 
 		case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:

From f69d5492b43868aec1483632506b6415f6f7d75d Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 23 Feb 2021 11:35:47 +0100
Subject: [PATCH 091/265] git-artifacts: Use the shiny new
 setup-git-for-windows-sdk Action

This simplifies the workflow dramatically.

Note that we have to reinstate that `/usr/bin/git` hack (a shell script
that simply redirects to `/mingw64/bin/git.exe`) in the `pkg` job
manually, since we no longer cache the `build-installers` artifact
_after_ installing that hack in `bundle-artifacts`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/git-artifacts.yml | 288 ++++++++--------------------
 1 file changed, 81 insertions(+), 207 deletions(-)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index 35dd92fc521457..1f63d9fa419ba9 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -27,8 +27,6 @@ env:
 jobs:
   bundle-artifacts:
     runs-on: windows-latest
-    outputs:
-      latest-sdk64-extra-build-id: ${{ steps.determine-latest-sdk64-extra-build-id.outputs.id }}
     steps:
       - name: Configure user
         shell: bash
@@ -39,42 +37,13 @@ jobs:
           git config --global user.name "$USER_NAME" &&
           git config --global user.email "$USER_EMAIL" &&
           echo "PACKAGER=$USER_NAME <$USER_EMAIL>" >>$GITHUB_ENV
-      - name: Determine latest git-sdk-64-extra-artifacts build ID
-        id: determine-latest-sdk64-extra-build-id
-        shell: bash
-        run: |
-          urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
-          id=$(curl "$urlbase?definitions=29&statusFilter=completed&resultFilter=succeeded&\$top=1" |
-            jq -r '.value[0].id')
-
-          echo "Latest ID is ${id}"
-          echo "::set-output name=id::$id"
-      - name: Cache git-sdk-64-build-installers
-        id: cache-sdk-build-installers
-        uses: actions/cache@v2
+      - uses: git-for-windows/setup-git-for-windows-sdk@v0
         with:
-          path: git-sdk-64-build-installers
-          key: build-installers-64-${{ steps.determine-latest-sdk64-extra-build-id.outputs.id }}
-      - name: Download git-sdk-64-build-installers
-        if: steps.cache-sdk-build-installers.outputs.cache-hit != 'true'
-        shell: bash
-        run: |
-          # Use Git Bash to download and unpack the artifact
-
-          ## Get artifact
-          urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
-          id=${{ steps.determine-latest-sdk64-extra-build-id.outputs.id }}
-          download_url=$(curl "$urlbase/$id/artifacts" |
-            jq -r '.value[] | select(.name == "git-sdk-64-build-installers").resource.downloadUrl')
-
-          curl -o artifacts.zip "$download_url"
-
-          ## Unpack artifact
-          unzip artifacts.zip
+          flavor: build-installers
       - name: Clone build-extra
         shell: bash
         run: |
-          d=git-sdk-64-build-installers/usr/src/build-extra &&
+          d=/usr/src/build-extra &&
           if test ! -d $d/.git
           then
             git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d
@@ -88,7 +57,7 @@ jobs:
         run: |
           echo '${{secrets.PRIVGPGKEY}}' | tr % '\n' | gpg $GPG_OPTIONS --import &&
           mkdir -p home &&
-          git config --global gpg.program "$PWD/git-sdk-64-build-installers/usr/src/build-extra/gnupg-with-gpgkey.sh" &&
+          git config --global gpg.program "/usr/src/build-extra/gnupg-with-gpgkey.sh" &&
           info="$(gpg --list-keys --with-colons "${GPGKEY%% *}" | cut -d : -f 1,10 | sed -n '/^uid/{s|uid:||p;q}')" &&
           git config --global user.name "${info% <*}" &&
           git config --global user.email "<${info#*<}"
@@ -97,28 +66,26 @@ jobs:
       - name: Generate bundle artifacts
         env:
           GPGKEY: ${{secrets.GPGKEY}}
-        shell: powershell
+        shell: bash
         run: |
-          & .\git-sdk-64-build-installers\git-cmd.exe --command=usr\bin\bash.exe -lc @"
-            printf '#!/bin/sh\n\nexec /mingw64/bin/git.exe "`$@"\n' >/usr/bin/git &&
-            mkdir -p bundle-artifacts &&
+          printf '#!/bin/sh\n\nexec /mingw64/bin/git.exe "$@"\n' >/usr/bin/git &&
+          mkdir -p bundle-artifacts &&
 
-            { test -n \"`$REPOSITORY\" || REPOSITORY='${{github.repository}}'; } &&
-            { test -n \"`$REF\" || REF='${{github.ref}}'; } &&
-            git -c init.defaultBranch=main init --bare &&
-            git remote add -f origin https://github.com/git-for-windows/git &&
-            git fetch \"https://github.com/`$REPOSITORY\" \"`$REF:`$REF\" &&
+          { test -n "$REPOSITORY" || REPOSITORY='${{github.repository}}'; } &&
+          { test -n "$REF" || REF='${{github.ref}}'; } &&
+          git -c init.defaultBranch=main init --bare &&
+          git remote add -f origin https://github.com/git-for-windows/git &&
+          git fetch "https://github.com/$REPOSITORY" "$REF:$REF" &&
 
-            tag_name=\"`$(git describe --match 'v[0-9]*' FETCH_HEAD)-`$(date +%Y%m%d%H%M%S)\" &&
-            echo \"prerelease-`${tag_name#v}\" >bundle-artifacts/ver &&
-            echo \"`${tag_name#v}\" >bundle-artifacts/display_version &&
-            echo \"`$tag_name\" >bundle-artifacts/next_version &&
-            git tag `$(test -z \"`$GPGKEY\" || echo \" -s\") -m \"Snapshot build\" \"`$tag_name\" FETCH_HEAD &&
-            git bundle create bundle-artifacts/git.bundle origin/main..\"`$tag_name\" &&
+          tag_name="$(git describe --match 'v[0-9]*' FETCH_HEAD)-$(date +%Y%m%d%H%M%S)" &&
+          echo "prerelease-${tag_name#v}" >bundle-artifacts/ver &&
+          echo "${tag_name#v}" >bundle-artifacts/display_version &&
+          echo "$tag_name" >bundle-artifacts/next_version &&
+          git tag $(test -z "$GPGKEY" || echo " -s") -m "Snapshot build" "$tag_name" FETCH_HEAD &&
+          git bundle create bundle-artifacts/git.bundle origin/main.."$tag_name" &&
 
-            sh -x /usr/src/build-extra/please.sh mention feature \"Snapshot of `$(git show -s  --pretty='tformat:%h (%s, %ad)' --date=short FETCH_HEAD)\" &&
-            git -C /usr/src/build-extra bundle create \"`$PWD/bundle-artifacts/build-extra.bundle\" origin/main..main
-          "@
+          sh -x /usr/src/build-extra/please.sh mention feature "Snapshot of $(git show -s  --pretty='tformat:%h (%s, %ad)' --date=short FETCH_HEAD)" &&
+          git -C /usr/src/build-extra bundle create "$PWD/bundle-artifacts/build-extra.bundle" origin/main..main
       - name: Clean up temporary files
         if: always()
         shell: bash
@@ -131,8 +98,6 @@ jobs:
   pkg:
     runs-on: windows-latest
     needs: bundle-artifacts
-    outputs:
-      latest-sdk64-extra-build-id: ${{ needs.bundle-artifacts.outputs.latest-sdk64-extra-build-id }}
     strategy:
       matrix:
         arch:
@@ -166,29 +131,10 @@ jobs:
           git config --global user.name "$USER_NAME" &&
           git config --global user.email "$USER_EMAIL" &&
           echo "PACKAGER=$USER_NAME <$USER_EMAIL>" >>$GITHUB_ENV
-      - name: Cache git-sdk-64-build-installers
+      - uses: git-for-windows/setup-git-for-windows-sdk@v0
         if: env.SKIP != 'true'
-        id: cache-sdk-build-installers
-        uses: actions/cache@v2
         with:
-          path: git-sdk-64-build-installers
-          key: build-installers-64-${{ needs.bundle-artifacts.outputs.latest-sdk64-extra-build-id }}
-      - name: Download git-sdk-64-build-installers
-        if: env.SKIP != 'true' && steps.cache-sdk-build-installers.outputs.cache-hit != 'true'
-        shell: bash
-        run: |
-          # Use Git Bash to download and unpack the artifact
-
-          ## Get artifact
-          urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
-          id=${{ needs.pkg.outputs.latest-sdk64-extra-build-id }}
-          download_url=$(curl "$urlbase/$id/artifacts" |
-            jq -r '.value[] | select(.name == "git-sdk-64-build-installers").resource.downloadUrl')
-
-          curl -o artifacts.zip "$download_url"
-
-          ## Unpack artifact
-          unzip artifacts.zip
+          flavor: build-installers
       - name: Download bundle-artifacts
         if: env.SKIP != 'true'
         uses: actions/download-artifact@v1
@@ -199,7 +145,7 @@ jobs:
         if: env.SKIP != 'true'
         shell: bash
         run: |
-          d=git-sdk-64-build-installers/usr/src/build-extra &&
+          d=/usr/src/build-extra &&
           if test ! -d $d/.git
           then
             git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d
@@ -242,32 +188,33 @@ jobs:
         if: env.SKIP != 'true'
         env:
           GPGKEY: "${{secrets.GPGKEY}}"
-        shell: powershell
+        shell: bash
         run: |
-          & git-sdk-64-build-installers\usr\bin\sh.exe -lc @"
-            set -x
+          set -x
 
-            # Restrict `PATH` to MSYS2 and to Visual Studio (to let `cv2pdb` find the relevant DLLs)
-            PATH=\"`/mingw64/bin:/usr/bin:/c/Program Files/Microsoft Visual Studio/2022/Enterprise/Common7/IDE/:/C/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin${{matrix.arch.bin}}:/C/Windows/system32\"
+          # Make sure that there is a `/usr/bin/git` that can be used by `makepkg-mingw`
+          printf '#!/bin/sh\n\nexec /mingw64/bin/git.exe "$@"\n' >/usr/bin/git &&
 
-            type -p mspdb140.dll || exit 1
-            sh -x /usr/src/build-extra/please.sh build-mingw-w64-git --only-${{matrix.arch.bitness}}-bit --build-src-pkg -o artifacts HEAD &&
-            cp bundle-artifacts/ver artifacts/ &&
-            if test -n \"`$GPGKEY\"
-            then
-              for tar in artifacts/*.tar*
-              do
-                /usr/src/build-extra/gnupg-with-gpgkey.sh --detach-sign --no-armor `$tar
-              done
-            fi &&
+          # Restrict `PATH` to MSYS2 and to Visual Studio (to let `cv2pdb` find the relevant DLLs)
+          PATH="/mingw64/bin:/usr/bin:/c/Program Files/Microsoft Visual Studio/2022/Enterprise/Common7/IDE/:/C/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin${{matrix.arch.bin}}:/C/Windows/system32"
 
-            b=`$PWD/artifacts &&
-            version=`$(cat bundle-artifacts/next_version) &&
-            (cd /usr/src/MINGW-packages/mingw-w64-git &&
-            cp PKGBUILD.`$version PKGBUILD &&
-            git commit -s -m \"mingw-w64-git: new version (`$version)\" PKGBUILD &&
-            git bundle create \"`$b\"/MINGW-packages.bundle origin/main..main)
-          "@
+          type -p mspdb140.dll || exit 1
+          sh -x /usr/src/build-extra/please.sh build-mingw-w64-git --only-${{matrix.arch.bitness}}-bit --build-src-pkg -o artifacts HEAD &&
+          cp bundle-artifacts/ver artifacts/ &&
+          if test -n "$GPGKEY"
+          then
+            for tar in artifacts/*.tar*
+            do
+              /usr/src/build-extra/gnupg-with-gpgkey.sh --detach-sign --no-armor $tar
+            done
+          fi &&
+
+          b=$PWD/artifacts &&
+          version=$(cat bundle-artifacts/next_version) &&
+          (cd /usr/src/MINGW-packages/mingw-w64-git &&
+          cp PKGBUILD.$version PKGBUILD &&
+          git commit -s -m "mingw-w64-git: new version ($version)" PKGBUILD &&
+          git bundle create "$b"/MINGW-packages.bundle origin/main..main)
       - name: Clean up temporary files
         if: always() && env.SKIP != 'true'
         shell: bash
@@ -417,63 +364,15 @@ jobs:
         with:
           name: bundle-artifacts
           path: bundle-artifacts
-      - name: Cache git-sdk-64-build-installers
+      - uses: git-for-windows/setup-git-for-windows-sdk@v0
         if: env.SKIP != 'true' && matrix.arch.bitness == '64'
-        id: cache-sdk64-build-installers
-        uses: actions/cache@v2
         with:
-          path: git-sdk-64-build-installers
-          key: build-installers-64-${{ needs.pkg.outputs.latest-sdk64-extra-build-id }}
-      - name: Download git-sdk-64-build-installers
-        if: env.SKIP != 'true' && matrix.arch.bitness == '64' && steps.cache-sdk64-build-installers.outputs.cache-hit != 'true'
-        shell: bash
-        run: |
-          # Use Git Bash to download and unpack the artifact
-
-          ## Get artifact
-          urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
-          id=${{ needs.pkg.outputs.latest-sdk64-extra-build-id }}
-          download_url="$(curl "$urlbase/$id/artifacts" |
-            jq -r '.value[] | select(.name == "git-sdk-64-build-installers").resource.downloadUrl')"
-
-          curl -o artifacts.zip "$download_url"
-
-          ## Unpack artifact
-          unzip artifacts.zip
-      - name: Determine latest git-sdk-32-extra-artifacts build ID
+          flavor: build-installers
+      - uses: git-for-windows/setup-git-for-windows-sdk@v0
         if: env.SKIP != 'true' && matrix.arch.bitness == '32'
-        id: determine-latest-sdk32-extra-build-id
-        shell: bash
-        run: |
-          urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
-          id=$(curl "$urlbase?definitions=30&statusFilter=completed&resultFilter=succeeded&\$top=1" |
-            jq -r '.value[0].id')
-
-          echo "Latest ID is ${id}"
-          echo "::set-output name=id::$id"
-      - name: Cache git-sdk-32-build-installers
-        if: env.SKIP != 'true' && matrix.arch.bitness == '32'
-        id: cache-sdk32-build-installers
-        uses: actions/cache@v2
         with:
-          path: git-sdk-32-build-installers
-          key: build-installers-32-${{ steps.determine-latest-sdk32-extra-build-id.outputs.id }}
-      - name: Download git-sdk-32-build-installers
-        if: env.SKIP != 'true' && matrix.arch.bitness == '32' && steps.cache-sdk32-build-installers.outputs.cache-hit != 'true'
-        shell: bash
-        run: |
-          # Use Git Bash to download and unpack the artifact
-
-          ## Get artifact
-          urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
-          id=${{ steps.determine-latest-sdk32-extra-build-id.outputs.id }}
-          download_url=$(curl "$urlbase/$id/artifacts" |
-            jq -r '.value[] | select(.name == "git-sdk-32-build-installers").resource.downloadUrl')
-
-          curl -o artifacts.zip "$download_url"
-
-          ## Unpack artifact
-          unzip artifacts.zip
+          flavor: build-installers
+          architecture: i686
       - name: Download arm64 artifact
         if: env.SKIP != 'true' && matrix.arch.arm64 == true
         uses: actions/download-artifact@v1
@@ -484,7 +383,7 @@ jobs:
         if: env.SKIP != 'true'
         shell: bash
         run: |
-          d=git-sdk-${{matrix.arch.bitness}}-build-installers/usr/src/build-extra &&
+          d=/usr/src/build-extra &&
           if test ! -d $d/.git
           then
             git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d
@@ -506,38 +405,34 @@ jobs:
           git config --global alias.signtool '!sh "/usr/src/build-extra/signtool.sh"'
       - name: Build ${{matrix.arch.bitness}}-bit ${{matrix.artifact.name}}
         if: env.SKIP != 'true'
-        shell: powershell
+        shell: bash
         run: |
-          & .\git-sdk-${{matrix.arch.bitness}}-build-installers\usr\bin\bash.exe -lc @"
-            set -x
-            if test "${{matrix.arch.arm64}}" = true
-            then
-              ARM64="--include-arm64-artifacts=\"$PWD/arm64\""
-            else
-              ARM64=
-            fi
+          set -x
+          if test "${{matrix.arch.arm64}}" = true
+          then
+            ARM64="--include-arm64-artifacts=\"$PWD/arm64\""
+          else
+            ARM64=
+          fi
 
-            eval /usr/src/build-extra/please.sh make_installers_from_mingw_w64_git `$ARM64 --version=`$(cat pkg-${{matrix.arch.name}}/ver) -o artifacts --${{matrix.artifact.name}} --pkg=pkg-${{matrix.arch.name}}/mingw-w64-${{matrix.arch.name}}-git-[0-9]*.tar.xz --pkg=pkg-${{matrix.arch.name}}/mingw-w64-${{matrix.arch.name}}-git-doc-html-[0-9]*.tar.xz &&
-            if test portable = '${{matrix.artifact.name}}' && test -n \"`$(git config alias.signtool)\"
-            then
-              git signtool artifacts/PortableGit-*.exe
-            fi &&
-            openssl dgst -sha256 artifacts/${{matrix.artifact.fileprefix}}-*.${{matrix.artifact.fileextension}} | sed \"s/.* //\" >artifacts/sha-256.txt
-          "@
+          eval /usr/src/build-extra/please.sh make_installers_from_mingw_w64_git $ARM64 --version=$(cat pkg-${{matrix.arch.name}}/ver) -o artifacts --${{matrix.artifact.name}} --pkg=pkg-${{matrix.arch.name}}/mingw-w64-${{matrix.arch.name}}-git-[0-9]*.tar.xz --pkg=pkg-${{matrix.arch.name}}/mingw-w64-${{matrix.arch.name}}-git-doc-html-[0-9]*.tar.xz &&
+          if test portable = '${{matrix.artifact.name}}' && test -n "$(git config alias.signtool)"
+          then
+            git signtool artifacts/PortableGit-*.exe
+          fi &&
+          openssl dgst -sha256 artifacts/${{matrix.artifact.fileprefix}}-*.${{matrix.artifact.fileextension}} | sed "s/.* //" >artifacts/sha-256.txt
       - name: Copy package-versions and pdbs
         if: env.SKIP != 'true' && matrix.artifact.name == 'installer'
-        shell: powershell
+        shell: bash
         run: |
-          & .\git-sdk-${{matrix.arch.bitness}}-build-installers\usr\bin\bash.exe -lc @"
-            cp /usr/src/build-extra/installer/package-versions.txt artifacts/ &&
+          cp /usr/src/build-extra/installer/package-versions.txt artifacts/ &&
 
-            a=`$PWD/artifacts &&
-            p=`$PWD/pkg-${{matrix.arch.name}} &&
-            (cd /usr/src/build-extra &&
-            mkdir -p cached-source-packages &&
-            cp \"`$p\"/*-pdb* cached-source-packages/ &&
-            GIT_CONFIG_PARAMETERS=\"'windows.sdk${{matrix.arch.bitness}}.path='\" ./please.sh bundle_pdbs --arch=${{matrix.arch.name}} --directory=\"`$a\" installer/package-versions.txt)
-          "@
+          a=$PWD/artifacts &&
+          p=$PWD/pkg-${{matrix.arch.name}} &&
+          (cd /usr/src/build-extra &&
+          mkdir -p cached-source-packages &&
+          cp "$p"/*-pdb* cached-source-packages/ &&
+          GIT_CONFIG_PARAMETERS="'windows.sdk${{matrix.arch.bitness}}.path='" ./please.sh bundle_pdbs --arch=${{matrix.arch.name}} --directory="$a" installer/package-versions.txt)
       - name: Clean up temporary files
         if: always() && env.SKIP != 'true'
         shell: bash
@@ -578,34 +473,15 @@ jobs:
         with:
           name: bundle-artifacts
           path: bundle-artifacts
-      - name: Cache git-sdk-64-build-installers
+      - uses: git-for-windows/setup-git-for-windows-sdk@v0
         if: env.SKIP != 'true'
-        id: cache-sdk-build-installers
-        uses: actions/cache@v2
         with:
-          path: git-sdk-64-build-installers
-          key: build-installers-64-${{ needs.pkg.outputs.latest-sdk64-extra-build-id }}
-      - name: Download git-sdk-64-build-installers
-        if: env.SKIP != 'true' && steps.cache-sdk-build-installers.outputs.cache-hit != 'true'
-        shell: bash
-        run: |
-          # Use Git Bash to download and unpack the artifact
-
-          ## Get artifact
-          urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
-          id=${{ needs.pkg.outputs.latest-sdk64-extra-build-id }}
-          download_url=$(curl "$urlbase/$id/artifacts" |
-            jq -r '.value[] | select(.name == "git-sdk-64-build-installers").resource.downloadUrl')
-
-          curl -o artifacts.zip "$download_url"
-
-          ## Unpack artifact
-          unzip artifacts.zip
+          flavor: build-installers
       - name: Clone and update build-extra
         if: env.SKIP != 'true'
         shell: bash
         run: |
-          d=git-sdk-64-build-installers/usr/src/build-extra &&
+          d=/usr/src/build-extra &&
           if test ! -d $d/.git
           then
             git clone --single-branch -b main https://github.com/git-for-windows/build-extra $d
@@ -618,13 +494,11 @@ jobs:
         if: env.SKIP != 'true'
       - name: Build 64-bit NuGet packages
         if: env.SKIP != 'true'
-        shell: powershell
+        shell: bash
         run: |
-          & .\git-sdk-64-build-installers\usr\bin\bash.exe -lc @"
-            /usr/src/build-extra/please.sh make_installers_from_mingw_w64_git --version=`$(cat pkg-x86_64/ver) -o artifacts --nuget --pkg=pkg-x86_64/mingw-w64-x86_64-git-[0-9]*.tar.xz --pkg=pkg-x86_64/mingw-w64-x86_64-git-doc-html-[0-9]*.tar.xz &&
-            /usr/src/build-extra/please.sh make_installers_from_mingw_w64_git --version=`$(cat pkg-x86_64/ver) -o artifacts --nuget-mingit &&
-            openssl dgst -sha256 artifacts/Git*.nupkg | sed \"s/.* //\" >artifacts/sha-256.txt
-          "@
+          /usr/src/build-extra/please.sh make_installers_from_mingw_w64_git --version=$(cat pkg-x86_64/ver) -o artifacts --nuget --pkg=pkg-x86_64/mingw-w64-x86_64-git-[0-9]*.tar.xz --pkg=pkg-x86_64/mingw-w64-x86_64-git-doc-html-[0-9]*.tar.xz &&
+          /usr/src/build-extra/please.sh make_installers_from_mingw_w64_git --version=$(cat pkg-x86_64/ver) -o artifacts --nuget-mingit &&
+          openssl dgst -sha256 artifacts/Git*.nupkg | sed "s/.* //" >artifacts/sha-256.txt
       - name: Publish nuget-x86_64
         if: env.SKIP != 'true'
         uses: actions/upload-artifact@v1

From 213652aeb00a4611e2f89bef4d203152f79eb920 Mon Sep 17 00:00:00 2001
From: Philip Oakley <philipoakley@iee.email>
Date: Sat, 24 Apr 2021 11:09:58 +0100
Subject: [PATCH 092/265] .gitignore: add Visual Studio CMakeSetting.json file

The CMakeSettings.json file is tool generated. Developers may track it
should they provide additional settings.

Signed-off-by: Philip Oakley <philipoakley@iee.email>
---
 .gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index e81de1063a4004..7e83ac36aa22f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -245,3 +245,4 @@ Release/
 /git.VC.db
 *.dSYM
 /contrib/buildsystems/out
+CMakeSettings.json

From 71e617fe99d738983db388b2f6d398eaa6c373e2 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Thu, 2 Sep 2021 16:03:40 -0400
Subject: [PATCH 093/265] fsmonitor--daemon: cd out of worktree root

Teach the fsmonitor--daemon to CD outside of the worktree
before starting up.

The common Git startup mechanism causes the CWD of the daemon process
to be in the root of the worktree.  On Windows, this causes the daemon
process to hold a locked handle on the CWD and prevents other
processes from moving or deleting the worktree while the daemon is
running.

CD to HOME before entering main event loops.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 builtin/fsmonitor--daemon.c         | 32 +++++++++++++++++++++++++++--
 compat/fsmonitor/fsm-listen-win32.c | 22 ++++++++++++++------
 fsmonitor--daemon.h                 |  1 +
 3 files changed, 47 insertions(+), 8 deletions(-)

diff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c
index 50ae3cca575be9..1b7c757f583ebf 100644
--- a/builtin/fsmonitor--daemon.c
+++ b/builtin/fsmonitor--daemon.c
@@ -1181,11 +1181,11 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)
 	 * before we need it.
 	 */
 	if (ipc_server_run_async(&state->ipc_server_data,
-				 fsmonitor_ipc__get_path(), &ipc_opts,
+				 state->path_ipc.buf, &ipc_opts,
 				 handle_client, state))
 		return error_errno(
 			_("could not start IPC thread pool on '%s'"),
-			fsmonitor_ipc__get_path());
+			state->path_ipc.buf);
 
 	/*
 	 * Start the fsmonitor listener thread to collect filesystem
@@ -1220,6 +1220,7 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)
 static int fsmonitor_run_daemon(void)
 {
 	struct fsmonitor_daemon_state state;
+	const char *home;
 	int err;
 
 	memset(&state, 0, sizeof(state));
@@ -1289,6 +1290,15 @@ static int fsmonitor_run_daemon(void)
 
 	strbuf_addch(&state.path_cookie_prefix, '/');
 
+	/*
+	 * We create a named-pipe or unix domain socket inside of the
+	 * ".git" directory.  (Well, on Windows, we base our named
+	 * pipe in the NPFS on the absolute path of the git
+	 * directory.)
+	 */
+	strbuf_init(&state.path_ipc, 0);
+	strbuf_addstr(&state.path_ipc, absolute_path(fsmonitor_ipc__get_path()));
+
 	/*
 	 * Confirm that we can create platform-specific resources for the
 	 * filesystem listener before we bother starting all the threads.
@@ -1298,6 +1308,23 @@ static int fsmonitor_run_daemon(void)
 		goto done;
 	}
 
+	/*
+	 * CD out of the worktree root directory.
+	 *
+	 * The common Git startup mechanism causes our CWD to be the
+	 * root of the worktree.  On Windows, this causes our process
+	 * to hold a locked handle on the CWD.  This prevents the
+	 * worktree from being moved or deleted while the daemon is
+	 * running.
+	 *
+	 * We assume that our FS and IPC listener threads have either
+	 * opened all of the handles that they need or will do
+	 * everything using absolute paths.
+	 */
+	home = getenv("HOME");
+	if (home && *home && chdir(home))
+		die_errno(_("could not cd home '%s'"), home);
+
 	err = fsmonitor_run_daemon_1(&state);
 
 done:
@@ -1310,6 +1337,7 @@ static int fsmonitor_run_daemon(void)
 	strbuf_release(&state.path_worktree_watch);
 	strbuf_release(&state.path_gitdir_watch);
 	strbuf_release(&state.path_cookie_prefix);
+	strbuf_release(&state.path_ipc);
 
 	return err;
 }
diff --git a/compat/fsmonitor/fsm-listen-win32.c b/compat/fsmonitor/fsm-listen-win32.c
index 3f1b68267bdc0b..c43d92b9620878 100644
--- a/compat/fsmonitor/fsm-listen-win32.c
+++ b/compat/fsmonitor/fsm-listen-win32.c
@@ -402,12 +402,22 @@ static int recv_rdcw_watch(struct one_watch *watch)
 	}
 
 	/*
-	 * NEEDSWORK: If an external <gitdir> is deleted, the above
-	 * returns an error.  I'm not sure that there's anything that
-	 * we can do here other than failing -- the <worktree>/.git
-	 * link file would be broken anyway.  We might try to check
-	 * for that and return a better error message, but I'm not
-	 * sure it is worth it.
+	 * GetOverlappedResult() fails if the watched directory is
+	 * deleted while we were waiting for an overlapped IO to
+	 * complete.  The documentation did not list specific errors,
+	 * but I observed ERROR_ACCESS_DENIED (0x05) errors during
+	 * testing.
+	 *
+	 * Note that we only get notificaiton events for events
+	 * *within* the directory, not *on* the directory itself.
+	 * (These might be properies of the parent directory, for
+	 * example).
+	 *
+	 * NEEDSWORK: We might try to check for the deleted directory
+	 * case and return a better error message, but I'm not sure it
+	 * is worth it.
+	 *
+	 * Shutdown if we get any error.
 	 */
 
 	error(_("GetOverlappedResult failed on '%s' [GLE %ld]"),
diff --git a/fsmonitor--daemon.h b/fsmonitor--daemon.h
index bd09fffc1767a3..223c2131b58eb6 100644
--- a/fsmonitor--daemon.h
+++ b/fsmonitor--daemon.h
@@ -54,6 +54,7 @@ struct fsmonitor_daemon_state {
 	struct fsmonitor_daemon_backend_data *backend_data;
 
 	struct ipc_server_data *ipc_server_data;
+	struct strbuf path_ipc;
 };
 
 /*

From c14ffc9c2beafe79d16bd2fd39ccc1c5a884f0f4 Mon Sep 17 00:00:00 2001
From: Victoria Dye <vdye@github.com>
Date: Thu, 5 Aug 2021 19:04:13 -0400
Subject: [PATCH 094/265] subtree: update `contrib/subtree` `test` target

The intention of this change is to align with how the top-level git
`Makefile` defines its own test target (which also internally calls
`$(MAKE) -C t/ all`). This change also ensures the consistency of
`make -C contrib/subtree test` with other testing in CI executions
(which rely on `$DEFAULT_TEST_TARGET` being defined as `prove`).

Signed-off-by: Victoria Dye <vdye@github.com>
---
 contrib/subtree/Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/contrib/subtree/Makefile b/contrib/subtree/Makefile
index 6fa7496bfdb3fd..6f6e90c4cb49b6 100644
--- a/contrib/subtree/Makefile
+++ b/contrib/subtree/Makefile
@@ -94,7 +94,7 @@ $(GIT_SUBTREE_TEST): $(GIT_SUBTREE)
 	cp $< $@
 
 test: $(GIT_SUBTREE_TEST)
-	$(MAKE) -C t/ test
+	$(MAKE) -C t/ all
 
 clean:
 	$(RM) $(GIT_SUBTREE)

From cb57b3eab786868332d3c39c72926792edf01a82 Mon Sep 17 00:00:00 2001
From: Philip Oakley <philipoakley@iee.email>
Date: Thu, 22 Apr 2021 11:11:38 +0100
Subject: [PATCH 095/265] CMakeLists: add default "x64-windows" arch for Visual
 Studio

In Git-for-Windows, work on using ARM64 has progressed. The
commit 2d94b77b27 (cmake: allow building for Windows/ARM64, 2020-12-04)
failed to notice that /compat/vcbuild/vcpkg_install.bat will default to
using the "x64-windows" architecture for the vcpkg installation if not set,
but CMake is not told of this default. Commit 635b6d99b3 (vcbuild: install
ARM64 dependencies when building ARM64 binaries, 2020-01-31) later updated
vcpkg_install.bat to accept an arch (%1) parameter, but retained the default.

This default is neccessary for the use case where the project directory is
opened directly in Visual Studio, which will find and build a CMakeLists.txt
file without any parameters, thus expecting use of the default setting.

Also Visual studio will generate internal .sln solution and .vcxproj project
files needed for some extension tools. Inform users of the additional
.sln/.vcxproj generation.

** How to test:
 rm -rf '.vs' # remove old visual studio settings
 rm -rf 'compat/vcbuild/vcpkg' # remove any vcpkg downloads
 rm -rf 'contrib/buildsystems/out' # remove builds & CMake artifacts
 with a fresh Visual Studio Community Edition, File>>Open>>(git *folder*)
   to load the project (which will take some time!).
 check for successful compilation.
The implicit .sln (etc.) are in the hidden .vs directory created by
Visual Studio.

Signed-off-by: Philip Oakley <philipoakley@iee.email>
---
 contrib/buildsystems/CMakeLists.txt | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index a214bfc8953e4e..f6d7b3857a1fbc 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -71,6 +71,10 @@ if(USE_VCPKG)
 		message("Initializing vcpkg and building the Git's dependencies (this will take a while...)")
 		execute_process(COMMAND ${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg_install.bat ${VCPKG_ARCH})
 	endif()
+	if(NOT EXISTS ${VCPKG_ARCH})
+		message("VCPKG_ARCH: unset, using 'x64-windows'")
+		set(VCPKG_ARCH "x64-windows") # default from vcpkg_install.bat
+	endif()
 	list(APPEND CMAKE_PREFIX_PATH "${VCPKG_DIR}/installed/${VCPKG_ARCH}")
 
 	# In the vcpkg edition, we need this to be able to link to libcurl

From 6fb700859dd8600dee9b8a80596905fdc39445eb Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Wed, 8 Sep 2021 16:11:02 -0400
Subject: [PATCH 096/265] fsmonitor--daemon: prepare for adding health thread

Refactor daemon thread startup to make it easier to start
a third thread class to monitor the health of the daemon.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 builtin/fsmonitor--daemon.c | 27 ++++++++++++++++++++-------
 1 file changed, 20 insertions(+), 7 deletions(-)

diff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c
index 1b7c757f583ebf..14cd2d5eb52c82 100644
--- a/builtin/fsmonitor--daemon.c
+++ b/builtin/fsmonitor--daemon.c
@@ -1174,6 +1174,8 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)
 		 */
 		.uds_disallow_chdir = 0
 	};
+	int listener_started = 0;
+	int err = 0;
 
 	/*
 	 * Start the IPC thread pool before the we've started the file
@@ -1194,15 +1196,20 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)
 	if (pthread_create(&state->listener_thread, NULL,
 			   fsm_listen__thread_proc, state) < 0) {
 		ipc_server_stop_async(state->ipc_server_data);
-		ipc_server_await(state->ipc_server_data);
-
-		return error(_("could not start fsmonitor listener thread"));
+		err = error(_("could not start fsmonitor listener thread"));
+		goto cleanup;
 	}
+	listener_started = 1;
 
 	/*
 	 * The daemon is now fully functional in background threads.
+	 * Our primary thread should now just wait while the threads
+	 * do all the work.
+	 */
+cleanup:
+	/*
 	 * Wait for the IPC thread pool to shutdown (whether by client
-	 * request or from filesystem activity).
+	 * request, from filesystem activity, or an error).
 	 */
 	ipc_server_await(state->ipc_server_data);
 
@@ -1211,10 +1218,16 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)
 	 * event from the IPC thread pool, but it doesn't hurt to tell
 	 * it again.  And wait for it to shutdown.
 	 */
-	fsm_listen__stop_async(state);
-	pthread_join(state->listener_thread, NULL);
+	if (listener_started) {
+		fsm_listen__stop_async(state);
+		pthread_join(state->listener_thread, NULL);
+	}
 
-	return state->error_code;
+	if (err)
+		return err;
+	if (state->error_code)
+		return state->error_code;
+	return 0;
 }
 
 static int fsmonitor_run_daemon(void)

From 8761e36ac7cee900d5ad63a7aebaec70279912a8 Mon Sep 17 00:00:00 2001
From: Dennis Ameling <dennis@dennisameling.com>
Date: Mon, 19 Jul 2021 13:04:27 +0200
Subject: [PATCH 097/265] ci(): add HOST_CPU to CMake command

As mentioned in the Makefile and CMakeLists.txt: "When cross-compiling, define HOST_CPU as the canonical name of the CPU on which the built Git will run (for instance "x86_64")"

This commit sets the HOST_CPU variable since Git for Windows arm64 is cross-compiled from an amd64 host.

Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
---
 .github/workflows/git-artifacts.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/git-artifacts.yml b/.github/workflows/git-artifacts.yml
index 5fc6a6fd6b72ea..9aa96b7f8df110 100644
--- a/.github/workflows/git-artifacts.yml
+++ b/.github/workflows/git-artifacts.yml
@@ -291,7 +291,7 @@ jobs:
         run: |
           cmake `pwd`/contrib/buildsystems/ -DCMAKE_PREFIX_PATH=`pwd`/compat/vcbuild/vcpkg/installed/arm64-windows \
           -DNO_GETTEXT=YesPlease -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON -DCMAKE_GENERATOR_PLATFORM=arm64 -DVCPKG_ARCH=arm64-windows \
-          -DCMAKE_INSTALL_PREFIX="`pwd`/git-arm64" -DSKIP_DASHED_BUILT_INS=ON
+          -DCMAKE_INSTALL_PREFIX="`pwd`/git-arm64" -DSKIP_DASHED_BUILT_INS=ON -DHOST_CPU=arm64
       - name: MSBuild
         if: env.SKIP != 'true'
         run: msbuild git.sln -property:Configuration=Release

From 039c412a133116575470f231165ec2e17e40aaff Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 19 Feb 2021 11:39:37 +0100
Subject: [PATCH 098/265] ci(vs-build): download the vcpkg artifacts using a
 dedicated Action

We now have a GitHub Action to download and cache Azure Pipelines
artifacts (such as the `vcpkg` artifacts), hiding gnarly internals, and
also providing some robustness against network glitches. Let's use it.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/main.yml | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index c35200defb9357..452898c1844e61 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -146,14 +146,10 @@ jobs:
         repository: 'microsoft/vcpkg'
         path: 'compat/vcbuild/vcpkg'
     - name: download vcpkg artifacts
-      shell: powershell
-      run: |
-        $urlbase = "https://dev.azure.com/git/git/_apis/build/builds"
-        $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=9&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id
-        $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[0].resource.downloadUrl
-        (New-Object Net.WebClient).DownloadFile($downloadUrl, "compat.zip")
-        Expand-Archive compat.zip -DestinationPath . -Force
-        Remove-Item compat.zip
+      uses: git-for-windows/get-azure-pipelines-artifact@v0
+      with:
+        repository: git/git
+        definitionId: 9
     - name: add msbuild to PATH
       uses: microsoft/setup-msbuild@v1
     - name: copy dlls to root

From 3c87d5e2bfdd8477469a90d7ab33db764583cc44 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 2 Apr 2021 22:50:54 +0200
Subject: [PATCH 099/265] mingw: allow for longer paths in
 `parse_interpreter()`
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

As reported in https://github.com/newren/git-filter-repo/pull/225, it
looks like 99 bytes is not really sufficient to represent e.g. the full
path to Python when installed via Windows Store (and this path is used
in the hasb bang line when installing scripts via `pip`).

Let's increase it to what is probably the maximum sensible path size:
MAX_PATH. This makes `parse_interpreter()` in line with what
`lookup_prog()` handles.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Vilius Šumskas <vilius@sumskas.eu>
---
 compat/mingw.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 6fe80fdf014021..26e6f7ad0eb2e9 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1249,7 +1249,7 @@ static const char *quote_arg_msys2(const char *arg)
 
 static const char *parse_interpreter(const char *cmd)
 {
-	static char buf[100];
+	static char buf[MAX_PATH];
 	char *p, *opt;
 	int n, fd;
 

From 6cc2bbd231eb98ff650d20a9c934cbccfa24ea29 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Mon, 17 May 2021 10:46:52 +0200
Subject: [PATCH 100/265] compat/vcbuild: document preferred way to build in
 Visual Studio

We used to have that `make vcxproj` hack, but a hack it is. In the
meantime, we have a much cleaner solution: using CMake, either
explicitly, or even more conveniently via Visual Studio's built-in CMake
support (simply open Git's top-level directory via File>Open>Folder...).

Let's let the `README` reflect this.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/vcbuild/README | 28 +++++++++-------------------
 1 file changed, 9 insertions(+), 19 deletions(-)

diff --git a/compat/vcbuild/README b/compat/vcbuild/README
index 29ec1d0f104b80..5c71ea2daa4017 100644
--- a/compat/vcbuild/README
+++ b/compat/vcbuild/README
@@ -37,27 +37,17 @@ The Steps to Build Git with VS2015 or VS2017 from the command line.
 
 ================================================================
 
-Alternatively, run `make vcxproj` and then load the generated `git.sln` in
-Visual Studio. The initial build will install the vcpkg system and build the
+Alternatively, just open Git's top-level directory in Visual Studio, via
+`File>Open>Folder...`. This will use CMake internally to generate the
+project definitions. It will also install the vcpkg system and build the
 dependencies automatically. This will take a while.
 
-Instead of generating the `git.sln` file yourself (which requires a full Git
-for Windows SDK), you may want to consider fetching the `vs/master` branch of
-https://github.com/git-for-windows/git instead (which is updated automatically
-via CI running `make vcxproj`). The `vs/master` branch does not require a Git
-for Windows to build, but you can run the test scripts in a regular Git Bash.
-
-Note that `make vcxproj` will automatically add and commit the generated `.sln`
-and `.vcxproj` files to the repo. This is necessary to allow building a
-fully-testable Git in Visual Studio, where a regular Git Bash can be used to
-run the test scripts (as opposed to a full Git for Windows SDK): a number of
-build targets, such as Git commands implemented as Unix shell scripts (where
-`@@SHELL_PATH@@` and other placeholders are interpolated) require a full-blown
-Git for Windows SDK (which is about 10x the size of a regular Git for Windows
-installation).
-
-If your plan is to open a Pull Request with Git for Windows, it is a good idea
-to drop this commit before submitting.
+You can also generate the Visual Studio solution manually by downloading
+and running CMake explicitly rather than letting Visual Studio doing
+that implicitly.
+
+Another, deprecated option is to run `make vcxproj`. This option is
+superseded by the CMake-based build, and will be removed at some point.
 
 ================================================================
 The Steps of Build Git with VS2008

From 48f4da010392449ca52e171d90e9fa69f4d7e620 Mon Sep 17 00:00:00 2001
From: Pascal Muller <pascalmuller@gmail.com>
Date: Wed, 23 Jun 2021 21:21:10 +0200
Subject: [PATCH 101/265] http: optionally send SSL client certificate

This adds support for a new http.sslAutoClientCert config value.

In cURL 7.77 or later the schannel backend does not automatically send
client certificates from the Windows Certificate Store anymore.

This config value is only used if http.sslBackend is set to "schannel",
and can be used to opt in to the old behavior and force cURL to send
client certificates.

This fixes https://github.com/git-for-windows/git/issues/3292

Signed-off-by: Pascal Muller <pascalmuller@gmail.com>
---
 Documentation/config/http.txt |  5 +++++
 git-curl-compat.h             |  8 ++++++++
 http.c                        | 26 ++++++++++++++++++++++----
 3 files changed, 35 insertions(+), 4 deletions(-)

diff --git a/Documentation/config/http.txt b/Documentation/config/http.txt
index 24f51879392040..380a3e86c9c061 100644
--- a/Documentation/config/http.txt
+++ b/Documentation/config/http.txt
@@ -189,6 +189,11 @@ http.schannelUseSSLCAInfo::
 	when the `schannel` backend was configured via `http.sslBackend`,
 	unless `http.schannelUseSSLCAInfo` overrides this behavior.
 
+http.sslAutoClientCert::
+	As of cURL v7.77.0, the Secure Channel backend won't automatically
+	send client certificates from the Windows Certificate Store anymore.
+	To opt in to the old behavior, http.sslAutoClientCert can be set.
+
 http.pinnedpubkey::
 	Public key of the https service. It may either be the filename of
 	a PEM or DER encoded public key file or a string starting with
diff --git a/git-curl-compat.h b/git-curl-compat.h
index 56a83b6bbd8c43..f9f784220f83e6 100644
--- a/git-curl-compat.h
+++ b/git-curl-compat.h
@@ -126,4 +126,12 @@
 #define GIT_CURL_HAVE_CURLSSLSET_NO_BACKENDS
 #endif
 
+/**
+ * CURLSSLOPT_AUTO_CLIENT_CERT was added in 7.77.0, released in May
+ * 2021.
+ */
+#if LIBCURL_VERSION_NUM >= 0x074d00
+#define GIT_CURL_HAVE_CURLSSLOPT_AUTO_CLIENT_CERT
+#endif
+
 #endif
diff --git a/http.c b/http.c
index b980f2f2e9d6bc..b08795715f8afc 100644
--- a/http.c
+++ b/http.c
@@ -148,6 +148,8 @@ static int http_schannel_check_revoke_mode =
  */
 static int http_schannel_use_ssl_cainfo;
 
+static int http_auto_client_cert;
+
 size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
 {
 	size_t size = eltsize * nmemb;
@@ -312,6 +314,11 @@ static int http_options(const char *var, const char *value, void *cb)
 		return 0;
 	}
 
+	if (!strcmp("http.sslautoclientcert", var)) {
+		http_auto_client_cert = git_config_bool(var, value);
+		return 0;
+	}
+
 	if (!strcmp("http.minsessions", var)) {
 		min_curl_sessions = git_config_int(var, value);
 		if (min_curl_sessions > 1)
@@ -822,13 +829,24 @@ static CURL *get_curl_handle(void)
 	}
 #endif
 
-	if (http_ssl_backend && !strcmp("schannel", http_ssl_backend) &&
-	    http_schannel_check_revoke_mode) {
+	if (http_ssl_backend && !strcmp("schannel", http_ssl_backend)) {
+		long ssl_options = 0;
+		if (http_schannel_check_revoke_mode) {
 #ifdef GIT_CURL_HAVE_CURLSSLOPT_NO_REVOKE
-		curl_easy_setopt(result, CURLOPT_SSL_OPTIONS, http_schannel_check_revoke_mode);
+			ssl_options |= http_schannel_check_revoke_mode;
 #else
-		warning(_("CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0"));
+			warning(_("CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0"));
 #endif
+		}
+
+		if (http_auto_client_cert) {
+#ifdef GIT_CURL_HAVE_CURLSSLOPT_AUTO_CLIENT_CERT
+			ssl_options |= CURLSSLOPT_AUTO_CLIENT_CERT;
+#endif
+		}
+
+		if (ssl_options)
+			curl_easy_setopt(result, CURLOPT_SSL_OPTIONS, ssl_options);
 	}
 
 	if (http_proactive_auth)

From 493c2b97bcbb3470dd75e84d4f8fc9265c2822dd Mon Sep 17 00:00:00 2001
From: Victoria Dye <vdye@github.com>
Date: Thu, 5 Aug 2021 19:11:59 -0400
Subject: [PATCH 102/265] ci: run `contrib/subtree` tests in CI builds

Because `git subtree` (unlike most other `contrib` modules) is included as
part of the standard release of Git for Windows, its stability should be
verified as consistently as it is for the rest of git. By including the
`git subtree` tests in the CI workflow, these tests are as much of a gate to
merging and indicator of stability as the standard test suite.

Signed-off-by: Victoria Dye <vdye@github.com>
---
 ci/run-build-and-tests.sh | 4 ++++
 ci/run-test-slice.sh      | 3 +++
 2 files changed, 7 insertions(+)

diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
index 280dda7d285674..4066afb5bf67f1 100755
--- a/ci/run-build-and-tests.sh
+++ b/ci/run-build-and-tests.sh
@@ -51,4 +51,8 @@ esac
 make $MAKE_TARGETS
 check_unignored_build_artifacts
 
+case " $MAKE_TARGETS " in
+*" all "*) make -C contrib/subtree test;;
+esac
+
 save_good_tree
diff --git a/ci/run-test-slice.sh b/ci/run-test-slice.sh
index f8c2c3106a2ef4..e7f0f923cc5a51 100755
--- a/ci/run-test-slice.sh
+++ b/ci/run-test-slice.sh
@@ -14,4 +14,7 @@ make --quiet -C t T="$(cd t &&
 	./helper/test-tool path-utils slice-tests "$1" "$2" t[0-9]*.sh |
 	tr '\n' ' ')"
 
+# Run the git subtree tests only if main tests succeeded
+test 0 != "$1" || make -C contrib/subtree test
+
 check_unignored_build_artifacts

From a61bf801c15f7cdc4ffa8fcf2fe2488584cf7579 Mon Sep 17 00:00:00 2001
From: Philip Oakley <philipoakley@iee.email>
Date: Mon, 10 May 2021 16:47:40 +0100
Subject: [PATCH 103/265] CMake: show Win32 and Generator_platform build-option
 values

Ensure key CMake option values are part of the CMake output to
facilitate user support when tool updates impact the wider CMake
actions, particularly ongoing 'improvements' in Visual Studio.

These CMake displays perform the same function as the build-options.txt
provided in the main Git for Windows. CMake is already chatty.
The setting of CMAKE_EXPORT_COMPILE_COMMANDS is also reported.

Include the environment's CMAKE_EXPORT_COMPILE_COMMANDS value which
may have been propogated to CMake's internal value.

Testing the CMAKE_EXPORT_COMPILE_COMMANDS processing can be difficult
in the Visual Studio environment, as it may be cached in many places.
The 'environment' may include the OS, the user shell, CMake's
own environment, along with the Visual Studio presets and caches.

See previous commit for arefacts that need removing for a clean test.

Signed-off-by: Philip Oakley <philipoakley@iee.email>
---
 contrib/buildsystems/CMakeLists.txt | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index f6d7b3857a1fbc..5b1b4527650795 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -63,10 +63,20 @@ endif()
 
 if(NOT DEFINED CMAKE_EXPORT_COMPILE_COMMANDS)
 	set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
+	message("settting CMAKE_EXPORT_COMPILE_COMMANDS: ${CMAKE_EXPORT_COMPILE_COMMANDS}")
 endif()
 
 if(USE_VCPKG)
 	set(VCPKG_DIR "${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg")
+	message("WIN32: ${WIN32}") # show its underlying text values
+	message("VCPKG_DIR: ${VCPKG_DIR}")
+	message("VCPKG_ARCH: ${VCPKG_ARCH}") # maybe unset
+	message("MSVC: ${MSVC}")
+	message("CMAKE_GENERATOR: ${CMAKE_GENERATOR}")
+	message("CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}")
+	message("CMAKE_GENERATOR_PLATFORM: ${CMAKE_GENERATOR_PLATFORM}")
+	message("CMAKE_EXPORT_COMPILE_COMMANDS: ${CMAKE_EXPORT_COMPILE_COMMANDS}")
+	message("ENV(CMAKE_EXPORT_COMPILE_COMMANDS): $ENV{CMAKE_EXPORT_COMPILE_COMMANDS}")
 	if(NOT EXISTS ${VCPKG_DIR})
 		message("Initializing vcpkg and building the Git's dependencies (this will take a while...)")
 		execute_process(COMMAND ${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg_install.bat ${VCPKG_ARCH})

From 4cc8eaa8178a0e5ea072793786c69946af8ceb1d Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 8 Sep 2021 13:05:42 +0200
Subject: [PATCH 104/265] init: do parse _all_ core.* settings early

In Git for Windows, `has_symlinks` is set to 0 by default. Therefore, we
need to parse the config setting `core.symlinks` to know if it has been
set to `true`. In `git init`, we must do that before copying the
templates because they might contain symbolic links.

Even if the support for symbolic links on Windows has not made it to
upstream Git yet, we really should make sure that all the `core.*`
settings are parsed before proceeding, as they might very well change
the behavior of `git init` in a way the user intended.

This fixes https://github.com/git-for-windows/git/issues/3414

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/init-db.c | 2 +-
 config.c          | 2 +-
 config.h          | 1 +
 3 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/builtin/init-db.c b/builtin/init-db.c
index 546f9c595e7d8c..f33d18e8533606 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -410,7 +410,7 @@ int init_db(const char *git_dir, const char *real_git_dir,
 	startup_info->have_repository = 1;
 
 	/* Ensure `core.hidedotfiles` is processed */
-	git_config(platform_core_config, NULL);
+	git_config(git_default_core_config, NULL);
 
 	safe_create_dir(git_dir, 0);
 
diff --git a/config.c b/config.c
index a5e11aad7fe3b7..47d340d9d33442 100644
--- a/config.c
+++ b/config.c
@@ -1463,7 +1463,7 @@ int git_config_color(char *dest, const char *var, const char *value)
 	return 0;
 }
 
-static int git_default_core_config(const char *var, const char *value, void *cb)
+int git_default_core_config(const char *var, const char *value, void *cb)
 {
 	/* This needs a better name */
 	if (!strcmp(var, "core.filemode")) {
diff --git a/config.h b/config.h
index 7654f61c6349a6..51d442bc014b99 100644
--- a/config.h
+++ b/config.h
@@ -131,6 +131,7 @@ struct config_options {
 typedef int (*config_fn_t)(const char *, const char *, void *);
 
 int git_default_config(const char *, const char *, void *);
+int git_default_core_config(const char *var, const char *value, void *cb);
 
 /**
  * Read a specific file in git-config format.

From 237eae767db24f5e6ee7425fe40c83f4ad8fe8de Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Wed, 8 Sep 2021 16:46:42 -0400
Subject: [PATCH 105/265] fsmonitor--daemon: rename listener thread related
 variables

Rename platform-specific listener thread related variables
and data types as we prepare to add another backend thread
type.

[] `struct fsmonitor_daemon_backend_data` becomes `struct fsm_listen_data`
[] `state->backend_data` becomes `state->listen_data`
[] `state->error_code` becomes `state->listen_error_code`

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 builtin/fsmonitor--daemon.c          |  6 +++---
 compat/fsmonitor/fsm-listen-darwin.c | 30 ++++++++++++++--------------
 compat/fsmonitor/fsm-listen-win32.c  | 28 +++++++++++++-------------
 compat/fsmonitor/fsm-listen.h        |  2 +-
 fsmonitor--daemon.h                  |  6 +++---
 5 files changed, 36 insertions(+), 36 deletions(-)

diff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c
index 14cd2d5eb52c82..d5def8faf90166 100644
--- a/builtin/fsmonitor--daemon.c
+++ b/builtin/fsmonitor--daemon.c
@@ -1225,8 +1225,8 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)
 
 	if (err)
 		return err;
-	if (state->error_code)
-		return state->error_code;
+	if (state->listen_error_code)
+		return state->listen_error_code;
 	return 0;
 }
 
@@ -1241,7 +1241,7 @@ static int fsmonitor_run_daemon(void)
 	hashmap_init(&state.cookies, cookies_cmp, NULL, 0);
 	pthread_mutex_init(&state.main_lock, NULL);
 	pthread_cond_init(&state.cookies_cond, NULL);
-	state.error_code = 0;
+	state.listen_error_code = 0;
 	state.current_token_data = fsmonitor_new_token_data();
 
 	/* Prepare to (recursively) watch the <worktree-root> directory. */
diff --git a/compat/fsmonitor/fsm-listen-darwin.c b/compat/fsmonitor/fsm-listen-darwin.c
index 14105f45c18fe4..07113205a6152b 100644
--- a/compat/fsmonitor/fsm-listen-darwin.c
+++ b/compat/fsmonitor/fsm-listen-darwin.c
@@ -27,7 +27,7 @@
 #include "fsm-listen.h"
 #include "fsmonitor--daemon.h"
 
-struct fsmonitor_daemon_backend_data
+struct fsm_listen_data
 {
 	CFStringRef cfsr_worktree_path;
 	CFStringRef cfsr_gitdir_path;
@@ -158,7 +158,7 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,
 			     const FSEventStreamEventId event_ids[])
 {
 	struct fsmonitor_daemon_state *state = ctx;
-	struct fsmonitor_daemon_backend_data *data = state->backend_data;
+	struct fsm_listen_data *data = state->listen_data;
 	char **paths = (char **)event_paths;
 	struct fsmonitor_batch *batch = NULL;
 	struct string_list cookie_list = STRING_LIST_INIT_DUP;
@@ -350,11 +350,11 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
 		NULL,
 		NULL
 	};
-	struct fsmonitor_daemon_backend_data *data;
+	struct fsm_listen_data *data;
 	const void *dir_array[2];
 
 	CALLOC_ARRAY(data, 1);
-	state->backend_data = data;
+	state->listen_data = data;
 
 	data->cfsr_worktree_path = CFStringCreateWithCString(
 		NULL, state->path_worktree_watch.buf, kCFStringEncodingUTF8);
@@ -386,18 +386,18 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
 failed:
 	error(_("Unable to create FSEventStream."));
 
-	FREE_AND_NULL(state->backend_data);
+	FREE_AND_NULL(state->listen_data);
 	return -1;
 }
 
 void fsm_listen__dtor(struct fsmonitor_daemon_state *state)
 {
-	struct fsmonitor_daemon_backend_data *data;
+	struct fsm_listen_data *data;
 
-	if (!state || !state->backend_data)
+	if (!state || !state->listen_data)
 		return;
 
-	data = state->backend_data;
+	data = state->listen_data;
 
 	if (data->stream) {
 		if (data->stream_started)
@@ -407,14 +407,14 @@ void fsm_listen__dtor(struct fsmonitor_daemon_state *state)
 		FSEventStreamRelease(data->stream);
 	}
 
-	FREE_AND_NULL(state->backend_data);
+	FREE_AND_NULL(state->listen_data);
 }
 
 void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
 {
-	struct fsmonitor_daemon_backend_data *data;
+	struct fsm_listen_data *data;
 
-	data = state->backend_data;
+	data = state->listen_data;
 	data->shutdown_style = SHUTDOWN_EVENT;
 
 	CFRunLoopStop(data->rl);
@@ -422,9 +422,9 @@ void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
 
 void fsm_listen__loop(struct fsmonitor_daemon_state *state)
 {
-	struct fsmonitor_daemon_backend_data *data;
+	struct fsm_listen_data *data;
 
-	data = state->backend_data;
+	data = state->listen_data;
 
 	data->rl = CFRunLoopGetCurrent();
 
@@ -441,7 +441,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state)
 
 	switch (data->shutdown_style) {
 	case FORCE_ERROR_STOP:
-		state->error_code = -1;
+		state->listen_error_code = -1;
 		/* fall thru */
 	case FORCE_SHUTDOWN:
 		ipc_server_stop_async(state->ipc_server_data);
@@ -453,7 +453,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state)
 	return;
 
 force_error_stop_without_loop:
-	state->error_code = -1;
+	state->listen_error_code = -1;
 	ipc_server_stop_async(state->ipc_server_data);
 	return;
 }
diff --git a/compat/fsmonitor/fsm-listen-win32.c b/compat/fsmonitor/fsm-listen-win32.c
index c43d92b9620878..be2d93f47b2e6f 100644
--- a/compat/fsmonitor/fsm-listen-win32.c
+++ b/compat/fsmonitor/fsm-listen-win32.c
@@ -54,7 +54,7 @@ struct one_watch
 	wchar_t dotgit_shortname[16]; /* for 8.3 name */
 };
 
-struct fsmonitor_daemon_backend_data
+struct fsm_listen_data
 {
 	struct one_watch *watch_worktree;
 	struct one_watch *watch_gitdir;
@@ -263,7 +263,7 @@ static enum get_relative_result get_relative_longname(
 
 void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
 {
-	SetEvent(state->backend_data->hListener[LISTENER_SHUTDOWN]);
+	SetEvent(state->listen_data->hListener[LISTENER_SHUTDOWN]);
 }
 
 static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,
@@ -337,7 +337,7 @@ static void destroy_watch(struct one_watch *watch)
 	free(watch);
 }
 
-static int start_rdcw_watch(struct fsmonitor_daemon_backend_data *data,
+static int start_rdcw_watch(struct fsm_listen_data *data,
 			    struct one_watch *watch)
 {
 	DWORD dwNotifyFilter =
@@ -516,7 +516,7 @@ static int process_1_worktree_event(
  */
 static int process_worktree_events(struct fsmonitor_daemon_state *state)
 {
-	struct fsmonitor_daemon_backend_data *data = state->backend_data;
+	struct fsm_listen_data *data = state->listen_data;
 	struct one_watch *watch = data->watch_worktree;
 	struct strbuf path = STRBUF_INIT;
 	struct string_list cookie_list = STRING_LIST_INIT_DUP;
@@ -646,7 +646,7 @@ static int process_worktree_events(struct fsmonitor_daemon_state *state)
  */
 static int process_gitdir_events(struct fsmonitor_daemon_state *state)
 {
-	struct fsmonitor_daemon_backend_data *data = state->backend_data;
+	struct fsm_listen_data *data = state->listen_data;
 	struct one_watch *watch = data->watch_gitdir;
 	struct strbuf path = STRBUF_INIT;
 	struct string_list cookie_list = STRING_LIST_INIT_DUP;
@@ -704,11 +704,11 @@ static int process_gitdir_events(struct fsmonitor_daemon_state *state)
 
 void fsm_listen__loop(struct fsmonitor_daemon_state *state)
 {
-	struct fsmonitor_daemon_backend_data *data = state->backend_data;
+	struct fsm_listen_data *data = state->listen_data;
 	DWORD dwWait;
 	int result;
 
-	state->error_code = 0;
+	state->listen_error_code = 0;
 
 	if (start_rdcw_watch(data, data->watch_worktree) == -1)
 		goto force_error_stop;
@@ -773,7 +773,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state)
 	}
 
 force_error_stop:
-	state->error_code = -1;
+	state->listen_error_code = -1;
 
 force_shutdown:
 	/*
@@ -790,7 +790,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state)
 
 int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
 {
-	struct fsmonitor_daemon_backend_data *data;
+	struct fsm_listen_data *data;
 
 	CALLOC_ARRAY(data, 1);
 
@@ -823,7 +823,7 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
 		data->nr_listener_handles++;
 	}
 
-	state->backend_data = data;
+	state->listen_data = data;
 	return 0;
 
 failed:
@@ -836,16 +836,16 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
 
 void fsm_listen__dtor(struct fsmonitor_daemon_state *state)
 {
-	struct fsmonitor_daemon_backend_data *data;
+	struct fsm_listen_data *data;
 
-	if (!state || !state->backend_data)
+	if (!state || !state->listen_data)
 		return;
 
-	data = state->backend_data;
+	data = state->listen_data;
 
 	CloseHandle(data->hEventShutdown);
 	destroy_watch(data->watch_worktree);
 	destroy_watch(data->watch_gitdir);
 
-	FREE_AND_NULL(state->backend_data);
+	FREE_AND_NULL(state->listen_data);
 }
diff --git a/compat/fsmonitor/fsm-listen.h b/compat/fsmonitor/fsm-listen.h
index f0539349baf848..41650bf8972217 100644
--- a/compat/fsmonitor/fsm-listen.h
+++ b/compat/fsmonitor/fsm-listen.h
@@ -33,7 +33,7 @@ void fsm_listen__dtor(struct fsmonitor_daemon_state *state);
  * do so if the listener thread receives a normal shutdown signal from
  * the IPC layer.)
  *
- * It should set `state->error_code` to -1 if the daemon should exit
+ * It should set `state->listen_error_code` to -1 if the daemon should exit
  * with an error.
  */
 void fsm_listen__loop(struct fsmonitor_daemon_state *state);
diff --git a/fsmonitor--daemon.h b/fsmonitor--daemon.h
index 223c2131b58eb6..2c6fa1a5d91dd4 100644
--- a/fsmonitor--daemon.h
+++ b/fsmonitor--daemon.h
@@ -33,7 +33,7 @@ void fsmonitor_batch__free_list(struct fsmonitor_batch *batch);
  */
 void fsmonitor_batch__add_path(struct fsmonitor_batch *batch, const char *path);
 
-struct fsmonitor_daemon_backend_data; /* opaque platform-specific data */
+struct fsm_listen_data; /* opaque platform-specific data for listener thread */
 
 struct fsmonitor_daemon_state {
 	pthread_t listener_thread;
@@ -50,8 +50,8 @@ struct fsmonitor_daemon_state {
 	int cookie_seq;
 	struct hashmap cookies;
 
-	int error_code;
-	struct fsmonitor_daemon_backend_data *backend_data;
+	int listen_error_code;
+	struct fsm_listen_data *listen_data;
 
 	struct ipc_server_data *ipc_server_data;
 	struct strbuf path_ipc;

From 4985ea180e6d41f431c5b094fce22bb9734e93c6 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Fri, 10 Sep 2021 12:11:17 -0400
Subject: [PATCH 106/265] fsmonitor--daemon: stub in health thread

Create another thread to watch over the daemon process and
automatically shut it down if necessary.

This commit creates the basic framework for a "health" thread
to monitor the daemon and/or the file system.  Later commits
will add platform-specific code to do the actual work.

The "health" thread is intended to monitor conditions that
would be difficult to track inside the IPC thread pool and/or
the file system listener threads.  For example, when there are
file system events outside of the watched worktree root or if
we want to have an idle-timeout auto-shutdown feature.

This commit creates the health thread itself, defines the thread-proc
and sets up the thread's event loop.  It integrates this new thread
into the existing IPC and Listener thread models.

This commit defines the API to the platform-specific code where all of
the monitoring will actually happen.

The platform-specific code for MacOS is just stubs.  Meaning that the
health thread will immediately exit on MacOS, but that is OK and
expected.  Future work can define MacOS-specific monitoring.

The platform-specific code for Windows sets up enough of the
WaitForMultipleObjects() machinery to watch for system and/or custom
events.  Currently, the set of wait handles only includes our custom
shutdown event (sent from our other theads).  Later commits in this
series will extend the set of wait handles to monitor other
conditions.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 Makefile                             |  6 ++-
 builtin/fsmonitor--daemon.c          | 39 +++++++++++++++
 compat/fsmonitor/fsm-health-darwin.c | 24 ++++++++++
 compat/fsmonitor/fsm-health-win32.c  | 72 ++++++++++++++++++++++++++++
 compat/fsmonitor/fsm-health.h        | 47 ++++++++++++++++++
 contrib/buildsystems/CMakeLists.txt  |  2 +
 fsmonitor--daemon.h                  |  4 ++
 7 files changed, 192 insertions(+), 2 deletions(-)
 create mode 100644 compat/fsmonitor/fsm-health-darwin.c
 create mode 100644 compat/fsmonitor/fsm-health-win32.c
 create mode 100644 compat/fsmonitor/fsm-health.h

diff --git a/Makefile b/Makefile
index d7b6218a6134a1..dcf2d8fd6d339e 100644
--- a/Makefile
+++ b/Makefile
@@ -477,8 +477,9 @@ include shared.mak
 #
 # If your platform supports a built-in fsmonitor backend, set
 # FSMONITOR_DAEMON_BACKEND to the "<name>" of the corresponding
-# `compat/fsmonitor/fsm-listen-<name>.c` that implements the
-# `fsm_listen__*()` routines.
+# `compat/fsmonitor/fsm-listen-<name>.c` and
+# `compat/fsmonitor/fsm-health-<name>.c` files
+# that implement the `fsm_listen__*()` and `fsm_health__*()` routines.
 #
 # If your platform has OS-specific ways to tell if a repo is incompatible with
 # fsmonitor (whether the hook or IPC daemon version), set FSMONITOR_OS_SETTINGS
@@ -2010,6 +2011,7 @@ endif
 ifdef FSMONITOR_DAEMON_BACKEND
 	COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND
 	COMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o
+	COMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o
 endif
 
 ifdef FSMONITOR_OS_SETTINGS
diff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c
index d5def8faf90166..e22f53026e9e21 100644
--- a/builtin/fsmonitor--daemon.c
+++ b/builtin/fsmonitor--daemon.c
@@ -3,6 +3,7 @@
 #include "parse-options.h"
 #include "fsmonitor.h"
 #include "fsmonitor-ipc.h"
+#include "compat/fsmonitor/fsm-health.h"
 #include "compat/fsmonitor/fsm-listen.h"
 #include "fsmonitor--daemon.h"
 #include "simple-ipc.h"
@@ -1136,6 +1137,18 @@ void fsmonitor_publish(struct fsmonitor_daemon_state *state,
 	pthread_mutex_unlock(&state->main_lock);
 }
 
+static void *fsm_health__thread_proc(void *_state)
+{
+	struct fsmonitor_daemon_state *state = _state;
+
+	trace2_thread_start("fsm-health");
+
+	fsm_health__loop(state);
+
+	trace2_thread_exit();
+	return NULL;
+}
+
 static void *fsm_listen__thread_proc(void *_state)
 {
 	struct fsmonitor_daemon_state *state = _state;
@@ -1174,6 +1187,7 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)
 		 */
 		.uds_disallow_chdir = 0
 	};
+	int health_started = 0;
 	int listener_started = 0;
 	int err = 0;
 
@@ -1201,6 +1215,17 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)
 	}
 	listener_started = 1;
 
+	/*
+	 * Start the health thread to watch over our process.
+	 */
+	if (pthread_create(&state->health_thread, NULL,
+			   fsm_health__thread_proc, state) < 0) {
+		ipc_server_stop_async(state->ipc_server_data);
+		err = error(_("could not start fsmonitor health thread"));
+		goto cleanup;
+	}
+	health_started = 1;
+
 	/*
 	 * The daemon is now fully functional in background threads.
 	 * Our primary thread should now just wait while the threads
@@ -1223,10 +1248,17 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)
 		pthread_join(state->listener_thread, NULL);
 	}
 
+	if (health_started) {
+		fsm_health__stop_async(state);
+		pthread_join(state->health_thread, NULL);
+	}
+
 	if (err)
 		return err;
 	if (state->listen_error_code)
 		return state->listen_error_code;
+	if (state->health_error_code)
+		return state->health_error_code;
 	return 0;
 }
 
@@ -1242,6 +1274,7 @@ static int fsmonitor_run_daemon(void)
 	pthread_mutex_init(&state.main_lock, NULL);
 	pthread_cond_init(&state.cookies_cond, NULL);
 	state.listen_error_code = 0;
+	state.health_error_code = 0;
 	state.current_token_data = fsmonitor_new_token_data();
 
 	/* Prepare to (recursively) watch the <worktree-root> directory. */
@@ -1321,6 +1354,11 @@ static int fsmonitor_run_daemon(void)
 		goto done;
 	}
 
+	if (fsm_health__ctor(&state)) {
+		err = error(_("could not initialize health thread"));
+		goto done;
+	}
+
 	/*
 	 * CD out of the worktree root directory.
 	 *
@@ -1344,6 +1382,7 @@ static int fsmonitor_run_daemon(void)
 	pthread_cond_destroy(&state.cookies_cond);
 	pthread_mutex_destroy(&state.main_lock);
 	fsm_listen__dtor(&state);
+	fsm_health__dtor(&state);
 
 	ipc_server_free(state.ipc_server_data);
 
diff --git a/compat/fsmonitor/fsm-health-darwin.c b/compat/fsmonitor/fsm-health-darwin.c
new file mode 100644
index 00000000000000..b9f709e854833f
--- /dev/null
+++ b/compat/fsmonitor/fsm-health-darwin.c
@@ -0,0 +1,24 @@
+#include "cache.h"
+#include "config.h"
+#include "fsmonitor.h"
+#include "fsm-health.h"
+#include "fsmonitor--daemon.h"
+
+int fsm_health__ctor(struct fsmonitor_daemon_state *state)
+{
+	return 0;
+}
+
+void fsm_health__dtor(struct fsmonitor_daemon_state *state)
+{
+	return;
+}
+
+void fsm_health__loop(struct fsmonitor_daemon_state *state)
+{
+	return;
+}
+
+void fsm_health__stop_async(struct fsmonitor_daemon_state *state)
+{
+}
diff --git a/compat/fsmonitor/fsm-health-win32.c b/compat/fsmonitor/fsm-health-win32.c
new file mode 100644
index 00000000000000..94b1d020f25ee2
--- /dev/null
+++ b/compat/fsmonitor/fsm-health-win32.c
@@ -0,0 +1,72 @@
+#include "cache.h"
+#include "config.h"
+#include "fsmonitor.h"
+#include "fsm-health.h"
+#include "fsmonitor--daemon.h"
+
+struct fsm_health_data
+{
+	HANDLE hEventShutdown;
+
+	HANDLE hHandles[1]; /* the array does not own these handles */
+#define HEALTH_SHUTDOWN 0
+	int nr_handles; /* number of active event handles */
+};
+
+int fsm_health__ctor(struct fsmonitor_daemon_state *state)
+{
+	struct fsm_health_data *data;
+
+	CALLOC_ARRAY(data, 1);
+
+	data->hEventShutdown = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+	data->hHandles[HEALTH_SHUTDOWN] = data->hEventShutdown;
+	data->nr_handles++;
+
+	state->health_data = data;
+	return 0;
+}
+
+void fsm_health__dtor(struct fsmonitor_daemon_state *state)
+{
+	struct fsm_health_data *data;
+
+	if (!state || !state->health_data)
+		return;
+
+	data = state->health_data;
+
+	CloseHandle(data->hEventShutdown);
+
+	FREE_AND_NULL(state->health_data);
+}
+
+void fsm_health__loop(struct fsmonitor_daemon_state *state)
+{
+	struct fsm_health_data *data = state->health_data;
+
+	for (;;) {
+		DWORD dwWait = WaitForMultipleObjects(data->nr_handles,
+						      data->hHandles,
+						      FALSE, INFINITE);
+
+		if (dwWait == WAIT_OBJECT_0 + HEALTH_SHUTDOWN)
+			goto clean_shutdown;
+
+		error(_("health thread wait failed [GLE %ld]"),
+		      GetLastError());
+		goto force_error_stop;
+	}
+
+force_error_stop:
+	state->health_error_code = -1;
+	ipc_server_stop_async(state->ipc_server_data);
+clean_shutdown:
+	return;
+}
+
+void fsm_health__stop_async(struct fsmonitor_daemon_state *state)
+{
+	SetEvent(state->health_data->hHandles[HEALTH_SHUTDOWN]);
+}
diff --git a/compat/fsmonitor/fsm-health.h b/compat/fsmonitor/fsm-health.h
new file mode 100644
index 00000000000000..45547ba93804a9
--- /dev/null
+++ b/compat/fsmonitor/fsm-health.h
@@ -0,0 +1,47 @@
+#ifndef FSM_HEALTH_H
+#define FSM_HEALTH_H
+
+/* This needs to be implemented by each backend */
+
+#ifdef HAVE_FSMONITOR_DAEMON_BACKEND
+
+struct fsmonitor_daemon_state;
+
+/*
+ * Initialize platform-specific data for the fsmonitor health thread.
+ * This will be called from the main thread PRIOR to staring the
+ * thread.
+ *
+ * Returns 0 if successful.
+ * Returns -1 otherwise.
+ */
+int fsm_health__ctor(struct fsmonitor_daemon_state *state);
+
+/*
+ * Cleanup platform-specific data for the health thread.
+ * This will be called from the main thread AFTER joining the thread.
+ */
+void fsm_health__dtor(struct fsmonitor_daemon_state *state);
+
+/*
+ * The main body of the platform-specific event loop to monitor the
+ * health of the daemon process.  This will run in the health thread.
+ *
+ * The health thread should call `ipc_server_stop_async()` if it needs
+ * to cause a shutdown.  (It should NOT do so if it receives a shutdown
+ * shutdown signal.)
+ *
+ * It should set `state->health_error_code` to -1 if the daemon should exit
+ * with an error.
+ */
+void fsm_health__loop(struct fsmonitor_daemon_state *state);
+
+/*
+ * Gently request that the health thread shutdown.
+ * It does not wait for it to stop.  The caller should do a JOIN
+ * to wait for it.
+ */
+void fsm_health__stop_async(struct fsmonitor_daemon_state *state);
+
+#endif /* HAVE_FSMONITOR_DAEMON_BACKEND */
+#endif /* FSM_HEALTH_H */
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index f04da4cfde6152..21baa6452ebe18 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -297,12 +297,14 @@ if(SUPPORTS_SIMPLE_IPC)
 	if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
 		add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)
 		list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)
+		list(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c)
 
 		add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)
 		list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c)
 	elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
 		add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)
 		list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)
+		list(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c)
 
 		add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)
 		list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)
diff --git a/fsmonitor--daemon.h b/fsmonitor--daemon.h
index 2c6fa1a5d91dd4..2102a5c9ff5bb0 100644
--- a/fsmonitor--daemon.h
+++ b/fsmonitor--daemon.h
@@ -34,9 +34,11 @@ void fsmonitor_batch__free_list(struct fsmonitor_batch *batch);
 void fsmonitor_batch__add_path(struct fsmonitor_batch *batch, const char *path);
 
 struct fsm_listen_data; /* opaque platform-specific data for listener thread */
+struct fsm_health_data; /* opaque platform-specific data for health thread */
 
 struct fsmonitor_daemon_state {
 	pthread_t listener_thread;
+	pthread_t health_thread;
 	pthread_mutex_t main_lock;
 
 	struct strbuf path_worktree_watch;
@@ -51,7 +53,9 @@ struct fsmonitor_daemon_state {
 	struct hashmap cookies;
 
 	int listen_error_code;
+	int health_error_code;
 	struct fsm_listen_data *listen_data;
+	struct fsm_health_data *health_data;
 
 	struct ipc_server_data *ipc_server_data;
 	struct strbuf path_ipc;

From 5d4396ceef945e573a4ce2d32f7543a24ff59868 Mon Sep 17 00:00:00 2001
From: Neeraj Singh <neerajsi@microsoft.com>
Date: Wed, 30 Mar 2022 05:05:19 +0000
Subject: [PATCH 107/265] bulk-checkin: rename 'state' variable and separate
 'plugged' boolean

This commit prepares for adding batch-fsync to the bulk-checkin
infrastructure.

The bulk-checkin infrastructure is currently used to batch up addition
of large blobs to a packfile. When a blob is larger than
big_file_threshold, we unconditionally add it to a pack. If bulk
checkins are 'plugged', we allow multiple large blobs to be added to a
single pack until we reach the packfile size limit; otherwise, we simply
make a new packfile for each large blob. The 'unplug' call tells us when
the series of blob additions is done so that we can finish the packfiles
and make their objects available to subsequent operations.

Stated another way, bulk-checkin allows callers to define a transaction
that adds multiple objects to the object database, where the object
database can optimize its internal operations within the transaction
boundary.

Batched fsync will fit into bulk-checkin by taking advantage of the
plug/unplug functionality to determine the appropriate time to fsync
and make newly-added objects available in the primary object database.

* Rename 'state' variable to 'bulk_checkin_state', since we will later
  be adding 'bulk_fsync_objdir'.  This also makes the variable easier to
  find in the debugger, since the name is more unique.

* Move the 'plugged' data member of 'bulk_checkin_state' into a separate
  static variable. Doing this avoids resetting the variable in
  finish_bulk_checkin when zeroing the 'bulk_checkin_state'. As-is, we
  seem to unintentionally disable the plugging functionality the first
  time a new packfile must be created due to packfile size limits. While
  disabling the plugging state only results in suboptimal behavior for
  the current code, it would be fatal for the bulk-fsync functionality
  later in this patch series.

Signed-off-by: Neeraj Singh <neerajsi@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 bulk-checkin.c | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/bulk-checkin.c b/bulk-checkin.c
index 6d6c37171c9578..577b135e39c37f 100644
--- a/bulk-checkin.c
+++ b/bulk-checkin.c
@@ -10,9 +10,9 @@
 #include "packfile.h"
 #include "object-store.h"
 
-static struct bulk_checkin_state {
-	unsigned plugged:1;
+static int bulk_checkin_plugged;
 
+static struct bulk_checkin_state {
 	char *pack_tmp_name;
 	struct hashfile *f;
 	off_t offset;
@@ -21,7 +21,7 @@ static struct bulk_checkin_state {
 	struct pack_idx_entry **written;
 	uint32_t alloc_written;
 	uint32_t nr_written;
-} state;
+} bulk_checkin_state;
 
 static void finish_tmp_packfile(struct strbuf *basename,
 				const char *pack_tmp_name,
@@ -278,21 +278,23 @@ int index_bulk_checkin(struct object_id *oid,
 		       int fd, size_t size, enum object_type type,
 		       const char *path, unsigned flags)
 {
-	int status = deflate_to_pack(&state, oid, fd, size, type,
+	int status = deflate_to_pack(&bulk_checkin_state, oid, fd, size, type,
 				     path, flags);
-	if (!state.plugged)
-		finish_bulk_checkin(&state);
+	if (!bulk_checkin_plugged)
+		finish_bulk_checkin(&bulk_checkin_state);
 	return status;
 }
 
 void plug_bulk_checkin(void)
 {
-	state.plugged = 1;
+	assert(!bulk_checkin_plugged);
+	bulk_checkin_plugged = 1;
 }
 
 void unplug_bulk_checkin(void)
 {
-	state.plugged = 0;
-	if (state.f)
-		finish_bulk_checkin(&state);
+	assert(bulk_checkin_plugged);
+	bulk_checkin_plugged = 0;
+	if (bulk_checkin_state.f)
+		finish_bulk_checkin(&bulk_checkin_state);
 }

From 7e55143a854c1af36e28f176abb6bf248e452817 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Fri, 10 Sep 2021 12:27:41 -0400
Subject: [PATCH 108/265] fsm-health-win32: add polling framework to monitor
 daemon health

Extend the Windows version of the "health" thread to periodically
inspect the system and shutdown if warranted.

This commit updates the thread's wait loop to use a timeout and
defines a (currently empty) table of functions to poll the system.

A later commit will add functions to the table to actually
inspect the system.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/fsmonitor/fsm-health-win32.c | 65 ++++++++++++++++++++++++++++-
 1 file changed, 64 insertions(+), 1 deletion(-)

diff --git a/compat/fsmonitor/fsm-health-win32.c b/compat/fsmonitor/fsm-health-win32.c
index 94b1d020f25ee2..24fc612bf02653 100644
--- a/compat/fsmonitor/fsm-health-win32.c
+++ b/compat/fsmonitor/fsm-health-win32.c
@@ -4,6 +4,24 @@
 #include "fsm-health.h"
 #include "fsmonitor--daemon.h"
 
+/*
+ * Every minute wake up and test our health.
+ */
+#define WAIT_FREQ_MS (60 * 1000)
+
+/*
+ * State machine states for each of the interval functions
+ * used for polling our health.
+ */
+enum interval_fn_ctx {
+	CTX_INIT = 0,
+	CTX_TERM,
+	CTX_TIMER
+};
+
+typedef int (interval_fn)(struct fsmonitor_daemon_state *state,
+			  enum interval_fn_ctx ctx);
+
 struct fsm_health_data
 {
 	HANDLE hEventShutdown;
@@ -42,18 +60,61 @@ void fsm_health__dtor(struct fsmonitor_daemon_state *state)
 	FREE_AND_NULL(state->health_data);
 }
 
+/*
+ * A table of the polling functions.
+ */
+static interval_fn *table[] = {
+	NULL, /* must be last */
+};
+
+/*
+ * Call all of the polling functions in the table.
+ * Shortcut and return first error.
+ *
+ * Return 0 if all succeeded.
+ */
+static int call_all(struct fsmonitor_daemon_state *state,
+		    enum interval_fn_ctx ctx)
+{
+	int k;
+
+	for (k = 0; table[k]; k++) {
+		int r = table[k](state, ctx);
+		if (r)
+			return r;
+	}
+
+	return 0;
+}
+
 void fsm_health__loop(struct fsmonitor_daemon_state *state)
 {
 	struct fsm_health_data *data = state->health_data;
+	int r;
+
+	r = call_all(state, CTX_INIT);
+	if (r < 0)
+		goto force_error_stop;
+	if (r > 0)
+		goto force_shutdown;
 
 	for (;;) {
 		DWORD dwWait = WaitForMultipleObjects(data->nr_handles,
 						      data->hHandles,
-						      FALSE, INFINITE);
+						      FALSE, WAIT_FREQ_MS);
 
 		if (dwWait == WAIT_OBJECT_0 + HEALTH_SHUTDOWN)
 			goto clean_shutdown;
 
+		if (dwWait == WAIT_TIMEOUT) {
+			r = call_all(state, CTX_TIMER);
+			if (r < 0)
+				goto force_error_stop;
+			if (r > 0)
+				goto force_shutdown;
+			continue;
+		}
+
 		error(_("health thread wait failed [GLE %ld]"),
 		      GetLastError());
 		goto force_error_stop;
@@ -61,8 +122,10 @@ void fsm_health__loop(struct fsmonitor_daemon_state *state)
 
 force_error_stop:
 	state->health_error_code = -1;
+force_shutdown:
 	ipc_server_stop_async(state->ipc_server_data);
 clean_shutdown:
+	call_all(state, CTX_TERM);
 	return;
 }
 

From 0633abbca4f66f1f43b0cf857b40e18f81a7f5f6 Mon Sep 17 00:00:00 2001
From: Neeraj Singh <neerajsi@microsoft.com>
Date: Wed, 30 Mar 2022 05:05:20 +0000
Subject: [PATCH 109/265] bulk-checkin: rebrand plug/unplug APIs as 'odb
 transactions'

Make it clearer in the naming and documentation of the plug_bulk_checkin
and unplug_bulk_checkin APIs that they can be thought of as
a "transaction" to optimize operations on the object database. These
transactions may be nested so that subsystems like the cache-tree
writing code can optimize their operations without caring whether the
top-level code has a transaction active.

Signed-off-by: Neeraj Singh <neerajsi@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/add.c  |  4 ++--
 bulk-checkin.c | 20 ++++++++++++--------
 bulk-checkin.h | 14 ++++++++++++--
 3 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/builtin/add.c b/builtin/add.c
index 3ffb86a43384f2..9bf37ceae8e2fc 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -670,7 +670,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 		string_list_clear(&only_match_skip_worktree, 0);
 	}
 
-	plug_bulk_checkin();
+	begin_odb_transaction();
 
 	if (add_renormalize)
 		exit_status |= renormalize_tracked_files(&pathspec, flags);
@@ -682,7 +682,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 
 	if (chmod_arg && pathspec.nr)
 		exit_status |= chmod_pathspec(&pathspec, chmod_arg[0], show_only);
-	unplug_bulk_checkin();
+	end_odb_transaction();
 
 finish:
 	if (write_locked_index(&the_index, &lock_file,
diff --git a/bulk-checkin.c b/bulk-checkin.c
index 577b135e39c37f..8b0fd5c77230c6 100644
--- a/bulk-checkin.c
+++ b/bulk-checkin.c
@@ -10,7 +10,7 @@
 #include "packfile.h"
 #include "object-store.h"
 
-static int bulk_checkin_plugged;
+static int odb_transaction_nesting;
 
 static struct bulk_checkin_state {
 	char *pack_tmp_name;
@@ -280,21 +280,25 @@ int index_bulk_checkin(struct object_id *oid,
 {
 	int status = deflate_to_pack(&bulk_checkin_state, oid, fd, size, type,
 				     path, flags);
-	if (!bulk_checkin_plugged)
+	if (!odb_transaction_nesting)
 		finish_bulk_checkin(&bulk_checkin_state);
 	return status;
 }
 
-void plug_bulk_checkin(void)
+void begin_odb_transaction(void)
 {
-	assert(!bulk_checkin_plugged);
-	bulk_checkin_plugged = 1;
+	odb_transaction_nesting += 1;
 }
 
-void unplug_bulk_checkin(void)
+void end_odb_transaction(void)
 {
-	assert(bulk_checkin_plugged);
-	bulk_checkin_plugged = 0;
+	odb_transaction_nesting -= 1;
+	if (odb_transaction_nesting < 0)
+		BUG("Unbalanced ODB transaction nesting");
+
+	if (odb_transaction_nesting)
+		return;
+
 	if (bulk_checkin_state.f)
 		finish_bulk_checkin(&bulk_checkin_state);
 }
diff --git a/bulk-checkin.h b/bulk-checkin.h
index b26f3dc3b74cb9..69a94422ac777c 100644
--- a/bulk-checkin.h
+++ b/bulk-checkin.h
@@ -10,7 +10,17 @@ int index_bulk_checkin(struct object_id *oid,
 		       int fd, size_t size, enum object_type type,
 		       const char *path, unsigned flags);
 
-void plug_bulk_checkin(void);
-void unplug_bulk_checkin(void);
+/*
+ * Tell the object database to optimize for adding
+ * multiple objects. end_odb_transaction must be called
+ * to make new objects visible.
+ */
+void begin_odb_transaction(void);
+
+/*
+ * Tell the object database to make any objects from the
+ * current transaction visible.
+ */
+void end_odb_transaction(void);
 
 #endif

From a41d256e052c75addf3e4f4d2051e7681da58660 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Fri, 10 Sep 2021 13:35:37 -0400
Subject: [PATCH 110/265] fsm-health-win32: force shutdown daemon if worktree
 root moves

Force shutdown fsmonitor daemon if the worktree root directory
is moved, renamed, or deleted.

Use Windows low-level GetFileInformationByHandle() to get and
compare the Windows system unique ID for the directory with a
cached version when we started up.  This lets us detect the
case where someone renames the directory that we are watching
and then creates a new directory with the original pathname.

This is important because we are listening to a named pipe for
requests and they are stored in the Named Pipe File System (NPFS)
which a kernel-resident pseudo filesystem not associated with
the actual NTFS directory.

For example, if the daemon was watching "~/foo/", it would have
a directory-watch handle on that directory and a named-pipe
handle for "//./pipe/...foo".  Moving the directory to "~/bar/"
does not invalidate the directory handle.  (So the daemon would
actually be watching "~/bar" but listening on "//./pipe/...foo".
If the user then does "git init ~/foo" and causes another daemon
to start, the first daemon will still have ownership of the pipe
and the second daemon instance will fail to start.  "git status"
clients in "~/foo" will ask "//./pipe/...foo" about changes and
the first daemon instance will tell them about "~/bar".

This commit causes the first daemon to shutdown if the system unique
ID for "~/foo" changes (changes from what it was when the daemon
started).  Shutdown occurs after a periodic poll.  After the
first daemon exits and releases the lock on the named pipe,
subsequent Git commands may cause another daemon to be started
on "~/foo".  Similarly, a subsequent Git command may cause another
daemon to be started on "~/bar".

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/fsmonitor/fsm-health-win32.c | 143 ++++++++++++++++++++++++++++
 1 file changed, 143 insertions(+)

diff --git a/compat/fsmonitor/fsm-health-win32.c b/compat/fsmonitor/fsm-health-win32.c
index 24fc612bf02653..2ea08c1d4e81ec 100644
--- a/compat/fsmonitor/fsm-health-win32.c
+++ b/compat/fsmonitor/fsm-health-win32.c
@@ -29,8 +29,150 @@ struct fsm_health_data
 	HANDLE hHandles[1]; /* the array does not own these handles */
 #define HEALTH_SHUTDOWN 0
 	int nr_handles; /* number of active event handles */
+
+	struct wt_moved
+	{
+		wchar_t wpath[MAX_PATH + 1];
+		BY_HANDLE_FILE_INFORMATION bhfi;
+	} wt_moved;
 };
 
+/*
+ * Lookup the system unique ID for the path.  This is as close as
+ * we get to an inode number, but this also contains volume info,
+ * so it is a little stronger.
+ */
+static int lookup_bhfi(wchar_t *wpath,
+		       BY_HANDLE_FILE_INFORMATION *bhfi)
+{
+	DWORD desired_access = FILE_LIST_DIRECTORY;
+	DWORD share_mode =
+		FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
+	HANDLE hDir;
+
+	hDir = CreateFileW(wpath, desired_access, share_mode, NULL,
+			   OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+	if (hDir == INVALID_HANDLE_VALUE) {
+		error(_("[GLE %ld] health thread could not open '%ls'"),
+		      GetLastError(), wpath);
+		return -1;
+	}
+
+	if (!GetFileInformationByHandle(hDir, bhfi)) {
+		error(_("[GLE %ld] health thread getting BHFI for '%ls'"),
+		      GetLastError(), wpath);
+		CloseHandle(hDir);
+		return -1;
+	}
+
+	CloseHandle(hDir);
+	return 0;
+}
+
+/*
+ * Compare the relevant fields from two system unique IDs.
+ * We use this to see if two different handles to the same
+ * path actually refer to the same *instance* of the file
+ * or directory.
+ */
+static int bhfi_eq(const BY_HANDLE_FILE_INFORMATION *bhfi_1,
+		   const BY_HANDLE_FILE_INFORMATION *bhfi_2)
+{
+	return (bhfi_1->dwVolumeSerialNumber == bhfi_2->dwVolumeSerialNumber &&
+		bhfi_1->nFileIndexHigh == bhfi_2->nFileIndexHigh &&
+		bhfi_1->nFileIndexLow == bhfi_2->nFileIndexLow);
+}
+
+/*
+ * Shutdown if the original worktree root directory been deleted,
+ * moved, or renamed?
+ *
+ * Since the main thread did a "chdir(getenv($HOME))" and our CWD
+ * is not in the worktree root directory and because the listener
+ * thread added FILE_SHARE_DELETE to the watch handle, it is possible
+ * for the root directory to be moved or deleted while we are still
+ * watching it.  We want to detect that here and force a shutdown.
+ *
+ * Granted, a delete MAY cause some operations to fail, such as
+ * GetOverlappedResult(), but it is not guaranteed.  And because
+ * ReadDirectoryChangesW() only reports on changes *WITHIN* the
+ * directory, not changes *ON* the directory, our watch will not
+ * receive a delete event for it.
+ *
+ * A move/rename of the worktree root will also not generate an event.
+ * And since the listener thread already has an open handle, it may
+ * continue to receive events for events within the directory.
+ * However, the pathname of the named-pipe was constructed using the
+ * original location of the worktree root.  (Remember named-pipes are
+ * stored in the NPFS and not in the actual file system.)  Clients
+ * trying to talk to the worktree after the move/rename will not
+ * reach our daemon process, since we're still listening on the
+ * pipe with original path.
+ *
+ * Furthermore, if the user does something like:
+ *
+ *   $ mv repo repo.old
+ *   $ git init repo
+ *
+ * A new daemon cannot be started in the new instance of "repo"
+ * because the named-pipe is still being used by the daemon on
+ * the original instance.
+ *
+ * So, detect move/rename/delete and shutdown.  This should also
+ * handle unsafe drive removal.
+ *
+ * We use the file system unique ID to distinguish the original
+ * directory instance from a new instance and force a shutdown
+ * if the unique ID changes.
+ *
+ * Since a worktree move/rename/delete/unmount doesn't happen
+ * that often (and we can't get an immediate event anyway), we
+ * use a timeout and periodically poll it.
+ */
+static int has_worktree_moved(struct fsmonitor_daemon_state *state,
+			      enum interval_fn_ctx ctx)
+{
+	struct fsm_health_data *data = state->health_data;
+	BY_HANDLE_FILE_INFORMATION bhfi;
+	int r;
+
+	switch (ctx) {
+	case CTX_TERM:
+		return 0;
+
+	case CTX_INIT:
+		if (xutftowcs_path(data->wt_moved.wpath,
+				   state->path_worktree_watch.buf) < 0) {
+			error(_("could not convert to wide characters: '%s'"),
+			      state->path_worktree_watch.buf);
+			return -1;
+		}
+
+		/*
+		 * On the first call we lookup the unique sequence ID for
+		 * the worktree root directory.
+		 */
+		return lookup_bhfi(data->wt_moved.wpath, &data->wt_moved.bhfi);
+
+	case CTX_TIMER:
+		r = lookup_bhfi(data->wt_moved.wpath, &bhfi);
+		if (r)
+			return r;
+		if (!bhfi_eq(&data->wt_moved.bhfi, &bhfi)) {
+			error(_("BHFI changed '%ls'"), data->wt_moved.wpath);
+			return -1;
+		}
+		return 0;
+
+	default:
+		die(_("unhandled case in 'has_worktree_moved': %d"),
+		    (int)ctx);
+	}
+
+	return 0;
+}
+
+
 int fsm_health__ctor(struct fsmonitor_daemon_state *state)
 {
 	struct fsm_health_data *data;
@@ -64,6 +206,7 @@ void fsm_health__dtor(struct fsmonitor_daemon_state *state)
  * A table of the polling functions.
  */
 static interval_fn *table[] = {
+	has_worktree_moved,
 	NULL, /* must be last */
 };
 

From 2cc21b1dab4afaa15f8e22d90f17040be73c5ccd Mon Sep 17 00:00:00 2001
From: Neeraj Singh <neerajsi@microsoft.com>
Date: Wed, 30 Mar 2022 05:05:22 +0000
Subject: [PATCH 111/265] core.fsyncmethod: batched disk flushes for
 loose-objects

When adding many objects to a repo with `core.fsync=loose-object`,
the cost of fsync'ing each object file can become prohibitive.

One major source of the cost of fsync is the implied flush of the
hardware writeback cache within the disk drive. This commit introduces
a new `core.fsyncMethod=batch` option that batches up hardware flushes.
It hooks into the bulk-checkin odb-transaction functionality, takes
advantage of tmp-objdir, and uses the writeout-only support code.

When the new mode is enabled, we do the following for each new object:
1a. Create the object in a tmp-objdir.
2a. Issue a pagecache writeback request and wait for it to complete.

At the end of the entire transaction when unplugging bulk checkin:
1b. Issue an fsync against a dummy file to flush the log and hardware
   writeback cache, which should by now have seen the tmp-objdir writes.
2b. Rename all of the tmp-objdir files to their final names.
3b. When updating the index and/or refs, we assume that Git will issue
   another fsync internal to that operation. This is not the default
   today, but the user now has the option of syncing the index and there
   is a separate patch series to implement syncing of refs.

On a filesystem with a singular journal that is updated during name
operations (e.g. create, link, rename, etc), such as NTFS, HFS+, or XFS
we would expect the fsync to trigger a journal writeout so that this
sequence is enough to ensure that the user's data is durable by the time
the git command returns. This sequence also ensures that no object files
appear in the main object store unless they are fsync-durable.

Batch mode is only enabled if core.fsync includes loose-objects. If
the legacy core.fsyncObjectFiles setting is enabled, but core.fsync does
not include loose-objects, we will use file-by-file fsyncing.

In step (1a) of the sequence, the tmp-objdir is created lazily to avoid
work if no loose objects are ever added to the ODB. We use a tmp-objdir
to maintain the invariant that no loose-objects are visible in the main
ODB unless they are properly fsync-durable. This is important since
future ODB operations that try to create an object with specific
contents will silently drop the new data if an object with the target
hash exists without checking that the loose-object contents match the
hash. Only a full git-fsck would restore the ODB to a functional state
where dataloss doesn't occur.

In step (1b) of the sequence, we issue a fsync against a dummy file
created specifically for the purpose. This method has a little higher
cost than using one of the input object files, but makes adding new
callers of this mechanism easier, since we don't need to figure out
which object file is "last" or risk sharing violations by caching the fd
of the last object file.

_Performance numbers_:

Linux - Hyper-V VM running Kernel 5.11 (Ubuntu 20.04) on a fast SSD.
Mac - macOS 11.5.1 running on a Mac mini on a 1TB Apple SSD.
Windows - Same host as Linux, a preview version of Windows 11.

Adding 500 files to the repo with 'git add' Times reported in seconds.

object file syncing | Linux | Mac   | Windows
--------------------|-------|-------|--------
           disabled | 0.06  |  0.35 | 0.61
              fsync | 1.88  | 11.18 | 2.47
              batch | 0.15  |  0.41 | 1.53

Signed-off-by: Neeraj Singh <neerajsi@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/config/core.txt |  8 ++++
 bulk-checkin.c                | 71 +++++++++++++++++++++++++++++++++++
 bulk-checkin.h                |  3 ++
 cache.h                       |  8 +++-
 config.c                      |  2 +
 object-file.c                 |  7 +++-
 6 files changed, 97 insertions(+), 2 deletions(-)

diff --git a/Documentation/config/core.txt b/Documentation/config/core.txt
index e67392cc838499..57388cce8e72f2 100644
--- a/Documentation/config/core.txt
+++ b/Documentation/config/core.txt
@@ -628,6 +628,14 @@ core.fsyncMethod::
 * `writeout-only` issues pagecache writeback requests, but depending on the
   filesystem and storage hardware, data added to the repository may not be
   durable in the event of a system crash. This is the default mode on macOS.
+* `batch` enables a mode that uses writeout-only flushes to stage multiple
+  updates in the disk writeback cache and then does a single full fsync of
+  a dummy file to trigger the disk cache flush at the end of the operation.
++
+  Currently `batch` mode only applies to loose-object files. Other repository
+  data is made durable as if `fsync` was specified. This mode is expected to
+  be as safe as `fsync` on macOS for repos stored on HFS+ or APFS filesystems
+  and on Windows for repos stored on NTFS or ReFS filesystems.
 
 core.fsyncObjectFiles::
 	This boolean will enable 'fsync()' when writing object files.
diff --git a/bulk-checkin.c b/bulk-checkin.c
index 8b0fd5c77230c6..9799d247cad390 100644
--- a/bulk-checkin.c
+++ b/bulk-checkin.c
@@ -3,15 +3,20 @@
  */
 #include "cache.h"
 #include "bulk-checkin.h"
+#include "lockfile.h"
 #include "repository.h"
 #include "csum-file.h"
 #include "pack.h"
 #include "strbuf.h"
+#include "string-list.h"
+#include "tmp-objdir.h"
 #include "packfile.h"
 #include "object-store.h"
 
 static int odb_transaction_nesting;
 
+static struct tmp_objdir *bulk_fsync_objdir;
+
 static struct bulk_checkin_state {
 	char *pack_tmp_name;
 	struct hashfile *f;
@@ -80,6 +85,40 @@ static void finish_bulk_checkin(struct bulk_checkin_state *state)
 	reprepare_packed_git(the_repository);
 }
 
+/*
+ * Cleanup after batch-mode fsync_object_files.
+ */
+static void do_batch_fsync(void)
+{
+	struct strbuf temp_path = STRBUF_INIT;
+	struct tempfile *temp;
+
+	if (!bulk_fsync_objdir)
+		return;
+
+	/*
+	 * Issue a full hardware flush against a temporary file to ensure
+	 * that all objects are durable before any renames occur. The code in
+	 * fsync_loose_object_bulk_checkin has already issued a writeout
+	 * request, but it has not flushed any writeback cache in the storage
+	 * hardware or any filesystem logs. This fsync call acts as a barrier
+	 * to ensure that the data in each new object file is durable before
+	 * the final name is visible.
+	 */
+	strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX", get_object_directory());
+	temp = xmks_tempfile(temp_path.buf);
+	fsync_or_die(get_tempfile_fd(temp), get_tempfile_path(temp));
+	delete_tempfile(&temp);
+	strbuf_release(&temp_path);
+
+	/*
+	 * Make the object files visible in the primary ODB after their data is
+	 * fully durable.
+	 */
+	tmp_objdir_migrate(bulk_fsync_objdir);
+	bulk_fsync_objdir = NULL;
+}
+
 static int already_written(struct bulk_checkin_state *state, struct object_id *oid)
 {
 	int i;
@@ -274,6 +313,36 @@ static int deflate_to_pack(struct bulk_checkin_state *state,
 	return 0;
 }
 
+void prepare_loose_object_bulk_checkin(void)
+{
+	/*
+	 * We lazily create the temporary object directory
+	 * the first time an object might be added, since
+	 * callers may not know whether any objects will be
+	 * added at the time they call begin_odb_transaction.
+	 */
+	if (!odb_transaction_nesting || bulk_fsync_objdir)
+		return;
+
+	bulk_fsync_objdir = tmp_objdir_create("bulk-fsync");
+	if (bulk_fsync_objdir)
+		tmp_objdir_replace_primary_odb(bulk_fsync_objdir, 0);
+}
+
+void fsync_loose_object_bulk_checkin(int fd, const char *filename)
+{
+	/*
+	 * If we have an active ODB transaction, we issue a call that
+	 * cleans the filesystem page cache but avoids a hardware flush
+	 * command. Later on we will issue a single hardware flush
+	 * before as part of do_batch_fsync.
+	 */
+	if (!bulk_fsync_objdir ||
+	    git_fsync(fd, FSYNC_WRITEOUT_ONLY) < 0) {
+		fsync_or_die(fd, filename);
+	}
+}
+
 int index_bulk_checkin(struct object_id *oid,
 		       int fd, size_t size, enum object_type type,
 		       const char *path, unsigned flags)
@@ -301,4 +370,6 @@ void end_odb_transaction(void)
 
 	if (bulk_checkin_state.f)
 		finish_bulk_checkin(&bulk_checkin_state);
+
+	do_batch_fsync();
 }
diff --git a/bulk-checkin.h b/bulk-checkin.h
index 69a94422ac777c..70edf745be8a0e 100644
--- a/bulk-checkin.h
+++ b/bulk-checkin.h
@@ -6,6 +6,9 @@
 
 #include "cache.h"
 
+void prepare_loose_object_bulk_checkin(void);
+void fsync_loose_object_bulk_checkin(int fd, const char *filename);
+
 int index_bulk_checkin(struct object_id *oid,
 		       int fd, size_t size, enum object_type type,
 		       const char *path, unsigned flags);
diff --git a/cache.h b/cache.h
index 6226f6a8a53f65..ea1466340d750e 100644
--- a/cache.h
+++ b/cache.h
@@ -1040,7 +1040,8 @@ extern int use_fsync;
 
 enum fsync_method {
 	FSYNC_METHOD_FSYNC,
-	FSYNC_METHOD_WRITEOUT_ONLY
+	FSYNC_METHOD_WRITEOUT_ONLY,
+	FSYNC_METHOD_BATCH,
 };
 
 extern enum fsync_method fsync_method;
@@ -1766,6 +1767,11 @@ void fsync_or_die(int fd, const char *);
 int fsync_component(enum fsync_component component, int fd);
 void fsync_component_or_die(enum fsync_component component, int fd, const char *msg);
 
+static inline int batch_fsync_enabled(enum fsync_component component)
+{
+	return (fsync_components & component) && (fsync_method == FSYNC_METHOD_BATCH);
+}
+
 ssize_t read_in_full(int fd, void *buf, size_t count);
 ssize_t write_in_full(int fd, const void *buf, size_t count);
 ssize_t pread_in_full(int fd, void *buf, size_t count, off_t offset);
diff --git a/config.c b/config.c
index a5e11aad7fe3b7..8ff25642906f08 100644
--- a/config.c
+++ b/config.c
@@ -1688,6 +1688,8 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
 			fsync_method = FSYNC_METHOD_FSYNC;
 		else if (!strcmp(value, "writeout-only"))
 			fsync_method = FSYNC_METHOD_WRITEOUT_ONLY;
+		else if (!strcmp(value, "batch"))
+			fsync_method = FSYNC_METHOD_BATCH;
 		else
 			warning(_("ignoring unknown core.fsyncMethod value '%s'"), value);
 
diff --git a/object-file.c b/object-file.c
index 5ffbf3d4fd46de..d2e0c13198f643 100644
--- a/object-file.c
+++ b/object-file.c
@@ -1893,7 +1893,9 @@ static void close_loose_object(int fd, const char *filename)
 	if (the_repository->objects->odb->will_destroy)
 		goto out;
 
-	if (fsync_object_files > 0)
+	if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
+		fsync_loose_object_bulk_checkin(fd, filename);
+	else if (fsync_object_files > 0)
 		fsync_or_die(fd, filename);
 	else
 		fsync_component_or_die(FSYNC_COMPONENT_LOOSE_OBJECT, fd,
@@ -1961,6 +1963,9 @@ static int write_loose_object(const struct object_id *oid, char *hdr,
 	static struct strbuf tmp_file = STRBUF_INIT;
 	static struct strbuf filename = STRBUF_INIT;
 
+	if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
+		prepare_loose_object_bulk_checkin();
+
 	loose_object_path(the_repository, &filename, oid);
 
 	fd = create_tmpfile(&tmp_file, filename.buf);

From 5c65472617f153b98a05f2e0786dbadc97e7745a Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Fri, 10 Sep 2021 14:59:48 -0400
Subject: [PATCH 112/265] fsm-listen-darwin: shutdown daemon if worktree root
 is moved/renamed

Teach the listener thread to shutdown the daemon if the spelling of the
worktree root directory changes.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/fsmonitor/fsm-listen-darwin.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/compat/fsmonitor/fsm-listen-darwin.c b/compat/fsmonitor/fsm-listen-darwin.c
index 07113205a6152b..83d38e8ac6c833 100644
--- a/compat/fsmonitor/fsm-listen-darwin.c
+++ b/compat/fsmonitor/fsm-listen-darwin.c
@@ -106,6 +106,11 @@ static void log_flags_set(const char *path, const FSEventStreamEventFlags flag)
 	strbuf_release(&msg);
 }
 
+static int ef_is_root_changed(const FSEventStreamEventFlags ef)
+{
+	return (ef & kFSEventStreamEventFlagRootChanged);
+}
+
 static int ef_is_root_delete(const FSEventStreamEventFlags ef)
 {
 	return (ef & kFSEventStreamEventFlagItemIsDir &&
@@ -215,6 +220,26 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,
 			continue;
 		}
 
+		if (ef_is_root_changed(event_flags[k])) {
+			/*
+			 * The spelling of the pathname of the root directory
+			 * has changed.  This includes the name of the root
+			 * directory itself or of any parent directory in the
+			 * path.
+			 *
+			 * (There may be other conditions that throw this,
+			 * but I couldn't find any information on it.)
+			 *
+			 * Force a shutdown now and avoid things getting
+			 * out of sync.  The Unix domain socket is inside
+			 * the .git directory and a spelling change will make
+			 * it hard for clients to rendezvous with us.
+			 */
+			trace_printf_key(&trace_fsmonitor,
+					 "event: root changed");
+			goto force_shutdown;
+		}
+
 		if (ef_ignore_xattr(event_flags[k])) {
 			trace_printf_key(&trace_fsmonitor,
 					 "ignore-xattr: '%s', flags=0x%x",

From fd84f6dc8189b547c85f5b458b0c36d3cc8a5c74 Mon Sep 17 00:00:00 2001
From: Neeraj Singh <neerajsi@microsoft.com>
Date: Wed, 30 Mar 2022 05:05:23 +0000
Subject: [PATCH 113/265] cache-tree: use ODB transaction around writing a tree

Take advantage of the odb transaction infrastructure around writing the
cached tree to the object database.

Signed-off-by: Neeraj Singh <neerajsi@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 cache-tree.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/cache-tree.c b/cache-tree.c
index 6752f69d515e12..8c5e8822716e04 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -3,6 +3,7 @@
 #include "tree.h"
 #include "tree-walk.h"
 #include "cache-tree.h"
+#include "bulk-checkin.h"
 #include "object-store.h"
 #include "replace-object.h"
 #include "promisor-remote.h"
@@ -474,8 +475,10 @@ int cache_tree_update(struct index_state *istate, int flags)
 
 	trace_performance_enter();
 	trace2_region_enter("cache_tree", "update", the_repository);
+	begin_odb_transaction();
 	i = update_one(istate->cache_tree, istate->cache, istate->cache_nr,
 		       "", 0, &skip, flags);
+	end_odb_transaction();
 	trace2_region_leave("cache_tree", "update", the_repository);
 	trace_performance_leave("cache_tree_update");
 	if (i < 0)

From 569a619af9e645f4f9206386224d3fcc45a88db0 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Tue, 21 Sep 2021 15:06:33 -0400
Subject: [PATCH 114/265] fsmonitor: optimize processing of directory events

Teach Git to perform binary search over the cache-entries for a directory
notification and then linearly scan forward to find the immediate children.

Previously, when the FSMonitor reported a modified directory Git would
perform a linear search on the entire cache-entry array for all
entries matching that directory prefix and invalidate them.  Since the
cache-entry array is already sorted, we can use a binary search to
find the first matching entry and then only linearly walk forward and
invalidate entries until the prefix changes.

Also, the original code would invalidate anything having the same
directory prefix.  Since a directory event should only be received for
items that are immediately within the directory (and not within
sub-directories of it), only invalidate those entries and not the
whole subtree.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 fsmonitor.c | 71 ++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 54 insertions(+), 17 deletions(-)

diff --git a/fsmonitor.c b/fsmonitor.c
index 292a6742b4fa23..e1229c289cfecc 100644
--- a/fsmonitor.c
+++ b/fsmonitor.c
@@ -184,30 +184,68 @@ static int query_fsmonitor_hook(struct repository *r,
 static void fsmonitor_refresh_callback(struct index_state *istate, char *name)
 {
 	int i, len = strlen(name);
-	if (name[len - 1] == '/') {
+	int pos = index_name_pos(istate, name, len);
+
+	trace_printf_key(&trace_fsmonitor,
+			 "fsmonitor_refresh_callback '%s' (pos %d)",
+			 name, pos);
 
+	if (name[len - 1] == '/') {
 		/*
-		 * TODO We should binary search to find the first path with
-		 * TODO this directory prefix.  Then linearly update entries
-		 * TODO while the prefix matches.  Taking care to search without
-		 * TODO the trailing slash -- because '/' sorts after a few
-		 * TODO interesting special chars, like '.' and ' '.
+		 * The daemon can decorate directory events, such as
+		 * moves or renames, with a trailing slash if the OS
+		 * FS Event contains sufficient information, such as
+		 * MacOS.
+		 *
+		 * Use this to invalidate the entire cone under that
+		 * directory.
+		 *
+		 * We do not expect an exact match because the index
+		 * does not normally contain directory entries, so we
+		 * start at the insertion point and scan.
 		 */
+		if (pos < 0)
+			pos = -pos - 1;
 
 		/* Mark all entries for the folder invalid */
-		for (i = 0; i < istate->cache_nr; i++) {
-			if (istate->cache[i]->ce_flags & CE_FSMONITOR_VALID &&
-			    starts_with(istate->cache[i]->name, name))
-				istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
+		for (i = pos; i < istate->cache_nr; i++) {
+			if (!starts_with(istate->cache[i]->name, name))
+				break;
+			istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
 		}
-		/* Need to remove the / from the path for the untracked cache */
+
+		/*
+		 * We need to remove the traling "/" from the path
+		 * for the untracked cache.
+		 */
 		name[len - 1] = '\0';
+	} else if (pos >= 0) {
+		/*
+		 * We have an exact match for this path and can just
+		 * invalidate it.
+		 */
+		istate->cache[pos]->ce_flags &= ~CE_FSMONITOR_VALID;
 	} else {
-		int pos = index_name_pos(istate, name, strlen(name));
-
-		if (pos >= 0) {
-			struct cache_entry *ce = istate->cache[pos];
-			ce->ce_flags &= ~CE_FSMONITOR_VALID;
+		/*
+		 * The path is not a tracked file -or- it is a
+		 * directory event on a platform that cannot
+		 * distinguish between file and directory events in
+		 * the event handler, such as Windows.
+		 *
+		 * Scan as if it is a directory and invalidate the
+		 * cone under it.  (But remember to ignore items
+		 * between "name" and "name/", such as "name-" and
+		 * "name.".
+		 */
+		pos = -pos - 1;
+
+		for (i = pos; i < istate->cache_nr; i++) {
+			if (!starts_with(istate->cache[i]->name, name))
+				break;
+			if ((unsigned char)istate->cache[i]->name[len] > '/')
+				break;
+			if (istate->cache[i]->name[len] == '/')
+				istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
 		}
 	}
 
@@ -215,7 +253,6 @@ static void fsmonitor_refresh_callback(struct index_state *istate, char *name)
 	 * Mark the untracked cache dirty even if it wasn't found in the index
 	 * as it could be a new untracked file.
 	 */
-	trace_printf_key(&trace_fsmonitor, "fsmonitor_refresh_callback '%s'", name);
 	untracked_cache_invalidate_path(istate, name, 0);
 }
 

From 0f6e6e14650f6d0ccb9dbf5f1a04ea7577c998ac Mon Sep 17 00:00:00 2001
From: Neeraj Singh <neerajsi@microsoft.com>
Date: Wed, 30 Mar 2022 05:05:24 +0000
Subject: [PATCH 115/265] builtin/add: add ODB transaction around
 add_files_to_cache

The add_files_to_cache function is invoked internally by
builtin/commit.c and builtin/checkout.c for their flags that stage
modified files before doing the larger operation. These commands
can benefit from batched fsyncing.

Signed-off-by: Neeraj Singh <neerajsi@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/add.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/builtin/add.c b/builtin/add.c
index 9bf37ceae8e2fc..e39770e4746fc4 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -141,7 +141,16 @@ int add_files_to_cache(const char *prefix,
 	rev.diffopt.format_callback_data = &data;
 	rev.diffopt.flags.override_submodule_config = 1;
 	rev.max_count = 0; /* do not compare unmerged paths with stage #2 */
+
+	/*
+	 * Use an ODB transaction to optimize adding multiple objects.
+	 * This function is invoked from commands other than 'add', which
+	 * may not have their own transaction active.
+	 */
+	begin_odb_transaction();
 	run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
+	end_odb_transaction();
+
 	clear_pathspec(&rev.prune_data);
 	return !!data.add_errors;
 }

From 216173ff3a980d53a866da8b75a090f753367142 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Wed, 13 Oct 2021 10:00:50 -0400
Subject: [PATCH 116/265] t7527: FSMonitor tests for directory moves

Create unit tests to move a directory.  Verify that `git status`
gives the same result with and without FSMonitor enabled.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 t/t7527-builtin-fsmonitor.sh | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh
index c9c7dd77e3cc18..d0e681d008f1cb 100755
--- a/t/t7527-builtin-fsmonitor.sh
+++ b/t/t7527-builtin-fsmonitor.sh
@@ -274,6 +274,16 @@ test_expect_success 'setup' '
 	trace*
 	EOF
 
+	mkdir -p T1/T2/T3/T4 &&
+	echo 1 >T1/F1 &&
+	echo 1 >T1/T2/F1 &&
+	echo 1 >T1/T2/T3/F1 &&
+	echo 1 >T1/T2/T3/T4/F1 &&
+	echo 2 >T1/F2 &&
+	echo 2 >T1/T2/F2 &&
+	echo 2 >T1/T2/T3/F2 &&
+	echo 2 >T1/T2/T3/T4/F2 &&
+
 	git -c core.fsmonitor=false add . &&
 	test_tick &&
 	git -c core.fsmonitor=false commit -m initial &&
@@ -356,6 +366,19 @@ directory_to_file () {
 	echo 1 >dir1
 }
 
+move_directory_contents_deeper() {
+	mkdir T1/_new_ &&
+	mv T1/[A-Z]* T1/_new_
+}
+
+move_directory_up() {
+	mv T1/T2/T3 T1
+}
+
+move_directory() {
+	mv T1/T2/T3 T1/T2/NewT3
+}
+
 # The next few test cases confirm that our fsmonitor daemon sees each type
 # of OS filesystem notification that we care about.  At this layer we just
 # ensure we are getting the OS notifications and do not try to confirm what
@@ -660,6 +683,10 @@ do
 		matrix_try $uc_val $fsm_val file_to_directory
 		matrix_try $uc_val $fsm_val directory_to_file
 
+		matrix_try $uc_val $fsm_val move_directory_contents_deeper
+		matrix_try $uc_val $fsm_val move_directory_up
+		matrix_try $uc_val $fsm_val move_directory
+
 		if test $fsm_val = true
 		then
 			test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] disable fsmonitor at end" '

From 3aea0d37c67da8e6e8e57d7d2473ae143459bce4 Mon Sep 17 00:00:00 2001
From: Philip Oakley <philipoakley@iee.email>
Date: Sun, 31 Oct 2021 23:15:13 +0000
Subject: [PATCH 117/265] hash-object: demonstrate a >4GB/LLP64 problem

On LLP64 systems, such as Windows, the size of `long`, `int`, etc. is
only 32 bits (for backward compatibility). Git's use of `unsigned long`
for file memory sizes in many places, rather than size_t, limits the
handling of large files on LLP64 systems (commonly given as `>4GB`).

Provide a minimum test for handling a >4GB file. The `hash-object`
command, with the  `--literally` and without `-w` option avoids
writing the object, either loose or packed. This avoids the code paths
hitting the `bigFileThreshold` config test code, the zlib code, and the
pack code.

Subsequent patches will walk the test's call chain, converting types to
`size_t` (which is larger in LLP64 data models) where appropriate.

Signed-off-by: Philip Oakley <philipoakley@iee.email>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t1007-hash-object.sh | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh
index ac5ad8c7402d2b..428298fa2934d6 100755
--- a/t/t1007-hash-object.sh
+++ b/t/t1007-hash-object.sh
@@ -50,6 +50,9 @@ test_expect_success 'setup' '
 
 	example sha1:ddd3f836d3e3fbb7ae289aa9ae83536f76956399
 	example sha256:b44fe1fe65589848253737db859bd490453510719d7424daab03daf0767b85ae
+
+	large5GB sha1:0be2be10a4c8764f32c4bf372a98edc731a4b204
+	large5GB sha256:dc18ca621300c8d3cfa505a275641ebab00de189859e022a975056882d313e64
 	EOF
 '
 
@@ -249,4 +252,12 @@ test_expect_success '--literally with extra-long type' '
 	echo example | git hash-object -t $t --literally --stdin
 '
 
+test_expect_failure EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \
+		'files over 4GB hash literally' '
+	test-tool genzeros $((5*1024*1024*1024)) >big &&
+	test_oid large5GB >expect &&
+	git hash-object --stdin --literally <big >actual &&
+	test_cmp expect actual
+'
+
 test_done

From 9eb99dc85359bc078a7fe68155f0866720bbefed Mon Sep 17 00:00:00 2001
From: Neeraj Singh <neerajsi@microsoft.com>
Date: Wed, 30 Mar 2022 05:05:25 +0000
Subject: [PATCH 118/265] update-index: use the bulk-checkin infrastructure

The update-index functionality is used internally by 'git stash push' to
setup the internal stashed commit.

This change enables odb-transactions for update-index infrastructure to
speed up adding new objects to the object database by leveraging the
batch fsync functionality.

There is some risk with this change, since under batch fsync, the object
files will be in a tmp-objdir until update-index is complete, so callers
using the --stdin option will not see them until update-index is done.
This risk is mitigated by not keeping an ODB transaction open around
--stdin processing if in --verbose mode. Without --verbose mode,
a caller feeding update-index via --stdin wouldn't know when
update-index adds an object, event without an ODB transaction.

Signed-off-by: Neeraj Singh <neerajsi@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/update-index.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/builtin/update-index.c b/builtin/update-index.c
index 876112abb21a4d..e2ec31f0b32f32 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -5,6 +5,7 @@
  */
 #define USE_THE_INDEX_COMPATIBILITY_MACROS
 #include "cache.h"
+#include "bulk-checkin.h"
 #include "config.h"
 #include "lockfile.h"
 #include "quote.h"
@@ -1116,6 +1117,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 	 */
 	parse_options_start(&ctx, argc, argv, prefix,
 			    options, PARSE_OPT_STOP_AT_NON_OPTION);
+
+	/*
+	 * Allow the object layer to optimize adding multiple objects in
+	 * a batch.
+	 */
+	begin_odb_transaction();
 	while (ctx.argc) {
 		if (parseopt_state != PARSE_OPT_DONE)
 			parseopt_state = parse_options_step(&ctx, options,
@@ -1167,6 +1174,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 		the_index.version = preferred_index_format;
 	}
 
+	/*
+	 * It is possible, though unlikely, that a caller could use the verbose
+	 * output to synchronize with addition of objects to the object
+	 * database. The current implementation of ODB transactions leaves
+	 * objects invisible while a transaction is active, so end the
+	 * transaction here if verbose output is enabled.
+	 */
+
+	if (verbose)
+		end_odb_transaction();
+
 	if (read_from_stdin) {
 		struct strbuf buf = STRBUF_INIT;
 		struct strbuf unquoted = STRBUF_INIT;
@@ -1190,6 +1208,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 		strbuf_release(&buf);
 	}
 
+	/*
+	 * By now we have added all of the new objects
+	 */
+	if (!verbose)
+		end_odb_transaction();
+
 	if (split_index > 0) {
 		if (git_config_get_split_index() == 0)
 			warning(_("core.splitIndex is set to false; "

From 062b07a67da5021a8bba1036b11091ae152b2020 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Fri, 1 Oct 2021 14:40:35 -0400
Subject: [PATCH 119/265] t/perf/p7527: add perf test for builtin FSMonitor

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 t/perf/p7527-builtin-fsmonitor.sh | 257 ++++++++++++++++++++++++++++++
 1 file changed, 257 insertions(+)
 create mode 100755 t/perf/p7527-builtin-fsmonitor.sh

diff --git a/t/perf/p7527-builtin-fsmonitor.sh b/t/perf/p7527-builtin-fsmonitor.sh
new file mode 100755
index 00000000000000..9338b9ea008d59
--- /dev/null
+++ b/t/perf/p7527-builtin-fsmonitor.sh
@@ -0,0 +1,257 @@
+#!/bin/sh
+
+test_description="Perf test for the builtin FSMonitor"
+
+. ./perf-lib.sh
+
+if ! test_have_prereq FSMONITOR_DAEMON
+then
+	skip_all="fsmonitor--daemon is not supported on this platform"
+	test_done
+fi
+
+test_lazy_prereq UNTRACKED_CACHE '
+	{ git update-index --test-untracked-cache; ret=$?; } &&
+	test $ret -ne 1
+'
+
+# Lie to perf-lib and ask for a new empty repo and avoid
+# the complaints about GIT_PERF_REPO not being big enough
+# the perf hit when GIT_PERF_LARGE_REPO is copied into
+# the trash directory.
+#
+# NEEDSWORK: It would be nice if perf-lib had an option to
+# "borrow" an existing large repo (especially for gigantic
+# monorepos) and use it in-place.  For now, fake it here.
+#
+test_perf_fresh_repo
+
+
+# Use a generated synthetic monorepo.  If it doesn't exist, we will
+# generate it.  If it does exist, we will put it in a known state
+# before we start our timings.
+#
+PARAM_D=5
+PARAM_W=10
+PARAM_F=9
+
+PARAMS="$PARAM_D"."$PARAM_W"."$PARAM_F"
+
+BALLAST_BR=p0006-ballast
+export BALLAST_BR
+
+TMP_BR=tmp_br
+export TMP_BR
+
+REPO=../repos/gen-many-files-"$PARAMS".git
+export REPO
+
+if ! test -d $REPO
+then
+	(cd ../repos; ./many-files.sh -d $PARAM_D -w $PARAM_W -f $PARAM_F)
+fi
+
+
+enable_uc () {
+	git -C $REPO config core.untrackedcache true
+	git -C $REPO update-index --untracked-cache
+	git -C $REPO status >/dev/null 2>&1
+}
+
+disable_uc () {
+	git -C $REPO config core.untrackedcache false
+	git -C $REPO update-index --no-untracked-cache
+	git -C $REPO status >/dev/null 2>&1
+}
+
+start_fsm () {
+	git -C $REPO fsmonitor--daemon start
+	git -C $REPO fsmonitor--daemon status
+	git -C $REPO config core.fsmonitor true
+	git -C $REPO update-index --fsmonitor
+	git -C $REPO status >/dev/null 2>&1
+}
+
+stop_fsm () {
+	git -C $REPO config --unset core.fsmonitor
+	git -C $REPO update-index --no-fsmonitor
+	test_might_fail git -C $REPO fsmonitor--daemon stop 2>/dev/null
+	git -C $REPO status >/dev/null 2>&1
+}
+
+
+# Ensure that FSMonitor is turned off on the borrowed repo.
+#
+test_expect_success "Setup borrowed repo (fsm+uc)" "
+	stop_fsm &&
+	disable_uc
+"
+
+# Also ensure that it starts in a known state.
+#
+# Because we assume that $GIT_PERF_REPEAT_COUNT > 1, we are not going to time
+# the ballast checkout, since only the first invocation does any work and the
+# subsequent ones just print "already on branch" and quit, so the reported
+# time is not useful.
+#
+# Create a temp branch and do all work relative to it so that we don't
+# accidentially alter the real ballast branch.
+#
+test_expect_success "Setup borrowed repo (temp ballast branch)" "
+	test_might_fail git -C $REPO checkout $BALLAST_BR &&
+	test_might_fail git -C $REPO reset --hard &&
+	git -C $REPO clean -d -f &&
+	test_might_fail git -C $REPO branch -D $TMP_BR &&
+	git -C $REPO branch $TMP_BR $BALLAST_BR &&
+	git -C $REPO checkout $TMP_BR
+"
+
+
+echo Data >data.txt
+
+# NEEDSWORK: We assume that $GIT_PERF_REPEAT_COUNT > 1.  With
+# FSMonitor enabled, we can get a skewed view of status times, since
+# the index MAY (or may not) be updated after the first invocation
+# which will update the FSMonitor Token, so the subsequent invocations
+# may get a smaller response from the daemon.
+#
+do_status () {
+	msg=$1
+
+	test_perf "$msg" "
+		git -C $REPO status >/dev/null 2>&1
+	"
+}
+
+do_matrix () {
+	uc=$1
+	fsm=$2
+
+	t="[uc $uc][fsm $fsm]"
+	MATRIX_BR="$TMP_BR-$uc-$fsm"
+
+	test_expect_success "$t Setup matrix branch" "
+		git -C $REPO clean -d -f &&
+		git -C $REPO checkout $TMP_BR &&
+		test_might_fail git -C $REPO branch -D $MATRIX_BR &&
+		git -C $REPO branch $MATRIX_BR $TMP_BR &&
+		git -C $REPO checkout $MATRIX_BR
+	"
+
+	if test $uc = true
+	then
+		enable_uc
+	else
+		disable_uc
+	fi
+
+	if test $fsm = true
+	then
+		start_fsm
+	else
+		stop_fsm
+	fi
+
+	do_status "$t status after checkout"
+
+	# Modify many files in the matrix branch.
+	# Stage them.
+	# Commit them.
+	# Rollback.
+	#
+	test_expect_success "$t modify tracked files" "
+		find $REPO -name file1 -exec cp data.txt {} \\;
+	"
+
+	do_status "$t status after big change"
+
+	# Don't bother timing the "add" because _REPEAT_COUNT
+	# issue described above.
+	#
+	test_expect_success "$t add all" "
+		git -C $REPO add -A
+	"
+
+	do_status "$t status after add all"
+
+	test_expect_success "$t add dot" "
+		git -C $REPO add .
+	"
+
+	do_status "$t status after add dot"
+
+	test_expect_success "$t commit staged" "
+		git -C $REPO commit -a -m data
+	"
+
+	do_status "$t status after commit"
+
+	test_expect_success "$t reset HEAD~1 hard" "
+		git -C $REPO reset --hard HEAD~1 >/dev/null 2>&1
+	"
+
+	do_status "$t status after reset hard"
+
+	# Create some untracked files.
+	#
+	test_expect_success "$t create untracked files" "
+		cp -R $REPO/ballast/dir1 $REPO/ballast/xxx1
+	"
+
+	do_status "$t status after create untracked files"
+
+	# Remove the new untracked files.
+	#
+	test_expect_success "$t clean -df" "
+		git -C $REPO clean -d -f
+	"
+
+	do_status "$t status after clean"
+
+	if test $fsm = true
+	then
+		stop_fsm
+	fi
+}
+
+# Begin testing each case in the matrix that we care about.
+#
+uc_values="false"
+test_have_prereq UNTRACKED_CACHE && uc_values="false true"
+
+fsm_values="false true"
+
+for uc_val in $uc_values
+do
+	for fsm_val in $fsm_values
+	do
+		do_matrix $uc_val $fsm_val
+	done
+done
+
+cleanup () {
+	uc=$1
+	fsm=$2
+
+	MATRIX_BR="$TMP_BR-$uc-$fsm"
+
+	test_might_fail git -C $REPO branch -D $MATRIX_BR
+}
+
+
+# We're borrowing this repo.  We should leave it in a clean state.
+#
+test_expect_success "Cleanup temp and matrix branches" "
+	git -C $REPO clean -d -f &&
+	test_might_fail git -C $REPO checkout $BALLAST_BR &&
+	test_might_fail git -C $REPO branch -D $TMP_BR &&
+	for uc_val in $uc_values
+	do
+		for fsm_val in $fsm_values
+		do
+			cleanup $uc_val $fsm_val
+		done
+	done
+"
+
+test_done

From 2f609e0007ae05c8d03e7f9fdf1a2293b48a82ce Mon Sep 17 00:00:00 2001
From: Philip Oakley <philipoakley@iee.email>
Date: Fri, 12 Nov 2021 21:07:03 +0000
Subject: [PATCH 120/265] hash_object_file_literally(): use size_t

The previous commit adds a test that demonstrates a problem in the
`hash-object --literally` command, manifesting in an unnecessary file
size limit on systems using the LLP64 data model (which includes
Windows).

Walking the affected code path is `cmd_hash_object()` >> `hash_fd()` >>
`hash_literally()` >> `hash_object_file_literally()`.

The function `hash_object_file_literally()` is the first with a file
length parameter (via a mem buffer). This commit changes the type of
that parameter to the LLP64 compatible `size_t` type.

There are no other uses of the function. The `strbuf` type is already
`size_t` compatible.

Note: The hash-object test does not yet pass. Subsequent commits will
continue to walk the call tree's lower level functions to identify
further fixes.

Signed-off-by: Philip Oakley <philipoakley@iee.email>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 object-file.c  | 4 ++--
 object-store.h | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/object-file.c b/object-file.c
index 5ffbf3d4fd46de..d52da4abe6792c 100644
--- a/object-file.c
+++ b/object-file.c
@@ -1810,7 +1810,7 @@ static void write_object_file_prepare(const struct git_hash_algo *algo,
 }
 
 static void write_object_file_prepare_literally(const struct git_hash_algo *algo,
-				      const void *buf, unsigned long len,
+				      const void *buf, size_t len,
 				      const char *type, struct object_id *oid,
 				      char *hdr, int *hdrlen)
 {
@@ -2060,7 +2060,7 @@ int write_object_file_flags(const void *buf, unsigned long len,
 	return write_loose_object(oid, hdr, hdrlen, buf, len, 0, flags);
 }
 
-int write_object_file_literally(const void *buf, unsigned long len,
+int write_object_file_literally(const void *buf, size_t len,
 				const char *type, struct object_id *oid,
 				unsigned flags)
 {
diff --git a/object-store.h b/object-store.h
index bd2322ed8ce336..a8fdc0cd178fbe 100644
--- a/object-store.h
+++ b/object-store.h
@@ -258,7 +258,7 @@ static inline int write_object_file(const void *buf, unsigned long len,
 	return write_object_file_flags(buf, len, type, oid, 0);
 }
 
-int write_object_file_literally(const void *buf, unsigned long len,
+int write_object_file_literally(const void *buf, size_t len,
 				const char *type, struct object_id *oid,
 				unsigned flags);
 

From 3ef676b1a3a2d41027555196340f043a684326c6 Mon Sep 17 00:00:00 2001
From: Neeraj Singh <neerajsi@microsoft.com>
Date: Wed, 30 Mar 2022 05:05:26 +0000
Subject: [PATCH 121/265] unpack-objects: use the bulk-checkin infrastructure

The unpack-objects functionality is used by fetch, push, and fast-import
to turn the transfered data into object database entries when there are
fewer objects than the 'unpacklimit' setting.

By enabling an odb-transaction when unpacking objects, we can take advantage
of batched fsyncs.

Here are some performance numbers to justify batch mode for
unpack-objects, collected on a WSL2 Ubuntu VM.

Fsync Mode | Time for 90 objects (ms)
-------------------------------------
       Off | 170
  On,fsync | 760
  On,batch | 230

Note that the default unpackLimit is 100 objects, so there's a 3x
benefit in the worst case. The non-batch mode fsync scales linearly
with the number of objects, so there are significant benefits even with
smaller numbers of objects.

Signed-off-by: Neeraj Singh <neerajsi@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/unpack-objects.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index dbeb0680a58563..56d05e2725db45 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -1,5 +1,6 @@
 #include "builtin.h"
 #include "cache.h"
+#include "bulk-checkin.h"
 #include "config.h"
 #include "object-store.h"
 #include "object.h"
@@ -503,10 +504,12 @@ static void unpack_all(void)
 	if (!quiet)
 		progress = start_progress(_("Unpacking objects"), nr_objects);
 	CALLOC_ARRAY(obj_list, nr_objects);
+	begin_odb_transaction();
 	for (i = 0; i < nr_objects; i++) {
 		unpack_one(i);
 		display_progress(progress, i + 1);
 	}
+	end_odb_transaction();
 	stop_progress(&progress);
 
 	if (delta_list)

From 5e4a6d3ae726f253802f3ea5b69d0f0da341a04e Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Thu, 24 Feb 2022 16:26:44 -0500
Subject: [PATCH 122/265] fsmonitor: never set CE_FSMONITOR_VALID on submodules

Never set CE_FSMONITOR_VALID on the cache-entry of submodule
directories.

During a client command like 'git status', we may need to recurse
into each submodule to compute a status summary for the submodule.
Since the purpose of the ce_flag is to let Git avoid scanning a
cache-entry, setting the flag causes the recursive call to be
avoided and we report incorrect (no status) for the submodule.

We created an OS watch on the root directory of our working
directory and we receive events for everything in the cone
under it.  When submodules are present inside our working
directory, we receive events for both our repo (the super) and
any subs within it.  Since our index doesn't have any information
for items within the submodules, we can't use those events.

We could try to truncate the paths of those events back to the
submodule boundary and mark the GITLINK as dirty, but that
feels expensive since we would have to prefix compare every FS
event that we receive against a list of submodule roots.  And
it still wouldn't be sufficient to correctly report status on
the submodule, since we don't have any space in the cache-entry
to cache the submodule's status (the 'SCMU' bits in porcelain
V2 speak).  That is, the CE_FSMONITOR_VALID bit just says that
we don't need to scan/inspect it because we already know the
answer -- it doesn't say that the item is clean -- and we
don't have space in the cache-entry to store those answers.
So we should always do the recursive scan.

Therefore, we should never set the flag on GITLINK cache-entries.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 fsmonitor.c                  |  2 +
 fsmonitor.h                  | 11 +++++
 t/t7527-builtin-fsmonitor.sh | 93 ++++++++++++++++++++++++++++++++++++
 3 files changed, 106 insertions(+)

diff --git a/fsmonitor.c b/fsmonitor.c
index e1229c289cfecc..57d6a483beede8 100644
--- a/fsmonitor.c
+++ b/fsmonitor.c
@@ -580,6 +580,8 @@ void tweak_fsmonitor(struct index_state *istate)
 		if (fsmonitor_enabled) {
 			/* Mark all entries valid */
 			for (i = 0; i < istate->cache_nr; i++) {
+				if (S_ISGITLINK(istate->cache[i]->ce_mode))
+					continue;
 				istate->cache[i]->ce_flags |= CE_FSMONITOR_VALID;
 			}
 
diff --git a/fsmonitor.h b/fsmonitor.h
index 3f41f653691abd..edf7ce5203b1a8 100644
--- a/fsmonitor.h
+++ b/fsmonitor.h
@@ -68,6 +68,15 @@ static inline int is_fsmonitor_refreshed(const struct index_state *istate)
  * Set the given cache entries CE_FSMONITOR_VALID bit. This should be
  * called any time the cache entry has been updated to reflect the
  * current state of the file on disk.
+ *
+ * However, never mark submodules as valid.  When commands like "git
+ * status" run they might need to recurse into the submodule (using a
+ * child process) to get a summary of the submodule state.  We don't
+ * have (and don't want to create) the facility to translate every
+ * FS event that we receive and that happens to be deep inside of a
+ * submodule back to the submodule root, so we cannot correctly keep
+ * track of this bit on the gitlink directory.  Therefore, we never
+ * set it on submodules.
  */
 static inline void mark_fsmonitor_valid(struct index_state *istate, struct cache_entry *ce)
 {
@@ -75,6 +84,8 @@ static inline void mark_fsmonitor_valid(struct index_state *istate, struct cache
 
 	if (fsm_mode > FSMONITOR_MODE_DISABLED &&
 	    !(ce->ce_flags & CE_FSMONITOR_VALID)) {
+		if (S_ISGITLINK(ce->ce_mode))
+			return;
 		istate->cache_changed = 1;
 		ce->ce_flags |= CE_FSMONITOR_VALID;
 		trace_printf_key(&trace_fsmonitor, "mark_fsmonitor_clean '%s'", ce->name);
diff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh
index d0e681d008f1cb..cc483dbd923cf0 100755
--- a/t/t7527-builtin-fsmonitor.sh
+++ b/t/t7527-builtin-fsmonitor.sh
@@ -721,4 +721,97 @@ do
 	'
 done
 
+# Test fsmonitor interaction with submodules.
+#
+# If we start the daemon in the super, it will see FS events for
+# everything in the working directory cone and this includes any
+# files/directories contained *within* the submodules.
+#
+# A `git status` at top level will get events for items within the
+# submodule and ignore them, since they aren't named in the index
+# of the super repo.  This makes the fsmonitor response a little
+# noisy, but it doesn't alter the correctness of the state of the
+# super-proper.
+#
+# When we have submodules, `git status` normally does a recursive
+# status on each of the submodules and adds a summary row for any
+# dirty submodules.  (See the "S..." bits in porcelain V2 output.)
+#
+# It is therefore important that the top level status not be tricked
+# by the FSMonitor response to skip those recursive calls.
+
+my_match_and_clean () {
+	git -C super --no-optional-locks status --porcelain=v2 >actual.with &&
+	git -C super --no-optional-locks -c core.fsmonitor=false \
+		status --porcelain=v2 >actual.without &&
+	test_cmp actual.with actual.without &&
+
+	git -C super/dir_1/dir_2/sub reset --hard &&
+	git -C super/dir_1/dir_2/sub clean -d -f
+}
+
+test_expect_success "Submodule" '
+	test_when_finished "git -C super fsmonitor--daemon stop" &&
+
+	git init "super" &&
+	echo x >super/file_1 &&
+	echo y >super/file_2 &&
+	echo z >super/file_3 &&
+	mkdir super/dir_1 &&
+	echo a >super/dir_1/file_11 &&
+	echo b >super/dir_1/file_12 &&
+	mkdir super/dir_1/dir_2 &&
+	echo a >super/dir_1/dir_2/file_21 &&
+	echo b >super/dir_1/dir_2/file_22 &&
+	git -C super add . &&
+	git -C super commit -m "initial super commit" &&
+
+	git init "sub" &&
+	echo x >sub/file_x &&
+	echo y >sub/file_y &&
+	echo z >sub/file_z &&
+	mkdir sub/dir_x &&
+	echo a >sub/dir_x/file_a &&
+	echo b >sub/dir_x/file_b &&
+	mkdir sub/dir_x/dir_y &&
+	echo a >sub/dir_x/dir_y/file_a &&
+	echo b >sub/dir_x/dir_y/file_b &&
+	git -C sub add . &&
+	git -C sub commit -m "initial sub commit" &&
+
+	git -C super submodule add ../sub ./dir_1/dir_2/sub &&
+	git -C super commit -m "add sub" &&
+
+	start_daemon -C super &&
+	git -C super config core.fsmonitor true &&
+	git -C super update-index --fsmonitor &&
+	git -C super status &&
+
+	# Now run pairs of commands w/ and w/o FSMonitor while we make
+	# some dirt in the submodule and confirm matching output.
+
+	# Completely clean status.
+	my_match_and_clean &&
+
+	# .M S..U
+	echo z >super/dir_1/dir_2/sub/dir_x/dir_y/foobar_u &&
+	my_match_and_clean &&
+
+	# .M S.M.
+	echo z >super/dir_1/dir_2/sub/dir_x/dir_y/foobar_m &&
+	git -C super/dir_1/dir_2/sub add . &&
+	my_match_and_clean &&
+
+	# .M S.M.
+	echo z >>super/dir_1/dir_2/sub/dir_x/dir_y/file_a &&
+	git -C super/dir_1/dir_2/sub add . &&
+	my_match_and_clean &&
+
+	# .M SC..
+	echo z >>super/dir_1/dir_2/sub/dir_x/dir_y/file_a &&
+	git -C super/dir_1/dir_2/sub add . &&
+	git -C super/dir_1/dir_2/sub commit -m "SC.." &&
+	my_match_and_clean
+'
+
 test_done

From f00ae0dc27af667a053390ff25cd20ec3697fb56 Mon Sep 17 00:00:00 2001
From: Philip Oakley <philipoakley@iee.email>
Date: Fri, 12 Nov 2021 21:14:50 +0000
Subject: [PATCH 123/265] object-file.c: use size_t for header lengths

Continue walking the code path for the >4GB `hash-object --literally`
test. The `hash_object_file_literally()` function internally uses both
`hash_object_file()` and `write_object_file_prepare()`. Both function
signatures use `unsigned long` rather than `size_t` for the mem buffer
sizes. Use `size_t` instead, for LLP64 compatibility.

While at it, convert those function's object's header buffer length to
`size_t` for consistency. The value is already upcast to `uintmax_t` for
print format compatibility.

Note: The hash-object test still does not pass. A subsequent commit
continues to walk the call tree's lower level hash functions to identify
further fixes.

Signed-off-by: Philip Oakley <philipoakley@iee.email>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 object-file.c  | 21 +++++++++++----------
 object-store.h |  4 ++--
 2 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/object-file.c b/object-file.c
index d52da4abe6792c..ade912d7114721 100644
--- a/object-file.c
+++ b/object-file.c
@@ -1787,7 +1787,7 @@ void *read_object_with_reference(struct repository *r,
 static void hash_object_body(const struct git_hash_algo *algo, git_hash_ctx *c,
 			     const void *buf, unsigned long len,
 			     struct object_id *oid,
-			     char *hdr, int *hdrlen)
+			     char *hdr, size_t *hdrlen)
 {
 	algo->init_fn(c);
 	algo->update_fn(c, hdr, *hdrlen);
@@ -1796,9 +1796,9 @@ static void hash_object_body(const struct git_hash_algo *algo, git_hash_ctx *c,
 }
 
 static void write_object_file_prepare(const struct git_hash_algo *algo,
-				      const void *buf, unsigned long len,
+				      const void *buf, size_t len,
 				      enum object_type type, struct object_id *oid,
-				      char *hdr, int *hdrlen)
+				      char *hdr, size_t *hdrlen)
 {
 	git_hash_ctx c;
 
@@ -1812,7 +1812,7 @@ static void write_object_file_prepare(const struct git_hash_algo *algo,
 static void write_object_file_prepare_literally(const struct git_hash_algo *algo,
 				      const void *buf, size_t len,
 				      const char *type, struct object_id *oid,
-				      char *hdr, int *hdrlen)
+				      char *hdr, size_t *hdrlen)
 {
 	git_hash_ctx c;
 
@@ -1871,17 +1871,17 @@ static int write_buffer(int fd, const void *buf, size_t len)
 }
 
 static void hash_object_file_literally(const struct git_hash_algo *algo,
-				       const void *buf, unsigned long len,
+				       const void *buf, size_t len,
 				       const char *type, struct object_id *oid)
 {
 	char hdr[MAX_HEADER_LEN];
-	int hdrlen = sizeof(hdr);
+	size_t hdrlen = sizeof(hdr);
 
 	write_object_file_prepare_literally(algo, buf, len, type, oid, hdr, &hdrlen);
 }
 
 void hash_object_file(const struct git_hash_algo *algo, const void *buf,
-		      unsigned long len, enum object_type type,
+		      size_t len, enum object_type type,
 		      struct object_id *oid)
 {
 	hash_object_file_literally(algo, buf, len, type_name(type), oid);
@@ -2043,12 +2043,12 @@ static int freshen_packed_object(const struct object_id *oid)
 	return 1;
 }
 
-int write_object_file_flags(const void *buf, unsigned long len,
+int write_object_file_flags(const void *buf, size_t len,
 			    enum object_type type, struct object_id *oid,
 			    unsigned flags)
 {
 	char hdr[MAX_HEADER_LEN];
-	int hdrlen = sizeof(hdr);
+	size_t hdrlen = sizeof(hdr);
 
 	/* Normally if we have it in the pack then we do not bother writing
 	 * it out into .git/objects/??/?{38} file.
@@ -2065,7 +2065,8 @@ int write_object_file_literally(const void *buf, size_t len,
 				unsigned flags)
 {
 	char *header;
-	int hdrlen, status = 0;
+	size_t hdrlen;
+	int status = 0;
 
 	/* type string, SP, %lu of the length plus NUL must fit this */
 	hdrlen = strlen(type) + MAX_HEADER_LEN;
diff --git a/object-store.h b/object-store.h
index a8fdc0cd178fbe..bb6f2eaefea914 100644
--- a/object-store.h
+++ b/object-store.h
@@ -246,10 +246,10 @@ static inline void *repo_read_object_file(struct repository *r,
 int oid_object_info(struct repository *r, const struct object_id *, unsigned long *);
 
 void hash_object_file(const struct git_hash_algo *algo, const void *buf,
-		      unsigned long len, enum object_type type,
+		      size_t len, enum object_type type,
 		      struct object_id *oid);
 
-int write_object_file_flags(const void *buf, unsigned long len,
+int write_object_file_flags(const void *buf, size_t len,
 			    enum object_type type, struct object_id *oid,
 			    unsigned flags);
 static inline int write_object_file(const void *buf, unsigned long len,

From 5814c499fed48c33442d928bcc4f0f351c49aa19 Mon Sep 17 00:00:00 2001
From: Neeraj Singh <neerajsi@microsoft.com>
Date: Wed, 30 Mar 2022 05:05:27 +0000
Subject: [PATCH 124/265] core.fsync: use batch mode and sync loose objects by
 default on Windows

Git for Windows has defaulted to core.fsyncObjectFiles=true since
September 2017. We turn on syncing of loose object files with batch mode
in upstream Git so that we can get broad coverage of the new code
upstream.

We don't actually do fsyncs in the most of the test suite, since
GIT_TEST_FSYNC is set to 0. However, we do exercise all of the
surrounding batch mode code since GIT_TEST_FSYNC merely makes the
maybe_fsync wrapper always appear to succeed.

Signed-off-by: Neeraj Singh <neerajsi@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 cache.h           | 4 ++++
 compat/mingw.h    | 3 +++
 config.c          | 2 +-
 git-compat-util.h | 2 ++
 4 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/cache.h b/cache.h
index ea1466340d750e..600375f786a25f 100644
--- a/cache.h
+++ b/cache.h
@@ -1031,6 +1031,10 @@ enum fsync_component {
 			      FSYNC_COMPONENT_INDEX | \
 			      FSYNC_COMPONENT_REFERENCE)
 
+#ifndef FSYNC_COMPONENTS_PLATFORM_DEFAULT
+#define FSYNC_COMPONENTS_PLATFORM_DEFAULT FSYNC_COMPONENTS_DEFAULT
+#endif
+
 /*
  * A bitmask indicating which components of the repo should be fsynced.
  */
diff --git a/compat/mingw.h b/compat/mingw.h
index 494cc8de92221b..a74da68f31321c 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -332,6 +332,9 @@ int mingw_getpagesize(void);
 int win32_fsync_no_flush(int fd);
 #define fsync_no_flush win32_fsync_no_flush
 
+#define FSYNC_COMPONENTS_PLATFORM_DEFAULT (FSYNC_COMPONENTS_DEFAULT | FSYNC_COMPONENT_LOOSE_OBJECT)
+#define FSYNC_METHOD_DEFAULT (FSYNC_METHOD_BATCH)
+
 struct rlimit {
 	unsigned int rlim_cur;
 };
diff --git a/config.c b/config.c
index 8ff25642906f08..027210a739cd2d 100644
--- a/config.c
+++ b/config.c
@@ -1342,7 +1342,7 @@ static const struct fsync_component_name {
 
 static enum fsync_component parse_fsync_components(const char *var, const char *string)
 {
-	enum fsync_component current = FSYNC_COMPONENTS_DEFAULT;
+	enum fsync_component current = FSYNC_COMPONENTS_PLATFORM_DEFAULT;
 	enum fsync_component positive = 0, negative = 0;
 
 	while (string) {
diff --git a/git-compat-util.h b/git-compat-util.h
index 58fd813bd012f1..c89a2c80e1c4c5 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -1273,11 +1273,13 @@ __attribute__((format (printf, 3, 4))) NORETURN
 void BUG_fl(const char *file, int line, const char *fmt, ...);
 #define BUG(...) BUG_fl(__FILE__, __LINE__, __VA_ARGS__)
 
+#ifndef FSYNC_METHOD_DEFAULT
 #ifdef __APPLE__
 #define FSYNC_METHOD_DEFAULT FSYNC_METHOD_WRITEOUT_ONLY
 #else
 #define FSYNC_METHOD_DEFAULT FSYNC_METHOD_FSYNC
 #endif
+#endif
 
 enum fsync_action {
 	FSYNC_WRITEOUT_ONLY,

From 610b8fc0ba143e84230787441be9fdd31addc27d Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Thu, 3 Mar 2022 17:26:13 -0500
Subject: [PATCH 125/265] t7527: test FSMonitor on case insensitive+preserving
 file system

Test that FS events from the OS are received using the preserved,
on-disk spelling of files/directories rather than spelling used
to make the change.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 t/t7527-builtin-fsmonitor.sh | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh
index cc483dbd923cf0..c6963d424f2454 100755
--- a/t/t7527-builtin-fsmonitor.sh
+++ b/t/t7527-builtin-fsmonitor.sh
@@ -814,4 +814,40 @@ test_expect_success "Submodule" '
 	my_match_and_clean
 '
 
+# On a case-insensitive file system, confirm that the daemon
+# notices when the .git directory is moved/renamed/deleted
+# regardless of how it is spelled in the the FS event.
+# That is, does the FS event receive the spelling of the
+# operation or does it receive the spelling preserved with
+# the file/directory.
+#
+test_expect_success CASE_INSENSITIVE_FS 'case insensitive+preserving' '
+#	test_when_finished "stop_daemon_delete_repo test_insensitive" &&
+
+	git init test_insensitive &&
+
+	start_daemon -C test_insensitive --tf "$PWD/insensitive.trace" &&
+
+	mkdir -p test_insensitive/abc/def &&
+	echo xyz >test_insensitive/ABC/DEF/xyz &&
+
+	test_path_is_dir test_insensitive/.git &&
+	test_path_is_dir test_insensitive/.GIT &&
+
+	# Rename .git using an alternate spelling to verify that that
+	# daemon detects it and automatically shuts down.
+	mv test_insensitive/.GIT test_insensitive/.FOO &&
+	sleep 1 &&
+	mv test_insensitive/.FOO test_insensitive/.git &&
+	test_must_fail git -C test_insensitive fsmonitor--daemon status &&
+
+	# Verify that events were reported using on-disk spellings of the
+	# directories and files that we touched.  We may or may not get a
+	# trailing slash on modified directories.
+	#
+	egrep "^event: abc/?$"       ./insensitive.trace &&
+	egrep "^event: abc/def/?$"   ./insensitive.trace &&
+	egrep "^event: abc/def/xyz$" ./insensitive.trace
+'
+
 test_done

From a3f3eee6f4cfccefeeaecb5feedc37297ea0b37e Mon Sep 17 00:00:00 2001
From: Philip Oakley <philipoakley@iee.email>
Date: Fri, 12 Nov 2021 21:16:51 +0000
Subject: [PATCH 126/265] hash algorithms: use size_t for section lengths

Continue walking the code path for the >4GB `hash-object --literally`
test to the hash algorithm step for LLP64 systems.

This patch lets the SHA1DC code use `size_t`, making it compatible with
LLP64 data models (as used e.g. by Windows).

The interested reader of this patch will note that we adjust the
signature of the `git_SHA1DCUpdate()` function without updating _any_
call site. This certainly puzzled at least one reviewer already, so here
is an explanation:

This function is never called directly, but always via the macro
`platform_SHA1_Update`, which is usually called via the macro
`git_SHA1_Update`. However, we never call `git_SHA1_Update()` directly
in `struct git_hash_algo`. Instead, we call `git_hash_sha1_update()`,
which is defined thusly:

    static void git_hash_sha1_update(git_hash_ctx *ctx,
                                     const void *data, size_t len)
    {
        git_SHA1_Update(&ctx->sha1, data, len);
    }

i.e. it contains an implicit downcast from `size_t` to `unsigned long`
(before this here patch). With this patch, there is no downcast anymore.

With this patch, finally, the t1007-hash-object.sh "files over 4GB hash
literally" test case is fixed.

Signed-off-by: Philip Oakley <philipoakley@iee.email>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 object-file.c          | 4 ++--
 sha1dc_git.c           | 3 +--
 sha1dc_git.h           | 2 +-
 t/t1007-hash-object.sh | 2 +-
 4 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/object-file.c b/object-file.c
index ade912d7114721..ef66366d01f3cb 100644
--- a/object-file.c
+++ b/object-file.c
@@ -1785,7 +1785,7 @@ void *read_object_with_reference(struct repository *r,
 }
 
 static void hash_object_body(const struct git_hash_algo *algo, git_hash_ctx *c,
-			     const void *buf, unsigned long len,
+			     const void *buf, size_t len,
 			     struct object_id *oid,
 			     char *hdr, size_t *hdrlen)
 {
@@ -1805,7 +1805,7 @@ static void write_object_file_prepare(const struct git_hash_algo *algo,
 	/* Generate the header */
 	*hdrlen = format_object_header(hdr, *hdrlen, type, len);
 
-	/* Sha1.. */
+	/* Hash (function pointers) computation */
 	hash_object_body(algo, &c, buf, len, oid, hdr, hdrlen);
 }
 
diff --git a/sha1dc_git.c b/sha1dc_git.c
index 5c300e812e0a11..65a6f1f2cd1bc2 100644
--- a/sha1dc_git.c
+++ b/sha1dc_git.c
@@ -25,10 +25,9 @@ void git_SHA1DCFinal(unsigned char hash[20], SHA1_CTX *ctx)
 /*
  * Same as SHA1DCUpdate, but adjust types to match git's usual interface.
  */
-void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *vdata, unsigned long len)
+void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *vdata, size_t len)
 {
 	const char *data = vdata;
-	/* We expect an unsigned long, but sha1dc only takes an int */
 	while (len > INT_MAX) {
 		SHA1DCUpdate(ctx, data, INT_MAX);
 		data += INT_MAX;
diff --git a/sha1dc_git.h b/sha1dc_git.h
index 41e1c3fd3f787e..847b38fb4b9f23 100644
--- a/sha1dc_git.h
+++ b/sha1dc_git.h
@@ -15,7 +15,7 @@ void git_SHA1DCInit(SHA1_CTX *);
 #endif
 
 void git_SHA1DCFinal(unsigned char [20], SHA1_CTX *);
-void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *data, unsigned long len);
+void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *data, size_t len);
 
 #define platform_SHA_CTX SHA1_CTX
 #define platform_SHA1_Init git_SHA1DCInit
diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh
index 428298fa2934d6..b30fb1282aa054 100755
--- a/t/t1007-hash-object.sh
+++ b/t/t1007-hash-object.sh
@@ -252,7 +252,7 @@ test_expect_success '--literally with extra-long type' '
 	echo example | git hash-object -t $t --literally --stdin
 '
 
-test_expect_failure EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \
+test_expect_success EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \
 		'files over 4GB hash literally' '
 	test-tool genzeros $((5*1024*1024*1024)) >big &&
 	test_oid large5GB >expect &&

From aaa227cb77dcf699df1b6db6bb80a15651f344bb Mon Sep 17 00:00:00 2001
From: Neeraj Singh <neerajsi@microsoft.com>
Date: Wed, 30 Mar 2022 05:05:28 +0000
Subject: [PATCH 127/265] test-lib-functions: add parsing helpers for ls-files
 and ls-tree

Several tests use awk to parse OIDs from the output of 'git ls-files
--stage' and 'git ls-tree'. Introduce helpers to centralize these uses
of awk.

Update t5317-pack-objects-filter-objects.sh to use the new ls-files
helper so that it has some usages to review. Other updates are left for
the future.

Signed-off-by: Neeraj Singh <neerajsi@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/t5317-pack-objects-filter-objects.sh | 91 +++++++++++++-------------
 t/test-lib-functions.sh                | 10 +++
 2 files changed, 54 insertions(+), 47 deletions(-)

diff --git a/t/t5317-pack-objects-filter-objects.sh b/t/t5317-pack-objects-filter-objects.sh
index 33b740ce628f83..bb633c9b099ed0 100755
--- a/t/t5317-pack-objects-filter-objects.sh
+++ b/t/t5317-pack-objects-filter-objects.sh
@@ -10,9 +10,6 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 # Test blob:none filter.
 
 test_expect_success 'setup r1' '
-	echo "{print \$1}" >print_1.awk &&
-	echo "{print \$2}" >print_2.awk &&
-
 	git init r1 &&
 	for n in 1 2 3 4 5
 	do
@@ -22,10 +19,13 @@ test_expect_success 'setup r1' '
 	done
 '
 
+parse_verify_pack_blob_oid () {
+	awk '{print $1}' -
+}
+
 test_expect_success 'verify blob count in normal packfile' '
-	git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
-		>ls_files_result &&
-	awk -f print_2.awk ls_files_result |
+	git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 |
+	test_parse_ls_files_stage_oids |
 	sort >expected &&
 
 	git -C r1 pack-objects --revs --stdout >all.pack <<-EOF &&
@@ -35,7 +35,7 @@ test_expect_success 'verify blob count in normal packfile' '
 
 	git -C r1 verify-pack -v ../all.pack >verify_result &&
 	grep blob verify_result |
-	awk -f print_1.awk |
+	parse_verify_pack_blob_oid |
 	sort >observed &&
 
 	test_cmp expected observed
@@ -54,12 +54,12 @@ test_expect_success 'verify blob:none packfile has no blobs' '
 test_expect_success 'verify normal and blob:none packfiles have same commits/trees' '
 	git -C r1 verify-pack -v ../all.pack >verify_result &&
 	grep -E "commit|tree" verify_result |
-	awk -f print_1.awk |
+	parse_verify_pack_blob_oid |
 	sort >expected &&
 
 	git -C r1 verify-pack -v ../filter.pack >verify_result &&
 	grep -E "commit|tree" verify_result |
-	awk -f print_1.awk |
+	parse_verify_pack_blob_oid |
 	sort >observed &&
 
 	test_cmp expected observed
@@ -123,8 +123,8 @@ test_expect_success 'setup r2' '
 '
 
 test_expect_success 'verify blob count in normal packfile' '
-	git -C r2 ls-files -s large.1000 large.10000 >ls_files_result &&
-	awk -f print_2.awk ls_files_result |
+	git -C r2 ls-files -s large.1000 large.10000 |
+	test_parse_ls_files_stage_oids |
 	sort >expected &&
 
 	git -C r2 pack-objects --revs --stdout >all.pack <<-EOF &&
@@ -134,7 +134,7 @@ test_expect_success 'verify blob count in normal packfile' '
 
 	git -C r2 verify-pack -v ../all.pack >verify_result &&
 	grep blob verify_result |
-	awk -f print_1.awk |
+	parse_verify_pack_blob_oid |
 	sort >observed &&
 
 	test_cmp expected observed
@@ -161,8 +161,8 @@ test_expect_success 'verify blob:limit=1000' '
 '
 
 test_expect_success 'verify blob:limit=1001' '
-	git -C r2 ls-files -s large.1000 >ls_files_result &&
-	awk -f print_2.awk ls_files_result |
+	git -C r2 ls-files -s large.1000 |
+	test_parse_ls_files_stage_oids |
 	sort >expected &&
 
 	git -C r2 pack-objects --revs --stdout --filter=blob:limit=1001 >filter.pack <<-EOF &&
@@ -172,15 +172,15 @@ test_expect_success 'verify blob:limit=1001' '
 
 	git -C r2 verify-pack -v ../filter.pack >verify_result &&
 	grep blob verify_result |
-	awk -f print_1.awk |
+	parse_verify_pack_blob_oid |
 	sort >observed &&
 
 	test_cmp expected observed
 '
 
 test_expect_success 'verify blob:limit=10001' '
-	git -C r2 ls-files -s large.1000 large.10000 >ls_files_result &&
-	awk -f print_2.awk ls_files_result |
+	git -C r2 ls-files -s large.1000 large.10000 |
+	test_parse_ls_files_stage_oids |
 	sort >expected &&
 
 	git -C r2 pack-objects --revs --stdout --filter=blob:limit=10001 >filter.pack <<-EOF &&
@@ -190,15 +190,15 @@ test_expect_success 'verify blob:limit=10001' '
 
 	git -C r2 verify-pack -v ../filter.pack >verify_result &&
 	grep blob verify_result |
-	awk -f print_1.awk |
+	parse_verify_pack_blob_oid |
 	sort >observed &&
 
 	test_cmp expected observed
 '
 
 test_expect_success 'verify blob:limit=1k' '
-	git -C r2 ls-files -s large.1000 >ls_files_result &&
-	awk -f print_2.awk ls_files_result |
+	git -C r2 ls-files -s large.1000 |
+	test_parse_ls_files_stage_oids |
 	sort >expected &&
 
 	git -C r2 pack-objects --revs --stdout --filter=blob:limit=1k >filter.pack <<-EOF &&
@@ -208,15 +208,15 @@ test_expect_success 'verify blob:limit=1k' '
 
 	git -C r2 verify-pack -v ../filter.pack >verify_result &&
 	grep blob verify_result |
-	awk -f print_1.awk |
+	parse_verify_pack_blob_oid |
 	sort >observed &&
 
 	test_cmp expected observed
 '
 
 test_expect_success 'verify explicitly specifying oversized blob in input' '
-	git -C r2 ls-files -s large.1000 large.10000 >ls_files_result &&
-	awk -f print_2.awk ls_files_result |
+	git -C r2 ls-files -s large.1000 large.10000 |
+	test_parse_ls_files_stage_oids |
 	sort >expected &&
 
 	echo HEAD >objects &&
@@ -226,15 +226,15 @@ test_expect_success 'verify explicitly specifying oversized blob in input' '
 
 	git -C r2 verify-pack -v ../filter.pack >verify_result &&
 	grep blob verify_result |
-	awk -f print_1.awk |
+	parse_verify_pack_blob_oid |
 	sort >observed &&
 
 	test_cmp expected observed
 '
 
 test_expect_success 'verify blob:limit=1m' '
-	git -C r2 ls-files -s large.1000 large.10000 >ls_files_result &&
-	awk -f print_2.awk ls_files_result |
+	git -C r2 ls-files -s large.1000 large.10000 |
+	test_parse_ls_files_stage_oids |
 	sort >expected &&
 
 	git -C r2 pack-objects --revs --stdout --filter=blob:limit=1m >filter.pack <<-EOF &&
@@ -244,7 +244,7 @@ test_expect_success 'verify blob:limit=1m' '
 
 	git -C r2 verify-pack -v ../filter.pack >verify_result &&
 	grep blob verify_result |
-	awk -f print_1.awk |
+	parse_verify_pack_blob_oid |
 	sort >observed &&
 
 	test_cmp expected observed
@@ -253,12 +253,12 @@ test_expect_success 'verify blob:limit=1m' '
 test_expect_success 'verify normal and blob:limit packfiles have same commits/trees' '
 	git -C r2 verify-pack -v ../all.pack >verify_result &&
 	grep -E "commit|tree" verify_result |
-	awk -f print_1.awk |
+	parse_verify_pack_blob_oid |
 	sort >expected &&
 
 	git -C r2 verify-pack -v ../filter.pack >verify_result &&
 	grep -E "commit|tree" verify_result |
-	awk -f print_1.awk |
+	parse_verify_pack_blob_oid |
 	sort >observed &&
 
 	test_cmp expected observed
@@ -289,9 +289,8 @@ test_expect_success 'setup r3' '
 '
 
 test_expect_success 'verify blob count in normal packfile' '
-	git -C r3 ls-files -s sparse1 sparse2 dir1/sparse1 dir1/sparse2 \
-		>ls_files_result &&
-	awk -f print_2.awk ls_files_result |
+	git -C r3 ls-files -s sparse1 sparse2 dir1/sparse1 dir1/sparse2 |
+	test_parse_ls_files_stage_oids |
 	sort >expected &&
 
 	git -C r3 pack-objects --revs --stdout >all.pack <<-EOF &&
@@ -301,7 +300,7 @@ test_expect_success 'verify blob count in normal packfile' '
 
 	git -C r3 verify-pack -v ../all.pack >verify_result &&
 	grep blob verify_result |
-	awk -f print_1.awk |
+	parse_verify_pack_blob_oid |
 	sort >observed &&
 
 	test_cmp expected observed
@@ -342,9 +341,8 @@ test_expect_success 'setup r4' '
 '
 
 test_expect_success 'verify blob count in normal packfile' '
-	git -C r4 ls-files -s pattern sparse1 sparse2 dir1/sparse1 dir1/sparse2 \
-		>ls_files_result &&
-	awk -f print_2.awk ls_files_result |
+	git -C r4 ls-files -s pattern sparse1 sparse2 dir1/sparse1 dir1/sparse2 |
+	test_parse_ls_files_stage_oids |
 	sort >expected &&
 
 	git -C r4 pack-objects --revs --stdout >all.pack <<-EOF &&
@@ -354,19 +352,19 @@ test_expect_success 'verify blob count in normal packfile' '
 
 	git -C r4 verify-pack -v ../all.pack >verify_result &&
 	grep blob verify_result |
-	awk -f print_1.awk |
+	parse_verify_pack_blob_oid |
 	sort >observed &&
 
 	test_cmp expected observed
 '
 
 test_expect_success 'verify sparse:oid=OID' '
-	git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 >ls_files_result &&
-	awk -f print_2.awk ls_files_result |
+	git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 |
+	test_parse_ls_files_stage_oids |
 	sort >expected &&
 
 	git -C r4 ls-files -s pattern >staged &&
-	oid=$(awk -f print_2.awk staged) &&
+	oid=$(test_parse_ls_files_stage_oids <staged) &&
 	git -C r4 pack-objects --revs --stdout --filter=sparse:oid=$oid >filter.pack <<-EOF &&
 	HEAD
 	EOF
@@ -374,15 +372,15 @@ test_expect_success 'verify sparse:oid=OID' '
 
 	git -C r4 verify-pack -v ../filter.pack >verify_result &&
 	grep blob verify_result |
-	awk -f print_1.awk |
+	parse_verify_pack_blob_oid |
 	sort >observed &&
 
 	test_cmp expected observed
 '
 
 test_expect_success 'verify sparse:oid=oid-ish' '
-	git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 >ls_files_result &&
-	awk -f print_2.awk ls_files_result |
+	git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 |
+	test_parse_ls_files_stage_oids |
 	sort >expected &&
 
 	git -C r4 pack-objects --revs --stdout --filter=sparse:oid=main:pattern >filter.pack <<-EOF &&
@@ -392,7 +390,7 @@ test_expect_success 'verify sparse:oid=oid-ish' '
 
 	git -C r4 verify-pack -v ../filter.pack >verify_result &&
 	grep blob verify_result |
-	awk -f print_1.awk |
+	parse_verify_pack_blob_oid |
 	sort >observed &&
 
 	test_cmp expected observed
@@ -402,9 +400,8 @@ test_expect_success 'verify sparse:oid=oid-ish' '
 # This models previously omitted objects that we did not receive.
 
 test_expect_success 'setup r1 - delete loose blobs' '
-	git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
-		>ls_files_result &&
-	awk -f print_2.awk ls_files_result |
+	git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 |
+	test_parse_ls_files_stage_oids |
 	sort >expected &&
 
 	for id in `cat expected | sed "s|..|&/|"`
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 93c03380d44966..50970d3e03eb8a 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -1782,6 +1782,16 @@ test_oid_to_path () {
 	echo "${1%$basename}/$basename"
 }
 
+# Parse oids from git ls-files --staged output
+test_parse_ls_files_stage_oids () {
+	awk '{print $2}' -
+}
+
+# Parse oids from git ls-tree output
+test_parse_ls_tree_oids () {
+	awk '{print $3}' -
+}
+
 # Choose a port number based on the test script's number and store it in
 # the given variable name, unless that variable already contains a number.
 test_set_port () {

From 9e3e43d5491ccdb9a06948a64653eaa4e7bea0b0 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Mon, 7 Mar 2022 12:56:29 -0500
Subject: [PATCH 128/265] fsmonitor: on macOS also emit NFC spelling for NFD
 pathname

Emit NFC or NFC and NFD spellings of pathnames on macOS.

MacOS is Unicode composition insensitive, so NFC and NFD spellings are
treated as aliases and collide.  While the spelling of pathnames in
filesystem events depends upon the underlying filesystem, such as
APFS, HFS+ or FAT32, the OS enforces such collisions regardless of
filesystem.

Teach the daemon to always report the NFC spelling and to report
the NFD spelling when stored in that format on the disk.

This is slightly more general than "core.precomposeUnicode".

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/fsmonitor/fsm-listen-darwin.c | 33 ++++++++++++++++++++++++++--
 1 file changed, 31 insertions(+), 2 deletions(-)

diff --git a/compat/fsmonitor/fsm-listen-darwin.c b/compat/fsmonitor/fsm-listen-darwin.c
index 83d38e8ac6c833..823cf63999e4cf 100644
--- a/compat/fsmonitor/fsm-listen-darwin.c
+++ b/compat/fsmonitor/fsm-listen-darwin.c
@@ -155,6 +155,35 @@ static int ef_ignore_xattr(const FSEventStreamEventFlags ef)
 	return ((ef & mask) == kFSEventStreamEventFlagItemXattrMod);
 }
 
+/*
+ * On MacOS we have to adjust for Unicode composition insensitivity
+ * (where NFC and NFD spellings are not respected).  The different
+ * spellings are essentially aliases regardless of how the path is
+ * actually stored on the disk.
+ *
+ * This is related to "core.precomposeUnicode" (which wants to try
+ * to hide NFD completely and treat everything as NFC).  Here, we
+ * don't know what the value the client has (or will have) for this
+ * config setting when they make a query, so assume the worst and
+ * emit both when the OS gives us an NFD path.
+ */
+static void my_add_path(struct fsmonitor_batch *batch, const char *path)
+{
+	char *composed;
+
+	/* add the NFC or NFD path as received from the OS */
+	fsmonitor_batch__add_path(batch, path);
+
+	/* if NFD, also add the corresponding NFC spelling */
+	composed = (char *)precompose_string_if_needed(path);
+	if (!composed || composed == path)
+		return;
+
+	fsmonitor_batch__add_path(batch, composed);
+	free(composed);
+}
+
+
 static void fsevent_callback(ConstFSEventStreamRef streamRef,
 			     void *ctx,
 			     size_t num_of_events,
@@ -305,7 +334,7 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,
 
 				if (!batch)
 					batch = fsmonitor_batch__new();
-				fsmonitor_batch__add_path(batch, rel);
+				my_add_path(batch, rel);
 			}
 
 			if (event_flags[k] & kFSEventStreamEventFlagItemIsDir) {
@@ -318,7 +347,7 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,
 
 				if (!batch)
 					batch = fsmonitor_batch__new();
-				fsmonitor_batch__add_path(batch, tmp.buf);
+				my_add_path(batch, tmp.buf);
 			}
 
 			break;

From 963ded6449bc43d052cb9000407fd386647e10a7 Mon Sep 17 00:00:00 2001
From: Philip Oakley <philipoakley@iee.email>
Date: Mon, 6 Dec 2021 22:26:50 +0000
Subject: [PATCH 129/265] hash-object --stdin: verify that it works with
 >4GB/LLP64

Just like the `hash-object --literally` code path, the `--stdin` code
path also needs to use `size_t` instead of `unsigned long` to represent
memory sizes, otherwise it would cause problems on platforms using the
LLP64 data model (such as Windows).

To limit the scope of the test case, the object is explicitly not
written to the object store, nor are any filters applied.

The `big` file from the previous test case is reused to save setup time;
To avoid relying on that side effect, it is generated if it does not
exist (e.g. when running via `sh t1007-*.sh --long --run=1,41`).

Signed-off-by: Philip Oakley <philipoakley@iee.email>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t1007-hash-object.sh | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh
index b30fb1282aa054..b6dbee4bb96064 100755
--- a/t/t1007-hash-object.sh
+++ b/t/t1007-hash-object.sh
@@ -260,4 +260,12 @@ test_expect_success EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \
 	test_cmp expect actual
 '
 
+test_expect_success EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \
+		'files over 4GB hash correctly via --stdin' '
+	{ test -f big || test-tool genzeros $((5*1024*1024*1024)) >big; } &&
+	test_oid large5GB >expect &&
+	git hash-object --stdin <big >actual &&
+	test_cmp expect actual
+'
+
 test_done

From e13449eab1bf64f5520bd7ce136a20690b6bba36 Mon Sep 17 00:00:00 2001
From: Neeraj Singh <neerajsi@microsoft.com>
Date: Wed, 30 Mar 2022 05:05:29 +0000
Subject: [PATCH 130/265] core.fsyncmethod: tests for batch mode

Add test cases to exercise batch mode for:
 * 'git add'
 * 'git stash'
 * 'git update-index'
 * 'git unpack-objects'

These tests ensure that the added data winds up in the object database.

In this change we introduce a new test helper lib-unique-files.sh. The
goal of this library is to create a tree of files that have different
oids from any other files that may have been created in the current test
repo. This helps us avoid missing validation of an object being added
due to it already being in the repo.

Signed-off-by: Neeraj Singh <neerajsi@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/lib-unique-files.sh  | 34 ++++++++++++++++++++++++++++++++++
 t/t3700-add.sh         | 28 ++++++++++++++++++++++++++++
 t/t3903-stash.sh       | 20 ++++++++++++++++++++
 t/t5300-pack-object.sh | 41 +++++++++++++++++++++++++++--------------
 4 files changed, 109 insertions(+), 14 deletions(-)
 create mode 100644 t/lib-unique-files.sh

diff --git a/t/lib-unique-files.sh b/t/lib-unique-files.sh
new file mode 100644
index 00000000000000..34c01a65256d0a
--- /dev/null
+++ b/t/lib-unique-files.sh
@@ -0,0 +1,34 @@
+# Helper to create files with unique contents
+
+# Create multiple files with unique contents within this test run. Takes the
+# number of directories, the number of files in each directory, and the base
+# directory.
+#
+# test_create_unique_files 2 3 my_dir -- Creates 2 directories with 3 files
+#					 each in my_dir, all with contents
+#					 different from previous invocations
+#					 of this command in this run.
+
+test_create_unique_files () {
+	test "$#" -ne 3 && BUG "3 param"
+
+	local dirs="$1" &&
+	local files="$2" &&
+	local basedir="$3" &&
+	local counter=0 &&
+	local i &&
+	local j &&
+	test_tick &&
+	local basedata=$basedir$test_tick &&
+	rm -rf "$basedir" &&
+	for i in $(test_seq $dirs)
+	do
+		local dir=$basedir/dir$i &&
+		mkdir -p "$dir" &&
+		for j in $(test_seq $files)
+		do
+			counter=$((counter + 1)) &&
+			echo "$basedata.$counter">"$dir/file$j.txt"
+		done
+	done
+}
diff --git a/t/t3700-add.sh b/t/t3700-add.sh
index b1f90ba3250fa3..8979c8a5f03e4c 100755
--- a/t/t3700-add.sh
+++ b/t/t3700-add.sh
@@ -8,6 +8,8 @@ test_description='Test of git add, including the -- option.'
 TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
+. $TEST_DIRECTORY/lib-unique-files.sh
+
 # Test the file mode "$1" of the file "$2" in the index.
 test_mode_in_index () {
 	case "$(git ls-files -s "$2")" in
@@ -34,6 +36,32 @@ test_expect_success \
     'Test that "git add -- -q" works' \
     'touch -- -q && git add -- -q'
 
+BATCH_CONFIGURATION='-c core.fsync=loose-object -c core.fsyncmethod=batch'
+
+test_expect_success 'git add: core.fsyncmethod=batch' "
+	test_create_unique_files 2 4 files_base_dir1 &&
+	GIT_TEST_FSYNC=1 git $BATCH_CONFIGURATION add -- ./files_base_dir1/ &&
+	git ls-files --stage files_base_dir1/ |
+	test_parse_ls_files_stage_oids >added_files_oids &&
+
+	# We created 2 subdirs with 4 files each (8 files total) above
+	test_line_count = 8 added_files_oids &&
+	git cat-file --batch-check='%(objectname)' <added_files_oids >added_files_actual &&
+	test_cmp added_files_oids added_files_actual
+"
+
+test_expect_success 'git update-index: core.fsyncmethod=batch' "
+	test_create_unique_files 2 4 files_base_dir2 &&
+	find files_base_dir2 ! -type d -print | xargs git $BATCH_CONFIGURATION update-index --add -- &&
+	git ls-files --stage files_base_dir2 |
+	test_parse_ls_files_stage_oids >added_files2_oids &&
+
+	# We created 2 subdirs with 4 files each (8 files total) above
+	test_line_count = 8 added_files2_oids &&
+	git cat-file --batch-check='%(objectname)' <added_files2_oids >added_files2_actual &&
+	test_cmp added_files2_oids added_files2_actual
+"
+
 test_expect_success \
 	'git add: Test that executable bit is not used if core.filemode=0' \
 	'git config core.filemode 0 &&
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 4abbc8fccae78c..20e94881964d55 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -9,6 +9,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
 . ./test-lib.sh
+. $TEST_DIRECTORY/lib-unique-files.sh
 
 test_expect_success 'usage on cmd and subcommand invalid option' '
 	test_expect_code 129 git stash --invalid-option 2>usage &&
@@ -1410,6 +1411,25 @@ test_expect_success 'stash handles skip-worktree entries nicely' '
 	git rev-parse --verify refs/stash:A.t
 '
 
+
+BATCH_CONFIGURATION='-c core.fsync=loose-object -c core.fsyncmethod=batch'
+
+test_expect_success 'stash with core.fsyncmethod=batch' "
+	test_create_unique_files 2 4 files_base_dir &&
+	GIT_TEST_FSYNC=1 git $BATCH_CONFIGURATION stash push -u -- ./files_base_dir/ &&
+
+	# The files were untracked, so use the third parent,
+	# which contains the untracked files
+	git ls-tree -r stash^3 -- ./files_base_dir/ |
+	test_parse_ls_tree_oids >stashed_files_oids &&
+
+	# We created 2 dirs with 4 files each (8 files total) above
+	test_line_count = 8 stashed_files_oids &&
+	git cat-file --batch-check='%(objectname)' <stashed_files_oids >stashed_files_actual &&
+	test_cmp stashed_files_oids stashed_files_actual
+"
+
+
 test_expect_success 'git stash succeeds despite directory/file change' '
 	test_create_repo directory_file_switch_v1 &&
 	(
diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh
index a11d61206addc4..f8a0f309e2dec0 100755
--- a/t/t5300-pack-object.sh
+++ b/t/t5300-pack-object.sh
@@ -161,22 +161,27 @@ test_expect_success 'pack-objects with bogus arguments' '
 '
 
 check_unpack () {
+	local packname="$1" &&
+	local object_list="$2" &&
+	local git_config="$3" &&
 	test_when_finished "rm -rf git2" &&
-	git init --bare git2 &&
-	git -C git2 unpack-objects -n <"$1".pack &&
-	git -C git2 unpack-objects <"$1".pack &&
-	(cd .git && find objects -type f -print) |
-	while read path
-	do
-		cmp git2/$path .git/$path || {
-			echo $path differs.
-			return 1
-		}
-	done
+	git $git_config init --bare git2 &&
+	(
+		git $git_config -C git2 unpack-objects -n <"$packname".pack &&
+		git $git_config -C git2 unpack-objects <"$packname".pack &&
+		git $git_config -C git2 cat-file --batch-check="%(objectname)"
+	) <"$object_list" >current &&
+	cmp "$object_list" current
 }
 
 test_expect_success 'unpack without delta' '
-	check_unpack test-1-${packname_1}
+	check_unpack test-1-${packname_1} obj-list
+'
+
+BATCH_CONFIGURATION='-c core.fsync=loose-object -c core.fsyncmethod=batch'
+
+test_expect_success 'unpack without delta (core.fsyncmethod=batch)' '
+	check_unpack test-1-${packname_1} obj-list "$BATCH_CONFIGURATION"
 '
 
 test_expect_success 'pack with REF_DELTA' '
@@ -185,7 +190,11 @@ test_expect_success 'pack with REF_DELTA' '
 '
 
 test_expect_success 'unpack with REF_DELTA' '
-	check_unpack test-2-${packname_2}
+	check_unpack test-2-${packname_2} obj-list
+'
+
+test_expect_success 'unpack with REF_DELTA (core.fsyncmethod=batch)' '
+       check_unpack test-2-${packname_2} obj-list "$BATCH_CONFIGURATION"
 '
 
 test_expect_success 'pack with OFS_DELTA' '
@@ -195,7 +204,11 @@ test_expect_success 'pack with OFS_DELTA' '
 '
 
 test_expect_success 'unpack with OFS_DELTA' '
-	check_unpack test-3-${packname_3}
+	check_unpack test-3-${packname_3} obj-list
+'
+
+test_expect_success 'unpack with OFS_DELTA (core.fsyncmethod=batch)' '
+       check_unpack test-3-${packname_3} obj-list "$BATCH_CONFIGURATION"
 '
 
 test_expect_success 'compare delta flavors' '

From d6fef66593251f30c6e26a50c4d30e2b13940c77 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Fri, 4 Mar 2022 17:34:14 -0500
Subject: [PATCH 131/265] t/lib-unicode-nfc-nfd: helper prereqs for testing
 unicode nfc/nfd

Create a set of prereqs to help understand how file names
are handled by the filesystem when they contain NFC and NFD
Unicode characters.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 t/lib-unicode-nfc-nfd.sh | 167 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 167 insertions(+)
 create mode 100755 t/lib-unicode-nfc-nfd.sh

diff --git a/t/lib-unicode-nfc-nfd.sh b/t/lib-unicode-nfc-nfd.sh
new file mode 100755
index 00000000000000..cf9c26d1e220aa
--- /dev/null
+++ b/t/lib-unicode-nfc-nfd.sh
@@ -0,0 +1,167 @@
+# Help detect how Unicode NFC and NFD are handled on the filesystem.
+
+# A simple character that has a NFD form.
+#
+# NFC:       U+00e9 LATIN SMALL LETTER E WITH ACUTE
+# UTF8(NFC): \xc3 \xa9
+#
+# NFD:       U+0065 LATIN SMALL LETTER E
+#            U+0301 COMBINING ACUTE ACCENT
+# UTF8(NFD): \x65  +  \xcc \x81
+#
+utf8_nfc=$(printf "\xc3\xa9")
+utf8_nfd=$(printf "\x65\xcc\x81")
+
+# Is the OS or the filesystem "Unicode composition sensitive"?
+#
+# That is, does the OS or the filesystem allow files to exist with
+# both the NFC and NFD spellings?  Or, does the OS/FS lie to us and
+# tell us that the NFC and NFD forms are equivalent.
+#
+# This is or may be independent of what type of filesystem we have,
+# since it might be handled by the OS at a layer above the FS.
+# Testing shows on MacOS using APFS, HFS+, and FAT32 reports a
+# collision, for example.
+#
+# This does not tell us how the Unicode pathname will be spelled
+# on disk, but rather only that the two spelling "collide".  We
+# will examine the actual on disk spelling in a later prereq.
+#
+test_lazy_prereq UNICODE_COMPOSITION_SENSITIVE '
+	mkdir trial_${utf8_nfc} &&
+	mkdir trial_${utf8_nfd}
+'
+
+# Is the spelling of an NFC pathname preserved on disk?
+#
+# On MacOS with HFS+ and FAT32, NFC paths are converted into NFD
+# and on APFS, NFC paths are preserved.  As we have established
+# above, this is independent of "composition sensitivity".
+#
+# 0000000 63 5f c3 a9
+#
+# (/usr/bin/od output contains different amount of whitespace
+# on different platforms, so we need the wildcards here.)
+#
+test_lazy_prereq UNICODE_NFC_PRESERVED '
+	mkdir c_${utf8_nfc} &&
+	ls | od -t x1 | grep "63 *5f *c3 *a9"
+'
+
+# Is the spelling of an NFD pathname preserved on disk?
+#
+# 0000000 64 5f 65 cc 81
+#
+test_lazy_prereq UNICODE_NFD_PRESERVED '
+	mkdir d_${utf8_nfd} &&
+	ls | od -t x1 | grep "64 *5f *65 *cc *81"
+'
+	mkdir c_${utf8_nfc} &&
+	mkdir d_${utf8_nfd} &&
+
+# The following _DOUBLE_ forms are more for my curiosity,
+# but there may be quirks lurking when there are multiple
+# combining characters in non-canonical order.
+
+# Unicode also allows multiple combining characters
+# that can be decomposed in pieces.
+#
+# NFC:        U+1f67 GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI
+# UTF8(NFC):  \xe1 \xbd \xa7
+#
+# NFD1:       U+1f61 GREEK SMALL LETTER OMEGA WITH DASIA
+#             U+0342 COMBINING GREEK PERISPOMENI
+# UTF8(NFD1): \xe1 \xbd \xa1  +  \xcd \x82
+#
+# But U+1f61 decomposes into
+# NFD2:       U+03c9 GREEK SMALL LETTER OMEGA
+#             U+0314 COMBINING REVERSED COMMA ABOVE
+# UTF8(NFD2): \xcf \x89  +  \xcc \x94
+#
+# Yielding:   \xcf \x89  +  \xcc \x94  +  \xcd \x82
+#
+# Note that I've used the canonical ordering of the
+# combinining characters.  It is also possible to
+# swap them.  My testing shows that that non-standard
+# ordering also causes a collision in mkdir.  However,
+# the resulting names don't draw correctly on the
+# terminal (implying that the on-disk format also has
+# them out of order).
+#
+greek_nfc=$(printf "\xe1\xbd\xa7")
+greek_nfd1=$(printf "\xe1\xbd\xa1\xcd\x82")
+greek_nfd2=$(printf "\xcf\x89\xcc\x94\xcd\x82")
+
+# See if a double decomposition also collides.
+#
+test_lazy_prereq UNICODE_DOUBLE_COMPOSITION_SENSITIVE '
+	mkdir trial_${greek_nfc} &&
+	mkdir trial_${greek_nfd2}
+'
+
+# See if the NFC spelling appears on the disk.
+#
+test_lazy_prereq UNICODE_DOUBLE_NFC_PRESERVED '
+	mkdir c_${greek_nfc} &&
+	ls | od -t x1 | grep "63 *5f *e1 *bd *a7"
+'
+
+# See if the NFD spelling appears on the disk.
+#
+test_lazy_prereq UNICODE_DOUBLE_NFD_PRESERVED '
+	mkdir d_${greek_nfd2} &&
+	ls | od -t x1 | grep "64 *5f *cf *89 *cc *94 *cd *82"
+'
+
+# The following is for debugging. I found it useful when
+# trying to understand the various (OS, FS) quirks WRT
+# Unicode and how composition/decomposition is handled.
+# For example, when trying to understand how (macOS, APFS)
+# and (macOS, HFS) and (macOS, FAT32) compare.
+#
+# It is rather noisy, so it is disabled by default.
+#
+if test "$unicode_debug" = "true"
+then
+	if test_have_prereq UNICODE_COMPOSITION_SENSITIVE
+	then
+		echo NFC and NFD are distinct on this OS/filesystem.
+	else
+		echo NFC and NFD are aliases on this OS/filesystem.
+	fi
+
+	if test_have_prereq UNICODE_NFC_PRESERVED
+	then
+		echo NFC maintains original spelling.
+	else
+		echo NFC is modified.
+	fi
+
+	if test_have_prereq UNICODE_NFD_PRESERVED
+	then
+		echo NFD maintains original spelling.
+	else
+		echo NFD is modified.
+	fi
+
+	if test_have_prereq UNICODE_DOUBLE_COMPOSITION_SENSITIVE
+	then
+		echo DOUBLE NFC and NFD are distinct on this OS/filesystem.
+	else
+		echo DOUBLE NFC and NFD are aliases on this OS/filesystem.
+	fi
+
+	if test_have_prereq UNICODE_DOUBLE_NFC_PRESERVED
+	then
+		echo Double NFC maintains original spelling.
+	else
+		echo Double NFC is modified.
+	fi
+
+	if test_have_prereq UNICODE_DOUBLE_NFD_PRESERVED
+	then
+		echo Double NFD maintains original spelling.
+	else
+		echo Double NFD is modified.
+	fi
+fi

From 371ba57bc2a0af1523387900ef3c4584814be693 Mon Sep 17 00:00:00 2001
From: Victoria Dye <vdye@github.com>
Date: Mon, 4 Apr 2022 15:38:58 -0700
Subject: [PATCH 132/265] fsmonitor: reintroduce core.useBuiltinFSMonitor

Reintroduce the 'core.useBuiltinFSMonitor' config setting (originally added
in 0a756b2a25 (fsmonitor: config settings are repository-specific,
2021-03-05)) after its removal from the upstream version of FSMonitor.

Upstream, the 'core.useBuiltinFSMonitor' setting was rendered obsolete by
"overloading" the 'core.fsmonitor' setting to take a boolean value. However,
several applications (e.g., 'scalar') utilize the original config setting,
so it should be preserved for a deprecation period before complete removal:

* if 'core.fsmonitor' is a boolean, the user is correctly using the new
  config syntax; do not use 'core.useBuiltinFSMonitor'.
* if 'core.fsmonitor' is unspecified, use 'core.useBuiltinFSMonitor'.
* if 'core.fsmonitor' is a path, override and use the builtin FSMonitor if
  'core.useBuiltinFSMonitor' is 'true'; otherwise, use the FSMonitor hook
  indicated by the path.

Additionally, for this deprecation period, advise users to switch to using
'core.fsmonitor' to specify their use of the builtin FSMonitor.

Signed-off-by: Victoria Dye <vdye@github.com>
---
 Documentation/config/advice.txt |  3 +++
 advice.c                        |  1 +
 advice.h                        |  1 +
 fsmonitor-settings.c            | 33 +++++++++++++++++++++++++++++++--
 4 files changed, 36 insertions(+), 2 deletions(-)

diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index a00d0100a82ba7..0d1e04f1ed631c 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -136,4 +136,7 @@ advice.*::
 		Advice shown when either linkgit:git-add[1] or linkgit:git-rm[1]
 		is asked to update index entries outside the current sparse
 		checkout.
+	useCoreFSMonitorConfig::
+		Advice shown if the deprecated 'core.useBuiltinFSMonitor' config
+		setting is in use.
 --
diff --git a/advice.c b/advice.c
index 6fda9edbc2474f..89cf9347ca7725 100644
--- a/advice.c
+++ b/advice.c
@@ -74,6 +74,7 @@ static struct {
 	[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie", 1 },
 	[ADVICE_SUBMODULES_NOT_UPDATED] 		= { "submodulesNotUpdated", 1 },
 	[ADVICE_UPDATE_SPARSE_PATH]			= { "updateSparsePath", 1 },
+	[ADVICE_USE_CORE_FSMONITOR_CONFIG]		= { "useCoreFSMonitorConfig", 1 },
 	[ADVICE_WAITING_FOR_EDITOR]			= { "waitingForEditor", 1 },
 };
 
diff --git a/advice.h b/advice.h
index 7ddc6cbc1ac8d7..7d14e382d789c8 100644
--- a/advice.h
+++ b/advice.h
@@ -48,6 +48,7 @@ struct string_list;
 	ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,
 	ADVICE_SUBMODULES_NOT_UPDATED,
 	ADVICE_UPDATE_SPARSE_PATH,
+	ADVICE_USE_CORE_FSMONITOR_CONFIG,
 	ADVICE_WAITING_FOR_EDITOR,
 	ADVICE_SKIPPED_CHERRY_PICKS,
 };
diff --git a/fsmonitor-settings.c b/fsmonitor-settings.c
index 60d5eaee497f3d..70d9fbc2ebd41c 100644
--- a/fsmonitor-settings.c
+++ b/fsmonitor-settings.c
@@ -48,6 +48,31 @@ static int check_for_incompatible(struct repository *r)
 	return 0;
 }
 
+static int check_deprecated_builtin_config(struct repository *r)
+{
+	int core_use_builtin_fsmonitor = 0;
+
+	/*
+	 * If 'core.useBuiltinFSMonitor' is set, print a deprecation warning
+	 * suggesting the use of 'core.fsmonitor' instead. If the config is
+	 * set to true, set the appropriate mode and return 1 indicating that
+	 * the check resulted the config being set by this (deprecated) setting.
+	 */
+	if(!repo_config_get_bool(r, "core.useBuiltinFSMonitor", &core_use_builtin_fsmonitor) &&
+	   core_use_builtin_fsmonitor) {
+		if (!git_env_bool("GIT_SUPPRESS_USEBUILTINFSMONITOR_ADVICE", 0)) {
+			advise_if_enabled(ADVICE_USE_CORE_FSMONITOR_CONFIG,
+					  _("core.useBuiltinFSMonitor will be deprecated "
+					    "soon; use core.fsmonitor instead"));
+			setenv("GIT_SUPPRESS_USEBUILTINFSMONITOR_ADVICE", "1", 1);
+		}
+		fsm_settings__set_ipc(r);
+		return 1;
+	}
+
+	return 0;
+}
+
 static void lookup_fsmonitor_settings(struct repository *r)
 {
 	struct fsmonitor_settings *s;
@@ -78,12 +103,16 @@ static void lookup_fsmonitor_settings(struct repository *r)
 		return;
 
 	case 1: /* config value was unset */
+		if (check_deprecated_builtin_config(r))
+			return;
+
 		const_str = getenv("GIT_TEST_FSMONITOR");
 		break;
 
 	case -1: /* config value set to an arbitrary string */
-		if (repo_config_get_pathname(r, "core.fsmonitor", &const_str))
-			return; /* should not happen */
+		if (check_deprecated_builtin_config(r) ||
+		    repo_config_get_pathname(r, "core.fsmonitor", &const_str))
+			return;
 		break;
 
 	default: /* should not happen */

From 042b297772703a8b10f8697888ce76bf798c5179 Mon Sep 17 00:00:00 2001
From: Philip Oakley <philipoakley@iee.email>
Date: Mon, 6 Dec 2021 22:42:46 +0000
Subject: [PATCH 133/265] hash-object: add another >4GB/LLP64 test case

To complement the `--stdin` and `--literally` test cases that verify
that we can hash files larger than 4GB on 64-bit platforms using the
LLP64 data model, here is a test case that exercises `hash-object`
_without_ any options.

Just as before, we use the `big` file from the previous test case if it
exists to save on setup time, otherwise generate it.

Signed-off-by: Philip Oakley <philipoakley@iee.email>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t1007-hash-object.sh | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh
index b6dbee4bb96064..f7062ebca18229 100755
--- a/t/t1007-hash-object.sh
+++ b/t/t1007-hash-object.sh
@@ -268,4 +268,12 @@ test_expect_success EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \
 	test_cmp expect actual
 '
 
+test_expect_success EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \
+		'files over 4GB hash correctly' '
+	{ test -f big || test-tool genzeros $((5*1024*1024*1024)) >big; } &&
+	test_oid large5GB >expect &&
+	git hash-object -- big >actual &&
+	test_cmp expect actual
+'
+
 test_done

From 19360e9eba3110185957d650d15e32edd60162d3 Mon Sep 17 00:00:00 2001
From: Neeraj Singh <neerajsi@microsoft.com>
Date: Wed, 30 Mar 2022 05:05:30 +0000
Subject: [PATCH 134/265] t/perf: add iteration setup mechanism to perf-lib

Tests that affect the repo in stateful ways are easier to write if we
can run setup steps outside of the measured portion of perf iteration.

This change adds a "--setup 'setup-script'" parameter to test_perf. To
make invocations easier to understand, I also moved the prerequisites to
a new --prereq parameter.

The setup facility will be used in the upcoming perf tests for batch
mode, but it already helps in some existing tests, like t5302 and t7820.

Signed-off-by: Neeraj Singh <neerajsi@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/perf/p4220-log-grep-engines.sh       |  3 +-
 t/perf/p4221-log-grep-engines-fixed.sh |  3 +-
 t/perf/p5302-pack-index.sh             | 15 +++----
 t/perf/p7519-fsmonitor.sh              | 20 ++-------
 t/perf/p7820-grep-engines.sh           |  6 ++-
 t/perf/perf-lib.sh                     | 62 +++++++++++++++++++++++---
 6 files changed, 75 insertions(+), 34 deletions(-)

diff --git a/t/perf/p4220-log-grep-engines.sh b/t/perf/p4220-log-grep-engines.sh
index 2bc47ded4d11f9..03fbfbb85d3414 100755
--- a/t/perf/p4220-log-grep-engines.sh
+++ b/t/perf/p4220-log-grep-engines.sh
@@ -36,7 +36,8 @@ do
 		else
 			prereq=""
 		fi
-		test_perf $prereq "$engine log$GIT_PERF_4220_LOG_OPTS --grep='$pattern'" "
+		test_perf "$engine log$GIT_PERF_4220_LOG_OPTS --grep='$pattern'" \
+			--prereq "$prereq" "
 			git -c grep.patternType=$engine log --pretty=format:%h$GIT_PERF_4220_LOG_OPTS --grep='$pattern' >'out.$engine' || :
 		"
 	done
diff --git a/t/perf/p4221-log-grep-engines-fixed.sh b/t/perf/p4221-log-grep-engines-fixed.sh
index 060971265a9c85..0a6d6dfc2198f7 100755
--- a/t/perf/p4221-log-grep-engines-fixed.sh
+++ b/t/perf/p4221-log-grep-engines-fixed.sh
@@ -26,7 +26,8 @@ do
 		else
 			prereq=""
 		fi
-		test_perf $prereq "$engine log$GIT_PERF_4221_LOG_OPTS --grep='$pattern'" "
+		test_perf "$engine log$GIT_PERF_4221_LOG_OPTS --grep='$pattern'" \
+			--prereq "$prereq" "
 			git -c grep.patternType=$engine log --pretty=format:%h$GIT_PERF_4221_LOG_OPTS --grep='$pattern' >'out.$engine' || :
 		"
 	done
diff --git a/t/perf/p5302-pack-index.sh b/t/perf/p5302-pack-index.sh
index c16f6a3ff698c0..14c601bbf860aa 100755
--- a/t/perf/p5302-pack-index.sh
+++ b/t/perf/p5302-pack-index.sh
@@ -26,9 +26,8 @@ test_expect_success 'set up thread-counting tests' '
 	done
 '
 
-test_perf PERF_EXTRA 'index-pack 0 threads' '
-	rm -rf repo.git &&
-	git init --bare repo.git &&
+test_perf 'index-pack 0 threads' --prereq PERF_EXTRA \
+	--setup 'rm -rf repo.git && git init --bare repo.git' '
 	GIT_DIR=repo.git git index-pack --threads=1 --stdin < $PACK
 '
 
@@ -36,17 +35,15 @@ for t in $threads
 do
 	THREADS=$t
 	export THREADS
-	test_perf PERF_EXTRA "index-pack $t threads" '
-		rm -rf repo.git &&
-		git init --bare repo.git &&
+	test_perf "index-pack $t threads" --prereq PERF_EXTRA \
+		--setup 'rm -rf repo.git && git init --bare repo.git' '
 		GIT_DIR=repo.git GIT_FORCE_THREADS=1 \
 		git index-pack --threads=$THREADS --stdin <$PACK
 	'
 done
 
-test_perf 'index-pack default number of threads' '
-	rm -rf repo.git &&
-	git init --bare repo.git &&
+test_perf 'index-pack default number of threads' \
+	--setup 'rm -rf repo.git && git init --bare repo.git' '
 	GIT_DIR=repo.git git index-pack --stdin < $PACK
 '
 
diff --git a/t/perf/p7519-fsmonitor.sh b/t/perf/p7519-fsmonitor.sh
index 0b9129ca7bc55a..82d3dac02038fc 100755
--- a/t/perf/p7519-fsmonitor.sh
+++ b/t/perf/p7519-fsmonitor.sh
@@ -60,19 +60,7 @@ then
 	esac
 fi
 
-if test -n "$GIT_PERF_7519_DROP_CACHE"
-then
-	# When using GIT_PERF_7519_DROP_CACHE, GIT_PERF_REPEAT_COUNT must be 1 to
-	# generate valid results. Otherwise the caching that happens for the nth
-	# run will negate the validity of the comparisons.
-	if test "$GIT_PERF_REPEAT_COUNT" -ne 1
-	then
-		echo "warning: Setting GIT_PERF_REPEAT_COUNT=1" >&2
-		GIT_PERF_REPEAT_COUNT=1
-	fi
-fi
-
-trace_start () {
+trace_start() {
 	if test -n "$GIT_PERF_7519_TRACE"
 	then
 		name="$1"
@@ -175,10 +163,10 @@ setup_for_fsmonitor_hook () {
 
 test_perf_w_drop_caches () {
 	if test -n "$GIT_PERF_7519_DROP_CACHE"; then
-		test-tool drop-caches
+		test_perf "$1" --setup "test-tool drop-caches" "$2"
+	else
+		test_perf "$@"
 	fi
-
-	test_perf "$@"
 }
 
 test_fsmonitor_suite () {
diff --git a/t/perf/p7820-grep-engines.sh b/t/perf/p7820-grep-engines.sh
index 8b09c5bf328b88..9bfb86842a90df 100755
--- a/t/perf/p7820-grep-engines.sh
+++ b/t/perf/p7820-grep-engines.sh
@@ -49,13 +49,15 @@ do
 		fi
 		if ! test_have_prereq PERF_GREP_ENGINES_THREADS
 		then
-			test_perf $prereq "$engine grep$GIT_PERF_7820_GREP_OPTS '$pattern'" "
+			test_perf "$engine grep$GIT_PERF_7820_GREP_OPTS '$pattern'" \
+				--prereq "$prereq" "
 				git -c grep.patternType=$engine grep$GIT_PERF_7820_GREP_OPTS -- '$pattern' >'out.$engine' || :
 			"
 		else
 			for threads in $GIT_PERF_GREP_THREADS
 			do
-				test_perf PTHREADS,$prereq "$engine grep$GIT_PERF_7820_GREP_OPTS '$pattern' with $threads threads" "
+				test_perf "$engine grep$GIT_PERF_7820_GREP_OPTS '$pattern' with $threads threads"
+					--prereq PTHREADS,$prereq "
 					git -c grep.patternType=$engine -c grep.threads=$threads grep$GIT_PERF_7820_GREP_OPTS -- '$pattern' >'out.$engine.$threads' || :
 				"
 			done
diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh
index 932105cd12c933..75699c50b1803e 100644
--- a/t/perf/perf-lib.sh
+++ b/t/perf/perf-lib.sh
@@ -189,19 +189,40 @@ exit $ret' >&3 2>&4
 }
 
 test_wrapper_ () {
+	local test_wrapper_func_ test_title_
 	test_wrapper_func_=$1; shift
+	test_title_=$1; shift
 	test_start_
-	test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
-	test "$#" = 2 ||
-	BUG "not 2 or 3 parameters to test-expect-success"
+	test_prereq=
+	test_perf_setup_=
+	while test $# != 0
+	do
+		case $1 in
+		--prereq)
+			test_prereq=$2
+			shift
+			;;
+		--setup)
+			test_perf_setup_=$2
+			shift
+			;;
+		*)
+			break
+			;;
+		esac
+		shift
+	done
+	test "$#" = 1 || BUG "test_wrapper_ needs 2 positional parameters"
 	export test_prereq
-	if ! test_skip "$@"
+	export test_perf_setup_
+
+	if ! test_skip "$test_title_" "$@"
 	then
 		base=$(basename "$0" .sh)
 		echo "$test_count" >>"$perf_results_dir"/$base.subtests
 		echo "$1" >"$perf_results_dir"/$base.$test_count.descr
 		base="$perf_results_dir"/"$PERF_RESULTS_PREFIX$(basename "$0" .sh)"."$test_count"
-		"$test_wrapper_func_" "$@"
+		"$test_wrapper_func_" "$test_title_" "$@"
 	fi
 
 	test_finish_
@@ -214,6 +235,16 @@ test_perf_ () {
 		echo "perf $test_count - $1:"
 	fi
 	for i in $(test_seq 1 $GIT_PERF_REPEAT_COUNT); do
+		if test -n "$test_perf_setup_"
+		then
+			say >&3 "setup: $test_perf_setup_"
+			if ! test_eval_ $test_perf_setup_
+			then
+				test_failure_ "$test_perf_setup_"
+				break
+			fi
+
+		fi
 		say >&3 "running: $2"
 		if test_run_perf_ "$2"
 		then
@@ -237,11 +268,24 @@ test_perf_ () {
 	rm test_time.*
 }
 
+# Usage: test_perf 'title' [options] 'perf-test'
+#	Run the performance test script specified in perf-test with
+#	optional prerequisite and setup steps.
+# Options:
+#	--prereq prerequisites: Skip the test if prequisites aren't met
+#	--setup "setup-steps": Run setup steps prior to each measured iteration
+#
 test_perf () {
 	test_wrapper_ test_perf_ "$@"
 }
 
 test_size_ () {
+	if test -n "$test_perf_setup_"
+	then
+		say >&3 "setup: $test_perf_setup_"
+		test_eval_ $test_perf_setup_
+	fi
+
 	say >&3 "running: $2"
 	if test_eval_ "$2" 3>"$base".result; then
 		test_ok_ "$1"
@@ -250,6 +294,14 @@ test_size_ () {
 	fi
 }
 
+# Usage: test_size 'title' [options] 'size-test'
+#	Run the size test script specified in size-test with optional
+#	prerequisites and setup steps. Returns the numeric value
+#	returned by size-test.
+# Options:
+#	--prereq prerequisites: Skip the test if prequisites aren't met
+#	--setup "setup-steps": Run setup steps prior to the size measurement
+
 test_size () {
 	test_wrapper_ test_size_ "$@"
 }

From 36579168c0c4185a507509fbf11d0dba8440bedb Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Fri, 4 Mar 2022 17:55:02 -0500
Subject: [PATCH 135/265] t7527: test Unicode NFC/NFD handling on MacOS

Confirm that the daemon reports events using the on-disk
spelling for Unicode NFC/NFD characters.  On APFS we still
have Unicode aliasing, so we cannot create two files that
only differ by NFC/NFD, but the on-disk format preserves
the spelling used to create the file.  On HFS+ we also
have aliasing, but the path is always stored on disk in
NFD.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 t/t7527-builtin-fsmonitor.sh | 55 ++++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh
index c6963d424f2454..6b8e54a5cae9df 100755
--- a/t/t7527-builtin-fsmonitor.sh
+++ b/t/t7527-builtin-fsmonitor.sh
@@ -850,4 +850,59 @@ test_expect_success CASE_INSENSITIVE_FS 'case insensitive+preserving' '
 	egrep "^event: abc/def/xyz$" ./insensitive.trace
 '
 
+# The variable "unicode_debug" is defined in the following library
+# script to dump information about how the (OS, FS) handles Unicode
+# composition.  Uncomment the following line if you want to enable it.
+#
+# unicode_debug=true
+
+. "$TEST_DIRECTORY/lib-unicode-nfc-nfd.sh"
+
+# See if the OS or filesystem does NFC/NFD aliasing/munging.
+#
+# The daemon should err on the side of caution and send BOTH the
+# NFC and NFD forms.  It does not know the original spelling of
+# the pathname (how the user thinks it should be spelled), so
+# emit both and let the client decide (when necessary).  This is
+# similar to "core.precomposeUnicode".
+#
+test_expect_success !UNICODE_COMPOSITION_SENSITIVE 'Unicode nfc/nfd' '
+	test_when_finished "stop_daemon_delete_repo test_unicode" &&
+
+	git init test_unicode &&
+
+	start_daemon -C test_unicode --tf "$PWD/unicode.trace" &&
+
+	# Create a directory using an NFC spelling.
+	#
+	mkdir test_unicode/nfc &&
+	mkdir test_unicode/nfc/c_${utf8_nfc} &&
+
+	# Create a directory using an NFD spelling.
+	#
+	mkdir test_unicode/nfd &&
+	mkdir test_unicode/nfd/d_${utf8_nfd} &&
+
+	git -C test_unicode fsmonitor--daemon stop &&
+
+	if test_have_prereq UNICODE_NFC_PRESERVED
+	then
+		# We should have seen NFC event from OS.
+		# We should not have synthesized an NFD event.
+		egrep    "^event: nfc/c_${utf8_nfc}/?$" ./unicode.trace &&
+		egrep -v "^event: nfc/c_${utf8_nfd}/?$" ./unicode.trace
+	else
+		# We should have seen NFD event from OS.
+		# We should have synthesized an NFC event.
+		egrep "^event: nfc/c_${utf8_nfd}/?$" ./unicode.trace &&
+		egrep "^event: nfc/c_${utf8_nfc}/?$" ./unicode.trace
+	fi &&
+
+	# We assume UNICODE_NFD_PRESERVED.
+	# We should have seen explicit NFD from OS.
+	# We should have synthesized an NFC event.
+	egrep "^event: nfd/d_${utf8_nfd}/?$" ./unicode.trace &&
+	egrep "^event: nfd/d_${utf8_nfc}/?$" ./unicode.trace
+'
+
 test_done

From 29928e1b1de53eb24c21fba86f440827d3065515 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 5 Mar 2021 23:12:11 +0100
Subject: [PATCH 136/265] Enable the built-in FSMonitor as an experimental
 feature

If `feature.experimental` and `feature.manyFiles` are set and the user
has not explicitly turned off the builtin FSMonitor, we now start
the built-in FSMonitor by default.

Only forcing it when UNSET matches the behavior of UPDATE_DEFAULT_BOOL()
used for other repo settings.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 repo-settings.c | 27 ++++++++++++++++++++++++++-
 1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/repo-settings.c b/repo-settings.c
index 2dfcb2b6542f2c..459041f65c4c12 100644
--- a/repo-settings.c
+++ b/repo-settings.c
@@ -2,7 +2,8 @@
 #include "config.h"
 #include "repository.h"
 #include "midx.h"
-#include "compat/fsmonitor/fsm-listen.h"
+#include "fsmonitor-ipc.h"
+#include "fsmonitor-settings.h"
 
 static void repo_cfg_bool(struct repository *r, const char *key, int *dest,
 			  int def)
@@ -36,6 +37,30 @@ void prepare_repo_settings(struct repository *r)
 	/* Defaults modified by feature.* */
 	if (experimental) {
 		r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;
+
+		/*
+		 * Force enable the builtin FSMonitor (unless the repo
+		 * is incompatible or they've already selected it or
+		 * the hook version).  But only if they haven't
+		 * explicitly turned it off -- so only if our config
+		 * value is UNSET.
+		 *
+		 * lookup_fsmonitor_settings() and check_for_ipc() do
+		 * not distinguish between explicitly set FALSE and
+		 * UNSET, so we re-test for an UNSET config key here.
+		 *
+		 * I'm not sure I want to fix fsmonitor-settings.c to
+		 * have more than one _DISABLED state since our usage
+		 * here is only to support this experimental period
+		 * (and I don't want to overload the _reason field
+		 * because it describes incompabilities).
+		 */
+		if (manyfiles &&
+		    fsmonitor_ipc__is_supported()  &&
+		    fsm_settings__get_mode(r) == FSMONITOR_MODE_DISABLED &&
+		    repo_config_get_maybe_bool(r, "core.fsmonitor", &value) > 0 &&
+		    repo_config_get_bool(r, "core.useBuiltinFSMonitor", &value))
+			fsm_settings__set_ipc(r);
 	}
 	if (manyfiles) {
 		r->settings.index_version = 4;

From 830515b2adb21da0d68ed69c17a0b19f2ca51b32 Mon Sep 17 00:00:00 2001
From: Philip Oakley <philipoakley@iee.email>
Date: Tue, 7 Dec 2021 09:53:41 +0000
Subject: [PATCH 137/265] hash-object: add a >4GB/LLP64 test case using
 filtered input

To verify that the `clean` side of the `clean`/`smudge` filter code is
correct with regards to LLP64 (read: to ensure that `size_t` is used
instead of `unsigned long`), here is a test case using a trivial filter,
specifically _not_ writing anything to the object store to limit the
scope of the test case.

As in previous commits, the `big` file from previous test cases is
reused if available, to save setup time, otherwise re-generated.

Signed-off-by: Philip Oakley <philipoakley@iee.email>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t1007-hash-object.sh | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh
index f7062ebca18229..df78646b073760 100755
--- a/t/t1007-hash-object.sh
+++ b/t/t1007-hash-object.sh
@@ -276,4 +276,16 @@ test_expect_success EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \
 	test_cmp expect actual
 '
 
+# This clean filter does nothing, other than excercising the interface.
+# We ensure that cleaning doesn't mangle large files on 64-bit Windows.
+test_expect_success EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \
+		'hash filtered files over 4GB correctly' '
+	{ test -f big || test-tool genzeros $((5*1024*1024*1024)) >big; } &&
+	test_oid large5GB >expect &&
+	test_config filter.null-filter.clean "cat" &&
+	echo "big filter=null-filter" >.gitattributes &&
+	git hash-object -- big >actual &&
+	test_cmp expect actual
+'
+
 test_done

From b1847e8dd5b64d3021806b6958eabbbf1ceb0d1d Mon Sep 17 00:00:00 2001
From: Neeraj Singh <neerajsi@microsoft.com>
Date: Wed, 30 Mar 2022 05:05:31 +0000
Subject: [PATCH 138/265] core.fsyncmethod: performance tests for batch mode

Add basic performance tests for git commands that can add data to the
object database. We cover:
* git add
* git stash
* git update-index (via git stash)
* git unpack-objects
* git commit --all

We cover all currently available fsync methods as well.

Signed-off-by: Neeraj Singh <neerajsi@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/perf/p0008-odb-fsync.sh | 81 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)
 create mode 100755 t/perf/p0008-odb-fsync.sh

diff --git a/t/perf/p0008-odb-fsync.sh b/t/perf/p0008-odb-fsync.sh
new file mode 100755
index 00000000000000..87092c2627e465
--- /dev/null
+++ b/t/perf/p0008-odb-fsync.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+#
+# This test measures the performance of adding new files to the object
+# database. The test was originally added to measure the effect of the
+# core.fsyncMethod=batch mode, which is why we are testing different values of
+# that setting explicitly and creating a lot of unique objects.
+
+test_description="Tests performance of adding things to the object database"
+
+. ./perf-lib.sh
+
+. $TEST_DIRECTORY/lib-unique-files.sh
+
+test_perf_fresh_repo
+test_checkout_worktree
+
+dir_count=10
+files_per_dir=50
+total_files=$((dir_count * files_per_dir))
+
+populate_files () {
+	test_create_unique_files $dir_count $files_per_dir files
+}
+
+setup_repo () {
+	(rm -rf .git || 1) &&
+	git init &&
+	test_commit first &&
+	populate_files
+}
+
+test_perf_fsync_cfgs () {
+	local method cfg &&
+	for method in none fsync batch writeout-only
+	do
+		case $method in
+		none)
+			cfg="-c core.fsync=none"
+			;;
+		*)
+			cfg="-c core.fsync=loose-object -c core.fsyncMethod=$method"
+		esac &&
+
+		# Set GIT_TEST_FSYNC=1 explicitly since fsync is normally
+		# disabled by t/test-lib.sh.
+		if ! test_perf "$1 (fsyncMethod=$method)" \
+						--setup "$2" \
+						"GIT_TEST_FSYNC=1 git $cfg $3"
+		then
+			break
+		fi
+	done
+}
+
+test_perf_fsync_cfgs "add $total_files files" \
+	"setup_repo" \
+	"add -- files"
+
+test_perf_fsync_cfgs "stash $total_files files" \
+	"setup_repo" \
+	"stash push -u -- files"
+
+test_perf_fsync_cfgs "unpack $total_files files" \
+	"
+	setup_repo &&
+	git -c core.fsync=none add -- files &&
+	git -c core.fsync=none commit -q -m second &&
+	echo HEAD | git pack-objects -q --stdout --revs >test_pack.pack &&
+	setup_repo
+	" \
+	"unpack-objects -q <test_pack.pack"
+
+test_perf_fsync_cfgs "commit $total_files files" \
+	"
+	setup_repo &&
+	git -c core.fsync=none add -- files &&
+	populate_files
+	" \
+	"commit -q -a -m test"
+
+test_done

From 38511ac1be70fc3ccbc9198595c565cf0eafa70a Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sat, 6 Jul 2013 02:09:35 +0200
Subject: [PATCH 139/265] Win32: make FILETIME conversion functions public

We will use them in the upcoming "FSCache" patches (to accelerate
sequential lstat() calls).

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 18 ------------------
 compat/mingw.h | 18 ++++++++++++++++++
 2 files changed, 18 insertions(+), 18 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 9fb8d3bb6504dc..8971a9b06d335a 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -733,24 +733,6 @@ int mingw_chmod(const char *filename, int mode)
 	return _wchmod(wfilename, mode);
 }
 
-/*
- * The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC.
- * Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch.
- */
-static inline long long filetime_to_hnsec(const FILETIME *ft)
-{
-	long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime;
-	/* Windows to Unix Epoch conversion */
-	return winTime - 116444736000000000LL;
-}
-
-static inline void filetime_to_timespec(const FILETIME *ft, struct timespec *ts)
-{
-	long long hnsec = filetime_to_hnsec(ft);
-	ts->tv_sec = (time_t)(hnsec / 10000000);
-	ts->tv_nsec = (hnsec % 10000000) * 100;
-}
-
 /**
  * Verifies that safe_create_leading_directories() would succeed.
  */
diff --git a/compat/mingw.h b/compat/mingw.h
index 67f41d5e69e10e..0bdb0d6b7b3467 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -351,6 +351,17 @@ static inline int getrlimit(int resource, struct rlimit *rlp)
 	return 0;
 }
 
+/*
+ * The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC.
+ * Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch.
+ */
+static inline long long filetime_to_hnsec(const FILETIME *ft)
+{
+	long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime;
+	/* Windows to Unix Epoch conversion */
+	return winTime - 116444736000000000LL;
+}
+
 /*
  * Use mingw specific stat()/lstat()/fstat() implementations on Windows,
  * including our own struct stat with 64 bit st_size and nanosecond-precision
@@ -367,6 +378,13 @@ struct timespec {
 #endif
 #endif
 
+static inline void filetime_to_timespec(const FILETIME *ft, struct timespec *ts)
+{
+	long long hnsec = filetime_to_hnsec(ft);
+	ts->tv_sec = (time_t)(hnsec / 10000000);
+	ts->tv_nsec = (hnsec % 10000000) * 100;
+}
+
 struct mingw_stat {
     _dev_t st_dev;
     _ino_t st_ino;

From 08f232cbc9e994954e58743cce32965d41defa6e Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sun, 8 Sep 2013 14:17:31 +0200
Subject: [PATCH 140/265] Win32: dirent.c: Move opendir down

Move opendir down in preparation for the next patch.

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 compat/win32/dirent.c | 68 +++++++++++++++++++++----------------------
 1 file changed, 34 insertions(+), 34 deletions(-)

diff --git a/compat/win32/dirent.c b/compat/win32/dirent.c
index 52420ec7d4dad7..2603a0fa39f45a 100644
--- a/compat/win32/dirent.c
+++ b/compat/win32/dirent.c
@@ -18,40 +18,6 @@ static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
 		ent->d_type = DT_REG;
 }
 
-DIR *opendir(const char *name)
-{
-	wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */
-	WIN32_FIND_DATAW fdata;
-	HANDLE h;
-	int len;
-	DIR *dir;
-
-	/* convert name to UTF-16 and check length < MAX_PATH */
-	if ((len = xutftowcs_path(pattern, name)) < 0)
-		return NULL;
-
-	/* append optional '/' and wildcard '*' */
-	if (len && !is_dir_sep(pattern[len - 1]))
-		pattern[len++] = '/';
-	pattern[len++] = '*';
-	pattern[len] = 0;
-
-	/* open find handle */
-	h = FindFirstFileW(pattern, &fdata);
-	if (h == INVALID_HANDLE_VALUE) {
-		DWORD err = GetLastError();
-		errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err);
-		return NULL;
-	}
-
-	/* initialize DIR structure and copy first dir entry */
-	dir = xmalloc(sizeof(DIR));
-	dir->dd_handle = h;
-	dir->dd_stat = 0;
-	finddata2dirent(&dir->dd_dir, &fdata);
-	return dir;
-}
-
 struct dirent *readdir(DIR *dir)
 {
 	if (!dir) {
@@ -90,3 +56,37 @@ int closedir(DIR *dir)
 	free(dir);
 	return 0;
 }
+
+DIR *opendir(const char *name)
+{
+	wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */
+	WIN32_FIND_DATAW fdata;
+	HANDLE h;
+	int len;
+	DIR *dir;
+
+	/* convert name to UTF-16 and check length < MAX_PATH */
+	if ((len = xutftowcs_path(pattern, name)) < 0)
+		return NULL;
+
+	/* append optional '/' and wildcard '*' */
+	if (len && !is_dir_sep(pattern[len - 1]))
+		pattern[len++] = '/';
+	pattern[len++] = '*';
+	pattern[len] = 0;
+
+	/* open find handle */
+	h = FindFirstFileW(pattern, &fdata);
+	if (h == INVALID_HANDLE_VALUE) {
+		DWORD err = GetLastError();
+		errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err);
+		return NULL;
+	}
+
+	/* initialize DIR structure and copy first dir entry */
+	dir = xmalloc(sizeof(DIR));
+	dir->dd_handle = h;
+	dir->dd_stat = 0;
+	finddata2dirent(&dir->dd_dir, &fdata);
+	return dir;
+}

From 0a75d7e1e87a8ff366fef775794f1ce89cf9d9b4 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sun, 8 Sep 2013 14:18:40 +0200
Subject: [PATCH 141/265] mingw: make the dirent implementation pluggable

Emulating the POSIX `dirent` API on Windows via
`FindFirstFile()`/`FindNextFile()` is pretty staightforward, however,
most of the information provided in the `WIN32_FIND_DATA` structure is
thrown away in the process. A more sophisticated implementation may
cache this data, e.g. for later reuse in calls to `lstat()`.

Make the `dirent` implementation pluggable so that it can be switched at
runtime, e.g. based on a config option.

Define a base DIR structure with pointers to `readdir()`/`closedir()`
that match the `opendir()` implementation (similar to vtable pointers in
Object-Oriented Programming). Define `readdir()`/`closedir()` so that
they call the function pointers in the `DIR` structure. This allows to
choose the `opendir()` implementation on a call-by-call basis.

Make the fixed-size `dirent.d_name` buffer a flex array, as `d_name` may
be implementation specific (e.g. a caching implementation may allocate a
`struct dirent` with _just_ the size needed to hold the `d_name` in
question).

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/win32/dirent.c | 30 +++++++++++++++++++-----------
 compat/win32/dirent.h | 26 +++++++++++++++++++-------
 2 files changed, 38 insertions(+), 18 deletions(-)

diff --git a/compat/win32/dirent.c b/compat/win32/dirent.c
index 2603a0fa39f45a..139d2ba3c4da34 100644
--- a/compat/win32/dirent.c
+++ b/compat/win32/dirent.c
@@ -1,15 +1,21 @@
 #include "../../git-compat-util.h"
 
-struct DIR {
-	struct dirent dd_dir; /* includes d_type */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpedantic"
+typedef struct dirent_DIR {
+	struct DIR base_dir;  /* extend base struct DIR */
 	HANDLE dd_handle;     /* FindFirstFile handle */
 	int dd_stat;          /* 0-based index */
-};
+	struct dirent dd_dir; /* includes d_type */
+} dirent_DIR;
+#pragma GCC diagnostic pop
+
+DIR *(*opendir)(const char *dirname) = dirent_opendir;
 
 static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
 {
-	/* convert UTF-16 name to UTF-8 */
-	xwcstoutf(ent->d_name, fdata->cFileName, sizeof(ent->d_name));
+	/* convert UTF-16 name to UTF-8 (d_name points to dirent_DIR.dd_name) */
+	xwcstoutf(ent->d_name, fdata->cFileName, MAX_PATH * 3);
 
 	/* Set file type, based on WIN32_FIND_DATA */
 	if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
@@ -18,7 +24,7 @@ static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
 		ent->d_type = DT_REG;
 }
 
-struct dirent *readdir(DIR *dir)
+static struct dirent *dirent_readdir(dirent_DIR *dir)
 {
 	if (!dir) {
 		errno = EBADF; /* No set_errno for mingw */
@@ -45,7 +51,7 @@ struct dirent *readdir(DIR *dir)
 	return &dir->dd_dir;
 }
 
-int closedir(DIR *dir)
+static int dirent_closedir(dirent_DIR *dir)
 {
 	if (!dir) {
 		errno = EBADF;
@@ -57,13 +63,13 @@ int closedir(DIR *dir)
 	return 0;
 }
 
-DIR *opendir(const char *name)
+DIR *dirent_opendir(const char *name)
 {
 	wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */
 	WIN32_FIND_DATAW fdata;
 	HANDLE h;
 	int len;
-	DIR *dir;
+	dirent_DIR *dir;
 
 	/* convert name to UTF-16 and check length < MAX_PATH */
 	if ((len = xutftowcs_path(pattern, name)) < 0)
@@ -84,9 +90,11 @@ DIR *opendir(const char *name)
 	}
 
 	/* initialize DIR structure and copy first dir entry */
-	dir = xmalloc(sizeof(DIR));
+	dir = xmalloc(sizeof(dirent_DIR) + MAX_PATH);
+	dir->base_dir.preaddir = (struct dirent *(*)(DIR *dir)) dirent_readdir;
+	dir->base_dir.pclosedir = (int (*)(DIR *dir)) dirent_closedir;
 	dir->dd_handle = h;
 	dir->dd_stat = 0;
 	finddata2dirent(&dir->dd_dir, &fdata);
-	return dir;
+	return (DIR*) dir;
 }
diff --git a/compat/win32/dirent.h b/compat/win32/dirent.h
index 058207e4bfed62..e0e0e1700f64d1 100644
--- a/compat/win32/dirent.h
+++ b/compat/win32/dirent.h
@@ -1,20 +1,32 @@
 #ifndef DIRENT_H
 #define DIRENT_H
 
-typedef struct DIR DIR;
-
 #define DT_UNKNOWN 0
 #define DT_DIR     1
 #define DT_REG     2
 #define DT_LNK     3
 
 struct dirent {
-	unsigned char d_type;      /* file type to prevent lstat after readdir */
-	char d_name[MAX_PATH * 3]; /* file name (* 3 for UTF-8 conversion) */
+	unsigned char d_type; /* file type to prevent lstat after readdir */
+	char d_name[FLEX_ARRAY]; /* file name */
 };
 
-DIR *opendir(const char *dirname);
-struct dirent *readdir(DIR *dir);
-int closedir(DIR *dir);
+/*
+ * Base DIR structure, contains pointers to readdir/closedir implementations so
+ * that opendir may choose a concrete implementation on a call-by-call basis.
+ */
+typedef struct DIR {
+	struct dirent *(*preaddir)(struct DIR *dir);
+	int (*pclosedir)(struct DIR *dir);
+} DIR;
+
+/* default dirent implementation */
+extern DIR *dirent_opendir(const char *dirname);
+
+/* current dirent implementation */
+extern DIR *(*opendir)(const char *dirname);
+
+#define readdir(dir) (dir->preaddir(dir))
+#define closedir(dir) (dir->pclosedir(dir))
 
 #endif /* DIRENT_H */

From 906aea130d6b5bc28adebf53aeb19e16ffaa65bb Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sun, 8 Sep 2013 14:21:30 +0200
Subject: [PATCH 142/265] Win32: make the lstat implementation pluggable

Emulating the POSIX lstat API on Windows via GetFileAttributes[Ex] is quite
slow. Windows operating system APIs seem to be much better at scanning the
status of entire directories than checking single files. A caching
implementation may improve performance by bulk-reading entire directories
or reusing data obtained via opendir / readdir.

Make the lstat implementation pluggable so that it can be switched at
runtime, e.g. based on a config option.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 2 ++
 compat/mingw.h | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 8971a9b06d335a..3c213fff3960df 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -872,6 +872,8 @@ static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
 	return do_lstat(follow, alt_name, buf);
 }
 
+int (*lstat)(const char *file_name, struct stat *buf) = mingw_lstat;
+
 static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
 {
 	BY_HANDLE_FILE_INFORMATION fdata;
diff --git a/compat/mingw.h b/compat/mingw.h
index 0bdb0d6b7b3467..c45c1a70db7cc6 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -417,7 +417,7 @@ int mingw_fstat(int fd, struct stat *buf);
 #ifdef lstat
 #undef lstat
 #endif
-#define lstat mingw_lstat
+extern int (*lstat)(const char *file_name, struct stat *buf);
 
 
 int mingw_utime(const char *file_name, const struct utimbuf *times);

From 146090bba7e584b25e546aaedfc23e9d9bd06573 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sun, 8 Sep 2013 14:23:27 +0200
Subject: [PATCH 143/265] add infrastructure for read-only file system level
 caches

Add a macro to mark code sections that only read from the file system,
along with a config option and documentation.

This facilitates implementation of relatively simple file system level
caches without the need to synchronize with the file system.

Enable read-only sections for 'git status' and preload_index.

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 Documentation/config/core.txt |  6 ++++++
 builtin/commit.c              |  1 +
 compat/mingw.c                |  6 ++++++
 compat/mingw.h                |  2 ++
 git-compat-util.h             | 15 +++++++++++++++
 preload-index.c               |  3 +++
 6 files changed, 33 insertions(+)

diff --git a/Documentation/config/core.txt b/Documentation/config/core.txt
index 57388cce8e72f2..5e0b296bbafff3 100644
--- a/Documentation/config/core.txt
+++ b/Documentation/config/core.txt
@@ -655,6 +655,12 @@ relatively high IO latencies.  When enabled, Git will do the
 index comparison to the filesystem data in parallel, allowing
 overlapping IO's.  Defaults to true.
 
+core.fscache::
+	Enable additional caching of file system data for some operations.
++
+Git for Windows uses this to bulk-read and cache lstat data of entire
+directories (instead of doing lstat file by file).
+
 core.unsetenvvars::
 	Windows-only: comma-separated list of environment variables'
 	names that need to be unset before spawning any other process.
diff --git a/builtin/commit.c b/builtin/commit.c
index 009a1de0a3d3f6..8cda2fc90ffca2 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1540,6 +1540,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 		       PATHSPEC_PREFER_FULL,
 		       prefix, argv);
 
+	enable_fscache(1);
 	if (status_format != STATUS_FORMAT_PORCELAIN &&
 	    status_format != STATUS_FORMAT_PORCELAIN_V2)
 		progress_flag = REFRESH_PROGRESS;
diff --git a/compat/mingw.c b/compat/mingw.c
index 3c213fff3960df..8fd23a3686b3a1 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -232,6 +232,7 @@ enum hide_dotfiles_type {
 static int core_restrict_inherited_handles = -1;
 static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
 static char *unset_environment_variables;
+int core_fscache;
 
 int mingw_core_config(const char *var, const char *value, void *cb)
 {
@@ -243,6 +244,11 @@ int mingw_core_config(const char *var, const char *value, void *cb)
 		return 0;
 	}
 
+	if (!strcmp(var, "core.fscache")) {
+		core_fscache = git_config_bool(var, value);
+		return 0;
+	}
+
 	if (!strcmp(var, "core.unsetenvvars")) {
 		free(unset_environment_variables);
 		unset_environment_variables = xstrdup(value);
diff --git a/compat/mingw.h b/compat/mingw.h
index c45c1a70db7cc6..0dac10171c67a8 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -11,6 +11,8 @@ typedef _sigset_t sigset_t;
 #undef _POSIX_THREAD_SAFE_FUNCTIONS
 #endif
 
+extern int core_fscache;
+
 int mingw_core_config(const char *var, const char *value, void *cb);
 #define platform_core_config mingw_core_config
 
diff --git a/git-compat-util.h b/git-compat-util.h
index a07b48212d8bc2..dee7a01ac3d3b3 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -1404,6 +1404,21 @@ static inline int is_missing_file_error(int errno_)
 	return (errno_ == ENOENT || errno_ == ENOTDIR);
 }
 
+/*
+ * Enable/disable a read-only cache for file system data on platforms that
+ * support it.
+ *
+ * Implementing a live-cache is complicated and requires special platform
+ * support (inotify, ReadDirectoryChangesW...). enable_fscache shall be used
+ * to mark sections of git code that extensively read from the file system
+ * without modifying anything. Implementations can use this to cache e.g. stat
+ * data or even file content without the need to synchronize with the file
+ * system.
+ */
+#ifndef enable_fscache
+#define enable_fscache(x) /* noop */
+#endif
+
 int cmd_main(int, const char **);
 
 /*
diff --git a/preload-index.c b/preload-index.c
index e5529a586366d4..46c632e52dcd50 100644
--- a/preload-index.c
+++ b/preload-index.c
@@ -126,6 +126,7 @@ void preload_index(struct index_state *index,
 		pthread_mutex_init(&pd.mutex, NULL);
 	}
 
+	enable_fscache(1);
 	for (i = 0; i < threads; i++) {
 		struct thread_data *p = data+i;
 		int err;
@@ -155,6 +156,8 @@ void preload_index(struct index_state *index,
 
 	trace2_data_intmax("index", NULL, "preload/sum_lstat", t2_sum_lstat);
 	trace2_region_leave("index", "preload", NULL);
+
+	enable_fscache(0);
 }
 
 int repo_read_index_preload(struct repository *repo,

From 20572a137cfd66155df8b4f1b0c63f8f70c3dd80 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Tue, 24 Jan 2017 15:12:13 -0500
Subject: [PATCH 144/265] fscache: add key for GIT_TRACE_FSCACHE

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/win32/fscache.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index ff6d272285fe45..02814edb214604 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -8,6 +8,7 @@ static int initialized;
 static volatile long enabled;
 static struct hashmap map;
 static CRITICAL_SECTION mutex;
+static struct trace_key trace_fscache = TRACE_KEY_INIT(FSCACHE);
 
 /*
  * An entry in the file system cache. Used for both entire directory listings
@@ -206,6 +207,8 @@ static struct fsentry *fsentry_create_list(const struct fsentry *dir)
 	if (h == INVALID_HANDLE_VALUE) {
 		err = GetLastError();
 		errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err);
+		trace_printf_key(&trace_fscache, "fscache: error(%d) '%s'\n",
+						 errno, dir->dirent.d_name);
 		return NULL;
 	}
 
@@ -391,6 +394,7 @@ int fscache_enable(int enable)
 		fscache_clear();
 		LeaveCriticalSection(&mutex);
 	}
+	trace_printf_key(&trace_fscache, "fscache: enable(%d)\n", enable);
 	return result;
 }
 

From 42e968d2475f8fedd788206f7fe772a040796370 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Wed, 1 Nov 2017 15:05:44 -0400
Subject: [PATCH 145/265] dir.c: make add_excludes aware of fscache during
 status

Teach read_directory_recursive() and add_excludes() to
be aware of optional fscache and avoid trying to open()
and fstat() non-existant ".gitignore" files in every
directory in the worktree.

The current code in add_excludes() calls open() and then
fstat() for a ".gitignore" file in each directory present
in the worktree.  Change that when fscache is enabled to
call lstat() first and if present, call open().

This seems backwards because both lstat needs to do more
work than fstat.  But when fscache is enabled, fscache will
already know if the .gitignore file exists and can completely
avoid the IO calls.  This works because of the lstat diversion
to mingw_lstat when fscache is enabled.

This reduced status times on a 350K file enlistment of the
Windows repo on a NVMe SSD by 0.25 seconds.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/win32/fscache.c |  5 +++++
 compat/win32/fscache.h |  3 +++
 dir.c                  | 33 ++++++++++++++++++++++++---------
 git-compat-util.h      |  4 ++++
 4 files changed, 36 insertions(+), 9 deletions(-)

diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 403441f6a96a5a..330e3d9a11d750 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -10,6 +10,11 @@ static struct hashmap map;
 static CRITICAL_SECTION mutex;
 static struct trace_key trace_fscache = TRACE_KEY_INIT(FSCACHE);
 
+int fscache_is_enabled(void)
+{
+	return enabled;
+}
+
 /*
  * An entry in the file system cache. Used for both entire directory listings
  * and file entries.
diff --git a/compat/win32/fscache.h b/compat/win32/fscache.h
index ed518b422d705e..9a21fd5709c5bc 100644
--- a/compat/win32/fscache.h
+++ b/compat/win32/fscache.h
@@ -4,6 +4,9 @@
 int fscache_enable(int enable);
 #define enable_fscache(x) fscache_enable(x)
 
+int fscache_is_enabled(void);
+#define is_fscache_enabled() (fscache_is_enabled())
+
 DIR *fscache_opendir(const char *dir);
 int fscache_lstat(const char *file_name, struct stat *buf);
 
diff --git a/dir.c b/dir.c
index f2b0f242101b29..b3f7c37ab45891 100644
--- a/dir.c
+++ b/dir.c
@@ -1063,16 +1063,31 @@ static int add_patterns(const char *fname, const char *base, int baselen,
 	size_t size = 0;
 	char *buf;
 
-	if (flags & PATTERN_NOFOLLOW)
-		fd = open_nofollow(fname, O_RDONLY);
-	else
-		fd = open(fname, O_RDONLY);
-
-	if (fd < 0 || fstat(fd, &st) < 0) {
-		if (fd < 0)
-			warn_on_fopen_errors(fname);
+	if (is_fscache_enabled()) {
+		if (lstat(fname, &st) < 0) {
+			fd = -1;
+		} else {
+			fd = open(fname, O_RDONLY);
+			if (fd < 0)
+				warn_on_fopen_errors(fname);
+		}
+	} else {
+		if (flags & PATTERN_NOFOLLOW)
+			fd = open_nofollow(fname, O_RDONLY);
 		else
-			close(fd);
+			fd = open(fname, O_RDONLY);
+
+		if (fd < 0 || fstat(fd, &st) < 0) {
+			if (fd < 0)
+				warn_on_fopen_errors(fname);
+			else {
+				close(fd);
+				fd = -1;
+			}
+		}
+	}
+
+	if (fd < 0) {
 		if (!istate)
 			return -1;
 		r = read_skip_worktree_file_from_index(istate, fname,
diff --git a/git-compat-util.h b/git-compat-util.h
index f2fb9b8b169590..7975c7e22153a8 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -1421,6 +1421,10 @@ static inline int is_missing_file_error(int errno_)
 #define enable_fscache(x) /* noop */
 #endif
 
+#ifndef is_fscache_enabled
+#define is_fscache_enabled() (0)
+#endif
+
 int cmd_main(int, const char **);
 
 /*

From 114943b6c84529be6a18969c1362ff3b60985c6e Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Tue, 1 Oct 2013 12:51:54 +0200
Subject: [PATCH 146/265] mingw: add a cache below mingw's lstat and dirent
 implementations

Checking the work tree status is quite slow on Windows, due to slow
`lstat()` emulation (git calls `lstat()` once for each file in the
index). Windows operating system APIs seem to be much better at scanning
the status of entire directories than checking single files.

Add an `lstat()` implementation that uses a cache for lstat data. Cache
misses read the entire parent directory and add it to the cache.
Subsequent `lstat()` calls for the same directory are served directly
from the cache.

Also implement `opendir()`/`readdir()`/`closedir()` so that they create
and use directory listings in the cache.

The cache doesn't track file system changes and doesn't plug into any
modifying file APIs, so it has to be explicitly enabled for git functions
that don't modify the working copy.

Note: in an earlier version of this patch, the cache was always active and
tracked file system changes via ReadDirectoryChangesW. However, this was
much more complex and had negative impact on the performance of modifying
git commands such as 'git checkout'.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/win32/fscache.c              | 462 ++++++++++++++++++++++++++++
 compat/win32/fscache.h              |  10 +
 config.mak.uname                    |   4 +-
 contrib/buildsystems/CMakeLists.txt |   3 +-
 git-compat-util.h                   |   2 +
 5 files changed, 478 insertions(+), 3 deletions(-)
 create mode 100644 compat/win32/fscache.c
 create mode 100644 compat/win32/fscache.h

diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
new file mode 100644
index 00000000000000..f3bcb1881b700b
--- /dev/null
+++ b/compat/win32/fscache.c
@@ -0,0 +1,462 @@
+#include "../../cache.h"
+#include "../../hashmap.h"
+#include "../win32.h"
+#include "fscache.h"
+#include "../../dir.h"
+
+static int initialized;
+static volatile long enabled;
+static struct hashmap map;
+static CRITICAL_SECTION mutex;
+
+/*
+ * An entry in the file system cache. Used for both entire directory listings
+ * and file entries.
+ */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpedantic"
+struct fsentry {
+	struct hashmap_entry ent;
+	mode_t st_mode;
+	/* Pointer to the directory listing, or NULL for the listing itself. */
+	struct fsentry *list;
+	/* Pointer to the next file entry of the list. */
+	struct fsentry *next;
+
+	union {
+		/* Reference count of the directory listing. */
+		volatile long refcnt;
+		struct {
+			/* More stat members (only used for file entries). */
+			off64_t st_size;
+			struct timespec st_atim;
+			struct timespec st_mtim;
+			struct timespec st_ctim;
+		} s;
+	} u;
+
+	/* Length of name. */
+	unsigned short len;
+	/*
+	 * Name of the entry. For directory listings: relative path of the
+	 * directory, without trailing '/' (empty for cwd()). For file entries:
+	 * name of the file. Typically points to the end of the structure if
+	 * the fsentry is allocated on the heap (see fsentry_alloc), or to a
+	 * local variable if on the stack (see fsentry_init).
+	 */
+	struct dirent dirent;
+};
+#pragma GCC diagnostic pop
+
+struct heap_fsentry {
+	union {
+		struct fsentry ent;
+		char dummy[sizeof(struct fsentry) + MAX_PATH];
+	} u;
+};
+
+/*
+ * Compares the paths of two fsentry structures for equality.
+ */
+static int fsentry_cmp(void *unused_cmp_data,
+		       const struct fsentry *fse1, const struct fsentry *fse2,
+		       void *unused_keydata)
+{
+	int res;
+	if (fse1 == fse2)
+		return 0;
+
+	/* compare the list parts first */
+	if (fse1->list != fse2->list &&
+	    (res = fsentry_cmp(NULL, fse1->list ? fse1->list : fse1,
+			       fse2->list ? fse2->list	: fse2, NULL)))
+		return res;
+
+	/* if list parts are equal, compare len and name */
+	if (fse1->len != fse2->len)
+		return fse1->len - fse2->len;
+	return fspathncmp(fse1->dirent.d_name, fse2->dirent.d_name, fse1->len);
+}
+
+/*
+ * Calculates the hash code of an fsentry structure's path.
+ */
+static unsigned int fsentry_hash(const struct fsentry *fse)
+{
+	unsigned int hash = fse->list ? fse->list->ent.hash : 0;
+	return hash ^ memihash(fse->dirent.d_name, fse->len);
+}
+
+/*
+ * Initialize an fsentry structure for use by fsentry_hash and fsentry_cmp.
+ */
+static void fsentry_init(struct fsentry *fse, struct fsentry *list,
+			 const char *name, size_t len)
+{
+	fse->list = list;
+	if (len > MAX_PATH)
+		BUG("Trying to allocate fsentry for long path '%.*s'",
+		    (int)len, name);
+	memcpy(fse->dirent.d_name, name, len);
+	fse->dirent.d_name[len] = 0;
+	fse->len = len;
+	hashmap_entry_init(&fse->ent, fsentry_hash(fse));
+}
+
+/*
+ * Allocate an fsentry structure on the heap.
+ */
+static struct fsentry *fsentry_alloc(struct fsentry *list, const char *name,
+		size_t len)
+{
+	/* overallocate fsentry and copy the name to the end */
+	struct fsentry *fse = xmalloc(sizeof(struct fsentry) + len + 1);
+	/* init the rest of the structure */
+	fsentry_init(fse, list, name, len);
+	fse->next = NULL;
+	fse->u.refcnt = 1;
+	return fse;
+}
+
+/*
+ * Add a reference to an fsentry.
+ */
+inline static void fsentry_addref(struct fsentry *fse)
+{
+	if (fse->list)
+		fse = fse->list;
+
+	InterlockedIncrement(&(fse->u.refcnt));
+}
+
+/*
+ * Release the reference to an fsentry, frees the memory if its the last ref.
+ */
+static void fsentry_release(struct fsentry *fse)
+{
+	if (fse->list)
+		fse = fse->list;
+
+	if (InterlockedDecrement(&(fse->u.refcnt)))
+		return;
+
+	while (fse) {
+		struct fsentry *next = fse->next;
+		free(fse);
+		fse = next;
+	}
+}
+
+/*
+ * Allocate and initialize an fsentry from a WIN32_FIND_DATA structure.
+ */
+static struct fsentry *fseentry_create_entry(struct fsentry *list,
+					     const WIN32_FIND_DATAW *fdata)
+{
+	char buf[MAX_PATH * 3];
+	int len;
+	struct fsentry *fse;
+	len = xwcstoutf(buf, fdata->cFileName, ARRAY_SIZE(buf));
+
+	fse = fsentry_alloc(list, buf, len);
+
+	fse->st_mode = file_attr_to_st_mode(fdata->dwFileAttributes);
+	fse->dirent.d_type = S_ISDIR(fse->st_mode) ? DT_DIR : DT_REG;
+	fse->u.s.st_size = (((off64_t) (fdata->nFileSizeHigh)) << 32)
+			| fdata->nFileSizeLow;
+	filetime_to_timespec(&(fdata->ftLastAccessTime), &(fse->u.s.st_atim));
+	filetime_to_timespec(&(fdata->ftLastWriteTime), &(fse->u.s.st_mtim));
+	filetime_to_timespec(&(fdata->ftCreationTime), &(fse->u.s.st_ctim));
+
+	return fse;
+}
+
+/*
+ * Create an fsentry-based directory listing (similar to opendir / readdir).
+ * Dir should not contain trailing '/'. Use an empty string for the current
+ * directory (not "."!).
+ */
+static struct fsentry *fsentry_create_list(const struct fsentry *dir)
+{
+	wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */
+	WIN32_FIND_DATAW fdata;
+	HANDLE h;
+	int wlen;
+	struct fsentry *list, **phead;
+	DWORD err;
+
+	/* convert name to UTF-16 and check length < MAX_PATH */
+	if ((wlen = xutftowcsn(pattern, dir->dirent.d_name, MAX_PATH,
+			       dir->len)) < 0) {
+		if (errno == ERANGE)
+			errno = ENAMETOOLONG;
+		return NULL;
+	}
+
+	/* append optional '/' and wildcard '*' */
+	if (wlen)
+		pattern[wlen++] = '/';
+	pattern[wlen++] = '*';
+	pattern[wlen] = 0;
+
+	/* open find handle */
+	h = FindFirstFileW(pattern, &fdata);
+	if (h == INVALID_HANDLE_VALUE) {
+		err = GetLastError();
+		errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err);
+		return NULL;
+	}
+
+	/* allocate object to hold directory listing */
+	list = fsentry_alloc(NULL, dir->dirent.d_name, dir->len);
+
+	/* walk directory and build linked list of fsentry structures */
+	phead = &list->next;
+	do {
+		*phead = fseentry_create_entry(list, &fdata);
+		phead = &(*phead)->next;
+	} while (FindNextFileW(h, &fdata));
+
+	/* remember result of last FindNextFile, then close find handle */
+	err = GetLastError();
+	FindClose(h);
+
+	/* return the list if we've got all the files */
+	if (err == ERROR_NO_MORE_FILES)
+		return list;
+
+	/* otherwise free the list and return error */
+	fsentry_release(list);
+	errno = err_win_to_posix(err);
+	return NULL;
+}
+
+/*
+ * Adds a directory listing to the cache.
+ */
+static void fscache_add(struct fsentry *fse)
+{
+	if (fse->list)
+		fse = fse->list;
+
+	for (; fse; fse = fse->next)
+		hashmap_add(&map, &fse->ent);
+}
+
+/*
+ * Clears the cache.
+ */
+static void fscache_clear(void)
+{
+	hashmap_clear_and_free(&map, struct fsentry, ent);
+	hashmap_init(&map, (hashmap_cmp_fn)fsentry_cmp, NULL, 0);
+}
+
+/*
+ * Checks if the cache is enabled for the given path.
+ */
+static inline int fscache_enabled(const char *path)
+{
+	return enabled > 0 && !is_absolute_path(path);
+}
+
+/*
+ * Looks up or creates a cache entry for the specified key.
+ */
+static struct fsentry *fscache_get(struct fsentry *key)
+{
+	struct fsentry *fse;
+
+	EnterCriticalSection(&mutex);
+	/* check if entry is in cache */
+	fse = hashmap_get_entry(&map, key, ent, NULL);
+	if (fse) {
+		fsentry_addref(fse);
+		LeaveCriticalSection(&mutex);
+		return fse;
+	}
+	/* if looking for a file, check if directory listing is in cache */
+	if (!fse && key->list) {
+		fse = hashmap_get_entry(&map, key->list, ent, NULL);
+		if (fse) {
+			LeaveCriticalSection(&mutex);
+			/* dir entry without file entry -> file doesn't exist */
+			errno = ENOENT;
+			return NULL;
+		}
+	}
+
+	/* create the directory listing (outside mutex!) */
+	LeaveCriticalSection(&mutex);
+	fse = fsentry_create_list(key->list ? key->list : key);
+	if (!fse)
+		return NULL;
+
+	EnterCriticalSection(&mutex);
+	/* add directory listing if it hasn't been added by some other thread */
+	if (!hashmap_get_entry(&map, key, ent, NULL))
+		fscache_add(fse);
+
+	/* lookup file entry if requested (fse already points to directory) */
+	if (key->list)
+		fse = hashmap_get_entry(&map, key, ent, NULL);
+
+	/* return entry or ENOENT */
+	if (fse)
+		fsentry_addref(fse);
+	else
+		errno = ENOENT;
+
+	LeaveCriticalSection(&mutex);
+	return fse;
+}
+
+/*
+ * Enables or disables the cache. Note that the cache is read-only, changes to
+ * the working directory are NOT reflected in the cache while enabled.
+ */
+int fscache_enable(int enable)
+{
+	int result;
+
+	if (!initialized) {
+		/* allow the cache to be disabled entirely */
+		if (!core_fscache)
+			return 0;
+
+		InitializeCriticalSection(&mutex);
+		hashmap_init(&map, (hashmap_cmp_fn) fsentry_cmp, NULL, 0);
+		initialized = 1;
+	}
+
+	result = enable ? InterlockedIncrement(&enabled)
+			: InterlockedDecrement(&enabled);
+
+	if (enable && result == 1) {
+		/* redirect opendir and lstat to the fscache implementations */
+		opendir = fscache_opendir;
+		lstat = fscache_lstat;
+	} else if (!enable && !result) {
+		/* reset opendir and lstat to the original implementations */
+		opendir = dirent_opendir;
+		lstat = mingw_lstat;
+		EnterCriticalSection(&mutex);
+		fscache_clear();
+		LeaveCriticalSection(&mutex);
+	}
+	return result;
+}
+
+/*
+ * Lstat replacement, uses the cache if enabled, otherwise redirects to
+ * mingw_lstat.
+ */
+int fscache_lstat(const char *filename, struct stat *st)
+{
+	int dirlen, base, len;
+	struct heap_fsentry key[2];
+	struct fsentry *fse;
+
+	if (!fscache_enabled(filename))
+		return mingw_lstat(filename, st);
+
+	/* split filename into path + name */
+	len = strlen(filename);
+	if (len && is_dir_sep(filename[len - 1]))
+		len--;
+	base = len;
+	while (base && !is_dir_sep(filename[base - 1]))
+		base--;
+	dirlen = base ? base - 1 : 0;
+
+	/* lookup entry for path + name in cache */
+	fsentry_init(&key[0].u.ent, NULL, filename, dirlen);
+	fsentry_init(&key[1].u.ent, &key[0].u.ent, filename + base, len - base);
+	fse = fscache_get(&key[1].u.ent);
+	if (!fse) {
+		errno = ENOENT;
+		return -1;
+	}
+
+	/* copy stat data */
+	st->st_ino = 0;
+	st->st_gid = 0;
+	st->st_uid = 0;
+	st->st_dev = 0;
+	st->st_rdev = 0;
+	st->st_nlink = 1;
+	st->st_mode = fse->st_mode;
+	st->st_size = fse->u.s.st_size;
+	st->st_atim = fse->u.s.st_atim;
+	st->st_mtim = fse->u.s.st_mtim;
+	st->st_ctim = fse->u.s.st_ctim;
+
+	/* don't forget to release fsentry */
+	fsentry_release(fse);
+	return 0;
+}
+
+typedef struct fscache_DIR {
+	struct DIR base_dir; /* extend base struct DIR */
+	struct fsentry *pfsentry;
+	struct dirent *dirent;
+} fscache_DIR;
+
+/*
+ * Readdir replacement.
+ */
+static struct dirent *fscache_readdir(DIR *base_dir)
+{
+	fscache_DIR *dir = (fscache_DIR*) base_dir;
+	struct fsentry *next = dir->pfsentry->next;
+	if (!next)
+		return NULL;
+	dir->pfsentry = next;
+	dir->dirent = &next->dirent;
+	return dir->dirent;
+}
+
+/*
+ * Closedir replacement.
+ */
+static int fscache_closedir(DIR *base_dir)
+{
+	fscache_DIR *dir = (fscache_DIR*) base_dir;
+	fsentry_release(dir->pfsentry);
+	free(dir);
+	return 0;
+}
+
+/*
+ * Opendir replacement, uses a directory listing from the cache if enabled,
+ * otherwise calls original dirent implementation.
+ */
+DIR *fscache_opendir(const char *dirname)
+{
+	struct heap_fsentry key;
+	struct fsentry *list;
+	fscache_DIR *dir;
+	int len;
+
+	if (!fscache_enabled(dirname))
+		return dirent_opendir(dirname);
+
+	/* prepare name (strip trailing '/', replace '.') */
+	len = strlen(dirname);
+	if ((len == 1 && dirname[0] == '.') ||
+	    (len && is_dir_sep(dirname[len - 1])))
+		len--;
+
+	/* get directory listing from cache */
+	fsentry_init(&key.u.ent, NULL, dirname, len);
+	list = fscache_get(&key.u.ent);
+	if (!list)
+		return NULL;
+
+	/* alloc and return DIR structure */
+	dir = (fscache_DIR*) xmalloc(sizeof(fscache_DIR));
+	dir->base_dir.preaddir = fscache_readdir;
+	dir->base_dir.pclosedir = fscache_closedir;
+	dir->pfsentry = list;
+	return (DIR*) dir;
+}
diff --git a/compat/win32/fscache.h b/compat/win32/fscache.h
new file mode 100644
index 00000000000000..ed518b422d705e
--- /dev/null
+++ b/compat/win32/fscache.h
@@ -0,0 +1,10 @@
+#ifndef FSCACHE_H
+#define FSCACHE_H
+
+int fscache_enable(int enable);
+#define enable_fscache(x) fscache_enable(x)
+
+DIR *fscache_opendir(const char *dir);
+int fscache_lstat(const char *file_name, struct stat *buf);
+
+#endif
diff --git a/config.mak.uname b/config.mak.uname
index bf98b0ae2d25d6..39e0b8cc4b96ad 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -486,7 +486,7 @@ endif
 		compat/win32/path-utils.o \
 		compat/win32/pthread.o compat/win32/syslog.o \
 		compat/win32/trace2_win32_process_info.o \
-		compat/win32/dirent.o
+		compat/win32/dirent.o compat/win32/fscache.o
 	COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DDETECT_MSYS_TTY -DENSURE_MSYSTEM_IS_SET -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
 	BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO
 	# invalidcontinue.obj allows Git's source code to close the same file
@@ -674,7 +674,7 @@ ifeq ($(uname_S),MINGW)
 		compat/win32/flush.o \
 		compat/win32/path-utils.o \
 		compat/win32/pthread.o compat/win32/syslog.o \
-		compat/win32/dirent.o
+		compat/win32/dirent.o compat/win32/fscache.o
 	BASIC_CFLAGS += -DWIN32
 	EXTLIBS += -lws2_32
 	GITLIBS += git.res
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index f289e65c557dc8..ac20894c59d52d 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -297,7 +297,8 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
 		compat/win32/trace2_win32_process_info.c
 		compat/win32/dirent.c
 		compat/nedmalloc/nedmalloc.c
-		compat/strdup.c)
+		compat/strdup.c
+		compat/win32/fscache.c)
 	set(NO_UNIX_SOCKETS 1)
 
 elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
diff --git a/git-compat-util.h b/git-compat-util.h
index dee7a01ac3d3b3..f2fb9b8b169590 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -243,9 +243,11 @@
 /* pull in Windows compatibility stuff */
 #include "compat/win32/path-utils.h"
 #include "compat/mingw.h"
+#include "compat/win32/fscache.h"
 #elif defined(_MSC_VER)
 #include "compat/win32/path-utils.h"
 #include "compat/msvc.h"
+#include "compat/win32/fscache.h"
 #else
 #include <sys/utsname.h>
 #include <sys/wait.h>

From 0f396b534c2253eb70681300a731342a9ef7e215 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Tue, 13 Dec 2016 14:05:32 -0500
Subject: [PATCH 147/265] fscache: remember not-found directories

Teach FSCACHE to remember "not found" directories.

This is a performance optimization.

FSCACHE is a performance optimization available for Windows.  It
intercepts Posix-style lstat() calls into an in-memory directory
using FindFirst/FindNext.  It improves performance on Windows by
catching the first lstat() call in a directory, using FindFirst/
FindNext to read the list of files (and attribute data) for the
entire directory into the cache, and short-cut subsequent lstat()
calls in the same directory.  This gives a major performance
boost on Windows.

However, it does not remember "not found" directories.  When STATUS
runs and there are missing directories, the lstat() interception
fails to find the parent directory and simply return ENOENT for the
file -- it does not remember that the FindFirst on the directory
failed. Thus subsequent lstat() calls in the same directory, each
re-attempt the FindFirst.  This completely defeats any performance
gains.

This can be seen by doing a sparse-checkout on a large repo and
then doing a read-tree to reset the skip-worktree bits and then
running status.

This change reduced status times for my very large repo by 60%.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/win32/fscache.c | 36 ++++++++++++++++++++++++++++++++----
 1 file changed, 32 insertions(+), 4 deletions(-)

diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 02814edb214604..403441f6a96a5a 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -179,7 +179,8 @@ static struct fsentry *fseentry_create_entry(struct fsentry *list,
  * Dir should not contain trailing '/'. Use an empty string for the current
  * directory (not "."!).
  */
-static struct fsentry *fsentry_create_list(const struct fsentry *dir)
+static struct fsentry *fsentry_create_list(const struct fsentry *dir,
+					   int *dir_not_found)
 {
 	wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */
 	WIN32_FIND_DATAW fdata;
@@ -188,6 +189,8 @@ static struct fsentry *fsentry_create_list(const struct fsentry *dir)
 	struct fsentry *list, **phead;
 	DWORD err;
 
+	*dir_not_found = 0;
+
 	/* convert name to UTF-16 and check length < MAX_PATH */
 	if ((wlen = xutftowcsn(pattern, dir->dirent.d_name, MAX_PATH,
 			       dir->len)) < 0) {
@@ -206,6 +209,7 @@ static struct fsentry *fsentry_create_list(const struct fsentry *dir)
 	h = FindFirstFileW(pattern, &fdata);
 	if (h == INVALID_HANDLE_VALUE) {
 		err = GetLastError();
+		*dir_not_found = 1; /* or empty directory */
 		errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err);
 		trace_printf_key(&trace_fscache, "fscache: error(%d) '%s'\n",
 						 errno, dir->dirent.d_name);
@@ -214,6 +218,8 @@ static struct fsentry *fsentry_create_list(const struct fsentry *dir)
 
 	/* allocate object to hold directory listing */
 	list = fsentry_alloc(NULL, dir->dirent.d_name, dir->len);
+	list->st_mode = S_IFDIR;
+	list->dirent.d_type = DT_DIR;
 
 	/* walk directory and build linked list of fsentry structures */
 	phead = &list->next;
@@ -298,12 +304,16 @@ static struct fsentry *fscache_get_wait(struct fsentry *key)
 static struct fsentry *fscache_get(struct fsentry *key)
 {
 	struct fsentry *fse, *future, *waiter;
+	int dir_not_found;
 
 	EnterCriticalSection(&mutex);
 	/* check if entry is in cache */
 	fse = fscache_get_wait(key);
 	if (fse) {
-		fsentry_addref(fse);
+		if (fse->st_mode)
+			fsentry_addref(fse);
+		else
+			fse = NULL; /* non-existing directory */
 		LeaveCriticalSection(&mutex);
 		return fse;
 	}
@@ -312,7 +322,10 @@ static struct fsentry *fscache_get(struct fsentry *key)
 		fse = fscache_get_wait(key->list);
 		if (fse) {
 			LeaveCriticalSection(&mutex);
-			/* dir entry without file entry -> file doesn't exist */
+			/*
+			 * dir entry without file entry, or dir does not
+			 * exist -> file doesn't exist
+			 */
 			errno = ENOENT;
 			return NULL;
 		}
@@ -326,7 +339,7 @@ static struct fsentry *fscache_get(struct fsentry *key)
 
 	/* create the directory listing (outside mutex!) */
 	LeaveCriticalSection(&mutex);
-	fse = fsentry_create_list(future);
+	fse = fsentry_create_list(future, &dir_not_found);
 	EnterCriticalSection(&mutex);
 
 	/* remove future entry and signal waiting threads */
@@ -340,6 +353,18 @@ static struct fsentry *fscache_get(struct fsentry *key)
 
 	/* leave on error (errno set by fsentry_create_list) */
 	if (!fse) {
+		if (dir_not_found && key->list) {
+			/*
+			 * Record that the directory does not exist (or is
+			 * empty, which for all practical matters is the same
+			 * thing as far as fscache is concerned).
+			 */
+			fse = fsentry_alloc(key->list->list,
+					    key->list->dirent.d_name,
+					    key->list->len);
+			fse->st_mode = 0;
+			hashmap_add(&map, &fse->ent);
+		}
 		LeaveCriticalSection(&mutex);
 		return NULL;
 	}
@@ -351,6 +376,9 @@ static struct fsentry *fscache_get(struct fsentry *key)
 	if (key->list)
 		fse = hashmap_get_entry(&map, key, ent, NULL);
 
+	if (fse && !fse->st_mode)
+		fse = NULL; /* non-existing directory */
+
 	/* return entry or ENOENT */
 	if (fse)
 		fsentry_addref(fse);

From c4b410d3e382555176f1e212ccc6996647d8c8dd Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Wed, 20 Dec 2017 10:43:41 -0500
Subject: [PATCH 148/265] fscache: make fscache_enabled() public

Make fscache_enabled() function public rather than static.
Remove unneeded fscache_is_enabled() function.
Change is_fscache_enabled() macro to call fscache_enabled().

is_fscache_enabled() now takes a pathname so that the answer
is more precise and mean "is fscache enabled for this pathname",
since fscache only stores repo-relative paths and not absolute
paths, we can avoid attempting lookups for absolute paths.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/win32/fscache.c | 7 +------
 compat/win32/fscache.h | 4 ++--
 dir.c                  | 2 +-
 git-compat-util.h      | 2 +-
 4 files changed, 5 insertions(+), 10 deletions(-)

diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 330e3d9a11d750..303a5d6274a064 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -10,11 +10,6 @@ static struct hashmap map;
 static CRITICAL_SECTION mutex;
 static struct trace_key trace_fscache = TRACE_KEY_INIT(FSCACHE);
 
-int fscache_is_enabled(void)
-{
-	return enabled;
-}
-
 /*
  * An entry in the file system cache. Used for both entire directory listings
  * and file entries.
@@ -271,7 +266,7 @@ static void fscache_clear(void)
 /*
  * Checks if the cache is enabled for the given path.
  */
-static inline int fscache_enabled(const char *path)
+int fscache_enabled(const char *path)
 {
 	return enabled > 0 && !is_absolute_path(path);
 }
diff --git a/compat/win32/fscache.h b/compat/win32/fscache.h
index 9a21fd5709c5bc..660ada053b4309 100644
--- a/compat/win32/fscache.h
+++ b/compat/win32/fscache.h
@@ -4,8 +4,8 @@
 int fscache_enable(int enable);
 #define enable_fscache(x) fscache_enable(x)
 
-int fscache_is_enabled(void);
-#define is_fscache_enabled() (fscache_is_enabled())
+int fscache_enabled(const char *path);
+#define is_fscache_enabled(path) fscache_enabled(path)
 
 DIR *fscache_opendir(const char *dir);
 int fscache_lstat(const char *file_name, struct stat *buf);
diff --git a/dir.c b/dir.c
index b3f7c37ab45891..647ad9dee3b3bb 100644
--- a/dir.c
+++ b/dir.c
@@ -1063,7 +1063,7 @@ static int add_patterns(const char *fname, const char *base, int baselen,
 	size_t size = 0;
 	char *buf;
 
-	if (is_fscache_enabled()) {
+	if (is_fscache_enabled(fname)) {
 		if (lstat(fname, &st) < 0) {
 			fd = -1;
 		} else {
diff --git a/git-compat-util.h b/git-compat-util.h
index 7975c7e22153a8..871524d78bfd81 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -1422,7 +1422,7 @@ static inline int is_missing_file_error(int errno_)
 #endif
 
 #ifndef is_fscache_enabled
-#define is_fscache_enabled() (0)
+#define is_fscache_enabled(path) (0)
 #endif
 
 int cmd_main(int, const char **);

From b8e681966e75437eb12a7fa914e21c1bc06fe40a Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Tue, 24 Jun 2014 13:22:35 +0200
Subject: [PATCH 149/265] fscache: load directories only once

If multiple threads access a directory that is not yet in the cache, the
directory will be loaded by each thread. Only one of the results is added
to the cache, all others are leaked. This wastes performance and memory.

On cache miss, add a future object to the cache to indicate that the
directory is currently being loaded. Subsequent threads register themselves
with the future object and wait. When the first thread has loaded the
directory, it replaces the future object with the result and notifies
waiting threads.

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 compat/win32/fscache.c | 65 ++++++++++++++++++++++++++++++++++++------
 1 file changed, 56 insertions(+), 9 deletions(-)

diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index f3bcb1881b700b..ff6d272285fe45 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -26,6 +26,8 @@ struct fsentry {
 	union {
 		/* Reference count of the directory listing. */
 		volatile long refcnt;
+		/* Handle to wait on the loading thread. */
+		HANDLE hwait;
 		struct {
 			/* More stat members (only used for file entries). */
 			off64_t st_size;
@@ -260,16 +262,43 @@ static inline int fscache_enabled(const char *path)
 	return enabled > 0 && !is_absolute_path(path);
 }
 
+/*
+ * Looks up a cache entry, waits if its being loaded by another thread.
+ * The mutex must be owned by the calling thread.
+ */
+static struct fsentry *fscache_get_wait(struct fsentry *key)
+{
+	struct fsentry *fse = hashmap_get_entry(&map, key, ent, NULL);
+
+	/* return if its a 'real' entry (future entries have refcnt == 0) */
+	if (!fse || fse->list || fse->u.refcnt)
+		return fse;
+
+	/* create an event and link our key to the future entry */
+	key->u.hwait = CreateEvent(NULL, TRUE, FALSE, NULL);
+	key->next = fse->next;
+	fse->next = key;
+
+	/* wait for the loading thread to signal us */
+	LeaveCriticalSection(&mutex);
+	WaitForSingleObject(key->u.hwait, INFINITE);
+	CloseHandle(key->u.hwait);
+	EnterCriticalSection(&mutex);
+
+	/* repeat cache lookup */
+	return hashmap_get_entry(&map, key, ent, NULL);
+}
+
 /*
  * Looks up or creates a cache entry for the specified key.
  */
 static struct fsentry *fscache_get(struct fsentry *key)
 {
-	struct fsentry *fse;
+	struct fsentry *fse, *future, *waiter;
 
 	EnterCriticalSection(&mutex);
 	/* check if entry is in cache */
-	fse = hashmap_get_entry(&map, key, ent, NULL);
+	fse = fscache_get_wait(key);
 	if (fse) {
 		fsentry_addref(fse);
 		LeaveCriticalSection(&mutex);
@@ -277,7 +306,7 @@ static struct fsentry *fscache_get(struct fsentry *key)
 	}
 	/* if looking for a file, check if directory listing is in cache */
 	if (!fse && key->list) {
-		fse = hashmap_get_entry(&map, key->list, ent, NULL);
+		fse = fscache_get_wait(key->list);
 		if (fse) {
 			LeaveCriticalSection(&mutex);
 			/* dir entry without file entry -> file doesn't exist */
@@ -286,16 +315,34 @@ static struct fsentry *fscache_get(struct fsentry *key)
 		}
 	}
 
+	/* add future entry to indicate that we're loading it */
+	future = key->list ? key->list : key;
+	future->next = NULL;
+	future->u.refcnt = 0;
+	hashmap_add(&map, &future->ent);
+
 	/* create the directory listing (outside mutex!) */
 	LeaveCriticalSection(&mutex);
-	fse = fsentry_create_list(key->list ? key->list : key);
-	if (!fse)
+	fse = fsentry_create_list(future);
+	EnterCriticalSection(&mutex);
+
+	/* remove future entry and signal waiting threads */
+	hashmap_remove(&map, &future->ent, NULL);
+	waiter = future->next;
+	while (waiter) {
+		HANDLE h = waiter->u.hwait;
+		waiter = waiter->next;
+		SetEvent(h);
+	}
+
+	/* leave on error (errno set by fsentry_create_list) */
+	if (!fse) {
+		LeaveCriticalSection(&mutex);
 		return NULL;
+	}
 
-	EnterCriticalSection(&mutex);
-	/* add directory listing if it hasn't been added by some other thread */
-	if (!hashmap_get_entry(&map, key, ent, NULL))
-		fscache_add(fse);
+	/* add directory listing to the cache */
+	fscache_add(fse);
 
 	/* lookup file entry if requested (fse already points to directory) */
 	if (key->list)

From dac602c38af93a2fb6e7d19bd45887195e02c611 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 25 Jan 2017 18:39:16 +0100
Subject: [PATCH 150/265] fscache: add a test for the dir-not-found
 optimization

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t1090-sparse-checkout-scope.sh | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh
index d1833c0f31b467..6b52cc9086784f 100755
--- a/t/t1090-sparse-checkout-scope.sh
+++ b/t/t1090-sparse-checkout-scope.sh
@@ -103,4 +103,24 @@ test_expect_success 'in partial clone, sparse checkout only fetches needed blobs
 	test_cmp expect actual
 '
 
+test_expect_success MINGW 'no unnecessary opendir() with fscache' '
+	git clone . fscache-test &&
+	(
+		cd fscache-test &&
+		git config core.fscache 1 &&
+		echo "/excluded/*" >.git/info/sparse-checkout &&
+		for f in $(test_seq 10)
+		do
+			sha1=$(echo $f | git hash-object -w --stdin) &&
+			git update-index --add \
+				--cacheinfo 100644,$sha1,excluded/$f || break
+		done &&
+		test_tick &&
+		git commit -m excluded &&
+		GIT_TRACE_FSCACHE=1 git status >out 2>err &&
+		grep excluded err >grep.out &&
+		test_line_count = 1 grep.out
+	)
+'
+
 test_done

From a33710fc13b4287f691ec94789aae2d500e23b03 Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Tue, 22 Nov 2016 11:26:38 -0500
Subject: [PATCH 151/265] add: use preload-index and fscache for performance

Teach "add" to use preload-index and fscache features
to improve performance on very large repositories.

During an "add", a call is made to run_diff_files()
which calls check_remove() for each index-entry.  This
calls lstat().  On Windows, the fscache code intercepts
the lstat() calls and builds a private cache using the
FindFirst/FindNext routines, which are much faster.

Somewhat independent of this, is the preload-index code
which distributes some of the start-up costs across
multiple threads.

We need to keep the call to read_cache() before parsing the
pathspecs (and hence cannot use the pathspecs to limit any preload)
because parse_pathspec() is using the index to determine whether a
pathspec is, in fact, in a submodule. If we would not read the index
first, parse_pathspec() would not error out on a path that is inside
a submodule, and t7400-submodule-basic.sh would fail with

	not ok 47 - do not add files from a submodule

We still want the nice preload performance boost, though, so we simply
call read_cache_preload(&pathspecs) after parsing the pathspecs.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/add.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/builtin/add.c b/builtin/add.c
index e39770e4746fc4..051e7e68d12665 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -597,6 +597,10 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 	die_in_unpopulated_submodule(&the_index, prefix);
 	die_path_inside_submodule(&the_index, &pathspec);
 
+	enable_fscache(1);
+	/* We do not really re-read the index but update the up-to-date flags */
+	preload_index(&the_index, &pathspec, 0);
+
 	if (add_new_files) {
 		int baselen;
 
@@ -699,6 +703,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 		die(_("Unable to write new index file"));
 
 	dir_clear(&dir);
+	enable_fscache(0);
 	UNLEAK(pathspec);
 	return exit_status;
 }

From 8cc6d5553f7b331db0c693599cc3e81ff44bd1dc Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Wed, 20 Dec 2017 11:19:27 -0500
Subject: [PATCH 152/265] dir.c: regression fix for add_excludes with fscache

Fix regression described in:
https://github.com/git-for-windows/git/issues/1392

which was introduced in:
https://github.com/git-for-windows/git/commit/b2353379bba414e6c00dde913497cc9c827366f2

Problem Symptoms
================
When the user has a .gitignore file that is a symlink, the fscache
optimization introduced above caused the stat-data from the symlink,
rather that of the target file, to be returned.  Later when the ignore
file was read, the buffer length did not match the stat.st_size field
and we called die("cannot use <path> as an exclude file")

Optimization Rationale
======================
The above optimization calls lstat() before open() primarily to ask
fscache if the file exists.  It gets the current stat-data as a side
effect essentially for free (since we already have it in memory).
If the file does not exist, it does not need to call open().  And
since very few directories have .gitignore files, we can greatly
reduce time spent in the filesystem.

Discussion of Fix
=================
The above optimization calls lstat() rather than stat() because the
fscache only intercepts lstat() calls.  Calls to stat() stay directed
to the mingw_stat() completly bypassing fscache.  Furthermore, calls
to mingw_stat() always call {open, fstat, close} so that symlinks are
properly dereferenced, which adds *additional* open/close calls on top
of what the original code in dir.c is doing.

Since the problem only manifests for symlinks, we add code to overwrite
the stat-data when the path is a symlink.  This preserves the effect of
the performance gains provided by the fscache in the normal case.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 dir.c | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/dir.c b/dir.c
index 647ad9dee3b3bb..6369d3f6e59b08 100644
--- a/dir.c
+++ b/dir.c
@@ -1063,6 +1063,29 @@ static int add_patterns(const char *fname, const char *base, int baselen,
 	size_t size = 0;
 	char *buf;
 
+	/*
+	 * A performance optimization for status.
+	 *
+	 * During a status scan, git looks in each directory for a .gitignore
+	 * file before scanning the directory.  Since .gitignore files are not
+	 * that common, we can waste a lot of time looking for files that are
+	 * not there.  Fortunately, the fscache already knows if the directory
+	 * contains a .gitignore file, since it has already read the directory
+	 * and it already has the stat-data.
+	 *
+	 * If the fscache is enabled, use the fscache-lstat() interlude to see
+	 * if the file exists (in the fscache hash maps) before trying to open()
+	 * it.
+	 *
+	 * This causes problem when the .gitignore file is a symlink, because
+	 * we call lstat() rather than stat() on the symlnk and the resulting
+	 * stat-data is for the symlink itself rather than the target file.
+	 * We CANNOT use stat() here because the fscache DOES NOT install an
+	 * interlude for stat() and mingw_stat() always calls "open-fstat-close"
+	 * on the file and defeats the purpose of the optimization here.  Since
+	 * symlinks are even more rare than .gitignore files, we force a fstat()
+	 * after our open() to get stat-data for the target file.
+	 */
 	if (is_fscache_enabled(fname)) {
 		if (lstat(fname, &st) < 0) {
 			fd = -1;
@@ -1070,6 +1093,11 @@ static int add_patterns(const char *fname, const char *base, int baselen,
 			fd = open(fname, O_RDONLY);
 			if (fd < 0)
 				warn_on_fopen_errors(fname);
+			else if (S_ISLNK(st.st_mode) && fstat(fd, &st) < 0) {
+				warn_on_fopen_errors(fname);
+				close(fd);
+				fd = -1;
+			}
 		}
 	} else {
 		if (flags & PATTERN_NOFOLLOW)

From 2c66f91ec66d17084f207849e3c88ee80c34694b Mon Sep 17 00:00:00 2001
From: Takuto Ikuta <tikuta@chromium.org>
Date: Wed, 22 Nov 2017 20:39:38 +0900
Subject: [PATCH 153/265] fetch-pack.c: enable fscache for stats under
 .git/objects

When I do git fetch, git call file stats under .git/objects for each
refs. This takes time when there are many refs.

By enabling fscache, git takes file stats by directory traversing and that
improved the speed of fetch-pack for repository having large number of
refs.

In my windows workstation, this improves the time of `git fetch` for
chromium repository like below. I took stats 3 times.

* With this patch
TotalSeconds: 9.9825165
TotalSeconds: 9.1862075
TotalSeconds: 10.1956256
Avg: 9.78811653333333

* Without this patch
TotalSeconds: 15.8406702
TotalSeconds: 15.6248053
TotalSeconds: 15.2085938
Avg: 15.5580231

Signed-off-by: Takuto Ikuta <tikuta@chromium.org>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 fetch-pack.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/fetch-pack.c b/fetch-pack.c
index 4e1e88eea097dd..8b50cb09902d1d 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -700,6 +700,7 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,
 	save_commit_buffer = 0;
 
 	trace2_region_enter("fetch-pack", "parse_remote_refs_and_find_cutoff", NULL);
+	enable_fscache(1);
 	for (ref = *refs; ref; ref = ref->next) {
 		struct commit *commit;
 
@@ -726,6 +727,7 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,
 		if (!cutoff || cutoff < commit->date)
 			cutoff = commit->date;
 	}
+	enable_fscache(0);
 	trace2_region_leave("fetch-pack", "parse_remote_refs_and_find_cutoff", NULL);
 
 	/*

From 76727ea53680ab56e7e1da37517ea8a813af8f40 Mon Sep 17 00:00:00 2001
From: Takuto Ikuta <tikuta@chromium.org>
Date: Tue, 30 Jan 2018 22:42:58 +0900
Subject: [PATCH 154/265] checkout.c: enable fscache for checkout again

This is retry of #1419.

I added flush_fscache macro to flush cached stats after disk writing
with tests for regression reported in #1438 and #1442.

git checkout checks each file path in sorted order, so cache flushing does not
make performance worse unless we have large number of modified files in
a directory containing many files.

Using chromium repository, I tested `git checkout .` performance when I
delete 10 files in different directories.
With this patch:
TotalSeconds: 4.307272
TotalSeconds: 4.4863595
TotalSeconds: 4.2975562
Avg: 4.36372923333333

Without this patch:
TotalSeconds: 20.9705431
TotalSeconds: 22.4867685
TotalSeconds: 18.8968292
Avg: 20.7847136

I confirmed this patch passed all tests in t/ with core_fscache=1.

Signed-off-by: Takuto Ikuta <tikuta@chromium.org>
---
 builtin/checkout.c     |  2 ++
 compat/win32/fscache.c | 12 ++++++++++++
 compat/win32/fscache.h |  3 +++
 entry.c                |  3 +++
 git-compat-util.h      |  4 ++++
 t/t7201-co.sh          | 36 ++++++++++++++++++++++++++++++++++++
 6 files changed, 60 insertions(+)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 797681481d10b5..3ce6bb021c2a61 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -391,6 +391,7 @@ static int checkout_worktree(const struct checkout_opts *opts,
 	if (pc_workers > 1)
 		init_parallel_checkout();
 
+	enable_fscache(1);
 	for (pos = 0; pos < active_nr; pos++) {
 		struct cache_entry *ce = active_cache[pos];
 		if (ce->ce_flags & CE_MATCHED) {
@@ -415,6 +416,7 @@ static int checkout_worktree(const struct checkout_opts *opts,
 		errs |= run_parallel_checkout(&state, pc_workers, pc_threshold,
 					      NULL, NULL);
 	mem_pool_discard(&ce_mem_pool, should_validate_cache_entries());
+	enable_fscache(0);
 	remove_marked_cache_entries(&the_index, 1);
 	remove_scheduled_dirs();
 	errs |= finish_delayed_checkout(&state, &nr_checkouts, opts->show_progress);
diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 303a5d6274a064..62a9034a523668 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -426,6 +426,18 @@ int fscache_enable(int enable)
 	return result;
 }
 
+/*
+ * Flush cached stats result when fscache is enabled.
+ */
+void fscache_flush(void)
+{
+	if (enabled) {
+		EnterCriticalSection(&mutex);
+		fscache_clear();
+		LeaveCriticalSection(&mutex);
+	}
+}
+
 /*
  * Lstat replacement, uses the cache if enabled, otherwise redirects to
  * mingw_lstat.
diff --git a/compat/win32/fscache.h b/compat/win32/fscache.h
index 660ada053b4309..2f06f8df97dcd0 100644
--- a/compat/win32/fscache.h
+++ b/compat/win32/fscache.h
@@ -7,6 +7,9 @@ int fscache_enable(int enable);
 int fscache_enabled(const char *path);
 #define is_fscache_enabled(path) fscache_enabled(path)
 
+void fscache_flush(void);
+#define flush_fscache() fscache_flush()
+
 DIR *fscache_opendir(const char *dir);
 int fscache_lstat(const char *file_name, struct stat *buf);
 
diff --git a/entry.c b/entry.c
index 1c9df62b306479..b079c787516fab 100644
--- a/entry.c
+++ b/entry.c
@@ -386,6 +386,9 @@ static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca
 	}
 
 finish:
+	/* Flush cached lstat in fscache after writing to disk. */
+	flush_fscache();
+
 	if (state->refresh_cache) {
 		if (!fstat_done && lstat(ce->name, &st) < 0)
 			return error_errno("unable to stat just-written file %s",
diff --git a/git-compat-util.h b/git-compat-util.h
index 871524d78bfd81..c532eca1e3f34d 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -1425,6 +1425,10 @@ static inline int is_missing_file_error(int errno_)
 #define is_fscache_enabled(path) (0)
 #endif
 
+#ifndef flush_fscache
+#define flush_fscache() /* noop */
+#endif
+
 int cmd_main(int, const char **);
 
 /*
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 61ad47b0c18d23..df068f892006f2 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -35,6 +35,42 @@ fill () {
 }
 
 
+test_expect_success MINGW 'fscache flush cache' '
+
+	git init fscache-test &&
+	cd fscache-test &&
+	git config core.fscache 1 &&
+	echo A > test.txt &&
+	git add test.txt &&
+	git commit -m A &&
+	echo B >> test.txt &&
+	git checkout . &&
+	test -z "$(git status -s)" &&
+	echo A > expect.txt &&
+	test_cmp expect.txt test.txt &&
+	cd .. &&
+	rm -rf fscache-test
+'
+
+test_expect_success MINGW 'fscache flush cache dir' '
+
+	git init fscache-test &&
+	cd fscache-test &&
+	git config core.fscache 1 &&
+	echo A > test.txt &&
+	git add test.txt &&
+	git commit -m A &&
+	rm test.txt &&
+	mkdir test.txt &&
+	touch test.txt/test.txt &&
+	git checkout . &&
+	test -z "$(git status -s)" &&
+	echo A > expect.txt &&
+	test_cmp expect.txt test.txt &&
+	cd .. &&
+	rm -rf fscache-test
+'
+
 test_expect_success setup '
 	fill x y z >same &&
 	fill 1 2 3 4 5 6 7 8 >one &&

From 38ec59c7407e7a98776d7e14a9f1cb79a4a8c899 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sat, 4 Feb 2012 21:54:36 +0100
Subject: [PATCH 155/265] gitk: Unicode file name support

Assumes file names in git tree objects are UTF-8 encoded.

On most unix systems, the system encoding (and thus the TCL system
encoding) will be UTF-8, so file names will be displayed correctly.

On Windows, it is impossible to set the system encoding to UTF-8.
Changing the TCL system encoding (via 'encoding system ...', e.g. in the
startup code) is explicitly discouraged by the TCL docs.

Change gitk functions dealing with file names to always convert
from and to UTF-8.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 gitk-git/gitk | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/gitk-git/gitk b/gitk-git/gitk
index 23d9dd1fe0d024..f5d7577ba80112 100755
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -7689,7 +7689,7 @@ proc gettreeline {gtf id} {
         if {[string index $fname 0] eq "\""} {
             set fname [lindex $fname 0]
         }
-        set fname [encoding convertfrom $fname]
+        set fname [encoding convertfrom utf-8 $fname]
         lappend treefilelist($id) $fname
     }
     if {![eof $gtf]} {
@@ -7951,7 +7951,7 @@ proc gettreediffline {gdtf ids} {
             if {[string index $file 0] eq "\""} {
                 set file [lindex $file 0]
             }
-            set file [encoding convertfrom $file]
+            set file [encoding convertfrom utf-8 $file]
             if {$file ne [lindex $treediff end]} {
                 lappend treediff $file
                 lappend sublist $file
@@ -8096,7 +8096,7 @@ proc makediffhdr {fname ids} {
     global ctext curdiffstart treediffs diffencoding
     global ctext_file_names jump_to_here targetline diffline
 
-    set fname [encoding convertfrom $fname]
+    set fname [encoding convertfrom utf-8 $fname]
     set diffencoding [get_path_encoding $fname]
     set i [lsearch -exact $treediffs($ids) $fname]
     if {$i >= 0} {
@@ -8158,7 +8158,7 @@ proc parseblobdiffline {ids line} {
 
     if {![string compare -length 5 "diff " $line]} {
         if {![regexp {^diff (--cc|--git) } $line m type]} {
-            set line [encoding convertfrom $line]
+            set line [encoding convertfrom utf-8 $line]
             $ctext insert end "$line\n" hunksep
             continue
         }
@@ -8207,7 +8207,7 @@ proc parseblobdiffline {ids line} {
         makediffhdr $fname $ids
 
     } elseif {![string compare -length 16 "* Unmerged path " $line]} {
-        set fname [encoding convertfrom [string range $line 16 end]]
+        set fname [encoding convertfrom utf-8 [string range $line 16 end]]
         $ctext insert end "\n"
         set curdiffstart [$ctext index "end - 1c"]
         lappend ctext_file_names $fname
@@ -8260,7 +8260,7 @@ proc parseblobdiffline {ids line} {
             if {[string index $fname 0] eq "\""} {
                 set fname [lindex $fname 0]
             }
-            set fname [encoding convertfrom $fname]
+            set fname [encoding convertfrom utf-8 $fname]
             set i [lsearch -exact $treediffs($ids) $fname]
             if {$i >= 0} {
                 setinlist difffilestart $i $curdiffstart
@@ -8279,6 +8279,7 @@ proc parseblobdiffline {ids line} {
             set diffinhdr 0
             return
         }
+        set line [encoding convertfrom utf-8 $line]
         $ctext insert end "$line\n" filesep
 
     } else {
@@ -12241,7 +12242,7 @@ proc cache_gitattr {attr pathlist} {
             foreach row [split $rlist "\n"] {
                 if {[regexp "(.*): $attr: (.*)" $row m path value]} {
                     if {[string index $path 0] eq "\""} {
-                        set path [encoding convertfrom [lindex $path 0]]
+                        set path [encoding convertfrom utf-8 [lindex $path 0]]
                     }
                     set path_attr_cache($attr,$path) $value
                 }

From 9c438283446c0b5d041fce3383a669ce8456f7af Mon Sep 17 00:00:00 2001
From: Ben Peart <benpeart@microsoft.com>
Date: Thu, 4 Oct 2018 18:10:21 -0400
Subject: [PATCH 156/265] mem_pool: add GIT_TRACE_MEMPOOL support

Add tracing around initializing and discarding mempools. In discard report
on the amount of memory unused in the current block to help tune setting
the initial_size.

Signed-off-by: Ben Peart <benpeart@microsoft.com>
---
 mem-pool.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/mem-pool.c b/mem-pool.c
index 599d8e895f8121..a961d71f2ab10c 100644
--- a/mem-pool.c
+++ b/mem-pool.c
@@ -5,6 +5,7 @@
 #include "cache.h"
 #include "mem-pool.h"
 
+static struct trace_key trace_mem_pool = TRACE_KEY_INIT(MEMPOOL);
 #define BLOCK_GROWTH_SIZE (1024 * 1024 - sizeof(struct mp_block))
 
 /*
@@ -62,12 +63,20 @@ void mem_pool_init(struct mem_pool *pool, size_t initial_size)
 
 	if (initial_size > 0)
 		mem_pool_alloc_block(pool, initial_size, NULL);
+
+	trace_printf_key(&trace_mem_pool,
+		"mem_pool (%p): init (%"PRIuMAX") initial size\n",
+		(void *)pool, (uintmax_t)initial_size);
 }
 
 void mem_pool_discard(struct mem_pool *pool, int invalidate_memory)
 {
 	struct mp_block *block, *block_to_free;
 
+	trace_printf_key(&trace_mem_pool,
+		"mem_pool (%p): discard (%"PRIuMAX") unused\n",
+		(void *)pool,
+		(uintmax_t)(pool->mp_block->end - pool->mp_block->next_free));
 	block = pool->mp_block;
 	while (block)
 	{

From f8783d648c0574340b343c413edd3d34811fd3e8 Mon Sep 17 00:00:00 2001
From: Heiko Voigt <hvoigt@hvoigt.net>
Date: Sun, 21 Feb 2010 21:05:04 +0100
Subject: [PATCH 157/265] git-gui: provide question helper for retry fallback
 on Windows

Make use of the new environment variable GIT_ASK_YESNO to support the
recently implemented fallback in case unlink, rename or rmdir fail for
files in use on Windows. The added dialog will present a yes/no question
to the the user which will currently be used by the windows compat layer
to let the user retry a failed file operation.

Signed-off-by: Heiko Voigt <hvoigt@hvoigt.net>
---
 git-gui/Makefile          |  2 ++
 git-gui/git-gui--askyesno | 51 +++++++++++++++++++++++++++++++++++++++
 git-gui/git-gui.sh        |  3 +++
 3 files changed, 56 insertions(+)
 create mode 100755 git-gui/git-gui--askyesno

diff --git a/git-gui/Makefile b/git-gui/Makefile
index 56c85a85c1e493..8bc0789d7dfded 100644
--- a/git-gui/Makefile
+++ b/git-gui/Makefile
@@ -295,6 +295,7 @@ install: all
 	$(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL_D1)
 	$(QUIET)$(INSTALL_X0)git-gui $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
 	$(QUIET)$(INSTALL_X0)git-gui--askpass $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
+	$(QUIET)$(INSTALL_X0)git-gui--askyesno $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
 	$(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(INSTALL_L0)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L1)'$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' $(INSTALL_L2)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L3) &&) true
 ifdef GITGUI_WINDOWS_WRAPPER
 	$(QUIET)$(INSTALL_R0)git-gui.tcl $(INSTALL_R1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
@@ -313,6 +314,7 @@ uninstall:
 	$(QUIET)$(CLEAN_DST) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
 	$(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui $(REMOVE_F1)
 	$(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui--askpass $(REMOVE_F1)
+	$(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui--askyesno $(REMOVE_F1)
 	$(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/$p $(REMOVE_F1) &&) true
 ifdef GITGUI_WINDOWS_WRAPPER
 	$(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui.tcl $(REMOVE_F1)
diff --git a/git-gui/git-gui--askyesno b/git-gui/git-gui--askyesno
new file mode 100755
index 00000000000000..2a6e6fd11122f5
--- /dev/null
+++ b/git-gui/git-gui--askyesno
@@ -0,0 +1,51 @@
+#!/bin/sh
+# Tcl ignores the next line -*- tcl -*- \
+exec wish "$0" -- "$@"
+
+# This is an implementation of a simple yes no dialog
+# which is injected into the git commandline by git gui
+# in case a yesno question needs to be answered.
+
+set NS {}
+set use_ttk [package vsatisfies [package provide Tk] 8.5]
+if {$use_ttk} {
+	set NS ttk
+}
+
+if {$argc < 1} {
+	puts stderr "Usage: $argv0 <question>"
+	exit 1
+} else {
+	set prompt [join $argv " "]
+}
+
+${NS}::frame .t
+${NS}::label .t.m -text $prompt -justify center -width 40
+.t.m configure -wraplength 400
+pack .t.m -side top -fill x -padx 20 -pady 20 -expand 1
+pack .t -side top -fill x -ipadx 20 -ipady 20 -expand 1
+
+${NS}::frame .b
+${NS}::frame .b.left -width 200
+${NS}::button .b.yes -text Yes -command yes
+${NS}::button .b.no  -text No  -command no
+
+
+pack .b.left -side left -expand 1 -fill x
+pack .b.yes -side left -expand 1
+pack .b.no -side right -expand 1 -ipadx 5
+pack .b -side bottom -fill x -ipadx 20 -ipady 15
+
+bind . <Key-Return> {exit 0}
+bind . <Key-Escape> {exit 1}
+
+proc no {} {
+	exit 1
+}
+
+proc yes {} {
+	exit 0
+}
+
+wm title . "Question?"
+tk::PlaceWindow .
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 201524c34edac0..e6860a0ae8ba89 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -1230,6 +1230,9 @@ set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
 if {![info exists env(SSH_ASKPASS)]} {
 	set env(SSH_ASKPASS) [gitexec git-gui--askpass]
 }
+if {![info exists env(GIT_ASK_YESNO)]} {
+	set env(GIT_ASK_YESNO) [gitexec git-gui--askyesno]
+}
 
 ######################################################################
 ##

From fb9e5c37b732fffe81f477680deeef304b237370 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 20 Sep 2017 21:52:28 +0200
Subject: [PATCH 158/265] git-gui--askyesno: fix funny text wrapping

The text wrapping seems to be aligned to the right side of the Yes
button, leaving an awful lot of empty space.

Let's try to counter this by using pixel units.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 git-gui/git-gui--askyesno | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/git-gui/git-gui--askyesno b/git-gui/git-gui--askyesno
index 2a6e6fd11122f5..cf9c990d0919b3 100755
--- a/git-gui/git-gui--askyesno
+++ b/git-gui/git-gui--askyesno
@@ -20,8 +20,8 @@ if {$argc < 1} {
 }
 
 ${NS}::frame .t
-${NS}::label .t.m -text $prompt -justify center -width 40
-.t.m configure -wraplength 400
+${NS}::label .t.m -text $prompt -justify center -width 400px
+.t.m configure -wraplength 400px
 pack .t.m -side top -fill x -padx 20 -pady 20 -expand 1
 pack .t -side top -fill x -ipadx 20 -ipady 20 -expand 1
 

From 34bb009360fad396b3c9cd6dce3214777967c657 Mon Sep 17 00:00:00 2001
From: Sebastian Schuberth <sschuberth@gmail.com>
Date: Sun, 22 Jul 2012 23:19:24 +0200
Subject: [PATCH 159/265] gitk: Use an external icon file on Windows

Git for Windows now ships with the new Git icon from git-scm.com. Use that
icon file if it exists instead of the old procedurally drawn one.

This patch was sent upstream but so far no decision on its inclusion was
made, so commit it to our fork.

Signed-off-by: Sebastian Schuberth <sschuberth@gmail.com>
---
 gitk-git/gitk | 49 ++++++++++++++++++++++++++-----------------------
 1 file changed, 26 insertions(+), 23 deletions(-)

diff --git a/gitk-git/gitk b/gitk-git/gitk
index f5d7577ba80112..e5ebd74cb84562 100755
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -12272,7 +12272,6 @@ if { [info exists ::env(GITK_MSGSDIR)] } {
     set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
     set gitk_libdir [file join $gitk_prefix share gitk lib]
     set gitk_msgsdir [file join $gitk_libdir msgs]
-    unset gitk_prefix
 }
 
 ## Internationalization (i18n) through msgcat and gettext. See
@@ -12635,28 +12634,32 @@ if {[expr {[exec git rev-parse --is-inside-work-tree] == "true"}]} {
 set worktree [gitworktree]
 setcoords
 makewindow
-catch {
-    image create photo gitlogo      -width 16 -height 16
-
-    image create photo gitlogominus -width  4 -height  2
-    gitlogominus put #C00000 -to 0 0 4 2
-    gitlogo copy gitlogominus -to  1 5
-    gitlogo copy gitlogominus -to  6 5
-    gitlogo copy gitlogominus -to 11 5
-    image delete gitlogominus
-
-    image create photo gitlogoplus  -width  4 -height  4
-    gitlogoplus  put #008000 -to 1 0 3 4
-    gitlogoplus  put #008000 -to 0 1 4 3
-    gitlogo copy gitlogoplus  -to  1 9
-    gitlogo copy gitlogoplus  -to  6 9
-    gitlogo copy gitlogoplus  -to 11 9
-    image delete gitlogoplus
-
-    image create photo gitlogo32    -width 32 -height 32
-    gitlogo32 copy gitlogo -zoom 2 2
-
-    wm iconphoto . -default gitlogo gitlogo32
+if {$::tcl_platform(platform) eq {windows} && [file exists $gitk_prefix/etc/git.ico]} {
+    wm iconbitmap . -default $gitk_prefix/etc/git.ico
+} else {
+    catch {
+        image create photo gitlogo      -width 16 -height 16
+
+        image create photo gitlogominus -width  4 -height  2
+        gitlogominus put #C00000 -to 0 0 4 2
+        gitlogo copy gitlogominus -to  1 5
+        gitlogo copy gitlogominus -to  6 5
+        gitlogo copy gitlogominus -to 11 5
+        image delete gitlogominus
+
+        image create photo gitlogoplus  -width  4 -height  4
+        gitlogoplus  put #008000 -to 1 0 3 4
+        gitlogoplus  put #008000 -to 0 1 4 3
+        gitlogo copy gitlogoplus  -to  1 9
+        gitlogo copy gitlogoplus  -to  6 9
+        gitlogo copy gitlogoplus  -to 11 9
+        image delete gitlogoplus
+
+        image create photo gitlogo32    -width 32 -height 32
+        gitlogo32 copy gitlogo -zoom 2 2
+
+        wm iconphoto . -default gitlogo gitlogo32
+    }
 }
 # wait for the window to become visible
 tkwait visibility .

From 02a974dfa6a40e55bf7d6cdb6eaaadd4def8be85 Mon Sep 17 00:00:00 2001
From: Ben Peart <benpeart@microsoft.com>
Date: Fri, 2 Nov 2018 11:19:10 -0400
Subject: [PATCH 160/265] fscache: fscache takes an initial size

Update enable_fscache() to take an optional initial size parameter which is
used to initialize the hashmap so that it can avoid having to rehash as
additional entries are added.

Add a separate disable_fscache() macro to make the code clearer and easier
to read.

Signed-off-by: Ben Peart <benpeart@microsoft.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/add.c          | 2 +-
 builtin/checkout.c     | 4 ++--
 builtin/commit.c       | 4 ++--
 compat/win32/fscache.c | 8 ++++++--
 compat/win32/fscache.h | 5 +++--
 fetch-pack.c           | 4 ++--
 git-compat-util.h      | 4 ++++
 preload-index.c        | 4 ++--
 read-cache.c           | 4 ++--
 9 files changed, 24 insertions(+), 15 deletions(-)

diff --git a/builtin/add.c b/builtin/add.c
index 051e7e68d12665..f8f10676ec656e 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -597,7 +597,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 	die_in_unpopulated_submodule(&the_index, prefix);
 	die_path_inside_submodule(&the_index, &pathspec);
 
-	enable_fscache(1);
+	enable_fscache(0);
 	/* We do not really re-read the index but update the up-to-date flags */
 	preload_index(&the_index, &pathspec, 0);
 
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 3ce6bb021c2a61..97c5b5bd58caf5 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -391,7 +391,7 @@ static int checkout_worktree(const struct checkout_opts *opts,
 	if (pc_workers > 1)
 		init_parallel_checkout();
 
-	enable_fscache(1);
+	enable_fscache(active_nr);
 	for (pos = 0; pos < active_nr; pos++) {
 		struct cache_entry *ce = active_cache[pos];
 		if (ce->ce_flags & CE_MATCHED) {
@@ -416,7 +416,7 @@ static int checkout_worktree(const struct checkout_opts *opts,
 		errs |= run_parallel_checkout(&state, pc_workers, pc_threshold,
 					      NULL, NULL);
 	mem_pool_discard(&ce_mem_pool, should_validate_cache_entries());
-	enable_fscache(0);
+	disable_fscache();
 	remove_marked_cache_entries(&the_index, 1);
 	remove_scheduled_dirs();
 	errs |= finish_delayed_checkout(&state, &nr_checkouts, opts->show_progress);
diff --git a/builtin/commit.c b/builtin/commit.c
index 7ffec56d7312f1..921e799f9cd72e 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1540,7 +1540,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 		       PATHSPEC_PREFER_FULL,
 		       prefix, argv);
 
-	enable_fscache(1);
+	enable_fscache(0);
 	if (status_format != STATUS_FORMAT_PORCELAIN &&
 	    status_format != STATUS_FORMAT_PORCELAIN_V2)
 		progress_flag = REFRESH_PROGRESS;
@@ -1581,7 +1581,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 	wt_status_print(&s);
 	wt_status_collect_free_buffers(&s);
 
-	enable_fscache(0);
+	disable_fscache();
 	return 0;
 }
 
diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 2fc69b91de4438..d3efc5c57e5966 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -403,7 +403,7 @@ static struct fsentry *fscache_get(struct fsentry *key)
  * Enables or disables the cache. Note that the cache is read-only, changes to
  * the working directory are NOT reflected in the cache while enabled.
  */
-int fscache_enable(int enable)
+int fscache_enable(int enable, size_t initial_size)
 {
 	int result;
 
@@ -419,7 +419,11 @@ int fscache_enable(int enable)
 		InitializeCriticalSection(&mutex);
 		lstat_requests = opendir_requests = 0;
 		fscache_misses = fscache_requests = 0;
-		hashmap_init(&map, (hashmap_cmp_fn) fsentry_cmp, NULL, 0);
+		/*
+		 * avoid having to rehash by leaving room for the parent dirs.
+		 * '4' was determined empirically by testing several repos
+		 */
+		hashmap_init(&map, (hashmap_cmp_fn) fsentry_cmp, NULL, initial_size * 4);
 		initialized = 1;
 	}
 
diff --git a/compat/win32/fscache.h b/compat/win32/fscache.h
index 2f06f8df97dcd0..d49c9381114da6 100644
--- a/compat/win32/fscache.h
+++ b/compat/win32/fscache.h
@@ -1,8 +1,9 @@
 #ifndef FSCACHE_H
 #define FSCACHE_H
 
-int fscache_enable(int enable);
-#define enable_fscache(x) fscache_enable(x)
+int fscache_enable(int enable, size_t initial_size);
+#define enable_fscache(initial_size) fscache_enable(1, initial_size)
+#define disable_fscache() fscache_enable(0, 0)
 
 int fscache_enabled(const char *path);
 #define is_fscache_enabled(path) fscache_enabled(path)
diff --git a/fetch-pack.c b/fetch-pack.c
index 8b50cb09902d1d..d206d0fccdcfcb 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -700,7 +700,7 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,
 	save_commit_buffer = 0;
 
 	trace2_region_enter("fetch-pack", "parse_remote_refs_and_find_cutoff", NULL);
-	enable_fscache(1);
+	enable_fscache(0);
 	for (ref = *refs; ref; ref = ref->next) {
 		struct commit *commit;
 
@@ -727,7 +727,7 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,
 		if (!cutoff || cutoff < commit->date)
 			cutoff = commit->date;
 	}
-	enable_fscache(0);
+	disable_fscache();
 	trace2_region_leave("fetch-pack", "parse_remote_refs_and_find_cutoff", NULL);
 
 	/*
diff --git a/git-compat-util.h b/git-compat-util.h
index c532eca1e3f34d..581c38d4e51586 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -1421,6 +1421,10 @@ static inline int is_missing_file_error(int errno_)
 #define enable_fscache(x) /* noop */
 #endif
 
+#ifndef disable_fscache
+#define disable_fscache() /* noop */
+#endif
+
 #ifndef is_fscache_enabled
 #define is_fscache_enabled(path) (0)
 #endif
diff --git a/preload-index.c b/preload-index.c
index 46c632e52dcd50..d15b49e22dd6e7 100644
--- a/preload-index.c
+++ b/preload-index.c
@@ -126,7 +126,7 @@ void preload_index(struct index_state *index,
 		pthread_mutex_init(&pd.mutex, NULL);
 	}
 
-	enable_fscache(1);
+	enable_fscache(index->cache_nr);
 	for (i = 0; i < threads; i++) {
 		struct thread_data *p = data+i;
 		int err;
@@ -157,7 +157,7 @@ void preload_index(struct index_state *index,
 	trace2_data_intmax("index", NULL, "preload/sum_lstat", t2_sum_lstat);
 	trace2_region_leave("index", "preload", NULL);
 
-	enable_fscache(0);
+	disable_fscache();
 }
 
 int repo_read_index_preload(struct repository *repo,
diff --git a/read-cache.c b/read-cache.c
index 956c330b8d0813..cd085195b59731 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -1625,7 +1625,7 @@ int refresh_index(struct index_state *istate, unsigned int flags,
 	typechange_fmt = in_porcelain ? "T\t%s\n" : "%s: needs update\n";
 	added_fmt      = in_porcelain ? "A\t%s\n" : "%s: needs update\n";
 	unmerged_fmt   = in_porcelain ? "U\t%s\n" : "%s: needs merge\n";
-	enable_fscache(1);
+	enable_fscache(0);
 	/*
 	 * Use the multi-threaded preload_index() to refresh most of the
 	 * cache entries quickly then in the single threaded loop below,
@@ -1720,7 +1720,7 @@ int refresh_index(struct index_state *istate, unsigned int flags,
 	display_progress(progress, istate->cache_nr);
 	stop_progress(&progress);
 	trace_performance_leave("refresh index");
-	enable_fscache(0);
+	disable_fscache();
 	return has_errors;
 }
 

From 737e8bb47865bf12470b508489d8bf73c55d487b Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 11 Dec 2018 12:59:29 +0100
Subject: [PATCH 161/265] fscache: remember the reparse tag for each entry

We will use this in the next commit to implement an FSCache-aware
version of is_mount_point().

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/win32/fscache.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 760f8b80d65f2b..6ac0ed692e5849 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -44,6 +44,7 @@ static struct trace_key trace_fscache = TRACE_KEY_INIT(FSCACHE);
 struct fsentry {
 	struct hashmap_entry ent;
 	mode_t st_mode;
+	ULONG reparse_tag;
 	/* Pointer to the directory listing, or NULL for the listing itself. */
 	struct fsentry *list;
 	/* Pointer to the next file entry of the list. */
@@ -195,6 +196,10 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache,
 
 	fse = fsentry_alloc(cache, list, buf, len);
 
+	fse->reparse_tag =
+		fdata->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ?
+		fdata->EaSize : 0;
+
 	fse->st_mode = file_attr_to_st_mode(fdata->FileAttributes);
 	fse->dirent.d_type = S_ISDIR(fse->st_mode) ? DT_DIR : DT_REG;
 	fse->u.s.st_size = fdata->EndOfFile.LowPart |

From f819f120eaf0121ab446ceb6ff5c22ab70e298fc Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 23 Jul 2010 18:06:05 +0200
Subject: [PATCH 162/265] git gui: set GIT_ASKPASS=git-gui--askpass if not set
 yet

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 git-gui/git-gui.sh | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index e6860a0ae8ba89..b387c2533213d3 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -1230,6 +1230,9 @@ set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
 if {![info exists env(SSH_ASKPASS)]} {
 	set env(SSH_ASKPASS) [gitexec git-gui--askpass]
 }
+if {![info exists env(GIT_ASKPASS)]} {
+	set env(GIT_ASKPASS) [gitexec git-gui--askpass]
+}
 if {![info exists env(GIT_ASK_YESNO)]} {
 	set env(GIT_ASK_YESNO) [gitexec git-gui--askyesno]
 }

From 70cff622b3c6f403eee80023523f6d33bbb2a826 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 20 Sep 2017 21:53:45 +0200
Subject: [PATCH 163/265] git-gui--askyesno: allow overriding the window title

"Question?" is maybe not the most informative thing to ask. In the
absence of better information, it is the best we can do, of course.

However, Git for Windows' auto updater just learned the trick to use
git-gui--askyesno to ask the user whether to update now or not. And in
this scripted scenario, we can easily pass a command-line option to
change the window title.

So let's support that with the new `--title <title>` option.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 git-gui/git-gui--askyesno | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/git-gui/git-gui--askyesno b/git-gui/git-gui--askyesno
index cf9c990d0919b3..45b0260eff8145 100755
--- a/git-gui/git-gui--askyesno
+++ b/git-gui/git-gui--askyesno
@@ -12,10 +12,15 @@ if {$use_ttk} {
 	set NS ttk
 }
 
+set title "Question?"
 if {$argc < 1} {
 	puts stderr "Usage: $argv0 <question>"
 	exit 1
 } else {
+	if {$argc > 2 && [lindex $argv 0] == "--title"} {
+		set title [lindex $argv 1]
+		set argv [lreplace $argv 0 1]
+	}
 	set prompt [join $argv " "]
 }
 
@@ -47,5 +52,5 @@ proc yes {} {
 	exit 0
 }
 
-wm title . "Question?"
+wm title . $title
 tk::PlaceWindow .

From 58b0121b1fe867631ac4c11e3e50311380afd25c Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 16 Feb 2016 16:42:06 +0100
Subject: [PATCH 164/265] gitk: fix arrow keys in input fields with Tcl/Tk >=
 8.6

Tcl/Tk 8.6 introduced new events for the cursor left/right keys and
apparently changed the behavior of the previous event.

Let's work around that by using the new events when we are running with
Tcl/Tk 8.6 or later.

This fixes https://github.com/git-for-windows/git/issues/495

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 gitk-git/gitk | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/gitk-git/gitk b/gitk-git/gitk
index e5ebd74cb84562..27a255204ffef4 100755
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -2079,7 +2079,7 @@ proc makewindow {} {
     global headctxmenu progresscanv progressitem progresscoords statusw
     global fprogitem fprogcoord lastprogupdate progupdatepending
     global rprogitem rprogcoord rownumsel numcommits
-    global have_tk85 use_ttk NS
+    global have_tk85 have_tk86 use_ttk NS
     global git_version
     global worddiff
 
@@ -2577,8 +2577,13 @@ proc makewindow {} {
     bind . <Key-Down> "selnextline 1"
     bind . <Shift-Key-Up> "dofind -1 0"
     bind . <Shift-Key-Down> "dofind 1 0"
-    bindkey <Key-Right> "goforw"
-    bindkey <Key-Left> "goback"
+    if {$have_tk86} {
+        bindkey <<NextChar>> "goforw"
+        bindkey <<PrevChar>> "goback"
+    } else {
+        bindkey <Key-Right> "goforw"
+        bindkey <Key-Left> "goback"
+    }
     bind . <Key-Prior> "selnextpage -1"
     bind . <Key-Next> "selnextpage 1"
     bind . <$M1B-Home> "allcanvs yview moveto 0.0"
@@ -12570,6 +12575,7 @@ set nullid2 "0000000000000000000000000000000000000001"
 set nullfile "/dev/null"
 
 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
+set have_tk86 [expr {[package vcompare $tk_version "8.6"] >= 0}]
 if {![info exists have_ttk]} {
     set have_ttk [llength [info commands ::ttk::style]]
 }

From 18edd84a00a371a54534fe33e3df664145f86686 Mon Sep 17 00:00:00 2001
From: Ben Peart <benpeart@microsoft.com>
Date: Thu, 4 Oct 2018 15:38:08 -0400
Subject: [PATCH 165/265] fscache: update fscache to be thread specific instead
 of global

The threading model for fscache has been to have a single, global cache.
This puts requirements on it to be thread safe so that callers like
preload-index can call it from multiple threads.  This was implemented
with a single mutex and completion events which introduces contention
between the calling threads.

Simplify the threading model by making fscache thread specific.  This allows
us to remove the global mutex and synchronization events entirely and instead
associate a fscache with every thread that requests one. This works well with
the current multi-threading which divides the cache entries into blocks with
a separate thread processing each block.

At the end of each worker thread, if there is a fscache on the primary
thread, merge the cached results from the worker into the primary thread
cache. This enables us to reuse the cache later especially when scanning for
untracked files.

In testing, this reduced the time spent in preload_index() by about 25% and
also reduced the CPU utilization significantly.  On a repo with ~200K files,
it reduced overall status times by ~12%.

Signed-off-by: Ben Peart <benpeart@microsoft.com>
---
 compat/win32/fscache.c | 294 +++++++++++++++++++++++++----------------
 compat/win32/fscache.h |  22 ++-
 git-compat-util.h      |  12 ++
 preload-index.c        |   8 +-
 4 files changed, 215 insertions(+), 121 deletions(-)

diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index d3efc5c57e5966..7fad622e1d1e43 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -5,14 +5,24 @@
 #include "../../dir.h"
 #include "config.h"
 
-static int initialized;
-static volatile long enabled;
-static struct hashmap map;
+static volatile long initialized;
+static DWORD dwTlsIndex;
 static CRITICAL_SECTION mutex;
-static unsigned int lstat_requests;
-static unsigned int opendir_requests;
-static unsigned int fscache_requests;
-static unsigned int fscache_misses;
+
+/*
+ * Store one fscache per thread to avoid thread contention and locking.
+ * This is ok because multi-threaded access is 1) uncommon and 2) always
+ * splitting up the cache entries across multiple threads so there isn't
+ * any overlap between threads anyway.
+ */
+struct fscache {
+	volatile long enabled;
+	struct hashmap map;
+	unsigned int lstat_requests;
+	unsigned int opendir_requests;
+	unsigned int fscache_requests;
+	unsigned int fscache_misses;
+};
 static struct trace_key trace_fscache = TRACE_KEY_INIT(FSCACHE);
 
 /*
@@ -32,8 +42,6 @@ struct fsentry {
 	union {
 		/* Reference count of the directory listing. */
 		volatile long refcnt;
-		/* Handle to wait on the loading thread. */
-		HANDLE hwait;
 		struct {
 			/* More stat members (only used for file entries). */
 			off64_t st_size;
@@ -251,86 +259,63 @@ static struct fsentry *fsentry_create_list(const struct fsentry *dir,
 /*
  * Adds a directory listing to the cache.
  */
-static void fscache_add(struct fsentry *fse)
+static void fscache_add(struct fscache *cache, struct fsentry *fse)
 {
 	if (fse->list)
 		fse = fse->list;
 
 	for (; fse; fse = fse->next)
-		hashmap_add(&map, &fse->ent);
+		hashmap_add(&cache->map, &fse->ent);
 }
 
 /*
  * Clears the cache.
  */
-static void fscache_clear(void)
+static void fscache_clear(struct fscache *cache)
 {
-	hashmap_clear_and_free(&map, struct fsentry, ent);
-	hashmap_init(&map, (hashmap_cmp_fn)fsentry_cmp, NULL, 0);
-	lstat_requests = opendir_requests = 0;
-	fscache_misses = fscache_requests = 0;
+	hashmap_clear_and_free(&cache->map, struct fsentry, ent);
+	hashmap_init(&cache->map, (hashmap_cmp_fn)fsentry_cmp, NULL, 0);
+	cache->lstat_requests = cache->opendir_requests = 0;
+	cache->fscache_misses = cache->fscache_requests = 0;
 }
 
 /*
  * Checks if the cache is enabled for the given path.
  */
-int fscache_enabled(const char *path)
+static int do_fscache_enabled(struct fscache *cache, const char *path)
 {
-	return enabled > 0 && !is_absolute_path(path);
+	return cache->enabled > 0 && !is_absolute_path(path);
 }
 
-/*
- * Looks up a cache entry, waits if its being loaded by another thread.
- * The mutex must be owned by the calling thread.
- */
-static struct fsentry *fscache_get_wait(struct fsentry *key)
+int fscache_enabled(const char *path)
 {
-	struct fsentry *fse = hashmap_get_entry(&map, key, ent, NULL);
-
-	/* return if its a 'real' entry (future entries have refcnt == 0) */
-	if (!fse || fse->list || fse->u.refcnt)
-		return fse;
-
-	/* create an event and link our key to the future entry */
-	key->u.hwait = CreateEvent(NULL, TRUE, FALSE, NULL);
-	key->next = fse->next;
-	fse->next = key;
-
-	/* wait for the loading thread to signal us */
-	LeaveCriticalSection(&mutex);
-	WaitForSingleObject(key->u.hwait, INFINITE);
-	CloseHandle(key->u.hwait);
-	EnterCriticalSection(&mutex);
+	struct fscache *cache = fscache_getcache();
 
-	/* repeat cache lookup */
-	return hashmap_get_entry(&map, key, ent, NULL);
+	return cache ? do_fscache_enabled(cache, path) : 0;
 }
 
 /*
  * Looks up or creates a cache entry for the specified key.
  */
-static struct fsentry *fscache_get(struct fsentry *key)
+static struct fsentry *fscache_get(struct fscache *cache, struct fsentry *key)
 {
-	struct fsentry *fse, *future, *waiter;
+	struct fsentry *fse;
 	int dir_not_found;
 
-	EnterCriticalSection(&mutex);
-	fscache_requests++;
+	cache->fscache_requests++;
 	/* check if entry is in cache */
-	fse = fscache_get_wait(key);
+	fse = hashmap_get_entry(&cache->map, key, ent, NULL);
 	if (fse) {
 		if (fse->st_mode)
 			fsentry_addref(fse);
 		else
 			fse = NULL; /* non-existing directory */
-		LeaveCriticalSection(&mutex);
 		return fse;
 	}
 	/* if looking for a file, check if directory listing is in cache */
 	if (!fse && key->list) {
-		fse = fscache_get_wait(key->list);
+		fse = hashmap_get_entry(&cache->map, key->list, ent, NULL);
 		if (fse) {
-			LeaveCriticalSection(&mutex);
 			/*
 			 * dir entry without file entry, or dir does not
 			 * exist -> file doesn't exist
@@ -340,25 +325,8 @@ static struct fsentry *fscache_get(struct fsentry *key)
 		}
 	}
 
-	/* add future entry to indicate that we're loading it */
-	future = key->list ? key->list : key;
-	future->next = NULL;
-	future->u.refcnt = 0;
-	hashmap_add(&map, &future->ent);
-
-	/* create the directory listing (outside mutex!) */
-	LeaveCriticalSection(&mutex);
-	fse = fsentry_create_list(future, &dir_not_found);
-	EnterCriticalSection(&mutex);
-
-	/* remove future entry and signal waiting threads */
-	hashmap_remove(&map, &future->ent, NULL);
-	waiter = future->next;
-	while (waiter) {
-		HANDLE h = waiter->u.hwait;
-		waiter = waiter->next;
-		SetEvent(h);
-	}
+	/* create the directory listing */
+	fse = fsentry_create_list(key->list ? key->list : key, &dir_not_found);
 
 	/* leave on error (errno set by fsentry_create_list) */
 	if (!fse) {
@@ -372,19 +340,18 @@ static struct fsentry *fscache_get(struct fsentry *key)
 					    key->list->dirent.d_name,
 					    key->list->len);
 			fse->st_mode = 0;
-			hashmap_add(&map, &fse->ent);
+			hashmap_add(&cache->map, &fse->ent);
 		}
-		LeaveCriticalSection(&mutex);
 		return NULL;
 	}
 
 	/* add directory listing to the cache */
-	fscache_misses++;
-	fscache_add(fse);
+	cache->fscache_misses++;
+	fscache_add(cache, fse);
 
 	/* lookup file entry if requested (fse already points to directory) */
 	if (key->list)
-		fse = hashmap_get_entry(&map, key, ent, NULL);
+		fse = hashmap_get_entry(&cache->map, key, ent, NULL);
 
 	if (fse && !fse->st_mode)
 		fse = NULL; /* non-existing directory */
@@ -395,59 +362,104 @@ static struct fsentry *fscache_get(struct fsentry *key)
 	else
 		errno = ENOENT;
 
-	LeaveCriticalSection(&mutex);
 	return fse;
 }
 
 /*
- * Enables or disables the cache. Note that the cache is read-only, changes to
+ * Enables the cache. Note that the cache is read-only, changes to
  * the working directory are NOT reflected in the cache while enabled.
  */
-int fscache_enable(int enable, size_t initial_size)
+int fscache_enable(size_t initial_size)
 {
-	int result;
+	int fscache;
+	struct fscache *cache;
+	int result = 0;
+
+	/* allow the cache to be disabled entirely */
+	fscache = git_env_bool("GIT_TEST_FSCACHE", -1);
+	if (fscache != -1)
+		core_fscache = fscache;
+	if (!core_fscache)
+		return 0;
 
+	/*
+	 * refcount the global fscache initialization so that the
+	 * opendir and lstat function pointers are redirected if
+	 * any threads are using the fscache.
+	 */
 	if (!initialized) {
-		int fscache = git_env_bool("GIT_TEST_FSCACHE", -1);
-
-		/* allow the cache to be disabled entirely */
-		if (fscache != -1)
-			core_fscache = fscache;
-		if (!core_fscache)
-			return 0;
-
 		InitializeCriticalSection(&mutex);
-		lstat_requests = opendir_requests = 0;
-		fscache_misses = fscache_requests = 0;
+		if (!dwTlsIndex) {
+			dwTlsIndex = TlsAlloc();
+			if (dwTlsIndex == TLS_OUT_OF_INDEXES) {
+				LeaveCriticalSection(&mutex);
+				return 0;
+			}
+		}
+
+		/* redirect opendir and lstat to the fscache implementations */
+		opendir = fscache_opendir;
+		lstat = fscache_lstat;
+	}
+	InterlockedIncrement(&initialized);
+
+	/* refcount the thread specific initialization */
+	cache = fscache_getcache();
+	if (cache) {
+		InterlockedIncrement(&cache->enabled);
+	} else {
+		cache = (struct fscache *)xcalloc(1, sizeof(*cache));
+		cache->enabled = 1;
 		/*
 		 * avoid having to rehash by leaving room for the parent dirs.
 		 * '4' was determined empirically by testing several repos
 		 */
-		hashmap_init(&map, (hashmap_cmp_fn) fsentry_cmp, NULL, initial_size * 4);
-		initialized = 1;
+		hashmap_init(&cache->map, (hashmap_cmp_fn)fsentry_cmp, NULL, initial_size * 4);
+		if (!TlsSetValue(dwTlsIndex, cache))
+			BUG("TlsSetValue error");
 	}
 
-	result = enable ? InterlockedIncrement(&enabled)
-			: InterlockedDecrement(&enabled);
+	trace_printf_key(&trace_fscache, "fscache: enable\n");
+	return result;
+}
 
-	if (enable && result == 1) {
-		/* redirect opendir and lstat to the fscache implementations */
-		opendir = fscache_opendir;
-		lstat = fscache_lstat;
-	} else if (!enable && !result) {
+/*
+ * Disables the cache.
+ */
+void fscache_disable(void)
+{
+	struct fscache *cache;
+
+	if (!core_fscache)
+		return;
+
+	/* update the thread specific fscache initialization */
+	cache = fscache_getcache();
+	if (!cache)
+		BUG("fscache_disable() called on a thread where fscache has not been initialized");
+	if (!cache->enabled)
+		BUG("fscache_disable() called on an fscache that is already disabled");
+	InterlockedDecrement(&cache->enabled);
+	if (!cache->enabled) {
+		TlsSetValue(dwTlsIndex, NULL);
+		trace_printf_key(&trace_fscache, "fscache_disable: lstat %u, opendir %u, "
+			"total requests/misses %u/%u\n",
+			cache->lstat_requests, cache->opendir_requests,
+			cache->fscache_requests, cache->fscache_misses);
+		fscache_clear(cache);
+		free(cache);
+	}
+
+	/* update the global fscache initialization */
+	InterlockedDecrement(&initialized);
+	if (!initialized) {
 		/* reset opendir and lstat to the original implementations */
 		opendir = dirent_opendir;
 		lstat = mingw_lstat;
-		EnterCriticalSection(&mutex);
-		trace_printf_key(&trace_fscache, "fscache: lstat %u, opendir %u, "
-						 "total requests/misses %u/%u\n",
-				lstat_requests, opendir_requests,
-				fscache_requests, fscache_misses);
-		fscache_clear();
-		LeaveCriticalSection(&mutex);
 	}
-	trace_printf_key(&trace_fscache, "fscache: enable(%d)\n", enable);
-	return result;
+
+	trace_printf_key(&trace_fscache, "fscache: disable\n");
+	return;
 }
 
 /*
@@ -455,10 +467,10 @@ int fscache_enable(int enable, size_t initial_size)
  */
 void fscache_flush(void)
 {
-	if (enabled) {
-		EnterCriticalSection(&mutex);
-		fscache_clear();
-		LeaveCriticalSection(&mutex);
+	struct fscache *cache = fscache_getcache();
+
+	if (cache && cache->enabled) {
+		fscache_clear(cache);
 	}
 }
 
@@ -471,11 +483,12 @@ int fscache_lstat(const char *filename, struct stat *st)
 	int dirlen, base, len;
 	struct heap_fsentry key[2];
 	struct fsentry *fse;
+	struct fscache *cache = fscache_getcache();
 
-	if (!fscache_enabled(filename))
+	if (!cache || !do_fscache_enabled(cache, filename))
 		return mingw_lstat(filename, st);
 
-	lstat_requests++;
+	cache->lstat_requests++;
 	/* split filename into path + name */
 	len = strlen(filename);
 	if (len && is_dir_sep(filename[len - 1]))
@@ -488,7 +501,7 @@ int fscache_lstat(const char *filename, struct stat *st)
 	/* lookup entry for path + name in cache */
 	fsentry_init(&key[0].u.ent, NULL, filename, dirlen);
 	fsentry_init(&key[1].u.ent, &key[0].u.ent, filename + base, len - base);
-	fse = fscache_get(&key[1].u.ent);
+	fse = fscache_get(cache, &key[1].u.ent);
 	if (!fse) {
 		errno = ENOENT;
 		return -1;
@@ -553,11 +566,12 @@ DIR *fscache_opendir(const char *dirname)
 	struct fsentry *list;
 	fscache_DIR *dir;
 	int len;
+	struct fscache *cache = fscache_getcache();
 
-	if (!fscache_enabled(dirname))
+	if (!cache || !do_fscache_enabled(cache, dirname))
 		return dirent_opendir(dirname);
 
-	opendir_requests++;
+	cache->opendir_requests++;
 	/* prepare name (strip trailing '/', replace '.') */
 	len = strlen(dirname);
 	if ((len == 1 && dirname[0] == '.') ||
@@ -566,7 +580,7 @@ DIR *fscache_opendir(const char *dirname)
 
 	/* get directory listing from cache */
 	fsentry_init(&key.u.ent, NULL, dirname, len);
-	list = fscache_get(&key.u.ent);
+	list = fscache_get(cache, &key.u.ent);
 	if (!list)
 		return NULL;
 
@@ -577,3 +591,53 @@ DIR *fscache_opendir(const char *dirname)
 	dir->pfsentry = list;
 	return (DIR*) dir;
 }
+
+struct fscache *fscache_getcache(void)
+{
+	return (struct fscache *)TlsGetValue(dwTlsIndex);
+}
+
+void fscache_merge(struct fscache *dest)
+{
+	struct hashmap_iter iter;
+	struct hashmap_entry *e;
+	struct fscache *cache = fscache_getcache();
+
+	/*
+	 * Only do the merge if fscache was enabled and we have a dest
+	 * cache to merge into.
+	 */
+	if (!dest) {
+		fscache_enable(0);
+		return;
+	}
+	if (!cache)
+		BUG("fscache_merge() called on a thread where fscache has not been initialized");
+
+	TlsSetValue(dwTlsIndex, NULL);
+	trace_printf_key(&trace_fscache, "fscache_merge: lstat %u, opendir %u, "
+		"total requests/misses %u/%u\n",
+		cache->lstat_requests, cache->opendir_requests,
+		cache->fscache_requests, cache->fscache_misses);
+
+	/*
+	 * This is only safe because the primary thread we're merging into
+	 * isn't being used so the critical section only needs to prevent
+	 * the the child threads from stomping on each other.
+	 */
+	EnterCriticalSection(&mutex);
+
+	hashmap_iter_init(&cache->map, &iter);
+	while ((e = hashmap_iter_next(&iter)))
+		hashmap_add(&dest->map, e);
+
+	dest->lstat_requests += cache->lstat_requests;
+	dest->opendir_requests += cache->opendir_requests;
+	dest->fscache_requests += cache->fscache_requests;
+	dest->fscache_misses += cache->fscache_misses;
+	LeaveCriticalSection(&mutex);
+
+	free(cache);
+
+	InterlockedDecrement(&initialized);
+}
diff --git a/compat/win32/fscache.h b/compat/win32/fscache.h
index d49c9381114da6..2eb8bf3f5cfee8 100644
--- a/compat/win32/fscache.h
+++ b/compat/win32/fscache.h
@@ -1,9 +1,16 @@
 #ifndef FSCACHE_H
 #define FSCACHE_H
 
-int fscache_enable(int enable, size_t initial_size);
-#define enable_fscache(initial_size) fscache_enable(1, initial_size)
-#define disable_fscache() fscache_enable(0, 0)
+/*
+ * The fscache is thread specific. enable_fscache() must be called
+ * for each thread where caching is desired.
+ */
+
+int fscache_enable(size_t initial_size);
+#define enable_fscache(initial_size) fscache_enable(initial_size)
+
+void fscache_disable(void);
+#define disable_fscache() fscache_disable()
 
 int fscache_enabled(const char *path);
 #define is_fscache_enabled(path) fscache_enabled(path)
@@ -14,4 +21,13 @@ void fscache_flush(void);
 DIR *fscache_opendir(const char *dir);
 int fscache_lstat(const char *file_name, struct stat *buf);
 
+/* opaque fscache structure */
+struct fscache;
+
+struct fscache *fscache_getcache(void);
+#define getcache_fscache() fscache_getcache()
+
+void fscache_merge(struct fscache *dest);
+#define merge_fscache(dest) fscache_merge(dest)
+
 #endif
diff --git a/git-compat-util.h b/git-compat-util.h
index 581c38d4e51586..c1e61663c7d61c 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -1417,6 +1417,10 @@ static inline int is_missing_file_error(int errno_)
  * data or even file content without the need to synchronize with the file
  * system.
  */
+
+ /* opaque fscache structure */
+struct fscache;
+
 #ifndef enable_fscache
 #define enable_fscache(x) /* noop */
 #endif
@@ -1433,6 +1437,14 @@ static inline int is_missing_file_error(int errno_)
 #define flush_fscache() /* noop */
 #endif
 
+#ifndef getcache_fscache
+#define getcache_fscache() (NULL) /* noop */
+#endif
+
+#ifndef merge_fscache
+#define merge_fscache(dest) /* noop */
+#endif
+
 int cmd_main(int, const char **);
 
 /*
diff --git a/preload-index.c b/preload-index.c
index d15b49e22dd6e7..5927a605e54f1e 100644
--- a/preload-index.c
+++ b/preload-index.c
@@ -10,6 +10,8 @@
 #include "thread-utils.h"
 #include "repository.h"
 
+static struct fscache *fscache;
+
 /*
  * Mostly randomly chosen maximum thread counts: we
  * cap the parallelism to 20 threads, and we want
@@ -47,6 +49,7 @@ static void *preload_thread(void *_data)
 		nr = index->cache_nr - p->offset;
 	last_nr = nr;
 
+	enable_fscache(nr);
 	do {
 		struct cache_entry *ce = *cep++;
 		struct stat st;
@@ -90,6 +93,7 @@ static void *preload_thread(void *_data)
 		pthread_mutex_unlock(&pd->mutex);
 	}
 	cache_def_clear(&cache);
+	merge_fscache(fscache);
 	return NULL;
 }
 
@@ -105,6 +109,7 @@ void preload_index(struct index_state *index,
 	if (!HAVE_THREADS || !core_preload_index)
 		return;
 
+	fscache = getcache_fscache();
 	threads = index->cache_nr / THREAD_COST;
 	if ((index->cache_nr > 1) && (threads < 2) && git_env_bool("GIT_TEST_PRELOAD_INDEX", 0))
 		threads = 2;
@@ -126,7 +131,6 @@ void preload_index(struct index_state *index,
 		pthread_mutex_init(&pd.mutex, NULL);
 	}
 
-	enable_fscache(index->cache_nr);
 	for (i = 0; i < threads; i++) {
 		struct thread_data *p = data+i;
 		int err;
@@ -156,8 +160,6 @@ void preload_index(struct index_state *index,
 
 	trace2_data_intmax("index", NULL, "preload/sum_lstat", t2_sum_lstat);
 	trace2_region_leave("index", "preload", NULL);
-
-	disable_fscache();
 }
 
 int repo_read_index_preload(struct repository *repo,

From 22bf23d76739182c87153f53c1b734d6e2dfd773 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 11 Dec 2018 12:17:49 +0100
Subject: [PATCH 166/265] fscache: implement an FSCache-aware is_mount_point()

When FSCache is active, we can cache the reparse tag and use it directly
to determine whether a path refers to an NTFS junction, without any
additional, costly I/O.

Note: this change only makes a difference with the next commit, which
will make use of the FSCache in `git clean` (contingent on
`core.fscache` set, of course).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c         |  2 ++
 compat/mingw.h         |  3 ++-
 compat/win32/fscache.c | 35 +++++++++++++++++++++++++++++++++++
 compat/win32/fscache.h |  1 +
 4 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 15c1299cd5673a..edb6409c1188d9 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2708,6 +2708,8 @@ pid_t waitpid(pid_t pid, int *status, int options)
 	return -1;
 }
 
+int (*win32_is_mount_point)(struct strbuf *path) = mingw_is_mount_point;
+
 int mingw_is_mount_point(struct strbuf *path)
 {
 	WIN32_FIND_DATAW findbuf = { 0 };
diff --git a/compat/mingw.h b/compat/mingw.h
index 0dac10171c67a8..03a01e6d904d3f 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -471,7 +471,8 @@ static inline void convert_slashes(char *path)
 }
 struct strbuf;
 int mingw_is_mount_point(struct strbuf *path);
-#define is_mount_point mingw_is_mount_point
+extern int (*win32_is_mount_point)(struct strbuf *path);
+#define is_mount_point win32_is_mount_point
 #define CAN_UNLINK_MOUNT_POINTS 1
 #define PATH_SEP ';'
 char *mingw_query_user_email(void);
diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 6ac0ed692e5849..351b1dff7fff65 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -467,6 +467,7 @@ int fscache_enable(size_t initial_size)
 		/* redirect opendir and lstat to the fscache implementations */
 		opendir = fscache_opendir;
 		lstat = fscache_lstat;
+		win32_is_mount_point = fscache_is_mount_point;
 	}
 	initialized++;
 	LeaveCriticalSection(&fscache_cs);
@@ -527,6 +528,7 @@ void fscache_disable(void)
 		/* reset opendir and lstat to the original implementations */
 		opendir = dirent_opendir;
 		lstat = mingw_lstat;
+		win32_is_mount_point = mingw_is_mount_point;
 	}
 	LeaveCriticalSection(&fscache_cs);
 
@@ -597,6 +599,39 @@ int fscache_lstat(const char *filename, struct stat *st)
 	return 0;
 }
 
+/*
+ * is_mount_point() replacement, uses cache if enabled, otherwise falls
+ * back to mingw_is_mount_point().
+ */
+int fscache_is_mount_point(struct strbuf *path)
+{
+	int dirlen, base, len;
+	struct heap_fsentry key[2];
+	struct fsentry *fse;
+	struct fscache *cache = fscache_getcache();
+
+	if (!cache || !do_fscache_enabled(cache, path->buf))
+		return mingw_is_mount_point(path);
+
+	cache->lstat_requests++;
+	/* split path into path + name */
+	len = path->len;
+	if (len && is_dir_sep(path->buf[len - 1]))
+		len--;
+	base = len;
+	while (base && !is_dir_sep(path->buf[base - 1]))
+		base--;
+	dirlen = base ? base - 1 : 0;
+
+	/* lookup entry for path + name in cache */
+	fsentry_init(&key[0].u.ent, NULL, path->buf, dirlen);
+	fsentry_init(&key[1].u.ent, &key[0].u.ent, path->buf + base, len - base);
+	fse = fscache_get(cache, &key[1].u.ent);
+	if (!fse)
+		return mingw_is_mount_point(path);
+	return fse->reparse_tag == IO_REPARSE_TAG_MOUNT_POINT;
+}
+
 typedef struct fscache_DIR {
 	struct DIR base_dir; /* extend base struct DIR */
 	struct fsentry *pfsentry;
diff --git a/compat/win32/fscache.h b/compat/win32/fscache.h
index 042b247a542554..386c770a85d321 100644
--- a/compat/win32/fscache.h
+++ b/compat/win32/fscache.h
@@ -22,6 +22,7 @@ void fscache_flush(void);
 
 DIR *fscache_opendir(const char *dir);
 int fscache_lstat(const char *file_name, struct stat *buf);
+int fscache_is_mount_point(struct strbuf *path);
 
 /* opaque fscache structure */
 struct fscache;

From a765cbcb894e943407060e7d21181c31e6578ab9 Mon Sep 17 00:00:00 2001
From: Ben Peart <benpeart@microsoft.com>
Date: Fri, 7 Sep 2018 11:39:57 -0400
Subject: [PATCH 167/265] Enable the filesystem cache (fscache) in
 refresh_index().

On file systems that support it, this can dramatically speed up operations
like add, commit, describe, rebase, reset, rm that would otherwise have to
lstat() every file to "re-match" the stat information in the index to that
of the file system.

On a synthetic repo with 1M files, "git reset" dropped from 52.02 seconds to
14.42 seconds for a savings of 72%.

Signed-off-by: Ben Peart <benpeart@microsoft.com>
---
 read-cache.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/read-cache.c b/read-cache.c
index 4df97e185e9c80..956c330b8d0813 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -1625,6 +1625,7 @@ int refresh_index(struct index_state *istate, unsigned int flags,
 	typechange_fmt = in_porcelain ? "T\t%s\n" : "%s: needs update\n";
 	added_fmt      = in_porcelain ? "A\t%s\n" : "%s: needs update\n";
 	unmerged_fmt   = in_porcelain ? "U\t%s\n" : "%s: needs merge\n";
+	enable_fscache(1);
 	/*
 	 * Use the multi-threaded preload_index() to refresh most of the
 	 * cache entries quickly then in the single threaded loop below,
@@ -1719,6 +1720,7 @@ int refresh_index(struct index_state *istate, unsigned int flags,
 	display_progress(progress, istate->cache_nr);
 	stop_progress(&progress);
 	trace_performance_leave("refresh index");
+	enable_fscache(0);
 	return has_errors;
 }
 

From 6e82c0a912ae60828c0646e5ff79b914ed875530 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 4 Oct 2018 14:46:00 +0200
Subject: [PATCH 168/265] respect core.hooksPath, falling back to .git/hooks

Since v2.9.0, Git knows about the config variable core.hookspath
that allows overriding the path to the directory containing the
Git hooks.

Since v2.10.0, the `--git-path` option respects that config
variable, too, so we may just as well use that command.

For Git versions older than v2.5.0 (which was the first version to
support the `--git-path` option for the `rev-parse` command), we
simply fall back to the previous code.

This fixes https://github.com/git-for-windows/git/issues/1755

Initial-patch-by: Philipp Gortan <philipp@gortan.org>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 git-gui/git-gui.sh | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 201524c34edac0..068634ab6eff7e 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -623,7 +623,11 @@ proc git_write {args} {
 }
 
 proc githook_read {hook_name args} {
-	set pchook [gitdir hooks $hook_name]
+	if {[package vcompare $::_git_version 2.5.0] >= 0} {
+		set pchook [git rev-parse --git-path "hooks/$hook_name"]
+	} else {
+		set pchook [gitdir hooks $hook_name]
+	}
 	lappend args 2>@1
 
 	# On Windows [file executable] might lie so we need to ask

From b94eef632722942f4404da69dcd0dd4429ba961a Mon Sep 17 00:00:00 2001
From: Ben Peart <benpeart@microsoft.com>
Date: Tue, 23 Oct 2018 11:42:06 -0400
Subject: [PATCH 169/265] fscache: use FindFirstFileExW to avoid retrieving the
 short name

Use FindFirstFileExW with FindExInfoBasic to avoid forcing NTFS to look up
the short name.  Also switch to a larger (64K vs 4K) buffer using
FIND_FIRST_EX_LARGE_FETCH to minimize round trips to the kernel.

In a repo with ~200K files, this drops warm cache status times from 3.19
seconds to 2.67 seconds for a 16% savings.

Signed-off-by: Ben Peart <benpeart@microsoft.com>
---
 compat/win32/fscache.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 62a9034a523668..780a3e3cf71ff3 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -206,7 +206,8 @@ static struct fsentry *fsentry_create_list(const struct fsentry *dir,
 	pattern[wlen] = 0;
 
 	/* open find handle */
-	h = FindFirstFileW(pattern, &fdata);
+	h = FindFirstFileExW(pattern, FindExInfoBasic, &fdata, FindExSearchNameMatch,
+		NULL, FIND_FIRST_EX_LARGE_FETCH);
 	if (h == INVALID_HANDLE_VALUE) {
 		err = GetLastError();
 		*dir_not_found = 1; /* or empty directory */

From 112abacb06e5c79eeb4e43e4426132b4065101b5 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 20 Sep 2017 21:55:45 +0200
Subject: [PATCH 170/265] git-gui--askyesno (mingw): use Git for Windows' icon,
 if available

For additional GUI goodness.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 git-gui/git-gui--askyesno | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/git-gui/git-gui--askyesno b/git-gui/git-gui--askyesno
index 45b0260eff8145..c0c82e7cbd01d6 100755
--- a/git-gui/git-gui--askyesno
+++ b/git-gui/git-gui--askyesno
@@ -52,5 +52,17 @@ proc yes {} {
 	exit 0
 }
 
+if {$::tcl_platform(platform) eq {windows}} {
+	set icopath [file dirname [file normalize $argv0]]
+	if {[file tail $icopath] eq {git-core}} {
+		set icopath [file dirname $icopath]
+	}
+	set icopath [file dirname $icopath]
+	set icopath [file join $icopath share git git-for-windows.ico]
+	if {[file exists $icopath]} {
+		wm iconbitmap . -default $icopath
+	}
+}
+
 wm title . $title
 tk::PlaceWindow .

From 775cdefcf89cc7a3c6e83137f3f8fbe44804e38c Mon Sep 17 00:00:00 2001
From: Ben Peart <benpeart@microsoft.com>
Date: Thu, 1 Nov 2018 11:40:51 -0400
Subject: [PATCH 171/265] status: disable and free fscache at the end of the
 status command

At the end of the status command, disable and free the fscache so that we
don't leak the memory and so that we can dump the fscache statistics.

Signed-off-by: Ben Peart <benpeart@microsoft.com>
---
 builtin/commit.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/builtin/commit.c b/builtin/commit.c
index 8cda2fc90ffca2..7ffec56d7312f1 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1581,6 +1581,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 	wt_status_print(&s);
 	wt_status_collect_free_buffers(&s);
 
+	enable_fscache(0);
 	return 0;
 }
 

From 201f0f9a9f95eeff7fc9d5fb7dbbc570a7f08f23 Mon Sep 17 00:00:00 2001
From: "James J. Raden" <james.raden@gmail.com>
Date: Thu, 21 Jan 2016 12:07:47 -0500
Subject: [PATCH 172/265] gitk: make the "list references" default window width
 wider

When using remotes (with git-flow especially), the remote reference names
are almost always wordwrapped in the "list references" window because it's
somewhat narrow by default. It's possible to resize it with a mouse,
but it's annoying to have to do this every time, especially on Windows 10,
where the window border seems to be only one (1) pixel wide, thus making
the grabbing of the window border tricky.

Signed-off-by: James J. Raden <james.raden@gmail.com>
---
 gitk-git/gitk | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gitk-git/gitk b/gitk-git/gitk
index 27a255204ffef4..0a7182633357d4 100755
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -10043,7 +10043,7 @@ proc showrefs {} {
     text $top.list -background $bgcolor -foreground $fgcolor \
         -selectbackground $selectbgcolor -font mainfont \
         -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
-        -width 30 -height 20 -cursor $maincursor \
+        -width 60 -height 20 -cursor $maincursor \
         -spacing1 1 -spacing3 1 -state disabled
     $top.list tag configure highlight -background $selectbgcolor
     if {![lsearch -exact $bglist $top.list]} {

From b955478ced78365f7b679b923041c07951302e9a Mon Sep 17 00:00:00 2001
From: Ben Peart <benpeart@microsoft.com>
Date: Thu, 4 Oct 2018 18:10:21 -0400
Subject: [PATCH 173/265] fscache: add GIT_TEST_FSCACHE support

Add support to fscache to enable running the entire test suite with the
fscache enabled.

Signed-off-by: Ben Peart <benpeart@microsoft.com>
---
 compat/win32/fscache.c | 5 +++++
 t/README               | 3 +++
 2 files changed, 8 insertions(+)

diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 780a3e3cf71ff3..6c60a136dc14db 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -3,6 +3,7 @@
 #include "../win32.h"
 #include "fscache.h"
 #include "../../dir.h"
+#include "config.h"
 
 static int initialized;
 static volatile long enabled;
@@ -399,7 +400,11 @@ int fscache_enable(int enable)
 	int result;
 
 	if (!initialized) {
+		int fscache = git_env_bool("GIT_TEST_FSCACHE", -1);
+
 		/* allow the cache to be disabled entirely */
+		if (fscache != -1)
+			core_fscache = fscache;
 		if (!core_fscache)
 			return 0;
 
diff --git a/t/README b/t/README
index 9ffea1d3147802..bbe947d4917b36 100644
--- a/t/README
+++ b/t/README
@@ -472,6 +472,9 @@ a test and then fails then the whole test run will abort. This can help to make
 sure the expected tests are executed and not silently skipped when their
 dependency breaks or is simply not present in a new environment.
 
+GIT_TEST_FSCACHE=<boolean> exercises the uncommon fscache code path
+which adds a cache below mingw's lstat and dirent implementations.
+
 Naming Tests
 ------------
 

From b5c25613085801e2c518ddac0a60e449a117ea19 Mon Sep 17 00:00:00 2001
From: Ben Peart <benpeart@microsoft.com>
Date: Tue, 25 Sep 2018 16:28:16 -0400
Subject: [PATCH 174/265] fscache: add fscache hit statistics

Track fscache hits and misses for lstat and opendir requests.  Reporting of
statistics is done when the cache is disabled for the last time and freed
and is only reported if GIT_TRACE_FSCACHE is set.

Sample output is:

11:33:11.836428 compat/win32/fscache.c:433 fscache: lstat 3775, opendir 263, total requests/misses 4052/269

Signed-off-by: Ben Peart <benpeart@microsoft.com>
---
 compat/win32/fscache.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 6c60a136dc14db..2fc69b91de4438 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -9,6 +9,10 @@ static int initialized;
 static volatile long enabled;
 static struct hashmap map;
 static CRITICAL_SECTION mutex;
+static unsigned int lstat_requests;
+static unsigned int opendir_requests;
+static unsigned int fscache_requests;
+static unsigned int fscache_misses;
 static struct trace_key trace_fscache = TRACE_KEY_INIT(FSCACHE);
 
 /*
@@ -263,6 +267,8 @@ static void fscache_clear(void)
 {
 	hashmap_clear_and_free(&map, struct fsentry, ent);
 	hashmap_init(&map, (hashmap_cmp_fn)fsentry_cmp, NULL, 0);
+	lstat_requests = opendir_requests = 0;
+	fscache_misses = fscache_requests = 0;
 }
 
 /*
@@ -309,6 +315,7 @@ static struct fsentry *fscache_get(struct fsentry *key)
 	int dir_not_found;
 
 	EnterCriticalSection(&mutex);
+	fscache_requests++;
 	/* check if entry is in cache */
 	fse = fscache_get_wait(key);
 	if (fse) {
@@ -372,6 +379,7 @@ static struct fsentry *fscache_get(struct fsentry *key)
 	}
 
 	/* add directory listing to the cache */
+	fscache_misses++;
 	fscache_add(fse);
 
 	/* lookup file entry if requested (fse already points to directory) */
@@ -409,6 +417,8 @@ int fscache_enable(int enable)
 			return 0;
 
 		InitializeCriticalSection(&mutex);
+		lstat_requests = opendir_requests = 0;
+		fscache_misses = fscache_requests = 0;
 		hashmap_init(&map, (hashmap_cmp_fn) fsentry_cmp, NULL, 0);
 		initialized = 1;
 	}
@@ -425,6 +435,10 @@ int fscache_enable(int enable)
 		opendir = dirent_opendir;
 		lstat = mingw_lstat;
 		EnterCriticalSection(&mutex);
+		trace_printf_key(&trace_fscache, "fscache: lstat %u, opendir %u, "
+						 "total requests/misses %u/%u\n",
+				lstat_requests, opendir_requests,
+				fscache_requests, fscache_misses);
 		fscache_clear();
 		LeaveCriticalSection(&mutex);
 	}
@@ -457,6 +471,7 @@ int fscache_lstat(const char *filename, struct stat *st)
 	if (!fscache_enabled(filename))
 		return mingw_lstat(filename, st);
 
+	lstat_requests++;
 	/* split filename into path + name */
 	len = strlen(filename);
 	if (len && is_dir_sep(filename[len - 1]))
@@ -538,6 +553,7 @@ DIR *fscache_opendir(const char *dirname)
 	if (!fscache_enabled(dirname))
 		return dirent_opendir(dirname);
 
+	opendir_requests++;
 	/* prepare name (strip trailing '/', replace '.') */
 	len = strlen(dirname);
 	if ((len == 1 && dirname[0] == '.') ||

From f6ba4a602950ff10692f03b1ede83215e40c6c0f Mon Sep 17 00:00:00 2001
From: Ben Peart <benpeart@microsoft.com>
Date: Fri, 2 Nov 2018 11:19:10 -0400
Subject: [PATCH 175/265] fscache: teach fscache to use mempool

Now that the fscache is single threaded, take advantage of the mem_pool as
the allocator to significantly reduce the cost of allocations and frees.

With the reduced cost of free, in future patches, we can start freeing the
fscache at the end of commands instead of just leaking it.

Signed-off-by: Ben Peart <benpeart@microsoft.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/win32/fscache.c | 45 ++++++++++++++++++++++--------------------
 1 file changed, 24 insertions(+), 21 deletions(-)

diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 7fad622e1d1e43..0817dd3b48fbf5 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -4,6 +4,7 @@
 #include "fscache.h"
 #include "../../dir.h"
 #include "config.h"
+#include "../../mem-pool.h"
 
 static volatile long initialized;
 static DWORD dwTlsIndex;
@@ -18,6 +19,7 @@ static CRITICAL_SECTION mutex;
 struct fscache {
 	volatile long enabled;
 	struct hashmap map;
+	struct mem_pool mem_pool;
 	unsigned int lstat_requests;
 	unsigned int opendir_requests;
 	unsigned int fscache_requests;
@@ -122,11 +124,12 @@ static void fsentry_init(struct fsentry *fse, struct fsentry *list,
 /*
  * Allocate an fsentry structure on the heap.
  */
-static struct fsentry *fsentry_alloc(struct fsentry *list, const char *name,
+static struct fsentry *fsentry_alloc(struct fscache *cache, struct fsentry *list, const char *name,
 		size_t len)
 {
 	/* overallocate fsentry and copy the name to the end */
-	struct fsentry *fse = xmalloc(sizeof(struct fsentry) + len + 1);
+	struct fsentry *fse =
+		mem_pool_alloc(&cache->mem_pool, sizeof(*fse) + len + 1);
 	/* init the rest of the structure */
 	fsentry_init(fse, list, name, len);
 	fse->next = NULL;
@@ -146,27 +149,21 @@ inline static void fsentry_addref(struct fsentry *fse)
 }
 
 /*
- * Release the reference to an fsentry, frees the memory if its the last ref.
+ * Release the reference to an fsentry.
  */
 static void fsentry_release(struct fsentry *fse)
 {
 	if (fse->list)
 		fse = fse->list;
 
-	if (InterlockedDecrement(&(fse->u.refcnt)))
-		return;
-
-	while (fse) {
-		struct fsentry *next = fse->next;
-		free(fse);
-		fse = next;
-	}
+	InterlockedDecrement(&(fse->u.refcnt));
 }
 
 /*
  * Allocate and initialize an fsentry from a WIN32_FIND_DATA structure.
  */
-static struct fsentry *fseentry_create_entry(struct fsentry *list,
+static struct fsentry *fseentry_create_entry(struct fscache *cache,
+					     struct fsentry *list,
 					     const WIN32_FIND_DATAW *fdata)
 {
 	char buf[MAX_PATH * 3];
@@ -174,7 +171,7 @@ static struct fsentry *fseentry_create_entry(struct fsentry *list,
 	struct fsentry *fse;
 	len = xwcstoutf(buf, fdata->cFileName, ARRAY_SIZE(buf));
 
-	fse = fsentry_alloc(list, buf, len);
+	fse = fsentry_alloc(cache, list, buf, len);
 
 	fse->st_mode = file_attr_to_st_mode(fdata->dwFileAttributes);
 	fse->dirent.d_type = S_ISDIR(fse->st_mode) ? DT_DIR : DT_REG;
@@ -192,7 +189,7 @@ static struct fsentry *fseentry_create_entry(struct fsentry *list,
  * Dir should not contain trailing '/'. Use an empty string for the current
  * directory (not "."!).
  */
-static struct fsentry *fsentry_create_list(const struct fsentry *dir,
+static struct fsentry *fsentry_create_list(struct fscache *cache, const struct fsentry *dir,
 					   int *dir_not_found)
 {
 	wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */
@@ -231,14 +228,14 @@ static struct fsentry *fsentry_create_list(const struct fsentry *dir,
 	}
 
 	/* allocate object to hold directory listing */
-	list = fsentry_alloc(NULL, dir->dirent.d_name, dir->len);
+	list = fsentry_alloc(cache, NULL, dir->dirent.d_name, dir->len);
 	list->st_mode = S_IFDIR;
 	list->dirent.d_type = DT_DIR;
 
 	/* walk directory and build linked list of fsentry structures */
 	phead = &list->next;
 	do {
-		*phead = fseentry_create_entry(list, &fdata);
+		*phead = fseentry_create_entry(cache, list, &fdata);
 		phead = &(*phead)->next;
 	} while (FindNextFileW(h, &fdata));
 
@@ -250,7 +247,7 @@ static struct fsentry *fsentry_create_list(const struct fsentry *dir,
 	if (err == ERROR_NO_MORE_FILES)
 		return list;
 
-	/* otherwise free the list and return error */
+	/* otherwise release the list and return error */
 	fsentry_release(list);
 	errno = err_win_to_posix(err);
 	return NULL;
@@ -273,7 +270,9 @@ static void fscache_add(struct fscache *cache, struct fsentry *fse)
  */
 static void fscache_clear(struct fscache *cache)
 {
-	hashmap_clear_and_free(&cache->map, struct fsentry, ent);
+	mem_pool_discard(&cache->mem_pool, 0);
+	mem_pool_init(&cache->mem_pool, 0);
+	hashmap_clear(&cache->map);
 	hashmap_init(&cache->map, (hashmap_cmp_fn)fsentry_cmp, NULL, 0);
 	cache->lstat_requests = cache->opendir_requests = 0;
 	cache->fscache_misses = cache->fscache_requests = 0;
@@ -326,7 +325,7 @@ static struct fsentry *fscache_get(struct fscache *cache, struct fsentry *key)
 	}
 
 	/* create the directory listing */
-	fse = fsentry_create_list(key->list ? key->list : key, &dir_not_found);
+	fse = fsentry_create_list(cache, key->list ? key->list : key, &dir_not_found);
 
 	/* leave on error (errno set by fsentry_create_list) */
 	if (!fse) {
@@ -336,7 +335,7 @@ static struct fsentry *fscache_get(struct fscache *cache, struct fsentry *key)
 			 * empty, which for all practical matters is the same
 			 * thing as far as fscache is concerned).
 			 */
-			fse = fsentry_alloc(key->list->list,
+			fse = fsentry_alloc(cache, key->list->list,
 					    key->list->dirent.d_name,
 					    key->list->len);
 			fse->st_mode = 0;
@@ -415,6 +414,7 @@ int fscache_enable(size_t initial_size)
 		 * '4' was determined empirically by testing several repos
 		 */
 		hashmap_init(&cache->map, (hashmap_cmp_fn)fsentry_cmp, NULL, initial_size * 4);
+		mem_pool_init(&cache->mem_pool, 0);
 		if (!TlsSetValue(dwTlsIndex, cache))
 			BUG("TlsSetValue error");
 	}
@@ -446,7 +446,8 @@ void fscache_disable(void)
 			"total requests/misses %u/%u\n",
 			cache->lstat_requests, cache->opendir_requests,
 			cache->fscache_requests, cache->fscache_misses);
-		fscache_clear(cache);
+		mem_pool_discard(&cache->mem_pool, 0);
+		hashmap_clear(&cache->map);
 		free(cache);
 	}
 
@@ -631,6 +632,8 @@ void fscache_merge(struct fscache *dest)
 	while ((e = hashmap_iter_next(&iter)))
 		hashmap_add(&dest->map, e);
 
+	mem_pool_combine(&dest->mem_pool, &cache->mem_pool);
+
 	dest->lstat_requests += cache->lstat_requests;
 	dest->opendir_requests += cache->opendir_requests;
 	dest->fscache_requests += cache->fscache_requests;

From ab1c63e9ea7847ca2731022a231cc1c41462c817 Mon Sep 17 00:00:00 2001
From: Ben Peart <benpeart@microsoft.com>
Date: Fri, 16 Nov 2018 10:59:18 -0500
Subject: [PATCH 176/265] fscache: make fscache_enable() thread safe

The recent change to make fscache thread specific relied on fscache_enable()
being called first from the primary thread before being called in parallel
from worker threads.  Make that more robust and protect it with a critical
section to avoid any issues.

Helped-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Ben Peart <benpeart@microsoft.com>
---
 compat/mingw.c         |  4 ++++
 compat/win32/fscache.c | 23 +++++++++++++----------
 compat/win32/fscache.h |  2 ++
 3 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 8fd23a3686b3a1..15c1299cd5673a 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -11,6 +11,7 @@
 #include "dir.h"
 #define SECURITY_WIN32
 #include <sspi.h>
+#include "win32/fscache.h"
 
 #define HCAST(type, handle) ((type)(intptr_t)handle)
 
@@ -3311,6 +3312,9 @@ int wmain(int argc, const wchar_t **wargv)
 	/* initialize critical section for waitpid pinfo_t list */
 	InitializeCriticalSection(&pinfo_cs);
 
+	/* initialize critical section for fscache */
+	InitializeCriticalSection(&fscache_cs);
+
 	/* set up default file mode and file modes for stdin/out/err */
 	_fmode = _O_BINARY;
 	_setmode(_fileno(stdin), _O_BINARY);
diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 0817dd3b48fbf5..75dcd70b803334 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -8,7 +8,7 @@
 
 static volatile long initialized;
 static DWORD dwTlsIndex;
-static CRITICAL_SECTION mutex;
+CRITICAL_SECTION fscache_cs;
 
 /*
  * Store one fscache per thread to avoid thread contention and locking.
@@ -386,12 +386,12 @@ int fscache_enable(size_t initial_size)
 	 * opendir and lstat function pointers are redirected if
 	 * any threads are using the fscache.
 	 */
+	EnterCriticalSection(&fscache_cs);
 	if (!initialized) {
-		InitializeCriticalSection(&mutex);
 		if (!dwTlsIndex) {
 			dwTlsIndex = TlsAlloc();
 			if (dwTlsIndex == TLS_OUT_OF_INDEXES) {
-				LeaveCriticalSection(&mutex);
+				LeaveCriticalSection(&fscache_cs);
 				return 0;
 			}
 		}
@@ -400,12 +400,13 @@ int fscache_enable(size_t initial_size)
 		opendir = fscache_opendir;
 		lstat = fscache_lstat;
 	}
-	InterlockedIncrement(&initialized);
+	initialized++;
+	LeaveCriticalSection(&fscache_cs);
 
 	/* refcount the thread specific initialization */
 	cache = fscache_getcache();
 	if (cache) {
-		InterlockedIncrement(&cache->enabled);
+		cache->enabled++;
 	} else {
 		cache = (struct fscache *)xcalloc(1, sizeof(*cache));
 		cache->enabled = 1;
@@ -439,7 +440,7 @@ void fscache_disable(void)
 		BUG("fscache_disable() called on a thread where fscache has not been initialized");
 	if (!cache->enabled)
 		BUG("fscache_disable() called on an fscache that is already disabled");
-	InterlockedDecrement(&cache->enabled);
+	cache->enabled--;
 	if (!cache->enabled) {
 		TlsSetValue(dwTlsIndex, NULL);
 		trace_printf_key(&trace_fscache, "fscache_disable: lstat %u, opendir %u, "
@@ -452,12 +453,14 @@ void fscache_disable(void)
 	}
 
 	/* update the global fscache initialization */
-	InterlockedDecrement(&initialized);
+	EnterCriticalSection(&fscache_cs);
+	initialized--;
 	if (!initialized) {
 		/* reset opendir and lstat to the original implementations */
 		opendir = dirent_opendir;
 		lstat = mingw_lstat;
 	}
+	LeaveCriticalSection(&fscache_cs);
 
 	trace_printf_key(&trace_fscache, "fscache: disable\n");
 	return;
@@ -626,7 +629,7 @@ void fscache_merge(struct fscache *dest)
 	 * isn't being used so the critical section only needs to prevent
 	 * the the child threads from stomping on each other.
 	 */
-	EnterCriticalSection(&mutex);
+	EnterCriticalSection(&fscache_cs);
 
 	hashmap_iter_init(&cache->map, &iter);
 	while ((e = hashmap_iter_next(&iter)))
@@ -638,9 +641,9 @@ void fscache_merge(struct fscache *dest)
 	dest->opendir_requests += cache->opendir_requests;
 	dest->fscache_requests += cache->fscache_requests;
 	dest->fscache_misses += cache->fscache_misses;
-	LeaveCriticalSection(&mutex);
+	initialized--;
+	LeaveCriticalSection(&fscache_cs);
 
 	free(cache);
 
-	InterlockedDecrement(&initialized);
 }
diff --git a/compat/win32/fscache.h b/compat/win32/fscache.h
index 2eb8bf3f5cfee8..042b247a542554 100644
--- a/compat/win32/fscache.h
+++ b/compat/win32/fscache.h
@@ -6,6 +6,8 @@
  * for each thread where caching is desired.
  */
 
+extern CRITICAL_SECTION fscache_cs;
+
 int fscache_enable(size_t initial_size);
 #define enable_fscache(initial_size) fscache_enable(initial_size)
 

From f4cce13a21407be7db287cd3d492c12b680c6d65 Mon Sep 17 00:00:00 2001
From: Ben Peart <benpeart@microsoft.com>
Date: Thu, 15 Nov 2018 14:15:40 -0500
Subject: [PATCH 177/265] fscache: teach fscache to use NtQueryDirectoryFile

Using FindFirstFileExW() requires the OS to allocate a 64K buffer for each
directory and then free it when we call FindClose().  Update fscache to call
the underlying kernel API NtQueryDirectoryFile so that we can do the buffer
management ourselves.  That allows us to allocate a single buffer for the
lifetime of the cache and reuse it for each directory.

This change improves performance of 'git status' by 18% in a repo with ~200K
files and 30k folders.

Documentation for NtQueryDirectoryFile can be found at:

https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-ntquerydirectoryfile
https://docs.microsoft.com/en-us/windows/desktop/FileIO/file-attribute-constants
https://docs.microsoft.com/en-us/windows/desktop/fileio/reparse-point-tags

To determine if the specified directory is a symbolic link, inspect the
FileAttributes member to see if the FILE_ATTRIBUTE_REPARSE_POINT flag is
set. If so, EaSize will contain the reparse tag (this is a so far
undocumented feature, but confirmed by the NTFS developers). To
determine if the reparse point is a symbolic link (and not some other
form of reparse point), test whether the tag value equals the value
IO_REPARSE_TAG_SYMLINK.

The NtQueryDirectoryFile() call works best (and on Windows 8.1 and
earlier, it works *only*) with buffer sizes up to 64kB. Which is 32k
wide characters, so let's use that as our buffer size.

Signed-off-by: Ben Peart <benpeart@microsoft.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/win32/fscache.c | 123 ++++++++++++++++++++++++++++----------
 compat/win32/ntifs.h   | 131 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 224 insertions(+), 30 deletions(-)
 create mode 100644 compat/win32/ntifs.h

diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 75dcd70b803334..760f8b80d65f2b 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -5,6 +5,7 @@
 #include "../../dir.h"
 #include "config.h"
 #include "../../mem-pool.h"
+#include "ntifs.h"
 
 static volatile long initialized;
 static DWORD dwTlsIndex;
@@ -24,6 +25,13 @@ struct fscache {
 	unsigned int opendir_requests;
 	unsigned int fscache_requests;
 	unsigned int fscache_misses;
+	/*
+	 * 32k wide characters translates to 64kB, which is the maximum that
+	 * Windows 8.1 and earlier can handle. On network drives, not only
+	 * the client's Windows version matters, but also the server's,
+	 * therefore we need to keep this to 64kB.
+	 */
+	WCHAR buffer[32 * 1024];
 };
 static struct trace_key trace_fscache = TRACE_KEY_INIT(FSCACHE);
 
@@ -159,27 +167,44 @@ static void fsentry_release(struct fsentry *fse)
 	InterlockedDecrement(&(fse->u.refcnt));
 }
 
+static int xwcstoutfn(char *utf, int utflen, const wchar_t *wcs, int wcslen)
+{
+	if (!wcs || !utf || utflen < 1) {
+		errno = EINVAL;
+		return -1;
+	}
+	utflen = WideCharToMultiByte(CP_UTF8, 0, wcs, wcslen, utf, utflen, NULL, NULL);
+	if (utflen)
+		return utflen;
+	errno = ERANGE;
+	return -1;
+}
+
 /*
- * Allocate and initialize an fsentry from a WIN32_FIND_DATA structure.
+ * Allocate and initialize an fsentry from a FILE_FULL_DIR_INFORMATION structure.
  */
 static struct fsentry *fseentry_create_entry(struct fscache *cache,
 					     struct fsentry *list,
-					     const WIN32_FIND_DATAW *fdata)
+					     PFILE_FULL_DIR_INFORMATION fdata)
 {
 	char buf[MAX_PATH * 3];
 	int len;
 	struct fsentry *fse;
-	len = xwcstoutf(buf, fdata->cFileName, ARRAY_SIZE(buf));
+
+	len = xwcstoutfn(buf, ARRAY_SIZE(buf), fdata->FileName, fdata->FileNameLength / sizeof(wchar_t));
 
 	fse = fsentry_alloc(cache, list, buf, len);
 
-	fse->st_mode = file_attr_to_st_mode(fdata->dwFileAttributes);
+	fse->st_mode = file_attr_to_st_mode(fdata->FileAttributes);
 	fse->dirent.d_type = S_ISDIR(fse->st_mode) ? DT_DIR : DT_REG;
-	fse->u.s.st_size = (((off64_t) (fdata->nFileSizeHigh)) << 32)
-			| fdata->nFileSizeLow;
-	filetime_to_timespec(&(fdata->ftLastAccessTime), &(fse->u.s.st_atim));
-	filetime_to_timespec(&(fdata->ftLastWriteTime), &(fse->u.s.st_mtim));
-	filetime_to_timespec(&(fdata->ftCreationTime), &(fse->u.s.st_ctim));
+	fse->u.s.st_size = fdata->EndOfFile.LowPart |
+		(((off_t)fdata->EndOfFile.HighPart) << 32);
+	filetime_to_timespec((FILETIME *)&(fdata->LastAccessTime),
+			     &(fse->u.s.st_atim));
+	filetime_to_timespec((FILETIME *)&(fdata->LastWriteTime),
+			     &(fse->u.s.st_mtim));
+	filetime_to_timespec((FILETIME *)&(fdata->CreationTime),
+			     &(fse->u.s.st_ctim));
 
 	return fse;
 }
@@ -192,8 +217,10 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache,
 static struct fsentry *fsentry_create_list(struct fscache *cache, const struct fsentry *dir,
 					   int *dir_not_found)
 {
-	wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */
-	WIN32_FIND_DATAW fdata;
+	wchar_t pattern[MAX_PATH];
+	NTSTATUS status;
+	IO_STATUS_BLOCK iosb;
+	PFILE_FULL_DIR_INFORMATION di;
 	HANDLE h;
 	int wlen;
 	struct fsentry *list, **phead;
@@ -209,15 +236,18 @@ static struct fsentry *fsentry_create_list(struct fscache *cache, const struct f
 		return NULL;
 	}
 
-	/* append optional '/' and wildcard '*' */
-	if (wlen)
-		pattern[wlen++] = '/';
-	pattern[wlen++] = '*';
-	pattern[wlen] = 0;
+	/* handle CWD */
+	if (!wlen) {
+		wlen = GetCurrentDirectoryW(ARRAY_SIZE(pattern), pattern);
+		if (!wlen || wlen >= ARRAY_SIZE(pattern)) {
+			errno = wlen ? ENAMETOOLONG : err_win_to_posix(GetLastError());
+			return NULL;
+		}
+	}
 
-	/* open find handle */
-	h = FindFirstFileExW(pattern, FindExInfoBasic, &fdata, FindExSearchNameMatch,
-		NULL, FIND_FIRST_EX_LARGE_FETCH);
+	h = CreateFileW(pattern, FILE_LIST_DIRECTORY,
+		FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+		NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
 	if (h == INVALID_HANDLE_VALUE) {
 		err = GetLastError();
 		*dir_not_found = 1; /* or empty directory */
@@ -234,22 +264,55 @@ static struct fsentry *fsentry_create_list(struct fscache *cache, const struct f
 
 	/* walk directory and build linked list of fsentry structures */
 	phead = &list->next;
-	do {
-		*phead = fseentry_create_entry(cache, list, &fdata);
+	status = NtQueryDirectoryFile(h, NULL, 0, 0, &iosb, cache->buffer,
+		sizeof(cache->buffer), FileFullDirectoryInformation, FALSE, NULL, FALSE);
+	if (!NT_SUCCESS(status)) {
+		/*
+		 * NtQueryDirectoryFile returns STATUS_INVALID_PARAMETER when
+		 * asked to enumerate an invalid directory (ie it is a file
+		 * instead of a directory).  Verify that is the actual cause
+		 * of the error.
+		*/
+		if (status == STATUS_INVALID_PARAMETER) {
+			DWORD attributes = GetFileAttributesW(pattern);
+			if (!(attributes & FILE_ATTRIBUTE_DIRECTORY))
+				status = ERROR_DIRECTORY;
+		}
+		goto Error;
+	}
+	di = (PFILE_FULL_DIR_INFORMATION)(cache->buffer);
+	for (;;) {
+
+		*phead = fseentry_create_entry(cache, list, di);
 		phead = &(*phead)->next;
-	} while (FindNextFileW(h, &fdata));
 
-	/* remember result of last FindNextFile, then close find handle */
-	err = GetLastError();
-	FindClose(h);
+		/* If there is no offset in the entry, the buffer has been exhausted. */
+		if (di->NextEntryOffset == 0) {
+			status = NtQueryDirectoryFile(h, NULL, 0, 0, &iosb, cache->buffer,
+				sizeof(cache->buffer), FileFullDirectoryInformation, FALSE, NULL, FALSE);
+			if (!NT_SUCCESS(status)) {
+				if (status == STATUS_NO_MORE_FILES)
+					break;
+				goto Error;
+			}
+
+			di = (PFILE_FULL_DIR_INFORMATION)(cache->buffer);
+			continue;
+		}
+
+		/* Advance to the next entry. */
+		di = (PFILE_FULL_DIR_INFORMATION)(((PUCHAR)di) + di->NextEntryOffset);
+	}
 
-	/* return the list if we've got all the files */
-	if (err == ERROR_NO_MORE_FILES)
-		return list;
+	CloseHandle(h);
+	return list;
 
-	/* otherwise release the list and return error */
+Error:
+	trace_printf_key(&trace_fscache,
+			 "fscache: status(%ld) unable to query directory "
+			 "contents '%s'\n", status, dir->dirent.d_name);
+	CloseHandle(h);
 	fsentry_release(list);
-	errno = err_win_to_posix(err);
 	return NULL;
 }
 
diff --git a/compat/win32/ntifs.h b/compat/win32/ntifs.h
new file mode 100644
index 00000000000000..64ed792c52f352
--- /dev/null
+++ b/compat/win32/ntifs.h
@@ -0,0 +1,131 @@
+#ifndef _NTIFS_
+#define _NTIFS_
+
+/*
+ * Copy necessary structures and definitions out of the Windows DDK
+ * to enable calling NtQueryDirectoryFile()
+ */
+
+typedef _Return_type_success_(return >= 0) LONG NTSTATUS;
+#define NT_SUCCESS(Status)  (((NTSTATUS)(Status)) >= 0)
+
+#if !defined(_NTSECAPI_) && !defined(_WINTERNL_) && \
+	!defined(__UNICODE_STRING_DEFINED)
+#define __UNICODE_STRING_DEFINED
+typedef struct _UNICODE_STRING {
+	USHORT Length;
+	USHORT MaximumLength;
+	PWSTR Buffer;
+} UNICODE_STRING;
+typedef UNICODE_STRING *PUNICODE_STRING;
+typedef const UNICODE_STRING *PCUNICODE_STRING;
+#endif /* !_NTSECAPI_ && !_WINTERNL_ && !__UNICODE_STRING_DEFINED */
+
+typedef enum _FILE_INFORMATION_CLASS {
+	FileDirectoryInformation = 1,
+	FileFullDirectoryInformation,
+	FileBothDirectoryInformation,
+	FileBasicInformation,
+	FileStandardInformation,
+	FileInternalInformation,
+	FileEaInformation,
+	FileAccessInformation,
+	FileNameInformation,
+	FileRenameInformation,
+	FileLinkInformation,
+	FileNamesInformation,
+	FileDispositionInformation,
+	FilePositionInformation,
+	FileFullEaInformation,
+	FileModeInformation,
+	FileAlignmentInformation,
+	FileAllInformation,
+	FileAllocationInformation,
+	FileEndOfFileInformation,
+	FileAlternateNameInformation,
+	FileStreamInformation,
+	FilePipeInformation,
+	FilePipeLocalInformation,
+	FilePipeRemoteInformation,
+	FileMailslotQueryInformation,
+	FileMailslotSetInformation,
+	FileCompressionInformation,
+	FileObjectIdInformation,
+	FileCompletionInformation,
+	FileMoveClusterInformation,
+	FileQuotaInformation,
+	FileReparsePointInformation,
+	FileNetworkOpenInformation,
+	FileAttributeTagInformation,
+	FileTrackingInformation,
+	FileIdBothDirectoryInformation,
+	FileIdFullDirectoryInformation,
+	FileValidDataLengthInformation,
+	FileShortNameInformation,
+	FileIoCompletionNotificationInformation,
+	FileIoStatusBlockRangeInformation,
+	FileIoPriorityHintInformation,
+	FileSfioReserveInformation,
+	FileSfioVolumeInformation,
+	FileHardLinkInformation,
+	FileProcessIdsUsingFileInformation,
+	FileNormalizedNameInformation,
+	FileNetworkPhysicalNameInformation,
+	FileIdGlobalTxDirectoryInformation,
+	FileIsRemoteDeviceInformation,
+	FileAttributeCacheInformation,
+	FileNumaNodeInformation,
+	FileStandardLinkInformation,
+	FileRemoteProtocolInformation,
+	FileMaximumInformation
+} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;
+
+typedef struct _FILE_FULL_DIR_INFORMATION {
+	ULONG NextEntryOffset;
+	ULONG FileIndex;
+	LARGE_INTEGER CreationTime;
+	LARGE_INTEGER LastAccessTime;
+	LARGE_INTEGER LastWriteTime;
+	LARGE_INTEGER ChangeTime;
+	LARGE_INTEGER EndOfFile;
+	LARGE_INTEGER AllocationSize;
+	ULONG FileAttributes;
+	ULONG FileNameLength;
+	ULONG EaSize;
+	WCHAR FileName[1];
+} FILE_FULL_DIR_INFORMATION, *PFILE_FULL_DIR_INFORMATION;
+
+typedef struct _IO_STATUS_BLOCK {
+	union {
+		NTSTATUS Status;
+		PVOID Pointer;
+	} u;
+	ULONG_PTR Information;
+} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
+
+typedef VOID
+(NTAPI *PIO_APC_ROUTINE)(
+	IN PVOID ApcContext,
+	IN PIO_STATUS_BLOCK IoStatusBlock,
+	IN ULONG Reserved);
+
+NTSYSCALLAPI
+NTSTATUS
+NTAPI
+NtQueryDirectoryFile(
+	_In_ HANDLE FileHandle,
+	_In_opt_ HANDLE Event,
+	_In_opt_ PIO_APC_ROUTINE ApcRoutine,
+	_In_opt_ PVOID ApcContext,
+	_Out_ PIO_STATUS_BLOCK IoStatusBlock,
+	_Out_writes_bytes_(Length) PVOID FileInformation,
+	_In_ ULONG Length,
+	_In_ FILE_INFORMATION_CLASS FileInformationClass,
+	_In_ BOOLEAN ReturnSingleEntry,
+	_In_opt_ PUNICODE_STRING FileName,
+	_In_ BOOLEAN RestartScan
+);
+
+#define STATUS_NO_MORE_FILES             ((NTSTATUS)0x80000006L)
+
+#endif

From 270140ed5a1671d024b9437d2a07af8ae53f308f Mon Sep 17 00:00:00 2001
From: Derrick Stolee <dstolee@microsoft.com>
Date: Wed, 12 Jun 2019 00:58:49 +0000
Subject: [PATCH 178/265] unpack-trees: enable fscache for sparse-checkout

When updating the skip-worktree bits in the index to align with new
values in a sparse-checkout file, Git scans the entire working
directory with lstat() calls. In a sparse-checkout, many of these
lstat() calls are for paths that do not exist.

Enable the fscache feature during this scan. Since enable_fscache()
calls nest, the disable_fscache() method decrements a counter and
would only clear the cache if that counter reaches zero.

In a local test of a repo with ~2.2 million paths, updating the index
with git read-tree -m -u HEAD with a sparse-checkout file containing
only /.gitattributes improved from 2-3 minutes to ~6 seconds.

Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 unpack-trees.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/unpack-trees.c b/unpack-trees.c
index bbbcf83fa1c981..bb4a78a73c164d 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -1709,7 +1709,9 @@ static void mark_new_skip_worktree(struct pattern_list *pl,
 	 * 2. Widen worktree according to sparse-checkout file.
 	 * Matched entries will have skip_wt_flag cleared (i.e. "in")
 	 */
+	enable_fscache(istate->cache_nr);
 	clear_ce_flags(istate, select_flag, skip_wt_flag, pl, show_progress);
+	disable_fscache();
 }
 
 static void populate_from_existing_patterns(struct unpack_trees_options *o,

From bf37d21fe592c2227c9e9288c98f7c731fdae179 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 11 Dec 2018 12:17:49 +0100
Subject: [PATCH 179/265] clean: make use of FSCache

The `git clean` command needs to enumerate plenty of files and
directories, and can therefore benefit from the FSCache.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/clean.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/builtin/clean.c b/builtin/clean.c
index cb55e8956b0747..ae4c6f37bab701 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -1041,6 +1041,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 
 	if (read_cache() < 0)
 		die(_("index file corrupt"));
+	enable_fscache(active_nr);
 
 	pl = add_pattern_list(&dir, EXC_CMDL, "--exclude option");
 	for (i = 0; i < exclude_list.nr; i++)
@@ -1115,6 +1116,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 		}
 	}
 
+	disable_fscache();
 	strbuf_release(&abs_path);
 	strbuf_release(&buf);
 	string_list_clear(&del_list, 0);

From acb7a379b906df02a25f691ba976e33fde1c4ad5 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Mon, 11 May 2015 19:54:23 +0200
Subject: [PATCH 180/265] strbuf_readlink: don't call readlink twice if hint is
 the exact link size

strbuf_readlink() calls readlink() twice if the hint argument specifies the
exact size of the link target (e.g. by passing stat.st_size as returned by
lstat()). This is necessary because 'readlink(..., hint) == hint' could
mean that the buffer was too small.

Use hint + 1 as buffer size to prevent this.

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 strbuf.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/strbuf.c b/strbuf.c
index dd9eb85527ab5a..2b8c8b2c660ead 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -571,12 +571,12 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
 	while (hint < STRBUF_MAXLINK) {
 		ssize_t len;
 
-		strbuf_grow(sb, hint);
-		len = readlink(path, sb->buf, hint);
+		strbuf_grow(sb, hint + 1);
+		len = readlink(path, sb->buf, hint + 1);
 		if (len < 0) {
 			if (errno != ERANGE)
 				break;
-		} else if (len < hint) {
+		} else if (len <= hint) {
 			strbuf_setlen(sb, len);
 			return 0;
 		}

From 55588f2b4312d8963b0c2b2643c47a0abe27b0b2 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Mon, 11 May 2015 22:15:40 +0200
Subject: [PATCH 181/265] strbuf_readlink: support link targets that exceed
 PATH_MAX

strbuf_readlink() refuses to read link targets that exceed PATH_MAX (even
if a sufficient size was specified by the caller).

As some platforms support longer paths, remove this restriction (similar
to strbuf_getcwd()).

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 strbuf.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/strbuf.c b/strbuf.c
index 2b8c8b2c660ead..6fc4f2926d75f6 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -559,8 +559,6 @@ ssize_t strbuf_write(struct strbuf *sb, FILE *f)
 	return sb->len ? fwrite(sb->buf, 1, sb->len, f) : 0;
 }
 
-#define STRBUF_MAXLINK (2*PATH_MAX)
-
 int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
 {
 	size_t oldalloc = sb->alloc;
@@ -568,7 +566,7 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
 	if (hint < 32)
 		hint = 32;
 
-	while (hint < STRBUF_MAXLINK) {
+	for (;;) {
 		ssize_t len;
 
 		strbuf_grow(sb, hint + 1);

From 46eba2f0182df92fed069fd7e6d36fb6b5e7a26d Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Mon, 11 May 2015 19:58:14 +0200
Subject: [PATCH 182/265] lockfile.c: use is_dir_sep() instead of hardcoded '/'
 checks

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 lockfile.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lockfile.c b/lockfile.c
index cc9a4b84283be3..62e4c9a14f0060 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -17,14 +17,14 @@ static void trim_last_path_component(struct strbuf *path)
 	int i = path->len;
 
 	/* back up past trailing slashes, if any */
-	while (i && path->buf[i - 1] == '/')
+	while (i && is_dir_sep(path->buf[i - 1]))
 		i--;
 
 	/*
 	 * then go backwards until a slash, or the beginning of the
 	 * string
 	 */
-	while (i && path->buf[i - 1] != '/')
+	while (i && !is_dir_sep(path->buf[i - 1]))
 		i--;
 
 	strbuf_setlen(path, i);

From 844dd5886d122af40f00f56369a3cbdc740e8d91 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Tue, 12 May 2015 11:09:01 +0200
Subject: [PATCH 183/265] Win32: don't call GetFileAttributes twice in
 mingw_lstat()

GetFileAttributes cannot handle paths with trailing dir separator. The
current [l]stat implementation calls GetFileAttributes twice if the path
has trailing slashes (first with the original path passed to [l]stat, and
and a second time with a path copy with trailing '/' removed).

With Unicode conversion, we get the length of the path for free and also
have a (wide char) buffer that can be modified.

Remove trailing directory separators before calling the Win32 API.

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 compat/mingw.c | 48 ++++++++++++------------------------------------
 1 file changed, 12 insertions(+), 36 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index f1f7fd90ba00fd..953dd3e1e5a443 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -799,8 +799,17 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
 {
 	WIN32_FILE_ATTRIBUTE_DATA fdata;
 	wchar_t wfilename[MAX_LONG_PATH];
-	if (xutftowcs_long_path(wfilename, file_name) < 0)
+	int wlen = xutftowcs_long_path(wfilename, file_name);
+	if (wlen < 0)
+		return -1;
+
+	/* strip trailing '/', or GetFileAttributes will fail */
+	while (wlen && is_dir_sep(wfilename[wlen - 1]))
+		wfilename[--wlen] = 0;
+	if (!wlen) {
+		errno = ENOENT;
 		return -1;
+	}
 
 	if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
 		buf->st_ino = 0;
@@ -861,39 +870,6 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
 	return -1;
 }
 
-/* We provide our own lstat/fstat functions, since the provided
- * lstat/fstat functions are so slow. These stat functions are
- * tailored for Git's usage (read: fast), and are not meant to be
- * complete. Note that Git stat()s are redirected to mingw_lstat()
- * too, since Windows doesn't really handle symlinks that well.
- */
-static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
-{
-	int namelen;
-	char alt_name[MAX_LONG_PATH];
-
-	if (!do_lstat(follow, file_name, buf))
-		return 0;
-
-	/* if file_name ended in a '/', Windows returned ENOENT;
-	 * try again without trailing slashes
-	 */
-	if (errno != ENOENT)
-		return -1;
-
-	namelen = strlen(file_name);
-	if (namelen && file_name[namelen-1] != '/')
-		return -1;
-	while (namelen && file_name[namelen-1] == '/')
-		--namelen;
-	if (!namelen || namelen >= MAX_LONG_PATH)
-		return -1;
-
-	memcpy(alt_name, file_name, namelen);
-	alt_name[namelen] = 0;
-	return do_lstat(follow, alt_name, buf);
-}
-
 int (*lstat)(const char *file_name, struct stat *buf) = mingw_lstat;
 
 static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
@@ -921,11 +897,11 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
 
 int mingw_lstat(const char *file_name, struct stat *buf)
 {
-	return do_stat_internal(0, file_name, buf);
+	return do_lstat(0, file_name, buf);
 }
 int mingw_stat(const char *file_name, struct stat *buf)
 {
-	return do_stat_internal(1, file_name, buf);
+	return do_lstat(1, file_name, buf);
 }
 
 int mingw_fstat(int fd, struct stat *buf)

From 3efc5ab42f3d3a9d3f32698a51826d09b8d79edd Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sat, 16 May 2015 01:18:14 +0200
Subject: [PATCH 184/265] Win32: implement stat() with symlink support

With respect to symlinks, the current stat() implementation is almost the
same as lstat(): except for the file type (st_mode & S_IFMT), it returns
information about the link rather than the target.

Implement stat by opening the file with as little permissions as possible
and calling GetFileInformationByHandle on it. This way, all link resoltion
is handled by the Windows file system layer.

If symlinks are disabled, use lstat() as before, but fail with ELOOP if a
symlink would have to be resolved.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 953dd3e1e5a443..2ca4b72c4e5291 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -899,9 +899,26 @@ int mingw_lstat(const char *file_name, struct stat *buf)
 {
 	return do_lstat(0, file_name, buf);
 }
+
 int mingw_stat(const char *file_name, struct stat *buf)
 {
-	return do_lstat(1, file_name, buf);
+	wchar_t wfile_name[MAX_LONG_PATH];
+	HANDLE hnd;
+	int result;
+
+	/* open the file and let Windows resolve the links */
+	if (xutftowcs_long_path(wfile_name, file_name) < 0)
+		return -1;
+	hnd = CreateFileW(wfile_name, 0,
+			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+			OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+	if (hnd == INVALID_HANDLE_VALUE) {
+		errno = err_win_to_posix(GetLastError());
+		return -1;
+	}
+	result = get_file_info_by_handle(hnd, buf);
+	CloseHandle(hnd);
+	return result;
 }
 
 int mingw_fstat(int fd, struct stat *buf)

From a78c837ddc6eb40a410f3410ad78be545bef2485 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Tue, 12 May 2015 00:58:39 +0200
Subject: [PATCH 185/265] Win32: remove separate do_lstat() function

With the new mingw_stat() implementation, do_lstat() is only called from
mingw_lstat() (with follow == 0). Remove the extra function and the old
mingw_stat()-specific (follow == 1) logic.

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 compat/mingw.c | 22 ++--------------------
 1 file changed, 2 insertions(+), 20 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 2ca4b72c4e5291..1d80dc20aff401 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -788,14 +788,7 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
 	return 1;
 }
 
-/* We keep the do_lstat code in a separate function to avoid recursion.
- * When a path ends with a slash, the stat will fail with ENOENT. In
- * this case, we strip the trailing slashes and stat again.
- *
- * If follow is true then act like stat() and report on the link
- * target. Otherwise report on the link itself.
- */
-static int do_lstat(int follow, const char *file_name, struct stat *buf)
+int mingw_lstat(const char *file_name, struct stat *buf)
 {
 	WIN32_FILE_ATTRIBUTE_DATA fdata;
 	wchar_t wfilename[MAX_LONG_PATH];
@@ -829,13 +822,7 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
 			if (handle != INVALID_HANDLE_VALUE) {
 				if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
 						(findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) {
-					if (follow) {
-						char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
-						buf->st_size = readlink(file_name, buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
-					} else {
-						buf->st_mode = S_IFLNK;
-					}
-					buf->st_mode |= S_IREAD;
+					buf->st_mode = S_IFLNK | S_IREAD;
 					if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
 						buf->st_mode |= S_IWRITE;
 				}
@@ -895,11 +882,6 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
 	return 0;
 }
 
-int mingw_lstat(const char *file_name, struct stat *buf)
-{
-	return do_lstat(0, file_name, buf);
-}
-
 int mingw_stat(const char *file_name, struct stat *buf)
 {
 	wchar_t wfile_name[MAX_LONG_PATH];

From dfdb11cca72331ad89b72df50932fb790e324d07 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sun, 24 May 2015 00:17:56 +0200
Subject: [PATCH 186/265] Win32: let mingw_lstat() error early upon problems
 with reparse points

When obtaining lstat information for reparse points, we need to call
FindFirstFile() in addition to GetFileInformationEx() to obtain the type
of the reparse point (symlink, mount point etc.). However, currently there
is no error handling whatsoever if FindFirstFile() fails.

Call FindFirstFile() before modifying the stat *buf output parameter and
error out if the call fails.

Note: The FindFirstFile() return value includes all the data that we get
from GetFileAttributesEx(), so we could replace GetFileAttributesEx() with
FindFirstFile(). We don't do that because GetFileAttributesEx() is about
twice as fast for single files. I.e. we only pay the extra cost of calling
FindFirstFile() in the rare case that we encounter a reparse point.

Note: The indentation of the remaining reparse point code will be fixed in
the next patch.

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 compat/mingw.c | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 1d80dc20aff401..918708784936b3 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -791,6 +791,7 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
 int mingw_lstat(const char *file_name, struct stat *buf)
 {
 	WIN32_FILE_ATTRIBUTE_DATA fdata;
+	WIN32_FIND_DATAW findbuf = { 0 };
 	wchar_t wfilename[MAX_LONG_PATH];
 	int wlen = xutftowcs_long_path(wfilename, file_name);
 	if (wlen < 0)
@@ -805,6 +806,13 @@ int mingw_lstat(const char *file_name, struct stat *buf)
 	}
 
 	if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
+		/* for reparse points, use FindFirstFile to get the reparse tag */
+		if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+			HANDLE handle = FindFirstFileW(wfilename, &findbuf);
+			if (handle == INVALID_HANDLE_VALUE)
+				goto error;
+			FindClose(handle);
+		}
 		buf->st_ino = 0;
 		buf->st_gid = 0;
 		buf->st_uid = 0;
@@ -817,20 +825,16 @@ int mingw_lstat(const char *file_name, struct stat *buf)
 		filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
 		filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
 		if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
-			WIN32_FIND_DATAW findbuf;
-			HANDLE handle = FindFirstFileW(wfilename, &findbuf);
-			if (handle != INVALID_HANDLE_VALUE) {
 				if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
 						(findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) {
 					buf->st_mode = S_IFLNK | S_IREAD;
 					if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
 						buf->st_mode |= S_IWRITE;
 				}
-				FindClose(handle);
-			}
 		}
 		return 0;
 	}
+error:
 	switch (GetLastError()) {
 	case ERROR_ACCESS_DENIED:
 	case ERROR_SHARING_VIOLATION:

From f7e04528eba1eabf1e68a8ee0d27579d2546f64a Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Tue, 10 Jan 2017 23:21:56 +0100
Subject: [PATCH 187/265] mingw: teach fscache and dirent about symlinks

Move S_IFLNK detection to file_attr_to_st_mode() and reuse it in fscache.

Implement DT_LNK detection in dirent.c and the fscache readdir version.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c         | 13 +++----------
 compat/win32.h         |  6 ++++--
 compat/win32/dirent.c  |  5 ++++-
 compat/win32/fscache.c | 11 +++++++----
 4 files changed, 18 insertions(+), 17 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 918708784936b3..1bf1c42d57e920 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -817,21 +817,14 @@ int mingw_lstat(const char *file_name, struct stat *buf)
 		buf->st_gid = 0;
 		buf->st_uid = 0;
 		buf->st_nlink = 1;
-		buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
+		buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
+				findbuf.dwReserved0);
 		buf->st_size = fdata.nFileSizeLow |
 			(((off_t)fdata.nFileSizeHigh)<<32);
 		buf->st_dev = buf->st_rdev = 0; /* not used by Git */
 		filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
 		filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
 		filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
-		if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
-				if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
-						(findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) {
-					buf->st_mode = S_IFLNK | S_IREAD;
-					if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
-						buf->st_mode |= S_IWRITE;
-				}
-		}
 		return 0;
 	}
 error:
@@ -876,7 +869,7 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
 	buf->st_gid = 0;
 	buf->st_uid = 0;
 	buf->st_nlink = 1;
-	buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
+	buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes, 0);
 	buf->st_size = fdata.nFileSizeLow |
 		(((off_t)fdata.nFileSizeHigh)<<32);
 	buf->st_dev = buf->st_rdev = 0; /* not used by Git */
diff --git a/compat/win32.h b/compat/win32.h
index a97e880757b6f1..671bcc81f93351 100644
--- a/compat/win32.h
+++ b/compat/win32.h
@@ -6,10 +6,12 @@
 #include <windows.h>
 #endif
 
-static inline int file_attr_to_st_mode (DWORD attr)
+static inline int file_attr_to_st_mode (DWORD attr, DWORD tag)
 {
 	int fMode = S_IREAD;
-	if (attr & FILE_ATTRIBUTE_DIRECTORY)
+	if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) && tag == IO_REPARSE_TAG_SYMLINK)
+		fMode |= S_IFLNK;
+	else if (attr & FILE_ATTRIBUTE_DIRECTORY)
 		fMode |= S_IFDIR;
 	else
 		fMode |= S_IFREG;
diff --git a/compat/win32/dirent.c b/compat/win32/dirent.c
index aae364b67aa6f8..db3b08c84606a9 100644
--- a/compat/win32/dirent.c
+++ b/compat/win32/dirent.c
@@ -18,7 +18,10 @@ static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
 	xwcstoutf(ent->d_name, fdata->cFileName, MAX_PATH * 3);
 
 	/* Set file type, based on WIN32_FIND_DATA */
-	if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+	if ((fdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
+			&& fdata->dwReserved0 == IO_REPARSE_TAG_SYMLINK)
+		ent->d_type = DT_LNK;
+	else if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
 		ent->d_type = DT_DIR;
 	else
 		ent->d_type = DT_REG;
diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 3c1ded64ae6c56..39eade0777e32b 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -200,10 +200,13 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache,
 		fdata->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ?
 		fdata->EaSize : 0;
 
-	fse->st_mode = file_attr_to_st_mode(fdata->FileAttributes);
-	fse->dirent.d_type = S_ISDIR(fse->st_mode) ? DT_DIR : DT_REG;
-	fse->u.s.st_size = fdata->EndOfFile.LowPart |
-		(((off_t)fdata->EndOfFile.HighPart) << 32);
+	fse->st_mode = file_attr_to_st_mode(fdata->FileAttributes,
+					    fdata->EaSize);
+	fse->dirent.d_type = S_ISREG(fse->st_mode) ? DT_REG :
+			S_ISDIR(fse->st_mode) ? DT_DIR : DT_LNK;
+	fse->u.s.st_size = S_ISLNK(fse->st_mode) ? MAX_LONG_PATH :
+			fdata->EndOfFile.LowPart |
+			(((off_t)fdata->EndOfFile.HighPart) << 32);
 	filetime_to_timespec((FILETIME *)&(fdata->LastAccessTime),
 			     &(fse->u.s.st_atim));
 	filetime_to_timespec((FILETIME *)&(fdata->LastWriteTime),

From 89935098e5b4133df337a16459cebada73e9d63e Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sat, 16 May 2015 01:11:37 +0200
Subject: [PATCH 188/265] Win32: lstat(): return adequate stat.st_size for
 symlinks

Git typically doesn't trust the stat.st_size member of symlinks (e.g. see
strbuf_readlink()). However, some functions take shortcuts if st_size is 0
(e.g. diff_populate_filespec()).

In mingw_lstat() and fscache_lstat(), make sure to return an adequate size.

The extra overhead of opening and reading the reparse point to calculate
the exact size is not necessary, as git doesn't rely on the value anyway.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 1bf1c42d57e920..1337f765f43419 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -819,8 +819,8 @@ int mingw_lstat(const char *file_name, struct stat *buf)
 		buf->st_nlink = 1;
 		buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
 				findbuf.dwReserved0);
-		buf->st_size = fdata.nFileSizeLow |
-			(((off_t)fdata.nFileSizeHigh)<<32);
+		buf->st_size = S_ISLNK(buf->st_mode) ? MAX_LONG_PATH :
+			fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
 		buf->st_dev = buf->st_rdev = 0; /* not used by Git */
 		filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
 		filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));

From abdb2993992450a13543b714306d2ac301f1a501 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Tue, 19 May 2015 21:48:55 +0200
Subject: [PATCH 189/265] Win32: factor out retry logic

The retry pattern is duplicated in three places. It also seems to be too
hard to use: mingw_unlink() and mingw_rmdir() duplicate the code to retry,
and both of them do so incompletely. They also do not restore errno if the
user answers 'no'.

Introduce a retry_ask_yes_no() helper function that handles retry with
small delay, asking the user, and restoring errno.

mingw_unlink: include _wchmod in the retry loop (which may fail if the
file is locked exclusively).

mingw_rmdir: include special error handling in the retry loop.

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 compat/mingw.c | 102 ++++++++++++++++++++++---------------------------
 1 file changed, 45 insertions(+), 57 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 1337f765f43419..862b240689c858 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -15,8 +15,6 @@
 
 #define HCAST(type, handle) ((type)(intptr_t)handle)
 
-static const int delay[] = { 0, 1, 10, 20, 40 };
-
 void open_in_gdb(void)
 {
 	static struct child_process cp = CHILD_PROCESS_INIT;
@@ -193,15 +191,12 @@ static int read_yes_no_answer(void)
 	return -1;
 }
 
-static int ask_yes_no_if_possible(const char *format, ...)
+static int ask_yes_no_if_possible(const char *format, va_list args)
 {
 	char question[4096];
 	const char *retry_hook[] = { NULL, NULL, NULL };
-	va_list args;
 
-	va_start(args, format);
 	vsnprintf(question, sizeof(question), format, args);
-	va_end(args);
 
 	if ((retry_hook[0] = mingw_getenv("GIT_ASK_YESNO"))) {
 		retry_hook[1] = question;
@@ -223,6 +218,31 @@ static int ask_yes_no_if_possible(const char *format, ...)
 	}
 }
 
+static int retry_ask_yes_no(int *tries, const char *format, ...)
+{
+	static const int delay[] = { 0, 1, 10, 20, 40 };
+	va_list args;
+	int result, saved_errno = errno;
+
+	if ((*tries) < ARRAY_SIZE(delay)) {
+		/*
+		 * We assume that some other process had the file open at the wrong
+		 * moment and retry. In order to give the other process a higher
+		 * chance to complete its operation, we give up our time slice now.
+		 * If we have to retry again, we do sleep a bit.
+		 */
+		Sleep(delay[*tries]);
+		(*tries)++;
+		return 1;
+	}
+
+	va_start(args, format);
+	result = ask_yes_no_if_possible(format, args);
+	va_end(args);
+	errno = saved_errno;
+	return result;
+}
+
 /* Windows only */
 enum hide_dotfiles_type {
 	HIDE_DOTFILES_FALSE = 0,
@@ -301,7 +321,7 @@ static wchar_t *normalize_ntpath(wchar_t *wbuf)
 
 int mingw_unlink(const char *pathname)
 {
-	int ret, tries = 0;
+	int tries = 0;
 	wchar_t wpathname[MAX_LONG_PATH];
 	if (xutftowcs_long_path(wpathname, pathname) < 0)
 		return -1;
@@ -309,26 +329,16 @@ int mingw_unlink(const char *pathname)
 	if (DeleteFileW(wpathname))
 		return 0;
 
-	/* read-only files cannot be removed */
-	_wchmod(wpathname, 0666);
-	while ((ret = _wunlink(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
+	do {
+		/* read-only files cannot be removed */
+		_wchmod(wpathname, 0666);
+		if (!_wunlink(wpathname))
+			return 0;
 		if (!is_file_in_use_error(GetLastError()))
 			break;
-		/*
-		 * We assume that some other process had the source or
-		 * destination file open at the wrong moment and retry.
-		 * In order to give the other process a higher chance to
-		 * complete its operation, we give up our time slice now.
-		 * If we have to retry again, we do sleep a bit.
-		 */
-		Sleep(delay[tries]);
-		tries++;
-	}
-	while (ret == -1 && is_file_in_use_error(GetLastError()) &&
-	       ask_yes_no_if_possible("Unlink of file '%s' failed. "
-			"Should I try again?", pathname))
-	       ret = _wunlink(wpathname);
-	return ret;
+	} while (retry_ask_yes_no(&tries, "Unlink of file '%s' failed. "
+			"Should I try again?", pathname));
+	return -1;
 }
 
 static int is_dir_empty(const wchar_t *wpath)
@@ -355,7 +365,7 @@ static int is_dir_empty(const wchar_t *wpath)
 
 int mingw_rmdir(const char *pathname)
 {
-	int ret, tries = 0;
+	int tries = 0;
 	wchar_t wpathname[MAX_LONG_PATH];
 	struct stat st;
 
@@ -381,7 +391,11 @@ int mingw_rmdir(const char *pathname)
 	if (xutftowcs_long_path(wpathname, pathname) < 0)
 		return -1;
 
-	while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
+	do {
+		if (!_wrmdir(wpathname)) {
+			invalidate_lstat_cache();
+			return 0;
+		}
 		if (!is_file_in_use_error(GetLastError()))
 			errno = err_win_to_posix(GetLastError());
 		if (errno != EACCES)
@@ -390,23 +404,9 @@ int mingw_rmdir(const char *pathname)
 			errno = ENOTEMPTY;
 			break;
 		}
-		/*
-		 * We assume that some other process had the source or
-		 * destination file open at the wrong moment and retry.
-		 * In order to give the other process a higher chance to
-		 * complete its operation, we give up our time slice now.
-		 * If we have to retry again, we do sleep a bit.
-		 */
-		Sleep(delay[tries]);
-		tries++;
-	}
-	while (ret == -1 && errno == EACCES && is_file_in_use_error(GetLastError()) &&
-	       ask_yes_no_if_possible("Deletion of directory '%s' failed. "
-			"Should I try again?", pathname))
-	       ret = _wrmdir(wpathname);
-	if (!ret)
-		invalidate_lstat_cache();
-	return ret;
+	} while (retry_ask_yes_no(&tries, "Deletion of directory '%s' failed. "
+			"Should I try again?", pathname));
+	return -1;
 }
 
 static inline int needs_hiding(const char *path)
@@ -2359,20 +2359,8 @@ int mingw_rename(const char *pold, const char *pnew)
 			SetFileAttributesW(wpnew, attrs);
 		}
 	}
-	if (tries < ARRAY_SIZE(delay) && gle == ERROR_ACCESS_DENIED) {
-		/*
-		 * We assume that some other process had the source or
-		 * destination file open at the wrong moment and retry.
-		 * In order to give the other process a higher chance to
-		 * complete its operation, we give up our time slice now.
-		 * If we have to retry again, we do sleep a bit.
-		 */
-		Sleep(delay[tries]);
-		tries++;
-		goto repeat;
-	}
 	if (gle == ERROR_ACCESS_DENIED &&
-	       ask_yes_no_if_possible("Rename from '%s' to '%s' failed. "
+	       retry_ask_yes_no(&tries, "Rename from '%s' to '%s' failed. "
 		       "Should I try again?", pold, pnew))
 		goto repeat;
 

From 2c5b8bbdbdf9883c888377c9e5d1b1f378e8fb68 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sun, 24 May 2015 01:55:05 +0200
Subject: [PATCH 190/265] Win32: change default of 'core.symlinks' to false

Symlinks on Windows don't work the same way as on Unix systems. E.g. there
are different types of symlinks for directories and files, creating
symlinks requires administrative privileges etc.

By default, disable symlink support on Windows. I.e. users explicitly have
to enable it with 'git config [--system|--global] core.symlinks true'.

The test suite ignores system / global config files. Allow testing *with*
symlink support by checking if native symlinks are enabled in MSys2 (via
'MSYS=winsymlinks:nativestrict').

Reminder: This would need to be changed if / when we find a way to run the
test suite in a non-MSys-based shell (e.g. dash).

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 compat/mingw.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/compat/mingw.c b/compat/mingw.c
index 862b240689c858..66e654c9ad4bfd 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2934,6 +2934,15 @@ static void setup_windows_environment(void)
 
 	if (!getenv("LC_ALL") && !getenv("LC_CTYPE") && !getenv("LANG"))
 		setenv("LC_CTYPE", "C.UTF-8", 1);
+
+	/*
+	 * Change 'core.symlinks' default to false, unless native symlinks are
+	 * enabled in MSys2 (via 'MSYS=winsymlinks:nativestrict'). Thus we can
+	 * run the test suite (which doesn't obey config files) with or without
+	 * symlink support.
+	 */
+	if (!(tmp = getenv("MSYS")) || !strstr(tmp, "winsymlinks:nativestrict"))
+		has_symlinks = 0;
 }
 
 static PSID get_current_user_sid(void)

From 28febe7fda824216a25007f908d2d8d3da9b815a Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sat, 16 May 2015 00:32:03 +0200
Subject: [PATCH 191/265] Win32: add symlink-specific error codes

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 compat/mingw.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/compat/mingw.c b/compat/mingw.c
index 66e654c9ad4bfd..1a1b812ddb4d66 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -90,6 +90,7 @@ int err_win_to_posix(DWORD winerr)
 	case ERROR_INVALID_PARAMETER: error = EINVAL; break;
 	case ERROR_INVALID_PASSWORD: error = EPERM; break;
 	case ERROR_INVALID_PRIMARY_GROUP: error = EINVAL; break;
+	case ERROR_INVALID_REPARSE_DATA: error = EINVAL; break;
 	case ERROR_INVALID_SIGNAL_NUMBER: error = EINVAL; break;
 	case ERROR_INVALID_TARGET_HANDLE: error = EIO; break;
 	case ERROR_INVALID_WORKSTATION: error = EACCES; break;
@@ -104,6 +105,7 @@ int err_win_to_posix(DWORD winerr)
 	case ERROR_NEGATIVE_SEEK: error = ESPIPE; break;
 	case ERROR_NOACCESS: error = EFAULT; break;
 	case ERROR_NONE_MAPPED: error = EINVAL; break;
+	case ERROR_NOT_A_REPARSE_POINT: error = EINVAL; break;
 	case ERROR_NOT_ENOUGH_MEMORY: error = ENOMEM; break;
 	case ERROR_NOT_READY: error = EAGAIN; break;
 	case ERROR_NOT_SAME_DEVICE: error = EXDEV; break;
@@ -124,6 +126,9 @@ int err_win_to_posix(DWORD winerr)
 	case ERROR_PIPE_NOT_CONNECTED: error = EPIPE; break;
 	case ERROR_PRIVILEGE_NOT_HELD: error = EACCES; break;
 	case ERROR_READ_FAULT: error = EIO; break;
+	case ERROR_REPARSE_ATTRIBUTE_CONFLICT: error = EINVAL; break;
+	case ERROR_REPARSE_TAG_INVALID: error = EINVAL; break;
+	case ERROR_REPARSE_TAG_MISMATCH: error = EINVAL; break;
 	case ERROR_SEEK: error = EIO; break;
 	case ERROR_SEEK_ON_DEVICE: error = ESPIPE; break;
 	case ERROR_SHARING_BUFFER_EXCEEDED: error = ENFILE; break;

From e1ae98085aa9633f618483f3e176cf7755f3e27f Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sun, 24 May 2015 01:06:10 +0200
Subject: [PATCH 192/265] Win32: mingw_unlink: support symlinks to directories

_wunlink() / DeleteFileW() refuses to delete symlinks to directories. If
_wunlink() fails with ERROR_ACCESS_DENIED, try _wrmdir() as well.

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 compat/mingw.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/compat/mingw.c b/compat/mingw.c
index 1a1b812ddb4d66..f282899624c5a3 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -341,6 +341,13 @@ int mingw_unlink(const char *pathname)
 			return 0;
 		if (!is_file_in_use_error(GetLastError()))
 			break;
+		/*
+		 * _wunlink() / DeleteFileW() for directory symlinks fails with
+		 * ERROR_ACCESS_DENIED (EACCES), so try _wrmdir() as well. This is the
+		 * same error we get if a file is in use (already checked above).
+		 */
+		if (!_wrmdir(wpathname))
+			return 0;
 	} while (retry_ask_yes_no(&tries, "Unlink of file '%s' failed. "
 			"Should I try again?", pathname));
 	return -1;

From 98f949e02b6df909a602906b57a6c65d934a0512 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Tue, 19 May 2015 22:42:48 +0200
Subject: [PATCH 193/265] Win32: mingw_rename: support renaming symlinks

MSVCRT's _wrename() cannot rename symlinks over existing files: it returns
success without doing anything. Newer MSVCR*.dll versions probably do not
have this problem: according to CRT sources, they just call MoveFileEx()
with the MOVEFILE_COPY_ALLOWED flag.

Get rid of _wrename() and call MoveFileEx() with proper error handling.

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 compat/mingw.c | 38 +++++++++++++++++---------------------
 1 file changed, 17 insertions(+), 21 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index f282899624c5a3..dff8bada88f7d6 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2331,27 +2331,29 @@ int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz)
 #undef rename
 int mingw_rename(const char *pold, const char *pnew)
 {
-	DWORD attrs, gle;
+	DWORD attrs = INVALID_FILE_ATTRIBUTES, gle;
 	int tries = 0;
 	wchar_t wpold[MAX_LONG_PATH], wpnew[MAX_LONG_PATH];
 	if (xutftowcs_long_path(wpold, pold) < 0 ||
 	    xutftowcs_long_path(wpnew, pnew) < 0)
 		return -1;
 
-	/*
-	 * Try native rename() first to get errno right.
-	 * It is based on MoveFile(), which cannot overwrite existing files.
-	 */
-	if (!_wrename(wpold, wpnew))
-		return 0;
-	if (errno != EEXIST)
-		return -1;
 repeat:
-	if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
+	if (MoveFileExW(wpold, wpnew,
+			MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
 		return 0;
-	/* TODO: translate more errors */
 	gle = GetLastError();
-	if (gle == ERROR_ACCESS_DENIED &&
+
+	/* revert file attributes on failure */
+	if (attrs != INVALID_FILE_ATTRIBUTES)
+		SetFileAttributesW(wpnew, attrs);
+
+	if (!is_file_in_use_error(gle)) {
+		errno = err_win_to_posix(gle);
+		return -1;
+	}
+
+	if (attrs == INVALID_FILE_ATTRIBUTES &&
 	    (attrs = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) {
 		if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
 			DWORD attrsold = GetFileAttributesW(wpold);
@@ -2363,16 +2365,10 @@ int mingw_rename(const char *pold, const char *pnew)
 			return -1;
 		}
 		if ((attrs & FILE_ATTRIBUTE_READONLY) &&
-		    SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
-			if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
-				return 0;
-			gle = GetLastError();
-			/* revert file attributes on failure */
-			SetFileAttributesW(wpnew, attrs);
-		}
+		    SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY))
+			goto repeat;
 	}
-	if (gle == ERROR_ACCESS_DENIED &&
-	       retry_ask_yes_no(&tries, "Rename from '%s' to '%s' failed. "
+	if (retry_ask_yes_no(&tries, "Rename from '%s' to '%s' failed. "
 		       "Should I try again?", pold, pnew))
 		goto repeat;
 

From 617ec51e8ae797060a36e941358d827161394768 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sun, 24 May 2015 01:17:31 +0200
Subject: [PATCH 194/265] Win32: mingw_chdir: change to symlink-resolved
 directory

If symlinks are enabled, resolve all symlinks when changing directories,
as required by POSIX.

Note: Git's real_path() function bases its link resolution algorithm on
this property of chdir(). Unfortunately, the current directory on Windows
is limited to only MAX_PATH (260) characters. Therefore using symlinks and
long paths in combination may be problematic.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index dff8bada88f7d6..ead99b3f588f34 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -754,7 +754,24 @@ int mingw_chdir(const char *dirname)
 	wchar_t wdirname[MAX_LONG_PATH];
 	if (xutftowcs_long_path(wdirname, dirname) < 0)
 		return -1;
-	result = _wchdir(wdirname);
+
+	if (has_symlinks) {
+		HANDLE hnd = CreateFileW(wdirname, 0,
+				FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+				OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+		if (hnd == INVALID_HANDLE_VALUE) {
+			errno = err_win_to_posix(GetLastError());
+			return -1;
+		}
+		if (!GetFinalPathNameByHandleW(hnd, wdirname, ARRAY_SIZE(wdirname), 0)) {
+			errno = err_win_to_posix(GetLastError());
+			CloseHandle(hnd);
+			return -1;
+		}
+		CloseHandle(hnd);
+	}
+
+	result = _wchdir(normalize_ntpath(wdirname));
 	current_directory_len = GetCurrentDirectoryW(0, NULL);
 	return result;
 }

From 7ded10e5ef82be3d0a64003e7d7a7b78d0a657e9 Mon Sep 17 00:00:00 2001
From: Doug Kelly <dougk.ff7@gmail.com>
Date: Wed, 8 Jan 2014 20:28:15 -0600
Subject: [PATCH 195/265] pack-objects (mingw): demonstrate a segmentation
 fault with large deltas

There is a problem in the way 9ac3f0e5b3e4 (pack-objects: fix
performance issues on packing large deltas, 2018-07-22) initializes that
mutex in the `packing_data` struct. The problem manifests in a
segmentation fault on Windows, when a mutex (AKA critical section) is
accessed without being initialized. (With pthreads, you apparently do
not really have to initialize them?)

This was reported in https://github.com/git-for-windows/git/issues/1839.

Signed-off-by: Doug Kelly <dougk.ff7@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t7422-submodule-long-path.sh | 105 +++++++++++++++++++++++++++++++++
 1 file changed, 105 insertions(+)
 create mode 100755 t/t7422-submodule-long-path.sh

diff --git a/t/t7422-submodule-long-path.sh b/t/t7422-submodule-long-path.sh
new file mode 100755
index 00000000000000..1c03c76080a429
--- /dev/null
+++ b/t/t7422-submodule-long-path.sh
@@ -0,0 +1,105 @@
+#!/bin/sh
+#
+# Copyright (c) 2013 Doug Kelly
+#
+
+test_description='Test submodules with a path near PATH_MAX
+
+This test verifies that "git submodule" initialization, update and clones work, including with recursive submodules and paths approaching PATH_MAX (260 characters on Windows)
+'
+
+TEST_NO_CREATE_REPO=1
+. ./test-lib.sh
+
+longpath=""
+for (( i=0; i<4; i++ )); do
+	longpath="0123456789abcdefghijklmnopqrstuvwxyz$longpath"
+done
+# Pick a substring maximum of 90 characters
+# This should be good, since we'll add on a lot for temp directories
+longpath=${longpath:0:90}; export longpath
+
+test_expect_failure 'submodule with a long path' '
+	GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
+	git -c init.defaultBranch=long init --bare remote &&
+	test_create_repo bundle1 &&
+	(
+		cd bundle1 &&
+		test_commit "shoot" &&
+		git rev-parse --verify HEAD >../expect
+	) &&
+	mkdir home &&
+	(
+		cd home &&
+		git clone ../remote test &&
+		cd test &&
+		git checkout -B long &&
+		git submodule add ../bundle1 $longpath &&
+		test_commit "sogood" &&
+		(
+			cd $longpath &&
+			git rev-parse --verify HEAD >actual &&
+			test_cmp ../../../expect actual
+		) &&
+		git push origin long
+	) &&
+	mkdir home2 &&
+	(
+		cd home2 &&
+		git clone ../remote test &&
+		cd test &&
+		git checkout long &&
+		git submodule update --init &&
+		(
+			cd $longpath &&
+			git rev-parse --verify HEAD >actual &&
+			test_cmp ../../../expect actual
+		)
+	)
+'
+
+test_expect_failure 'recursive submodule with a long path' '
+	GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
+	git -c init.defaultBranch=long init --bare super &&
+	test_create_repo child &&
+	(
+		cd child &&
+		test_commit "shoot" &&
+		git rev-parse --verify HEAD >../expect
+	) &&
+	test_create_repo parent &&
+	(
+		cd parent &&
+		git submodule add ../child $longpath &&
+		test_commit "aim"
+	) &&
+	mkdir home3 &&
+	(
+		cd home3 &&
+		git clone ../super test &&
+		cd test &&
+		git checkout -B long &&
+		git submodule add ../parent foo &&
+		git submodule update --init --recursive &&
+		test_commit "sogood" &&
+		(
+			cd foo/$longpath &&
+			git rev-parse --verify HEAD >actual &&
+			test_cmp ../../../../expect actual
+		) &&
+		git push origin long
+	) &&
+	mkdir home4 &&
+	(
+		cd home4 &&
+		git clone ../super test --recursive &&
+		(
+			cd test/foo/$longpath &&
+			git rev-parse --verify HEAD >actual &&
+			test_cmp ../../../../expect actual
+		)
+	)
+'
+unset longpath
+
+test_done

From e40bf625c857207f631245753cf208aec4bd17c8 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Thu, 19 Mar 2015 16:33:44 +0100
Subject: [PATCH 196/265] mingw: Support `git_terminal_prompt` with more
 terminals

The `git_terminal_prompt()` function expects the terminal window to be
attached to a Win32 Console. However, this is not the case with terminal
windows other than `cmd.exe`'s, e.g. with MSys2's own `mintty`.

Non-cmd terminals such as `mintty` still have to have a Win32 Console
to be proper console programs, but have to hide the Win32 Console to
be able to provide more flexibility (such as being resizeable not only
vertically but also horizontally). By writing to that Win32 Console,
`git_terminal_prompt()` manages only to send the prompt to nowhere and
to wait for input from a Console to which the user has no access.

This commit introduces a function specifically to support `mintty` -- or
other terminals that are compatible with MSys2's `/dev/tty` emulation. We
use the `TERM` environment variable as an indicator for that: if the value
starts with "xterm" (such as `mintty`'s "xterm_256color"), we prefer to
let `xterm_prompt()` handle the user interaction.

The most prominent user of `git_terminal_prompt()` is certainly
`git-remote-https.exe`. It is an interesting use case because both
`stdin` and `stdout` are redirected when Git calls said executable, yet
it still wants to access the terminal.

When running inside a `mintty`, the terminal is not accessible to the
`git-remote-https.exe` program, though, because it is a MinGW program
and the `mintty` terminal is not backed by a Win32 console.

To solve that problem, we simply call out to the shell -- which is an
*MSys2* program and can therefore access `/dev/tty`.

Helped-by: nalla <nalla@hamal.uberspace.de>
Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/terminal.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 54 insertions(+)

diff --git a/compat/terminal.c b/compat/terminal.c
index 7db330c52dcc23..eadc5516f98db9 100644
--- a/compat/terminal.c
+++ b/compat/terminal.c
@@ -418,6 +418,54 @@ static int getchar_with_timeout(int timeout)
 	return getchar();
 }
 
+static char *shell_prompt(const char *prompt, int echo)
+{
+	const char *read_input[] = {
+		/* Note: call 'bash' explicitly, as 'read -s' is bash-specific */
+		"bash", "-c", echo ?
+		"cat >/dev/tty && read -r line </dev/tty && echo \"$line\"" :
+		"cat >/dev/tty && read -r -s line </dev/tty && echo \"$line\" && echo >/dev/tty",
+		NULL
+	};
+	struct child_process child = CHILD_PROCESS_INIT;
+	static struct strbuf buffer = STRBUF_INIT;
+	int prompt_len = strlen(prompt), len = -1, code;
+
+	strvec_pushv(&child.args, read_input);
+	child.in = -1;
+	child.out = -1;
+
+	if (start_command(&child))
+		return NULL;
+
+	if (write_in_full(child.in, prompt, prompt_len) != prompt_len) {
+		error("could not write to prompt script");
+		close(child.in);
+		goto ret;
+	}
+	close(child.in);
+
+	strbuf_reset(&buffer);
+	len = strbuf_read(&buffer, child.out, 1024);
+	if (len < 0) {
+		error("could not read from prompt script");
+		goto ret;
+	}
+
+	strbuf_strip_suffix(&buffer, "\n");
+	strbuf_strip_suffix(&buffer, "\r");
+
+ret:
+	close(child.out);
+	code = finish_command(&child);
+	if (code) {
+		error("failed to execute prompt script (exit code %d)", code);
+		return NULL;
+	}
+
+	return len < 0 ? NULL : buffer.buf;
+}
+
 #endif
 
 #ifndef FORCE_TEXT
@@ -429,6 +477,12 @@ char *git_terminal_prompt(const char *prompt, int echo)
 	static struct strbuf buf = STRBUF_INIT;
 	int r;
 	FILE *input_fh, *output_fh;
+#ifdef GIT_WINDOWS_NATIVE
+	const char *term = getenv("TERM");
+
+	if (term && starts_with(term, "xterm"))
+		return shell_prompt(prompt, echo);
+#endif
 
 	input_fh = fopen(INPUT_PATH, "r" FORCE_TEXT);
 	if (!input_fh)

From 49361c071f3191a3ffc68d774517d62f71be05c3 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sun, 24 May 2015 01:24:41 +0200
Subject: [PATCH 197/265] Win32: implement readlink()

Implement readlink() by reading NTFS reparse points. Works for symlinks
and directory junctions. If symlinks are disabled, fail with ENOSYS.

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 compat/mingw.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++
 compat/mingw.h |  3 +-
 2 files changed, 99 insertions(+), 2 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index ead99b3f588f34..914dabcb745c93 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -3,6 +3,7 @@
 #include <aclapi.h>
 #include <conio.h>
 #include <wchar.h>
+#include <winioctl.h>
 #include "../strbuf.h"
 #include "../run-command.h"
 #include "../cache.h"
@@ -2662,6 +2663,103 @@ int link(const char *oldpath, const char *newpath)
 	return 0;
 }
 
+#ifndef _WINNT_H
+/*
+ * The REPARSE_DATA_BUFFER structure is defined in the Windows DDK (in
+ * ntifs.h) and in MSYS1's winnt.h (which defines _WINNT_H). So define
+ * it ourselves if we are on MSYS2 (whose winnt.h defines _WINNT_).
+ */
+typedef struct _REPARSE_DATA_BUFFER {
+	DWORD  ReparseTag;
+	WORD   ReparseDataLength;
+	WORD   Reserved;
+#ifndef _MSC_VER
+	_ANONYMOUS_UNION
+#endif
+	union {
+		struct {
+			WORD   SubstituteNameOffset;
+			WORD   SubstituteNameLength;
+			WORD   PrintNameOffset;
+			WORD   PrintNameLength;
+			ULONG  Flags;
+			WCHAR PathBuffer[1];
+		} SymbolicLinkReparseBuffer;
+		struct {
+			WORD   SubstituteNameOffset;
+			WORD   SubstituteNameLength;
+			WORD   PrintNameOffset;
+			WORD   PrintNameLength;
+			WCHAR PathBuffer[1];
+		} MountPointReparseBuffer;
+		struct {
+			BYTE   DataBuffer[1];
+		} GenericReparseBuffer;
+	} DUMMYUNIONNAME;
+} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
+#endif
+
+int readlink(const char *path, char *buf, size_t bufsiz)
+{
+	HANDLE handle;
+	WCHAR wpath[MAX_LONG_PATH], *wbuf;
+	REPARSE_DATA_BUFFER *b = alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
+	DWORD dummy;
+	char tmpbuf[MAX_LONG_PATH];
+	int len;
+
+	if (xutftowcs_long_path(wpath, path) < 0)
+		return -1;
+
+	/* read reparse point data */
+	handle = CreateFileW(wpath, 0,
+			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+			OPEN_EXISTING,
+			FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
+	if (handle == INVALID_HANDLE_VALUE) {
+		errno = err_win_to_posix(GetLastError());
+		return -1;
+	}
+	if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, b,
+			MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dummy, NULL)) {
+		errno = err_win_to_posix(GetLastError());
+		CloseHandle(handle);
+		return -1;
+	}
+	CloseHandle(handle);
+
+	/* get target path for symlinks or mount points (aka 'junctions') */
+	switch (b->ReparseTag) {
+	case IO_REPARSE_TAG_SYMLINK:
+		wbuf = (WCHAR*) (((char*) b->SymbolicLinkReparseBuffer.PathBuffer)
+				+ b->SymbolicLinkReparseBuffer.SubstituteNameOffset);
+		*(WCHAR*) (((char*) wbuf)
+				+ b->SymbolicLinkReparseBuffer.SubstituteNameLength) = 0;
+		break;
+	case IO_REPARSE_TAG_MOUNT_POINT:
+		wbuf = (WCHAR*) (((char*) b->MountPointReparseBuffer.PathBuffer)
+				+ b->MountPointReparseBuffer.SubstituteNameOffset);
+		*(WCHAR*) (((char*) wbuf)
+				+ b->MountPointReparseBuffer.SubstituteNameLength) = 0;
+		break;
+	default:
+		errno = EINVAL;
+		return -1;
+	}
+
+	/*
+	 * Adapt to strange readlink() API: Copy up to bufsiz *bytes*, potentially
+	 * cutting off a UTF-8 sequence. Insufficient bufsize is *not* a failure
+	 * condition. There is no conversion function that produces invalid UTF-8,
+	 * so convert to a (hopefully large enough) temporary buffer, then memcpy
+	 * the requested number of bytes (including '\0' for robustness).
+	 */
+	if ((len = xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
+		return -1;
+	memcpy(buf, tmpbuf, min(bufsiz, len + 1));
+	return min(bufsiz, len);
+}
+
 pid_t waitpid(pid_t pid, int *status, int options)
 {
 	HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
diff --git a/compat/mingw.h b/compat/mingw.h
index 240d6b949c5157..a9dc0a79b88e6c 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -123,8 +123,6 @@ struct utsname {
  * trivial stubs
  */
 
-static inline int readlink(const char *path, char *buf, size_t bufsiz)
-{ errno = ENOSYS; return -1; }
 static inline int symlink(const char *oldpath, const char *newpath)
 { errno = ENOSYS; return -1; }
 static inline int fchmod(int fildes, mode_t mode)
@@ -217,6 +215,7 @@ int setitimer(int type, struct itimerval *in, struct itimerval *out);
 int sigaction(int sig, struct sigaction *in, struct sigaction *out);
 int link(const char *oldpath, const char *newpath);
 int uname(struct utsname *buf);
+int readlink(const char *path, char *buf, size_t bufsiz);
 
 /*
  * replacements of existing functions

From 6e419894537b05196c77de32718419735b07bb93 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Tue, 28 Jul 2015 21:07:41 +0200
Subject: [PATCH 198/265] mingw: support long paths

Windows paths are typically limited to MAX_PATH = 260 characters, even
though the underlying NTFS file system supports paths up to 32,767 chars.
This limitation is also evident in Windows Explorer, cmd.exe and many
other applications (including IDEs).

Particularly annoying is that most Windows APIs return bogus error codes
if a relative path only barely exceeds MAX_PATH in conjunction with the
current directory, e.g. ERROR_PATH_NOT_FOUND / ENOENT instead of the
infinitely more helpful ERROR_FILENAME_EXCED_RANGE / ENAMETOOLONG.

Many Windows wide char APIs support longer than MAX_PATH paths through the
file namespace prefix ('\\?\' or '\\?\UNC\') followed by an absolute path.
Notable exceptions include functions dealing with executables and the
current directory (CreateProcess, LoadLibrary, Get/SetCurrentDirectory) as
well as the entire shell API (ShellExecute, SHGetSpecialFolderPath...).

Introduce a handle_long_path function to check the length of a specified
path properly (and fail with ENAMETOOLONG), and to optionally expand long
paths using the '\\?\' file namespace prefix. Short paths will not be
modified, so we don't need to worry about device names (NUL, CON, AUX).

Contrary to MSDN docs, the GetFullPathNameW function doesn't seem to be
limited to MAX_PATH (at least not on Win7), so we can use it to do the
heavy lifting of the conversion (translate '/' to '\', eliminate '.' and
'..', and make an absolute path).

Add long path error checking to xutftowcs_path for APIs with hard MAX_PATH
limit.

Add a new MAX_LONG_PATH constant and xutftowcs_long_path function for APIs
that support long paths.

While improved error checking is always active, long paths support must be
explicitly enabled via 'core.longpaths' option. This is to prevent end
users to shoot themselves in the foot by checking out files that Windows
Explorer, cmd/bash or their favorite IDE cannot handle.

Test suite:
Test the case is when the full pathname length of a dir is close
to 260 (MAX_PATH).
Bug report and an original reproducer by Andrey Rogozhnikov:
https://github.com/msysgit/git/pull/122#issuecomment-43604199

[jes: adjusted test number to avoid conflicts, added support for
chdir(), etc]

Thanks-to: Martin W. Kirst <maki@bitkings.de>
Thanks-to: Doug Kelly <dougk.ff7@gmail.com>
Original-test-by: Andrey Rogozhnikov <rogozhnikov.andrey@gmail.com>
Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Stepan Kasal <kasal@ucw.cz>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/config/core.txt  |   7 ++
 compat/mingw.c                 | 152 ++++++++++++++++++++++++++-------
 compat/mingw.h                 |  75 ++++++++++++++--
 compat/win32/dirent.c          |  16 ++--
 compat/win32/fscache.c         |  16 ++--
 t/t2031-checkout-long-paths.sh | 102 ++++++++++++++++++++++
 t/t7422-submodule-long-path.sh |  24 +++---
 7 files changed, 329 insertions(+), 63 deletions(-)
 create mode 100755 t/t2031-checkout-long-paths.sh

diff --git a/Documentation/config/core.txt b/Documentation/config/core.txt
index 5e0b296bbafff3..7d3fc4fc5bc37d 100644
--- a/Documentation/config/core.txt
+++ b/Documentation/config/core.txt
@@ -661,6 +661,13 @@ core.fscache::
 Git for Windows uses this to bulk-read and cache lstat data of entire
 directories (instead of doing lstat file by file).
 
+core.longpaths::
+	Enable long path (> 260) support for builtin commands in Git for
+	Windows. This is disabled by default, as long paths are not supported
+	by Windows Explorer, cmd.exe and the Git for Windows tool chain
+	(msys, bash, tcl, perl...). Only enable this if you know what you're
+	doing and are prepared to live with a few quirks.
+
 core.unsetenvvars::
 	Windows-only: comma-separated list of environment variables'
 	names that need to be unset before spawning any other process.
diff --git a/compat/mingw.c b/compat/mingw.c
index edb6409c1188d9..9cff6cbbe54172 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -234,6 +234,7 @@ static int core_restrict_inherited_handles = -1;
 static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
 static char *unset_environment_variables;
 int core_fscache;
+int core_long_paths;
 
 int mingw_core_config(const char *var, const char *value, void *cb)
 {
@@ -250,6 +251,11 @@ int mingw_core_config(const char *var, const char *value, void *cb)
 		return 0;
 	}
 
+	if (!strcmp(var, "core.longpaths")) {
+		core_long_paths = git_config_bool(var, value);
+		return 0;
+	}
+
 	if (!strcmp(var, "core.unsetenvvars")) {
 		free(unset_environment_variables);
 		unset_environment_variables = xstrdup(value);
@@ -296,8 +302,8 @@ static wchar_t *normalize_ntpath(wchar_t *wbuf)
 int mingw_unlink(const char *pathname)
 {
 	int ret, tries = 0;
-	wchar_t wpathname[MAX_PATH];
-	if (xutftowcs_path(wpathname, pathname) < 0)
+	wchar_t wpathname[MAX_LONG_PATH];
+	if (xutftowcs_long_path(wpathname, pathname) < 0)
 		return -1;
 
 	if (DeleteFileW(wpathname))
@@ -329,7 +335,7 @@ static int is_dir_empty(const wchar_t *wpath)
 {
 	WIN32_FIND_DATAW findbuf;
 	HANDLE handle;
-	wchar_t wbuf[MAX_PATH + 2];
+	wchar_t wbuf[MAX_LONG_PATH + 2];
 	wcscpy(wbuf, wpath);
 	wcscat(wbuf, L"\\*");
 	handle = FindFirstFileW(wbuf, &findbuf);
@@ -350,7 +356,7 @@ static int is_dir_empty(const wchar_t *wpath)
 int mingw_rmdir(const char *pathname)
 {
 	int ret, tries = 0;
-	wchar_t wpathname[MAX_PATH];
+	wchar_t wpathname[MAX_LONG_PATH];
 	struct stat st;
 
 	/*
@@ -372,7 +378,7 @@ int mingw_rmdir(const char *pathname)
 		return -1;
 	}
 
-	if (xutftowcs_path(wpathname, pathname) < 0)
+	if (xutftowcs_long_path(wpathname, pathname) < 0)
 		return -1;
 
 	while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
@@ -451,15 +457,18 @@ static int set_hidden_flag(const wchar_t *path, int set)
 int mingw_mkdir(const char *path, int mode)
 {
 	int ret;
-	wchar_t wpath[MAX_PATH];
+	wchar_t wpath[MAX_LONG_PATH];
 
 	if (!is_valid_win32_path(path, 0)) {
 		errno = EINVAL;
 		return -1;
 	}
 
-	if (xutftowcs_path(wpath, path) < 0)
+	/* CreateDirectoryW path limit is 248 (MAX_PATH - 8.3 file name) */
+	if (xutftowcs_path_ex(wpath, path, MAX_LONG_PATH, -1, 248,
+			core_long_paths) < 0)
 		return -1;
+
 	ret = _wmkdir(wpath);
 	if (!ret && needs_hiding(path))
 		return set_hidden_flag(wpath, 1);
@@ -545,7 +554,7 @@ int mingw_open (const char *filename, int oflags, ...)
 	va_list args;
 	unsigned mode;
 	int fd, create = (oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL);
-	wchar_t wfilename[MAX_PATH];
+	wchar_t wfilename[MAX_LONG_PATH];
 	open_fn_t open_fn;
 
 	va_start(args, oflags);
@@ -564,7 +573,7 @@ int mingw_open (const char *filename, int oflags, ...)
 
 	if (filename && !strcmp(filename, "/dev/null"))
 		wcscpy(wfilename, L"nul");
-	else if (xutftowcs_path(wfilename, filename) < 0)
+	else if (xutftowcs_long_path(wfilename, filename) < 0)
 		return -1;
 
 	fd = open_fn(wfilename, oflags, mode);
@@ -622,14 +631,14 @@ FILE *mingw_fopen (const char *filename, const char *otype)
 {
 	int hide = needs_hiding(filename);
 	FILE *file;
-	wchar_t wfilename[MAX_PATH], wotype[4];
+	wchar_t wfilename[MAX_LONG_PATH], wotype[4];
 	if (filename && !strcmp(filename, "/dev/null"))
 		wcscpy(wfilename, L"nul");
 	else if (!is_valid_win32_path(filename, 1)) {
 		int create = otype && strchr(otype, 'w');
 		errno = create ? EINVAL : ENOENT;
 		return NULL;
-	} else if (xutftowcs_path(wfilename, filename) < 0)
+	} else if (xutftowcs_long_path(wfilename, filename) < 0)
 		return NULL;
 
 	if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
@@ -651,14 +660,14 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
 {
 	int hide = needs_hiding(filename);
 	FILE *file;
-	wchar_t wfilename[MAX_PATH], wotype[4];
+	wchar_t wfilename[MAX_LONG_PATH], wotype[4];
 	if (filename && !strcmp(filename, "/dev/null"))
 		wcscpy(wfilename, L"nul");
 	else if (!is_valid_win32_path(filename, 1)) {
 		int create = otype && strchr(otype, 'w');
 		errno = create ? EINVAL : ENOENT;
 		return NULL;
-	} else if (xutftowcs_path(wfilename, filename) < 0)
+	} else if (xutftowcs_long_path(wfilename, filename) < 0)
 		return NULL;
 
 	if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
@@ -715,27 +724,33 @@ ssize_t mingw_write(int fd, const void *buf, size_t len)
 
 int mingw_access(const char *filename, int mode)
 {
-	wchar_t wfilename[MAX_PATH];
+	wchar_t wfilename[MAX_LONG_PATH];
 	if (!strcmp("nul", filename) || !strcmp("/dev/null", filename))
 		return 0;
-	if (xutftowcs_path(wfilename, filename) < 0)
+	if (xutftowcs_long_path(wfilename, filename) < 0)
 		return -1;
 	/* X_OK is not supported by the MSVCRT version */
 	return _waccess(wfilename, mode & ~X_OK);
 }
 
+/* cached length of current directory for handle_long_path */
+static int current_directory_len = 0;
+
 int mingw_chdir(const char *dirname)
 {
-	wchar_t wdirname[MAX_PATH];
-	if (xutftowcs_path(wdirname, dirname) < 0)
+	int result;
+	wchar_t wdirname[MAX_LONG_PATH];
+	if (xutftowcs_long_path(wdirname, dirname) < 0)
 		return -1;
-	return _wchdir(wdirname);
+	result = _wchdir(wdirname);
+	current_directory_len = GetCurrentDirectoryW(0, NULL);
+	return result;
 }
 
 int mingw_chmod(const char *filename, int mode)
 {
-	wchar_t wfilename[MAX_PATH];
-	if (xutftowcs_path(wfilename, filename) < 0)
+	wchar_t wfilename[MAX_LONG_PATH];
+	if (xutftowcs_long_path(wfilename, filename) < 0)
 		return -1;
 	return _wchmod(wfilename, mode);
 }
@@ -783,8 +798,8 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
 static int do_lstat(int follow, const char *file_name, struct stat *buf)
 {
 	WIN32_FILE_ATTRIBUTE_DATA fdata;
-	wchar_t wfilename[MAX_PATH];
-	if (xutftowcs_path(wfilename, file_name) < 0)
+	wchar_t wfilename[MAX_LONG_PATH];
+	if (xutftowcs_long_path(wfilename, file_name) < 0)
 		return -1;
 
 	if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
@@ -955,10 +970,10 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
 	FILETIME mft, aft;
 	int rc;
 	DWORD attrs;
-	wchar_t wfilename[MAX_PATH];
+	wchar_t wfilename[MAX_LONG_PATH];
 	HANDLE osfilehandle;
 
-	if (xutftowcs_path(wfilename, file_name) < 0)
+	if (xutftowcs_long_path(wfilename, file_name) < 0)
 		return -1;
 
 	/* must have write permission */
@@ -1041,6 +1056,7 @@ char *mingw_mktemp(char *template)
 	wchar_t wtemplate[MAX_PATH];
 	int offset = 0;
 
+	/* we need to return the path, thus no long paths here! */
 	if (xutftowcs_path(wtemplate, template) < 0)
 		return NULL;
 
@@ -1678,6 +1694,10 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
 
 	if (*argv && !strcmp(cmd, *argv))
 		wcmd[0] = L'\0';
+	/*
+	 * Paths to executables and to the current directory do not support
+	 * long paths, therefore we cannot use xutftowcs_long_path() here.
+	 */
 	else if (xutftowcs_path(wcmd, cmd) < 0)
 		return -1;
 	if (dir && xutftowcs_path(wdir, dir) < 0)
@@ -2329,8 +2349,9 @@ int mingw_rename(const char *pold, const char *pnew)
 {
 	DWORD attrs, gle;
 	int tries = 0;
-	wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
-	if (xutftowcs_path(wpold, pold) < 0 || xutftowcs_path(wpnew, pnew) < 0)
+	wchar_t wpold[MAX_LONG_PATH], wpnew[MAX_LONG_PATH];
+	if (xutftowcs_long_path(wpold, pold) < 0 ||
+	    xutftowcs_long_path(wpnew, pnew) < 0)
 		return -1;
 
 	/*
@@ -2644,9 +2665,9 @@ int mingw_raise(int sig)
 
 int link(const char *oldpath, const char *newpath)
 {
-	wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH];
-	if (xutftowcs_path(woldpath, oldpath) < 0 ||
-		xutftowcs_path(wnewpath, newpath) < 0)
+	wchar_t woldpath[MAX_LONG_PATH], wnewpath[MAX_LONG_PATH];
+	if (xutftowcs_long_path(woldpath, oldpath) < 0 ||
+	    xutftowcs_long_path(wnewpath, newpath) < 0)
 		return -1;
 
 	if (!CreateHardLinkW(wnewpath, woldpath, NULL)) {
@@ -2714,8 +2735,8 @@ int mingw_is_mount_point(struct strbuf *path)
 {
 	WIN32_FIND_DATAW findbuf = { 0 };
 	HANDLE handle;
-	wchar_t wfilename[MAX_PATH];
-	int wlen = xutftowcs_path(wfilename, path->buf);
+	wchar_t wfilename[MAX_LONG_PATH];
+	int wlen = xutftowcs_long_path(wfilename, path->buf);
 	if (wlen < 0)
 		die(_("could not get long path for '%s'"), path->buf);
 
@@ -2860,9 +2881,9 @@ static size_t append_system_bin_dirs(char *path, size_t size)
 
 static int is_system32_path(const char *path)
 {
-	WCHAR system32[MAX_PATH], wpath[MAX_PATH];
+	WCHAR system32[MAX_LONG_PATH], wpath[MAX_LONG_PATH];
 
-	if (xutftowcs_path(wpath, path) < 0 ||
+	if (xutftowcs_long_path(wpath, path) < 0 ||
 	    !GetSystemDirectoryW(system32, ARRAY_SIZE(system32)) ||
 	    _wcsicmp(system32, wpath))
 		return 0;
@@ -3165,6 +3186,68 @@ int is_valid_win32_path(const char *path, int allow_literal_nul)
 	}
 }
 
+int handle_long_path(wchar_t *path, int len, int max_path, int expand)
+{
+	int result;
+	wchar_t buf[MAX_LONG_PATH];
+
+	/*
+	 * we don't need special handling if path is relative to the current
+	 * directory, and current directory + path don't exceed the desired
+	 * max_path limit. This should cover > 99 % of cases with minimal
+	 * performance impact (git almost always uses relative paths).
+	 */
+	if ((len < 2 || (!is_dir_sep(path[0]) && path[1] != ':')) &&
+	    (current_directory_len + len < max_path))
+		return len;
+
+	/*
+	 * handle everything else:
+	 * - absolute paths: "C:\dir\file"
+	 * - absolute UNC paths: "\\server\share\dir\file"
+	 * - absolute paths on current drive: "\dir\file"
+	 * - relative paths on other drive: "X:file"
+	 * - prefixed paths: "\\?\...", "\\.\..."
+	 */
+
+	/* convert to absolute path using GetFullPathNameW */
+	result = GetFullPathNameW(path, MAX_LONG_PATH, buf, NULL);
+	if (!result) {
+		errno = err_win_to_posix(GetLastError());
+		return -1;
+	}
+
+	/*
+	 * return absolute path if it fits within max_path (even if
+	 * "cwd + path" doesn't due to '..' components)
+	 */
+	if (result < max_path) {
+		wcscpy(path, buf);
+		return result;
+	}
+
+	/* error out if we shouldn't expand the path or buf is too small */
+	if (!expand || result >= MAX_LONG_PATH - 6) {
+		errno = ENAMETOOLONG;
+		return -1;
+	}
+
+	/* prefix full path with "\\?\" or "\\?\UNC\" */
+	if (buf[0] == '\\') {
+		/* ...unless already prefixed */
+		if (buf[1] == '\\' && (buf[2] == '?' || buf[2] == '.'))
+			return len;
+
+		wcscpy(path, L"\\\\?\\UNC\\");
+		wcscpy(path + 8, buf + 2);
+		return result + 6;
+	} else {
+		wcscpy(path, L"\\\\?\\");
+		wcscpy(path + 4, buf);
+		return result + 4;
+	}
+}
+
 #if !defined(_MSC_VER)
 /*
  * Disable MSVCRT command line wildcard expansion (__getmainargs called from
@@ -3326,6 +3409,9 @@ int wmain(int argc, const wchar_t **wargv)
 	/* initialize Unicode console */
 	winansi_init();
 
+	/* init length of current directory for handle_long_path */
+	current_directory_len = GetCurrentDirectoryW(0, NULL);
+
 	/* invoke the real main() using our utf8 version of argv. */
 	exit_status = main(argc, argv);
 
diff --git a/compat/mingw.h b/compat/mingw.h
index 03a01e6d904d3f..240d6b949c5157 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -12,6 +12,7 @@ typedef _sigset_t sigset_t;
 #endif
 
 extern int core_fscache;
+extern int core_long_paths;
 
 int mingw_core_config(const char *var, const char *value, void *cb);
 #define platform_core_config mingw_core_config
@@ -515,6 +516,42 @@ int is_path_owned_by_current_sid(const char *path);
 int is_valid_win32_path(const char *path, int allow_literal_nul);
 #define is_valid_path(path) is_valid_win32_path(path, 0)
 
+/**
+ * Max length of long paths (exceeding MAX_PATH). The actual maximum supported
+ * by NTFS is 32,767 (* sizeof(wchar_t)), but we choose an arbitrary smaller
+ * value to limit required stack memory.
+ */
+#define MAX_LONG_PATH 4096
+
+/**
+ * Handles paths that would exceed the MAX_PATH limit of Windows Unicode APIs.
+ *
+ * With expand == false, the function checks for over-long paths and fails
+ * with ENAMETOOLONG. The path parameter is not modified, except if cwd + path
+ * exceeds max_path, but the resulting absolute path doesn't (e.g. due to
+ * eliminating '..' components). The path parameter must point to a buffer
+ * of max_path wide characters.
+ *
+ * With expand == true, an over-long path is automatically converted in place
+ * to an absolute path prefixed with '\\?\', and the new length is returned.
+ * The path parameter must point to a buffer of MAX_LONG_PATH wide characters.
+ *
+ * Parameters:
+ * path: path to check and / or convert
+ * len: size of path on input (number of wide chars without \0)
+ * max_path: max short path length to check (usually MAX_PATH = 260, but just
+ * 248 for CreateDirectoryW)
+ * expand: false to only check the length, true to expand the path to a
+ * '\\?\'-prefixed absolute path
+ *
+ * Return:
+ * length of the resulting path, or -1 on failure
+ *
+ * Errors:
+ * ENAMETOOLONG if path is too long
+ */
+int handle_long_path(wchar_t *path, int len, int max_path, int expand);
+
 /**
  * Converts UTF-8 encoded string to UTF-16LE.
  *
@@ -573,18 +610,46 @@ static inline int xutftowcs(wchar_t *wcs, const char *utf, size_t wcslen)
 }
 
 /**
- * Simplified file system specific variant of xutftowcsn, assumes output
- * buffer size is MAX_PATH wide chars and input string is \0-terminated,
- * fails with ENAMETOOLONG if input string is too long.
+ * Simplified file system specific wrapper of xutftowcsn and handle_long_path.
+ * Converts ERANGE to ENAMETOOLONG. If expand is true, wcs must be at least
+ * MAX_LONG_PATH wide chars (see handle_long_path).
  */
-static inline int xutftowcs_path(wchar_t *wcs, const char *utf)
+static inline int xutftowcs_path_ex(wchar_t *wcs, const char *utf,
+		size_t wcslen, int utflen, int max_path, int expand)
 {
-	int result = xutftowcsn(wcs, utf, MAX_PATH, -1);
+	int result = xutftowcsn(wcs, utf, wcslen, utflen);
 	if (result < 0 && errno == ERANGE)
 		errno = ENAMETOOLONG;
+	if (result >= 0)
+		result = handle_long_path(wcs, result, max_path, expand);
 	return result;
 }
 
+/**
+ * Simplified file system specific variant of xutftowcsn, assumes output
+ * buffer size is MAX_PATH wide chars and input string is \0-terminated,
+ * fails with ENAMETOOLONG if input string is too long. Typically used for
+ * Windows APIs that don't support long paths, e.g. SetCurrentDirectory,
+ * LoadLibrary, CreateProcess...
+ */
+static inline int xutftowcs_path(wchar_t *wcs, const char *utf)
+{
+	return xutftowcs_path_ex(wcs, utf, MAX_PATH, -1, MAX_PATH, 0);
+}
+
+/**
+ * Simplified file system specific variant of xutftowcsn for Windows APIs
+ * that support long paths via '\\?\'-prefix, assumes output buffer size is
+ * MAX_LONG_PATH wide chars, fails with ENAMETOOLONG if input string is too
+ * long. The 'core.longpaths' git-config option controls whether the path
+ * is only checked or expanded to a long path.
+ */
+static inline int xutftowcs_long_path(wchar_t *wcs, const char *utf)
+{
+	return xutftowcs_path_ex(wcs, utf, MAX_LONG_PATH, -1, MAX_PATH,
+			core_long_paths);
+}
+
 /**
  * Converts UTF-16LE encoded string to UTF-8.
  *
diff --git a/compat/win32/dirent.c b/compat/win32/dirent.c
index 139d2ba3c4da34..aae364b67aa6f8 100644
--- a/compat/win32/dirent.c
+++ b/compat/win32/dirent.c
@@ -65,19 +65,23 @@ static int dirent_closedir(dirent_DIR *dir)
 
 DIR *dirent_opendir(const char *name)
 {
-	wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */
+	wchar_t pattern[MAX_LONG_PATH + 2]; /* + 2 for "\*" */
 	WIN32_FIND_DATAW fdata;
 	HANDLE h;
 	int len;
 	dirent_DIR *dir;
 
-	/* convert name to UTF-16 and check length < MAX_PATH */
-	if ((len = xutftowcs_path(pattern, name)) < 0)
+	/* convert name to UTF-16 and check length */
+	if ((len = xutftowcs_path_ex(pattern, name, MAX_LONG_PATH, -1,
+			MAX_PATH - 2, core_long_paths)) < 0)
 		return NULL;
 
-	/* append optional '/' and wildcard '*' */
+	/*
+	 * append optional '\' and wildcard '*'. Note: we need to use '\' as
+	 * Windows doesn't translate '/' to '\' for "\\?\"-prefixed paths.
+	 */
 	if (len && !is_dir_sep(pattern[len - 1]))
-		pattern[len++] = '/';
+		pattern[len++] = '\\';
 	pattern[len++] = '*';
 	pattern[len] = 0;
 
@@ -90,7 +94,7 @@ DIR *dirent_opendir(const char *name)
 	}
 
 	/* initialize DIR structure and copy first dir entry */
-	dir = xmalloc(sizeof(dirent_DIR) + MAX_PATH);
+	dir = xmalloc(sizeof(dirent_DIR) + MAX_LONG_PATH);
 	dir->base_dir.preaddir = (struct dirent *(*)(DIR *dir)) dirent_readdir;
 	dir->base_dir.pclosedir = (int (*)(DIR *dir)) dirent_closedir;
 	dir->dd_handle = h;
diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 351b1dff7fff65..3c1ded64ae6c56 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -78,7 +78,7 @@ struct fsentry {
 struct heap_fsentry {
 	union {
 		struct fsentry ent;
-		char dummy[sizeof(struct fsentry) + MAX_PATH];
+		char dummy[sizeof(struct fsentry) + MAX_LONG_PATH];
 	} u;
 };
 
@@ -121,7 +121,7 @@ static void fsentry_init(struct fsentry *fse, struct fsentry *list,
 			 const char *name, size_t len)
 {
 	fse->list = list;
-	if (len > MAX_PATH)
+	if (len > MAX_LONG_PATH)
 		BUG("Trying to allocate fsentry for long path '%.*s'",
 		    (int)len, name);
 	memcpy(fse->dirent.d_name, name, len);
@@ -222,7 +222,7 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache,
 static struct fsentry *fsentry_create_list(struct fscache *cache, const struct fsentry *dir,
 					   int *dir_not_found)
 {
-	wchar_t pattern[MAX_PATH];
+	wchar_t pattern[MAX_LONG_PATH];
 	NTSTATUS status;
 	IO_STATUS_BLOCK iosb;
 	PFILE_FULL_DIR_INFORMATION di;
@@ -233,13 +233,11 @@ static struct fsentry *fsentry_create_list(struct fscache *cache, const struct f
 
 	*dir_not_found = 0;
 
-	/* convert name to UTF-16 and check length < MAX_PATH */
-	if ((wlen = xutftowcsn(pattern, dir->dirent.d_name, MAX_PATH,
-			       dir->len)) < 0) {
-		if (errno == ERANGE)
-			errno = ENAMETOOLONG;
+	/* convert name to UTF-16 and check length */
+	if ((wlen = xutftowcs_path_ex(pattern, dir->dirent.d_name,
+				      MAX_LONG_PATH, dir->len, MAX_PATH - 2,
+				      core_long_paths)) < 0)
 		return NULL;
-	}
 
 	/* handle CWD */
 	if (!wlen) {
diff --git a/t/t2031-checkout-long-paths.sh b/t/t2031-checkout-long-paths.sh
new file mode 100755
index 00000000000000..f30f8920ca689c
--- /dev/null
+++ b/t/t2031-checkout-long-paths.sh
@@ -0,0 +1,102 @@
+#!/bin/sh
+
+test_description='checkout long paths on Windows
+
+Ensures that Git for Windows can deal with long paths (>260) enabled via core.longpaths'
+
+. ./test-lib.sh
+
+if test_have_prereq !MINGW
+then
+	skip_all='skipping MINGW specific long paths test'
+	test_done
+fi
+
+test_expect_success setup '
+	p=longpathxx && # -> 10
+	p=$p$p$p$p$p && # -> 50
+	p=$p$p$p$p$p && # -> 250
+
+	path=${p}/longtestfile && # -> 263 (MAX_PATH = 260)
+
+	blob=$(echo foobar | git hash-object -w --stdin) &&
+
+	printf "100644 %s 0\t%s\n" "$blob" "$path" |
+	git update-index --add --index-info &&
+	git commit -m initial -q
+'
+
+test_expect_success 'checkout of long paths without core.longpaths fails' '
+	git config core.longpaths false &&
+	test_must_fail git checkout -f 2>error &&
+	grep -q "Filename too long" error &&
+	test ! -d longpa*
+'
+
+test_expect_success 'checkout of long paths with core.longpaths works' '
+	git config core.longpaths true &&
+	git checkout -f &&
+	test_path_is_file longpa*/longtestfile
+'
+
+test_expect_success 'update of long paths' '
+	echo frotz >>$(ls longpa*/longtestfile) &&
+	echo $path > expect &&
+	git ls-files -m > actual &&
+	test_cmp expect actual &&
+	git add $path &&
+	git commit -m second &&
+	git grep "frotz" HEAD -- $path
+'
+
+test_expect_success cleanup '
+	# bash cannot delete the trash dir if it contains a long path
+	# lets help cleaning up (unless in debug mode)
+	if test -z "$debug"
+	then
+		rm -rf longpa~1
+	fi
+'
+
+# check that the template used in the test won't be too long:
+abspath="$(pwd)"/testdir
+test ${#abspath} -gt 230 ||
+test_set_prereq SHORTABSPATH
+
+test_expect_success SHORTABSPATH 'clean up path close to MAX_PATH' '
+	p=/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef &&
+	p=y$p$p$p$p &&
+	subdir="x$(echo "$p" | tail -c $((253 - ${#abspath})) - )" &&
+	# Now, $abspath/$subdir has exactly 254 characters, and is inside CWD
+	p2="$abspath/$subdir" &&
+	test 254 = ${#p2} &&
+
+	# Be careful to overcome path limitations of the MSys tools and split
+	# the $subdir into two parts. ($subdir2 has to contain 16 chars and a
+	# slash somewhere following; that is why we asked for abspath <= 230 and
+	# why we placed a slash near the end of the $subdir template.)
+	subdir2=${subdir#????????????????*/} &&
+	subdir1=testdir/${subdir%/$subdir2} &&
+	mkdir -p "$subdir1" &&
+	i=0 &&
+	# The most important case is when absolute path is 258 characters long,
+	# and that will be when i == 4.
+	while test $i -le 7
+	do
+		mkdir -p $subdir2 &&
+		touch $subdir2/one-file &&
+		mv ${subdir2%%/*} "$subdir1/" &&
+		subdir2=z${subdir2} &&
+		i=$(($i+1)) ||
+		exit 1
+	done &&
+
+	# now check that git is able to clear the tree:
+	(cd testdir &&
+	 git init &&
+	 git config core.longpaths yes &&
+	 git clean -fdx) &&
+	test ! -d "$subdir1"
+'
+
+test_done
diff --git a/t/t7422-submodule-long-path.sh b/t/t7422-submodule-long-path.sh
index 1c03c76080a429..0da221b45aeca8 100755
--- a/t/t7422-submodule-long-path.sh
+++ b/t/t7422-submodule-long-path.sh
@@ -11,15 +11,20 @@ This test verifies that "git submodule" initialization, update and clones work,
 TEST_NO_CREATE_REPO=1
 . ./test-lib.sh
 
-longpath=""
-for (( i=0; i<4; i++ )); do
-	longpath="0123456789abcdefghijklmnopqrstuvwxyz$longpath"
-done
-# Pick a substring maximum of 90 characters
-# This should be good, since we'll add on a lot for temp directories
-longpath=${longpath:0:90}; export longpath
+# cloning a submodule calls is_git_directory("$path/../.git/modules/$path"),
+# which effectively limits the maximum length to PATH_MAX / 2 minus some
+# overhead; start with 3 * 36 = 108 chars (test 2 fails if >= 110)
+longpath36=0123456789abcdefghijklmnopqrstuvwxyz
+longpath180=$longpath36$longpath36$longpath36$longpath36$longpath36
 
-test_expect_failure 'submodule with a long path' '
+# the git database must fit within PATH_MAX, which limits the submodule name
+# to PATH_MAX - len(pwd) - ~90 (= len("/objects//") + 40-byte sha1 + some
+# overhead from the test case)
+pwd=$(pwd)
+pwdlen=$(echo "$pwd" | wc -c)
+longpath=$(echo $longpath180 | cut -c 1-$((170-$pwdlen)))
+
+test_expect_success 'submodule with a long path' '
 	GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
 	git -c init.defaultBranch=long init --bare remote &&
 	test_create_repo bundle1 &&
@@ -58,7 +63,7 @@ test_expect_failure 'submodule with a long path' '
 	)
 '
 
-test_expect_failure 'recursive submodule with a long path' '
+test_expect_success 'recursive submodule with a long path' '
 	GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
 	git -c init.defaultBranch=long init --bare super &&
 	test_create_repo child &&
@@ -100,6 +105,5 @@ test_expect_failure 'recursive submodule with a long path' '
 		)
 	)
 '
-unset longpath
 
 test_done

From dc751ee1f8a1f0fa8bbd98b79b3c86e2ae4a2390 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sat, 9 May 2015 02:11:48 +0200
Subject: [PATCH 199/265] compat/terminal.c: only use the Windows console if
 bash 'read -r' fails
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Accessing the Windows console through the special CONIN$ / CONOUT$ devices
doesn't work properly for non-ASCII usernames an passwords.

It also doesn't work for terminal emulators that hide the native console
window (such as mintty), and 'TERM=xterm*' is not necessarily a reliable
indicator for such terminals.

The new shell_prompt() function, on the other hand, works fine for both
MSys1 and MSys2, in native console windows as well as mintty, and properly
supports Unicode. It just needs bash on the path (for 'read -s', which is
bash-specific).

On Windows, try to use the shell to read from the terminal. If that fails
with ENOENT (i.e. bash was not found), use CONIN/OUT as fallback.

Note: To test this, create a UTF-8 credential file with non-ASCII chars,
e.g. in git-bash: 'echo url=http://täst.com > cred.txt'. Then in git-cmd,
'git credential fill <cred.txt' works (shell version), while calling git
without the git-wrapper (i.e. 'mingw64\bin\git credential fill <cred.txt')
mangles non-ASCII chars in both console output and input.

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 compat/terminal.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/compat/terminal.c b/compat/terminal.c
index eadc5516f98db9..776faa19eb1e3c 100644
--- a/compat/terminal.c
+++ b/compat/terminal.c
@@ -434,6 +434,7 @@ static char *shell_prompt(const char *prompt, int echo)
 	strvec_pushv(&child.args, read_input);
 	child.in = -1;
 	child.out = -1;
+	child.silent_exec_failure = 1;
 
 	if (start_command(&child))
 		return NULL;
@@ -477,11 +478,14 @@ char *git_terminal_prompt(const char *prompt, int echo)
 	static struct strbuf buf = STRBUF_INIT;
 	int r;
 	FILE *input_fh, *output_fh;
+
 #ifdef GIT_WINDOWS_NATIVE
-	const char *term = getenv("TERM");
 
-	if (term && starts_with(term, "xterm"))
-		return shell_prompt(prompt, echo);
+	/* try shell_prompt first, fall back to CONIN/OUT if bash is missing */
+	char *result = shell_prompt(prompt, echo);
+	if (result || errno != ENOENT)
+		return result;
+
 #endif
 
 	input_fh = fopen(INPUT_PATH, "r" FORCE_TEXT);

From a90a180ea936503c440accb3a31b93c78d023bef Mon Sep 17 00:00:00 2001
From: Bill Zissimopoulos <billziss@navimatics.com>
Date: Thu, 28 May 2020 16:35:57 -0700
Subject: [PATCH 200/265] mingw: lstat: compute correct size for symlinks

This commit fixes mingw_lstat by computing the proper size for symlinks
according to POSIX. POSIX specifies that upon successful return from
lstat: "the value of the st_size member shall be set to the length of
the pathname contained in the symbolic link not including any
terminating null byte".

Prior to this commit the mingw_lstat function returned a fixed size of
4096. This caused problems in git repositories that were accessed by
git for Cygwin or git for WSL. For example, doing `git reset --hard`
using git for Windows would update the size of symlinks in the index
to be 4096; at a later time git for Cygwin or git for WSL would find
that symlinks have changed size during `git status`. Vice versa doing
`git reset --hard` in git for Cygwin or git for WSL would update the
size of symlinks in the index with the correct value, only for git for
Windows to find incorrectly at a later time that the size had changed.

Signed-off-by: Bill Zissimopoulos <billziss@navimatics.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c         | 65 ++++++++++++++++++++++++++++--------------
 compat/win32/fscache.c | 12 ++++++++
 2 files changed, 56 insertions(+), 21 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 914dabcb745c93..7927fe6f14c658 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -818,10 +818,14 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
 	return 1;
 }
 
+static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
+		      char *tmpbuf, int *plen, DWORD *ptag);
+
 int mingw_lstat(const char *file_name, struct stat *buf)
 {
 	WIN32_FILE_ATTRIBUTE_DATA fdata;
-	WIN32_FIND_DATAW findbuf = { 0 };
+	DWORD reparse_tag = 0;
+	int link_len = 0;
 	wchar_t wfilename[MAX_LONG_PATH];
 	int wlen = xutftowcs_long_path(wfilename, file_name);
 	if (wlen < 0)
@@ -836,20 +840,21 @@ int mingw_lstat(const char *file_name, struct stat *buf)
 	}
 
 	if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
-		/* for reparse points, use FindFirstFile to get the reparse tag */
+		/* for reparse points, get the link tag and length */
 		if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
-			HANDLE handle = FindFirstFileW(wfilename, &findbuf);
-			if (handle == INVALID_HANDLE_VALUE)
-				goto error;
-			FindClose(handle);
+			char tmpbuf[MAX_LONG_PATH];
+
+			if (readlink_1(wfilename, FALSE, tmpbuf, &link_len,
+				       &reparse_tag) < 0)
+				return -1;
 		}
 		buf->st_ino = 0;
 		buf->st_gid = 0;
 		buf->st_uid = 0;
 		buf->st_nlink = 1;
 		buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
-				findbuf.dwReserved0);
-		buf->st_size = S_ISLNK(buf->st_mode) ? MAX_LONG_PATH :
+				reparse_tag);
+		buf->st_size = S_ISLNK(buf->st_mode) ? link_len :
 			fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
 		buf->st_dev = buf->st_rdev = 0; /* not used by Git */
 		filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
@@ -857,7 +862,7 @@ int mingw_lstat(const char *file_name, struct stat *buf)
 		filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
 		return 0;
 	}
-error:
+
 	switch (GetLastError()) {
 	case ERROR_ACCESS_DENIED:
 	case ERROR_SHARING_VIOLATION:
@@ -2699,17 +2704,13 @@ typedef struct _REPARSE_DATA_BUFFER {
 } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
 #endif
 
-int readlink(const char *path, char *buf, size_t bufsiz)
+static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
+		      char *tmpbuf, int *plen, DWORD *ptag)
 {
 	HANDLE handle;
-	WCHAR wpath[MAX_LONG_PATH], *wbuf;
+	WCHAR *wbuf;
 	REPARSE_DATA_BUFFER *b = alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
 	DWORD dummy;
-	char tmpbuf[MAX_LONG_PATH];
-	int len;
-
-	if (xutftowcs_long_path(wpath, path) < 0)
-		return -1;
 
 	/* read reparse point data */
 	handle = CreateFileW(wpath, 0,
@@ -2729,7 +2730,7 @@ int readlink(const char *path, char *buf, size_t bufsiz)
 	CloseHandle(handle);
 
 	/* get target path for symlinks or mount points (aka 'junctions') */
-	switch (b->ReparseTag) {
+	switch ((*ptag = b->ReparseTag)) {
 	case IO_REPARSE_TAG_SYMLINK:
 		wbuf = (WCHAR*) (((char*) b->SymbolicLinkReparseBuffer.PathBuffer)
 				+ b->SymbolicLinkReparseBuffer.SubstituteNameOffset);
@@ -2743,10 +2744,34 @@ int readlink(const char *path, char *buf, size_t bufsiz)
 				+ b->MountPointReparseBuffer.SubstituteNameLength) = 0;
 		break;
 	default:
-		errno = EINVAL;
-		return -1;
+		if (fail_on_unknown_tag) {
+			errno = EINVAL;
+			return -1;
+		} else {
+			*plen = MAX_LONG_PATH;
+			return 0;
+		}
 	}
 
+	if ((*plen =
+	     xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) <  0)
+		return -1;
+	return 0;
+}
+
+int readlink(const char *path, char *buf, size_t bufsiz)
+{
+	WCHAR wpath[MAX_LONG_PATH];
+	char tmpbuf[MAX_LONG_PATH];
+	int len;
+	DWORD tag;
+
+	if (xutftowcs_long_path(wpath, path) < 0)
+		return -1;
+
+	if (readlink_1(wpath, TRUE, tmpbuf, &len, &tag) < 0)
+		return -1;
+
 	/*
 	 * Adapt to strange readlink() API: Copy up to bufsiz *bytes*, potentially
 	 * cutting off a UTF-8 sequence. Insufficient bufsize is *not* a failure
@@ -2754,8 +2779,6 @@ int readlink(const char *path, char *buf, size_t bufsiz)
 	 * so convert to a (hopefully large enough) temporary buffer, then memcpy
 	 * the requested number of bytes (including '\0' for robustness).
 	 */
-	if ((len = xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
-		return -1;
 	memcpy(buf, tmpbuf, min(bufsiz, len + 1));
 	return min(bufsiz, len);
 }
diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 39eade0777e32b..16b1e522f34182 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -582,6 +582,18 @@ int fscache_lstat(const char *filename, struct stat *st)
 		return -1;
 	}
 
+	/*
+	 * Special case symbolic links: FindFirstFile()/FindNextFile() did not
+	 * provide us with the length of the target path.
+	 */
+	if (fse->u.s.st_size == MAX_LONG_PATH && S_ISLNK(fse->st_mode)) {
+		char buf[MAX_LONG_PATH];
+		int len = readlink(filename, buf, sizeof(buf) - 1);
+
+		if (len > 0)
+			fse->u.s.st_size = len;
+	}
+
 	/* copy stat data */
 	st->st_ino = 0;
 	st->st_gid = 0;

From d1012e5d2286366bdbad0f8553f94781e9624c17 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sat, 5 Jul 2014 00:00:36 +0200
Subject: [PATCH 201/265] Win32: fix 'lstat("dir/")' with long paths

Use a suffciently large buffer to strip the trailing slash.

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 compat/mingw.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 9cff6cbbe54172..f1f7fd90ba00fd 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -870,7 +870,7 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
 static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
 {
 	int namelen;
-	char alt_name[PATH_MAX];
+	char alt_name[MAX_LONG_PATH];
 
 	if (!do_lstat(follow, file_name, buf))
 		return 0;
@@ -886,7 +886,7 @@ static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
 		return -1;
 	while (namelen && file_name[namelen-1] == '/')
 		--namelen;
-	if (!namelen || namelen >= PATH_MAX)
+	if (!namelen || namelen >= MAX_LONG_PATH)
 		return -1;
 
 	memcpy(alt_name, file_name, namelen);

From 25c6d03b94da402543d1ad990ae93e33913218cb Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 23 Feb 2018 02:50:03 +0100
Subject: [PATCH 202/265] mingw (git_terminal_prompt): do fall back to
 CONIN$/CONOUT$ method

To support Git Bash running in a MinTTY, we use a dirty trick to access
the MSYS2 pseudo terminal: we execute a Bash snippet that accesses
/dev/tty.

The idea was to fall back to writing to/reading from CONOUT$/CONIN$ if
that Bash call failed because Bash was not found.

However, we should fall back even in other error conditions, because we
have not successfully read the user input. Let's make it so.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/terminal.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/compat/terminal.c b/compat/terminal.c
index 776faa19eb1e3c..7d89e29d78125b 100644
--- a/compat/terminal.c
+++ b/compat/terminal.c
@@ -483,7 +483,7 @@ char *git_terminal_prompt(const char *prompt, int echo)
 
 	/* try shell_prompt first, fall back to CONIN/OUT if bash is missing */
 	char *result = shell_prompt(prompt, echo);
-	if (result || errno != ENOENT)
+	if (result)
 		return result;
 
 #endif

From cf1318d59f4bd65c18d28d33e17d16e700d73973 Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sun, 24 May 2015 01:32:03 +0200
Subject: [PATCH 203/265] Win32: implement basic symlink() functionality (file
 symlinks only)

Implement symlink() that always creates file symlinks. Fails with ENOSYS
if symlinks are disabled or unsupported.

Note: CreateSymbolicLinkW() was introduced with symlink support in Windows
Vista. For compatibility with Windows XP, we need to load it dynamically
and fail gracefully if it isnt's available.

Signed-off-by: Karsten Blees <blees@dcon.de>
---
 compat/mingw.c | 28 ++++++++++++++++++++++++++++
 compat/mingw.h |  3 +--
 2 files changed, 29 insertions(+), 2 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 7927fe6f14c658..81c00f9383c357 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2668,6 +2668,34 @@ int link(const char *oldpath, const char *newpath)
 	return 0;
 }
 
+int symlink(const char *target, const char *link)
+{
+	wchar_t wtarget[MAX_LONG_PATH], wlink[MAX_LONG_PATH];
+	int len;
+
+	/* fail if symlinks are disabled or API is not supported (WinXP) */
+	if (!has_symlinks) {
+		errno = ENOSYS;
+		return -1;
+	}
+
+	if ((len = xutftowcs_long_path(wtarget, target)) < 0
+			|| xutftowcs_long_path(wlink, link) < 0)
+		return -1;
+
+	/* convert target dir separators to backslashes */
+	while (len--)
+		if (wtarget[len] == '/')
+			wtarget[len] = '\\';
+
+	/* create file symlink */
+	if (!CreateSymbolicLinkW(wlink, wtarget, 0)) {
+		errno = err_win_to_posix(GetLastError());
+		return -1;
+	}
+	return 0;
+}
+
 #ifndef _WINNT_H
 /*
  * The REPARSE_DATA_BUFFER structure is defined in the Windows DDK (in
diff --git a/compat/mingw.h b/compat/mingw.h
index a9dc0a79b88e6c..3b6fc2fd812b58 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -123,8 +123,6 @@ struct utsname {
  * trivial stubs
  */
 
-static inline int symlink(const char *oldpath, const char *newpath)
-{ errno = ENOSYS; return -1; }
 static inline int fchmod(int fildes, mode_t mode)
 { errno = ENOSYS; return -1; }
 #ifndef __MINGW64_VERSION_MAJOR
@@ -215,6 +213,7 @@ int setitimer(int type, struct itimerval *in, struct itimerval *out);
 int sigaction(int sig, struct sigaction *in, struct sigaction *out);
 int link(const char *oldpath, const char *newpath);
 int uname(struct utsname *buf);
+int symlink(const char *target, const char *link);
 int readlink(const char *path, char *buf, size_t bufsiz);
 
 /*

From 0eee62837adeaefc6301a64887cd5f82727b3b45 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 30 Aug 2017 01:28:22 +0200
Subject: [PATCH 204/265] mingw: ensure that core.longPaths is handled *always*

A ton of Git commands simply do not read (or at least parse) the core.*
settings. This is not good, as Git for Windows relies on the
core.longPaths setting to be read quite early on.

So let's just make sure that all commands read the config and give
platform_core_config() a chance.

This patch teaches tons of Git commands to respect the config setting
`core.longPaths = true`, including `pack-refs`, thereby fixing
https://github.com/git-for-windows/git/issues/1218

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/archive.c           | 2 ++
 builtin/bisect--helper.c    | 2 ++
 builtin/bundle.c            | 2 ++
 builtin/check-ref-format.c  | 2 ++
 builtin/clone.c             | 2 ++
 builtin/column.c            | 2 ++
 builtin/credential-store.c  | 3 +++
 builtin/fetch-pack.c        | 2 ++
 builtin/get-tar-commit-id.c | 2 ++
 builtin/log.c               | 1 +
 builtin/ls-remote.c         | 2 ++
 builtin/mailinfo.c          | 2 ++
 builtin/mailsplit.c         | 2 ++
 builtin/merge-index.c       | 3 +++
 builtin/merge-tree.c        | 2 ++
 builtin/mktag.c             | 1 +
 builtin/mktree.c            | 2 ++
 builtin/pack-refs.c         | 1 +
 builtin/prune-packed.c      | 2 ++
 builtin/prune.c             | 3 +++
 builtin/reflog.c            | 1 +
 builtin/remote-ext.c        | 2 ++
 builtin/remote.c            | 1 +
 builtin/rev-parse.c         | 1 +
 builtin/show-index.c        | 2 ++
 builtin/show-ref.c          | 1 +
 builtin/stripspace.c        | 5 ++---
 builtin/submodule--helper.c | 1 +
 builtin/upload-archive.c    | 3 +++
 http-backend.c              | 1 +
 refs.c                      | 2 +-
 31 files changed, 56 insertions(+), 4 deletions(-)

diff --git a/builtin/archive.c b/builtin/archive.c
index 7176b041b6d85b..77d80fdfebb71e 100644
--- a/builtin/archive.c
+++ b/builtin/archive.c
@@ -9,6 +9,7 @@
 #include "parse-options.h"
 #include "pkt-line.h"
 #include "sideband.h"
+#include "config.h"
 
 static void create_output_file(const char *output_file)
 {
@@ -93,6 +94,7 @@ int cmd_archive(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 
+	git_config(git_default_config, NULL);
 	argc = parse_options(argc, argv, prefix, local_opts, NULL,
 			     PARSE_OPT_KEEP_ALL);
 
diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
index 8b2b259ff0d11e..641e52f57f8e6b 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -9,6 +9,7 @@
 #include "prompt.h"
 #include "quote.h"
 #include "revision.h"
+#include "config.h"
 
 static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
 static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
@@ -1277,6 +1278,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
 	};
 	struct bisect_terms terms = { .term_good = NULL, .term_bad = NULL };
 
+	git_config(git_default_config, NULL);
 	argc = parse_options(argc, argv, prefix, options,
 			     git_bisect_helper_usage,
 			     PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_UNKNOWN);
diff --git a/builtin/bundle.c b/builtin/bundle.c
index 2adad545a2e972..95219c0944a55d 100644
--- a/builtin/bundle.c
+++ b/builtin/bundle.c
@@ -3,6 +3,7 @@
 #include "parse-options.h"
 #include "cache.h"
 #include "bundle.h"
+#include "config.h"
 
 /*
  * Basic handler for bundle files to connect repositories via sneakernet.
@@ -110,6 +111,7 @@ static int cmd_bundle_verify(int argc, const char **argv, const char *prefix) {
 	};
 	char *bundle_file;
 
+	git_config(git_default_config, NULL);
 	argc = parse_options_cmd_bundle(argc, argv, prefix,
 			builtin_bundle_verify_usage, options, &bundle_file);
 	/* bundle internals use argv[1] as further parameters */
diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c
index bc67d3f0a83d35..abee1be4721055 100644
--- a/builtin/check-ref-format.c
+++ b/builtin/check-ref-format.c
@@ -6,6 +6,7 @@
 #include "refs.h"
 #include "builtin.h"
 #include "strbuf.h"
+#include "config.h"
 
 static const char builtin_check_ref_format_usage[] =
 "git check-ref-format [--normalize] [<options>] <refname>\n"
@@ -58,6 +59,7 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
 	int flags = 0;
 	const char *refname;
 
+	git_config(git_default_config, NULL);
 	if (argc == 2 && !strcmp(argv[1], "-h"))
 		usage(builtin_check_ref_format_usage);
 
diff --git a/builtin/clone.c b/builtin/clone.c
index 52316563795fe3..a57b5ce9e6660c 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -890,6 +890,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 	struct transport_ls_refs_options transport_ls_refs_options =
 		TRANSPORT_LS_REFS_OPTIONS_INIT;
 
+	git_config(git_default_core_config, NULL);
+
 	packet_trace_identity("clone");
 
 	git_config(git_clone_config, NULL);
diff --git a/builtin/column.c b/builtin/column.c
index 158fdf53d9fb9c..69718917526868 100644
--- a/builtin/column.c
+++ b/builtin/column.c
@@ -34,6 +34,8 @@ int cmd_column(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 
+	git_config(git_default_core_config, NULL);
+
 	/* This one is special and must be the first one */
 	if (argc > 1 && starts_with(argv[1], "--command=")) {
 		command = argv[1] + 10;
diff --git a/builtin/credential-store.c b/builtin/credential-store.c
index 62a4f3c2653143..a693b235f34208 100644
--- a/builtin/credential-store.c
+++ b/builtin/credential-store.c
@@ -4,6 +4,7 @@
 #include "credential.h"
 #include "string-list.h"
 #include "parse-options.h"
+#include "config.h"
 
 static struct lock_file credential_lock;
 
@@ -165,6 +166,8 @@ int cmd_credential_store(int argc, const char **argv, const char *prefix)
 
 	umask(077);
 
+	git_config(git_default_config, NULL);
+
 	argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0);
 	if (argc != 1)
 		usage_with_options(usage, options);
diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c
index f045bbbe946dcb..89005baa9d1760 100644
--- a/builtin/fetch-pack.c
+++ b/builtin/fetch-pack.c
@@ -5,6 +5,7 @@
 #include "connect.h"
 #include "oid-array.h"
 #include "protocol.h"
+#include "config.h"
 
 static const char fetch_pack_usage[] =
 "git fetch-pack [--all] [--stdin] [--quiet | -q] [--keep | -k] [--thin] "
@@ -57,6 +58,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
 	struct packet_reader reader;
 	enum protocol_version version;
 
+	git_config(git_default_config, NULL);
 	fetch_if_missing = 0;
 
 	packet_trace_identity("fetch-pack");
diff --git a/builtin/get-tar-commit-id.c b/builtin/get-tar-commit-id.c
index 491af9202dc937..a623518ae7dc23 100644
--- a/builtin/get-tar-commit-id.c
+++ b/builtin/get-tar-commit-id.c
@@ -6,6 +6,7 @@
 #include "tar.h"
 #include "builtin.h"
 #include "quote.h"
+#include "config.h"
 
 static const char builtin_get_tar_commit_id_usage[] =
 "git get-tar-commit-id";
@@ -27,6 +28,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
 	if (argc != 1)
 		usage(builtin_get_tar_commit_id_usage);
 
+	git_config(git_default_config, NULL);
 	n = read_in_full(0, buffer, HEADERSIZE);
 	if (n < 0)
 		die_errno("git get-tar-commit-id: read error");
diff --git a/builtin/log.c b/builtin/log.c
index c211d66d1d0e7f..974b7b4c393fef 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -2339,6 +2339,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 
+	git_config(git_default_config, NULL);
 	argc = parse_options(argc, argv, prefix, options, cherry_usage, 0);
 
 	switch (argc) {
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
index d856085e9414a2..7511e43b892828 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -4,6 +4,7 @@
 #include "ref-filter.h"
 #include "remote.h"
 #include "refs.h"
+#include "config.h"
 
 static const char * const ls_remote_usage[] = {
 	N_("git ls-remote [--heads] [--tags] [--refs] [--upload-pack=<exec>]\n"
@@ -86,6 +87,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
 
 	packet_trace_identity("ls-remote");
 
+	git_config(git_default_config, NULL);
 	if (argc > 1) {
 		int i;
 		CALLOC_ARRAY(pattern, argc);
diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c
index 01d16ef9e5a2d6..df6cc4172120f1 100644
--- a/builtin/mailinfo.c
+++ b/builtin/mailinfo.c
@@ -8,6 +8,7 @@
 #include "strbuf.h"
 #include "mailinfo.h"
 #include "parse-options.h"
+#include "config.h"
 
 static const char * const mailinfo_usage[] = {
 	/* TRANSLATORS: keep <> in "<" mail ">" info. */
@@ -78,6 +79,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 
+	git_config(git_default_config, NULL);
 	setup_mailinfo(&mi);
 	meta_charset.policy = CHARSET_DEFAULT;
 
diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c
index 30952353a370a3..04c3e4639c66d1 100644
--- a/builtin/mailsplit.c
+++ b/builtin/mailsplit.c
@@ -8,6 +8,7 @@
 #include "builtin.h"
 #include "string-list.h"
 #include "strbuf.h"
+#include "config.h"
 
 static const char git_mailsplit_usage[] =
 "git mailsplit [-d<prec>] [-f<n>] [-b] [--keep-cr] -o<directory> [(<mbox>|<Maildir>)...]";
@@ -277,6 +278,7 @@ int cmd_mailsplit(int argc, const char **argv, const char *prefix)
 	const char **argp;
 	static const char *stdin_only[] = { "-", NULL };
 
+	git_config(git_default_config, NULL);
 	for (argp = argv+1; *argp; argp++) {
 		const char *arg = *argp;
 
diff --git a/builtin/merge-index.c b/builtin/merge-index.c
index c0383fe9df9a3e..24e5c1f6ea9f7c 100644
--- a/builtin/merge-index.c
+++ b/builtin/merge-index.c
@@ -1,6 +1,7 @@
 #define USE_THE_INDEX_COMPATIBILITY_MACROS
 #include "builtin.h"
 #include "run-command.h"
+#include "config.h"
 
 static const char *pgm;
 static int one_shot, quiet;
@@ -77,6 +78,8 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)
 	 */
 	signal(SIGCHLD, SIG_DFL);
 
+	git_config(git_default_config, NULL);
+
 	if (argc < 3)
 		usage("git merge-index [-o] [-q] <merge-program> (-a | [--] [<filename>...])");
 
diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c
index 5dc94d6f8804d6..d1fa487ce6d4c1 100644
--- a/builtin/merge-tree.c
+++ b/builtin/merge-tree.c
@@ -7,6 +7,7 @@
 #include "blob.h"
 #include "exec-cmd.h"
 #include "merge-blobs.h"
+#include "config.h"
 
 static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
 
@@ -375,6 +376,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 	if (argc != 4)
 		usage(merge_tree_usage);
 
+	git_config(git_default_config, NULL);
 	buf1 = get_tree_descriptor(r, t+0, argv[1]);
 	buf2 = get_tree_descriptor(r, t+1, argv[2]);
 	buf3 = get_tree_descriptor(r, t+2, argv[3]);
diff --git a/builtin/mktag.c b/builtin/mktag.c
index 5d22909122d195..275feb0667a4bf 100644
--- a/builtin/mktag.c
+++ b/builtin/mktag.c
@@ -84,6 +84,7 @@ int cmd_mktag(int argc, const char **argv, const char *prefix)
 			     builtin_mktag_options,
 			     builtin_mktag_usage, 0);
 
+	git_config(git_default_config, NULL);
 	if (strbuf_read(&buf, 0, 0) < 0)
 		die_errno(_("could not read from stdin"));
 
diff --git a/builtin/mktree.c b/builtin/mktree.c
index 902edba6d2cb43..8a550b6cad29a2 100644
--- a/builtin/mktree.c
+++ b/builtin/mktree.c
@@ -8,6 +8,7 @@
 #include "tree.h"
 #include "parse-options.h"
 #include "object-store.h"
+#include "config.h"
 
 static struct treeent {
 	unsigned mode;
@@ -157,6 +158,7 @@ int cmd_mktree(int ac, const char **av, const char *prefix)
 		OPT_END()
 	};
 
+	git_config(git_default_config, NULL);
 	ac = parse_options(ac, av, prefix, option, mktree_usage, 0);
 	getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf;
 
diff --git a/builtin/pack-refs.c b/builtin/pack-refs.c
index cfbd5c36c7640b..28f79b993862e7 100644
--- a/builtin/pack-refs.c
+++ b/builtin/pack-refs.c
@@ -3,6 +3,7 @@
 #include "parse-options.h"
 #include "refs.h"
 #include "repository.h"
+#include "config.h"
 
 static char const * const pack_refs_usage[] = {
 	N_("git pack-refs [<options>]"),
diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c
index da3273a268b47d..221fd881eb7b34 100644
--- a/builtin/prune-packed.c
+++ b/builtin/prune-packed.c
@@ -1,6 +1,7 @@
 #include "builtin.h"
 #include "parse-options.h"
 #include "prune-packed.h"
+#include "config.h"
 
 static const char * const prune_packed_usage[] = {
 	"git prune-packed [-n | --dry-run] [-q | --quiet]",
@@ -18,6 +19,7 @@ int cmd_prune_packed(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 
+	git_config(git_default_config, NULL);
 	argc = parse_options(argc, argv, prefix, prune_packed_options,
 			     prune_packed_usage, 0);
 
diff --git a/builtin/prune.c b/builtin/prune.c
index c2bcdc07db46a7..7d69a05e71323f 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -9,6 +9,7 @@
 #include "prune-packed.h"
 #include "object-store.h"
 #include "shallow.h"
+#include "config.h"
 
 static const char * const prune_usage[] = {
 	N_("git prune [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]"),
@@ -152,6 +153,8 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
 	};
 	char *s;
 
+	git_config(git_default_config, NULL);
+
 	expire = TIME_MAX;
 	save_commit_buffer = 0;
 	read_replace_refs = 0;
diff --git a/builtin/reflog.c b/builtin/reflog.c
index c943c2aabe6e00..3137a25b911c34 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -407,6 +407,7 @@ int cmd_reflog(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 
+	git_config(git_default_config, NULL);
 	argc = parse_options(argc, argv, prefix, options, reflog_usage,
 			     PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0 |
 			     PARSE_OPT_KEEP_UNKNOWN |
diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c
index fd3538d4f0e692..cdd6fddd65b1e8 100644
--- a/builtin/remote-ext.c
+++ b/builtin/remote-ext.c
@@ -2,6 +2,7 @@
 #include "transport.h"
 #include "run-command.h"
 #include "pkt-line.h"
+#include "config.h"
 
 static const char usage_msg[] =
 	"git remote-ext <remote> <url>";
@@ -198,5 +199,6 @@ int cmd_remote_ext(int argc, const char **argv, const char *prefix)
 	if (argc != 3)
 		usage(usage_msg);
 
+	git_config(git_default_config, NULL);
 	return command_loop(argv[2]);
 }
diff --git a/builtin/remote.c b/builtin/remote.c
index 5f4cde9d784e31..eddd40c8f8759d 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1729,6 +1729,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
 	};
 	int result;
 
+	git_config(git_default_config, NULL);
 	argc = parse_options(argc, argv, prefix, options, builtin_remote_usage,
 		PARSE_OPT_STOP_AT_NON_OPTION);
 
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 8480a59f573e28..2198ba9ac5175e 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -426,6 +426,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
 	struct option *opts = NULL;
 	int onb = 0, osz = 0, unb = 0, usz = 0;
 
+	git_config(git_default_config, NULL);
 	strbuf_addstr(&parsed, "set --");
 	argc = parse_options(argc, argv, prefix, parseopt_opts, parseopt_usage,
 	                     PARSE_OPT_KEEP_DASHDASH);
diff --git a/builtin/show-index.c b/builtin/show-index.c
index 0e0b9fb95bc113..ff2cc3ee48291c 100644
--- a/builtin/show-index.c
+++ b/builtin/show-index.c
@@ -2,6 +2,7 @@
 #include "cache.h"
 #include "pack.h"
 #include "parse-options.h"
+#include "config.h"
 
 static const char *const show_index_usage[] = {
 	"git show-index [--object-format=<hash-algorithm>]",
@@ -23,6 +24,7 @@ int cmd_show_index(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 
+	git_config(git_default_config, NULL);
 	argc = parse_options(argc, argv, prefix, show_index_options, show_index_usage, 0);
 
 	if (hash_name) {
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 7f8a5332f838ac..01400a6abf1be4 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -7,6 +7,7 @@
 #include "tag.h"
 #include "string-list.h"
 #include "parse-options.h"
+#include "config.h"
 
 static const char * const show_ref_usage[] = {
 	N_("git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference] [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [<pattern>...]"),
diff --git a/builtin/stripspace.c b/builtin/stripspace.c
index 1e34cf2bebdf51..166211662f7b70 100644
--- a/builtin/stripspace.c
+++ b/builtin/stripspace.c
@@ -46,10 +46,9 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix)
 	if (argc)
 		usage_with_options(stripspace_usage, options);
 
-	if (mode == STRIP_COMMENTS || mode == COMMENT_LINES) {
+	if (mode == STRIP_COMMENTS || mode == COMMENT_LINES)
 		setup_git_directory_gently(&nongit);
-		git_config(git_default_config, NULL);
-	}
+	git_config(git_default_config, NULL);
 
 	if (strbuf_read(&buf, 0, 1024) < 0)
 		die_errno("could not read the input");
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 2c87ef9364fa6d..62215538cb82d6 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -3510,6 +3510,7 @@ static struct cmd_struct commands[] = {
 int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
 {
 	int i;
+	git_config(git_default_config, NULL);
 	if (argc < 2 || !strcmp(argv[1], "-h"))
 		usage("git submodule--helper <command>");
 
diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c
index 98d028dae67908..b639d78b2cabf1 100644
--- a/builtin/upload-archive.c
+++ b/builtin/upload-archive.c
@@ -8,6 +8,7 @@
 #include "sideband.h"
 #include "run-command.h"
 #include "strvec.h"
+#include "config.h"
 
 static const char upload_archive_usage[] =
 	"git upload-archive <repo>";
@@ -28,6 +29,7 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix)
 	if (!enter_repo(argv[1], 0))
 		die("'%s' does not appear to be a git repository", argv[1]);
 
+	git_config(git_default_config, NULL);
 	init_archivers();
 
 	/* put received options in sent_argv[] */
@@ -79,6 +81,7 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)
 {
 	struct child_process writer = CHILD_PROCESS_INIT;
 
+	git_config(git_default_config, NULL);
 	if (argc == 2 && !strcmp(argv[1], "-h"))
 		usage(upload_archive_usage);
 
diff --git a/http-backend.c b/http-backend.c
index 81a7229ece00ad..a79f55791cf90e 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -783,6 +783,7 @@ int cmd_main(int argc, const char **argv)
 	setup_path();
 	if (!enter_repo(dir, 0))
 		not_found(&hdr, "Not a git repository: '%s'", dir);
+	git_config(git_default_config, NULL);
 	if (!getenv("GIT_HTTP_EXPORT_ALL") &&
 	    access("git-daemon-export-ok", F_OK) )
 		not_found(&hdr, "Repository not exported: '%s'", dir);
diff --git a/refs.c b/refs.c
index 1a964505f92ad6..1a63cd050f6546 100644
--- a/refs.c
+++ b/refs.c
@@ -1318,7 +1318,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti
 		}
 		string_list_append(hide_refs, ref);
 	}
-	return 0;
+	return git_default_config(var, value, NULL);
 }
 
 int ref_is_hidden(const char *refname, const char *refname_full)

From 259c006de1f9ee52fb54f1fdf77b212930c956ba Mon Sep 17 00:00:00 2001
From: Jeff Hostetler <jeffhost@microsoft.com>
Date: Fri, 25 Mar 2022 16:56:04 -0400
Subject: [PATCH 205/265] compat/fsmonitor/fsm-*-win32: support long paths

Update wchar_t buffers to use MAX_LONG_PATH instead of MAX_PATH and call
xutftowcs_long_path() in the Win32 backend source files.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
---
 compat/fsmonitor/fsm-health-win32.c   |  6 +++---
 compat/fsmonitor/fsm-listen-win32.c   | 24 ++++++++++++------------
 compat/fsmonitor/fsm-settings-win32.c |  8 ++++----
 3 files changed, 19 insertions(+), 19 deletions(-)

diff --git a/compat/fsmonitor/fsm-health-win32.c b/compat/fsmonitor/fsm-health-win32.c
index 2ea08c1d4e81ec..5551d6d97dee87 100644
--- a/compat/fsmonitor/fsm-health-win32.c
+++ b/compat/fsmonitor/fsm-health-win32.c
@@ -32,7 +32,7 @@ struct fsm_health_data
 
 	struct wt_moved
 	{
-		wchar_t wpath[MAX_PATH + 1];
+		wchar_t wpath[MAX_LONG_PATH + 1];
 		BY_HANDLE_FILE_INFORMATION bhfi;
 	} wt_moved;
 };
@@ -141,8 +141,8 @@ static int has_worktree_moved(struct fsmonitor_daemon_state *state,
 		return 0;
 
 	case CTX_INIT:
-		if (xutftowcs_path(data->wt_moved.wpath,
-				   state->path_worktree_watch.buf) < 0) {
+		if (xutftowcs_long_path(data->wt_moved.wpath,
+					state->path_worktree_watch.buf) < 0) {
 			error(_("could not convert to wide characters: '%s'"),
 			      state->path_worktree_watch.buf);
 			return -1;
diff --git a/compat/fsmonitor/fsm-listen-win32.c b/compat/fsmonitor/fsm-listen-win32.c
index be2d93f47b2e6f..c3bf3a86e6bb31 100644
--- a/compat/fsmonitor/fsm-listen-win32.c
+++ b/compat/fsmonitor/fsm-listen-win32.c
@@ -25,7 +25,7 @@ struct one_watch
 	DWORD count;
 
 	struct strbuf path;
-	wchar_t wpath_longname[MAX_PATH + 1];
+	wchar_t wpath_longname[MAX_LONG_PATH + 1];
 	DWORD wpath_longname_len;
 
 	HANDLE hDir;
@@ -128,8 +128,8 @@ static int normalize_path_in_utf8(wchar_t *wpath, DWORD wpath_len,
  */
 static void check_for_shortnames(struct one_watch *watch)
 {
-	wchar_t buf_in[MAX_PATH + 1];
-	wchar_t buf_out[MAX_PATH + 1];
+	wchar_t buf_in[MAX_LONG_PATH + 1];
+	wchar_t buf_out[MAX_LONG_PATH + 1];
 	wchar_t *last_slash = NULL;
 	wchar_t *last_bslash = NULL;
 	wchar_t *last;
@@ -138,7 +138,7 @@ static void check_for_shortnames(struct one_watch *watch)
 	wcscpy(buf_in, watch->wpath_longname);
 	wcscpy(buf_in + watch->wpath_longname_len, L".git");
 
-	if (!GetShortPathNameW(buf_in, buf_out, MAX_PATH))
+	if (!GetShortPathNameW(buf_in, buf_out, MAX_LONG_PATH))
 		return;
 
 	last_slash = wcsrchr(buf_out, L'/');
@@ -196,8 +196,8 @@ static enum get_relative_result get_relative_longname(
 	const wchar_t *wpath, DWORD wpath_len,
 	wchar_t *wpath_longname)
 {
-	wchar_t buf_in[2 * MAX_PATH + 1];
-	wchar_t buf_out[MAX_PATH + 1];
+	wchar_t buf_in[2 * MAX_LONG_PATH + 1];
+	wchar_t buf_out[MAX_LONG_PATH + 1];
 	DWORD root_len;
 
 	/* Build L"<wt-root-path>/<event-rel-path>" */
@@ -211,7 +211,7 @@ static enum get_relative_result get_relative_longname(
 	 * shortname or a longname.  This routine allows either to be
 	 * given as input.
 	 */
-	if (!GetLongPathNameW(buf_in, buf_out, MAX_PATH)) {
+	if (!GetLongPathNameW(buf_in, buf_out, MAX_LONG_PATH)) {
 		/*
 		 * The shortname to longname conversion can fail for
 		 * various reasons, for example if the file has been
@@ -275,10 +275,10 @@ static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,
 		FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
 	HANDLE hDir;
 	DWORD len_longname;
-	wchar_t wpath[MAX_PATH + 1];
-	wchar_t wpath_longname[MAX_PATH + 1];
+	wchar_t wpath[MAX_LONG_PATH + 1];
+	wchar_t wpath_longname[MAX_LONG_PATH + 1];
 
-	if (xutftowcs_path(wpath, path) < 0) {
+	if (xutftowcs_long_path(wpath, path) < 0) {
 		error(_("could not convert to wide characters: '%s'"), path);
 		return NULL;
 	}
@@ -293,7 +293,7 @@ static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,
 		return NULL;
 	}
 
-	if (!GetLongPathNameW(wpath, wpath_longname, MAX_PATH)) {
+	if (!GetLongPathNameW(wpath, wpath_longname, MAX_LONG_PATH)) {
 		error(_("[GLE %ld] could not get longname of '%s'"),
 		      GetLastError(), path);
 		CloseHandle(hDir);
@@ -522,7 +522,7 @@ static int process_worktree_events(struct fsmonitor_daemon_state *state)
 	struct string_list cookie_list = STRING_LIST_INIT_DUP;
 	struct fsmonitor_batch *batch = NULL;
 	const char *p = watch->buffer;
-	wchar_t wpath_longname[MAX_PATH + 1];
+	wchar_t wpath_longname[MAX_LONG_PATH + 1];
 
 	/*
 	 * If the kernel gets more events than will fit in the kernel
diff --git a/compat/fsmonitor/fsm-settings-win32.c b/compat/fsmonitor/fsm-settings-win32.c
index 907655720bb41e..cc57790ea546bb 100644
--- a/compat/fsmonitor/fsm-settings-win32.c
+++ b/compat/fsmonitor/fsm-settings-win32.c
@@ -76,8 +76,8 @@ static enum fsmonitor_reason check_vfs4git(struct repository *r)
  */
 static enum fsmonitor_reason check_remote(struct repository *r)
 {
-	wchar_t wpath[MAX_PATH];
-	wchar_t wfullpath[MAX_PATH];
+	wchar_t wpath[MAX_LONG_PATH];
+	wchar_t wfullpath[MAX_LONG_PATH];
 	size_t wlen;
 	UINT driveType;
 
@@ -85,7 +85,7 @@ static enum fsmonitor_reason check_remote(struct repository *r)
 	 * Do everything in wide chars because the drive letter might be
 	 * a multi-byte sequence.  See win32_has_dos_drive_prefix().
 	 */
-	if (xutftowcs_path(wpath, r->worktree) < 0)
+	if (xutftowcs_long_path(wpath, r->worktree) < 0)
 		return FSMONITOR_REASON_ERROR;
 
 	/*
@@ -103,7 +103,7 @@ static enum fsmonitor_reason check_remote(struct repository *r)
 	 * slashes to backslashes.  This is essential to get GetDriveTypeW()
 	 * correctly handle some UNC "\\server\share\..." paths.
 	 */
-	if (!GetFullPathNameW(wpath, MAX_PATH, wfullpath, NULL))
+	if (!GetFullPathNameW(wpath, MAX_LONG_PATH, wfullpath, NULL))
 		return FSMONITOR_REASON_ERROR;
 
 	driveType = GetDriveTypeW(wfullpath);

From ec2dc25145b2b6f1134ef42f28679f27114dc2c4 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 6 Sep 2016 09:50:33 +0200
Subject: [PATCH 206/265] Unbreak interactive GPG prompt upon signing

With the recent update in efee955 (gpg-interface: check gpg signature
creation status, 2016-06-17), we ask GPG to send all status updates to
stderr, and then catch the stderr in an strbuf.

But GPG might fail, and send error messages to stderr. And we simply
do not show them to the user.

Even worse: this swallows any interactive prompt for a passphrase. And
detaches stderr from the tty so that the passphrase cannot be read.

So while the first problem could be fixed (by printing the captured
stderr upon error), the second problem cannot be easily fixed, and
presents a major regression.

So let's just revert commit efee9553a4f97b2ecd8f49be19606dd4cf7d9c28.

This fixes https://github.com/git-for-windows/git/issues/871

Cc: Michael J Gruber <git@drmicha.warpmail.net>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 gpg-interface.c | 15 ++-------------
 t/t7004-tag.sh  | 31 -------------------------------
 2 files changed, 2 insertions(+), 44 deletions(-)

diff --git a/gpg-interface.c b/gpg-interface.c
index 280f1fa1a58233..e077a2ebfbdbf2 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -934,12 +934,9 @@ static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,
 	struct child_process gpg = CHILD_PROCESS_INIT;
 	int ret;
 	size_t bottom;
-	const char *cp;
-	struct strbuf gpg_status = STRBUF_INIT;
 
 	strvec_pushl(&gpg.args,
 		     use_format->program,
-		     "--status-fd=2",
 		     "-bsau", signing_key,
 		     NULL);
 
@@ -951,18 +948,10 @@ static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,
 	 */
 	sigchain_push(SIGPIPE, SIG_IGN);
 	ret = pipe_command(&gpg, buffer->buf, buffer->len,
-			   signature, 1024, &gpg_status, 0);
+			   signature, 1024, NULL, 0);
 	sigchain_pop(SIGPIPE);
 
-	for (cp = gpg_status.buf;
-	     cp && (cp = strstr(cp, "[GNUPG:] SIG_CREATED "));
-	     cp++) {
-		if (cp == gpg_status.buf || cp[-1] == '\n')
-			break; /* found */
-	}
-	ret |= !cp;
-	strbuf_release(&gpg_status);
-	if (ret)
+	if (ret || signature->len == bottom)
 		return error(_("gpg failed to sign the data"));
 
 	/* Strip CR from the line endings, in case we are on Windows. */
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 9aa1660651b8a9..4e56d5c70124d0 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -1375,30 +1375,6 @@ test_expect_success GPG \
 	'test_config user.signingkey BobTheMouse &&
 	test_must_fail git tag -s -m tail tag-gpg-failure'
 
-# try to produce invalid signature
-test_expect_success GPG \
-	'git tag -s fails if gpg is misconfigured (bad signature format)' \
-	'test_config gpg.program echo &&
-	 test_must_fail git tag -s -m tail tag-gpg-failure'
-
-# try to produce invalid signature
-test_expect_success GPG 'git verifies tag is valid with double signature' '
-	git tag -s -m tail tag-gpg-double-sig &&
-	git cat-file tag tag-gpg-double-sig >tag &&
-	othersigheader=$(test_oid othersigheader) &&
-	sed -ne "/^\$/q;p" tag >new-tag &&
-	cat <<-EOM >>new-tag &&
-	$othersigheader -----BEGIN PGP SIGNATURE-----
-	 someinvaliddata
-	 -----END PGP SIGNATURE-----
-	EOM
-	sed -e "1,/^tagger/d" tag >>new-tag &&
-	new_tag=$(git hash-object -t tag -w new-tag) &&
-	git update-ref refs/tags/tag-gpg-double-sig $new_tag &&
-	git verify-tag tag-gpg-double-sig &&
-	git fsck
-'
-
 # try to sign with bad user.signingkey
 test_expect_success GPGSM \
 	'git tag -s fails if gpgsm is misconfigured (bad key)' \
@@ -1406,13 +1382,6 @@ test_expect_success GPGSM \
 	 test_config gpg.format x509 &&
 	 test_must_fail git tag -s -m tail tag-gpg-failure'
 
-# try to produce invalid signature
-test_expect_success GPGSM \
-	'git tag -s fails if gpgsm is misconfigured (bad signature format)' \
-	'test_config gpg.x509.program echo &&
-	 test_config gpg.format x509 &&
-	 test_must_fail git tag -s -m tail tag-gpg-failure'
-
 # try to verify without gpg:
 
 rm -rf gpghome

From 96747f4b405dcabcc15d35c9ecef696832cc731d Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 20 Jul 2017 22:45:01 +0200
Subject: [PATCH 207/265] mingw: explicitly specify with which cmd to prefix
 the cmdline

The main idea of this patch is that even if we have to look up the
absolute path of the script, if only the basename was specified as
argv[0], then we should use that basename on the command line, too, not
the absolute path.

This patch will also help with the upcoming patch where we automatically
substitute "sh ..." by "busybox sh ..." if "sh" is not in the PATH but
"busybox" is: we will do that by substituting the actual executable, but
still keep prepending "sh" to the command line.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index dc7893a41d1d4a..9f22659f0a8f55 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1812,8 +1812,8 @@ static int is_msys2_sh(const char *cmd)
 }
 
 static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaenv,
-			      const char *dir,
-			      int prepend_cmd, int fhin, int fhout, int fherr)
+			      const char *dir, const char *prepend_cmd,
+			      int fhin, int fhout, int fherr)
 {
 	static int restrict_handle_inheritance = -1;
 	STARTUPINFOEXW si;
@@ -1904,9 +1904,9 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
 	/* concatenate argv, quoting args as we go */
 	strbuf_init(&args, 0);
 	if (prepend_cmd) {
-		char *quoted = (char *)quote_arg(cmd);
+		char *quoted = (char *)quote_arg(prepend_cmd);
 		strbuf_addstr(&args, quoted);
-		if (quoted != cmd)
+		if (quoted != prepend_cmd)
 			free(quoted);
 	}
 	for (; *argv; argv++) {
@@ -2065,7 +2065,8 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
 	return (pid_t)pi.dwProcessId;
 }
 
-static pid_t mingw_spawnv(const char *cmd, const char **argv, int prepend_cmd)
+static pid_t mingw_spawnv(const char *cmd, const char **argv,
+			  const char *prepend_cmd)
 {
 	return mingw_spawnve_fd(cmd, argv, NULL, NULL, prepend_cmd, 0, 1, 2);
 }
@@ -2093,14 +2094,14 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **deltaenv,
 				pid = -1;
 			}
 			else {
-				pid = mingw_spawnve_fd(iprog, argv, deltaenv, dir, 1,
+				pid = mingw_spawnve_fd(iprog, argv, deltaenv, dir, interpr,
 						       fhin, fhout, fherr);
 				free(iprog);
 			}
 			argv[0] = argv0;
 		}
 		else
-			pid = mingw_spawnve_fd(prog, argv, deltaenv, dir, 0,
+			pid = mingw_spawnve_fd(prog, argv, deltaenv, dir, NULL,
 					       fhin, fhout, fherr);
 		free(prog);
 	}
@@ -2128,7 +2129,7 @@ static int try_shell_exec(const char *cmd, char *const *argv)
 		argv2[0] = (char *)cmd;	/* full path to the script file */
 		COPY_ARRAY(&argv2[1], &argv[1], argc);
 		exec_id = trace2_exec(prog, argv2);
-		pid = mingw_spawnv(prog, argv2, 1);
+		pid = mingw_spawnv(prog, argv2, interpr);
 		if (pid >= 0) {
 			int status;
 			if (waitpid(pid, &status, 0) < 0)
@@ -2152,7 +2153,7 @@ int mingw_execv(const char *cmd, char *const *argv)
 		int exec_id;
 
 		exec_id = trace2_exec(cmd, (const char **)argv);
-		pid = mingw_spawnv(cmd, (const char **)argv, 0);
+		pid = mingw_spawnv(cmd, (const char **)argv, NULL);
 		if (pid < 0) {
 			trace2_exec_result(exec_id, -1);
 			return -1;

From 304ecc405fcac7031ef554eb2bea17f69edced3e Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 20 Jul 2017 20:41:29 +0200
Subject: [PATCH 208/265] mingw: when path_lookup() failed, try BusyBox

BusyBox comes with a ton of applets ("applet" being the identical
concept to Git's "builtins"). And similar to Git's builtins, the applets
can be called via `busybox <command>`, or the BusyBox executable can be
copied/hard-linked to the command name.

The similarities do not end here. Just as with Git's builtins, it is
problematic that BusyBox' hard-linked applets cannot easily be put into
a .zip file: .zip archives have no concept of hard-links and therefore
would store identical copies (and also extract identical copies,
"inflating" the archive unnecessarily).

To counteract that issue, MinGit already ships without hard-linked
copies of the builtins, and the plan is to do the same with BusyBox'
applets: simply ship busybox.exe as single executable, without
hard-linked applets.

To accommodate that, Git is being taught by this commit a very special
trick, exploiting the fact that it is possible to call an executable
with a command-line whose argv[0] is different from the executable's
name: when `sh` is to be spawned, and no `sh` is found in the PATH, but
busybox.exe is, use that executable (with unchanged argv).

Likewise, if any executable to be spawned is not on the PATH, but
busybox.exe is found, parse the output of `busybox.exe --help` to find
out what applets are included, and if the command matches an included
applet name, use busybox.exe to execute it.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c   | 63 ++++++++++++++++++++++++++++++++++++++++++++++++
 t/t0014-alias.sh |  2 +-
 2 files changed, 64 insertions(+), 1 deletion(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 9f22659f0a8f55..8b21326d2a3db0 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -14,6 +14,7 @@
 #include <sspi.h>
 #include "win32/fscache.h"
 #include "../attr.h"
+#include "../string-list.h"
 
 #define HCAST(type, handle) ((type)(intptr_t)handle)
 
@@ -1585,6 +1586,65 @@ static char *lookup_prog(const char *dir, int dirlen, const char *cmd,
 	return NULL;
 }
 
+static char *path_lookup(const char *cmd, int exe_only);
+
+static char *is_busybox_applet(const char *cmd)
+{
+	static struct string_list applets = STRING_LIST_INIT_DUP;
+	static char *busybox_path;
+	static int busybox_path_initialized;
+
+	/* Avoid infinite loop */
+	if (!strncasecmp(cmd, "busybox", 7) &&
+	    (!cmd[7] || !strcasecmp(cmd + 7, ".exe")))
+		return NULL;
+
+	if (!busybox_path_initialized) {
+		busybox_path = path_lookup("busybox.exe", 1);
+		busybox_path_initialized = 1;
+	}
+
+	/* Assume that sh is compiled in... */
+	if (!busybox_path || !strcasecmp(cmd, "sh"))
+		return xstrdup_or_null(busybox_path);
+
+	if (!applets.nr) {
+		struct child_process cp = CHILD_PROCESS_INIT;
+		struct strbuf buf = STRBUF_INIT;
+		char *p;
+
+		strvec_pushl(&cp.args, busybox_path, "--help", NULL);
+
+		if (capture_command(&cp, &buf, 2048)) {
+			string_list_append(&applets, "");
+			return NULL;
+		}
+
+		/* parse output */
+		p = strstr(buf.buf, "Currently defined functions:\n");
+		if (!p) {
+			warning("Could not parse output of busybox --help");
+			string_list_append(&applets, "");
+			return NULL;
+		}
+		p = strchrnul(p, '\n');
+		for (;;) {
+			size_t len;
+
+			p += strspn(p, "\n\t ,");
+			len = strcspn(p, "\n\t ,");
+			if (!len)
+				break;
+			p[len] = '\0';
+			string_list_insert(&applets, p);
+			p = p + len + 1;
+		}
+	}
+
+	return string_list_has_string(&applets, cmd) ?
+		xstrdup(busybox_path) : NULL;
+}
+
 /*
  * Determines the absolute path of cmd using the split path in path.
  * If cmd contains a slash or backslash, no lookup is performed.
@@ -1613,6 +1673,9 @@ static char *path_lookup(const char *cmd, int exe_only)
 		path = sep + 1;
 	}
 
+	if (!prog && !isexe)
+		prog = is_busybox_applet(cmd);
+
 	return prog;
 }
 
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 288e08299abc4e..06669cab78eec6 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -39,7 +39,7 @@ test_expect_success 'looping aliases - internal execution' '
 
 test_expect_success 'run-command formats empty args properly' '
 	test_must_fail env GIT_TRACE=1 git frotz a "" b " " c 2>actual.raw &&
-	sed -ne "/run_command:/s/.*trace: run_command: //p" actual.raw >actual &&
+	sed -ne "/run_command: git-frotz/s/.*trace: run_command: //p" actual.raw >actual &&
 	echo "git-frotz a '\'''\'' b '\'' '\'' c" >expect &&
 	test_cmp expect actual
 '

From 66d99c5ed4fa5bf3c507468c43129ee30c84cc9f Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Sat, 5 Aug 2017 22:23:36 +0200
Subject: [PATCH 209/265] test-lib: avoid unnecessary Perl invocation

It is a bit strange, and even undesirable, to require Perl just to run
the test suite even when NO_PERL was set.

This patch does not fix this problem by any stretch of imagination.
However, it fixes *the* Perl invocation that *every single* test script
has to run.

While at it, it makes the source code also more grep'able, as the code
that unsets some, but not all, GIT_* environment variables just became a
*lot* more explicit. And all that while still reducing the total number
of lines.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/test-lib.sh | 29 ++++++++++++-----------------
 1 file changed, 12 insertions(+), 17 deletions(-)

diff --git a/t/test-lib.sh b/t/test-lib.sh
index 531cef097db0c6..605e9d89e91d88 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -459,23 +459,18 @@ EDITOR=:
 # /usr/xpg4/bin/sh and /bin/ksh to bail out.  So keep the unsets
 # deriving from the command substitution clustered with the other
 # ones.
-unset VISUAL EMAIL LANGUAGE $("$PERL_PATH" -e '
-	my @env = keys %ENV;
-	my $ok = join("|", qw(
-		TRACE
-		DEBUG
-		TEST
-		.*_TEST
-		PROVE
-		VALGRIND
-		UNZIP
-		PERF_
-		CURL_VERBOSE
-		TRACE_CURL
-	));
-	my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env);
-	print join("\n", @vars);
-')
+unset VISUAL EMAIL LANGUAGE $(env | sed -n \
+	-e '/^GIT_TRACE/d' \
+	-e '/^GIT_DEBUG/d' \
+	-e '/^GIT_TEST/d' \
+	-e '/^GIT_.*_TEST/d' \
+	-e '/^GIT_PROVE/d' \
+	-e '/^GIT_VALGRIND/d' \
+	-e '/^GIT_UNZIP/d' \
+	-e '/^GIT_PERF_/d' \
+	-e '/^GIT_CURL_VERBOSE/d' \
+	-e '/^GIT_TRACE_CURL/d' \
+	-e 's/^\(GIT_[^=]*\)=.*/\1/p')
 unset XDG_CACHE_HOME
 unset XDG_CONFIG_HOME
 unset GITPERLLIB

From 29a5f0db8017ae44356b20b225cdf5f90158bd81 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 7 Jun 2018 10:47:25 +0200
Subject: [PATCH 210/265] tests: replace mingw_test_cmp with a helper in C

This helper is slightly more performant than the script with MSYS2's
Bash. And a lot more readable.

To accommodate t1050, which wants to compare files weighing in with 3MB
(falling outside of t1050's malloc limit of 1.5MB), we simply lift the
allocation limit by setting the environment variable GIT_ALLOC_LIMIT to
zero when calling the helper.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile                |  1 +
 t/helper/test-cmp.c     | 73 +++++++++++++++++++++++++++++++++++++++++
 t/helper/test-tool.c    |  1 +
 t/helper/test-tool.h    |  1 +
 t/test-lib-functions.sh | 68 +-------------------------------------
 t/test-lib.sh           |  2 +-
 6 files changed, 78 insertions(+), 68 deletions(-)
 create mode 100644 t/helper/test-cmp.c

diff --git a/Makefile b/Makefile
index 820166ce3abb4f..d89959b001acaa 100644
--- a/Makefile
+++ b/Makefile
@@ -713,6 +713,7 @@ TEST_BUILTINS_OBJS += test-advise.o
 TEST_BUILTINS_OBJS += test-bitmap.o
 TEST_BUILTINS_OBJS += test-bloom.o
 TEST_BUILTINS_OBJS += test-chmtime.o
+TEST_BUILTINS_OBJS += test-cmp.o
 TEST_BUILTINS_OBJS += test-config.o
 TEST_BUILTINS_OBJS += test-crontab.o
 TEST_BUILTINS_OBJS += test-csprng.o
diff --git a/t/helper/test-cmp.c b/t/helper/test-cmp.c
new file mode 100644
index 00000000000000..1c646a54bf609b
--- /dev/null
+++ b/t/helper/test-cmp.c
@@ -0,0 +1,73 @@
+#include "test-tool.h"
+#include "git-compat-util.h"
+#include "strbuf.h"
+#include "gettext.h"
+#include "parse-options.h"
+#include "run-command.h"
+
+#ifdef WIN32
+#define NO_SUCH_DIR "\\\\.\\GLOBALROOT\\invalid"
+#else
+#define NO_SUCH_DIR "/dev/null"
+#endif
+
+static int run_diff(const char *path1, const char *path2)
+{
+	const char *argv[] = {
+		"diff", "--no-index", NULL, NULL, NULL
+	};
+	const char *env[] = {
+		"GIT_PAGER=cat",
+		"GIT_DIR=" NO_SUCH_DIR,
+		"HOME=" NO_SUCH_DIR,
+		NULL
+	};
+
+	argv[2] = path1;
+	argv[3] = path2;
+	return run_command_v_opt_cd_env(argv,
+					RUN_COMMAND_NO_STDIN | RUN_GIT_CMD,
+					NULL, env);
+}
+
+int cmd__cmp(int argc, const char **argv)
+{
+	FILE *f0, *f1;
+	struct strbuf b0 = STRBUF_INIT, b1 = STRBUF_INIT;
+
+	if (argc != 3)
+		die("Require exactly 2 arguments, got %d", argc);
+
+	if (!(f0 = !strcmp(argv[1], "-") ? stdin : fopen(argv[1], "r")))
+		return error_errno("could not open '%s'", argv[1]);
+	if (!(f1 = !strcmp(argv[2], "-") ? stdin : fopen(argv[2], "r"))) {
+		fclose(f0);
+		return error_errno("could not open '%s'", argv[2]);
+	}
+
+	for (;;) {
+		int r0 = strbuf_getline(&b0, f0);
+		int r1 = strbuf_getline(&b1, f1);
+
+		if (r0 == EOF) {
+			fclose(f0);
+			fclose(f1);
+			strbuf_release(&b0);
+			strbuf_release(&b1);
+			if (r1 == EOF)
+				return 0;
+cmp_failed:
+			if (!run_diff(argv[1], argv[2]))
+				die("Huh? 'diff --no-index %s %s' succeeded",
+				    argv[1], argv[2]);
+			return 1;
+		}
+		if (r1 == EOF || strbuf_cmp(&b0, &b1)) {
+			fclose(f0);
+			fclose(f1);
+			strbuf_release(&b0);
+			strbuf_release(&b1);
+			goto cmp_failed;
+		}
+	}
+}
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index 0424f7adf5d69c..d3a3991c999b6f 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -18,6 +18,7 @@ static struct test_cmd cmds[] = {
 	{ "bitmap", cmd__bitmap },
 	{ "bloom", cmd__bloom },
 	{ "chmtime", cmd__chmtime },
+	{ "cmp", cmd__cmp },
 	{ "config", cmd__config },
 	{ "crontab", cmd__crontab },
 	{ "csprng", cmd__csprng },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index c876e8246fbdff..d53ec6ff1e3bc5 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -8,6 +8,7 @@ int cmd__advise_if_enabled(int argc, const char **argv);
 int cmd__bitmap(int argc, const char **argv);
 int cmd__bloom(int argc, const char **argv);
 int cmd__chmtime(int argc, const char **argv);
+int cmd__cmp(int argc, const char **argv);
 int cmd__config(int argc, const char **argv);
 int cmd__crontab(int argc, const char **argv);
 int cmd__csprng(int argc, const char **argv);
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 50970d3e03eb8a..6a9da51579ce80 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -1239,7 +1239,7 @@ test_expect_code () {
 
 test_cmp () {
 	test "$#" -ne 2 && BUG "2 param"
-	eval "$GIT_TEST_CMP" '"$@"'
+	GIT_ALLOC_LIMIT=0 eval "$GIT_TEST_CMP" '"$@"'
 }
 
 # Check that the given config key has the expected value.
@@ -1540,72 +1540,6 @@ test_skip_or_die () {
 	error "$2"
 }
 
-# The following mingw_* functions obey POSIX shell syntax, but are actually
-# bash scripts, and are meant to be used only with bash on Windows.
-
-# A test_cmp function that treats LF and CRLF equal and avoids to fork
-# diff when possible.
-mingw_test_cmp () {
-	# Read text into shell variables and compare them. If the results
-	# are different, use regular diff to report the difference.
-	local test_cmp_a= test_cmp_b=
-
-	# When text came from stdin (one argument is '-') we must feed it
-	# to diff.
-	local stdin_for_diff=
-
-	# Since it is difficult to detect the difference between an
-	# empty input file and a failure to read the files, we go straight
-	# to diff if one of the inputs is empty.
-	if test -s "$1" && test -s "$2"
-	then
-		# regular case: both files non-empty
-		mingw_read_file_strip_cr_ test_cmp_a <"$1"
-		mingw_read_file_strip_cr_ test_cmp_b <"$2"
-	elif test -s "$1" && test "$2" = -
-	then
-		# read 2nd file from stdin
-		mingw_read_file_strip_cr_ test_cmp_a <"$1"
-		mingw_read_file_strip_cr_ test_cmp_b
-		stdin_for_diff='<<<"$test_cmp_b"'
-	elif test "$1" = - && test -s "$2"
-	then
-		# read 1st file from stdin
-		mingw_read_file_strip_cr_ test_cmp_a
-		mingw_read_file_strip_cr_ test_cmp_b <"$2"
-		stdin_for_diff='<<<"$test_cmp_a"'
-	fi
-	test -n "$test_cmp_a" &&
-	test -n "$test_cmp_b" &&
-	test "$test_cmp_a" = "$test_cmp_b" ||
-	eval "diff -u \"\$@\" $stdin_for_diff"
-}
-
-# $1 is the name of the shell variable to fill in
-mingw_read_file_strip_cr_ () {
-	# Read line-wise using LF as the line separator
-	# and use IFS to strip CR.
-	local line
-	while :
-	do
-		if IFS=$'\r' read -r -d $'\n' line
-		then
-			# good
-			line=$line$'\n'
-		else
-			# we get here at EOF, but also if the last line
-			# was not terminated by LF; in the latter case,
-			# some text was read
-			if test -z "$line"
-			then
-				# EOF, really
-				break
-			fi
-		fi
-		eval "$1=\$$1\$line"
-	done
-}
-
 # Like "env FOO=BAR some-program", but run inside a subshell, which means
 # it also works for shell functions (though those functions cannot impact
 # the environment outside of the test_env invocation).
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 605e9d89e91d88..20967ce4ded5be 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -1626,7 +1626,7 @@ case $uname_s in
 	test_set_prereq SED_STRIPS_CR
 	test_set_prereq GREP_STRIPS_CR
 	test_set_prereq WINDOWS
-	GIT_TEST_CMP=mingw_test_cmp
+	GIT_TEST_CMP="test-tool cmp"
 	;;
 *CYGWIN*)
 	test_set_prereq POSIXPERM

From 789f4fd3c9201846a08c38ac1d4269fd79d98939 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 20 Jul 2017 22:18:56 +0200
Subject: [PATCH 211/265] test-tool: learn to act as a drop-in replacement for
 `iconv`

It is convenient to assume that everybody who wants to build & test Git
has access to a working `iconv` executable (after all, we already pretty
much require libiconv).

However, that limits esoteric test scenarios such as Git for Windows',
where an end user installation has to ship with `iconv` for the sole
purpose of being testable. That payload serves no other purpose.

So let's just have a test helper (to be able to test Git, the test
helpers have to be available, after all) to act as `iconv` replacement.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile              |  1 +
 t/helper/test-iconv.c | 47 +++++++++++++++++++++++++++++++++++++++++++
 t/helper/test-tool.c  |  1 +
 t/helper/test-tool.h  |  1 +
 4 files changed, 50 insertions(+)
 create mode 100644 t/helper/test-iconv.c

diff --git a/Makefile b/Makefile
index d89959b001acaa..b2b9ae9ab51ab3 100644
--- a/Makefile
+++ b/Makefile
@@ -735,6 +735,7 @@ TEST_BUILTINS_OBJS += test-getcwd.o
 TEST_BUILTINS_OBJS += test-hash-speed.o
 TEST_BUILTINS_OBJS += test-hash.o
 TEST_BUILTINS_OBJS += test-hashmap.o
+TEST_BUILTINS_OBJS += test-iconv.o
 TEST_BUILTINS_OBJS += test-index-version.o
 TEST_BUILTINS_OBJS += test-json-writer.o
 TEST_BUILTINS_OBJS += test-lazy-init-name-hash.o
diff --git a/t/helper/test-iconv.c b/t/helper/test-iconv.c
new file mode 100644
index 00000000000000..d3c772fddf990b
--- /dev/null
+++ b/t/helper/test-iconv.c
@@ -0,0 +1,47 @@
+#include "test-tool.h"
+#include "git-compat-util.h"
+#include "strbuf.h"
+#include "gettext.h"
+#include "parse-options.h"
+#include "utf8.h"
+
+int cmd__iconv(int argc, const char **argv)
+{
+	struct strbuf buf = STRBUF_INIT;
+	char *from = NULL, *to = NULL, *p;
+	size_t len;
+	int ret = 0;
+	const char * const iconv_usage[] = {
+		N_("test-helper --iconv [<options>]"),
+		NULL
+	};
+	struct option options[] = {
+		OPT_STRING('f', "from-code", &from, "encoding", "from"),
+		OPT_STRING('t', "to-code", &to, "encoding", "to"),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, NULL, options,
+			iconv_usage, 0);
+
+	if (argc > 1 || !from || !to)
+		usage_with_options(iconv_usage, options);
+
+	if (!argc) {
+		if (strbuf_read(&buf, 0, 2048) < 0)
+			die_errno("Could not read from stdin");
+	} else if (strbuf_read_file(&buf, argv[0], 2048) < 0)
+		die_errno("Could not read from '%s'", argv[0]);
+
+	p = reencode_string_len(buf.buf, buf.len, to, from, &len);
+	if (!p)
+		die_errno("Could not reencode");
+	if (write(1, p, len) < 0)
+		ret = !!error_errno("Could not write %"PRIuMAX" bytes",
+				    (uintmax_t)len);
+
+	strbuf_release(&buf);
+	free(p);
+
+	return ret;
+}
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index d3a3991c999b6f..8d9d03732adb57 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -39,6 +39,7 @@ static struct test_cmd cmds[] = {
 	{ "getcwd", cmd__getcwd },
 	{ "hashmap", cmd__hashmap },
 	{ "hash-speed", cmd__hash_speed },
+	{ "iconv", cmd__iconv },
 	{ "index-version", cmd__index_version },
 	{ "json-writer", cmd__json_writer },
 	{ "lazy-init-name-hash", cmd__lazy_init_name_hash },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index d53ec6ff1e3bc5..556114ae064b69 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -30,6 +30,7 @@ int cmd__genzeros(int argc, const char **argv);
 int cmd__getcwd(int argc, const char **argv);
 int cmd__hashmap(int argc, const char **argv);
 int cmd__hash_speed(int argc, const char **argv);
+int cmd__iconv(int argc, const char **argv);
 int cmd__index_version(int argc, const char **argv);
 int cmd__json_writer(int argc, const char **argv);
 int cmd__lazy_init_name_hash(int argc, const char **argv);

From 98178258f05c162fc3d942d0ddd3734541b1e27e Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 20 Jul 2017 22:25:21 +0200
Subject: [PATCH 212/265] tests(mingw): if `iconv` is unavailable, use
 `test-helper --iconv`

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/test-lib.sh | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/t/test-lib.sh b/t/test-lib.sh
index 20967ce4ded5be..45a87d18958af6 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -1627,6 +1627,12 @@ case $uname_s in
 	test_set_prereq GREP_STRIPS_CR
 	test_set_prereq WINDOWS
 	GIT_TEST_CMP="test-tool cmp"
+	if ! type iconv >/dev/null 2>&1
+	then
+		iconv () {
+			test-tool iconv "$@"
+		}
+	fi
 	;;
 *CYGWIN*)
 	test_set_prereq POSIXPERM

From 1a2ff9e925adb2f58b442a7d58d78d9c910e5a02 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 11 Oct 2018 23:55:44 +0200
Subject: [PATCH 213/265] gitattributes: mark .png files as binary

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitattributes | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitattributes b/.gitattributes
index b0044cf272fec9..2bc4defac6720c 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -4,6 +4,7 @@
 *.perl eol=lf diff=perl
 *.pl eof=lf diff=perl
 *.pm eol=lf diff=perl
+*.png binary
 *.py eol=lf diff=python
 *.bat eol=crlf
 CODE_OF_CONDUCT.md -whitespace

From c45bd1eced3d6f4b7debfe9dcc81214275df8f95 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Sat, 5 Aug 2017 20:28:37 +0200
Subject: [PATCH 214/265] tests: move test PNGs into t/lib-diff/

We already have a directory where we store files intended for use by
multiple test scripts. The same directory is a better home for the
test-binary-*.png files than t/.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/{ => lib-diff}/test-binary-1.png | Bin
 t/{ => lib-diff}/test-binary-2.png | Bin
 t/t3307-notes-man.sh               |   2 +-
 t/t3903-stash.sh                   |   2 +-
 t/t4012-diff-binary.sh             |   2 +-
 t/t4049-diff-stat-count.sh         |   2 +-
 t/t4108-apply-threeway.sh          |  12 ++++++------
 t/t6403-merge-file.sh              |   2 +-
 t/t6407-merge-binary.sh            |   2 +-
 t/t9200-git-cvsexportcommit.sh     |  15 ++++++++-------
 10 files changed, 20 insertions(+), 19 deletions(-)
 rename t/{ => lib-diff}/test-binary-1.png (100%)
 rename t/{ => lib-diff}/test-binary-2.png (100%)

diff --git a/t/test-binary-1.png b/t/lib-diff/test-binary-1.png
similarity index 100%
rename from t/test-binary-1.png
rename to t/lib-diff/test-binary-1.png
diff --git a/t/test-binary-2.png b/t/lib-diff/test-binary-2.png
similarity index 100%
rename from t/test-binary-2.png
rename to t/lib-diff/test-binary-2.png
diff --git a/t/t3307-notes-man.sh b/t/t3307-notes-man.sh
index 1aa366a410e9a3..7e5c06e6615d7a 100755
--- a/t/t3307-notes-man.sh
+++ b/t/t3307-notes-man.sh
@@ -26,7 +26,7 @@ test_expect_success 'example 1: notes to add an Acked-by line' '
 '
 
 test_expect_success 'example 2: binary notes' '
-	cp "$TEST_DIRECTORY"/test-binary-1.png . &&
+	cp "$TEST_DIRECTORY"/lib-diff/test-binary-1.png . &&
 	git checkout B &&
 	blob=$(git hash-object -w test-binary-1.png) &&
 	git notes --ref=logo add -C "$blob" &&
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 20e94881964d55..53f0bdacd05fd0 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -1332,7 +1332,7 @@ test_expect_success 'stash -- <subdir> works with binary files' '
 	git reset &&
 	>subdir/untracked &&
 	>subdir/tracked &&
-	cp "$TEST_DIRECTORY"/test-binary-1.png subdir/tracked-binary &&
+	cp "$TEST_DIRECTORY"/lib-diff/test-binary-1.png subdir/tracked-binary &&
 	git add subdir/tracked* &&
 	git stash -- subdir/ &&
 	test_path_is_missing subdir/tracked &&
diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh
index c509143c8141e0..c2c7c021346421 100755
--- a/t/t4012-diff-binary.sh
+++ b/t/t4012-diff-binary.sh
@@ -20,7 +20,7 @@ test_expect_success 'prepare repository' '
 	echo AIT >a && echo BIT >b && echo CIT >c && echo DIT >d &&
 	git update-index --add a b c d &&
 	echo git >a &&
-	cat "$TEST_DIRECTORY"/test-binary-1.png >b &&
+	cat "$TEST_DIRECTORY"/lib-diff/test-binary-1.png >b &&
 	echo git >c &&
 	cat b b >d
 '
diff --git a/t/t4049-diff-stat-count.sh b/t/t4049-diff-stat-count.sh
index 0a4fc735d44ad5..6c028646485971 100755
--- a/t/t4049-diff-stat-count.sh
+++ b/t/t4049-diff-stat-count.sh
@@ -34,7 +34,7 @@ test_expect_success 'binary changes do not count in lines' '
 	git reset --hard &&
 	echo a >a &&
 	echo c >c &&
-	cat "$TEST_DIRECTORY"/test-binary-1.png >d &&
+	cat "$TEST_DIRECTORY"/lib-diff/test-binary-1.png >d &&
 	cat >expect <<-\EOF &&
 	 a | 1 +
 	 c | 1 +
diff --git a/t/t4108-apply-threeway.sh b/t/t4108-apply-threeway.sh
index c558282bc09475..1c85e7051d5f72 100755
--- a/t/t4108-apply-threeway.sh
+++ b/t/t4108-apply-threeway.sh
@@ -232,11 +232,11 @@ test_expect_success 'apply with --3way --cached and conflicts' '
 
 test_expect_success 'apply binary file patch' '
 	git reset --hard main &&
-	cp "$TEST_DIRECTORY/test-binary-1.png" bin.png &&
+	cp "$TEST_DIRECTORY/lib-diff/test-binary-1.png" bin.png &&
 	git add bin.png &&
 	git commit -m "add binary file" &&
 
-	cp "$TEST_DIRECTORY/test-binary-2.png" bin.png &&
+	cp "$TEST_DIRECTORY/lib-diff/test-binary-2.png" bin.png &&
 
 	git diff --binary >bin.diff &&
 	git reset --hard &&
@@ -247,11 +247,11 @@ test_expect_success 'apply binary file patch' '
 
 test_expect_success 'apply binary file patch with 3way' '
 	git reset --hard main &&
-	cp "$TEST_DIRECTORY/test-binary-1.png" bin.png &&
+	cp "$TEST_DIRECTORY/lib-diff/test-binary-1.png" bin.png &&
 	git add bin.png &&
 	git commit -m "add binary file" &&
 
-	cp "$TEST_DIRECTORY/test-binary-2.png" bin.png &&
+	cp "$TEST_DIRECTORY/lib-diff/test-binary-2.png" bin.png &&
 
 	git diff --binary >bin.diff &&
 	git reset --hard &&
@@ -262,11 +262,11 @@ test_expect_success 'apply binary file patch with 3way' '
 
 test_expect_success 'apply full-index patch with 3way' '
 	git reset --hard main &&
-	cp "$TEST_DIRECTORY/test-binary-1.png" bin.png &&
+	cp "$TEST_DIRECTORY/lib-diff/test-binary-1.png" bin.png &&
 	git add bin.png &&
 	git commit -m "add binary file" &&
 
-	cp "$TEST_DIRECTORY/test-binary-2.png" bin.png &&
+	cp "$TEST_DIRECTORY/lib-diff/test-binary-2.png" bin.png &&
 
 	git diff --full-index >bin.diff &&
 	git reset --hard &&
diff --git a/t/t6403-merge-file.sh b/t/t6403-merge-file.sh
index 2f421d967abefb..52e29f9713e29d 100755
--- a/t/t6403-merge-file.sh
+++ b/t/t6403-merge-file.sh
@@ -250,7 +250,7 @@ test_expect_success "expected conflict markers" '
 
 test_expect_success 'binary files cannot be merged' '
 	test_must_fail git merge-file -p \
-		orig.txt "$TEST_DIRECTORY"/test-binary-1.png new1.txt 2> merge.err &&
+		orig.txt "$TEST_DIRECTORY"/lib-diff/test-binary-1.png new1.txt 2> merge.err &&
 	grep "Cannot merge binary files" merge.err
 '
 
diff --git a/t/t6407-merge-binary.sh b/t/t6407-merge-binary.sh
index 0753fc95f45efb..914014e1a9c551 100755
--- a/t/t6407-merge-binary.sh
+++ b/t/t6407-merge-binary.sh
@@ -10,7 +10,7 @@ TEST_PASSES_SANITIZE_LEAK=true
 
 test_expect_success setup '
 
-	cat "$TEST_DIRECTORY"/test-binary-1.png >m &&
+	cat "$TEST_DIRECTORY"/lib-diff/test-binary-1.png >m &&
 	git add m &&
 	git ls-files -s | sed -e "s/ 0	/ 1	/" >E1 &&
 	test_tick &&
diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh
index c5946cb0b8a94c..3e1034e6b44059 100755
--- a/t/t9200-git-cvsexportcommit.sh
+++ b/t/t9200-git-cvsexportcommit.sh
@@ -55,8 +55,8 @@ test_expect_success \
     'mkdir A B C D E F &&
      echo hello1 >A/newfile1.txt &&
      echo hello2 >B/newfile2.txt &&
-     cp "$TEST_DIRECTORY"/test-binary-1.png C/newfile3.png &&
-     cp "$TEST_DIRECTORY"/test-binary-1.png D/newfile4.png &&
+     cp "$TEST_DIRECTORY"/lib-diff/test-binary-1.png C/newfile3.png &&
+     cp "$TEST_DIRECTORY"/lib-diff/test-binary-1.png D/newfile4.png &&
      git add A/newfile1.txt &&
      git add B/newfile2.txt &&
      git add C/newfile3.png &&
@@ -81,8 +81,8 @@ test_expect_success \
      rm -f B/newfile2.txt &&
      rm -f C/newfile3.png &&
      echo Hello5  >E/newfile5.txt &&
-     cp "$TEST_DIRECTORY"/test-binary-2.png D/newfile4.png &&
-     cp "$TEST_DIRECTORY"/test-binary-1.png F/newfile6.png &&
+     cp "$TEST_DIRECTORY"/lib-diff/test-binary-2.png D/newfile4.png &&
+     cp "$TEST_DIRECTORY"/lib-diff/test-binary-1.png F/newfile6.png &&
      git add E/newfile5.txt &&
      git add F/newfile6.png &&
      git commit -a -m "Test: Remove, add and update" &&
@@ -170,7 +170,7 @@ test_expect_success \
      'mkdir "G g" &&
       echo ok then >"G g/with spaces.txt" &&
       git add "G g/with spaces.txt" && \
-      cp "$TEST_DIRECTORY"/test-binary-1.png "G g/with spaces.png" && \
+      cp "$TEST_DIRECTORY"/lib-diff/test-binary-1.png "G g/with spaces.png" && \
       git add "G g/with spaces.png" &&
       git commit -a -m "With spaces" &&
       id=$(git rev-list --max-count=1 HEAD) &&
@@ -182,7 +182,8 @@ test_expect_success \
 test_expect_success \
      'Update file with spaces in file name' \
      'echo Ok then >>"G g/with spaces.txt" &&
-      cat "$TEST_DIRECTORY"/test-binary-1.png >>"G g/with spaces.png" && \
+      cat "$TEST_DIRECTORY"/lib-diff/test-binary-1.png \
+	>>"G g/with spaces.png" && \
       git add "G g/with spaces.png" &&
       git commit -a -m "Update with spaces" &&
       id=$(git rev-list --max-count=1 HEAD) &&
@@ -207,7 +208,7 @@ test_expect_success !MINGW \
      'mkdir -p Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö &&
       echo Foo >Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt &&
       git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt &&
-      cp "$TEST_DIRECTORY"/test-binary-1.png Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png &&
+      cp "$TEST_DIRECTORY"/lib-diff/test-binary-1.png Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png &&
       git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png &&
       git commit -a -m "Går det så går det" && \
       id=$(git rev-list --max-count=1 HEAD) &&

From f515ca918f0e469cd49ec480abcd614593ba12f4 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 18 Jul 2017 01:15:40 +0200
Subject: [PATCH 215/265] tests: only override sort & find if there are usable
 ones in /usr/bin/

The idea is to allow running the test suite on MinGit with BusyBox
installed in /mingw64/bin/sh.exe. In that case, we will want to exclude
sort & find (and other Unix utilities) from being bundled.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 git-sh-setup.sh | 21 ++++++++++++++-------
 t/test-lib.sh   | 21 ++++++++++++++-------
 2 files changed, 28 insertions(+), 14 deletions(-)

diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index d92df37e992471..770caafae58837 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -308,13 +308,20 @@ create_virtual_base() {
 # Platform specific tweaks to work around some commands
 case $(uname -s) in
 *MINGW*)
-	# Windows has its own (incompatible) sort and find
-	sort () {
-		/usr/bin/sort "$@"
-	}
-	find () {
-		/usr/bin/find "$@"
-	}
+	if test -x /usr/bin/sort
+	then
+		# Windows has its own (incompatible) sort; override
+		sort () {
+			/usr/bin/sort "$@"
+		}
+	fi
+	if test -x /usr/bin/find
+	then
+		# Windows has its own (incompatible) find; override
+		find () {
+			/usr/bin/find "$@"
+		}
+	fi
 	# git sees Windows-style pwd
 	pwd () {
 		builtin pwd -W
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 45a87d18958af6..b794a61a2697dd 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -1607,13 +1607,20 @@ fi
 uname_s=$(uname -s)
 case $uname_s in
 *MINGW*)
-	# Windows has its own (incompatible) sort and find
-	sort () {
-		/usr/bin/sort "$@"
-	}
-	find () {
-		/usr/bin/find "$@"
-	}
+	if test -x /usr/bin/sort
+	then
+		# Windows has its own (incompatible) sort; override
+		sort () {
+			/usr/bin/sort "$@"
+		}
+	fi
+	if test -x /usr/bin/find
+	then
+		# Windows has its own (incompatible) find; override
+		find () {
+			/usr/bin/find "$@"
+		}
+	fi
 	# git sees Windows-style pwd
 	pwd () {
 		builtin pwd -W

From de7d3498ff883a59b8152ce9668747539fe1e7ac Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Mon, 19 Nov 2018 20:34:13 +0100
Subject: [PATCH 216/265] tests: use the correct path separator with BusyBox

BusyBox-w32 is a true Win32 application, i.e. it does not come with a
POSIX emulation layer.

That also means that it does *not* use the Unix convention of separating
the entries in the PATH variable using colons, but semicolons.

However, there are also BusyBox ports to Windows which use a POSIX
emulation layer such as Cygwin's or MSYS2's runtime, i.e. using colons
as PATH separators.

As a tell-tale, let's use the presence of semicolons in the PATH
variable: on Unix, it is highly unlikely that it contains semicolons,
and on Windows (without POSIX emulation), it is virtually guaranteed, as
everybody should have both $SYSTEMROOT and $SYSTEMROOT/system32 in their
PATH.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/interop/interop-lib.sh    |  8 ++++++--
 t/lib-proto-disable.sh      |  2 +-
 t/t0021-conversion.sh       |  2 +-
 t/t0060-path-utils.sh       | 24 ++++++++++++------------
 t/t0061-run-command.sh      |  6 +++---
 t/t0300-credentials.sh      |  2 +-
 t/t1504-ceiling-dirs.sh     | 10 +++++-----
 t/t2300-cd-to-toplevel.sh   |  2 +-
 t/t3402-rebase-merge.sh     |  2 +-
 t/t3418-rebase-continue.sh  |  8 ++++----
 t/t5615-alternate-env.sh    |  4 ++--
 t/t5802-connect-helper.sh   |  2 +-
 t/t7006-pager.sh            |  4 ++--
 t/t7606-merge-custom.sh     |  2 +-
 t/t7811-grep-open.sh        |  2 +-
 t/t9003-help-autocorrect.sh |  2 +-
 t/t9800-git-p4-basic.sh     |  2 +-
 t/test-lib.sh               | 17 +++++++++++++----
 18 files changed, 57 insertions(+), 44 deletions(-)

diff --git a/t/interop/interop-lib.sh b/t/interop/interop-lib.sh
index 3e0a2911d4f9ba..dea8883821fe3e 100644
--- a/t/interop/interop-lib.sh
+++ b/t/interop/interop-lib.sh
@@ -4,6 +4,10 @@
 . ../../GIT-BUILD-OPTIONS
 INTEROP_ROOT=$(pwd)
 BUILD_ROOT=$INTEROP_ROOT/build
+case "$PATH" in
+*\;*) PATH_SEP=\; ;;
+*) PATH_SEP=: ;;
+esac
 
 build_version () {
 	if test -z "$1"
@@ -57,7 +61,7 @@ wrap_git () {
 	write_script "$1" <<-EOF
 	GIT_EXEC_PATH="$2"
 	export GIT_EXEC_PATH
-	PATH="$2:\$PATH"
+	PATH="$2$PATH_SEP\$PATH"
 	export GIT_EXEC_PATH
 	exec git "\$@"
 	EOF
@@ -71,7 +75,7 @@ generate_wrappers () {
 	echo >&2 fatal: test tried to run generic git
 	exit 1
 	EOF
-	PATH=$(pwd)/.bin:$PATH
+	PATH=$(pwd)/.bin$PATH_SEP$PATH
 }
 
 VERSION_A=${GIT_TEST_VERSION_A:-$VERSION_A}
diff --git a/t/lib-proto-disable.sh b/t/lib-proto-disable.sh
index 83babe57d95900..9dc55a83a0621c 100644
--- a/t/lib-proto-disable.sh
+++ b/t/lib-proto-disable.sh
@@ -214,7 +214,7 @@ setup_ext_wrapper () {
 		cd "$TRASH_DIRECTORY/remote" &&
 		eval "$*"
 		EOF
-		PATH=$TRASH_DIRECTORY:$PATH &&
+		PATH=$TRASH_DIRECTORY$PATH_SEP$PATH &&
 		export TRASH_DIRECTORY
 	'
 }
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
index bad37abad2c376..fed0feae247ec8 100755
--- a/t/t0021-conversion.sh
+++ b/t/t0021-conversion.sh
@@ -9,7 +9,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 . "$TEST_DIRECTORY"/lib-terminal.sh
 
 TEST_ROOT="$PWD"
-PATH=$TEST_ROOT:$PATH
+PATH=$TEST_ROOT$PATH_SEP$PATH
 
 write_script <<\EOF "$TEST_ROOT/rot13.sh"
 tr \
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index f0272a8c0c00a1..8a34890e04dafd 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -138,25 +138,25 @@ ancestor /foo /fo -1
 ancestor /foo /foo -1
 ancestor /foo /bar -1
 ancestor /foo /foo/bar -1
-ancestor /foo /foo:/bar -1
-ancestor /foo /:/foo:/bar 0
-ancestor /foo /foo:/:/bar 0
-ancestor /foo /:/bar:/foo 0
+ancestor /foo "/foo$PATH_SEP/bar" -1
+ancestor /foo "/$PATH_SEP/foo$PATH_SEP/bar" 0
+ancestor /foo "/foo$PATH_SEP/$PATH_SEP/bar" 0
+ancestor /foo "/$PATH_SEP/bar$PATH_SEP/foo" 0
 ancestor /foo/bar / 0
 ancestor /foo/bar /fo -1
 ancestor /foo/bar /foo 4
 ancestor /foo/bar /foo/ba -1
-ancestor /foo/bar /:/fo 0
-ancestor /foo/bar /foo:/foo/ba 4
+ancestor /foo/bar "/$PATH_SEP/fo" 0
+ancestor /foo/bar "/foo$PATH_SEP/foo/ba" 4
 ancestor /foo/bar /bar -1
 ancestor /foo/bar /fo -1
-ancestor /foo/bar /foo:/bar 4
-ancestor /foo/bar /:/foo:/bar 4
-ancestor /foo/bar /foo:/:/bar 4
-ancestor /foo/bar /:/bar:/fo 0
-ancestor /foo/bar /:/bar 0
+ancestor /foo/bar "/foo$PATH_SEP/bar" 4
+ancestor /foo/bar "/$PATH_SEP/foo$PATH_SEP/bar" 4
+ancestor /foo/bar "/foo$PATH_SEP/$PATH_SEP/bar" 4
+ancestor /foo/bar "/$PATH_SEP/bar$PATH_SEP/fo" 0
+ancestor /foo/bar "/$PATH_SEP/bar" 0
 ancestor /foo/bar /foo 4
-ancestor /foo/bar /foo:/bar 4
+ancestor /foo/bar "/foo$PATH_SEP/bar" 4
 ancestor /foo/bar /bar -1
 
 # Windows-specific: DOS drives, network shares
diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh
index ee281909bc3726..1d264fb43b3185 100755
--- a/t/t0061-run-command.sh
+++ b/t/t0061-run-command.sh
@@ -70,7 +70,7 @@ test_expect_success 'run_command does not try to execute a directory' '
 	cat bin2/greet
 	EOF
 
-	PATH=$PWD/bin1:$PWD/bin2:$PATH \
+	PATH=$PWD/bin1$PATH_SEP$PWD/bin2$PATH_SEP$PATH \
 		test-tool run-command run-command greet >actual 2>err &&
 	test_cmp bin2/greet actual &&
 	test_must_be_empty err
@@ -87,7 +87,7 @@ test_expect_success POSIXPERM 'run_command passes over non-executable file' '
 	cat bin2/greet
 	EOF
 
-	PATH=$PWD/bin1:$PWD/bin2:$PATH \
+	PATH=$PWD/bin1$PATH_SEP$PWD/bin2$PATH_SEP$PATH \
 		test-tool run-command run-command greet >actual 2>err &&
 	test_cmp bin2/greet actual &&
 	test_must_be_empty err
@@ -107,7 +107,7 @@ test_expect_success POSIXPERM,SANITY 'unreadable directory in PATH' '
 	git config alias.nitfol "!echo frotz" &&
 	chmod a-rx local-command &&
 	(
-		PATH=./local-command:$PATH &&
+		PATH=./local-command$PATH_SEP$PATH &&
 		git nitfol >actual
 	) &&
 	echo frotz >expect &&
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
index 3485c0534e6d39..ed045188e0f124 100755
--- a/t/t0300-credentials.sh
+++ b/t/t0300-credentials.sh
@@ -35,7 +35,7 @@ test_expect_success 'setup helper scripts' '
 	test -z "$pass" || echo password=$pass
 	EOF
 
-	PATH="$PWD:$PATH"
+	PATH="$PWD$PATH_SEP$PATH"
 '
 
 test_expect_success 'credential_fill invokes helper' '
diff --git a/t/t1504-ceiling-dirs.sh b/t/t1504-ceiling-dirs.sh
index 0fafcf9dde385f..060296febcce1c 100755
--- a/t/t1504-ceiling-dirs.sh
+++ b/t/t1504-ceiling-dirs.sh
@@ -81,9 +81,9 @@ then
 	GIT_CEILING_DIRECTORIES="$TRASH_ROOT/top/"
 	test_fail subdir_ceil_at_top_slash
 
-	GIT_CEILING_DIRECTORIES=":$TRASH_ROOT/top"
+	GIT_CEILING_DIRECTORIES="$PATH_SEP$TRASH_ROOT/top"
 	test_prefix subdir_ceil_at_top_no_resolve "sub/dir/"
-	GIT_CEILING_DIRECTORIES=":$TRASH_ROOT/top/"
+	GIT_CEILING_DIRECTORIES="$PATH_SEP$TRASH_ROOT/top/"
 	test_prefix subdir_ceil_at_top_slash_no_resolve "sub/dir/"
 fi
 
@@ -113,13 +113,13 @@ GIT_CEILING_DIRECTORIES="$TRASH_ROOT/subdi"
 test_prefix subdir_ceil_at_subdi_slash "sub/dir/"
 
 
-GIT_CEILING_DIRECTORIES="/foo:$TRASH_ROOT/sub"
+GIT_CEILING_DIRECTORIES="/foo$PATH_SEP$TRASH_ROOT/sub"
 test_fail second_of_two
 
-GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub:/bar"
+GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub$PATH_SEP/bar"
 test_fail first_of_two
 
-GIT_CEILING_DIRECTORIES="/foo:$TRASH_ROOT/sub:/bar"
+GIT_CEILING_DIRECTORIES="/foo$PATH_SEP$TRASH_ROOT/sub$PATH_SEP/bar"
 test_fail second_of_three
 
 
diff --git a/t/t2300-cd-to-toplevel.sh b/t/t2300-cd-to-toplevel.sh
index b40eeb263fe896..4328b9172a5e6e 100755
--- a/t/t2300-cd-to-toplevel.sh
+++ b/t/t2300-cd-to-toplevel.sh
@@ -17,7 +17,7 @@ test_cd_to_toplevel () {
 	test_expect_success $3 "$2" '
 		(
 			cd '"'$1'"' &&
-			PATH="$EXEC_PATH:$PATH" &&
+			PATH="$EXEC_PATH$PATH_SEP$PATH" &&
 			. git-sh-setup &&
 			cd_to_toplevel &&
 			[ "$(pwd -P)" = "$TOPLEVEL" ]
diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh
index 7e46f4ca850616..ce553b6edd50e4 100755
--- a/t/t3402-rebase-merge.sh
+++ b/t/t3402-rebase-merge.sh
@@ -146,7 +146,7 @@ test_expect_success 'rebase -s funny -Xopt' '
 	git checkout -b test-funny main^ &&
 	test_commit funny &&
 	(
-		PATH=./test-bin:$PATH &&
+		PATH=./test-bin$PATH_SEP$PATH &&
 		git rebase -s funny -Xopt main
 	) &&
 	test -f funny.was.run
diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh
index 130e2f9b553003..f1c1eff4eed4f5 100755
--- a/t/t3418-rebase-continue.sh
+++ b/t/t3418-rebase-continue.sh
@@ -73,7 +73,7 @@ test_expect_success 'rebase --continue remembers merge strategy and options' '
 	EOF
 	chmod +x test-bin/git-merge-funny &&
 	(
-		PATH=./test-bin:$PATH &&
+		PATH=./test-bin$PATH_SEP$PATH &&
 		test_must_fail git rebase -s funny -Xopt main topic
 	) &&
 	test -f funny.was.run &&
@@ -81,7 +81,7 @@ test_expect_success 'rebase --continue remembers merge strategy and options' '
 	echo "Resolved" >F2 &&
 	git add F2 &&
 	(
-		PATH=./test-bin:$PATH &&
+		PATH=./test-bin$PATH_SEP$PATH &&
 		git rebase --continue
 	) &&
 	test -f funny.was.run
@@ -105,7 +105,7 @@ test_expect_success 'rebase -i --continue handles merge strategy and options' '
 	EOF
 	chmod +x test-bin/git-merge-funny &&
 	(
-		PATH=./test-bin:$PATH &&
+		PATH=./test-bin$PATH_SEP$PATH &&
 		test_must_fail git rebase -i -s funny -Xopt -Xfoo main topic
 	) &&
 	test -f funny.was.run &&
@@ -113,7 +113,7 @@ test_expect_success 'rebase -i --continue handles merge strategy and options' '
 	echo "Resolved" >F2 &&
 	git add F2 &&
 	(
-		PATH=./test-bin:$PATH &&
+		PATH=./test-bin$PATH_SEP$PATH &&
 		git rebase --continue
 	) &&
 	test -f funny.was.run
diff --git a/t/t5615-alternate-env.sh b/t/t5615-alternate-env.sh
index 83513e46a3556b..fa2dd08084d21c 100755
--- a/t/t5615-alternate-env.sh
+++ b/t/t5615-alternate-env.sh
@@ -40,7 +40,7 @@ test_expect_success 'access alternate via absolute path' '
 '
 
 test_expect_success 'access multiple alternates' '
-	check_obj "$PWD/one.git/objects:$PWD/two.git/objects" <<-EOF
+	check_obj "$PWD/one.git/objects$PATH_SEP$PWD/two.git/objects" <<-EOF
 	$one blob
 	$two blob
 	EOF
@@ -76,7 +76,7 @@ test_expect_success 'access alternate via relative path (subdir)' '
 quoted='"one.git\057objects"'
 unquoted='two.git/objects'
 test_expect_success 'mix of quoted and unquoted alternates' '
-	check_obj "$quoted:$unquoted" <<-EOF
+	check_obj "$quoted$PATH_SEP$unquoted" <<-EOF
 	$one blob
 	$two blob
 	EOF
diff --git a/t/t5802-connect-helper.sh b/t/t5802-connect-helper.sh
index c6c2661878c0ca..a096eeeeb427cf 100755
--- a/t/t5802-connect-helper.sh
+++ b/t/t5802-connect-helper.sh
@@ -85,7 +85,7 @@ test_expect_success 'set up fake git-daemon' '
 		"$TRASH_DIRECTORY/remote"
 	EOF
 	export TRASH_DIRECTORY &&
-	PATH=$TRASH_DIRECTORY:$PATH
+	PATH=$TRASH_DIRECTORY$PATH_SEP$PATH
 '
 
 test_expect_success 'ext command can connect to git daemon (no vhost)' '
diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh
index e56ca5b0fa8d47..2a90dc74f2b73d 100755
--- a/t/t7006-pager.sh
+++ b/t/t7006-pager.sh
@@ -54,7 +54,7 @@ test_expect_success !MINGW,TTY 'LESS and LV envvars set by git-sh-setup' '
 		sane_unset LESS LV &&
 		PAGER="env >pager-env.out; wc" &&
 		export PAGER &&
-		PATH="$(git --exec-path):$PATH" &&
+		PATH="$(git --exec-path)$PATH_SEP$PATH" &&
 		export PATH &&
 		test_terminal sh -c ". git-sh-setup && git_pager"
 	) &&
@@ -388,7 +388,7 @@ test_default_pager() {
 		EOF
 		chmod +x \$less &&
 		(
-			PATH=.:\$PATH &&
+			PATH=.$PATH_SEP\$PATH &&
 			export PATH &&
 			$full_command
 		) &&
diff --git a/t/t7606-merge-custom.sh b/t/t7606-merge-custom.sh
index 81fb7c474c14c1..8197a1c46bb5b6 100755
--- a/t/t7606-merge-custom.sh
+++ b/t/t7606-merge-custom.sh
@@ -23,7 +23,7 @@ test_expect_success 'set up custom strategy' '
 	EOF
 
 	chmod +x git-merge-theirs &&
-	PATH=.:$PATH &&
+	PATH=.$PATH_SEP$PATH &&
 	export PATH
 '
 
diff --git a/t/t7811-grep-open.sh b/t/t7811-grep-open.sh
index 1dd07141a7df9f..668de47c42e226 100755
--- a/t/t7811-grep-open.sh
+++ b/t/t7811-grep-open.sh
@@ -53,7 +53,7 @@ test_expect_success SIMPLEPAGER 'git grep -O' '
 	EOF
 	echo grep.h >expect.notless &&
 
-	PATH=.:$PATH git grep -O GREP_PATTERN >out &&
+	PATH=.$PATH_SEP$PATH git grep -O GREP_PATTERN >out &&
 	{
 		test_cmp expect.less pager-args ||
 		test_cmp expect.notless pager-args
diff --git a/t/t9003-help-autocorrect.sh b/t/t9003-help-autocorrect.sh
index f00deaf3815f3b..982aac7d169f55 100755
--- a/t/t9003-help-autocorrect.sh
+++ b/t/t9003-help-autocorrect.sh
@@ -12,7 +12,7 @@ test_expect_success 'setup' '
 		echo distimdistim was called
 	EOF
 
-	PATH="$PATH:." &&
+	PATH="$PATH$PATH_SEP." &&
 	export PATH &&
 
 	git commit --allow-empty -m "a single log entry" &&
diff --git a/t/t9800-git-p4-basic.sh b/t/t9800-git-p4-basic.sh
index 8b30062c0cfcc8..106e3c0b5d42ed 100755
--- a/t/t9800-git-p4-basic.sh
+++ b/t/t9800-git-p4-basic.sh
@@ -201,7 +201,7 @@ test_expect_success 'exit when p4 fails to produce marshaled output' '
 	EOF
 	chmod 755 badp4dir/p4 &&
 	(
-		PATH="$TRASH_DIRECTORY/badp4dir:$PATH" &&
+		PATH="$TRASH_DIRECTORY/badp4dir$PATH_SEP$PATH" &&
 		export PATH &&
 		test_expect_code 1 git p4 clone --dest="$git" //depot >errs 2>&1
 	) &&
diff --git a/t/test-lib.sh b/t/test-lib.sh
index b794a61a2697dd..e65488de2d57a9 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -15,6 +15,15 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see http://www.gnu.org/licenses/ .
 
+# On Unix/Linux, the path separator is the colon, on other systems it
+# may be different, though. On Windows, for example, it is a semicolon.
+# If the PATH variable contains semicolons, it is pretty safe to assume
+# that the path separator is a semicolon.
+case "$PATH" in
+*\;*) PATH_SEP=\; ;;
+*) PATH_SEP=: ;;
+esac
+
 # Test the binaries we have just built.  The tests are kept in
 # t/ subdirectory and are run in 'trash directory' subdirectory.
 if test -z "$TEST_DIRECTORY"
@@ -1399,7 +1408,7 @@ then
 		done
 	done
 	IFS=$OLDIFS
-	PATH=$GIT_VALGRIND/bin:$PATH
+	PATH=$GIT_VALGRIND/bin$PATH_SEP$PATH
 	GIT_EXEC_PATH=$GIT_VALGRIND/bin
 	export GIT_VALGRIND
 	GIT_VALGRIND_MODE="$valgrind"
@@ -1411,7 +1420,7 @@ elif test -n "$GIT_TEST_INSTALLED"
 then
 	GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path)  ||
 	error "Cannot run git from $GIT_TEST_INSTALLED."
-	PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR/t/helper:$PATH
+	PATH=$GIT_TEST_INSTALLED$PATH_SEP$GIT_BUILD_DIR/t/helper$PATH_SEP$PATH
 	GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH}
 else # normal case, use ../bin-wrappers only unless $with_dashes:
 	if test -n "$no_bin_wrappers"
@@ -1427,12 +1436,12 @@ else # normal case, use ../bin-wrappers only unless $with_dashes:
 			fi
 			with_dashes=t
 		fi
-		PATH="$git_bin_dir:$PATH"
+		PATH="$git_bin_dir$PATH_SEP$PATH"
 	fi
 	GIT_EXEC_PATH=$GIT_BUILD_DIR
 	if test -n "$with_dashes"
 	then
-		PATH="$GIT_BUILD_DIR:$GIT_BUILD_DIR/t/helper:$PATH"
+		PATH="$GIT_BUILD_DIR$PATH_SEP$GIT_BUILD_DIR/t/helper$PATH_SEP$PATH"
 	fi
 fi
 GIT_TEMPLATE_DIR="$GIT_BUILD_DIR"/templates/blt

From 408a5fc59d4b7ba666a37933f26e5080cb84cada Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 30 Jun 2017 00:35:40 +0200
Subject: [PATCH 217/265] mingw: only use Bash-ism `builtin pwd -W` when
 available

Traditionally, Git for Windows' SDK uses Bash as its default shell.
However, other Unix shells are available, too. Most notably, the Win32
port of BusyBox comes with `ash` whose `pwd` command already prints
Windows paths as Git for Windows wants them, while there is not even a
`builtin` command.

Therefore, let's be careful not to override `pwd` unless we know that
the `builtin` command is available.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 git-sh-setup.sh | 14 ++++++++++----
 t/test-lib.sh   | 14 ++++++++++----
 2 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index 770caafae58837..76ec80c51172c3 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -322,10 +322,16 @@ case $(uname -s) in
 			/usr/bin/find "$@"
 		}
 	fi
-	# git sees Windows-style pwd
-	pwd () {
-		builtin pwd -W
-	}
+	# On Windows, Git wants Windows paths. But /usr/bin/pwd spits out
+	# Unix-style paths. At least in Bash, we have a builtin pwd that
+	# understands the -W option to force "mixed" paths, i.e. with drive
+	# prefix but still with forward slashes. Let's use that, if available.
+	if type builtin >/dev/null 2>&1
+	then
+		pwd () {
+			builtin pwd -W
+		}
+	fi
 	is_absolute_path () {
 		case "$1" in
 		[/\\]* | [A-Za-z]:*)
diff --git a/t/test-lib.sh b/t/test-lib.sh
index e65488de2d57a9..be4a10ab47261b 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -1630,10 +1630,16 @@ case $uname_s in
 			/usr/bin/find "$@"
 		}
 	fi
-	# git sees Windows-style pwd
-	pwd () {
-		builtin pwd -W
-	}
+	# On Windows, Git wants Windows paths. But /usr/bin/pwd spits out
+	# Unix-style paths. At least in Bash, we have a builtin pwd that
+	# understands the -W option to force "mixed" paths, i.e. with drive
+	# prefix but still with forward slashes. Let's use that, if available.
+	if type builtin >/dev/null 2>&1
+	then
+		pwd () {
+			builtin pwd -W
+		}
+	fi
 	# no POSIX permissions
 	# backslashes in pathspec are converted to '/'
 	# exec does not inherit the PID

From 7c5bb6ec2515291524179e1ddce3c499473234b7 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 30 Jun 2017 22:32:33 +0200
Subject: [PATCH 218/265] tests (mingw): remove Bash-specific pwd option

The -W option is only understood by MSYS2 Bash's pwd command. We already
make sure to override `pwd` by `builtin pwd -W` for MINGW, so let's not
double the effort here.

This will also help when switching the shell to another one (such as
BusyBox' ash) whose pwd does *not* understand the -W option.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t9902-completion.sh | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 31526e6b641568..98bd7ee79679a6 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -126,12 +126,7 @@ invalid_variable_name='${foo.bar}'
 
 actual="$TRASH_DIRECTORY/actual"
 
-if test_have_prereq MINGW
-then
-	ROOT="$(pwd -W)"
-else
-	ROOT="$(pwd)"
-fi
+ROOT="$(pwd)"
 
 test_expect_success 'setup for __git_find_repo_path/__gitdir tests' '
 	mkdir -p subdir/subsubdir &&

From 0126d53d53af81dc631a096e8e8015361fb1dec0 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 19 Jul 2017 17:07:56 +0200
Subject: [PATCH 219/265] test-lib: add BUSYBOX prerequisite

When running with BusyBox, we will want to avoid calling executables on
the PATH that are implemented in BusyBox itself.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/test-lib.sh | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/t/test-lib.sh b/t/test-lib.sh
index be4a10ab47261b..9c1bdddf596ea9 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -1810,6 +1810,10 @@ test_lazy_prereq UNZIP '
 	test $? -ne 127
 '
 
+test_lazy_prereq BUSYBOX '
+	case "$($SHELL --help 2>&1)" in *BusyBox*) true;; *) false;; esac
+'
+
 run_with_limited_cmdline () {
 	(ulimit -s 128 && "$@")
 }

From 54fb9c47943f08728eb4199607d8cc056bb6f469 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 4 Aug 2017 11:51:56 +0200
Subject: [PATCH 220/265] t0021: use Windows path when appropriate

Since c6b0831c9c1 (docs: warn about possible '=' in clean/smudge filter
process values, 2016-12-03), t0021 writes out a file with quotes in its
name, and MSYS2's path conversion heuristics mistakes that to mean that
we are not talking about a path here.

Therefore, we need to use Windows paths, as the test-helper is a Win32
program that would otherwise have no idea where to look for the file.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t0021-conversion.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
index fed0feae247ec8..665466f8026b04 100755
--- a/t/t0021-conversion.sh
+++ b/t/t0021-conversion.sh
@@ -8,8 +8,8 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-terminal.sh
 
-TEST_ROOT="$PWD"
-PATH=$TEST_ROOT$PATH_SEP$PATH
+TEST_ROOT="$(pwd)"
+PATH=$PWD$PATH_SEP$PATH
 
 write_script <<\EOF "$TEST_ROOT/rot13.sh"
 tr \

From ec5ada50f08e1859adb7bb5032cec5d5482f8ce4 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Sat, 5 Aug 2017 21:36:01 +0200
Subject: [PATCH 221/265] t5003: use binary file from t/lib-diff/

At some stage, t5003-archive-zip wants to add a file that is not ASCII.
To that end, it uses /bin/sh. But that file may actually not exist (it
is too easy to forget that not all the world is Unix/Linux...)! Besides,
we already have perfectly fine binary files intended for use solely by
the tests. So let's use one of them instead.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t5003-archive-zip.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh
index d726964307ca89..16d9943b266655 100755
--- a/t/t5003-archive-zip.sh
+++ b/t/t5003-archive-zip.sh
@@ -87,7 +87,7 @@ test_expect_success \
     'mkdir a &&
      echo simple textfile >a/a &&
      mkdir a/bin &&
-     cp /bin/sh a/bin &&
+     cp "$TEST_DIRECTORY/lib-diff/test-binary-1.png" a/bin &&
      printf "text\r"	>a/text.cr &&
      printf "text\r\n"	>a/text.crlf &&
      printf "text\n"	>a/text.lf &&

From c8365cb2c79854f85185ef04d43307151edb5b91 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 21 Jul 2017 12:48:33 +0200
Subject: [PATCH 222/265] t5532: workaround for BusyBox on Windows

While it may seem super convenient to some old Unix hands to simpy
require Perl to be available when running the test suite, this is a
major hassle on Windows, where we want to verify that Perl is not,
actually, required in a NO_PERL build.

As a super ugly workaround, we "install" a script into /usr/bin/perl
reading like this:

	#!/bin/sh

	# We'd much rather avoid requiring Perl altogether when testing
	# an installed Git. Oh well, that's why we cannot have nice
	# things.
	exec c:/git-sdk-64/usr/bin/perl.exe "$@"

The problem with that is that BusyBox assumes that the #! line in a
script refers to an executable, not to a script. So when it encounters
the line #!/usr/bin/perl in t5532's proxy-get-cmd, it barfs.

Let's help this situation by simply executing the Perl script with the
"interpreter" specified explicitly.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t5532-fetch-proxy.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/t/t5532-fetch-proxy.sh b/t/t5532-fetch-proxy.sh
index 9c2798603b4d7b..11fc3f2eea4baf 100755
--- a/t/t5532-fetch-proxy.sh
+++ b/t/t5532-fetch-proxy.sh
@@ -25,7 +25,7 @@ test_expect_success 'setup proxy script' '
 
 	write_script proxy <<-\EOF
 	echo >&2 "proxying for $*"
-	cmd=$(./proxy-get-cmd)
+	cmd=$("$PERL_PATH" ./proxy-get-cmd)
 	echo >&2 "Running $cmd"
 	exec $cmd
 	EOF

From b444506706e7ae3473bc26b0727e4b8beb99284b Mon Sep 17 00:00:00 2001
From: Karsten Blees <blees@dcon.de>
Date: Sun, 24 May 2015 01:48:35 +0200
Subject: [PATCH 223/265] Win32: symlink: add support for symlinks to
 directories

Symlinks on Windows have a flag that indicates whether the target is a file
or a directory. Symlinks of wrong type simply don't work. This even affects
core Win32 APIs (e.g. DeleteFile() refuses to delete directory symlinks).

However, CreateFile() with FILE_FLAG_BACKUP_SEMANTICS doesn't seem to care.
Check the target type by first creating a tentative file symlink, opening
it, and checking the type of the resulting handle. If it is a directory,
recreate the symlink with the directory flag set.

It is possible to create symlinks before the target exists (or in case of
symlinks to symlinks: before the target type is known). If this happens,
create a tentative file symlink and postpone the directory decision: keep
a list of phantom symlinks to be processed whenever a new directory is
created in mingw_mkdir().

Limitations: This algorithm may fail if a link target changes from file to
directory or vice versa, or if the target directory is created in another
process.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 164 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 164 insertions(+)

diff --git a/compat/mingw.c b/compat/mingw.c
index 81c00f9383c357..7db869e12b0745 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -300,6 +300,131 @@ int mingw_core_config(const char *var, const char *value, void *cb)
 	return 0;
 }
 
+enum phantom_symlink_result {
+	PHANTOM_SYMLINK_RETRY,
+	PHANTOM_SYMLINK_DONE,
+	PHANTOM_SYMLINK_DIRECTORY
+};
+
+static inline int is_wdir_sep(wchar_t wchar)
+{
+	return wchar == L'/' || wchar == L'\\';
+}
+
+static const wchar_t *make_relative_to(const wchar_t *path,
+				       const wchar_t *relative_to, wchar_t *out,
+				       size_t size)
+{
+	size_t i = wcslen(relative_to), len;
+
+	/* Is `path` already absolute? */
+	if (is_wdir_sep(path[0]) ||
+	    (iswalpha(path[0]) && path[1] == L':' && is_wdir_sep(path[2])))
+		return path;
+
+	while (i > 0 && !is_wdir_sep(relative_to[i - 1]))
+		i--;
+
+	/* Is `relative_to` in the current directory? */
+	if (!i)
+		return path;
+
+	len = wcslen(path);
+	if (i + len + 1 > size) {
+		error("Could not make '%ls' relative to '%ls' (too large)",
+		      path, relative_to);
+		return NULL;
+	}
+
+	memcpy(out, relative_to, i * sizeof(wchar_t));
+	wcscpy(out + i, path);
+	return out;
+}
+
+/*
+ * Changes a file symlink to a directory symlink if the target exists and is a
+ * directory.
+ */
+static enum phantom_symlink_result
+process_phantom_symlink(const wchar_t *wtarget, const wchar_t *wlink)
+{
+	HANDLE hnd;
+	BY_HANDLE_FILE_INFORMATION fdata;
+	wchar_t relative[MAX_LONG_PATH];
+	const wchar_t *rel;
+
+	/* check that wlink is still a file symlink */
+	if ((GetFileAttributesW(wlink)
+			& (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
+			!= FILE_ATTRIBUTE_REPARSE_POINT)
+		return PHANTOM_SYMLINK_DONE;
+
+	/* make it relative, if necessary */
+	rel = make_relative_to(wtarget, wlink, relative, ARRAY_SIZE(relative));
+	if (!rel)
+		return PHANTOM_SYMLINK_DONE;
+
+	/* let Windows resolve the link by opening it */
+	hnd = CreateFileW(rel, 0,
+			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+			OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+	if (hnd == INVALID_HANDLE_VALUE) {
+		errno = err_win_to_posix(GetLastError());
+		return PHANTOM_SYMLINK_RETRY;
+	}
+
+	if (!GetFileInformationByHandle(hnd, &fdata)) {
+		errno = err_win_to_posix(GetLastError());
+		CloseHandle(hnd);
+		return PHANTOM_SYMLINK_RETRY;
+	}
+	CloseHandle(hnd);
+
+	/* if target exists and is a file, we're done */
+	if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+		return PHANTOM_SYMLINK_DONE;
+
+	/* otherwise recreate the symlink with directory flag */
+	if (DeleteFileW(wlink) && CreateSymbolicLinkW(wlink, wtarget, 1))
+		return PHANTOM_SYMLINK_DIRECTORY;
+
+	errno = err_win_to_posix(GetLastError());
+	return PHANTOM_SYMLINK_RETRY;
+}
+
+/* keep track of newly created symlinks to non-existing targets */
+struct phantom_symlink_info {
+	struct phantom_symlink_info *next;
+	wchar_t *wlink;
+	wchar_t *wtarget;
+};
+
+static struct phantom_symlink_info *phantom_symlinks = NULL;
+static CRITICAL_SECTION phantom_symlinks_cs;
+
+static void process_phantom_symlinks(void)
+{
+	struct phantom_symlink_info *current, **psi;
+	EnterCriticalSection(&phantom_symlinks_cs);
+	/* process phantom symlinks list */
+	psi = &phantom_symlinks;
+	while ((current = *psi)) {
+		enum phantom_symlink_result result = process_phantom_symlink(
+				current->wtarget, current->wlink);
+		if (result == PHANTOM_SYMLINK_RETRY) {
+			psi = &current->next;
+		} else {
+			/* symlink was processed, remove from list */
+			*psi = current->next;
+			free(current);
+			/* if symlink was a directory, start over */
+			if (result == PHANTOM_SYMLINK_DIRECTORY)
+				psi = &phantom_symlinks;
+		}
+	}
+	LeaveCriticalSection(&phantom_symlinks_cs);
+}
+
 /* Normalizes NT paths as returned by some low-level APIs. */
 static wchar_t *normalize_ntpath(wchar_t *wbuf)
 {
@@ -483,6 +608,8 @@ int mingw_mkdir(const char *path, int mode)
 		return -1;
 
 	ret = _wmkdir(wpath);
+	if (!ret)
+		process_phantom_symlinks();
 	if (!ret && needs_hiding(path))
 		return set_hidden_flag(wpath, 1);
 	return ret;
@@ -2693,6 +2820,42 @@ int symlink(const char *target, const char *link)
 		errno = err_win_to_posix(GetLastError());
 		return -1;
 	}
+
+	/* convert to directory symlink if target exists */
+	switch (process_phantom_symlink(wtarget, wlink)) {
+	case PHANTOM_SYMLINK_RETRY:	{
+		/* if target doesn't exist, add to phantom symlinks list */
+		wchar_t wfullpath[MAX_LONG_PATH];
+		struct phantom_symlink_info *psi;
+
+		/* convert to absolute path to be independent of cwd */
+		len = GetFullPathNameW(wlink, MAX_LONG_PATH, wfullpath, NULL);
+		if (!len || len >= MAX_LONG_PATH) {
+			errno = err_win_to_posix(GetLastError());
+			return -1;
+		}
+
+		/* over-allocate and fill phantom_symlink_info structure */
+		psi = xmalloc(sizeof(struct phantom_symlink_info)
+			+ sizeof(wchar_t) * (len + wcslen(wtarget) + 2));
+		psi->wlink = (wchar_t *)(psi + 1);
+		wcscpy(psi->wlink, wfullpath);
+		psi->wtarget = psi->wlink + len + 1;
+		wcscpy(psi->wtarget, wtarget);
+
+		EnterCriticalSection(&phantom_symlinks_cs);
+		psi->next = phantom_symlinks;
+		phantom_symlinks = psi;
+		LeaveCriticalSection(&phantom_symlinks_cs);
+		break;
+	}
+	case PHANTOM_SYMLINK_DIRECTORY:
+		/* if we created a dir symlink, process other phantom symlinks */
+		process_phantom_symlinks();
+		break;
+	default:
+		break;
+	}
 	return 0;
 }
 
@@ -3539,6 +3702,7 @@ int wmain(int argc, const wchar_t **wargv)
 
 	/* initialize critical section for waitpid pinfo_t list */
 	InitializeCriticalSection(&pinfo_cs);
+	InitializeCriticalSection(&phantom_symlinks_cs);
 
 	/* initialize critical section for fscache */
 	InitializeCriticalSection(&fscache_cs);

From d59af0756c63756d0b028f9d1aafd618f9c67aab Mon Sep 17 00:00:00 2001
From: JiSeop Moon <zcube@zcube.kr>
Date: Mon, 23 Apr 2018 22:30:18 +0900
Subject: [PATCH 224/265] mingw: introduce code to detect whether we're inside
 a Windows container

This will come in handy in the next commit.

Signed-off-by: JiSeop Moon <zcube@zcube.kr>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 32 ++++++++++++++++++++++++++++++++
 compat/mingw.h |  5 +++++
 2 files changed, 37 insertions(+)

diff --git a/compat/mingw.c b/compat/mingw.c
index 19da2d63c7868f..d705077238fe3c 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -3776,3 +3776,35 @@ int uname(struct utsname *buf)
 		  "%u", (v >> 16) & 0x7fff);
 	return 0;
 }
+
+/*
+ * Based on https://stackoverflow.com/questions/43002803
+ *
+ * [HKLM\SYSTEM\CurrentControlSet\Services\cexecsvc]
+ * "DisplayName"="@%systemroot%\\system32\\cexecsvc.exe,-100"
+ * "ErrorControl"=dword:00000001
+ * "ImagePath"=hex(2):25,00,73,00,79,00,73,00,74,00,65,00,6d,00,72,00,6f,00,
+ *    6f,00,74,00,25,00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,
+ *    5c,00,63,00,65,00,78,00,65,00,63,00,73,00,76,00,63,00,2e,00,65,00,78,00,
+ *    65,00,00,00
+ * "Start"=dword:00000002
+ * "Type"=dword:00000010
+ * "Description"="@%systemroot%\\system32\\cexecsvc.exe,-101"
+ * "ObjectName"="LocalSystem"
+ * "ServiceSidType"=dword:00000001
+ */
+int is_inside_windows_container(void)
+{
+	static int inside_container = -1; /* -1 uninitialized */
+	const char *key = "SYSTEM\\CurrentControlSet\\Services\\cexecsvc";
+	HKEY handle = NULL;
+
+	if (inside_container != -1)
+		return inside_container;
+
+	inside_container = ERROR_SUCCESS ==
+		RegOpenKeyExA(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &handle);
+	RegCloseKey(handle);
+
+	return inside_container;
+}
diff --git a/compat/mingw.h b/compat/mingw.h
index 3b6fc2fd812b58..900d789f1f7c76 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -717,3 +717,8 @@ void open_in_gdb(void);
  * Used by Pthread API implementation for Windows
  */
 int err_win_to_posix(DWORD winerr);
+
+/*
+ * Check current process is inside Windows Container.
+ */
+int is_inside_windows_container(void);

From 8212959958304bfd92f257a0d29548eb78f7a346 Mon Sep 17 00:00:00 2001
From: Bert Belder <bertbelder@gmail.com>
Date: Fri, 26 Oct 2018 11:13:45 +0200
Subject: [PATCH 225/265] Win32: symlink: move phantom symlink creation to a
 separate function

Signed-off-by: Bert Belder <bertbelder@gmail.com>
---
 compat/mingw.c | 91 +++++++++++++++++++++++++++-----------------------
 1 file changed, 49 insertions(+), 42 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 19da2d63c7868f..97ac042a39a03a 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -428,6 +428,54 @@ static void process_phantom_symlinks(void)
 	LeaveCriticalSection(&phantom_symlinks_cs);
 }
 
+static int create_phantom_symlink(wchar_t *wtarget, wchar_t *wlink)
+{
+	int len;
+
+	/* create file symlink */
+	if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags)) {
+		errno = err_win_to_posix(GetLastError());
+		return -1;
+	}
+
+	/* convert to directory symlink if target exists */
+	switch (process_phantom_symlink(wtarget, wlink)) {
+	case PHANTOM_SYMLINK_RETRY: {
+		/* if target doesn't exist, add to phantom symlinks list */
+		wchar_t wfullpath[MAX_LONG_PATH];
+		struct phantom_symlink_info *psi;
+
+		/* convert to absolute path to be independent of cwd */
+		len = GetFullPathNameW(wlink, MAX_LONG_PATH, wfullpath, NULL);
+		if (!len || len >= MAX_LONG_PATH) {
+			errno = err_win_to_posix(GetLastError());
+			return -1;
+		}
+
+		/* over-allocate and fill phantom_symlink_info structure */
+		psi = xmalloc(sizeof(struct phantom_symlink_info) +
+			      sizeof(wchar_t) * (len + wcslen(wtarget) + 2));
+		psi->wlink = (wchar_t *)(psi + 1);
+		wcscpy(psi->wlink, wfullpath);
+		psi->wtarget = psi->wlink + len + 1;
+		wcscpy(psi->wtarget, wtarget);
+
+		EnterCriticalSection(&phantom_symlinks_cs);
+		psi->next = phantom_symlinks;
+		phantom_symlinks = psi;
+		LeaveCriticalSection(&phantom_symlinks_cs);
+		break;
+	}
+	case PHANTOM_SYMLINK_DIRECTORY:
+		/* if we created a dir symlink, process other phantom symlinks */
+		process_phantom_symlinks();
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
 /* Normalizes NT paths as returned by some low-level APIs. */
 static wchar_t *normalize_ntpath(wchar_t *wbuf)
 {
@@ -2830,48 +2878,7 @@ int symlink(const char *target, const char *link)
 		if (wtarget[len] == '/')
 			wtarget[len] = '\\';
 
-	/* create file symlink */
-	if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags)) {
-		errno = err_win_to_posix(GetLastError());
-		return -1;
-	}
-
-	/* convert to directory symlink if target exists */
-	switch (process_phantom_symlink(wtarget, wlink)) {
-	case PHANTOM_SYMLINK_RETRY:	{
-		/* if target doesn't exist, add to phantom symlinks list */
-		wchar_t wfullpath[MAX_LONG_PATH];
-		struct phantom_symlink_info *psi;
-
-		/* convert to absolute path to be independent of cwd */
-		len = GetFullPathNameW(wlink, MAX_LONG_PATH, wfullpath, NULL);
-		if (!len || len >= MAX_LONG_PATH) {
-			errno = err_win_to_posix(GetLastError());
-			return -1;
-		}
-
-		/* over-allocate and fill phantom_symlink_info structure */
-		psi = xmalloc(sizeof(struct phantom_symlink_info)
-			+ sizeof(wchar_t) * (len + wcslen(wtarget) + 2));
-		psi->wlink = (wchar_t *)(psi + 1);
-		wcscpy(psi->wlink, wfullpath);
-		psi->wtarget = psi->wlink + len + 1;
-		wcscpy(psi->wtarget, wtarget);
-
-		EnterCriticalSection(&phantom_symlinks_cs);
-		psi->next = phantom_symlinks;
-		phantom_symlinks = psi;
-		LeaveCriticalSection(&phantom_symlinks_cs);
-		break;
-	}
-	case PHANTOM_SYMLINK_DIRECTORY:
-		/* if we created a dir symlink, process other phantom symlinks */
-		process_phantom_symlinks();
-		break;
-	default:
-		break;
-	}
-	return 0;
+	return create_phantom_symlink(wtarget, wlink);
 }
 
 #ifndef _WINNT_H

From 24835466650204141966e02ca14169d9f38410eb Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 21 Jul 2017 13:24:55 +0200
Subject: [PATCH 226/265] t5605: special-case hardlink test for BusyBox-w32

When t5605 tries to verify that files are hardlinked (or that they are
not), it uses the `-links` option of the `find` utility.

BusyBox' implementation does not support that option, and BusyBox-w32's
lstat() does not even report the number of hard links correctly (for
performance reasons).

So let's just switch to a different method that actually works on
Windows.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t5605-clone-local.sh | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh
index 7d63365f93a0a5..673d50cbf8bace 100755
--- a/t/t5605-clone-local.sh
+++ b/t/t5605-clone-local.sh
@@ -11,6 +11,21 @@ repo_is_hardlinked() {
 	test_line_count = 0 output
 }
 
+if test_have_prereq MINGW,BUSYBOX
+then
+	# BusyBox' `find` does not support `-links`. Besides, BusyBox-w32's
+	# lstat() does not report hard links, just like Git's mingw_lstat()
+	# (from where BusyBox-w32 got its initial implementation).
+	repo_is_hardlinked() {
+		for f in $(find "$1/objects" -type f)
+		do
+			"$SYSTEMROOT"/system32/fsutil.exe \
+				hardlink list $f >links &&
+			test_line_count -gt 1 links || return 1
+		done
+	}
+fi
+
 test_expect_success 'preparing origin repository' '
 	: >file && git add . && git commit -m1 &&
 	git clone --bare . a.git &&

From 880c79d43b901caede5d8937bb30bccafdf64e51 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 30 May 2017 21:50:57 +0200
Subject: [PATCH 227/265] mingw: try to create symlinks without elevated
 permissions

With Windows 10 Build 14972 in Developer Mode, a new flag is supported
by CreateSymbolicLink() to create symbolic links even when running
outside of an elevated session (which was previously required).

This new flag is called SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE and
has the numeric value 0x02.

Previous Windows 10 versions will not understand that flag and return an
ERROR_INVALID_PARAMETER, therefore we have to be careful to try passing
that flag only when the build number indicates that it is supported.

For more information about the new flag, see this blog post:
https://blogs.windows.com/buildingapps/2016/12/02/symlinks-windows-10/

This patch is loosely based on the patch submitted by Samuel D. Leslie
as https://github.com/git-for-windows/git/pull/1184.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 26 ++++++++++++++++++++++++--
 1 file changed, 24 insertions(+), 2 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 7db869e12b0745..3800aeaa4de06d 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -300,6 +300,8 @@ int mingw_core_config(const char *var, const char *value, void *cb)
 	return 0;
 }
 
+static DWORD symlink_file_flags = 0, symlink_directory_flags = 1;
+
 enum phantom_symlink_result {
 	PHANTOM_SYMLINK_RETRY,
 	PHANTOM_SYMLINK_DONE,
@@ -385,7 +387,8 @@ process_phantom_symlink(const wchar_t *wtarget, const wchar_t *wlink)
 		return PHANTOM_SYMLINK_DONE;
 
 	/* otherwise recreate the symlink with directory flag */
-	if (DeleteFileW(wlink) && CreateSymbolicLinkW(wlink, wtarget, 1))
+	if (DeleteFileW(wlink) &&
+	    CreateSymbolicLinkW(wlink, wtarget, symlink_directory_flags))
 		return PHANTOM_SYMLINK_DIRECTORY;
 
 	errno = err_win_to_posix(GetLastError());
@@ -2816,7 +2819,7 @@ int symlink(const char *target, const char *link)
 			wtarget[len] = '\\';
 
 	/* create file symlink */
-	if (!CreateSymbolicLinkW(wlink, wtarget, 0)) {
+	if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags)) {
 		errno = err_win_to_posix(GetLastError());
 		return -1;
 	}
@@ -3638,6 +3641,24 @@ static void maybe_redirect_std_handles(void)
 				  GENERIC_WRITE, FILE_FLAG_NO_BUFFERING);
 }
 
+static void adjust_symlink_flags(void)
+{
+	/*
+	 * Starting with Windows 10 Build 14972, symbolic links can be created
+	 * using CreateSymbolicLink() without elevation by passing the flag
+	 * SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE (0x02) as last
+	 * parameter, provided the Developer Mode has been enabled. Some
+	 * earlier Windows versions complain about this flag with an
+	 * ERROR_INVALID_PARAMETER, hence we have to test the build number
+	 * specifically.
+	 */
+	if (GetVersion() >= 14972 << 16) {
+		symlink_file_flags |= 2;
+		symlink_directory_flags |= 2;
+	}
+
+}
+
 #ifdef _MSC_VER
 #ifdef _DEBUG
 #include <crtdbg.h>
@@ -3672,6 +3693,7 @@ int wmain(int argc, const wchar_t **wargv)
 #endif
 
 	maybe_redirect_std_handles();
+	adjust_symlink_flags();
 	fsync_object_files = 1;
 
 	/* determine size of argv and environ conversion buffer */

From 4462e8ba62dc25fdd417fa91c24a11fb68238c4d Mon Sep 17 00:00:00 2001
From: JiSeop Moon <zcube@zcube.kr>
Date: Mon, 23 Apr 2018 22:31:42 +0200
Subject: [PATCH 228/265] mingw: when running in a Windows container, try to
 rename() harder

It is a known issue that a rename() can fail with an "Access denied"
error at times, when copying followed by deleting the original file
works. Let's just fall back to that behavior.

Signed-off-by: JiSeop Moon <zcube@zcube.kr>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/compat/mingw.c b/compat/mingw.c
index d705077238fe3c..dd1c97f40bb23c 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2509,6 +2509,13 @@ int mingw_rename(const char *pold, const char *pnew)
 		return 0;
 	gle = GetLastError();
 
+	if (gle == ERROR_ACCESS_DENIED && is_inside_windows_container()) {
+		/* Fall back to copy to destination & remove source */
+		if (CopyFileW(wpold, wpnew, FALSE) && !mingw_unlink(pold))
+			return 0;
+		gle = GetLastError();
+	}
+
 	/* revert file attributes on failure */
 	if (attrs != INVALID_FILE_ATTRIBUTES)
 		SetFileAttributesW(wpnew, attrs);

From 86ea7f5e0b6f6254aca82e9497859fb0e30b5da6 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Mon, 11 Feb 2019 14:19:18 +0100
Subject: [PATCH 229/265] Introduce helper to create symlinks that knows about
 index_state

On Windows, symbolic links actually have a type depending on the target:
it can be a file or a directory.

In certain circumstances, this poses problems, e.g. when a symbolic link
is supposed to point into a submodule that is not checked out, so there
is no way for Git to auto-detect the type.

To help with that, we will add support over the course of the next
commits to specify that symlink type via the Git attributes. This
requires an index_state, though, something that Git for Windows'
`symlink()` replacement cannot know about because the function signature
is defined by the POSIX standard and not ours to change.

So let's introduce a helper function to create symbolic links that
*does* know about the index_state.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 apply.c              | 2 +-
 builtin/difftool.c   | 2 +-
 builtin/init-db.c    | 4 ++--
 compat/mingw.c       | 2 +-
 compat/mingw.h       | 4 +++-
 entry.c              | 2 +-
 git-compat-util.h    | 9 +++++++++
 merge-recursive.c    | 2 +-
 refs/files-backend.c | 2 +-
 9 files changed, 20 insertions(+), 9 deletions(-)

diff --git a/apply.c b/apply.c
index d19c26d332e790..90cf4ed57dd77a 100644
--- a/apply.c
+++ b/apply.c
@@ -4366,7 +4366,7 @@ static int try_create_file(struct apply_state *state, const char *path,
 		/* Although buf:size is counted string, it also is NUL
 		 * terminated.
 		 */
-		return !!symlink(buf, path);
+		return !!create_symlink(state && state->repo ? state->repo->index : NULL, buf, path);
 
 	fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666);
 	if (fd < 0)
diff --git a/builtin/difftool.c b/builtin/difftool.c
index faa3507369a23d..827508c38a482c 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -513,7 +513,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
 				}
 				add_path(&wtdir, wtdir_len, dst_path);
 				if (symlinks) {
-					if (symlink(wtdir.buf, rdir.buf)) {
+					if (create_symlink(lstate.istate, wtdir.buf, rdir.buf)) {
 						ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf);
 						goto finish;
 					}
diff --git a/builtin/init-db.c b/builtin/init-db.c
index f33d18e8533606..665d2da51bce8f 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -78,7 +78,7 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template_path,
 			if (strbuf_readlink(&lnk, template_path->buf,
 					    st_template.st_size) < 0)
 				die_errno(_("cannot readlink '%s'"), template_path->buf);
-			if (symlink(lnk.buf, path->buf))
+			if (create_symlink(NULL, lnk.buf, path->buf))
 				die_errno(_("cannot symlink '%s' '%s'"),
 					  lnk.buf, path->buf);
 			strbuf_release(&lnk);
@@ -300,7 +300,7 @@ static int create_default_files(const char *template_path,
 		path = git_path_buf(&buf, "tXXXXXX");
 		if (!close(xmkstemp(path)) &&
 		    !unlink(path) &&
-		    !symlink("testing", path) &&
+		    !create_symlink(NULL, "testing", path) &&
 		    !lstat(path, &st1) &&
 		    S_ISLNK(st1.st_mode))
 			unlink(path); /* good */
diff --git a/compat/mingw.c b/compat/mingw.c
index 97ac042a39a03a..f14e99154b43b7 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2858,7 +2858,7 @@ int link(const char *oldpath, const char *newpath)
 	return 0;
 }
 
-int symlink(const char *target, const char *link)
+int mingw_create_symlink(struct index_state *index, const char *target, const char *link)
 {
 	wchar_t wtarget[MAX_LONG_PATH], wlink[MAX_LONG_PATH];
 	int len;
diff --git a/compat/mingw.h b/compat/mingw.h
index 3b6fc2fd812b58..ac66aa939f4fe2 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -213,8 +213,10 @@ int setitimer(int type, struct itimerval *in, struct itimerval *out);
 int sigaction(int sig, struct sigaction *in, struct sigaction *out);
 int link(const char *oldpath, const char *newpath);
 int uname(struct utsname *buf);
-int symlink(const char *target, const char *link);
 int readlink(const char *path, char *buf, size_t bufsiz);
+struct index_state;
+int mingw_create_symlink(struct index_state *index, const char *target, const char *link);
+#define create_symlink mingw_create_symlink
 
 /*
  * replacements of existing functions
diff --git a/entry.c b/entry.c
index b079c787516fab..0c6ba1052d3a88 100644
--- a/entry.c
+++ b/entry.c
@@ -305,7 +305,7 @@ static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca
 		if (!has_symlinks || to_tempfile)
 			goto write_file_entry;
 
-		ret = symlink(new_blob, path);
+		ret = create_symlink(state->istate, new_blob, path);
 		free(new_blob);
 		if (ret)
 			return error_errno("unable to create symlink %s", path);
diff --git a/git-compat-util.h b/git-compat-util.h
index c1e61663c7d61c..586d1a199a0f0d 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -470,6 +470,15 @@ static inline int git_has_dir_sep(const char *path)
 #define is_mount_point is_mount_point_via_stat
 #endif
 
+#ifndef create_symlink
+struct index_state;
+static inline int git_create_symlink(struct index_state *index, const char *target, const char *link)
+{
+	return symlink(target, link);
+}
+#define create_symlink git_create_symlink
+#endif
+
 #ifndef query_user_email
 #define query_user_email() NULL
 #endif
diff --git a/merge-recursive.c b/merge-recursive.c
index 1ee6364e8b16b1..e4e1c7863bd8ba 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -993,7 +993,7 @@ static int update_file_flags(struct merge_options *opt,
 			char *lnk = xmemdupz(buf, size);
 			safe_create_leading_directories_const(path);
 			unlink(path);
-			if (symlink(lnk, path))
+			if (create_symlink(&opt->priv->orig_index, lnk, path))
 				ret = err(opt, _("failed to symlink '%s': %s"),
 					  path, strerror(errno));
 			free(lnk);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 95acab78eef34c..081512b9bbac89 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1890,7 +1890,7 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target)
 #ifndef NO_SYMLINK_HEAD
 	char *ref_path = get_locked_file_path(&lock->lk);
 	unlink(ref_path);
-	ret = symlink(target, ref_path);
+	ret = create_symlink(NULL, target, ref_path);
 	free(ref_path);
 
 	if (ret)

From ae5d6b0da221c26da9bb0f2f1a3e363f578203f0 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 5 Jul 2017 15:14:50 +0200
Subject: [PATCH 230/265] t5813: allow for $PWD to be a Windows path

Git for Windows uses MSYS2's Bash to run the test suite, which comes
with benefits but also at a heavy price: on the plus side, MSYS2's
POSIX emulation layer allows us to continue pretending that we are on a
Unix system, e.g. use Unix paths instead of Windows ones, yet this is
bought at a rather noticeable performance penalty.

There *are* some more native ports of Unix shells out there, though,
most notably BusyBox-w32's ash. These native ports do not use any POSIX
emulation layer (or at most a *very* thin one, choosing to avoid
features such as fork() that are expensive to emulate on Windows), and
they use native Windows paths (usually with forward slashes instead of
backslashes, which is perfectly legal in almost all use cases).

And here comes the problem: with a $PWD looking like, say,
C:/git-sdk-64/usr/src/git/t/trash directory.t5813-proto-disable-ssh
Git's test scripts get quite a bit confused, as their assumptions have
been shattered. Not only does this path contain a colon (oh no!), it
also does not start with a slash.

This is a problem e.g. when constructing a URL as t5813 does it:
ssh://remote$PWD. Not only is it impossible to separate the "host" from
the path with a $PWD as above, even prefixing $PWD by a slash won't
work, as /C:/git-sdk-64/... is not a valid path.

As a workaround, detect when $PWD does not start with a slash on
Windows, and simply strip the drive prefix, using an obscure feature of
Windows paths: if an absolute Windows path starts with a slash, it is
implicitly prefixed by the drive prefix of the current directory. As we
are talking about the current directory here, anyway, that strategy
works.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t5813-proto-disable-ssh.sh | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

diff --git a/t/t5813-proto-disable-ssh.sh b/t/t5813-proto-disable-ssh.sh
index 3f084ee306517b..0a2c77093babad 100755
--- a/t/t5813-proto-disable-ssh.sh
+++ b/t/t5813-proto-disable-ssh.sh
@@ -14,8 +14,23 @@ test_expect_success 'setup repository to clone' '
 '
 
 test_proto "host:path" ssh "remote:repo.git"
-test_proto "ssh://" ssh "ssh://remote$PWD/remote/repo.git"
-test_proto "git+ssh://" ssh "git+ssh://remote$PWD/remote/repo.git"
+
+hostdir="$PWD"
+if test_have_prereq MINGW && test "/${PWD#/}" != "$PWD"
+then
+	case "$PWD" in
+	[A-Za-z]:/*)
+		hostdir="${PWD#?:}"
+		;;
+	*)
+		skip_all="Unhandled PWD '$PWD'; skipping rest"
+		test_done
+		;;
+	esac
+fi
+
+test_proto "ssh://" ssh "ssh://remote$hostdir/remote/repo.git"
+test_proto "git+ssh://" ssh "git+ssh://remote$hostdir/remote/repo.git"
 
 # Don't even bother setting up a "-remote" directory, as ssh would generally
 # complain about the bogus option rather than completing our request. Our

From 36f6f3f2cbe99821e70e26b4eae3d87d4cdce749 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Mon, 2 Mar 2020 21:54:29 +0100
Subject: [PATCH 231/265] mingw: emulate stat() a little more faithfully

When creating directories via `safe_create_leading_directories()`, we
might encounter an already-existing directory which is not
readable by the current user. To handle that situation, Git's code calls
`stat()` to determine whether we're looking at a directory.

In such a case, `CreateFile()` will fail, though, no matter what, and
consequently `mingw_stat()` will fail, too. But POSIX semantics seem to
still allow `stat()` to go forward.

So let's call `mingw_lstat()` for the rescue if we fail to get a file
handle due to denied permission in `mingw_stat()`, and fill the stat
info that way.

We need to be careful to not allow this to go forward in case that we're
looking at a symbolic link: to resolve the link, we would still have to
create a file handle, and we just found out that we cannot. Therefore,
`stat()` still needs to fail with `EACCES` in that case.

This fixes https://github.com/git-for-windows/git/issues/2531.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 3800aeaa4de06d..19da2d63c7868f 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1057,7 +1057,19 @@ int mingw_stat(const char *file_name, struct stat *buf)
 			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
 			OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
 	if (hnd == INVALID_HANDLE_VALUE) {
-		errno = err_win_to_posix(GetLastError());
+		DWORD err = GetLastError();
+
+		if (err == ERROR_ACCESS_DENIED &&
+		    !mingw_lstat(file_name, buf) &&
+		    !S_ISLNK(buf->st_mode))
+			/*
+			 * POSIX semantics state to still try to fill
+			 * information, even if permission is denied to create
+			 * a file handle.
+			 */
+			return 0;
+
+		errno = err_win_to_posix(err);
 		return -1;
 	}
 	result = get_file_info_by_handle(hnd, buf);

From 4918f39a177d4ac7932a15d59e73226e51984c9e Mon Sep 17 00:00:00 2001
From: JiSeop Moon <zcube@zcube.kr>
Date: Mon, 23 Apr 2018 22:35:26 +0200
Subject: [PATCH 232/265] mingw: move the file_attr_to_st_mode() function
 definition

In preparation for making this function a bit more complicated (to allow
for special-casing the `ContainerMappedDirectories` in Windows
containers, which look like a symbolic link, but are not), let's move it
out of the header.

Signed-off-by: JiSeop Moon <zcube@zcube.kr>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 14 ++++++++++++++
 compat/win32.h | 14 +-------------
 2 files changed, 15 insertions(+), 13 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index dd1c97f40bb23c..bdda6e540c9965 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -3815,3 +3815,17 @@ int is_inside_windows_container(void)
 
 	return inside_container;
 }
+
+int file_attr_to_st_mode (DWORD attr, DWORD tag)
+{
+	int fMode = S_IREAD;
+	if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) && tag == IO_REPARSE_TAG_SYMLINK)
+		fMode |= S_IFLNK;
+	else if (attr & FILE_ATTRIBUTE_DIRECTORY)
+		fMode |= S_IFDIR;
+	else
+		fMode |= S_IFREG;
+	if (!(attr & FILE_ATTRIBUTE_READONLY))
+		fMode |= S_IWRITE;
+	return fMode;
+}
diff --git a/compat/win32.h b/compat/win32.h
index 671bcc81f93351..52169ae19f4371 100644
--- a/compat/win32.h
+++ b/compat/win32.h
@@ -6,19 +6,7 @@
 #include <windows.h>
 #endif
 
-static inline int file_attr_to_st_mode (DWORD attr, DWORD tag)
-{
-	int fMode = S_IREAD;
-	if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) && tag == IO_REPARSE_TAG_SYMLINK)
-		fMode |= S_IFLNK;
-	else if (attr & FILE_ATTRIBUTE_DIRECTORY)
-		fMode |= S_IFDIR;
-	else
-		fMode |= S_IFREG;
-	if (!(attr & FILE_ATTRIBUTE_READONLY))
-		fMode |= S_IWRITE;
-	return fMode;
-}
+extern int file_attr_to_st_mode (DWORD attr, DWORD tag);
 
 static inline int get_file_attr(const char *fname, WIN32_FILE_ATTRIBUTE_DATA *fdata)
 {

From 62c9b068021f3579607a4de5d7ce3fcc83136118 Mon Sep 17 00:00:00 2001
From: Bert Belder <bertbelder@gmail.com>
Date: Fri, 26 Oct 2018 11:51:51 +0200
Subject: [PATCH 233/265] mingw: allow to specify the symlink type in
 .gitattributes

On Windows, symbolic links have a type: a "file symlink" must point at
a file, and a "directory symlink" must point at a directory. If the
type of symlink does not match its target, it doesn't work.

Git does not record the type of symlink in the index or in a tree. On
checkout it'll guess the type, which only works if the target exists
at the time the symlink is created. This may often not be the case,
for example when the link points at a directory inside a submodule.

By specifying `symlink=file` or `symlink=dir` the user can specify what
type of symlink Git should create, so Git doesn't have to rely on
unreliable heuristics.

Signed-off-by: Bert Belder <bertbelder@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/gitattributes.txt | 30 +++++++++++++++++
 compat/mingw.c                  | 58 ++++++++++++++++++++++++++++++++-
 2 files changed, 87 insertions(+), 1 deletion(-)

diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 4b36d51beb66f0..85faf9c1429485 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -385,6 +385,36 @@ sign `$` upon checkout.  Any byte sequence that begins with
 with `$Id$` upon check-in.
 
 
+`symlink`
+^^^^^^^^^
+
+On Windows, symbolic links have a type: a "file symlink" must point at
+a file, and a "directory symlink" must point at a directory. If the
+type of symlink does not match its target, it doesn't work.
+
+Git does not record the type of symlink in the index or in a tree. On
+checkout it'll guess the type, which only works if the target exists
+at the time the symlink is created. This may often not be the case,
+for example when the link points at a directory inside a submodule.
+
+The `symlink` attribute allows you to explicitly set the type of symlink
+to `file` or `dir`, so Git doesn't have to guess. If you have a set of
+symlinks that point at other files, you can do:
+
+------------------------
+*.gif 	symlink=file
+------------------------
+
+To tell Git that a symlink points at a directory, use:
+
+------------------------
+tools_folder 	symlink=dir
+------------------------
+
+The `symlink` attribute is ignored on platforms other than Windows,
+since they don't distinguish between different types of symlinks.
+
+
 `filter`
 ^^^^^^^^
 
diff --git a/compat/mingw.c b/compat/mingw.c
index f14e99154b43b7..dc7893a41d1d4a 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -13,6 +13,7 @@
 #define SECURITY_WIN32
 #include <sspi.h>
 #include "win32/fscache.h"
+#include "../attr.h"
 
 #define HCAST(type, handle) ((type)(intptr_t)handle)
 
@@ -2858,6 +2859,37 @@ int link(const char *oldpath, const char *newpath)
 	return 0;
 }
 
+enum symlink_type {
+	SYMLINK_TYPE_UNSPECIFIED = 0,
+	SYMLINK_TYPE_FILE,
+	SYMLINK_TYPE_DIRECTORY,
+};
+
+static enum symlink_type check_symlink_attr(struct index_state *index, const char *link)
+{
+	static struct attr_check *check;
+	const char *value;
+
+	if (!index)
+		return SYMLINK_TYPE_UNSPECIFIED;
+
+	if (!check)
+		check = attr_check_initl("symlink", NULL);
+
+	git_check_attr(index, link, check);
+
+	value = check->items[0].value;
+	if (ATTR_UNSET(value))
+		return SYMLINK_TYPE_UNSPECIFIED;
+	if (!strcmp(value, "file"))
+		return SYMLINK_TYPE_FILE;
+	if (!strcmp(value, "dir") || !strcmp(value, "directory"))
+		return SYMLINK_TYPE_DIRECTORY;
+
+	warning(_("ignoring invalid symlink type '%s' for '%s'"), value, link);
+	return SYMLINK_TYPE_UNSPECIFIED;
+}
+
 int mingw_create_symlink(struct index_state *index, const char *target, const char *link)
 {
 	wchar_t wtarget[MAX_LONG_PATH], wlink[MAX_LONG_PATH];
@@ -2878,7 +2910,31 @@ int mingw_create_symlink(struct index_state *index, const char *target, const ch
 		if (wtarget[len] == '/')
 			wtarget[len] = '\\';
 
-	return create_phantom_symlink(wtarget, wlink);
+	switch (check_symlink_attr(index, link)) {
+	case SYMLINK_TYPE_UNSPECIFIED:
+		/* Create a phantom symlink: it is initially created as a file
+		 * symlink, but may change to a directory symlink later if/when
+		 * the target exists. */
+		return create_phantom_symlink(wtarget, wlink);
+	case SYMLINK_TYPE_FILE:
+		if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags))
+			break;
+		return 0;
+	case SYMLINK_TYPE_DIRECTORY:
+		if (!CreateSymbolicLinkW(wlink, wtarget,
+					 symlink_directory_flags))
+			break;
+		/* There may be dangling phantom symlinks that point at this
+		 * one, which should now morph into directory symlinks. */
+		process_phantom_symlinks();
+		return 0;
+	default:
+		BUG("unhandled symlink type");
+	}
+
+	/* CreateSymbolicLinkW failed. */
+	errno = err_win_to_posix(GetLastError());
+	return -1;
 }
 
 #ifndef _WINNT_H

From b0ee922c2180a0aae1da9c7f53083df7a186ac2a Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 7 Jul 2017 10:15:36 +0200
Subject: [PATCH 234/265] t9200: skip tests when $PWD contains a colon

On Windows, the current working directory is pretty much guaranteed to
contain a colon. If we feed that path to CVS, it mistakes it for a
separator between host and port, though.

This has not been a problem so far because Git for Windows uses MSYS2's
Bash using a POSIX emulation layer that also pretends that the current
directory is a Unix path (at least as long as we're in a shell script).

However, that is rather limiting, as Git for Windows also explores other
ports of other Unix shells. One of those is BusyBox-w32's ash, which is
a native port (i.e. *not* using any POSIX emulation layer, and certainly
not emulating Unix paths).

So let's just detect if there is a colon in $PWD and punt in that case.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t9200-git-cvsexportcommit.sh | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh
index 3e1034e6b44059..450ff1c2bc773d 100755
--- a/t/t9200-git-cvsexportcommit.sh
+++ b/t/t9200-git-cvsexportcommit.sh
@@ -11,6 +11,13 @@ if ! test_have_prereq PERL; then
 	test_done
 fi
 
+case "$PWD" in
+*:*)
+	skip_all='cvs would get confused by the colon in `pwd`; skipping tests'
+	test_done
+	;;
+esac
+
 cvs >/dev/null 2>&1
 if test $? -ne 1
 then

From d833cee7bab3255eefbf55e55e171740164930b9 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 17 May 2017 17:05:09 +0200
Subject: [PATCH 235/265] mingw: kill child processes in a gentler way

The TerminateProcess() function does not actually leave the child
processes any chance to perform any cleanup operations. This is bad
insofar as Git itself expects its signal handlers to run.

A symptom is e.g. a left-behind .lock file that would not be left behind
if the same operation was run, say, on Linux.

To remedy this situation, we use an obscure trick: we inject a thread
into the process that needs to be killed and to let that thread run the
ExitProcess() function with the desired exit status. Thanks J Wyman for
describing this trick.

The advantage is that the ExitProcess() function lets the atexit
handlers run. While this is still different from what Git expects (i.e.
running a signal handler), in practice Git sets up signal handlers and
atexit handlers that call the same code to clean up after itself.

In case that the gentle method to terminate the process failed, we still
fall back to calling TerminateProcess(), but in that case we now also
make sure that processes spawned by the spawned process are terminated;
TerminateProcess() does not give the spawned process a chance to do so
itself.

Please note that this change only affects how Git for Windows tries to
terminate processes spawned by Git's own executables. Third-party
software that *calls* Git and wants to terminate it *still* need to make
sure to imitate this gentle method, otherwise this patch will not have
any effect.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c              |  29 +++++--
 compat/win32/exit-process.h | 165 ++++++++++++++++++++++++++++++++++++
 2 files changed, 186 insertions(+), 8 deletions(-)
 create mode 100644 compat/win32/exit-process.h

diff --git a/compat/mingw.c b/compat/mingw.c
index 19da2d63c7868f..8630938ac6586d 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -7,6 +7,7 @@
 #include "../strbuf.h"
 #include "../run-command.h"
 #include "../cache.h"
+#include "win32/exit-process.h"
 #include "win32/lazyload.h"
 #include "../config.h"
 #include "dir.h"
@@ -2132,16 +2133,28 @@ int mingw_execvp(const char *cmd, char *const *argv)
 int mingw_kill(pid_t pid, int sig)
 {
 	if (pid > 0 && sig == SIGTERM) {
-		HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
-
-		if (TerminateProcess(h, -1)) {
+		HANDLE h = OpenProcess(PROCESS_CREATE_THREAD |
+				       PROCESS_QUERY_INFORMATION |
+				       PROCESS_VM_OPERATION | PROCESS_VM_WRITE |
+				       PROCESS_VM_READ | PROCESS_TERMINATE,
+				       FALSE, pid);
+		int ret;
+
+		if (h)
+			ret = exit_process(h, 128 + sig);
+		else {
+			h = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
+			if (!h) {
+				errno = err_win_to_posix(GetLastError());
+				return -1;
+			}
+			ret = terminate_process_tree(h, 128 + sig);
+		}
+		if (ret) {
+			errno = err_win_to_posix(GetLastError());
 			CloseHandle(h);
-			return 0;
 		}
-
-		errno = err_win_to_posix(GetLastError());
-		CloseHandle(h);
-		return -1;
+		return ret;
 	} else if (pid > 0 && sig == 0) {
 		HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
 		if (h) {
diff --git a/compat/win32/exit-process.h b/compat/win32/exit-process.h
new file mode 100644
index 00000000000000..d53989884cfb0c
--- /dev/null
+++ b/compat/win32/exit-process.h
@@ -0,0 +1,165 @@
+#ifndef EXIT_PROCESS_H
+#define EXIT_PROCESS_H
+
+/*
+ * This file contains functions to terminate a Win32 process, as gently as
+ * possible.
+ *
+ * At first, we will attempt to inject a thread that calls ExitProcess(). If
+ * that fails, we will fall back to terminating the entire process tree.
+ *
+ * For simplicity, these functions are marked as file-local.
+ */
+
+#include <tlhelp32.h>
+
+/*
+ * Terminates the process corresponding to the process ID and all of its
+ * directly and indirectly spawned subprocesses.
+ *
+ * This way of terminating the processes is not gentle: the processes get
+ * no chance of cleaning up after themselves (closing file handles, removing
+ * .lock files, terminating spawned processes (if any), etc).
+ */
+static int terminate_process_tree(HANDLE main_process, int exit_status)
+{
+	HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+	PROCESSENTRY32 entry;
+	DWORD pids[16384];
+	int max_len = sizeof(pids) / sizeof(*pids), i, len, ret = 0;
+	pid_t pid = GetProcessId(main_process);
+
+	pids[0] = (DWORD)pid;
+	len = 1;
+
+	/*
+	 * Even if Process32First()/Process32Next() seem to traverse the
+	 * processes in topological order (i.e. parent processes before
+	 * child processes), there is nothing in the Win32 API documentation
+	 * suggesting that this is guaranteed.
+	 *
+	 * Therefore, run through them at least twice and stop when no more
+	 * process IDs were added to the list.
+	 */
+	for (;;) {
+		int orig_len = len;
+
+		memset(&entry, 0, sizeof(entry));
+		entry.dwSize = sizeof(entry);
+
+		if (!Process32First(snapshot, &entry))
+			break;
+
+		do {
+			for (i = len - 1; i >= 0; i--) {
+				if (pids[i] == entry.th32ProcessID)
+					break;
+				if (pids[i] == entry.th32ParentProcessID)
+					pids[len++] = entry.th32ProcessID;
+			}
+		} while (len < max_len && Process32Next(snapshot, &entry));
+
+		if (orig_len == len || len >= max_len)
+			break;
+	}
+
+	for (i = len - 1; i > 0; i--) {
+		HANDLE process = OpenProcess(PROCESS_TERMINATE, FALSE, pids[i]);
+
+		if (process) {
+			if (!TerminateProcess(process, exit_status))
+				ret = -1;
+			CloseHandle(process);
+		}
+	}
+	if (!TerminateProcess(main_process, exit_status))
+		ret = -1;
+	CloseHandle(main_process);
+
+	return ret;
+}
+
+/**
+ * Determine whether a process runs in the same architecture as the current
+ * one. That test is required before we assume that GetProcAddress() returns
+ * a valid address *for the target process*.
+ */
+static inline int process_architecture_matches_current(HANDLE process)
+{
+	static BOOL current_is_wow = -1;
+	BOOL is_wow;
+
+	if (current_is_wow == -1 &&
+	    !IsWow64Process (GetCurrentProcess(), &current_is_wow))
+		current_is_wow = -2;
+	if (current_is_wow == -2)
+		return 0; /* could not determine current process' WoW-ness */
+	if (!IsWow64Process (process, &is_wow))
+		return 0; /* cannot determine */
+	return is_wow == current_is_wow;
+}
+
+/**
+ * Inject a thread into the given process that runs ExitProcess().
+ *
+ * Note: as kernel32.dll is loaded before any process, the other process and
+ * this process will have ExitProcess() at the same address.
+ *
+ * This function expects the process handle to have the access rights for
+ * CreateRemoteThread(): PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION,
+ * PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ.
+ *
+ * The idea comes from the Dr Dobb's article "A Safer Alternative to
+ * TerminateProcess()" by Andrew Tucker (July 1, 1999),
+ * http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547
+ *
+ * If this method fails, we fall back to running terminate_process_tree().
+ */
+static int exit_process(HANDLE process, int exit_code)
+{
+	DWORD code;
+
+	if (GetExitCodeProcess(process, &code) && code == STILL_ACTIVE) {
+		static int initialized;
+		static LPTHREAD_START_ROUTINE exit_process_address;
+		PVOID arg = (PVOID)(intptr_t)exit_code;
+		DWORD thread_id;
+		HANDLE thread = NULL;
+
+		if (!initialized) {
+			HINSTANCE kernel32 = GetModuleHandleA("kernel32");
+			if (!kernel32)
+				die("BUG: cannot find kernel32");
+			exit_process_address =
+				(LPTHREAD_START_ROUTINE)(void (*)(void))
+				GetProcAddress(kernel32, "ExitProcess");
+			initialized = 1;
+		}
+		if (!exit_process_address ||
+		    !process_architecture_matches_current(process))
+			return terminate_process_tree(process, exit_code);
+
+		thread = CreateRemoteThread(process, NULL, 0,
+					    exit_process_address,
+					    arg, 0, &thread_id);
+		if (thread) {
+			CloseHandle(thread);
+			/*
+			 * If the process survives for 10 seconds (a completely
+			 * arbitrary value picked from thin air), fall back to
+			 * killing the process tree via TerminateProcess().
+			 */
+			if (WaitForSingleObject(process, 10000) ==
+			    WAIT_OBJECT_0) {
+				CloseHandle(process);
+				return 0;
+			}
+		}
+
+		return terminate_process_tree(process, exit_code);
+	}
+
+	return 0;
+}
+
+#endif

From 5dc2070f00e21af6f2b0dfa010d289d7be9601b2 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 4 Jun 2020 23:16:07 +0200
Subject: [PATCH 236/265] mingw: special-case index entries for symlinks with
 buggy size

In https://github.com/git-for-windows/git/pull/2637, we fixed a bug
where symbolic links' target path sizes were recorded incorrectly in the
index. The downside of this fix was that every user with tracked
symbolic links in their checkouts would see them as modified in `git
status`, but not in `git diff`, and only a `git add <path>` (or `git add
-u`) would "fix" this.

Let's do better than that: we can detect that situation and simply
pretend that a symbolic link with a known bad size (or a size that just
happens to be that bad size, a _very_ unlikely scenario because it would
overflow our buffers due to the trailing NUL byte) means that it needs
to be re-checked as if we had just checked it out.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 read-cache.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/read-cache.c b/read-cache.c
index cd085195b59731..ea6150ea28f329 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -479,6 +479,17 @@ int ie_modified(struct index_state *istate,
 	 * then we know it is.
 	 */
 	if ((changed & DATA_CHANGED) &&
+#ifdef GIT_WINDOWS_NATIVE
+	    /*
+	     * Work around Git for Windows v2.27.0 fixing a bug where symlinks'
+	     * target path lengths were not read at all, and instead recorded
+	     * as 4096: now, all symlinks would appear as modified.
+	     *
+	     * So let's just special-case symlinks with a target path length
+	     * (i.e. `sd_size`) of 4096 and force them to be re-checked.
+	     */
+	    (!S_ISLNK(st->st_mode) || ce->ce_stat_data.sd_size != MAX_LONG_PATH) &&
+#endif
 	    (S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0))
 		return changed;
 

From 0bcc0ed50a7874978630dca389a01abbee360c85 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Mon, 23 Apr 2018 23:20:00 +0200
Subject: [PATCH 237/265] mingw: Windows Docker volumes are *not* symbolic
 links

... even if they may look like them.

As looking up the target of the "symbolic link" (just to see whether it
starts with `/ContainerMappedDirectories/`) is pretty expensive, we
do it when we can be *really* sure that there is a possibility that this
might be the case.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: JiSeop Moon <zcube@zcube.kr>
---
 compat/mingw.c         | 25 +++++++++++++++++++------
 compat/win32.h         |  2 +-
 compat/win32/fscache.c | 24 +++++++++++++++++++++++-
 3 files changed, 43 insertions(+), 8 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index bdda6e540c9965..2b2205febf8216 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -983,7 +983,7 @@ int mingw_lstat(const char *file_name, struct stat *buf)
 		buf->st_uid = 0;
 		buf->st_nlink = 1;
 		buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
-				reparse_tag);
+				reparse_tag, file_name);
 		buf->st_size = S_ISLNK(buf->st_mode) ? link_len :
 			fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
 		buf->st_dev = buf->st_rdev = 0; /* not used by Git */
@@ -1034,7 +1034,7 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
 	buf->st_gid = 0;
 	buf->st_uid = 0;
 	buf->st_nlink = 1;
-	buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes, 0);
+	buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes, 0, NULL);
 	buf->st_size = fdata.nFileSizeLow |
 		(((off_t)fdata.nFileSizeHigh)<<32);
 	buf->st_dev = buf->st_rdev = 0; /* not used by Git */
@@ -3816,12 +3816,25 @@ int is_inside_windows_container(void)
 	return inside_container;
 }
 
-int file_attr_to_st_mode (DWORD attr, DWORD tag)
+int file_attr_to_st_mode (DWORD attr, DWORD tag, const char *path)
 {
 	int fMode = S_IREAD;
-	if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) && tag == IO_REPARSE_TAG_SYMLINK)
-		fMode |= S_IFLNK;
-	else if (attr & FILE_ATTRIBUTE_DIRECTORY)
+	if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) &&
+	    tag == IO_REPARSE_TAG_SYMLINK) {
+		int flag = S_IFLNK;
+		char buf[MAX_LONG_PATH];
+
+		/*
+		 * Windows containers' mapped volumes are marked as reparse
+		 * points and look like symbolic links, but they are not.
+		 */
+		if (path && is_inside_windows_container() &&
+		    readlink(path, buf, sizeof(buf)) > 27 &&
+		    starts_with(buf, "/ContainerMappedDirectories/"))
+			flag = S_IFDIR;
+
+		fMode |= flag;
+	} else if (attr & FILE_ATTRIBUTE_DIRECTORY)
 		fMode |= S_IFDIR;
 	else
 		fMode |= S_IFREG;
diff --git a/compat/win32.h b/compat/win32.h
index 52169ae19f4371..299f01bdf0f5a4 100644
--- a/compat/win32.h
+++ b/compat/win32.h
@@ -6,7 +6,7 @@
 #include <windows.h>
 #endif
 
-extern int file_attr_to_st_mode (DWORD attr, DWORD tag);
+extern int file_attr_to_st_mode (DWORD attr, DWORD tag, const char *path);
 
 static inline int get_file_attr(const char *fname, WIN32_FILE_ATTRIBUTE_DATA *fdata)
 {
diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index 16b1e522f34182..baba69b40e2c67 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -200,8 +200,30 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache,
 		fdata->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ?
 		fdata->EaSize : 0;
 
+	/*
+	 * On certain Windows versions, host directories mapped into
+	 * Windows Containers ("Volumes", see https://docs.docker.com/storage/volumes/)
+	 * look like symbolic links, but their targets are paths that
+	 * are valid only in kernel mode.
+	 *
+	 * Let's work around this by detecting that situation and
+	 * telling Git that these are *not* symbolic links.
+	 */
+	if (fse->reparse_tag == IO_REPARSE_TAG_SYMLINK &&
+	    sizeof(buf) > (list ? list->len + 1 : 0) + fse->len + 1 &&
+	    is_inside_windows_container()) {
+		size_t off = 0;
+		if (list) {
+			memcpy(buf, list->dirent.d_name, list->len);
+			buf[list->len] = '/';
+			off = list->len + 1;
+		}
+		memcpy(buf + off, fse->dirent.d_name, fse->len);
+		buf[off + fse->len] = '\0';
+	}
+
 	fse->st_mode = file_attr_to_st_mode(fdata->FileAttributes,
-					    fdata->EaSize);
+					    fdata->EaSize, buf);
 	fse->dirent.d_type = S_ISREG(fse->st_mode) ? DT_REG :
 			S_ISDIR(fse->st_mode) ? DT_DIR : DT_LNK;
 	fse->u.s.st_size = S_ISLNK(fse->st_mode) ? MAX_LONG_PATH :

From 026a8ef118248d887899122d365d4312b448a199 Mon Sep 17 00:00:00 2001
From: Bert Belder <bertbelder@gmail.com>
Date: Fri, 26 Oct 2018 23:42:09 +0200
Subject: [PATCH 238/265] Win32: symlink: add test for `symlink` attribute

To verify that the symlink is resolved correctly, we use the fact that
`git.exe` is a native Win32 program, and that `git.exe config -f <path>`
therefore uses the native symlink resolution.

Signed-off-by: Bert Belder <bertbelder@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t2040-checkout-symlink-attr.sh | 46 ++++++++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)
 create mode 100755 t/t2040-checkout-symlink-attr.sh

diff --git a/t/t2040-checkout-symlink-attr.sh b/t/t2040-checkout-symlink-attr.sh
new file mode 100755
index 00000000000000..e00c31d096ce88
--- /dev/null
+++ b/t/t2040-checkout-symlink-attr.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+test_description='checkout symlinks with `symlink` attribute on Windows
+
+Ensures that Git for Windows creates symlinks of the right type,
+as specified by the `symlink` attribute in `.gitattributes`.'
+
+# Tell MSYS to create native symlinks. Without this flag test-lib's
+# prerequisite detection for SYMLINKS doesn't detect the right thing.
+MSYS=winsymlinks:nativestrict && export MSYS
+
+. ./test-lib.sh
+
+if ! test_have_prereq MINGW,SYMLINKS
+then
+	skip_all='skipping $0: MinGW-only test, which requires symlink support.'
+	test_done
+fi
+
+# Adds a symlink to the index without clobbering the work tree.
+cache_symlink () {
+	sha=$(printf '%s' "$1" | git hash-object --stdin -w) &&
+	git update-index --add --cacheinfo 120000,$sha,"$2"
+}
+
+test_expect_success 'checkout symlinks with attr' '
+	cache_symlink file1 file-link &&
+	cache_symlink dir dir-link &&
+
+	printf "file-link symlink=file\ndir-link symlink=dir\n" >.gitattributes &&
+	git add .gitattributes &&
+
+	git checkout . &&
+
+	mkdir dir &&
+	echo "[a]b=c" >file1 &&
+	echo "[x]y=z" >dir/file2 &&
+
+	# MSYS2 is very forgiving, it will resolve symlinks even if the
+	# symlink type is incorrect. To make this test meaningful, try
+	# them with a native, non-MSYS executable, such as `git config`.
+	test "$(git config -f file-link a.b)" = "c" &&
+	test "$(git config -f dir-link/file2 x.y)" = "z"
+'
+
+test_done

From 8464315903d645701c7f8818ea949ba7a2f39a09 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 20 Jul 2017 00:23:26 +0200
Subject: [PATCH 239/265] mingw: add a Makefile target to copy test artifacts

The Makefile target `install-mingit-test-artifacts` simply copies stuff
and things directly into a MinGit directory, including an init.bat
script to set everything up so that the tests can be run in a cmd
window.

Sadly, Git's test suite still relies on a Perl interpreter even if
compiled with NO_PERL=YesPlease. We punt for now, installing a small
script into /usr/bin/perl that hands off to an existing Perl of a Git
for Windows SDK.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 config.mak.uname | 56 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 56 insertions(+)

diff --git a/config.mak.uname b/config.mak.uname
index 39e0b8cc4b96ad..9fb11fcfff3d0d 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -738,6 +738,62 @@ else
 		NO_CURL = YesPlease
 	endif
 endif
+ifeq (i686,$(uname_M))
+	MINGW_PREFIX := mingw32
+endif
+ifeq (x86_64,$(uname_M))
+	MINGW_PREFIX := mingw64
+endif
+
+	DESTDIR_WINDOWS = $(shell cygpath -aw '$(DESTDIR_SQ)')
+	DESTDIR_MIXED = $(shell cygpath -am '$(DESTDIR_SQ)')
+install-mingit-test-artifacts:
+	install -m755 -d '$(DESTDIR_SQ)/usr/bin'
+	printf '%s\n%s\n' >'$(DESTDIR_SQ)/usr/bin/perl' \
+		"#!/mingw64/bin/busybox sh" \
+		"exec \"$(shell cygpath -am /usr/bin/perl.exe)\" \"\$$@\""
+
+	install -m755 -d '$(DESTDIR_SQ)'
+	printf '%s%s\n%s\n%s\n%s\n%s\n' >'$(DESTDIR_SQ)/init.bat' \
+		"PATH=$(DESTDIR_WINDOWS)\\$(MINGW_PREFIX)\\bin;" \
+		"C:\\WINDOWS;C:\\WINDOWS\\system32" \
+		"@set GIT_TEST_INSTALLED=$(DESTDIR_MIXED)/$(MINGW_PREFIX)/bin" \
+		"@`echo "$(DESTDIR_WINDOWS)" | sed 's/:.*/:/'`" \
+		"@cd `echo "$(DESTDIR_WINDOWS)" | sed 's/^.://'`\\test-git\\t" \
+		"@echo Now, run 'helper\\test-run-command testsuite'"
+
+	install -m755 -d '$(DESTDIR_SQ)/test-git'
+	sed 's/^\(NO_PERL\|NO_PYTHON\)=.*/\1=YesPlease/' \
+		<GIT-BUILD-OPTIONS >'$(DESTDIR_SQ)/test-git/GIT-BUILD-OPTIONS'
+
+	install -m755 -d '$(DESTDIR_SQ)/test-git/t/helper'
+	install -m755 $(TEST_PROGRAMS) '$(DESTDIR_SQ)/test-git/t/helper'
+	(cd t && $(TAR) cf - t[0-9][0-9][0-9][0-9] lib-diff) | \
+	(cd '$(DESTDIR_SQ)/test-git/t' && $(TAR) xf -)
+	install -m755 t/t556x_common t/*.sh '$(DESTDIR_SQ)/test-git/t'
+
+	install -m755 -d '$(DESTDIR_SQ)/test-git/templates'
+	(cd templates && $(TAR) cf - blt) | \
+	(cd '$(DESTDIR_SQ)/test-git/templates' && $(TAR) xf -)
+
+	# po/build/locale for t0200
+	install -m755 -d '$(DESTDIR_SQ)/test-git/po/build/locale'
+	(cd po/build/locale && $(TAR) cf - .) | \
+	(cd '$(DESTDIR_SQ)/test-git/po/build/locale' && $(TAR) xf -)
+
+	# git-daemon.exe for t5802, git-http-backend.exe for t5560
+	install -m755 -d '$(DESTDIR_SQ)/$(MINGW_PREFIX)/bin'
+	install -m755 git-daemon.exe git-http-backend.exe \
+		'$(DESTDIR_SQ)/$(MINGW_PREFIX)/bin'
+
+	# git-upload-archive (dashed) for t5000
+	install -m755 -d '$(DESTDIR_SQ)/$(MINGW_PREFIX)/bin'
+	install -m755 git-upload-archive.exe '$(DESTDIR_SQ)/$(MINGW_PREFIX)/bin'
+
+	# git-difftool--helper for t7800
+	install -m755 -d '$(DESTDIR_SQ)/$(MINGW_PREFIX)/libexec/git-core'
+	install -m755 git-difftool--helper \
+		'$(DESTDIR_SQ)/$(MINGW_PREFIX)/libexec/git-core'
 endif
 ifeq ($(uname_S),QNX)
 	COMPAT_CFLAGS += -DSA_RESTART=0

From 124609a0141e8687446eaea4a9934e10a9531f57 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Mon, 23 Apr 2018 00:24:29 +0200
Subject: [PATCH 240/265] mingw: really handle SIGINT

Previously, we did not install any handler for Ctrl+C, but now we really
want to because the MSYS2 runtime learned the trick to call the
ConsoleCtrlHandler when Ctrl+C was pressed.

With this, hitting Ctrl+C while `git log` is running will only terminate
the Git process, but not the pager. This finally matches the behavior on
Linux and on macOS.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/compat/mingw.c b/compat/mingw.c
index 8630938ac6586d..3ada1c33b615ef 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -3681,7 +3681,14 @@ static void adjust_symlink_flags(void)
 		symlink_file_flags |= 2;
 		symlink_directory_flags |= 2;
 	}
+}
 
+static BOOL WINAPI handle_ctrl_c(DWORD ctrl_type)
+{
+	if (ctrl_type != CTRL_C_EVENT)
+		return FALSE; /* we did not handle this */
+	mingw_raise(SIGINT);
+	return TRUE; /* we did handle this */
 }
 
 #ifdef _MSC_VER
@@ -3717,6 +3724,8 @@ int wmain(int argc, const wchar_t **wargv)
 #endif
 #endif
 
+	SetConsoleCtrlHandler(handle_ctrl_c, TRUE);
+
 	maybe_redirect_std_handles();
 	adjust_symlink_flags();
 	fsync_object_files = 1;

From 11a290d7cb44de8b45a20ea04741b73f3a6a8daa Mon Sep 17 00:00:00 2001
From: "Neeraj K. Singh" <neerajsi@microsoft.com>
Date: Wed, 27 Oct 2021 14:22:42 -0700
Subject: [PATCH 241/265] mingw: do not call xutftowcs_path in mingw_mktemp

The `xutftowcs_path` function canonicalizes absolute paths using GetFullPathNameW.
This canonicalization may change the length of the string (e.g. getting rid of \.\),
which breaks callers that pass the template string in a strbuf and expect the
length of the string to remain the same.

In my particular case, the tmp-objdir code is passing a strbuf to mkdtemp and is
breaking since the strbuf.len is no longer synchronized with strlen(strbuf.buf).

Signed-off-by: Neeraj K. Singh <neerajsi@microsoft.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index 19da2d63c7868f..929cfaa638957d 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1206,8 +1206,11 @@ char *mingw_mktemp(char *template)
 	int offset = 0;
 
 	/* we need to return the path, thus no long paths here! */
-	if (xutftowcs_path(wtemplate, template) < 0)
+	if (xutftowcsn(wtemplate, template, MAX_PATH, -1) < 0) {
+		if (errno == ERANGE)
+			errno = ENAMETOOLONG;
 		return NULL;
+	}
 
 	if (is_dir_sep(template[0]) && !is_dir_sep(template[1]) &&
 	    iswalpha(wtemplate[0]) && wtemplate[1] == L':') {

From 8ab1a6da5ceedbd324fa7ae1f67748f80ccfef7f Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 25 Nov 2021 11:26:41 +0100
Subject: [PATCH 242/265] Partially un-revert "editor: save and reset terminal
 after calling EDITOR"

In e3f7e01b50be (Revert "editor: save and reset terminal after calling
EDITOR", 2021-11-22), we reverted the commit wholesale where the
terminal state would be saved and restored before/after calling an
editor.

The reverted commit was intended to fix a problem with Windows Terminal
where simply calling `vi` would cause problems afterwards.

To fix the problem addressed by the revert, but _still_ keep the problem
with Windows Terminal fixed, let's revert the revert, with a twist: we
restrict the save/restore _specifically_ to the case where `vi` (or
`vim`) is called, and do not do the same for any other editor.

This should still catch the majority of the cases, and will bridge the
time until the original patch is re-done in a way that addresses all
concerns.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 editor.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/editor.c b/editor.c
index 8b9648281d7b53..f91cd15acef25b 100644
--- a/editor.c
+++ b/editor.c
@@ -4,6 +4,7 @@
 #include "strvec.h"
 #include "run-command.h"
 #include "sigchain.h"
+#include "compat/terminal.h"
 
 #ifndef DEFAULT_EDITOR
 #define DEFAULT_EDITOR "vi"
@@ -55,6 +56,7 @@ static int launch_specified_editor(const char *editor, const char *path,
 		return error("Terminal is dumb, but EDITOR unset");
 
 	if (strcmp(editor, ":")) {
+		int save_and_restore_term = !strcmp(editor, "vi") || !strcmp(editor, "vim");
 		struct strbuf realpath = STRBUF_INIT;
 		struct child_process p = CHILD_PROCESS_INIT;
 		int ret, sig;
@@ -83,7 +85,11 @@ static int launch_specified_editor(const char *editor, const char *path,
 			strvec_pushv(&p.env_array, (const char **)env);
 		p.use_shell = 1;
 		p.trace2_child_class = "editor";
+		if (save_and_restore_term)
+			save_and_restore_term = !save_term(1);
 		if (start_command(&p) < 0) {
+			if (save_and_restore_term)
+				restore_term();
 			strbuf_release(&realpath);
 			return error("unable to start editor '%s'", editor);
 		}
@@ -91,6 +97,8 @@ static int launch_specified_editor(const char *editor, const char *path,
 		sigchain_push(SIGINT, SIG_IGN);
 		sigchain_push(SIGQUIT, SIG_IGN);
 		ret = finish_command(&p);
+		if (save_and_restore_term)
+			restore_term();
 		strbuf_release(&realpath);
 		sig = ret - 128;
 		sigchain_pop(SIGINT);

From 5d1e0c6f3d84e122a42fe006809619c43d34c7d5 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 12 Aug 2016 10:54:26 +0200
Subject: [PATCH 243/265] status: carry the --no-lock-index option for
 backwards-compatibility

When a third-party tool periodically runs `git status` in order to keep
track of the state of the working tree, it is a bad idea to lock the
index: it might interfere with interactive commands executed by the
user, e.g. when the user wants to commit files.

Git for Windows introduced the `--no-lock-index` option a long time ago
to fix that (it made it into Git for Windows v2.9.2(3)) by simply
avoiding to write that file.

The downside is that the periodic `git status` calls will be a little
bit more wasteful because they may have to refresh the index repeatedly,
only to throw away the updates when it exits. This cannot really be
helped, though, as tools wanting to get a periodic update of the status
have no way to predict when the user may want to lock the index herself.

Sadly, a competing approach was submitted (by somebody who apparently
has less work on their plate than this maintainer) that made it into
v2.15.0 but is *different*: instead of a `git status`-only option, it is
an option that comes *before* the Git command and is called differently,
too.

Let's give previous users a chance to upgrade to newer Git for Windows
versions by handling the `--no-lock-index` option, still, though with a
big fat warning.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/git-status.txt |  7 +++++++
 builtin/commit.c             | 10 ++++++++++
 t/t7508-status.sh            | 11 +++++++++++
 3 files changed, 28 insertions(+)

diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt
index 54a4b29b473cc4..3e4d76ee9a4e4e 100644
--- a/Documentation/git-status.txt
+++ b/Documentation/git-status.txt
@@ -149,6 +149,13 @@ ignored, then the directory is not shown, but all contents are shown.
 	threshold.
 	See also linkgit:git-diff[1] `--find-renames`.
 
+--no-lock-index::
+--lock-index::
+	(DEPRECATED: use --no-optional-locks instead)
+	Specifies whether `git status` should try to lock the index and
+	update it afterwards if any changes were detected. Defaults to
+	`--lock-index`.
+
 <pathspec>...::
 	See the 'pathspec' entry in linkgit:gitglossary[7].
 
diff --git a/builtin/commit.c b/builtin/commit.c
index 009a1de0a3d3f6..1b9151f1e57c33 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1475,6 +1475,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 {
 	static int no_renames = -1;
 	static const char *rename_score_arg = (const char *)-1;
+	static int no_lock_index = 0;
 	static struct wt_status s;
 	unsigned int progress_flag = 0;
 	int fd;
@@ -1513,6 +1514,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 		OPT_CALLBACK_F('M', "find-renames", &rename_score_arg,
 		  N_("n"), N_("detect renames, optionally set similarity index"),
 		  PARSE_OPT_OPTARG | PARSE_OPT_NONEG, opt_parse_rename_score),
+		OPT_BOOL(0, "no-lock-index", &no_lock_index,
+			 N_("(DEPRECATED: use `git --no-optional-locks status` "
+			    "instead) Do not lock the index")),
 		OPT_END(),
 	};
 
@@ -1529,6 +1533,12 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 	finalize_colopts(&s.colopts, -1);
 	finalize_deferred_config(&s);
 
+	if (no_lock_index) {
+		warning("--no-lock-index is deprecated, use --no-optional-locks"
+			" instead");
+		setenv(GIT_OPTIONAL_LOCKS_ENVIRONMENT, "false", 1);
+	}
+
 	handle_untracked_files_arg(&s);
 	handle_ignored_arg(&s);
 
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index 2b7ef6c41a4554..31fdb56eaba170 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -1646,6 +1646,17 @@ test_expect_success '"Initial commit" should not be noted in commit template' '
 	test_i18ngrep ! "Initial commit" output
 '
 
+test_expect_success '--no-lock-index prevents index update and is deprecated' '
+	test-tool chmtime =1234567890 .git/index &&
+	git status --no-lock-index 2>err &&
+	grep "no-lock-index is deprecated" err &&
+	test-tool chmtime -v +0 .git/index >out &&
+	grep ^1234567890 out &&
+	git status &&
+	test-tool chmtime -v +0 .git/index >out &&
+	! grep ^1234567890 out
+'
+
 test_expect_success '--no-optional-locks prevents index update' '
 	test_set_magic_mtime .git/index &&
 	git --no-optional-locks status &&

From 56bf049d52eb088109f59a478a8714da180d3061 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 11 Oct 2019 13:22:24 +0200
Subject: [PATCH 244/265] Modify the Code of Conduct for Git for Windows

The Git project followed suite and added their Code of Conduct, based on
the Contributors' Covenant v1.4.

We edit it slightly to reflect Git for Windows' particulars.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 CODE_OF_CONDUCT.md | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 0215b1fd4c05e6..8b373cf5518a5d 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,9 +1,9 @@
-# Git Code of Conduct
+# Git for Windows Code of Conduct
 
 This code of conduct outlines our expectations for participants within
-the Git community, as well as steps for reporting unacceptable behavior.
-We are committed to providing a welcoming and inspiring community for
-all and expect our code of conduct to be honored. Anyone who violates
+the **Git for Windows** community, as well as steps for reporting unacceptable
+behavior. We are committed to providing a welcoming and inspiring community
+for all and expect our code of conduct to be honored. Anyone who violates
 this code of conduct may be banned from the community.
 
 ## Our Pledge
@@ -65,8 +65,8 @@ representative at an online or offline event.
 ## Enforcement
 
 Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported to the community leaders responsible for enforcement at
-git@sfconservancy.org, or individually:
+reported by contacting the Git for Windows maintainer or the community leaders
+responsible for enforcement at git@sfconservancy.org, or individually:
 
   - Ævar Arnfjörð Bjarmason <avarab@gmail.com>
   - Christian Couder <christian.couder@gmail.com>

From c04c401fac3e170f0fc2060555da99c453df80d7 Mon Sep 17 00:00:00 2001
From: Derrick Stolee <dstolee@microsoft.com>
Date: Thu, 1 Mar 2018 12:10:14 -0500
Subject: [PATCH 245/265] CONTRIBUTING.md: add guide for first-time
 contributors

Getting started contributing to Git can be difficult on a Windows
machine. CONTRIBUTING.md contains a guide to getting started, including
detailed steps for setting up build tools, running tests, and
submitting patches to upstream.

[includes an example by Pratik Karki how to submit v2, v3, v4, etc.]

Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
---
 CONTRIBUTING.md | 417 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 417 insertions(+)
 create mode 100644 CONTRIBUTING.md

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000000000..ca9770df8c4808
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,417 @@
+How to Contribute to Git for Windows
+====================================
+
+Git was originally designed for Unix systems and still today, all the build tools for the Git
+codebase assume you have standard Unix tools available in your path. If you have an open-source
+mindset and want to start contributing to Git, but primarily use a Windows machine, then you may
+have trouble getting started. This guide is for you.
+
+Get the Source
+--------------
+
+Clone the [GitForWindows repository on GitHub](https://github.com/git-for-windows/git).
+It is helpful to create your own fork for storing your development branches.
+
+Windows uses different line endings than Unix systems. See
+[this GitHub article on working with line endings](https://help.github.com/articles/dealing-with-line-endings/#refreshing-a-repository-after-changing-line-endings)
+if you have trouble with line endings.
+
+Build the Source
+----------------
+
+First, download and install the latest [Git for Windows SDK (64-bit)](https://github.com/git-for-windows/build-extra/releases/latest).
+When complete, you can run the Git SDK, which creates a new Git Bash terminal window with
+the additional development commands, such as `make`.
+
+    As of time of writing, the SDK uses a different credential manager, so you may still want to use normal Git
+    Bash for interacting with your remotes.  Alternatively, use SSH rather than HTTPS and
+    avoid credential manager problems.
+
+You should now be ready to type `make` from the root of your `git` source directory.
+Here are some helpful variations:
+
+* `make -j[N] DEVELOPER=1`: Compile new sources using up to N concurrent processes.
+  The `DEVELOPER` flag turns on all warnings; code failing these warnings will not be
+  accepted upstream ("upstream" = "the core Git project").
+* `make clean`: Delete all compiled files.
+
+When running `make`, you can use `-j$(nproc)` to automatically use the number of processors
+on your machine as the number of concurrent build processes.
+
+You can go deeper on the Windows-specific build process by reading the
+[technical overview](https://github.com/git-for-windows/git/wiki/Technical-overview) or the
+[guide to compiling Git with Visual Studio](https://github.com/git-for-windows/git/wiki/Compiling-Git-with-Visual-Studio).
+
+## Building `git` on Windows with Visual Studio
+
+The typical approach to building `git` is to use the standard `Makefile` with GCC, as
+above. Developers working in a Windows environment may want to instead build with the
+[Microsoft Visual C++ compiler and libraries toolset (MSVC)](https://blogs.msdn.microsoft.com/vcblog/2017/03/07/msvc-the-best-choice-for-windows/).
+There are a few benefits to using MSVC over GCC during your development, including creating
+symbols for debugging and [performance tracing](https://github.com/Microsoft/perfview#perfview-overview).
+
+There are two ways to build Git for Windows using MSVC. Each have their own merits.
+
+### Using SDK Command Line
+
+Use one of the following commands from the SDK Bash window to build Git for Windows:
+
+```
+    make MSVC=1 -j12
+    make MSVC=1 DEBUG=1 -j12
+```
+
+The first form produces release-mode binaries; the second produces debug-mode binaries.
+Both forms produce PDB files and can be debugged.  However, the first is best for perf
+tracing and the second is best for single-stepping.
+
+You can then open Visual Studio and select File -> Open -> Project/Solution and select
+the compiled `git.exe` file. This creates a basic solution and you can use the debugging
+and performance tracing tools in Visual Studio to monitor a Git process. Use the Debug
+Properties page to set the working directory and command line arguments.
+
+Be sure to clean up before switching back to GCC (or to switch between debug and
+release MSVC builds):
+
+```
+    make MSVC=1 -j12 clean
+    make MSVC=1 DEBUG=1 -j12 clean
+```
+
+### Using the IDE
+
+If you prefer working in Visual Studio with a solution full of projects, then you can use
+CMake, either by letting Visual Studio configure it automatically (simply open Git's
+top-level directory via `File>Open>Folder...`) or by (downloading and) running
+[CMake](https://cmake.org) manually.
+
+What to Change?
+---------------
+
+Many new contributors ask: What should I start working on?
+
+One way to win big with the open-source community is to look at the
+[issues page](https://github.com/git-for-windows/git/issues) and see if there are any issues that
+you can fix quickly, or if anything catches your eye.
+
+You can also look at [the unofficial Chromium issues page](https://crbug.com/git) for
+multi-platform issues. You can look at recent user questions on
+[the Git mailing list](https://public-inbox.org/git).
+
+Or you can "scratch your own itch", i.e. address an issue you have with Git. The team at Microsoft where the Git for Windows maintainer works, for example, is focused almost entirely on [improving performance](https://blogs.msdn.microsoft.com/devops/2018/01/11/microsofts-performance-contributions-to-git-in-2017/).
+We approach our work by finding something that is slow and try to speed it up. We start our
+investigation by reliably reproducing the slow behavior, then running that example using
+the MSVC build and tracing the results in PerfView.
+
+You could also think of something you wish Git could do, and make it do that thing! The
+only concern I would have with this approach is whether or not that feature is something
+the community also wants. If this excites you though, go for it! Don't be afraid to
+[get involved in the mailing list](http://vger.kernel.org/vger-lists.html#git) early for
+feedback on the idea.
+
+Test Your Changes
+-----------------
+
+After you make your changes, it is important that you test your changes. Manual testing is
+important, but checking and extending the existing test suite is even more important. You
+want to run the functional tests to see if you broke something else during your change, and
+you want to extend the functional tests to be sure no one breaks your feature in the future.
+
+### Functional Tests
+
+Navigate to the `t/` directory and type `make` to run all tests or use `prove` as
+[described in the Git for Windows wiki](https://github.com/git-for-windows/git/wiki/Building-Git):
+
+```
+prove -j12 --state=failed,save ./t[0-9]*.sh
+```
+
+You can also run each test directly by running the corresponding shell script with a name
+like `tNNNN-descriptor.sh`.
+
+If you are adding new functionality, you may need to create unit tests by creating
+helper commands that test a very limited action. These commands are stored in `t/helpers`.
+When adding a helper, be sure to add a line to `t/Makefile` and to the `.gitignore` for the
+binary file you add. The Git community prefers functional tests using the full `git`
+executable, so try to exercise your new code using `git` commands before creating a test
+helper.
+
+To find out why a test failed, repeat the test with the `-x -v -d -i` options and then
+navigate to the appropriate "trash" directory to see the data shape that was used for the
+test failed step.
+
+Read [`t/README`](t/README) for more details.
+
+### Performance Tests
+
+If you are working on improving performance, you will need to be acquainted with the
+performance tests in `t/perf`. There are not too many performance tests yet, but adding one
+as your first commit in a patch series helps to communicate the boost your change provides.
+
+To check the change in performance across multiple versions of `git`, you can use the
+`t/perf/run` script. For example, to compare the performance of `git rev-list` across the
+`core/master` and `core/next` branches compared to a `topic` branch, you can run
+
+```
+cd t/perf
+./run core/master core/next topic -- p0001-rev-list.sh
+```
+
+You can also set certain environment variables to help test the performance on different
+repositories or with more repetitions. The full list is available in
+[the `t/perf/README` file](t/perf/README),
+but here are a few important ones:
+
+```
+GIT_PERF_REPO=/path/to/repo
+GIT_PERF_LARGE_REPO=/path/to/large/repo
+GIT_PERF_REPEAT_COUNT=10
+```
+
+When running the performance tests on Linux, you may see a message "Can't locate JSON.pm in
+@INC" and that means you need to run `sudo cpanm install JSON` to get the JSON perl package.
+
+For running performance tests, it can be helpful to set up a few repositories with strange
+data shapes, such as:
+
+**Many objects:** Clone repos such as [Kotlin](https://github.com/jetbrains/kotlin), [Linux](https://github.com/torvalds/linux), or [Android](https://source.android.com/setup/downloading).
+
+**Many pack-files:** You can split a fresh clone into multiple pack-files of size at most
+16MB by running `git repack -adfF --max-pack-size=16m`. See the
+[`git repack` documentation](https://git-scm.com/docs/git-repack) for more information.
+You can count the number of pack-files using `ls .git/objects/pack/*.pack | wc -l`.
+
+**Many loose objects:** If you already split your repository into multiple pack-files, then
+you can pick one to split into loose objects using `cat .git/objects/pack/[id].pack | git unpack-objects`;
+delete the `[id].pack` and `[id].idx` files after this. You can count the number of loose
+bjects using `ls .git/objects/??/* | wc -l`.
+
+**Deep history:** Usually large repositories also have deep histories, but you can use the
+[test-many-commits-1m repo](https://github.com/cirosantilli/test-many-commits-1m/) to
+target deep histories without the overhead of many objects. One issue with this repository:
+there are no merge commits, so you will need to use a different repository to test a "wide"
+commit history.
+
+**Large Index:** You can generate a large index and repo by using the scripts in
+`t/perf/repos`.  There are two scripts. `many-files.sh` which will generate a repo with
+same tree and blobs but different paths.  Using `many-files.sh -d 5 -w 10 -f 9` will create
+a repo with ~1 million entries in the index. `inflate-repo.sh` will use an existing repo
+and copy the current work tree until it is a specified size.
+
+Test Your Changes on Linux
+--------------------------
+
+It can be important to work directly on the [core Git codebase](https://github.com/git/git),
+such as a recent commit into the `master` or `next` branch that has not been incorporated
+into Git for Windows. Also, it can help to run functional and performance tests on your
+code in Linux before submitting patches to the mailing list, which focuses on many platforms.
+The differences between Windows and Linux are usually enough to catch most cross-platform
+issues.
+
+### Using the Windows Subsystem for Linux
+
+The [Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/install-win10)
+allows you to [install Ubuntu Linux as an app](https://www.microsoft.com/en-us/store/p/ubuntu/9nblggh4msv6)
+that can run Linux executables on top of the Windows kernel. Internally,
+Linux syscalls are interpreted by the WSL, everything else is plain Ubuntu.
+
+First, open WSL (either type "Bash" in Cortana, or execute "bash.exe" in a CMD window).
+Then install the prerequisites, and `git` for the initial clone:
+
+```
+sudo apt-get update
+sudo apt-get install git gcc make libssl-dev libcurl4-openssl-dev \
+		     libexpat-dev tcl tk gettext git-email zlib1g-dev
+```
+
+Then, clone and build:
+
+```
+git clone https://github.com/git-for-windows/git
+cd git
+git remote add -f upstream https://github.com/git/git
+make
+```
+
+Be sure to clone into `/home/[user]/` and not into any folder under `/mnt/?/` or your build
+will fail due to colons in file names.
+
+### Using a Linux Virtual Machine with Hyper-V
+
+If you prefer, you can use a virtual machine (VM) to run Linux and test your changes in the
+full environment. The test suite runs a lot faster on Linux than on Windows or with the WSL.
+You can connect to the VM using an SSH terminal like
+[PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/).
+
+The following instructions are for using Hyper-V, which is available in some versions of Windows.
+There are many virtual machine alternatives available, if you do not have such a version installed.
+
+* [Download an Ubuntu Server ISO](https://www.ubuntu.com/download/server).
+* Open [Hyper-V Manager](https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v).
+* [Set up a virtual switch](https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/quick-start/connect-to-network)
+  so your VM can reach the network.
+* Select "Quick Create", name your machine, select the ISO as installation source, and un-check
+  "This virtual machine will run Windows."
+* Go through the Ubuntu install process, being sure to select to install OpenSSH Server.
+* When install is complete, log in and check the SSH server status with `sudo service ssh status`.
+    * If the service is not found, install with `sudo apt-get install openssh-server`.
+    * If the service is not running, then use `sudo service ssh start`.
+* Use `shutdown -h now` to shutdown the VM, go to the Hyper-V settings for the VM, expand Network Adapter
+  to select "Advanced Features", and set the MAC address to be static (this can save your VM from losing
+  network if shut down incorrectly).
+* Provide as many cores to your VM as you can (for parallel builds).
+* Restart your VM, but do not connect.
+* Use `ssh` in Git Bash, download [PuTTY](http://www.putty.org/), or use your favorite SSH client to connect to the VM through SSH.
+
+In order to build and use `git`, you will need the following libraries via `apt-get`:
+
+```
+sudo apt-get update
+sudo apt-get install git gcc make libssl-dev libcurl4-openssl-dev \
+                     libexpat-dev tcl tk gettext git-email zlib1g-dev
+```
+
+To get your code from your Windows machine to the Linux VM, it is easiest to push the branch to your fork of Git and clone your fork in the Linux VM.
+
+Don't forget to set your `git` config with your preferred name, email, and editor.
+
+Polish Your Commits
+-------------------
+
+Before submitting your patch, be sure to read the [coding guidelines](https://github.com/git/git/blob/master/Documentation/CodingGuidelines)
+and check your code to match as best you can. This can be a lot of effort, but it saves
+time during review to avoid style issues.
+
+The other possibly major difference between the mailing list submissions and GitHub PR workflows
+is that each commit will be reviewed independently. Even if you are submitting a
+patch series with multiple commits, each commit must stand on it's own and be reviewable
+by itself. Make sure the commit message clearly explain the why of the commit not the how.
+Describe what is wrong with the current code and how your changes have made the code better.
+
+When preparing your patch, it is important to put yourself in the shoes of the Git community.
+Accepting a patch requires more justification than approving a pull request from someone on
+your team. The community has a stable product and is responsible for keeping it stable. If
+you introduce a bug, then they cannot count on you being around to fix it. When you decided
+to start work on a new feature, they were not part of the design discussion and may not
+even believe the feature is worth introducing.
+
+Questions to answer in your patch message (and commit messages) may include:
+* Why is this patch necessary?
+* How does the current behavior cause pain for users?
+* What kinds of repositories are necessary for noticing a difference?
+* What design options did you consider before writing this version? Do you have links to
+  code for those alternate designs?
+* Is this a performance fix? Provide clear performance numbers for various well-known repos.
+
+Here are some other tips that we use when cleaning up our commits:
+
+* Commit messages should be wrapped at 76 columns per line (or less; 72 is also a
+  common choice).
+* Make sure the commits are signed off using `git commit (-s|--signoff)`. See
+  [SubmittingPatches](https://github.com/git/git/blob/v2.8.1/Documentation/SubmittingPatches#L234-L286)
+  for more details about what this sign-off means.
+* Check for whitespace errors using `git diff --check [base]...HEAD` or `git log --check`.
+* Run `git rebase --whitespace=fix` to correct upstream issues with whitespace.
+* Become familiar with interactive rebase (`git rebase -i`) because you will be reordering,
+  squashing, and editing commits as your patch or series of patches is reviewed.
+* Make sure any shell scripts that you add have the executable bit set on them.  This is
+  usually for test files that you add in the `/t` directory.  You can use
+  `git add --chmod=+x [file]` to update it. You can test whether a file is marked as executable
+  using `git ls-files --stage \*.sh`; the first number is 100755 for executable files.
+* Your commit titles should match the "area: change description" format. Rules of thumb:
+    * Choose "<area>: " prefix appropriately.
+    * Keep the description short and to the point.
+    * The word that follows the "<area>: " prefix is not capitalized.
+    * Do not include a full-stop at the end of the title.
+    * Read a few commit messages -- using `git log origin/master`, for instance -- to
+      become acquainted with the preferred commit message style.
+* Build source using  `make DEVELOPER=1` for extra-strict compiler warnings.
+
+Submit Your Patch
+-----------------
+
+Git for Windows [accepts pull requests on GitHub](https://github.com/git-for-windows/git/pulls), but
+these are reserved for Windows-specific improvements. For core Git, submissions are accepted on
+[the Git mailing list](https://public-inbox.org/git).
+
+### Configure Git to Send Emails
+
+There are a bunch of options for configuring the `git send-email` command. These options can
+be found in the documentation for
+[`git config`](https://git-scm.com/docs/git-config) and
+[`git send-email`](https://git-scm.com/docs/git-send-email).
+
+```
+git config --global sendemail.smtpserver <smtp server>
+git config --global sendemail.smtpserverport 587
+git config --global sendemail.smtpencryption tls
+git config --global sendemail.smtpuser <email address>
+```
+
+To avoid storing your password in the config file, store it in the Git credential manager:
+
+```
+$ git credential fill
+protocol=smtp
+host=<stmp server>
+username=<email address>
+password=password
+```
+
+Before submitting a patch, read the [Git documentation on submitting patches](https://github.com/git/git/blob/master/Documentation/SubmittingPatches).
+
+To construct a patch set, use the `git format-patch` command. There are three important options:
+
+* `--cover-letter`: If specified, create a `[v#-]0000-cover-letter.patch` file that can be
+  edited to describe the patch as a whole. If you previously added a branch description using
+  `git branch --edit-description`, you will end up with a 0/N mail with that description and
+  a nice overall diffstat.
+* `--in-reply-to=[Message-ID]`: This will mark your cover letter as replying to the given
+  message (which should correspond to your previous iteration). To determine the correct Message-ID,
+  find the message you are replying to on [public-inbox.org/git](https://public-inbox.org/git) and take
+  the ID from between the angle brackets.
+
+* `--subject-prefix=[prefix]`: This defaults to [PATCH]. For subsequent iterations, you will want to
+  override it like `--subject-prefix="[PATCH v2]"`.  You can also use the `-v` option to have it
+  automatically generate the version number in the patches.
+
+If you have multiple commits and use the `--cover-letter` option be sure to open the
+`0000-cover-letter.patch` file to update the subject and add some details about the overall purpose
+of the patch series.
+
+### Examples
+
+To generate a single commit patch file:
+```
+git format-patch -s -o [dir] -1
+```
+To generate four patch files from the last three commits with a cover letter:
+```
+git format-patch --cover-letter -s -o [dir] HEAD~4
+```
+To generate version 3 with four patch files from the last four commits with a cover letter:
+```
+git format-patch --cover-letter -s -o [dir] -v 3 HEAD~4
+```
+
+### Submit the Patch
+
+Run [`git send-email`](https://git-scm.com/docs/git-send-email), starting with a test email:
+
+```
+git send-email --to=yourself@address.com  [dir with patches]/*.patch
+```
+
+After checking the receipt of your test email, you can send to the list and to any
+potentially interested reviewers.
+
+```
+git send-email --to=git@vger.kernel.org --cc=<email1> --cc=<email2> [dir with patches]/*.patch
+```
+
+To submit a nth version patch (say version 3):
+
+```
+git send-email --to=git@vger.kernel.org --cc=<email1> --cc=<email2> \
+    --in-reply-to=<the message id of cover letter of patch v2> [dir with patches]/*.patch
+```

From 5444e58b3205ef7d6cfd445afea687f4b213e91b Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 10 Jan 2014 16:16:03 -0600
Subject: [PATCH 246/265] README.md: Add a Windows-specific preamble

Includes touch-ups by Philip Oakley.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 README.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 75 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 7ce4f05bae8120..fe6cf6a90c3c74 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,76 @@
-[![Build status](https://github.com/git/git/workflows/CI/badge.svg)](https://github.com/git/git/actions?query=branch%3Amaster+event%3Apush)
+Git for Windows
+===============
+
+[![Open in Visual Studio Code](https://open.vscode.dev/badges/open-in-vscode.svg)](https://open.vscode.dev/git-for-windows/git)
+[![Build status](https://github.com/git-for-windows/git/workflows/CI/badge.svg)](https://github.com/git-for-windows/git/actions?query=branch%3Amain+event%3Apush)
+[![Join the chat at https://gitter.im/git-for-windows/git](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/git-for-windows/git?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+This is [Git for Windows](http://git-for-windows.github.io/), the Windows port
+of [Git](http://git-scm.com/).
+
+The Git for Windows project is run using a [governance
+model](http://git-for-windows.github.io/governance-model.html). If you
+encounter problems, you can report them as [GitHub
+issues](https://github.com/git-for-windows/git/issues), discuss them on Git
+for Windows' [Google Group](http://groups.google.com/group/git-for-windows),
+and [contribute bug
+fixes](https://github.com/git-for-windows/git/wiki/How-to-participate).
+
+To build Git for Windows, please either install [Git for Windows'
+SDK](https://gitforwindows.org/#download-sdk), start its `git-bash.exe`, `cd`
+to your Git worktree and run `make`, or open the Git worktree as a folder in
+Visual Studio.
+
+To verify that your build works, use one of the following methods:
+
+- If you want to test the built executables within Git for Windows' SDK,
+  prepend `<worktree>/bin-wrappers` to the `PATH`.
+- Alternatively, run `make install` in the Git worktree.
+- If you need to test this in a full installer, run `sdk build
+  git-and-installer`.
+- You can also "install" Git into an existing portable Git via `make install
+  DESTDIR=<dir>` where `<dir>` refers to the top-level directory of the
+  portable Git. In this instance, you will want to prepend that portable Git's
+  `/cmd` directory to the `PATH`, or test by running that portable Git's
+  `git-bash.exe` or `git-cmd.exe`.
+- If you built using a recent Visual Studio, you can use the menu item
+  `Build>Install git` (you will want to click on `Project>CMake Settings for
+  Git` first, then click on `Edit JSON` and then point `installRoot` to the
+  `mingw64` directory of an already-unpacked portable Git).
+
+  As in the previous  bullet point, you will then prepend `/cmd` to the `PATH`
+  or run using the portable Git's `git-bash.exe` or `git-cmd.exe`.
+- If you want to run the built executables in-place, but in a CMD instead of
+  inside a Bash, you can run a snippet like this in the `git-bash.exe` window
+  where Git was built (ensure that the `EOF` line has no leading spaces), and
+  then paste into the CMD window what was put in the clipboard:
+
+  ```sh
+  clip.exe <<EOF
+  set GIT_EXEC_PATH=$(cygpath -aw .)
+  set PATH=$(cygpath -awp ".:contrib/scalar:/mingw64/bin:/usr/bin:$PATH")
+  set GIT_TEMPLATE_DIR=$(cygpath -aw templates/blt)
+  set GITPERLLIB=$(cygpath -aw perl/build/lib)
+  EOF
+  ```
+- If you want to run the built executables in-place, but outside of Git for
+  Windows' SDK, and without an option to set/override any environment
+  variables (e.g. in Visual Studio's debugger), you can call the Git executable
+  by its absolute path and use the `--exec-path` option, like so:
+
+  ```cmd
+  C:\git-sdk-64\usr\src\git\git.exe --exec-path=C:\git-sdk-64\usr\src\git help
+  ```
+
+  Note: for this to work, you have to hard-link (or copy) the `.dll` files from
+  the `/mingw64/bin` directory to the Git worktree, or add the `/mingw64/bin`
+  directory to the `PATH` somehow or other.
+
+To make sure that you are testing the correct binary, call `./git.exe version`
+in the Git worktree, and then call `git version` in a directory/window where
+you want to test Git, and verify that they refer to the same version (you may
+even want to pass the command-line option `--build-options` to look at the
+exact commit from which the Git version was built).
 
 Git - fast, scalable, distributed revision control system
 =========================================================
@@ -29,7 +101,7 @@ CVS users may also want to read [Documentation/gitcvs-migration.txt][]
 (`man gitcvs-migration` or `git help cvs-migration` if git is
 installed).
 
-The user discussion and development of Git take place on the Git
+The user discussion and development of core Git take place on the Git
 mailing list -- everyone is welcome to post bug reports, feature
 requests, comments and patches to git@vger.kernel.org (read
 [Documentation/SubmittingPatches][] for instructions on patch submission
@@ -43,6 +115,7 @@ To subscribe to the list, send an email with just "subscribe git" in
 the body to majordomo@vger.kernel.org (not the Git list). The mailing
 list archives are available at <https://lore.kernel.org/git/>,
 <http://marc.info/?l=git> and other archival sites.
+The core git mailing list is plain text (no HTML!).
 
 Issues which are security relevant should be disclosed privately to
 the Git Security mailing list <git-security@googlegroups.com>.

From 1eb2e750a1c8521d0b011606305cd6a7c27f6893 Mon Sep 17 00:00:00 2001
From: Brendan Forster <brendan@github.com>
Date: Thu, 18 Feb 2016 21:29:50 +1100
Subject: [PATCH 247/265] Add an issue template

With improvements by Clive Chan, Adric Norris, Ben Bodenmiller and
Philip Oakley.

Helped-by: Clive Chan <cc@clive.io>
Helped-by: Adric Norris <landstander668@gmail.com>
Helped-by: Ben Bodenmiller <bbodenmiller@hotmail.com>
Helped-by: Philip Oakley <philipoakley@iee.org>
Signed-off-by: Brendan Forster <brendan@github.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/ISSUE_TEMPLATE.md | 63 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 63 insertions(+)
 create mode 100644 .github/ISSUE_TEMPLATE.md

diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 00000000000000..75edc4d5b47c29
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,63 @@
+ - [ ] I was not able to find an [open](https://github.com/git-for-windows/git/issues?q=is%3Aopen) or [closed](https://github.com/git-for-windows/git/issues?q=is%3Aclosed) issue matching what I'm seeing
+
+### Setup
+
+ - Which version of Git for Windows are you using? Is it 32-bit or 64-bit?
+
+```
+$ git --version --build-options
+
+** insert your machine's response here **
+```
+
+ - Which version of Windows are you running? Vista, 7, 8, 10? Is it 32-bit or 64-bit?
+
+```
+$ cmd.exe /c ver
+
+** insert your machine's response here **
+```
+
+ - What options did you set as part of the installation? Or did you choose the
+   defaults?
+
+```
+# One of the following:
+> type "C:\Program Files\Git\etc\install-options.txt"
+> type "C:\Program Files (x86)\Git\etc\install-options.txt"
+> type "%USERPROFILE%\AppData\Local\Programs\Git\etc\install-options.txt"
+$ cat /etc/install-options.txt
+
+** insert your machine's response here **
+```
+
+ - Any other interesting things about your environment that might be related
+   to the issue you're seeing?
+
+** insert your response here **
+
+### Details
+
+ - Which terminal/shell are you running Git from? e.g Bash/CMD/PowerShell/other
+
+** insert your response here **
+
+ - What commands did you run to trigger this issue? If you can provide a
+   [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve)
+   this will help us understand the issue.
+
+```
+** insert your commands here **
+```
+ - What did you expect to occur after running these commands?
+
+** insert here **
+
+ - What actually happened instead?
+
+** insert here **
+
+ - If the problem was occurring with a specific repository, can you provide the
+   URL to that repository to help us with testing?
+
+** insert URL here **

From 8a001c47226ae2a2bae1cef3d886f7e3afeef9e3 Mon Sep 17 00:00:00 2001
From: Philip Oakley <philipoakley@iee.org>
Date: Fri, 22 Dec 2017 17:15:50 +0000
Subject: [PATCH 248/265] Modify the GitHub Pull Request template (to reflect
 Git for Windows)

Git for Windows accepts pull requests; Core Git does not. Therefore we
need to adjust the template (because it only matches core Git's
project management style, not ours).

Also: direct Git for Windows enhancements to their contributions page,
space out the text for easy reading, and clarify that the mailing list
is plain text, not HTML.

Signed-off-by: Philip Oakley <philipoakley@iee.org>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/PULL_REQUEST_TEMPLATE.md | 20 ++++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 952c7c3a2aa11e..831ef6f19f1d11 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,7 +1,19 @@
-Thanks for taking the time to contribute to Git! Please be advised that the
-Git community does not use github.com for their contributions. Instead, we use
-a mailing list (git@vger.kernel.org) for code submissions, code reviews, and
-bug reports. Nevertheless, you can use GitGitGadget (https://gitgitgadget.github.io/)
+Thanks for taking the time to contribute to Git!
+
+Those seeking to contribute to the Git for Windows fork should see
+http://gitforwindows.org/#contribute on how to contribute Windows specific
+enhancements.
+
+If your contribution is for the core Git functions and documentation
+please be aware that the Git community does not use the github.com issues
+or pull request mechanism for their contributions.
+
+Instead, we use the Git mailing list (git@vger.kernel.org) for code and
+documentation submissions, code reviews, and bug reports. The
+mailing list is plain text only (anything with HTML is sent directly
+to the spam folder).
+
+Nevertheless, you can use GitGitGadget (https://gitgitgadget.github.io/)
 to conveniently send your Pull Requests commits to our mailing list.
 
 Please read the "guidelines for contributing" linked above!

From 7b95afbfa10a4be9d525f16e5c0045e232f7a776 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 20 Feb 2018 15:44:57 +0100
Subject: [PATCH 249/265] .github: Add configuration for the Sentiment Bot

The sentiment bot will help detect when things get too heated.
Hopefully.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/config.yml | 10 ++++++++++
 1 file changed, 10 insertions(+)
 create mode 100644 .github/config.yml

diff --git a/.github/config.yml b/.github/config.yml
new file mode 100644
index 00000000000000..45edb7ba37ce02
--- /dev/null
+++ b/.github/config.yml
@@ -0,0 +1,10 @@
+# Configuration for sentiment-bot - https://github.com/behaviorbot/sentiment-bot
+
+# *Required* toxicity threshold between 0 and .99 with the higher numbers being
+# the most toxic. Anything higher than this threshold will be marked as toxic
+# and commented on
+sentimentBotToxicityThreshold: .7
+
+# *Required* Comment to reply with
+sentimentBotReplyComment: >
+  Please be sure to review the code of conduct and be respectful of other users. cc/ @git-for-windows/trusted-git-for-windows-developers

From bfe5ad06647c59fbcf0cdae4d9bb106c4a709624 Mon Sep 17 00:00:00 2001
From: Alejandro Barreto <alejandro.barreto@ni.com>
Date: Fri, 9 Mar 2018 14:17:54 -0600
Subject: [PATCH 250/265] Document how $HOME is set on Windows

Git documentation refers to $HOME and $XDG_CONFIG_HOME often, but does not specify how or where these values come from on Windows where neither is set by default. The new documentation reflects the behavior of setup_windows_environment() in compat/mingw.c.

Signed-off-by: Alejandro Barreto <alejandro.barreto@ni.com>
---
 Documentation/git.txt | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/Documentation/git.txt b/Documentation/git.txt
index 13f83a2a3a1220..6965a88ae3b4c5 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -441,6 +441,14 @@ Environment Variables
 ---------------------
 Various Git commands use the following environment variables:
 
+System
+~~~~~~
+`HOME`::
+	Specifies the path to the user's home directory. On Windows, if
+	unset, Git will set a process environment variable equal to:
+	`$HOMEDRIVE$HOMEPATH` if both `$HOMEDRIVE` and `$HOMEPATH` exist;
+	otherwise `$USERPROFILE` if `$USERPROFILE` exists.
+
 The Git Repository
 ~~~~~~~~~~~~~~~~~~
 These environment variables apply to 'all' core Git commands. Nb: it

From 0e8e0e8d87eeea6760c364e89bce990ac2c0342e Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Thu, 9 Nov 2017 16:55:59 +0100
Subject: [PATCH 251/265] status: reinstate --show-ignored-directory as a
 deprecated option

It was a bad idea to just remove that option from Git for Windows
v2.15.0, as early users of that (still experimental) option would have
been puzzled what they are supposed to do now.

So let's reintroduce the flag, but make sure to show the user good
advice how to fix this going forward.

We'll remove this option in a more orderly fashion when we're certain
that the option is no longer used (previous Visual Studio versions
relied on it).

The option is deprecated now, therefore we make sure that keeps saying
so until we finally remove it.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/commit.c                         |  11 ++
 t/t7522-status-show-ignored-directory.sh | 150 +++++++++++++++++++++++
 2 files changed, 161 insertions(+)
 create mode 100755 t/t7522-status-show-ignored-directory.sh

diff --git a/builtin/commit.c b/builtin/commit.c
index 1b9151f1e57c33..5e690bbac228e5 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1476,6 +1476,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 	static int no_renames = -1;
 	static const char *rename_score_arg = (const char *)-1;
 	static int no_lock_index = 0;
+	static int show_ignored_directory = 0;
 	static struct wt_status s;
 	unsigned int progress_flag = 0;
 	int fd;
@@ -1514,6 +1515,10 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 		OPT_CALLBACK_F('M', "find-renames", &rename_score_arg,
 		  N_("n"), N_("detect renames, optionally set similarity index"),
 		  PARSE_OPT_OPTARG | PARSE_OPT_NONEG, opt_parse_rename_score),
+		OPT_BOOL(0, "show-ignored-directory", &show_ignored_directory,
+			N_("(DEPRECATED: use --ignore=matching instead) Only "
+			   "show directories that match an ignore pattern "
+			   "name.")),
 		OPT_BOOL(0, "no-lock-index", &no_lock_index,
 			 N_("(DEPRECATED: use `git --no-optional-locks status` "
 			    "instead) Do not lock the index")),
@@ -1539,6 +1544,12 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 		setenv(GIT_OPTIONAL_LOCKS_ENVIRONMENT, "false", 1);
 	}
 
+	if (show_ignored_directory) {
+		warning("--show-ignored-directory was deprecated, use "
+			"--ignored=matching instead");
+		ignored_arg = "matching";
+	}
+
 	handle_untracked_files_arg(&s);
 	handle_ignored_arg(&s);
 
diff --git a/t/t7522-status-show-ignored-directory.sh b/t/t7522-status-show-ignored-directory.sh
new file mode 100755
index 00000000000000..dd323814586815
--- /dev/null
+++ b/t/t7522-status-show-ignored-directory.sh
@@ -0,0 +1,150 @@
+#!/bin/sh
+#
+#
+
+test_description='git status collapse ignored'
+
+. ./test-lib.sh
+
+
+cat >.gitignore <<\EOF
+*.ign
+ignored_dir/
+!*.unignore
+EOF
+
+# commit initial ignore file
+test_expect_success 'setup initial commit and ignore file' '
+	git add . &&
+	test_tick &&
+	git commit -m "Initial commit"
+'
+
+cat >expect <<\EOF
+? err
+? expect
+? output
+! dir/ignored/ignored_1.ign
+! dir/ignored/ignored_2.ign
+! ignored/ignored_1.ign
+! ignored/ignored_2.ign
+EOF
+
+# Test status behavior on folder with ignored files
+test_expect_success 'setup folder with ignored files' '
+	mkdir -p ignored dir/ignored &&
+	touch ignored/ignored_1.ign ignored/ignored_2.ign \
+		dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign
+'
+
+test_expect_success 'Verify behavior of status on folders with ignored files' '
+	test_when_finished "git clean -fdx" &&
+	git status --porcelain=v2 --ignored --untracked-files=all --show-ignored-directory >output 2>err &&
+	test_cmp expect output &&
+	grep "deprecated.*use --ignored=matching instead" err
+'
+
+# Test status bahavior on folder with tracked and ignored files
+cat >expect <<\EOF
+? expect
+? output
+! dir/tracked_ignored/ignored_1.ign
+! dir/tracked_ignored/ignored_2.ign
+! tracked_ignored/ignored_1.ign
+! tracked_ignored/ignored_2.ign
+EOF
+
+test_expect_success 'setup folder with tracked & ignored files' '
+	mkdir -p tracked_ignored dir/tracked_ignored &&
+	touch tracked_ignored/tracked_1 tracked_ignored/tracked_2 \
+		tracked_ignored/ignored_1.ign tracked_ignored/ignored_2.ign \
+		dir/tracked_ignored/tracked_1 dir/tracked_ignored/tracked_2 \
+		dir/tracked_ignored/ignored_1.ign dir/tracked_ignored/ignored_2.ign &&
+
+	git add tracked_ignored/tracked_1 tracked_ignored/tracked_2 \
+		dir/tracked_ignored/tracked_1 dir/tracked_ignored/tracked_2 &&
+	test_tick &&
+	git commit -m "commit tracked files"
+'
+
+test_expect_success 'Verify status on folder with tracked & ignored files' '
+	test_when_finished "git clean -fdx && git reset HEAD~1 --hard" &&
+	git status --porcelain=v2 --ignored --untracked-files=all --show-ignored-directory >output &&
+	test_cmp expect output
+'
+
+
+# Test status behavior on folder with untracked and ignored files
+cat >expect <<\EOF
+? dir/untracked_ignored/untracked_1
+? dir/untracked_ignored/untracked_2
+? expect
+? output
+? untracked_ignored/untracked_1
+? untracked_ignored/untracked_2
+! dir/untracked_ignored/ignored_1.ign
+! dir/untracked_ignored/ignored_2.ign
+! untracked_ignored/ignored_1.ign
+! untracked_ignored/ignored_2.ign
+EOF
+
+test_expect_success 'setup folder with tracked & ignored files' '
+	mkdir -p untracked_ignored dir/untracked_ignored &&
+	touch untracked_ignored/untracked_1 untracked_ignored/untracked_2 \
+		untracked_ignored/ignored_1.ign untracked_ignored/ignored_2.ign \
+		dir/untracked_ignored/untracked_1 dir/untracked_ignored/untracked_2 \
+		dir/untracked_ignored/ignored_1.ign dir/untracked_ignored/ignored_2.ign
+'
+
+test_expect_success 'Verify status on folder with tracked & ignored files' '
+	test_when_finished "git clean -fdx" &&
+	git status --porcelain=v2 --ignored --untracked-files=all --show-ignored-directory >output &&
+	test_cmp expect output
+'
+
+# Test status behavior on ignored folder
+cat >expect <<\EOF
+? expect
+? output
+! ignored_dir/
+EOF
+
+test_expect_success 'setup folder with tracked & ignored files' '
+	mkdir ignored_dir &&
+	touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
+		ignored_dir/ignored_1.ign ignored_dir/ignored_2.ign
+'
+
+test_expect_success 'Verify status on folder with tracked & ignored files' '
+	test_when_finished "git clean -fdx" &&
+	git status --porcelain=v2 --ignored --untracked-files=all --show-ignored-directory >output &&
+	test_cmp expect output
+'
+
+# Test status behavior on ignored folder with tracked file
+cat >expect <<\EOF
+? expect
+? output
+! ignored_dir/ignored_1
+! ignored_dir/ignored_1.ign
+! ignored_dir/ignored_2
+! ignored_dir/ignored_2.ign
+EOF
+
+test_expect_success 'setup folder with tracked & ignored files' '
+	mkdir ignored_dir &&
+	touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
+		ignored_dir/ignored_1.ign ignored_dir/ignored_2.ign \
+		ignored_dir/tracked &&
+	git add -f ignored_dir/tracked &&
+	test_tick &&
+	git commit -m "Force add file in ignored directory"
+'
+
+test_expect_success 'Verify status on folder with tracked & ignored files' '
+	test_when_finished "git clean -fdx && git reset HEAD~1 --hard" &&
+	git status --porcelain=v2 --ignored --untracked-files=all --show-ignored-directory >output &&
+	test_cmp expect output
+'
+
+test_done

From 6753e36df596338cfb7b3124cb80931290dd8570 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 10 Dec 2019 21:41:57 +0100
Subject: [PATCH 252/265] reset: reinstate support for the deprecated --stdin
 option

The `--stdin` option was a well-established paradigm in other commands,
therefore we implemented it in `git reset` for use by Visual Studio.

Unfortunately, upstream Git decided that it is time to introduce
`--pathspec-from-file` instead.

To keep backwards-compatibility for some grace period, we therefore
reinstate the `--stdin` option on top of the `--pathspec-from-file`
option, but mark it firmly as deprecated.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/git-reset.txt | 11 +++++++++++
 builtin/reset.c             | 14 ++++++++++++++
 t/t7108-reset-stdin.sh      | 32 ++++++++++++++++++++++++++++++++
 3 files changed, 57 insertions(+)
 create mode 100755 t/t7108-reset-stdin.sh

diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 01cb4c9b9c56a7..6a44de12788439 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -12,6 +12,7 @@ SYNOPSIS
 'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]
 'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
 'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
+DEPRECATED: 'git reset' [-q] [--stdin [-z]] [<tree-ish>]
 
 DESCRIPTION
 -----------
@@ -132,6 +133,16 @@ OPTIONS
 +
 For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
 
+--stdin::
+	DEPRECATED (use `--pathspec-from-file=-` instead): Instead of taking
+	list of paths from the command line, read list of paths from the
+	standard input. Paths are separated by LF (i.e. one path per line) by
+	default.
+
+-z::
+	DEPRECATED (use `--pathspec-file-nul` instead): Only meaningful with
+	`--stdin`; paths are separated with NUL character instead of LF.
+
 EXAMPLES
 --------
 
diff --git a/builtin/reset.c b/builtin/reset.c
index 344fff8f3a9322..ef514f861ab0c3 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -26,6 +26,8 @@
 #include "submodule.h"
 #include "submodule-config.h"
 #include "dir.h"
+#include "strbuf.h"
+#include "quote.h"
 
 #define REFRESH_INDEX_DELAY_WARNING_IN_MS (2 * 1000)
 
@@ -34,6 +36,7 @@ static const char * const git_reset_usage[] = {
 	N_("git reset [-q] [<tree-ish>] [--] <pathspec>..."),
 	N_("git reset [-q] [--pathspec-from-file [--pathspec-file-nul]] [<tree-ish>]"),
 	N_("git reset --patch [<tree-ish>] [--] [<pathspec>...]"),
+	N_("DEPRECATED: git reset [-q] [--stdin [-z]] [<tree-ish>]"),
 	NULL
 };
 
@@ -398,6 +401,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	struct object_id oid;
 	struct pathspec pathspec;
 	int intent_to_add = 0;
+	int nul_term_line = 0, read_from_stdin = 0;
 	const struct option options[] = {
 		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
 		OPT_BOOL(0, "no-refresh", &no_refresh,
@@ -419,6 +423,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 				N_("record only the fact that removed paths will be added later")),
 		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
 		OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
+		OPT_BOOL('z', NULL, &nul_term_line,
+			N_("DEPRECATED (use --pathspec-file-nul instead): paths are separated with NUL character")),
+		OPT_BOOL(0, "stdin", &read_from_stdin,
+				N_("DEPRECATED (use --pathspec-from-file=- instead): read paths from <stdin>")),
 		OPT_END()
 	};
 
@@ -428,6 +436,12 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 						PARSE_OPT_KEEP_DASHDASH);
 	parse_args(&pathspec, argv, prefix, patch_mode, &rev);
 
+	if (read_from_stdin) {
+		pathspec_from_file = "-";
+		if (nul_term_line)
+			pathspec_file_nul = 1;
+	}
+
 	if (pathspec_from_file) {
 		if (patch_mode)
 			die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--patch");
diff --git a/t/t7108-reset-stdin.sh b/t/t7108-reset-stdin.sh
new file mode 100755
index 00000000000000..b7cbcbf869296c
--- /dev/null
+++ b/t/t7108-reset-stdin.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+test_description='reset --stdin'
+
+. ./test-lib.sh
+
+test_expect_success 'reset --stdin' '
+	test_commit hello &&
+	git rm hello.t &&
+	test -z "$(git ls-files hello.t)" &&
+	echo hello.t | git reset --stdin &&
+	test hello.t = "$(git ls-files hello.t)"
+'
+
+test_expect_success 'reset --stdin -z' '
+	test_commit world &&
+	git rm hello.t world.t &&
+	test -z "$(git ls-files hello.t world.t)" &&
+	printf world.tQworld.tQhello.tQ | q_to_nul | git reset --stdin -z &&
+	printf "hello.t\nworld.t\n" >expect &&
+	git ls-files >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success '--stdin requires --mixed' '
+	echo hello.t >list &&
+	test_must_fail git reset --soft --stdin <list &&
+	test_must_fail git reset --hard --stdin <list &&
+	git reset --mixed --stdin <list
+'
+
+test_done

From 01d80a17baed3eeaa8036c07d0dd117f937a57a1 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 11 Dec 2015 06:59:13 +0100
Subject: [PATCH 253/265] mingw: deprecate old-style runtime-prefix handling in
 interpolate_path()

On Windows, an absolute POSIX path needs to be turned into a Windows
one. We used to interpret paths starting with a single `/` as relative
to the runtime-prefix, but now these need to be prefixed with
`%(prefix)/`. Let's warn for now, but still handle it.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 path.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/path.c b/path.c
index d73146b6cd266a..de8d76411aa2b6 100644
--- a/path.c
+++ b/path.c
@@ -739,6 +739,12 @@ char *interpolate_path(const char *path, int real_home)
 	if (skip_prefix(path, "%(prefix)/", &path))
 		return system_path(path);
 
+#ifdef __MINGW32__
+	if (path[0] == '/') {
+		warning(_("encountered old-style '%s' that should be '%%(prefix)%s'"), path, path);
+		return system_path(path + 1);
+	}
+#endif
 	if (path[0] == '~') {
 		const char *first_slash = strchrnul(path, '/');
 		const char *username = path + 1;

From 1012bbb2e951228283430b98a7b5d6e7e5281c95 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 29 Sep 2020 13:50:59 +0200
Subject: [PATCH 254/265] Add a GitHub workflow to monitor component updates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Rather than using private IFTTT Applets that send mails to this
maintainer whenever a new version of a Git for Windows component was
released, let's use the power of GitHub workflows to make this process
publicly visible.

This workflow monitors the Atom/RSS feeds, and opens a ticket whenever a
new version was released.

Note: Bash sometimes releases multiple patched versions within a few
minutes of each other (i.e. 5.1p1 through 5.1p4, 5.0p15 and 5.0p16). The
MSYS2 runtime also has a similar system. We can address those patches as
a group, so we shouldn't get multiple issues about them.

Note further: We're not acting on newlib releases, OpenSSL alphas, Perl
release candidates or non-stable Perl releases. There's no need to open
issues about them.

Co-authored-by: Matthias Aßhauer <mha1993@live.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .github/workflows/monitor-components.yml | 84 ++++++++++++++++++++++++
 1 file changed, 84 insertions(+)
 create mode 100644 .github/workflows/monitor-components.yml

diff --git a/.github/workflows/monitor-components.yml b/.github/workflows/monitor-components.yml
new file mode 100644
index 00000000000000..a8192ad259d355
--- /dev/null
+++ b/.github/workflows/monitor-components.yml
@@ -0,0 +1,84 @@
+name: Monitor component updates
+
+# Git for Windows is a slightly modified subset of MSYS2. Some of its
+# components are maintained by Git for Windows, others by MSYS2. To help
+# keeping the former up to date, this workflow monitors the Atom/RSS feeds
+# and opens new tickets for each new component version.
+
+on:
+  schedule:
+    - cron: "23 8,11,14,17 * * *"
+  workflow_dispatch:
+
+env:
+  CHARACTER_LIMIT: 5000
+  MAX_AGE: 48h
+
+jobs:
+  job:
+    runs-on: ubuntu-latest
+    environment: monitor-components
+    strategy:
+      matrix:
+        component:
+          - label: git
+            feed: https://github.com/git/git/tags.atom
+          - label: git-lfs
+            feed: https://github.com/git-lfs/git-lfs/tags.atom
+          - label: gcm-core
+            feed: https://github.com/microsoft/git-credential-manager-core/tags.atom
+          - label: tig
+            feed: https://github.com/jonas/tig/tags.atom
+          - label: cygwin
+            feed: https://github.com/cygwin/cygwin/releases.atom
+            title-pattern: ^(?!.*newlib)
+          - label: msys2-runtime-package
+            feed: https://github.com/msys2/MSYS2-packages/commits/master/msys2-runtime.atom
+          - label: msys2-runtime
+            feed: https://github.com/msys2/msys2-runtime/commits/HEAD.atom
+            aggregate: true
+          - label: openssh
+            feed: https://github.com/openssh/openssh-portable/tags.atom
+          - label: openssl
+            feed: https://github.com/openssl/openssl/tags.atom
+            title-pattern: ^(?!.*alpha)
+          - label: gnutls
+            feed: https://gnutls.org/news.atom
+          - label: heimdal
+            feed: https://github.com/heimdal/heimdal/tags.atom
+          - label: git-sizer
+            feed: https://github.com/github/git-sizer/tags.atom
+          - label: gitflow
+            feed: https://github.com/petervanderdoes/gitflow-avh/tags.atom
+          - label: curl
+            feed: https://github.com/curl/curl/tags.atom
+          - label: libgpg-error
+            feed: https://github.com/gpg/libgpg-error/releases.atom
+            title-pattern: ^libgpg-error-[0-9\.]*$
+          - label: libgcrypt
+            feed: https://github.com/gpg/libgcrypt/releases.atom
+            title-pattern: ^libgcrypt-[0-9\.]*$
+          - label: gpg
+            feed: https://github.com/gpg/gnupg/releases.atom
+          - label: mintty
+            feed: https://github.com/mintty/mintty/releases.atom
+          - label: p7zip
+            feed: https://sourceforge.net/projects/p7zip/rss?path=/p7zip
+          - label: bash
+            feed: https://git.savannah.gnu.org/cgit/bash.git/atom/?h=master
+            aggregate: true
+          - label: perl
+            feed: https://github.com/Perl/perl5/tags.atom
+            title-pattern: ^(?!.*(5\.[0-9]+[13579]|RC))
+      fail-fast: false
+    steps:
+      - uses: git-for-windows/rss-to-issues@v0
+        with:
+          feed: ${{matrix.component.feed}}
+          prefix: "[New ${{matrix.component.label}} version]"
+          labels: component-update
+          github-token: ${{ secrets.MONITOR_COMPONENTS_PAT }}
+          character-limit: ${{ env.CHARACTER_LIMIT }}
+          max-age: ${{ env.MAX_AGE }}
+          aggregate: ${{matrix.component.aggregate}}
+          title-pattern: ${{matrix.component.title-pattern}}

From 16a922974b9ae1f57ea439d4687bc9cf56d71e09 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Fri, 23 Aug 2019 14:14:42 +0200
Subject: [PATCH 255/265] SECURITY.md: document Git for Windows' policies

This is the recommended way on GitHub to describe policies revolving around
security issues and about supported versions.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 SECURITY.md | 56 +++++++++++++++++++++++++++++++++--------------------
 1 file changed, 35 insertions(+), 21 deletions(-)

diff --git a/SECURITY.md b/SECURITY.md
index c720c2ae7f9580..328351684298a4 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -28,24 +28,38 @@ Examples for details to include:
 
 ## Supported Versions
 
-There are no official "Long Term Support" versions in Git.
-Instead, the maintenance track (i.e. the versions based on the
-most recently published feature release, also known as ".0"
-version) sees occasional updates with bug fixes.
-
-Fixes to vulnerabilities are made for the maintenance track for
-the latest feature release and merged up to the in-development
-branches. The Git project makes no formal guarantee for any
-older maintenance tracks to receive updates. In practice,
-though, critical vulnerability fixes are applied not only to the
-most recent track, but to at least a couple more maintenance
-tracks.
-
-This is typically done by making the fix on the oldest and still
-relevant maintenance track, and merging it upwards to newer and
-newer maintenance tracks.
-
-For example, v2.24.1 was released to address a couple of
-[CVEs](https://cve.mitre.org/), and at the same time v2.14.6,
-v2.15.4, v2.16.6, v2.17.3, v2.18.2, v2.19.3, v2.20.2, v2.21.1,
-v2.22.2 and v2.23.1 were released.
+Git for Windows is a "friendly fork" of [Git](https://git-scm.com/), i.e. changes in Git for Windows are frequently contributed back, and Git for Windows' release cycle closely following Git's.
+
+While Git maintains several release trains (when v2.19.1 was released, there were updates to v2.14.x-v2.18.x, too, for example), Git for Windows follows only the latest Git release. For example, there is no Git for Windows release corresponding to Git v2.16.5 (which was released after v2.19.0).
+
+One exception is [MinGit for Windows](https://github.com/git-for-windows/git/wiki/MinGit) (a minimal subset of Git for Windows, intended for bundling with third-party applications that do not need any interactive commands nor support for `git svn`): critical security fixes are backported to the v2.11.x, v2.14.x, v2.19.x, v2.21.x and v2.23.x release trains.
+
+## Version number scheme
+
+The Git for Windows versions reflect the Git version on which they are based. For example, Git for Windows v2.21.0 is based on Git v2.21.0.
+
+As Git for Windows bundles more than just Git (such as Bash, OpenSSL, OpenSSH, GNU Privacy Guard), sometimes there are interim releases without corresponding Git releases. In these cases, Git for Windows appends a number in parentheses, starting with the number 2, then 3, etc. For example, both Git for Windows v2.17.1 and v2.17.1(2) were based on Git v2.17.1, but the latter included updates for Git Credential Manager and Git LFS, fixing critical regressions.
+
+## Tag naming scheme
+
+Every Git for Windows version is tagged using a name that starts with the Git version on which it is based, with the suffix `.windows.<patchlevel>` appended. For example, Git for Windows v2.17.1' source code is tagged as [`v2.17.1.windows.1`](https://github.com/git-for-windows/git/releases/tag/v2.17.1.windows.1) (the patch level is always at least 1, given that Git for Windows always has patches on top of Git). Likewise, Git for Windows v2.17.1(2)' source code is tagged as [`v2.17.1.windows.2`](https://github.com/git-for-windows/git/releases/tag/v2.17.1.windows.2).
+
+## Release Candidate (rc) versions
+
+As a friendly fork of Git (the "upstream" project), Git for Windows is closely corelated to that project.
+
+Consequently, Git for Windows publishes versions based on Git's release candidates (for upcoming "`.0`" versions, see [Git's release schedule](https://tinyurl.com/gitCal)). These versions end in `-rc<n>`, starting with `-rc0` for a very early preview of what is to come, and as with regular versions, Git for Windows tries to follow Git's releases as quickly as possible.
+
+Note: there is currently a bug in the "Check daily for updates" code, where it mistakes the final version as a downgrade from release candidates. Example: if you installed Git for Windows v2.23.0-rc3 and enabled the auto-updater, it would ask you whether you want to "downgrade" to v2.23.0 when that version was available.
+
+[All releases](https://github.com/git-for-windows/git/releases/), including release candidates, are listed via a link at the footer of the [Git for Windows](https://gitforwindows.org/) home page.
+
+## Snapshot versions ('nightly builds')
+
+Git for Windows also provides snapshots (these are not releases) of the the current development as per git-for-Windows/git's `master` branch at the [Snapshots](https://wingit.blob.core.windows.net/files/index.html) page. This link is also listed in the footer of the [Git for Windows](https://gitforwindows.org/) home page.
+
+Note: even if those builds are not exactly "nightly", they are sometimes referred to as "nightly builds" to keep with other projects' nomenclature.
+
+## Following upstream's developments
+
+The [gitforwindows/git repository](https://github.com/git-for-windows/git) also provides the `shears/*` branches. The `shears/*` branches reflect Git for Windows' patches, rebased onto the upstream integration branches, [updated (mostly) via automated CI builds](https://dev.azure.com/git-for-windows/git/_build?definitionId=25).

From fd0f8d83f8a312733a2ef1d8523d04de4763faa4 Mon Sep 17 00:00:00 2001
From: Philip Oakley <philipoakley@iee.email>
Date: Mon, 21 Feb 2022 22:50:33 +0000
Subject: [PATCH 256/265] rebase: help user when dying with preserve-merges`

Git will die if a "rebase --preserve-merges" is in progress.
Users cannot --quit, --abort or --continue the rebase.

This sceario can occur if the user updates their Git, or switches
to another newer version, after starting a preserve-merges rebase,
commonly via the pull setting.

One trigger is an unexpectedly difficult to resolve conflict, as
reported on the `git-users` group.
(https://groups.google.com/g/git-for-windows/c/3jMWbBlXXHM)

Tell the user the cause, i.e. the existence of the directory.
The problem must be resolved manually, `git rebase --<option>`
commands will die, or the user must downgrade. Also, note that
the deleted options are no longer shown in the documentation.

Signed-off-by: Philip Oakley <philipoakley@iee.email>
---
 builtin/rebase.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 27fde7bf2815d3..9d66785399ed31 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1183,7 +1183,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		strbuf_reset(&buf);
 		strbuf_addf(&buf, "%s/rewritten", merge_dir());
 		if (is_directory(buf.buf)) {
-			die("`rebase -p` is no longer supported");
+			die("`rebase --preserve-merges` (-p) is no longer supported.\n"
+			"You still have a `.git/rebase-merge/rewritten` directory, \n"
+			"indicating a `rebase preserve-merge` is still in progress.\n");
 		} else {
 			strbuf_reset(&buf);
 			strbuf_addf(&buf, "%s/interactive", merge_dir());

From 6edc4b227ecb14d3f3cf6b33c57af0b312f806f1 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 30 Mar 2022 11:58:37 +0200
Subject: [PATCH 257/265] Allow debugging unsafe directories' ownership

When Git refuses to use an existing repository because it is owned by
someone else than the current user, it can be a bit tricky on Windows to
figure out what is going on.

Let's help with that by offering some more information via the
environment variable `GIT_TEST_DEBUG_UNSAFE_DIRECTORIES`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/config/safe.txt |  6 ++++++
 compat/mingw.c                | 21 +++++++++++++++++++++
 2 files changed, 27 insertions(+)

diff --git a/Documentation/config/safe.txt b/Documentation/config/safe.txt
index 63597b2df8f80f..9e57855f27c799 100644
--- a/Documentation/config/safe.txt
+++ b/Documentation/config/safe.txt
@@ -19,3 +19,9 @@ line option `-c safe.directory=<path>`.
 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.
++
+Due to the permission model on Windows where ACLs are used instead of
+Unix' simpler permission model, it can be a bit tricky to figure out why
+a directory is considered unsafe. To help with this, Git will provide
+more detailed information when the environment variable
+`GIT_TEST_DEBUG_UNSAFE_DIRECTORIES` is set to `true`.
diff --git a/compat/mingw.c b/compat/mingw.c
index b10355a7efbb15..93f4f599b2b715 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1,6 +1,7 @@
 #include "../git-compat-util.h"
 #include "win32.h"
 #include <aclapi.h>
+#include <sddl.h>
 #include <conio.h>
 #include <wchar.h>
 #include <winioctl.h>
@@ -3521,6 +3522,26 @@ int is_path_owned_by_current_sid(const char *path)
 		    IsValidSid(current_user_sid) &&
 		    EqualSid(sid, current_user_sid))
 			result = 1;
+		else if (git_env_bool("GIT_TEST_DEBUG_UNSAFE_DIRECTORIES", 0)) {
+			LPSTR str1, str2, to_free1 = NULL, to_free2 = NULL;
+
+			if (ConvertSidToStringSidA(sid, &str1))
+				to_free1 = str1;
+			else
+				str1 = "(inconvertible)";
+
+			if (!current_user_sid)
+				str2 = "(none)";
+			else if (!IsValidSid(current_user_sid))
+				str2 = "(invalid)";
+			else if (ConvertSidToStringSidA(current_user_sid, &str2))
+				to_free2 = str2;
+			else
+				str2 = "(inconvertible)";
+			warning("'%s' is owned by:\n\t'%s'\nbut the current user is:\n\t'%s'", path, str1, str2);
+			LocalFree(to_free1);
+			LocalFree(to_free2);
+		}
 	}
 
 	/*

From 69e6276ccb18ab7e1ec07472c39a6fbba0736108 Mon Sep 17 00:00:00 2001
From: Philip Oakley <philipoakley@iee.email>
Date: Mon, 21 Feb 2022 22:56:10 +0000
Subject: [PATCH 258/265] rebase: `preserve` is also a pull option, tell dying
 users

The `--preserve-merges` option was removed by v2.35.0. However
users may not be aware that it is also a Pull option, and it is
still offered by major IDE vendors such as Visual Studio.

Extend the `--preserve-merges` die message to direct users to
this option and it's locations.

Signed-off-by: Philip Oakley <philipoakley@iee.email>
---
 builtin/rebase.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 9d66785399ed31..3d49ba9ddd13da 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1207,7 +1207,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			     builtin_rebase_usage, 0);
 
 	if (preserve_merges_selected)
-		die(_("--preserve-merges was replaced by --rebase-merges"));
+		die(_("--preserve-merges was replaced by --rebase-merges\n"
+			"Also, check your `pull` configuration settings\n"
+			"`git config --show-scope --show-origin --get-regexp 'pull.*'`\n"
+			"which may also invoke this option."));
 
 	if (action != ACTION_NONE && total_argc != 2) {
 		usage_with_options(builtin_rebase_usage,

From 698ee939a4294678ffd70b4043703aff6b9b6d75 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 30 Mar 2022 15:31:52 +0200
Subject: [PATCH 259/265] mingw: handle a file owned by the Administrators
 group correctly

When an Administrator creates a file or directory, the created
file/directory is owned not by the Administrator SID, but by the
_Administrators Group_ SID. The reason is that users with administrator
privileges usually run in unprivileged ("non-elevated") mode, and their
user SID does not change when running in elevated mode.

This is is relevant e.g. when running a GitHub workflow on a build
agent, which runs in elevated mode: cloning a Git repository in a script
step will cause the worktree to be owned by the Administrators Group
SID, for example.

Let's handle this case as following: if the current user is an
administrator, Git should consider a worktree owned by the
Administrators Group as if it were owned by said user.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 compat/mingw.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/compat/mingw.c b/compat/mingw.c
index 93f4f599b2b715..ea34ec5f6d2490 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -3514,6 +3514,7 @@ int is_path_owned_by_current_sid(const char *path)
 	else if (sid && IsValidSid(sid)) {
 		/* Now, verify that the SID matches the current user's */
 		static PSID current_user_sid;
+		BOOL is_member;
 
 		if (!current_user_sid)
 			current_user_sid = get_current_user_sid();
@@ -3522,6 +3523,15 @@ int is_path_owned_by_current_sid(const char *path)
 		    IsValidSid(current_user_sid) &&
 		    EqualSid(sid, current_user_sid))
 			result = 1;
+		else if (IsWellKnownSid(sid, WinBuiltinAdministratorsSid) &&
+			 CheckTokenMembership(NULL, sid, &is_member) &&
+			 is_member)
+			/*
+			 * If owned by the Administrators group, and the
+			 * current user is an administrator, we consider that
+			 * okay, too.
+			 */
+			result = 1;
 		else if (git_env_bool("GIT_TEST_DEBUG_UNSAFE_DIRECTORIES", 0)) {
 			LPSTR str1, str2, to_free1 = NULL, to_free2 = NULL;
 

From 35e1b47156a04958104e66dcc4b2bebddeb812c5 Mon Sep 17 00:00:00 2001
From: Derrick Stolee <derrickstolee@github.com>
Date: Wed, 13 Apr 2022 11:08:28 -0400
Subject: [PATCH 260/265] t0033: add tests for safe.directory

It is difficult to change the ownership on a directory in our test
suite, so insert a new GIT_TEST_ASSUME_DIFFERENT_OWNER environment
variable to trick Git into thinking we are in a differently-owned
directory. This allows us to test that the config is parsed correctly.

Signed-off-by: Derrick Stolee <derrickstolee@github.com>
---
 setup.c                   |  3 ++-
 t/t0033-safe-directory.sh | 34 ++++++++++++++++++++++++++++++++++
 2 files changed, 36 insertions(+), 1 deletion(-)
 create mode 100755 t/t0033-safe-directory.sh

diff --git a/setup.c b/setup.c
index c8f67bfed5206f..f54f449008a09f 100644
--- a/setup.c
+++ b/setup.c
@@ -1119,7 +1119,8 @@ static int ensure_valid_ownership(const char *path)
 {
 	struct safe_directory_data data = { .path = path };
 
-	if (is_path_owned_by_current_user(path))
+	if (is_path_owned_by_current_user(path) &&
+	    !git_env_bool("GIT_TEST_ASSUME_DIFFERENT_OWNER", 0))
 		return 1;
 
 	read_very_early_config(safe_directory_cb, &data);
diff --git a/t/t0033-safe-directory.sh b/t/t0033-safe-directory.sh
new file mode 100755
index 00000000000000..9380ff3d017096
--- /dev/null
+++ b/t/t0033-safe-directory.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+test_description='verify safe.directory checks'
+
+. ./test-lib.sh
+
+GIT_TEST_ASSUME_DIFFERENT_OWNER=1
+export GIT_TEST_ASSUME_DIFFERENT_OWNER
+
+expect_rejected_dir () {
+	test_must_fail git status 2>err &&
+	grep "safe.directory" err
+}
+
+test_expect_success 'safe.directory is not set' '
+	expect_rejected_dir
+'
+
+test_expect_success 'safe.directory does not match' '
+	git config --global safe.directory bogus &&
+	expect_rejected_dir
+'
+
+test_expect_success 'safe.directory matches' '
+	git config --global --add safe.directory "$(pwd)" &&
+	git status
+'
+
+test_expect_success 'safe.directory matches, but is reset' '
+	git config --global --add safe.directory "" &&
+	expect_rejected_dir
+'
+
+test_done

From 76e46fa585f63cf89522a776a5b428bc7b00ef4c Mon Sep 17 00:00:00 2001
From: Matheus Valadares <me@m28.io>
Date: Tue, 12 Apr 2022 19:39:33 -0700
Subject: [PATCH 261/265] setup: fix safe.directory key not being checked

It seems that nothing is ever checking to make sure the safe directories
in the configs actually have the key safe.directory, so some unrelated
config that has a value with a certain directory would also make it a
safe directory.

Signed-off-by: Matheus Valadares <me@m28.io>
Signed-off-by: Derrick Stolee <derrickstolee@github.com>
---
 setup.c                   | 3 +++
 t/t0033-safe-directory.sh | 5 +++++
 2 files changed, 8 insertions(+)

diff --git a/setup.c b/setup.c
index f54f449008a09f..a995c359c3223f 100644
--- a/setup.c
+++ b/setup.c
@@ -1100,6 +1100,9 @@ static int safe_directory_cb(const char *key, const char *value, void *d)
 {
 	struct safe_directory_data *data = d;
 
+	if (strcmp(key, "safe.directory"))
+		return 0;
+
 	if (!value || !*value)
 		data->is_safe = 0;
 	else {
diff --git a/t/t0033-safe-directory.sh b/t/t0033-safe-directory.sh
index 9380ff3d017096..6f33c0dfefaaf3 100755
--- a/t/t0033-safe-directory.sh
+++ b/t/t0033-safe-directory.sh
@@ -21,6 +21,11 @@ test_expect_success 'safe.directory does not match' '
 	expect_rejected_dir
 '
 
+test_expect_success 'path exist as different key' '
+	git config --global foo.bar "$(pwd)" &&
+	expect_rejected_dir
+'
+
 test_expect_success 'safe.directory matches' '
 	git config --global --add safe.directory "$(pwd)" &&
 	git status

From 6d9766becbaf97d53e2b3fb39ae718f4f0d7d590 Mon Sep 17 00:00:00 2001
From: Derrick Stolee <derrickstolee@github.com>
Date: Wed, 13 Apr 2022 10:54:32 -0400
Subject: [PATCH 262/265] setup: opt-out of check with safe.directory=*

With the addition of the safe.directory in 8959555ce
(setup_git_directory(): add an owner check for the top-level directory,
2022-03-02) released in v2.35.2, we are receiving feedback from a
variety of users about the feature.

Some users have a very large list of shared repositories and find it
cumbersome to add this config for every one of them.

In a more difficult case, certain workflows involve running Git commands
within containers. The container boundary prevents any global or system
config from communicating `safe.directory` values from the host into the
container. Further, the container almost always runs as a different user
than the owner of the directory in the host.

To simplify the reactions necessary for these users, extend the
definition of the safe.directory config value to include a possible '*'
value. This value implies that all directories are safe, providing a
single setting to opt-out of this protection.

Note that an empty assignment of safe.directory clears all previous
values, and this is already the case with the "if (!value || !*value)"
condition.

Signed-off-by: Derrick Stolee <derrickstolee@github.com>
---
 Documentation/config/safe.txt |  7 +++++++
 setup.c                       |  6 ++++--
 t/t0033-safe-directory.sh     | 10 ++++++++++
 3 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/Documentation/config/safe.txt b/Documentation/config/safe.txt
index 9e57855f27c799..57ef566a3f735f 100644
--- a/Documentation/config/safe.txt
+++ b/Documentation/config/safe.txt
@@ -25,3 +25,10 @@ Unix' simpler permission model, it can be a bit tricky to figure out why
 a directory is considered unsafe. To help with this, Git will provide
 more detailed information when the environment variable
 `GIT_TEST_DEBUG_UNSAFE_DIRECTORIES` is set to `true`.
++
+To completely opt-out of this security check, set `safe.directory` to the
+string `*`. This will allow all repositories to be treated as if their
+directory was listed in the `safe.directory` list. If `safe.directory=*`
+is set in system config and you want to re-enable this protection, then
+initialize your list with an empty value before listing the repositories
+that you deem safe.
diff --git a/setup.c b/setup.c
index a995c359c3223f..a42b21307f7708 100644
--- a/setup.c
+++ b/setup.c
@@ -1103,9 +1103,11 @@ static int safe_directory_cb(const char *key, const char *value, void *d)
 	if (strcmp(key, "safe.directory"))
 		return 0;
 
-	if (!value || !*value)
+	if (!value || !*value) {
 		data->is_safe = 0;
-	else {
+	} else if (!strcmp(value, "*")) {
+		data->is_safe = 1;
+	} else {
 		const char *interpolated = NULL;
 
 		if (!git_config_pathname(&interpolated, key, value) &&
diff --git a/t/t0033-safe-directory.sh b/t/t0033-safe-directory.sh
index 6f33c0dfefaaf3..239d93f4d21141 100755
--- a/t/t0033-safe-directory.sh
+++ b/t/t0033-safe-directory.sh
@@ -36,4 +36,14 @@ test_expect_success 'safe.directory matches, but is reset' '
 	expect_rejected_dir
 '
 
+test_expect_success 'safe.directory=*' '
+	git config --global --add safe.directory "*" &&
+	git status
+'
+
+test_expect_success 'safe.directory=*, but is reset' '
+	git config --global --add safe.directory "" &&
+	expect_rejected_dir
+'
+
 test_done

From fde93f36e145d6c54061f56832fa15d55618fc04 Mon Sep 17 00:00:00 2001
From: Derrick Stolee <derrickstolee@github.com>
Date: Wed, 13 Apr 2022 14:49:17 -0400
Subject: [PATCH 263/265] setup: properly use "%(prefix)/" when in WSL

Signed-off-by: Derrick Stolee <derrickstolee@github.com>
---
 setup.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/setup.c b/setup.c
index a42b21307f7708..3f66dd5173ae03 100644
--- a/setup.c
+++ b/setup.c
@@ -1374,9 +1374,17 @@ const char *setup_git_directory_gently(int *nongit_ok)
 		break;
 	case GIT_DIR_INVALID_OWNERSHIP:
 		if (!nongit_ok) {
+			struct strbuf prequoted = STRBUF_INIT;
 			struct strbuf quoted = STRBUF_INIT;
 
-			sq_quote_buf_pretty(&quoted, dir.buf);
+#ifdef __MINGW32__
+			if (dir.buf[0] == '/')
+				strbuf_addstr(&prequoted, "%(prefix)/");
+#endif
+
+			strbuf_add(&prequoted, dir.buf, dir.len);
+			sq_quote_buf_pretty(&quoted, prequoted.buf);
+
 			die(_("unsafe repository ('%s' is owned by someone else)\n"
 			      "To add an exception for this directory, call:\n"
 			      "\n"

From 3ef2e3f8350209e21834ac521378f0571876c0ed Mon Sep 17 00:00:00 2001
From: Michael Osthege <michael.osthege@outlook.com>
Date: Wed, 13 Apr 2022 16:39:41 +0200
Subject: [PATCH 264/265] Include trailing slash in prefix instructions

Closes https://github.com/git-for-windows/git/issues/3786

Signed-off-by: Michael Osthege <michael.osthege@outlook.com>
---
 path.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/path.c b/path.c
index 47254b341620b7..78b9b189df65fa 100644
--- a/path.c
+++ b/path.c
@@ -741,7 +741,7 @@ char *interpolate_path(const char *path, int real_home)
 
 #ifdef __MINGW32__
 	if (path[0] == '/') {
-		warning(_("encountered old-style '%s' that should be '%%(prefix)%s'"), path, path);
+		warning(_("encountered old-style '%s' that should be '%%(prefix)/%s'"), path, path);
 		return system_path(path + 1);
 	}
 #endif

From 0ba14f3f68f27b24faef459382eaf1cee9e04f6d Mon Sep 17 00:00:00 2001
From: Derrick Stolee <derrickstolee@github.com>
Date: Wed, 13 Apr 2022 14:54:43 -0400
Subject: [PATCH 265/265] compat/mingw.c: do not warn when failing to get owner

In the case of Git for Windows (say, in a Git Bash window) running in a
Windows Subsystem for Linux (WSL) directory, the GetNamedSecurityInfoW()
call in is_path_owned_By_current_side() returns an error code other than
ERROR_SUCCESS. This is consistent behavior across this boundary.

In these cases, the owner would always be different because the WSL
owner is a different entity than the Windows user.

The change here is to suppress the error message that looks like this:

  error: failed to get owner for '//wsl.localhost/...' (1)

Before this change, this warning happens for every Git command,
regardless of whether the directory is marked with safe.directory.

Signed-off-by: Derrick Stolee <derrickstolee@github.com>
---
 compat/mingw.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index ea34ec5f6d2490..f3acdc57ad1538 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -3509,9 +3509,7 @@ int is_path_owned_by_current_sid(const char *path)
 				    DACL_SECURITY_INFORMATION,
 				    &sid, NULL, NULL, NULL, &descriptor);
 
-	if (err != ERROR_SUCCESS)
-		error(_("failed to get owner for '%s' (%ld)"), path, err);
-	else if (sid && IsValidSid(sid)) {
+	if (err == ERROR_SUCCESS && sid && IsValidSid(sid)) {
 		/* Now, verify that the SID matches the current user's */
 		static PSID current_user_sid;
 		BOOL is_member;
