Merge branch 'jc/safe-directory-leading-path' into maint-2.45

The safe.directory configuration knob has been updated to
optionally allow leading path matches.

* jc/safe-directory-leading-path:
  safe.directory: allow "lead/ing/path/*" match
diff --git a/Documentation/config/safe.txt b/Documentation/config/safe.txt
index 577df40..2d45c98 100644
--- a/Documentation/config/safe.txt
+++ b/Documentation/config/safe.txt
@@ -44,7 +44,8 @@
 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.
+that you deem safe.  Giving a directory with `/*` appended to it will
+allow access to all repositories under the named directory.
 +
 As explained, Git only allows you to access repositories owned by
 yourself, i.e. the user who is running Git, by default.  When Git
diff --git a/setup.c b/setup.c
index 15abbaf..8a52421 100644
--- a/setup.c
+++ b/setup.c
@@ -1177,13 +1177,21 @@ static int safe_directory_cb(const char *key, const char *value,
 	} else if (!strcmp(value, "*")) {
 		data->is_safe = 1;
 	} else {
-		const char *interpolated = NULL;
+		const char *allowed = NULL;
 
-		if (!git_config_pathname(&interpolated, key, value) &&
-		    !fspathcmp(data->path, interpolated ? interpolated : value))
-			data->is_safe = 1;
-
-		free((char *)interpolated);
+		if (!git_config_pathname(&allowed, key, value)) {
+			if (!allowed)
+				allowed = value;
+			if (ends_with(allowed, "/*")) {
+				size_t len = strlen(allowed);
+				if (!fspathncmp(allowed, data->path, len - 1))
+					data->is_safe = 1;
+			} else if (!fspathcmp(data->path, allowed)) {
+				data->is_safe = 1;
+			}
+		}
+		if (allowed != value)
+			free((char *)allowed);
 	}
 
 	return 0;
diff --git a/t/t0033-safe-directory.sh b/t/t0033-safe-directory.sh
index 11c3e8f..5fe61f1 100755
--- a/t/t0033-safe-directory.sh
+++ b/t/t0033-safe-directory.sh
@@ -71,7 +71,22 @@
 	expect_rejected_dir
 '
 
+test_expect_success 'safe.directory with matching glob' '
+	git config --global --unset-all safe.directory &&
+	p=$(pwd) &&
+	git config --global safe.directory "${p%/*}/*" &&
+	git status
+'
+
+test_expect_success 'safe.directory with unmatching glob' '
+	git config --global --unset-all safe.directory &&
+	p=$(pwd) &&
+	git config --global safe.directory "${p%/*}no/*" &&
+	expect_rejected_dir
+'
+
 test_expect_success 'safe.directory in included file' '
+	git config --global --unset-all safe.directory &&
 	cat >gitconfig-include <<-EOF &&
 	[safe]
 		directory = "$(pwd)"