From 21186cf9bbe5f1d8a039286027ebb4f33c7f85d1 Mon Sep 17 00:00:00 2001 From: Jonatan Holmgren Date: Fri, 24 Apr 2026 18:17:00 +0200 Subject: [PATCH 1/4] alias: restore support for simple dotted aliases Historically, config entries like alias.foo.bar expanded the alias "foo.bar". The subsection-based alias syntax introduced in ac1f12a9de (alias: support non-alphanumeric names via subsection syntax, 2026-02-18) broke that behavior by treating such entries as if they were subsection syntax. Restore support for the old dotted form by falling back to the full name when the final key is not "command". Add tests covering execution and help output for simple dotted aliases. Reported-by: Michael Grossfeld Helped-by: Jeff King Signed-off-by: Jonatan Holmgren Signed-off-by: Junio C Hamano --- alias.c | 16 ++++++++++++++-- help.c | 9 ++++++++- t/t0014-alias.sh | 12 ++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/alias.c b/alias.c index ec9833dd30f3cd..e737c49eddf35f 100644 --- a/alias.c +++ b/alias.c @@ -34,8 +34,20 @@ static int config_alias_cb(const char *var, const char *value, if (subsection && !subsection_len) subsection = NULL; - if (subsection && strcmp(key, "command")) - return 0; + if (subsection && strcmp(key, "command")) { + /* + * We have historically supported the "alias.name" form when + * "name" happens to contain dots (e.g., alias.foo.bar to allow + * "git foo.bar". But our parsing above would split that into + * subsection "foo". + * + * If we do not understand the final key in a subsection-style + * variable, fall back to treating it as a two-level alias. + */ + key = var + strlen("alias."); + subsection = NULL; + subsection_len = 0; + } if (data->alias) { int match; diff --git a/help.c b/help.c index 725e92a1958eeb..79150bf7a2d121 100644 --- a/help.c +++ b/help.c @@ -593,14 +593,21 @@ static int git_unknown_cmd_config(const char *var, const char *value, /* Also use aliases for command lookup */ if (!parse_config_key(var, "alias", &subsection, &subsection_len, &key)) { + size_t key_len = strlen(key); + if (subsection) { /* [alias "name"] command = value */ if (!strcmp(key, "command")) add_cmdname(&cfg->aliases, subsection, subsection_len); + else { + key = var + strlen("alias."); + key_len = strlen(key); + add_cmdname(&cfg->aliases, key, key_len); + } } else { /* alias.name = value */ - add_cmdname(&cfg->aliases, key, strlen(key)); + add_cmdname(&cfg->aliases, key, key_len); } } diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh index 68b4903cbfa595..5144b0effd78aa 100755 --- a/t/t0014-alias.sh +++ b/t/t0014-alias.sh @@ -128,6 +128,12 @@ test_expect_success 'subsection syntax works' ' test_grep "ran-subsection" output ' +test_expect_success 'simple dotted alias syntax still works' ' + test_config alias.simple.dotted "!echo ran-simple-dotted" && + git simple.dotted >output && + test_grep "ran-simple-dotted" output +' + test_expect_success 'subsection syntax only accepts command key' ' test_config alias.invalid.notcommand value && test_must_fail git invalid 2>error && @@ -183,6 +189,12 @@ test_expect_success 'subsection aliases listed in help -a' ' test_grep "förgrena" output ' +test_expect_success 'simple dotted aliases listed in help -a' ' + test_config alias.simple.listed "!echo test" && + git help -a >output && + test_grep "simple.listed" output +' + test_expect_success 'empty subsection treated as no subsection' ' test_config "alias..something" "!echo foobar" && git something >actual && From 2431f5e0e5cd415a3ab1bddb435b692536cc95e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samo=20Poga=C4=8Dnik?= Date: Mon, 11 May 2026 21:20:42 +0200 Subject: [PATCH 2/4] shallow: fix relative deepen on non-shallow repositories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit "3ef68ff40e (shallow: handling fetch relative-deepen, 2026-02-15)" introduced a bug where using --deepen= on a non- shallow repository incorrectly treated the value as an absolute depth, resulting in a shallow fetch and truncated history. This patch prevents any modification when a relative deepen is requested on a non-shallow repository. A test is added to ensure that history is not changed when --deepen is used on a non-shallow repository. Reported-by: Owen Stephens Signed-off-by: Samo Pogačnik Signed-off-by: Junio C Hamano --- shallow.c | 6 +++++- t/t5537-fetch-shallow.sh | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/shallow.c b/shallow.c index a8ad92e303d24d..610ff3d13bf17e 100644 --- a/shallow.c +++ b/shallow.c @@ -245,7 +245,11 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth, int shallow_flag, int not_shallow_flag) { if (shallows && deepen_relative) { - depth += get_shallows_depth(heads, shallows); + int cur_shallow_depth = get_shallows_depth(heads, shallows); + if (cur_shallow_depth) + depth += cur_shallow_depth; + else + return NULL; } return get_shallows_or_depth(heads, NULL, NULL, depth, shallow_flag, not_shallow_flag); diff --git a/t/t5537-fetch-shallow.sh b/t/t5537-fetch-shallow.sh index 6588ce62264331..9982dd2aa6d499 100755 --- a/t/t5537-fetch-shallow.sh +++ b/t/t5537-fetch-shallow.sh @@ -251,6 +251,16 @@ test_expect_success '.git/shallow is edited by repack' ' origin "+refs/heads/*:refs/remotes/origin/*" ' +test_expect_success 'fetch --deepen does not truncate' ' + git clone --no-local .git full-clone && + git -C full-clone rev-parse --is-shallow-repository >expect && + git -C full-clone log --oneline >>expect && + git -C full-clone fetch --deepen=1 && + git -C full-clone rev-parse --is-shallow-repository >actual && + git -C full-clone log --oneline >>actual && + test_cmp expect actual +' + . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd From 6e95b07e5fd82d248ed7985cd8d45bb20a12aba9 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 13 May 2026 09:31:13 +0200 Subject: [PATCH 3/4] builtin/maintenance: fix locking with "--detach" When running git-maintenance(1), we create a lockfile that is supposed to keep other maintenance processes from running at the same time. This lockfile is broken though in case the "--detach" flag is passed: the lockfile is created by the parent process and will be cleaned up either manually or on exit. But when detaching, the parent will exit before all of the background maintenance tasks have been run, and consequently the lock only covers a smaller part of the whole maintenance process. Fix this bug by reassigning all tempfiles from the parent process to the child process when daemonizing so that it becomes the responsibility of the child to clean them up. Note that this is a broader fix, as we now always reassign tempfiles when daemonizing. This is a natural consequence of the semantics of `daemonize()` though, as it essentially promises to continue running the current process in the background. It is thus sensible to have that function perform the whole dance of assigning resources to the child process, including tempfiles. There's only a single other caller in "daemon.c", but that process doesn't create any tempfiles before the call to `daemonize()` and is thus not impacted by this change. Reported-by: Jean-Christophe Manciot Helped-by: Jeff King Helped-by: Derrick Stolee Co-authored-by: Taylor Blau Signed-off-by: Taylor Blau Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- setup.c | 16 +++++++++++- setup.h | 15 +++++++++++ t/t7900-maintenance.sh | 58 ++++++++++++++++++++++++++++++++++++++++++ tempfile.c | 12 +++++++++ tempfile.h | 11 ++++++++ 5 files changed, 111 insertions(+), 1 deletion(-) diff --git a/setup.c b/setup.c index 7ec4427368a2a7..14445a71a4297e 100644 --- a/setup.c +++ b/setup.c @@ -2162,12 +2162,26 @@ int daemonize(void) errno = ENOSYS; return -1; #else - switch (fork()) { + pid_t parent_pid = getpid(); + pid_t child_pid = fork(); + + switch (child_pid) { case 0: + /* + * We're in the child process, so we take ownership of + * all tempfiles. + */ + reassign_tempfile_ownership(parent_pid, getpid()); break; case -1: die_errno(_("fork failed")); default: + /* + * We're in the parent process, so we drop ownership of + * all tempfiles to prevent us from removing them upon + * exit. + */ + reassign_tempfile_ownership(parent_pid, child_pid); exit(0); } if (setsid() == -1) diff --git a/setup.h b/setup.h index 80bc6e5f078af8..b5bc5f280c34f0 100644 --- a/setup.h +++ b/setup.h @@ -149,6 +149,21 @@ void verify_non_filename(const char *prefix, const char *name); int path_inside_repo(const char *prefix, const char *path); void sanitize_stdfds(void); + +/* + * Daemonize the current process by forking and then exiting the parent + * process. Returns 0 when successful, in which case the parent process will + * have exited and it's the child process that continues to run the code. + * Otherwise, a negative error code is returned and the parent process will + * continue execution. + * + * Note that this function will also perform the following changes: + * + * - Standard file descriptors in the child process are closed. + * - The child process is made a session leader via setsid(3p). + * - All tempfiles owned by the parent process are reassigned to the + * daemonized child process. + */ int daemonize(void); /* diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 4700beacc18281..df0bbc16692987 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -1438,6 +1438,64 @@ test_expect_success '--no-detach causes maintenance to not run in background' ' ) ' +test_expect_success PIPE '--detach holds maintenance lock until daemonized child exits' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + git config maintenance.auto false && + git config core.lockfilepid true && + + git remote add origin /does/not/exist && + git config set remote.origin.uploadpack "cat fifo-uploadpack" && + + mkfifo fifo-uploadpack fifo-maint && + + # Open the maintenance FIFO, as otherwise spawning + # git-maintenance(1) would block. Note that we need to open it + # as read-write, as otherwise we would block here already. + exec 9<>fifo-maint && + + { git maintenance run --task=prefetch --detach 7>&9 & } && + parent="$!" && + + # Reap the parent process so that the exec call below will not + # get SIGCHLD. + wait "$parent" && + + # Open the git-upload-pack(1) FIFO for writing, which will + # block until the upload-pack script opens it for reading. Once + # exec returns, we know that the daemonized child is alive and + # pinned. + exec 8>fifo-uploadpack && + + test_path_is_file .git/objects/maintenance.lock && + test_path_is_file .git/objects/"maintenance~pid.lock" && + + # Verify that the maintenance.lock still exists, and + # that it was created by the parent process, not the + # child. + echo "pid $parent" >expect && + test_cmp expect .git/objects/"maintenance~pid.lock" && + + # Reopen the maintenance FIFO as read-only so that + # git-maintenance(1) is the only writer. This will cause it to + # close the FIFO once the process exits. + exec 9<&- && + exec 9&- && + cat <&9 && + + test_path_is_missing .git/objects/maintenance.lock && + test_path_is_missing .git/objects/"maintenance~pid.lock" + ) +' + test_expect_success '--detach causes maintenance to run in background' ' test_when_finished "rm -rf repo" && git init repo && diff --git a/tempfile.c b/tempfile.c index 82dfa3d82f2ac9..f0fdf582794ba5 100644 --- a/tempfile.c +++ b/tempfile.c @@ -373,3 +373,15 @@ int delete_tempfile(struct tempfile **tempfile_p) return err ? -1 : 0; } + +void reassign_tempfile_ownership(pid_t from, pid_t to) +{ + volatile struct volatile_list_head *pos; + + list_for_each(pos, &tempfile_list) { + struct tempfile *p = list_entry(pos, struct tempfile, list); + + if (is_tempfile_active(p) && p->owner == from) + p->owner = to; + } +} diff --git a/tempfile.h b/tempfile.h index 2d2ae5b657d4a9..2227a095fd4289 100644 --- a/tempfile.h +++ b/tempfile.h @@ -282,4 +282,15 @@ int delete_tempfile(struct tempfile **tempfile_p); */ int rename_tempfile(struct tempfile **tempfile_p, const char *path); +/* + * Reassign ownership of all active tempfiles whose `owner` field matches + * `from` to `to`. + * + * This is intended for use by `daemonize()`; after `fork(2)`-ing, the parent + * transfers ownership to the daemonized child so that its atexit handler does + * not unlink tempfiles that should outlive it, and the child claims the + * inherited tempfiles so that they are cleaned up when the daemon exits. + */ +void reassign_tempfile_ownership(pid_t from, pid_t to); + #endif /* TEMPFILE_H */ From 29364f16242abd490160f66d2d239ac45e1d45fb Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 13 May 2026 09:31:14 +0200 Subject: [PATCH 4/4] run-command: honor "gc.auto" for auto-maintenance The "gc.auto" configuration has traditionally been used to turn off running git-gc(1) as part of our auto-maintenance. We have eventually switched over to git-maintenance(1) in a95ce12430 (maintenance: replace run_auto_gc(), 2020-09-17), and with 1942d48380 (maintenance: optionally skip --auto process, 2020-08-28) we have introduced "maintenance.auto" to control whether or not to run auto-maintenance. At that point though we still shelled out to git-gc(1) internally. So if "gc.auto=0" was set we would still _execute_ git-maintenance(1), but the command would have exited fast because git-gc(1) itself knew to honor the config key. This has recently changed though, as we have adapted the default maintenance strategy to not use git-gc(1) anymore. The consequence is that "gc.auto=0" doesn't have an effect anymore, which is a somewhat surprising change in behaviour for our users. Adapt `run_auto_maintenance()` so that it knows to also read "gc.auto", similar to how it also reads both "maintenance.autoDetach" and "gc.autoDetach". Reported-by: Jean-Christophe Manciot Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- run-command.c | 10 +++++++--- t/t7900-maintenance.sh | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/run-command.c b/run-command.c index c146a56532a139..28202a81d83d8b 100644 --- a/run-command.c +++ b/run-command.c @@ -1944,10 +1944,14 @@ void run_processes_parallel(const struct run_process_parallel_opts *opts) int prepare_auto_maintenance(struct repository *r, int quiet, struct child_process *maint) { - int enabled, auto_detach; + int enabled = 1, auto_detach; - if (!repo_config_get_bool(r, "maintenance.auto", &enabled) && - !enabled) + if (repo_config_get_bool(r, "maintenance.auto", &enabled)) { + int gc_threshold; + if (!repo_config_get_int(r, "gc.auto", &gc_threshold)) + enabled = gc_threshold > 0; + } + if (!enabled) return 0; /* diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index df0bbc16692987..97c8c701bbc911 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -73,6 +73,31 @@ test_expect_success 'maintenance.auto config option' ' test_subcommand ! git maintenance run --auto --quiet --detach