Skip to content

Commit 4251403

Browse files
committed
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
2 parents c58eee0 + b9183b0 commit 4251403

File tree

7 files changed

+114
-12
lines changed

7 files changed

+114
-12
lines changed

Documentation/config/credential.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ credential.helper::
99
Note that multiple helpers may be defined. See linkgit:gitcredentials[7]
1010
for details and examples.
1111

12+
credential.interactive::
13+
By default, Git and any configured credential helpers will ask for
14+
user input when new credentials are required. Many of these helpers
15+
will succeed based on stored credentials if those credentials are
16+
still valid. To avoid the possibility of user interactivity from
17+
Git, set `credential.interactive=false`. Some credential helpers
18+
respect this option as well.
19+
1220
credential.useHttpPath::
1321
When acquiring credentials, consider the "path" component of an http
1422
or https URL to be important. Defaults to false. See

builtin/gc.c

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,6 +1768,42 @@ static const char *get_frequency(enum schedule_priority schedule)
17681768
}
17691769
}
17701770

1771+
static const char *extraconfig[] = {
1772+
"credential.interactive=false",
1773+
"core.askPass=true", /* 'true' returns success, but no output. */
1774+
NULL
1775+
};
1776+
1777+
static const char *get_extra_config_parameters(void) {
1778+
static const char *result = NULL;
1779+
struct strbuf builder = STRBUF_INIT;
1780+
1781+
if (result)
1782+
return result;
1783+
1784+
for (const char **s = extraconfig; s && *s; s++)
1785+
strbuf_addf(&builder, "-c %s ", *s);
1786+
1787+
result = strbuf_detach(&builder, NULL);
1788+
return result;
1789+
}
1790+
1791+
static const char *get_extra_launchctl_strings(void) {
1792+
static const char *result = NULL;
1793+
struct strbuf builder = STRBUF_INIT;
1794+
1795+
if (result)
1796+
return result;
1797+
1798+
for (const char **s = extraconfig; s && *s; s++) {
1799+
strbuf_addstr(&builder, "<string>-c</string>\n");
1800+
strbuf_addf(&builder, "<string>%s</string>\n", *s);
1801+
}
1802+
1803+
result = strbuf_detach(&builder, NULL);
1804+
return result;
1805+
}
1806+
17711807
/*
17721808
* get_schedule_cmd` reads the GIT_TEST_MAINT_SCHEDULER environment variable
17731809
* 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
19742010
"<array>\n"
19752011
"<string>%s/git</string>\n"
19762012
"<string>--exec-path=%s</string>\n"
2013+
"%s" /* For extra config parameters. */
19772014
"<string>for-each-repo</string>\n"
19782015
"<string>--keep-going</string>\n"
19792016
"<string>--config=maintenance.repo</string>\n"
@@ -1983,7 +2020,8 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
19832020
"</array>\n"
19842021
"<key>StartCalendarInterval</key>\n"
19852022
"<array>\n";
1986-
strbuf_addf(&plist, preamble, name, exec_path, exec_path, frequency);
2023+
strbuf_addf(&plist, preamble, name, exec_path, exec_path,
2024+
get_extra_launchctl_strings(), frequency);
19872025

19882026
switch (schedule) {
19892027
case SCHEDULE_HOURLY:
@@ -2218,11 +2256,12 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
22182256
"<Actions Context=\"Author\">\n"
22192257
"<Exec>\n"
22202258
"<Command>\"%s\\headless-git.exe\"</Command>\n"
2221-
"<Arguments>--exec-path=\"%s\" for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%s</Arguments>\n"
2259+
"<Arguments>--exec-path=\"%s\" %s for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%s</Arguments>\n"
22222260
"</Exec>\n"
22232261
"</Actions>\n"
22242262
"</Task>\n";
2225-
fprintf(tfile->fp, xml, exec_path, exec_path, frequency);
2263+
fprintf(tfile->fp, xml, exec_path, exec_path,
2264+
get_extra_config_parameters(), frequency);
22262265
strvec_split(&child.args, cmd);
22272266
strvec_pushl(&child.args, "/create", "/tn", name, "/f", "/xml",
22282267
get_tempfile_path(tfile), NULL);
@@ -2363,8 +2402,8 @@ static int crontab_update_schedule(int run_maintenance, int fd)
23632402
"# replaced in the future by a Git command.\n\n");
23642403

23652404
strbuf_addf(&line_format,
2366-
"%%d %%s * * %%s \"%s/git\" --exec-path=\"%s\" for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%%s\n",
2367-
exec_path, exec_path);
2405+
"%%d %%s * * %%s \"%s/git\" --exec-path=\"%s\" %s for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%%s\n",
2406+
exec_path, exec_path, get_extra_config_parameters());
23682407
fprintf(cron_in, line_format.buf, minute, "1-23", "*", "hourly");
23692408
fprintf(cron_in, line_format.buf, minute, "0", "1-6", "daily");
23702409
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)
25642603
"\n"
25652604
"[Service]\n"
25662605
"Type=oneshot\n"
2567-
"ExecStart=\"%s/git\" --exec-path=\"%s\" for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%%i\n"
2606+
"ExecStart=\"%s/git\" --exec-path=\"%s\" %s for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%%i\n"
25682607
"LockPersonality=yes\n"
25692608
"MemoryDenyWriteExecute=yes\n"
25702609
"NoNewPrivileges=yes\n"
@@ -2574,7 +2613,7 @@ static int systemd_timer_write_service_template(const char *exec_path)
25742613
"RestrictSUIDSGID=yes\n"
25752614
"SystemCallArchitectures=native\n"
25762615
"SystemCallFilter=@system-service\n";
2577-
if (fprintf(file, unit, exec_path, exec_path) < 0) {
2616+
if (fprintf(file, unit, exec_path, exec_path, get_extra_config_parameters()) < 0) {
25782617
error(_("failed to write to '%s'"), filename);
25792618
fclose(file);
25802619
goto error;

credential.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include "strbuf.h"
1414
#include "urlmatch.h"
1515
#include "git-compat-util.h"
16+
#include "trace2.h"
17+
#include "repository.h"
1618

1719
void credential_init(struct credential *c)
1820
{
@@ -251,14 +253,36 @@ static char *credential_ask_one(const char *what, struct credential *c,
251253
return xstrdup(r);
252254
}
253255

254-
static void credential_getpass(struct credential *c)
256+
static int credential_getpass(struct credential *c)
255257
{
258+
int interactive;
259+
char *value;
260+
if (!git_config_get_maybe_bool("credential.interactive", &interactive) &&
261+
!interactive) {
262+
trace2_data_intmax("credential", the_repository,
263+
"interactive/skipped", 1);
264+
return -1;
265+
}
266+
if (!git_config_get_string("credential.interactive", &value)) {
267+
int same = !strcmp(value, "never");
268+
free(value);
269+
if (same) {
270+
trace2_data_intmax("credential", the_repository,
271+
"interactive/skipped", 1);
272+
return -1;
273+
}
274+
}
275+
276+
trace2_region_enter("credential", "interactive", the_repository);
256277
if (!c->username)
257278
c->username = credential_ask_one("Username", c,
258279
PROMPT_ASKPASS|PROMPT_ECHO);
259280
if (!c->password)
260281
c->password = credential_ask_one("Password", c,
261282
PROMPT_ASKPASS);
283+
trace2_region_leave("credential", "interactive", the_repository);
284+
285+
return 0;
262286
}
263287

264288
int credential_has_capability(const struct credential_capability *capa,
@@ -501,8 +525,8 @@ void credential_fill(struct credential *c, int all_capabilities)
501525
c->helpers.items[i].string);
502526
}
503527

504-
credential_getpass(c);
505-
if (!c->username && !c->password && !c->credential)
528+
if (credential_getpass(c) ||
529+
(!c->username && !c->password && !c->credential))
506530
die("unable to get password from user");
507531
}
508532

scalar.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,9 @@ static int cmd_reconfigure(int argc, const char **argv)
733733

734734
the_repository = old_repo;
735735

736+
if (toggle_maintenance(1) >= 0)
737+
succeeded = 1;
738+
736739
loop_end:
737740
if (!succeeded) {
738741
res = -1;

t/t5551-http-fetch-smart.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,28 @@ test_expect_success 'clone from password-protected repository' '
186186
test_cmp expect actual
187187
'
188188

189+
test_expect_success 'credential.interactive=false skips askpass' '
190+
set_askpass bogus nonsense &&
191+
(
192+
GIT_TRACE2_EVENT="$(pwd)/interactive-true" &&
193+
export GIT_TRACE2_EVENT &&
194+
test_must_fail git clone --bare "$HTTPD_URL/auth/smart/repo.git" interactive-true-dir &&
195+
test_region credential interactive interactive-true &&
196+
197+
GIT_TRACE2_EVENT="$(pwd)/interactive-false" &&
198+
export GIT_TRACE2_EVENT &&
199+
test_must_fail git -c credential.interactive=false \
200+
clone --bare "$HTTPD_URL/auth/smart/repo.git" interactive-false-dir &&
201+
test_region ! credential interactive interactive-false &&
202+
203+
GIT_TRACE2_EVENT="$(pwd)/interactive-never" &&
204+
export GIT_TRACE2_EVENT &&
205+
test_must_fail git -c credential.interactive=never \
206+
clone --bare "$HTTPD_URL/auth/smart/repo.git" interactive-never-dir &&
207+
test_region ! credential interactive interactive-never
208+
)
209+
'
210+
189211
test_expect_success 'clone from auth-only-for-push repository' '
190212
echo two >expect &&
191213
set_askpass wrong &&

t/t7900-maintenance.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,9 @@ test_expect_success 'start and stop Linux/systemd maintenance' '
825825
test_systemd_analyze_verify "systemd/user/[email protected]" &&
826826
test_systemd_analyze_verify "systemd/user/[email protected]" &&
827827
828+
grep "core.askPass=true" "systemd/user/[email protected]" &&
829+
grep "credential.interactive=false" "systemd/user/[email protected]" &&
830+
828831
printf -- "--user enable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
829832
test_cmp expect args &&
830833

t/t9210-scalar.sh

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,11 @@ test_expect_success 'scalar reconfigure' '
194194
scalar reconfigure one &&
195195
test true = "$(git -C one/src config core.preloadIndex)" &&
196196
git -C one/src config core.preloadIndex false &&
197-
scalar reconfigure -a &&
198-
test true = "$(git -C one/src config core.preloadIndex)"
197+
rm one/src/cron.txt &&
198+
GIT_TRACE2_EVENT="$(pwd)/reconfigure" scalar reconfigure -a &&
199+
test_path_is_file one/src/cron.txt &&
200+
test true = "$(git -C one/src config core.preloadIndex)" &&
201+
test_subcommand git maintenance start <reconfigure
199202
'
200203

201204
test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '

0 commit comments

Comments
 (0)