mirror of
https://github.com/git/git.git
synced 2024-10-28 04:49:43 +01:00
Merge branch 'ds/background-maintenance-with-credential'
Background tasks "git maintenance" runs may need to use credential information when going over the network, but a credential helper may work only in an interactive environment, and end up blocking a scheduled task waiting for UI. Credential helpers can now behave differently when they are not running interactively. * ds/background-maintenance-with-credential: scalar: configure maintenance during 'reconfigure' maintenance: add custom config to background jobs credential: add new interactive config option
This commit is contained in:
commit
4251403327
7 changed files with 114 additions and 12 deletions
|
@ -9,6 +9,14 @@ credential.helper::
|
||||||
Note that multiple helpers may be defined. See linkgit:gitcredentials[7]
|
Note that multiple helpers may be defined. See linkgit:gitcredentials[7]
|
||||||
for details and examples.
|
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::
|
credential.useHttpPath::
|
||||||
When acquiring credentials, consider the "path" component of an http
|
When acquiring credentials, consider the "path" component of an http
|
||||||
or https URL to be important. Defaults to false. See
|
or https URL to be important. Defaults to false. See
|
||||||
|
|
53
builtin/gc.c
53
builtin/gc.c
|
@ -1768,6 +1768,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, "<string>-c</string>\n");
|
||||||
|
strbuf_addf(&builder, "<string>%s</string>\n", *s);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = strbuf_detach(&builder, NULL);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get_schedule_cmd` reads the GIT_TEST_MAINT_SCHEDULER environment variable
|
* get_schedule_cmd` reads the GIT_TEST_MAINT_SCHEDULER environment variable
|
||||||
* to mock the schedulers that `git maintenance start` rely on.
|
* to mock the schedulers that `git maintenance start` rely on.
|
||||||
|
@ -1974,6 +2010,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
|
||||||
"<array>\n"
|
"<array>\n"
|
||||||
"<string>%s/git</string>\n"
|
"<string>%s/git</string>\n"
|
||||||
"<string>--exec-path=%s</string>\n"
|
"<string>--exec-path=%s</string>\n"
|
||||||
|
"%s" /* For extra config parameters. */
|
||||||
"<string>for-each-repo</string>\n"
|
"<string>for-each-repo</string>\n"
|
||||||
"<string>--keep-going</string>\n"
|
"<string>--keep-going</string>\n"
|
||||||
"<string>--config=maintenance.repo</string>\n"
|
"<string>--config=maintenance.repo</string>\n"
|
||||||
|
@ -1983,7 +2020,8 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
|
||||||
"</array>\n"
|
"</array>\n"
|
||||||
"<key>StartCalendarInterval</key>\n"
|
"<key>StartCalendarInterval</key>\n"
|
||||||
"<array>\n";
|
"<array>\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) {
|
switch (schedule) {
|
||||||
case SCHEDULE_HOURLY:
|
case SCHEDULE_HOURLY:
|
||||||
|
@ -2218,11 +2256,12 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
|
||||||
"<Actions Context=\"Author\">\n"
|
"<Actions Context=\"Author\">\n"
|
||||||
"<Exec>\n"
|
"<Exec>\n"
|
||||||
"<Command>\"%s\\headless-git.exe\"</Command>\n"
|
"<Command>\"%s\\headless-git.exe\"</Command>\n"
|
||||||
"<Arguments>--exec-path=\"%s\" for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%s</Arguments>\n"
|
"<Arguments>--exec-path=\"%s\" %s for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%s</Arguments>\n"
|
||||||
"</Exec>\n"
|
"</Exec>\n"
|
||||||
"</Actions>\n"
|
"</Actions>\n"
|
||||||
"</Task>\n";
|
"</Task>\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_split(&child.args, cmd);
|
||||||
strvec_pushl(&child.args, "/create", "/tn", name, "/f", "/xml",
|
strvec_pushl(&child.args, "/create", "/tn", name, "/f", "/xml",
|
||||||
get_tempfile_path(tfile), NULL);
|
get_tempfile_path(tfile), NULL);
|
||||||
|
@ -2363,8 +2402,8 @@ static int crontab_update_schedule(int run_maintenance, int fd)
|
||||||
"# replaced in the future by a Git command.\n\n");
|
"# replaced in the future by a Git command.\n\n");
|
||||||
|
|
||||||
strbuf_addf(&line_format,
|
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",
|
"%%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);
|
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, "1-23", "*", "hourly");
|
||||||
fprintf(cron_in, line_format.buf, minute, "0", "1-6", "daily");
|
fprintf(cron_in, line_format.buf, minute, "0", "1-6", "daily");
|
||||||
fprintf(cron_in, line_format.buf, minute, "0", "0", "weekly");
|
fprintf(cron_in, line_format.buf, minute, "0", "0", "weekly");
|
||||||
|
@ -2564,7 +2603,7 @@ static int systemd_timer_write_service_template(const char *exec_path)
|
||||||
"\n"
|
"\n"
|
||||||
"[Service]\n"
|
"[Service]\n"
|
||||||
"Type=oneshot\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"
|
"LockPersonality=yes\n"
|
||||||
"MemoryDenyWriteExecute=yes\n"
|
"MemoryDenyWriteExecute=yes\n"
|
||||||
"NoNewPrivileges=yes\n"
|
"NoNewPrivileges=yes\n"
|
||||||
|
@ -2574,7 +2613,7 @@ static int systemd_timer_write_service_template(const char *exec_path)
|
||||||
"RestrictSUIDSGID=yes\n"
|
"RestrictSUIDSGID=yes\n"
|
||||||
"SystemCallArchitectures=native\n"
|
"SystemCallArchitectures=native\n"
|
||||||
"SystemCallFilter=@system-service\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);
|
error(_("failed to write to '%s'"), filename);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
goto error;
|
goto error;
|
||||||
|
|
30
credential.c
30
credential.c
|
@ -13,6 +13,8 @@
|
||||||
#include "strbuf.h"
|
#include "strbuf.h"
|
||||||
#include "urlmatch.h"
|
#include "urlmatch.h"
|
||||||
#include "git-compat-util.h"
|
#include "git-compat-util.h"
|
||||||
|
#include "trace2.h"
|
||||||
|
#include "repository.h"
|
||||||
|
|
||||||
void credential_init(struct credential *c)
|
void credential_init(struct credential *c)
|
||||||
{
|
{
|
||||||
|
@ -251,14 +253,36 @@ static char *credential_ask_one(const char *what, struct credential *c,
|
||||||
return xstrdup(r);
|
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)
|
if (!c->username)
|
||||||
c->username = credential_ask_one("Username", c,
|
c->username = credential_ask_one("Username", c,
|
||||||
PROMPT_ASKPASS|PROMPT_ECHO);
|
PROMPT_ASKPASS|PROMPT_ECHO);
|
||||||
if (!c->password)
|
if (!c->password)
|
||||||
c->password = credential_ask_one("Password", c,
|
c->password = credential_ask_one("Password", c,
|
||||||
PROMPT_ASKPASS);
|
PROMPT_ASKPASS);
|
||||||
|
trace2_region_leave("credential", "interactive", the_repository);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int credential_has_capability(const struct credential_capability *capa,
|
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);
|
c->helpers.items[i].string);
|
||||||
}
|
}
|
||||||
|
|
||||||
credential_getpass(c);
|
if (credential_getpass(c) ||
|
||||||
if (!c->username && !c->password && !c->credential)
|
(!c->username && !c->password && !c->credential))
|
||||||
die("unable to get password from user");
|
die("unable to get password from user");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
3
scalar.c
3
scalar.c
|
@ -733,6 +733,9 @@ static int cmd_reconfigure(int argc, const char **argv)
|
||||||
|
|
||||||
the_repository = old_repo;
|
the_repository = old_repo;
|
||||||
|
|
||||||
|
if (toggle_maintenance(1) >= 0)
|
||||||
|
succeeded = 1;
|
||||||
|
|
||||||
loop_end:
|
loop_end:
|
||||||
if (!succeeded) {
|
if (!succeeded) {
|
||||||
res = -1;
|
res = -1;
|
||||||
|
|
|
@ -186,6 +186,28 @@ test_expect_success 'clone from password-protected repository' '
|
||||||
test_cmp expect actual
|
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' '
|
test_expect_success 'clone from auth-only-for-push repository' '
|
||||||
echo two >expect &&
|
echo two >expect &&
|
||||||
set_askpass wrong &&
|
set_askpass wrong &&
|
||||||
|
|
|
@ -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@daily.service" &&
|
||||||
test_systemd_analyze_verify "systemd/user/git-maintenance@weekly.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 &&
|
printf -- "--user enable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
|
||||||
test_cmp expect args &&
|
test_cmp expect args &&
|
||||||
|
|
||||||
|
|
|
@ -194,8 +194,11 @@ test_expect_success 'scalar reconfigure' '
|
||||||
scalar reconfigure one &&
|
scalar reconfigure one &&
|
||||||
test true = "$(git -C one/src config core.preloadIndex)" &&
|
test true = "$(git -C one/src config core.preloadIndex)" &&
|
||||||
git -C one/src config core.preloadIndex false &&
|
git -C one/src config core.preloadIndex false &&
|
||||||
scalar reconfigure -a &&
|
rm one/src/cron.txt &&
|
||||||
test true = "$(git -C one/src config core.preloadIndex)"
|
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 <reconfigure
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '
|
test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '
|
||||||
|
|
Loading…
Reference in a new issue