diff --git a/Documentation/config/credential.txt b/Documentation/config/credential.txt
index 0221c3e620da89..470482ff4c2a38 100644
--- a/Documentation/config/credential.txt
+++ b/Documentation/config/credential.txt
@@ -9,6 +9,14 @@ credential.helper::
Note that multiple helpers may be defined. See linkgit:gitcredentials[7]
for details and examples.
+credential.interactive::
+ By default, Git and any configured credential helpers will ask for
+ user input when new credentials are required. Many of these helpers
+ will succeed based on stored credentials if those credentials are
+ still valid. To avoid the possibility of user interactivity from
+ Git, set `credential.interactive=false`. Some credential helpers
+ respect this option as well.
+
credential.useHttpPath::
When acquiring credentials, consider the "path" component of an http
or https URL to be important. Defaults to false. See
diff --git a/builtin/gc.c b/builtin/gc.c
index 7dac97140549b4..fb1be542e06d75 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -1766,6 +1766,42 @@ static const char *get_frequency(enum schedule_priority schedule)
}
}
+static const char *extraconfig[] = {
+ "credential.interactive=false",
+ "core.askPass=true", /* 'true' returns success, but no output. */
+ NULL
+};
+
+static const char *get_extra_config_parameters(void) {
+ static const char *result = NULL;
+ struct strbuf builder = STRBUF_INIT;
+
+ if (result)
+ return result;
+
+ for (const char **s = extraconfig; s && *s; s++)
+ strbuf_addf(&builder, "-c %s ", *s);
+
+ result = strbuf_detach(&builder, NULL);
+ return result;
+}
+
+static const char *get_extra_launchctl_strings(void) {
+ static const char *result = NULL;
+ struct strbuf builder = STRBUF_INIT;
+
+ if (result)
+ return result;
+
+ for (const char **s = extraconfig; s && *s; s++) {
+ strbuf_addstr(&builder, "-c\n");
+ strbuf_addf(&builder, "%s\n", *s);
+ }
+
+ result = strbuf_detach(&builder, NULL);
+ return result;
+}
+
/*
* get_schedule_cmd` reads the GIT_TEST_MAINT_SCHEDULER environment variable
* to mock the schedulers that `git maintenance start` rely on.
@@ -1972,6 +2008,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
"\n"
"%s/git\n"
"--exec-path=%s\n"
+ "%s" /* For extra config parameters. */
"for-each-repo\n"
"--keep-going\n"
"--config=maintenance.repo\n"
@@ -1981,7 +2018,8 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
"\n"
"StartCalendarInterval\n"
"\n";
- strbuf_addf(&plist, preamble, name, exec_path, exec_path, frequency);
+ strbuf_addf(&plist, preamble, name, exec_path, exec_path,
+ get_extra_launchctl_strings(), frequency);
switch (schedule) {
case SCHEDULE_HOURLY:
@@ -2216,11 +2254,12 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
"\n"
"\n"
"\"%s\\headless-git.exe\"\n"
- "--exec-path=\"%s\" for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%s\n"
+ "--exec-path=\"%s\" %s for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%s\n"
"\n"
"\n"
"\n";
- fprintf(tfile->fp, xml, exec_path, exec_path, frequency);
+ fprintf(tfile->fp, xml, exec_path, exec_path,
+ get_extra_config_parameters(), frequency);
strvec_split(&child.args, cmd);
strvec_pushl(&child.args, "/create", "/tn", name, "/f", "/xml",
get_tempfile_path(tfile), NULL);
@@ -2361,8 +2400,8 @@ static int crontab_update_schedule(int run_maintenance, int fd)
"# replaced in the future by a Git command.\n\n");
strbuf_addf(&line_format,
- "%%d %%s * * %%s \"%s/git\" --exec-path=\"%s\" for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%%s\n",
- exec_path, exec_path);
+ "%%d %%s * * %%s \"%s/git\" --exec-path=\"%s\" %s for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%%s\n",
+ exec_path, exec_path, get_extra_config_parameters());
fprintf(cron_in, line_format.buf, minute, "1-23", "*", "hourly");
fprintf(cron_in, line_format.buf, minute, "0", "1-6", "daily");
fprintf(cron_in, line_format.buf, minute, "0", "0", "weekly");
@@ -2562,7 +2601,7 @@ static int systemd_timer_write_service_template(const char *exec_path)
"\n"
"[Service]\n"
"Type=oneshot\n"
- "ExecStart=\"%s/git\" --exec-path=\"%s\" for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%%i\n"
+ "ExecStart=\"%s/git\" --exec-path=\"%s\" %s for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%%i\n"
"LockPersonality=yes\n"
"MemoryDenyWriteExecute=yes\n"
"NoNewPrivileges=yes\n"
@@ -2572,7 +2611,7 @@ static int systemd_timer_write_service_template(const char *exec_path)
"RestrictSUIDSGID=yes\n"
"SystemCallArchitectures=native\n"
"SystemCallFilter=@system-service\n";
- if (fprintf(file, unit, exec_path, exec_path) < 0) {
+ if (fprintf(file, unit, exec_path, exec_path, get_extra_config_parameters()) < 0) {
error(_("failed to write to '%s'"), filename);
fclose(file);
goto error;
diff --git a/credential.c b/credential.c
index ee46351ce01510..6dea3859ece338 100644
--- a/credential.c
+++ b/credential.c
@@ -13,6 +13,8 @@
#include "strbuf.h"
#include "urlmatch.h"
#include "git-compat-util.h"
+#include "trace2.h"
+#include "repository.h"
void credential_init(struct credential *c)
{
@@ -251,14 +253,36 @@ static char *credential_ask_one(const char *what, struct credential *c,
return xstrdup(r);
}
-static void credential_getpass(struct credential *c)
+static int credential_getpass(struct credential *c)
{
+ int interactive;
+ char *value;
+ if (!git_config_get_maybe_bool("credential.interactive", &interactive) &&
+ !interactive) {
+ trace2_data_intmax("credential", the_repository,
+ "interactive/skipped", 1);
+ return -1;
+ }
+ if (!git_config_get_string("credential.interactive", &value)) {
+ int same = !strcmp(value, "never");
+ free(value);
+ if (same) {
+ trace2_data_intmax("credential", the_repository,
+ "interactive/skipped", 1);
+ return -1;
+ }
+ }
+
+ trace2_region_enter("credential", "interactive", the_repository);
if (!c->username)
c->username = credential_ask_one("Username", c,
PROMPT_ASKPASS|PROMPT_ECHO);
if (!c->password)
c->password = credential_ask_one("Password", c,
PROMPT_ASKPASS);
+ trace2_region_leave("credential", "interactive", the_repository);
+
+ return 0;
}
int credential_has_capability(const struct credential_capability *capa,
@@ -501,8 +525,8 @@ void credential_fill(struct credential *c, int all_capabilities)
c->helpers.items[i].string);
}
- credential_getpass(c);
- if (!c->username && !c->password && !c->credential)
+ if (credential_getpass(c) ||
+ (!c->username && !c->password && !c->credential))
die("unable to get password from user");
}
diff --git a/scalar.c b/scalar.c
index 09560aeab5418b..73b79a5d4c9979 100644
--- a/scalar.c
+++ b/scalar.c
@@ -733,6 +733,9 @@ static int cmd_reconfigure(int argc, const char **argv)
the_repository = old_repo;
+ if (toggle_maintenance(1) >= 0)
+ succeeded = 1;
+
loop_end:
if (!succeeded) {
res = -1;
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
index 7b5ab0eae16012..ceb3336a5c494f 100755
--- a/t/t5551-http-fetch-smart.sh
+++ b/t/t5551-http-fetch-smart.sh
@@ -186,6 +186,28 @@ test_expect_success 'clone from password-protected repository' '
test_cmp expect actual
'
+test_expect_success 'credential.interactive=false skips askpass' '
+ set_askpass bogus nonsense &&
+ (
+ GIT_TRACE2_EVENT="$(pwd)/interactive-true" &&
+ export GIT_TRACE2_EVENT &&
+ test_must_fail git clone --bare "$HTTPD_URL/auth/smart/repo.git" interactive-true-dir &&
+ test_region credential interactive interactive-true &&
+
+ GIT_TRACE2_EVENT="$(pwd)/interactive-false" &&
+ export GIT_TRACE2_EVENT &&
+ test_must_fail git -c credential.interactive=false \
+ clone --bare "$HTTPD_URL/auth/smart/repo.git" interactive-false-dir &&
+ test_region ! credential interactive interactive-false &&
+
+ GIT_TRACE2_EVENT="$(pwd)/interactive-never" &&
+ export GIT_TRACE2_EVENT &&
+ test_must_fail git -c credential.interactive=never \
+ clone --bare "$HTTPD_URL/auth/smart/repo.git" interactive-never-dir &&
+ test_region ! credential interactive interactive-never
+ )
+'
+
test_expect_success 'clone from auth-only-for-push repository' '
echo two >expect &&
set_askpass wrong &&
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index abae7a97546f66..3cd7e1fcacb78a 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -825,6 +825,9 @@ test_expect_success 'start and stop Linux/systemd maintenance' '
test_systemd_analyze_verify "systemd/user/git-maintenance@daily.service" &&
test_systemd_analyze_verify "systemd/user/git-maintenance@weekly.service" &&
+ grep "core.askPass=true" "systemd/user/git-maintenance@.service" &&
+ grep "credential.interactive=false" "systemd/user/git-maintenance@.service" &&
+
printf -- "--user enable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
test_cmp expect args &&
diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh
index e8613990e13705..027235d61aa35e 100755
--- a/t/t9210-scalar.sh
+++ b/t/t9210-scalar.sh
@@ -194,8 +194,11 @@ test_expect_success 'scalar reconfigure' '
scalar reconfigure one &&
test true = "$(git -C one/src config core.preloadIndex)" &&
git -C one/src config core.preloadIndex false &&
- scalar reconfigure -a &&
- test true = "$(git -C one/src config core.preloadIndex)"
+ rm one/src/cron.txt &&
+ GIT_TRACE2_EVENT="$(pwd)/reconfigure" scalar reconfigure -a &&
+ test_path_is_file one/src/cron.txt &&
+ test true = "$(git -C one/src config core.preloadIndex)" &&
+ test_subcommand git maintenance start