* [PATCH v12] checkout: extend --track with a "fetch" mode to refresh start-point
From: Harald Nordgren via GitGitGadget @ 2026-05-21 10:20 UTC (permalink / raw)
To: git
Cc: Ramsay Jones, D. Ben Knoble, Kristoffer Haugsbakk, Marc Branchaud,
Phillip Wood, Harald Nordgren, Harald Nordgren
In-Reply-To: <pull.2281.v11.git.git.1779177508772.gitgitgadget@gmail.com>
From: Harald Nordgren <haraldnordgren@gmail.com>
Add a "fetch" mode to the "--track" option of "git checkout" / "git
switch" that refreshes <start-point> before checking it out:
git checkout -b new_branch --track=fetch origin/some-branch
is shorthand for
git fetch origin some-branch
git checkout -b new_branch --track origin/some-branch
Identify the remote whose configured fetch refspec maps to
<start-point>, then run "git fetch <remote> <src-ref>" for just that
ref so other remote-tracking branches are left untouched. When
<start-point> is a bare <remote> (e.g. "origin"), follow
refs/remotes/<remote>/HEAD to learn which branch to refresh. If
"git fetch" fails but the remote-tracking ref already exists locally,
warn and proceed from the existing tip; otherwise abort.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
checkout: --track=fetch
* Find the right remote by matching fetch refspecs. Instead of assuming
the start-point begins with a remote's name, ask each configured
remote whether its fetch refspec maps to refs/remotes/<start-point>.
* Die clearly when no remote matches. Previously, if we couldn't figure
out the remote we'd silently skip the fetch and fall through to a
confusing later error.
* Die clearly when more than one remote matches. If two remotes both
map their fetches into the same refs/remotes/<ns>/* namespace, there
is no unambiguous choice.
* Bare-namespace form (--track=fetch origin) requires <ns>/HEAD. When
the user passes just a namespace, we now follow
refs/remotes/<ns>/HEAD to learn which branch to refresh. If that
symref is missing, die with a hint to run git remote set-head <ns>
--auto instead of guessing or fetching everything. If <ns>/HEAD
points outside the namespace, reject it.
* Validate the refname before doing anything. Reject obviously invalid
start-points like foo..bar up front, so we don't run a fetch we know
cannot succeed.
* Forward --quiet to the underlying fetch. checkout -q --track=fetch
... now suppresses the fetch progress output, matching the user's
intent.
* More tests coverage.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2281%2FHaraldNordgren%2Fcheckout-fetch-start-point-v12
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2281/HaraldNordgren/checkout-fetch-start-point-v12
Pull-Request: https://github.com/git/git/pull/2281
Range-diff vs v11:
1: d0c9e3e879 ! 1: bcd034dbed checkout: extend --track with a "fetch" mode to refresh start-point
@@ Metadata
## Commit message ##
checkout: extend --track with a "fetch" mode to refresh start-point
- If you want to fork your topic branch from the very latest of the
- tip of a branch your remote has, you would do:
-
- git fetch origin some-branch
- git checkout -b new_branch --track origin/some-branch
-
- Extend the "--track" option of "git checkout" and allow users to
- write
+ Add a "fetch" mode to the "--track" option of "git checkout" / "git
+ switch" that refreshes <start-point> before checking it out:
git checkout -b new_branch --track=fetch origin/some-branch
- to (1) fetch 'some-branch' from the remote 'origin', updating the
- remote-tracking branch 'origin/some-branch', (2) arrange subsequent
- 'git pull' on 'new_branch' to interact with 'origin/some-branch' and
- (3) fork 'new_branch' from it.
-
- In the value of the '--track' option, 'fetch' can be combined with
- the existing 'direct' (default) and 'inherit' modes via a
- comma-separated list. Examples:
+ is shorthand for
- git checkout -b new_branch --track=fetch,inherit some_local_branch
- git switch -c new_branch --track=fetch origin/some-branch
+ git fetch origin some-branch
+ git checkout -b new_branch --track origin/some-branch
- When "fetch" is requested and <start-point> is in <remote>/<branch>
- form, run "git fetch <remote> <branch>" before resolving the ref, so
- that other remote-tracking branches are left untouched. If
- <start-point> is a bare remote name like "origin" (which resolves to
- that remote's default branch), "git fetch <remote>" is run instead,
- since the target branch is not known up front. Abort the checkout if
- the fetch fails.
+ Identify the remote whose configured fetch refspec maps to
+ <start-point>, then run "git fetch <remote> <src-ref>" for just that
+ ref so other remote-tracking branches are left untouched. When
+ <start-point> is a bare <remote> (e.g. "origin"), follow
+ refs/remotes/<remote>/HEAD to learn which branch to refresh. If
+ "git fetch" fails but the remote-tracking ref already exists locally,
+ warn and proceed from the existing tip; otherwise abort.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
@@ Documentation/git-checkout.adoc: of it").
+`fetch` requests that the remote be fetched before _<start-point>_ is
+resolved, so the new branch starts from a fresh tip: when
+_<start-point>_ is in _<remote>/<branch>_ form, only that branch is
-+updated; when _<start-point>_ is a bare remote name (e.g. `origin`),
-+only the remote's default branch is updated. If the fetch fails and the
++updated; when _<start-point>_ is a bare _<remote>_ (e.g. `origin`), the
++branch named by _<remote>/HEAD_ is updated, and the checkout fails
++with a hint to configure that symref if it is not set. The checkout
++also fails if no configured remote's fetch refspec maps to
++_<start-point>_, or if more than one does (in which case the `fetch`
++cannot be unambiguously routed). If the fetch itself fails and the
+corresponding remote-tracking ref already exists, a warning is printed
+and the checkout proceeds from the existing tip; otherwise the checkout
+is aborted.
@@ Documentation/git-switch.adoc: variable.
+`--track[=(direct|inherit|fetch)[,...]]`::
When creating a new branch, set up "upstream" configuration.
`-c` is implied. See `--track` in linkgit:git-branch[1] for
- details.
+- details.
++ details, and `--track` in linkgit:git-checkout[1] for the
++ `fetch` mode.
+
-+The argument is a comma-separated list. `direct` (the default) and
-+`inherit` select the tracking mode and are mutually exclusive. Adding
-+`fetch` requests that the remote be fetched before _<start-point>_ is
-+resolved, so the new branch starts from a fresh tip: when
-+_<start-point>_ is in _<remote>/<branch>_ form, only that branch is
-+updated; when _<start-point>_ is a bare remote name (e.g. `origin`),
-+only the remote's default branch is updated. If the fetch fails and the
-+corresponding remote-tracking ref already exists, a warning is printed
-+and the switch proceeds from the existing tip; otherwise the switch is
-+aborted.
-++
If no `-c` option is given, the name of the new branch will be derived
from the remote-tracking branch, by looking at the local part of the
- refspec configured for the corresponding remote, and then stripping
## builtin/checkout.c ##
@@
@@ builtin/checkout.c: struct branch_info {
};
+struct fetch_target_cb {
-+ struct refspec_item query;
-+ const char *remote_name;
-+ int matches;
++ char *dst;
++ struct string_list matches;
+};
+
+static int match_fetch_target(struct remote *remote, void *priv)
+{
+ struct fetch_target_cb *cb = priv;
-+ struct refspec_item q = { .dst = cb->query.dst };
-+
-+ if (!remote_find_tracking(remote, &q) && q.src) {
-+ if (++cb->matches == 1) {
-+ cb->remote_name = remote->name;
-+ free(cb->query.src);
-+ cb->query.src = q.src;
-+ } else {
-+ free(q.src);
-+ }
-+ }
++ struct refspec_item q = { .dst = cb->dst };
++
++ if (!remote_find_tracking(remote, &q) && q.src)
++ string_list_append(&cb->matches, remote->name)->util = q.src;
+ return 0;
+}
+
-+static int resolve_fetch_target(const char *arg, char **remote_out,
-+ char **src_ref_out, char **existing_ref_out)
++static void fetch_remote_for_start_point(const char *arg, int quiet)
+{
+ struct strbuf dst = STRBUF_INIT;
-+ struct strbuf head_path = STRBUF_INIT;
-+ struct fetch_target_cb cb = { 0 };
++ struct fetch_target_cb cb = { .matches = STRING_LIST_INIT_NODUP };
++ struct child_process cmd = CHILD_PROCESS_INIT;
+ struct object_id oid;
-+ const char *head_target;
-+
-+ *remote_out = NULL;
-+ *src_ref_out = NULL;
-+ *existing_ref_out = NULL;
-+
-+ if (!arg || !*arg)
-+ return -1;
-+
-+ strbuf_addf(&head_path, "refs/remotes/%s/HEAD", arg);
-+ head_target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
-+ head_path.buf,
-+ RESOLVE_REF_READING |
-+ RESOLVE_REF_NO_RECURSE,
-+ &oid, NULL);
-+ if (head_target)
-+ strbuf_addstr(&dst, head_target);
-+ else
-+ strbuf_addf(&dst, "refs/remotes/%s", arg);
-+
-+ cb.query.dst = dst.buf;
-+ for_each_remote(match_fetch_target, &cb);
-+
-+ if (cb.matches != 1) {
-+ free(cb.query.src);
-+ strbuf_release(&dst);
-+ strbuf_release(&head_path);
-+ return -1;
++ struct remote *named_remote;
++ int bare_ns;
++ size_t i;
++
++ strbuf_addf(&dst, "refs/remotes/%s", arg);
++ if (check_refname_format(dst.buf, 0))
++ die(_("cannot fetch start-point '%s': not a valid "
++ "remote-tracking name"), arg);
++
++ named_remote = remote_get(arg);
++ bare_ns = !strchr(arg, '/') ||
++ (named_remote && remote_is_configured(named_remote, 1));
++ if (bare_ns) {
++ char *head_path = xstrfmt("refs/remotes/%s/HEAD", arg);
++ const char *head_target =
++ refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
++ head_path,
++ RESOLVE_REF_READING |
++ RESOLVE_REF_NO_RECURSE,
++ &oid, NULL);
++ if (head_target &&
++ starts_with(head_target, dst.buf) &&
++ head_target[dst.len] == '/' &&
++ !check_refname_format(head_target, 0)) {
++ strbuf_reset(&dst);
++ strbuf_addstr(&dst, head_target);
++ bare_ns = 0;
++ }
++ free(head_path);
+ }
+
-+ *remote_out = xstrdup(cb.remote_name);
-+ *src_ref_out = cb.query.src;
-+ if (head_target)
-+ *existing_ref_out = strbuf_detach(&head_path, NULL);
-+ else if (!refs_read_ref(get_main_ref_store(the_repository),
-+ dst.buf, &oid))
-+ *existing_ref_out = strbuf_detach(&dst, NULL);
-+
-+ strbuf_release(&dst);
-+ strbuf_release(&head_path);
-+ return 0;
-+}
++ cb.dst = dst.buf;
++ for_each_remote(match_fetch_target, &cb);
+
-+static void fetch_remote_for_start_point(const char *arg)
-+{
-+ char *remote_name = NULL;
-+ char *src_ref = NULL;
-+ char *existing_ref = NULL;
-+ struct child_process cmd = CHILD_PROCESS_INIT;
++ if (cb.matches.nr > 1) {
++ struct strbuf msg = STRBUF_INIT;
++
++ strbuf_addf(&msg,
++ _("cannot fetch start-point '%s': fetch refspecs "
++ "of multiple remotes map to the same destination:"),
++ arg);
++ for (i = 0; i < cb.matches.nr; i++)
++ strbuf_addf(&msg, "\n %s", cb.matches.items[i].string);
++ strbuf_addstr(&msg,
++ _("\nadjust 'remote.<name>.fetch' so only one "
++ "remote maps there, or omit '=fetch'"));
++ die("%s", msg.buf);
++ }
+
-+ if (resolve_fetch_target(arg, &remote_name, &src_ref, &existing_ref))
-+ return;
++ if (!cb.matches.nr) {
++ if (bare_ns && named_remote &&
++ remote_is_configured(named_remote, 1))
++ die(_("cannot fetch start-point '%s': "
++ "'refs/remotes/%s/HEAD' is not set; run "
++ "'git remote set-head %s --auto' to set it"),
++ arg, arg, arg);
++ die(_("cannot fetch start-point '%s': no configured remote's "
++ "fetch refspec matches it"), arg);
++ }
+
-+ strvec_pushl(&cmd.args, "fetch", remote_name, NULL);
-+ if (src_ref)
-+ strvec_push(&cmd.args, src_ref);
++ strvec_push(&cmd.args, "fetch");
++ if (quiet)
++ strvec_push(&cmd.args, "--quiet");
++ strvec_pushl(&cmd.args, cb.matches.items[0].string,
++ (char *)cb.matches.items[0].util, NULL);
+ cmd.git_cmd = 1;
+ if (run_command(&cmd)) {
-+ if (existing_ref)
++ if (!refs_read_ref(get_main_ref_store(the_repository),
++ dst.buf, &oid))
+ warning(_("failed to fetch start-point '%s'; "
-+ "using existing '%s'"),
-+ arg, existing_ref);
++ "using existing '%s'"), arg, dst.buf);
+ else
+ die(_("failed to fetch start-point '%s'"), arg);
+ }
+
-+ free(remote_name);
-+ free(src_ref);
-+ free(existing_ref);
++ for (i = 0; i < cb.matches.nr; i++)
++ free(cb.matches.items[i].util);
++ string_list_clear(&cb.matches, 0);
++ strbuf_release(&dst);
+}
+
+static int parse_opt_checkout_track(const struct option *opt,
@@ builtin/checkout.c: struct branch_info {
+ struct checkout_opts *opts = opt->value;
+ struct string_list tokens = STRING_LIST_INIT_DUP;
+ struct string_list_item *item;
-+ int saw_direct = 0, saw_inherit = 0;
++ int saw_direct = 0;
+ int ret = 0;
+
+ opts->fetch = 0;
-+
+ if (unset) {
+ opts->track = BRANCH_TRACK_NEVER;
+ return 0;
+ }
-+
+ opts->track = BRANCH_TRACK_EXPLICIT;
+ if (!arg)
+ return 0;
+
+ string_list_split(&tokens, arg, ",", -1);
+ for_each_string_list_item(item, &tokens) {
-+ if (!strcmp(item->string, "fetch")) {
++ if (!strcmp(item->string, "fetch"))
+ opts->fetch = 1;
-+ } else if (!strcmp(item->string, "direct")) {
++ else if (!strcmp(item->string, "direct"))
+ saw_direct = 1;
-+ opts->track = BRANCH_TRACK_EXPLICIT;
-+ } else if (!strcmp(item->string, "inherit")) {
-+ saw_inherit = 1;
++ else if (!strcmp(item->string, "inherit"))
+ opts->track = BRANCH_TRACK_INHERIT;
-+ } else {
++ else {
+ ret = error(_("option `%s' expects \"%s\", \"%s\", "
+ "or \"%s\""),
+ "--track", "direct", "inherit", "fetch");
+ goto out;
+ }
+ }
-+
-+ if (saw_direct && saw_inherit)
++ if (saw_direct && opts->track == BRANCH_TRACK_INHERIT)
+ ret = error(_("option `%s' cannot combine \"%s\" and \"%s\""),
+ "--track", "direct", "inherit");
-+
+out:
+ string_list_clear(&tokens, 0);
+ return ret;
@@ builtin/checkout.c: static int checkout_main(int argc, const char **argv, const
+ int n;
+
+ if (opts->fetch)
-+ fetch_remote_for_start_point(argv[0]);
++ fetch_remote_for_start_point(argv[0], opts->quiet);
+
+ n = parse_branchname_arg(argc, argv, dwim_ok, which_command,
+ &new_branch_info, opts, &rev);
@@ t/t7201-co.sh: test_expect_success 'tracking info copied with autoSetupMerge=inh
+ git checkout main &&
+ git remote add fetch_ns ./fetch_upstream &&
+ test_when_finished "git remote remove fetch_ns" &&
++ test_when_finished "git update-ref -d refs/remotes/ns_alias/HEAD" &&
+ git config --replace-all remote.fetch_ns.fetch \
+ "+refs/heads/*:refs/remotes/ns_alias/*" &&
+ git fetch fetch_ns &&
@@ t/t7201-co.sh: test_expect_success 'tracking info copied with autoSetupMerge=inh
+ test_cmp_config refs/heads/main branch.local_ns.merge
+'
+
++test_expect_success '--track=fetch on bare hierarchical remote name follows <ns>/HEAD' '
++ git checkout main &&
++ git remote add nested/bare ./fetch_upstream &&
++ test_when_finished "git remote remove nested/bare" &&
++ test_when_finished "git update-ref -d refs/remotes/nested/bare/HEAD" &&
++ git fetch nested/bare &&
++ git symbolic-ref refs/remotes/nested/bare/HEAD \
++ refs/remotes/nested/bare/main &&
++ git -C fetch_upstream checkout main &&
++ test_commit -C fetch_upstream u_nested_bare_post &&
++ git checkout --track=fetch -b local_nested_bare nested/bare &&
++ test_cmp_rev refs/remotes/nested/bare/main HEAD
++'
++
+test_expect_success 'checkout --track=fetch handles hierarchical remote name' '
+ git checkout main &&
-+ git -C fetch_upstream checkout -b fetch_hier &&
-+ test_commit -C fetch_upstream u_hier &&
+ git remote add nested/remote ./fetch_upstream &&
+ test_when_finished "git remote remove nested/remote" &&
-+ git fetch nested/remote fetch_hier &&
-+ test_commit -C fetch_upstream u_hier_post &&
++ git -C fetch_upstream checkout -b fetch_hier &&
++ test_commit -C fetch_upstream u_hier &&
++ test_must_fail git rev-parse --verify refs/remotes/nested/remote/fetch_hier &&
+ git checkout --track=fetch -b local_hier nested/remote/fetch_hier &&
+ test_cmp_rev refs/remotes/nested/remote/fetch_hier HEAD
+'
+
++test_expect_success 'checkout --track=fetch dies on bare remote name with no <ns>/HEAD' '
++ git checkout main &&
++ git remote add fetch_nohead ./fetch_upstream &&
++ test_when_finished "git remote remove fetch_nohead" &&
++ test_might_fail git symbolic-ref -d refs/remotes/fetch_nohead/HEAD &&
++ test_must_fail git checkout --track=fetch -b local_nohead fetch_nohead 2>err &&
++ test_grep "refs/remotes/fetch_nohead/HEAD" err &&
++ test_grep "git remote set-head fetch_nohead --auto" err &&
++ test_must_fail git rev-parse --verify refs/heads/local_nohead
++'
++
++test_expect_success 'checkout --track=fetch on bare unknown name does not suggest set-head' '
++ git checkout main &&
++ test_must_fail git rev-parse --verify refs/remotes/no_such_ns/HEAD &&
++ test_must_fail git config --get remote.no_such_ns.url &&
++ test_must_fail git checkout --track=fetch -b local_unknown no_such_ns 2>err &&
++ test_grep "no configured remote" err &&
++ test_grep ! "set-head" err &&
++ test_must_fail git rev-parse --verify refs/heads/local_unknown
++'
++
++test_expect_success 'checkout --track=fetch rejects <ns>/HEAD pointing outside namespace' '
++ git checkout main &&
++ git remote add fetch_crossns ./fetch_upstream &&
++ test_when_finished "git remote remove fetch_crossns" &&
++ test_when_finished "git update-ref -d refs/remotes/fetch_crossns/HEAD" &&
++ git fetch fetch_crossns &&
++ git symbolic-ref refs/remotes/fetch_crossns/HEAD \
++ refs/remotes/fetch_upstream/u_main &&
++ test_must_fail git checkout --track=fetch -b local_crossns fetch_crossns 2>err &&
++ test_grep "refs/remotes/fetch_crossns/HEAD" err &&
++ test_must_fail git rev-parse --verify refs/heads/local_crossns
++'
++
++test_expect_success 'checkout --track=fetch dies on ambiguous fetch refspec match' '
++ git checkout main &&
++ git remote add fetch_ambig_a ./fetch_upstream &&
++ git remote add fetch_ambig_b ./fetch_upstream &&
++ test_when_finished "git remote remove fetch_ambig_a" &&
++ test_when_finished "git remote remove fetch_ambig_b" &&
++ git config --replace-all remote.fetch_ambig_a.fetch \
++ "+refs/heads/*:refs/remotes/ambig_ns/*" &&
++ git config --replace-all remote.fetch_ambig_b.fetch \
++ "+refs/heads/*:refs/remotes/ambig_ns/*" &&
++ git -C fetch_upstream checkout -b fetch_ambig &&
++ test_commit -C fetch_upstream u_ambig &&
++ test_must_fail git checkout --track=fetch -b local_ambig ambig_ns/fetch_ambig 2>err &&
++ test_grep "fetch_ambig_a" err &&
++ test_grep "fetch_ambig_b" err &&
++ test_grep "remote.<name>.fetch" err &&
++ test_must_fail git rev-parse --verify refs/heads/local_ambig
++'
++
++test_expect_success 'checkout --track=fetch rejects invalid refname components' '
++ git checkout main &&
++ test_must_fail git checkout --track=fetch -b local_invalid "foo..bar" 2>err &&
++ test_grep "valid" err &&
++ test_must_fail git rev-parse --verify refs/heads/local_invalid
++'
++
++test_expect_success 'checkout --track=fetch,inherit rejects invalid refname components' '
++ git checkout main &&
++ test_must_fail git checkout --track=fetch,inherit -b local_invalid \
++ "foo..bar" 2>err &&
++ test_grep "valid" err &&
++ test_must_fail git rev-parse --verify refs/heads/local_invalid
++'
++
+test_expect_success 'checkout --track=inherit,direct is rejected' '
+ test_must_fail git checkout --track=inherit,direct -b bad fetch_upstream/fetch_new 2>err &&
+ test_grep "cannot combine" err
+'
+
++test_expect_success 'checkout --track=direct,inherit is rejected' '
++ test_must_fail git checkout --track=direct,inherit -b bad fetch_upstream/fetch_new 2>err &&
++ test_grep "cannot combine" err
++'
++
+test_expect_success 'checkout --track=fetch then --track=direct drops fetch (last-one-wins)' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_lastwin &&
@@ t/t7201-co.sh: test_expect_success 'tracking info copied with autoSetupMerge=inh
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_lastwin
+'
+
-+test_expect_success 'checkout --track=fetch,inherit fetches and inherits' '
++test_expect_success 'checkout --track=fetch then --no-track drops fetch' '
++ git checkout main &&
++ git -C fetch_upstream checkout -b fetch_notrack &&
++ test_commit -C fetch_upstream u_notrack &&
++ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_notrack &&
++ test_must_fail git checkout --track=fetch --no-track \
++ -b local_notrack fetch_upstream/fetch_notrack &&
++ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_notrack
++'
++
++test_expect_success 'checkout --track=fetch,inherit fetches remote-tracking start-point' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_inherit &&
+ test_commit -C fetch_upstream u_inherit &&
-+ git fetch fetch_upstream fetch_inherit &&
-+ git checkout -b base_inherit fetch_upstream/fetch_inherit &&
-+ test_commit -C fetch_upstream u_inherit2 &&
++ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_inherit &&
++ git checkout --track=fetch,inherit -b local_inherit \
++ fetch_upstream/fetch_inherit &&
++ test_cmp_rev refs/remotes/fetch_upstream/fetch_inherit HEAD
++'
++
++test_expect_success 'checkout --track=fetch,inherit errors when start-point does not map to a remote' '
+ git checkout main &&
-+ git checkout --track=fetch,inherit -b local_inherit base_inherit &&
-+ test_cmp_rev refs/remotes/fetch_upstream/fetch_inherit HEAD &&
-+ test_cmp_config fetch_upstream branch.local_inherit.remote &&
-+ test_cmp_config refs/heads/fetch_inherit branch.local_inherit.merge
++ test_must_fail git checkout --track=fetch,inherit -b bad main 2>err &&
++ test_grep "no configured remote" err &&
++ test_must_fail git rev-parse --verify refs/heads/bad
++'
++
++test_expect_success 'checkout --track=fetch on local start-point errors' '
++ git checkout main &&
++ test_must_fail git checkout --track=fetch -b bad main 2>err &&
++ test_grep "no configured remote" err &&
++ test_must_fail git rev-parse --verify refs/heads/bad
+'
+
+test_expect_success 'checkout --track=bogus reports an error' '
@@ t/t7201-co.sh: test_expect_success 'tracking info copied with autoSetupMerge=inh
+ test_grep "expects" err
+'
+
++test_expect_success 'checkout -q --track=fetch silences the fetch output' '
++ git checkout main &&
++ git -C fetch_upstream checkout -b fetch_quiet &&
++ test_commit -C fetch_upstream u_quiet &&
++ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_quiet &&
++ git checkout -q --track=fetch -b local_quiet \
++ fetch_upstream/fetch_quiet 2>err &&
++ test_grep ! "-> fetch_upstream/fetch_quiet" err &&
++ test_cmp_rev refs/remotes/fetch_upstream/fetch_quiet HEAD
++'
++
+test_expect_success 'switch --track=fetch -c picks up branch created upstream after clone' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_switch &&
Documentation/git-checkout.adoc | 17 +-
Documentation/git-switch.adoc | 5 +-
builtin/checkout.c | 159 +++++++++++++++++-
t/t7201-co.sh | 276 ++++++++++++++++++++++++++++++++
4 files changed, 450 insertions(+), 7 deletions(-)
diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc
index a8b3b8c2e2..20b6cae60e 100644
--- a/Documentation/git-checkout.adoc
+++ b/Documentation/git-checkout.adoc
@@ -158,11 +158,26 @@ of it").
resets _<branch>_ to the start point instead of failing.
`-t`::
-`--track[=(direct|inherit)]`::
+`--track[=(direct|inherit|fetch)[,...]]`::
When creating a new branch, set up "upstream" configuration. See
`--track` in linkgit:git-branch[1] for details. As a convenience,
--track without -b implies branch creation.
+
+The argument is a comma-separated list. `direct` (the default) and
+`inherit` select the tracking mode and are mutually exclusive. Adding
+`fetch` requests that the remote be fetched before _<start-point>_ is
+resolved, so the new branch starts from a fresh tip: when
+_<start-point>_ is in _<remote>/<branch>_ form, only that branch is
+updated; when _<start-point>_ is a bare _<remote>_ (e.g. `origin`), the
+branch named by _<remote>/HEAD_ is updated, and the checkout fails
+with a hint to configure that symref if it is not set. The checkout
+also fails if no configured remote's fetch refspec maps to
+_<start-point>_, or if more than one does (in which case the `fetch`
+cannot be unambiguously routed). If the fetch itself fails and the
+corresponding remote-tracking ref already exists, a warning is printed
+and the checkout proceeds from the existing tip; otherwise the checkout
+is aborted.
++
If no `-b` option is given, the name of the new branch will be
derived from the remote-tracking branch, by looking at the local part of
the refspec configured for the corresponding remote, and then stripping
diff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc
index d6c4f229a5..a8730b1da8 100644
--- a/Documentation/git-switch.adoc
+++ b/Documentation/git-switch.adoc
@@ -155,10 +155,11 @@ variable.
attached to a terminal, regardless of `--quiet`.
`-t`::
-`--track[ (direct|inherit)]`::
+`--track[=(direct|inherit|fetch)[,...]]`::
When creating a new branch, set up "upstream" configuration.
`-c` is implied. See `--track` in linkgit:git-branch[1] for
- details.
+ details, and `--track` in linkgit:git-checkout[1] for the
+ `fetch` mode.
+
If no `-c` option is given, the name of the new branch will be derived
from the remote-tracking branch, by looking at the local part of the
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 1345e8574a..9c5c4f1c2e 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -25,10 +25,12 @@
#include "preload-index.h"
#include "read-cache.h"
#include "refs.h"
+#include "refspec.h"
#include "remote.h"
#include "repo-settings.h"
#include "resolve-undo.h"
#include "revision.h"
+#include "run-command.h"
#include "sequencer.h"
#include "setup.h"
#include "strvec.h"
@@ -62,6 +64,7 @@ struct checkout_opts {
int count_checkout_paths;
int overlay_mode;
int dwim_new_local_branch;
+ int fetch;
int discard_changes;
int accept_ref;
int accept_pathspec;
@@ -115,6 +118,149 @@ struct branch_info {
char *checkout;
};
+struct fetch_target_cb {
+ char *dst;
+ struct string_list matches;
+};
+
+static int match_fetch_target(struct remote *remote, void *priv)
+{
+ struct fetch_target_cb *cb = priv;
+ struct refspec_item q = { .dst = cb->dst };
+
+ if (!remote_find_tracking(remote, &q) && q.src)
+ string_list_append(&cb->matches, remote->name)->util = q.src;
+ return 0;
+}
+
+static void fetch_remote_for_start_point(const char *arg, int quiet)
+{
+ struct strbuf dst = STRBUF_INIT;
+ struct fetch_target_cb cb = { .matches = STRING_LIST_INIT_NODUP };
+ struct child_process cmd = CHILD_PROCESS_INIT;
+ struct object_id oid;
+ struct remote *named_remote;
+ int bare_ns;
+ size_t i;
+
+ strbuf_addf(&dst, "refs/remotes/%s", arg);
+ if (check_refname_format(dst.buf, 0))
+ die(_("cannot fetch start-point '%s': not a valid "
+ "remote-tracking name"), arg);
+
+ named_remote = remote_get(arg);
+ bare_ns = !strchr(arg, '/') ||
+ (named_remote && remote_is_configured(named_remote, 1));
+ if (bare_ns) {
+ char *head_path = xstrfmt("refs/remotes/%s/HEAD", arg);
+ const char *head_target =
+ refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ head_path,
+ RESOLVE_REF_READING |
+ RESOLVE_REF_NO_RECURSE,
+ &oid, NULL);
+ if (head_target &&
+ starts_with(head_target, dst.buf) &&
+ head_target[dst.len] == '/' &&
+ !check_refname_format(head_target, 0)) {
+ strbuf_reset(&dst);
+ strbuf_addstr(&dst, head_target);
+ bare_ns = 0;
+ }
+ free(head_path);
+ }
+
+ cb.dst = dst.buf;
+ for_each_remote(match_fetch_target, &cb);
+
+ if (cb.matches.nr > 1) {
+ struct strbuf msg = STRBUF_INIT;
+
+ strbuf_addf(&msg,
+ _("cannot fetch start-point '%s': fetch refspecs "
+ "of multiple remotes map to the same destination:"),
+ arg);
+ for (i = 0; i < cb.matches.nr; i++)
+ strbuf_addf(&msg, "\n %s", cb.matches.items[i].string);
+ strbuf_addstr(&msg,
+ _("\nadjust 'remote.<name>.fetch' so only one "
+ "remote maps there, or omit '=fetch'"));
+ die("%s", msg.buf);
+ }
+
+ if (!cb.matches.nr) {
+ if (bare_ns && named_remote &&
+ remote_is_configured(named_remote, 1))
+ die(_("cannot fetch start-point '%s': "
+ "'refs/remotes/%s/HEAD' is not set; run "
+ "'git remote set-head %s --auto' to set it"),
+ arg, arg, arg);
+ die(_("cannot fetch start-point '%s': no configured remote's "
+ "fetch refspec matches it"), arg);
+ }
+
+ strvec_push(&cmd.args, "fetch");
+ if (quiet)
+ strvec_push(&cmd.args, "--quiet");
+ strvec_pushl(&cmd.args, cb.matches.items[0].string,
+ (char *)cb.matches.items[0].util, NULL);
+ cmd.git_cmd = 1;
+ if (run_command(&cmd)) {
+ if (!refs_read_ref(get_main_ref_store(the_repository),
+ dst.buf, &oid))
+ warning(_("failed to fetch start-point '%s'; "
+ "using existing '%s'"), arg, dst.buf);
+ else
+ die(_("failed to fetch start-point '%s'"), arg);
+ }
+
+ for (i = 0; i < cb.matches.nr; i++)
+ free(cb.matches.items[i].util);
+ string_list_clear(&cb.matches, 0);
+ strbuf_release(&dst);
+}
+
+static int parse_opt_checkout_track(const struct option *opt,
+ const char *arg, int unset)
+{
+ struct checkout_opts *opts = opt->value;
+ struct string_list tokens = STRING_LIST_INIT_DUP;
+ struct string_list_item *item;
+ int saw_direct = 0;
+ int ret = 0;
+
+ opts->fetch = 0;
+ if (unset) {
+ opts->track = BRANCH_TRACK_NEVER;
+ return 0;
+ }
+ opts->track = BRANCH_TRACK_EXPLICIT;
+ if (!arg)
+ return 0;
+
+ string_list_split(&tokens, arg, ",", -1);
+ for_each_string_list_item(item, &tokens) {
+ if (!strcmp(item->string, "fetch"))
+ opts->fetch = 1;
+ else if (!strcmp(item->string, "direct"))
+ saw_direct = 1;
+ else if (!strcmp(item->string, "inherit"))
+ opts->track = BRANCH_TRACK_INHERIT;
+ else {
+ ret = error(_("option `%s' expects \"%s\", \"%s\", "
+ "or \"%s\""),
+ "--track", "direct", "inherit", "fetch");
+ goto out;
+ }
+ }
+ if (saw_direct && opts->track == BRANCH_TRACK_INHERIT)
+ ret = error(_("option `%s' cannot combine \"%s\" and \"%s\""),
+ "--track", "direct", "inherit");
+out:
+ string_list_clear(&tokens, 0);
+ return ret;
+}
+
static void branch_info_release(struct branch_info *info)
{
free(info->name);
@@ -1733,10 +1879,10 @@ static struct option *add_common_switch_branch_options(
{
struct option options[] = {
OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
- OPT_CALLBACK_F('t', "track", &opts->track, "(direct|inherit)",
+ OPT_CALLBACK_F('t', "track", opts, "(direct|inherit|fetch)[,...]",
N_("set branch tracking configuration"),
PARSE_OPT_OPTARG,
- parse_opt_tracking_mode),
+ parse_opt_checkout_track),
OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
PARSE_OPT_NOCOMPLETE),
OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unborn branch")),
@@ -1941,8 +2087,13 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
opts->dwim_new_local_branch &&
opts->track == BRANCH_TRACK_UNSPECIFIED &&
!opts->new_branch;
- int n = parse_branchname_arg(argc, argv, dwim_ok, which_command,
- &new_branch_info, opts, &rev);
+ int n;
+
+ if (opts->fetch)
+ fetch_remote_for_start_point(argv[0], opts->quiet);
+
+ n = parse_branchname_arg(argc, argv, dwim_ok, which_command,
+ &new_branch_info, opts, &rev);
argv += n;
argc -= n;
} else if (!opts->accept_ref && opts->from_treeish) {
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 7613b1d2a4..c4a165cb1d 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -870,4 +870,280 @@ test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
test_cmp_config "" --default "" branch.main2.merge
'
+test_expect_success 'setup upstream for --track=fetch tests' '
+ git checkout main &&
+ git init fetch_upstream &&
+ test_commit -C fetch_upstream u_main &&
+ git remote add fetch_upstream fetch_upstream &&
+ git fetch fetch_upstream &&
+ git -C fetch_upstream checkout -b fetch_new &&
+ test_commit -C fetch_upstream u_new
+'
+
+test_expect_success 'checkout --track=fetch -b picks up branch created upstream after clone' '
+ git checkout main &&
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_new &&
+ git checkout --track=fetch -b local_new fetch_upstream/fetch_new &&
+ test_cmp_rev refs/remotes/fetch_upstream/fetch_new HEAD &&
+ test_cmp_config fetch_upstream branch.local_new.remote &&
+ test_cmp_config refs/heads/fetch_new branch.local_new.merge
+'
+
+test_expect_success 'checkout --track=fetch <remote>/<branch> leaves other tracking branches untouched' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_target &&
+ test_commit -C fetch_upstream u_target_pre &&
+ git -C fetch_upstream checkout -b fetch_other &&
+ test_commit -C fetch_upstream u_other_pre &&
+ git fetch fetch_upstream &&
+ other_before=$(git rev-parse refs/remotes/fetch_upstream/fetch_other) &&
+ git -C fetch_upstream checkout fetch_target &&
+ test_commit -C fetch_upstream u_target_post &&
+ git -C fetch_upstream checkout fetch_other &&
+ test_commit -C fetch_upstream u_other_post &&
+ git checkout --track=fetch -b local_target fetch_upstream/fetch_target &&
+ test_cmp_rev refs/remotes/fetch_upstream/fetch_target HEAD &&
+ test "$(git rev-parse refs/remotes/fetch_upstream/fetch_other)" = "$other_before"
+'
+
+test_expect_success 'checkout --track=fetch with bare remote name fetches only <remote>/HEAD target' '
+ git checkout main &&
+ git -C fetch_upstream checkout main &&
+ git remote set-head fetch_upstream main &&
+ git -C fetch_upstream checkout -b fetch_unrelated &&
+ test_commit -C fetch_upstream u_unrelated_pre &&
+ git fetch fetch_upstream fetch_unrelated &&
+ unrelated_before=$(git rev-parse refs/remotes/fetch_upstream/fetch_unrelated) &&
+ git -C fetch_upstream checkout main &&
+ test_commit -C fetch_upstream u_main_post &&
+ git -C fetch_upstream checkout fetch_unrelated &&
+ test_commit -C fetch_upstream u_unrelated_post &&
+ git checkout --track=fetch -b local_from_remote fetch_upstream &&
+ test_cmp_rev refs/remotes/fetch_upstream/main HEAD &&
+ test "$(git rev-parse refs/remotes/fetch_upstream/fetch_unrelated)" = "$unrelated_before"
+'
+
+test_expect_success 'checkout --track=fetch aborts and does not create branch when no existing ref' '
+ git checkout main &&
+ test_might_fail git branch -D bogus &&
+ test_must_fail git checkout --track=fetch -b bogus fetch_upstream/does_not_exist &&
+ test_must_fail git rev-parse --verify refs/heads/bogus
+'
+
+test_expect_success 'checkout --track=fetch warns and proceeds when fetch fails but ref exists' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_offline &&
+ test_commit -C fetch_upstream u_offline &&
+ git fetch fetch_upstream fetch_offline &&
+ saved_url=$(git config remote.fetch_upstream.url) &&
+ test_when_finished "git config remote.fetch_upstream.url \"$saved_url\"" &&
+ git config remote.fetch_upstream.url ./does-not-exist &&
+ git checkout --track=fetch -b local_offline fetch_upstream/fetch_offline 2>err &&
+ test_grep "failed to fetch" err &&
+ test_cmp_rev refs/remotes/fetch_upstream/fetch_offline HEAD
+'
+
+test_expect_success 'checkout --track=fetch resolves through configured fetch refspec' '
+ git checkout main &&
+ git remote add fetch_custom ./fetch_upstream &&
+ test_when_finished "git remote remove fetch_custom" &&
+ git config --replace-all remote.fetch_custom.fetch \
+ "+refs/heads/*:refs/remotes/custom-ns/*" &&
+ git -C fetch_upstream checkout -b fetch_refspec &&
+ test_commit -C fetch_upstream u_refspec &&
+ test_must_fail git rev-parse --verify refs/remotes/custom-ns/fetch_refspec &&
+ git checkout --track=fetch -b local_refspec custom-ns/fetch_refspec &&
+ test_cmp_rev refs/remotes/custom-ns/fetch_refspec HEAD
+'
+
+test_expect_success 'checkout --track=fetch on namespace bare name follows <ns>/HEAD' '
+ git checkout main &&
+ git remote add fetch_ns ./fetch_upstream &&
+ test_when_finished "git remote remove fetch_ns" &&
+ test_when_finished "git update-ref -d refs/remotes/ns_alias/HEAD" &&
+ git config --replace-all remote.fetch_ns.fetch \
+ "+refs/heads/*:refs/remotes/ns_alias/*" &&
+ git fetch fetch_ns &&
+ git symbolic-ref refs/remotes/ns_alias/HEAD refs/remotes/ns_alias/main &&
+ git -C fetch_upstream checkout main &&
+ test_commit -C fetch_upstream u_ns_post &&
+ git checkout --track=fetch -b local_ns ns_alias &&
+ test_cmp_rev refs/remotes/ns_alias/main HEAD &&
+ test_cmp_config fetch_ns branch.local_ns.remote &&
+ test_cmp_config refs/heads/main branch.local_ns.merge
+'
+
+test_expect_success '--track=fetch on bare hierarchical remote name follows <ns>/HEAD' '
+ git checkout main &&
+ git remote add nested/bare ./fetch_upstream &&
+ test_when_finished "git remote remove nested/bare" &&
+ test_when_finished "git update-ref -d refs/remotes/nested/bare/HEAD" &&
+ git fetch nested/bare &&
+ git symbolic-ref refs/remotes/nested/bare/HEAD \
+ refs/remotes/nested/bare/main &&
+ git -C fetch_upstream checkout main &&
+ test_commit -C fetch_upstream u_nested_bare_post &&
+ git checkout --track=fetch -b local_nested_bare nested/bare &&
+ test_cmp_rev refs/remotes/nested/bare/main HEAD
+'
+
+test_expect_success 'checkout --track=fetch handles hierarchical remote name' '
+ git checkout main &&
+ git remote add nested/remote ./fetch_upstream &&
+ test_when_finished "git remote remove nested/remote" &&
+ git -C fetch_upstream checkout -b fetch_hier &&
+ test_commit -C fetch_upstream u_hier &&
+ test_must_fail git rev-parse --verify refs/remotes/nested/remote/fetch_hier &&
+ git checkout --track=fetch -b local_hier nested/remote/fetch_hier &&
+ test_cmp_rev refs/remotes/nested/remote/fetch_hier HEAD
+'
+
+test_expect_success 'checkout --track=fetch dies on bare remote name with no <ns>/HEAD' '
+ git checkout main &&
+ git remote add fetch_nohead ./fetch_upstream &&
+ test_when_finished "git remote remove fetch_nohead" &&
+ test_might_fail git symbolic-ref -d refs/remotes/fetch_nohead/HEAD &&
+ test_must_fail git checkout --track=fetch -b local_nohead fetch_nohead 2>err &&
+ test_grep "refs/remotes/fetch_nohead/HEAD" err &&
+ test_grep "git remote set-head fetch_nohead --auto" err &&
+ test_must_fail git rev-parse --verify refs/heads/local_nohead
+'
+
+test_expect_success 'checkout --track=fetch on bare unknown name does not suggest set-head' '
+ git checkout main &&
+ test_must_fail git rev-parse --verify refs/remotes/no_such_ns/HEAD &&
+ test_must_fail git config --get remote.no_such_ns.url &&
+ test_must_fail git checkout --track=fetch -b local_unknown no_such_ns 2>err &&
+ test_grep "no configured remote" err &&
+ test_grep ! "set-head" err &&
+ test_must_fail git rev-parse --verify refs/heads/local_unknown
+'
+
+test_expect_success 'checkout --track=fetch rejects <ns>/HEAD pointing outside namespace' '
+ git checkout main &&
+ git remote add fetch_crossns ./fetch_upstream &&
+ test_when_finished "git remote remove fetch_crossns" &&
+ test_when_finished "git update-ref -d refs/remotes/fetch_crossns/HEAD" &&
+ git fetch fetch_crossns &&
+ git symbolic-ref refs/remotes/fetch_crossns/HEAD \
+ refs/remotes/fetch_upstream/u_main &&
+ test_must_fail git checkout --track=fetch -b local_crossns fetch_crossns 2>err &&
+ test_grep "refs/remotes/fetch_crossns/HEAD" err &&
+ test_must_fail git rev-parse --verify refs/heads/local_crossns
+'
+
+test_expect_success 'checkout --track=fetch dies on ambiguous fetch refspec match' '
+ git checkout main &&
+ git remote add fetch_ambig_a ./fetch_upstream &&
+ git remote add fetch_ambig_b ./fetch_upstream &&
+ test_when_finished "git remote remove fetch_ambig_a" &&
+ test_when_finished "git remote remove fetch_ambig_b" &&
+ git config --replace-all remote.fetch_ambig_a.fetch \
+ "+refs/heads/*:refs/remotes/ambig_ns/*" &&
+ git config --replace-all remote.fetch_ambig_b.fetch \
+ "+refs/heads/*:refs/remotes/ambig_ns/*" &&
+ git -C fetch_upstream checkout -b fetch_ambig &&
+ test_commit -C fetch_upstream u_ambig &&
+ test_must_fail git checkout --track=fetch -b local_ambig ambig_ns/fetch_ambig 2>err &&
+ test_grep "fetch_ambig_a" err &&
+ test_grep "fetch_ambig_b" err &&
+ test_grep "remote.<name>.fetch" err &&
+ test_must_fail git rev-parse --verify refs/heads/local_ambig
+'
+
+test_expect_success 'checkout --track=fetch rejects invalid refname components' '
+ git checkout main &&
+ test_must_fail git checkout --track=fetch -b local_invalid "foo..bar" 2>err &&
+ test_grep "valid" err &&
+ test_must_fail git rev-parse --verify refs/heads/local_invalid
+'
+
+test_expect_success 'checkout --track=fetch,inherit rejects invalid refname components' '
+ git checkout main &&
+ test_must_fail git checkout --track=fetch,inherit -b local_invalid \
+ "foo..bar" 2>err &&
+ test_grep "valid" err &&
+ test_must_fail git rev-parse --verify refs/heads/local_invalid
+'
+
+test_expect_success 'checkout --track=inherit,direct is rejected' '
+ test_must_fail git checkout --track=inherit,direct -b bad fetch_upstream/fetch_new 2>err &&
+ test_grep "cannot combine" err
+'
+
+test_expect_success 'checkout --track=direct,inherit is rejected' '
+ test_must_fail git checkout --track=direct,inherit -b bad fetch_upstream/fetch_new 2>err &&
+ test_grep "cannot combine" err
+'
+
+test_expect_success 'checkout --track=fetch then --track=direct drops fetch (last-one-wins)' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_lastwin &&
+ test_commit -C fetch_upstream u_lastwin &&
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_lastwin &&
+ test_must_fail git checkout --track=fetch --track=direct \
+ -b local_lastwin fetch_upstream/fetch_lastwin &&
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_lastwin
+'
+
+test_expect_success 'checkout --track=fetch then --no-track drops fetch' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_notrack &&
+ test_commit -C fetch_upstream u_notrack &&
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_notrack &&
+ test_must_fail git checkout --track=fetch --no-track \
+ -b local_notrack fetch_upstream/fetch_notrack &&
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_notrack
+'
+
+test_expect_success 'checkout --track=fetch,inherit fetches remote-tracking start-point' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_inherit &&
+ test_commit -C fetch_upstream u_inherit &&
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_inherit &&
+ git checkout --track=fetch,inherit -b local_inherit \
+ fetch_upstream/fetch_inherit &&
+ test_cmp_rev refs/remotes/fetch_upstream/fetch_inherit HEAD
+'
+
+test_expect_success 'checkout --track=fetch,inherit errors when start-point does not map to a remote' '
+ git checkout main &&
+ test_must_fail git checkout --track=fetch,inherit -b bad main 2>err &&
+ test_grep "no configured remote" err &&
+ test_must_fail git rev-parse --verify refs/heads/bad
+'
+
+test_expect_success 'checkout --track=fetch on local start-point errors' '
+ git checkout main &&
+ test_must_fail git checkout --track=fetch -b bad main 2>err &&
+ test_grep "no configured remote" err &&
+ test_must_fail git rev-parse --verify refs/heads/bad
+'
+
+test_expect_success 'checkout --track=bogus reports an error' '
+ git checkout main &&
+ test_must_fail git checkout --track=bogus -b bogus_branch fetch_upstream/fetch_new 2>err &&
+ test_grep "expects" err
+'
+
+test_expect_success 'checkout -q --track=fetch silences the fetch output' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_quiet &&
+ test_commit -C fetch_upstream u_quiet &&
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_quiet &&
+ git checkout -q --track=fetch -b local_quiet \
+ fetch_upstream/fetch_quiet 2>err &&
+ test_grep ! "-> fetch_upstream/fetch_quiet" err &&
+ test_cmp_rev refs/remotes/fetch_upstream/fetch_quiet HEAD
+'
+
+test_expect_success 'switch --track=fetch -c picks up branch created upstream after clone' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_switch &&
+ test_commit -C fetch_upstream u_switch &&
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_switch &&
+ git switch --track=fetch -c local_switch fetch_upstream/fetch_switch &&
+ test_cmp_rev refs/remotes/fetch_upstream/fetch_switch HEAD
+'
+
test_done
base-commit: aec3f587505a472db67e9462d0702e7d463a449d
--
gitgitgadget
^ permalink raw reply related
* Re: [PATCH v11] checkout: extend --track with a "fetch" mode to refresh start-point
From: Harald Nordgren @ 2026-05-21 10:24 UTC (permalink / raw)
To: phillip.wood
Cc: Junio C Hamano, Harald Nordgren via GitGitGadget, git,
Ramsay Jones, D. Ben Knoble, Kristoffer Haugsbakk, Marc Branchaud
In-Reply-To: <b8932b27-8006-4b43-b7e5-1fac0fbf42c7@gmail.com>
Interesting idea! I will think about it.
Harald
On Thu, May 21, 2026 at 11:49 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>
> On 19/05/2026 11:34, Junio C Hamano wrote:
> > "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
> >
> >> checkout: --track=fetch
> >>
> >> * Find the right remote by checking which remote's fetch refspec maps
> >> to the user's start-point, instead of assuming the start-point begins
> >> with the remote's name. This fixes cases where the user has a custom
> >> refspec mapping into a namespace whose name differs from the remote
> >> (e.g. fetching from origin into refs/remotes/upstream/*).
> >
> > This comment is even before looking at the patch text. After
> > getting one issue pointed out, I'd expect you to think about related
> > issues before sending a new round out.
> >
> > One. Have you considered the case where the remote-tracking refs
> > are overlapping, e.g., where "origin" and "upstream" point at
> > different URLs but they both store in "refs/remotes/upstream/*"?
> > Perhaps their URLs may textually be different but are pointing
> > logically at the same place (e.g., one ssh:// the other https:// for
> > example).
> >
> > What should happen? What does happen after you apply this patch?
>
> It would be worth looking at what "git checkout --track" does in that
> case and seeing if we can share the code.
>
> Thanks
>
> Phillip
>
> >
> >> * For a bare namespace name, follow <namespace>/HEAD first to figure
> >> out which branch to fetch.
> >
> > What should happen if HEAD does not exist? What does happen after
> > you apply this patch?
> >
> > Thanks.
> >
>
^ permalink raw reply
* [PATCH v5 1/4] approxidate: make "today" wrap to midnight
From: Tuomas Ahola @ 2026-05-21 10:54 UTC (permalink / raw)
To: git; +Cc: Jeff King, Junio C Hamano, Tuomas Ahola
In-Reply-To: <20260521105408.8222-1-taahol@utu.fi>
Although some commands do reject invalid approxidate expressions,
in other cases those are simply evaluated as the current time.
Oftentimes that is a perfectly good compromise to handle silly
requests, but it isn't without rough edges.
Because of the silent acceptance, it is easy to forget that
"today" isn't actually a valid approxidate format. That is
a bit awkward because while the fallback logic of using the
current time does make some sense, there is no deliberative
decision behind such behavior of "today". Indeed, whatever
(non-)action "today" currently has, is just an accidental
side effect.
That means "git log --since=today" is currently unlikely to
print anything at all as it tries to list commits dated with
*future* timestamps. Arguably it would be more useful to
list the commits of the current day---i.e. those made since
midnight.
On the other hand, "git log --until=today" doesn't really
filter commits at all. Changing the definition of "today"
would make it return the commits made before the current day.
That isn't without problems though---running "git log
--until=today" in the late afternoon could reasonably include
the work done earlier that day (as the command currently
does do).
Still the utility of no-op "--until=today" is debatable and
perhaps outweighed by the pros of having "--since=today" to
mean "--since=midnight". The thing is that the approxidate
machinery doesn't know about its consumers, so the meaning
of "today" has to be the same for "--since" and "--until".
In fact, "git log --until=" is documented as
`--until=<date>`::
`--before=<date>`::
Show commits older than _<date>_,
so excluding commits made today would actually match the
documentation more closely.
Moreover, a revision parameter "@{today}" is currently outright
rejected. Making "today" a valid approxidate time format could
make a natural way to specify the state of the ref at the start
of the current day.
Bind "today" to new function `date_today()` as an approxidate
special. Make it return the last midnight if no specific time
is given; i.e. retain the old behavior of "noon today" and such.
Document the new behavior of "git log --since=today" in
rev-list-options.adoc.
Signed-off-by: Tuomas Ahola <taahol@utu.fi>
---
Notes:
v4->v5: Add the usual `*num = 0` dance and a test to clarify why it's useful.
Documentation/rev-list-options.adoc | 3 ++-
date.c | 11 +++++++++++
t/t0006-date.sh | 3 +++
3 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc
index 2d195a1474..a5abadf689 100644
--- a/Documentation/rev-list-options.adoc
+++ b/Documentation/rev-list-options.adoc
@@ -23,7 +23,8 @@ ordering and formatting options, such as `--reverse`.
`--since=<date>`::
`--after=<date>`::
- Show commits more recent than _<date>_.
+ Show commits more recent than _<date>_. As a special case,
+ 'today' means the last midnight.
`--since-as-filter=<date>`::
Show all commits more recent than _<date>_. This visits
diff --git a/date.c b/date.c
index 17a95077cf..633d1176fe 100644
--- a/date.c
+++ b/date.c
@@ -1192,6 +1192,16 @@ static void date_never(struct tm *tm, struct tm *now UNUSED, int *num)
*num = 0;
}
+static void date_today(struct tm *tm, struct tm *now, int *num)
+{
+ if (tm->tm_hour == now->tm_hour &&
+ tm->tm_min == now->tm_min &&
+ tm->tm_sec == now->tm_sec)
+ date_time(tm, now, 0);
+ *num = 0;
+ update_tm(tm, now, 0);
+}
+
static const struct special {
const char *name;
void (*fn)(struct tm *, struct tm *, int *);
@@ -1204,6 +1214,7 @@ static const struct special {
{ "AM", date_am },
{ "never", date_never },
{ "now", date_now },
+ { "today", date_today },
{ NULL }
};
diff --git a/t/t0006-date.sh b/t/t0006-date.sh
index 53ced36df4..d95afdda33 100755
--- a/t/t0006-date.sh
+++ b/t/t0006-date.sh
@@ -164,6 +164,7 @@ check_approxidate() {
}
check_approxidate now '2009-08-30 19:20:00'
+check_approxidate today '2009-08-30 00:00:00'
check_approxidate '5 seconds ago' '2009-08-30 19:19:55'
check_approxidate 5.seconds.ago '2009-08-30 19:19:55'
check_approxidate 10.minutes.ago '2009-08-30 19:10:00'
@@ -181,12 +182,14 @@ check_approxidate '15:00' '2009-08-30 15:00:00'
check_approxidate 'noon today' '2009-08-30 12:00:00'
check_approxidate 'noon yesterday' '2009-08-29 12:00:00'
check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00'
+check_approxidate 'January 5th today pm' '2009-01-30 12:00:00'
check_approxidate '10am noon' '2009-08-29 12:00:00'
check_approxidate 'last tuesday' '2009-08-25 19:20:00'
check_approxidate 'July 5th' '2009-07-05 19:20:00'
check_approxidate '06/05/2009' '2009-06-05 19:20:00'
check_approxidate '06.05.2009' '2009-05-06 19:20:00'
+check_approxidate 'Jan 5 today' '2009-01-30 00:00:00'
check_approxidate 'Jun 6, 5AM' '2009-06-06 05:00:00'
check_approxidate '5AM Jun 6' '2009-06-06 05:00:00'
--
2.30.2
^ permalink raw reply related
* [PATCH v5 3/4] approxidate: make "specials" respect fixed day-of-month
From: Tuomas Ahola @ 2026-05-21 10:54 UTC (permalink / raw)
To: git; +Cc: Jeff King, Junio C Hamano, Tuomas Ahola
In-Reply-To: <20260521105408.8222-1-taahol@utu.fi>
The special approxidate time formats, "noon" and "tea" differ from
"12pm" and "5pm" by having the feature of wrapping to the previous day
if the current time is before those hours:
now -> 2026-05-13 11:00:00 +0000
12pm -> 2026-05-13 12:00:00 +0000
5pm -> 2026-05-13 17:00:00 +0000
noon -> 2026-05-12 12:00:00 +0000
tea -> 2026-05-12 17:00:00 +0000
However, that logic carries too far. Even when the date is specified,
the behavior of the "specials" depends on the current time. Assuming
the same time as above, we get:
today at noon -> 2026-05-12 12:00:00 +0000 (should be 13 May)
13 May at tea -> 2026-05-12 17:00:00 +0000
or, using an example mentioned in date-formats.adoc:
last Friday at noon -> 2026-05-07 12:00:00 +0000 (should be 8 May)
The quirk seems to be rather old. Already in 2006, Linus Torvalds
remarked that the date yielded by "one year ago yesterday at tea-time"
was "just silly and not even correct". Indeed, even today it gives:
One year ago yesterday at tea-time -> 2025-05-11 17:00:00 +0000
(should be 12 May)
Let's fix all of those with a simple patch. Check whether we already
have a specified day-of-month in `tm->tm_mday` and make `date_time()`
stick to it. Ensure the correct behavior with relevant tests.
Links:
1. https://lore.kernel.org/git/Pine.LNX.4.64.0610101102560.3952@g5.osdl.org/
Signed-off-by: Tuomas Ahola <taahol@utu.fi>
---
date.c | 6 +++++-
t/t0006-date.sh | 4 ++++
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/date.c b/date.c
index 633d1176fe..1e9cfe4b6f 100644
--- a/date.c
+++ b/date.c
@@ -1132,7 +1132,11 @@ static void date_yesterday(struct tm *tm, struct tm *now, int *num)
static void date_time(struct tm *tm, struct tm *now, int hour)
{
- if (tm->tm_hour < hour)
+ /*
+ * If we do not yet have a specified day, we'll use the most recent
+ * version of "hour" relative to now. But that may be yesterday.
+ */
+ if (tm->tm_mday < 0 && tm->tm_hour < hour)
update_tm(tm, now, 24*60*60);
tm->tm_hour = hour;
tm->tm_min = 0;
diff --git a/t/t0006-date.sh b/t/t0006-date.sh
index b9bb7a05d9..62cbada774 100755
--- a/t/t0006-date.sh
+++ b/t/t0006-date.sh
@@ -209,8 +209,12 @@ check_approxidate '6pm yesterday' '2009-08-29 18:00:00'
check_approxidate '3:00' '2009-08-30 03:00:00'
check_approxidate '15:00' '2009-08-30 15:00:00'
check_approxidate 'noon today' '2009-08-30 12:00:00'
+check_approxidate 'today at noon' '2009-08-30 12:00:00' '-12 hours'
check_approxidate 'noon yesterday' '2009-08-29 12:00:00'
+check_approxidate 'last Friday at noon' '2009-08-28 12:00:00'
+check_approxidate 'last Friday at noon' '2009-08-28 12:00:00' '-12 hours'
check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00'
+check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00' '-12 hours'
check_approxidate 'January 5th today pm' '2009-01-30 12:00:00'
check_approxidate '10am noon' '2009-08-29 12:00:00'
check_approxidate 'January 5th yesterday' '2009-01-29 19:20:00'
--
2.30.2
^ permalink raw reply related
* [PATCH v5 4/4] approxidate: use deferred mday adjustments for "specials"
From: Tuomas Ahola @ 2026-05-21 10:54 UTC (permalink / raw)
To: git; +Cc: Jeff King, Junio C Hamano, Tuomas Ahola
In-Reply-To: <20260521105408.8222-1-taahol@utu.fi>
There are cases where the "wrap-to-yesterday" behavior of "tea" and
"noon" should be reverted later on down the line, so that "today tea"
and "tea today" won't yield different results. However, the logic of
approxidate doesn't seem to lend itself particularly well to
such cases.
Start tackling the issue by reusing negative values of `tm->tm_mday`
field for deferred date adjustments which can be easily reverted, so
that the default logic of the special formats only applies if we don't
get any explicit date (mday) specification. In particular, overwrite
the field with -1 in "today" and "yesterday", so that those formats will
be relative to the current date. That makes specifications like "tea
yesterday" behave more sensibly: instead of going backwards to the
last tea-time and then a day back, Git will now understand that as the
tea-time of yesterday.
Replace the call of `update_tm()` in `date_time()` with the assignment
`tm->tm_mday = -2`. Add the corresponding code to handle that in
`update_tm()`, wrapping to the previous day if the field still holds
such assignment, meaning that we haven't seen any better specification
for the day-of-month. On the other hand, `mday=-3` would mean going
two days back and so on. Even though such functionality isn't
actually needed by this patch, it won't add much complexity in the
code and is rather natural way to handle such values.
As `date_time()` won't no longer need the `now` struct, mark the
associated function parameters as unused. The parameters themselves
have to stay, however, as those functions are called through pointers
in `approxidate_alpha`. Add relevant tests to cover the changes.
Signed-off-by: Tuomas Ahola <taahol@utu.fi>
---
Notes:
v4->v5: apply the earlier fixup patch
date.c | 31 +++++++++++++++++++++----------
t/t0006-date.sh | 4 ++++
2 files changed, 25 insertions(+), 10 deletions(-)
diff --git a/date.c b/date.c
index 1e9cfe4b6f..05b78d852f 100644
--- a/date.c
+++ b/date.c
@@ -1071,13 +1071,22 @@ void datestamp(struct strbuf *out)
/*
* Relative time update (eg "2 days ago"). If we haven't set the time
* yet, we need to set it from current time.
+ *
+ * The tm->tm_mday field has an additional logic of using negative values
+ * for date adjustments: -2 means yesterday and -3 the day before that,
+ * and so on. The idea is to deref such adjustments until we are sure
+ * there's no explicit mday specification in the approxidate string.
*/
static time_t update_tm(struct tm *tm, struct tm *now, time_t sec)
{
time_t n;
- if (tm->tm_mday < 0)
+ if (tm->tm_mday < 0) {
+ int offset = tm->tm_mday + 1;
+ if (sec == 0 && offset < 0)
+ sec = -offset * 24*60*60;
tm->tm_mday = now->tm_mday;
+ }
if (tm->tm_mon < 0)
tm->tm_mon = now->tm_mon;
if (tm->tm_year < 0) {
@@ -1127,38 +1136,39 @@ static void date_now(struct tm *tm, struct tm *now, int *num)
static void date_yesterday(struct tm *tm, struct tm *now, int *num)
{
*num = 0;
+ tm->tm_mday = -1;
update_tm(tm, now, 24*60*60);
}
-static void date_time(struct tm *tm, struct tm *now, int hour)
+static void date_time(struct tm *tm, int hour)
{
/*
* If we do not yet have a specified day, we'll use the most recent
* version of "hour" relative to now. But that may be yesterday.
*/
if (tm->tm_mday < 0 && tm->tm_hour < hour)
- update_tm(tm, now, 24*60*60);
+ tm->tm_mday = -2; /* eventually handled by update_tm() */
tm->tm_hour = hour;
tm->tm_min = 0;
tm->tm_sec = 0;
}
-static void date_midnight(struct tm *tm, struct tm *now, int *num)
+static void date_midnight(struct tm *tm, struct tm *now UNUSED, int *num)
{
pending_number(tm, num);
- date_time(tm, now, 0);
+ date_time(tm, 0);
}
-static void date_noon(struct tm *tm, struct tm *now, int *num)
+static void date_noon(struct tm *tm, struct tm *now UNUSED, int *num)
{
pending_number(tm, num);
- date_time(tm, now, 12);
+ date_time(tm, 12);
}
-static void date_tea(struct tm *tm, struct tm *now, int *num)
+static void date_tea(struct tm *tm, struct tm *now UNUSED, int *num)
{
pending_number(tm, num);
- date_time(tm, now, 17);
+ date_time(tm, 17);
}
static void date_pm(struct tm *tm, struct tm *now UNUSED, int *num)
@@ -1201,8 +1211,9 @@ static void date_today(struct tm *tm, struct tm *now, int *num)
if (tm->tm_hour == now->tm_hour &&
tm->tm_min == now->tm_min &&
tm->tm_sec == now->tm_sec)
- date_time(tm, now, 0);
+ date_time(tm, 0);
*num = 0;
+ tm->tm_mday = -1;
update_tm(tm, now, 0);
}
diff --git a/t/t0006-date.sh b/t/t0006-date.sh
index 62cbada774..9a76b84ed9 100755
--- a/t/t0006-date.sh
+++ b/t/t0006-date.sh
@@ -210,9 +210,13 @@ check_approxidate '3:00' '2009-08-30 03:00:00'
check_approxidate '15:00' '2009-08-30 15:00:00'
check_approxidate 'noon today' '2009-08-30 12:00:00'
check_approxidate 'today at noon' '2009-08-30 12:00:00' '-12 hours'
+check_approxidate 'noon today' '2009-09-01 12:00:00' '+36 hours'
check_approxidate 'noon yesterday' '2009-08-29 12:00:00'
+check_approxidate 'noon yesterday' '2009-08-29 12:00:00' '-12 hours'
check_approxidate 'last Friday at noon' '2009-08-28 12:00:00'
check_approxidate 'last Friday at noon' '2009-08-28 12:00:00' '-12 hours'
+check_approxidate 'tea last saturday' '2009-08-29 17:00:00'
+check_approxidate 'tea last saturday' '2009-08-29 17:00:00' '-12 hours'
check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00'
check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00' '-12 hours'
check_approxidate 'January 5th today pm' '2009-01-30 12:00:00'
--
2.30.2
^ permalink raw reply related
* [PATCH v5 2/4] t0006: add support for approxidate test date adjustment
From: Tuomas Ahola @ 2026-05-21 10:54 UTC (permalink / raw)
To: git; +Cc: Jeff King, Junio C Hamano, Tuomas Ahola
In-Reply-To: <20260521105408.8222-1-taahol@utu.fi>
t0006 uses a hard-coded test date and provides no convenient
way to override it temporarily. Add an optional parameter to
check_approxidate to adjust the time as needed, and demonstrate
the feature with a new test.
Signed-off-by: Tuomas Ahola <taahol@utu.fi>
---
t/t0006-date.sh | 33 ++++++++++++++++++++++++++++++++-
1 file changed, 32 insertions(+), 1 deletion(-)
diff --git a/t/t0006-date.sh b/t/t0006-date.sh
index d95afdda33..b9bb7a05d9 100755
--- a/t/t0006-date.sh
+++ b/t/t0006-date.sh
@@ -155,12 +155,41 @@ check_parse '2100-00-00 00:00:00 -11' bad
check_parse '2100-00-00 00:00:00 +11' bad
REQUIRE_64BIT_TIME=
+add_time_offset() {
+ case "$3" in
+ hours)
+ unit=$(( 60*60 ))
+ ;;
+ days)
+ unit=$(( 24*60*60 ))
+ ;;
+ esac
+ offset=$(( $2 * unit ))
+ echo $(( $1 + offset ))
+}
+
check_approxidate() {
+ old_date=$GIT_TEST_DATE_NOW
+ if test "$3" = "failure"
+ then
+ expection="$3"
+ else
+ expection=${4:-success}
+ offset="$3"
+ fi
+ if test -n "$offset"
+ then
+ GIT_TEST_DATE_NOW=$(add_time_offset $old_date $offset)
+ caption="$1; offset $offset"
+ else
+ caption=$1
+ fi
echo "$1 -> $2 +0000" >expect
- test_expect_${3:-success} "parse approxidate ($1)" "
+ test_expect_$expection "parse approxidate ($caption)" "
test-tool date approxidate '$1' >actual &&
test_cmp expect actual
"
+ GIT_TEST_DATE_NOW=$old_date
}
check_approxidate now '2009-08-30 19:20:00'
@@ -184,6 +213,8 @@ check_approxidate 'noon yesterday' '2009-08-29 12:00:00'
check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00'
check_approxidate 'January 5th today pm' '2009-01-30 12:00:00'
check_approxidate '10am noon' '2009-08-29 12:00:00'
+check_approxidate 'January 5th yesterday' '2009-01-29 19:20:00'
+check_approxidate 'January 5th yesterday' '2008-12-31 19:20:00' '+2 days'
check_approxidate 'last tuesday' '2009-08-25 19:20:00'
check_approxidate 'July 5th' '2009-07-05 19:20:00'
--
2.30.2
^ permalink raw reply related
* [PATCH v5 0/4] approxidate: tweak special date formats
From: Tuomas Ahola @ 2026-05-21 10:54 UTC (permalink / raw)
To: git; +Cc: Jeff King, Junio C Hamano, Tuomas Ahola
In-Reply-To: <20260516151540.9611-1-taahol@utu.fi>
> "Friday noon" asked in the morning on Sunday was parsed to be one
> day before the specified time, which has been corrected.
Should we add something about the new semantics of "today"?
Tuomas Ahola (4):
approxidate: make "today" wrap to midnight
t0006: add support for approxidate test date adjustment
approxidate: make "specials" respect fixed day-of-month
approxidate: use deferred mday adjustments for "specials"
Documentation/rev-list-options.adoc | 3 +-
date.c | 46 ++++++++++++++++++++++-------
t/t0006-date.sh | 44 ++++++++++++++++++++++++++-
3 files changed, 81 insertions(+), 12 deletions(-)
Interdiff mot v4:
diff --git a/date.c b/date.c
index 6e7cf907da..05b78d852f 100644
--- a/date.c
+++ b/date.c
@@ -1206,12 +1206,13 @@ static void date_never(struct tm *tm, struct tm *now UNUSED, int *num)
*num = 0;
}
-static void date_today(struct tm *tm, struct tm *now, int *num UNUSED)
+static void date_today(struct tm *tm, struct tm *now, int *num)
{
if (tm->tm_hour == now->tm_hour &&
tm->tm_min == now->tm_min &&
tm->tm_sec == now->tm_sec)
date_time(tm, 0);
+ *num = 0;
tm->tm_mday = -1;
update_tm(tm, now, 0);
}
diff --git a/t/t0006-date.sh b/t/t0006-date.sh
index b187b1bfc4..9a76b84ed9 100755
--- a/t/t0006-date.sh
+++ b/t/t0006-date.sh
@@ -212,13 +212,14 @@ check_approxidate 'noon today' '2009-08-30 12:00:00'
check_approxidate 'today at noon' '2009-08-30 12:00:00' '-12 hours'
check_approxidate 'noon today' '2009-09-01 12:00:00' '+36 hours'
check_approxidate 'noon yesterday' '2009-08-29 12:00:00'
+check_approxidate 'noon yesterday' '2009-08-29 12:00:00' '-12 hours'
check_approxidate 'last Friday at noon' '2009-08-28 12:00:00'
check_approxidate 'last Friday at noon' '2009-08-28 12:00:00' '-12 hours'
-check_approxidate 'noon yesterday' '2009-08-29 12:00:00' '-12 hours'
check_approxidate 'tea last saturday' '2009-08-29 17:00:00'
check_approxidate 'tea last saturday' '2009-08-29 17:00:00' '-12 hours'
check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00'
check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00' '-12 hours'
+check_approxidate 'January 5th today pm' '2009-01-30 12:00:00'
check_approxidate '10am noon' '2009-08-29 12:00:00'
check_approxidate 'January 5th yesterday' '2009-01-29 19:20:00'
check_approxidate 'January 5th yesterday' '2008-12-31 19:20:00' '+2 days'
base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0
--
2.30.2
^ permalink raw reply related
* Re: [PATCH 0/3] small quote.[ch] cleanup
From: Patrick Steinhardt @ 2026-05-21 11:39 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Jeff King, git
In-Reply-To: <xmqqjyt084sn.fsf@gitster.g>
On Tue, May 19, 2026 at 12:19:20PM +0900, Junio C Hamano wrote:
> Jeff King <peff@peff.net> writes:
>
> > I noticed some unused code while looking at an unrelated topic. So
> > here's a small cleanup.
> >
> > [1/3]: quote.h: bump strvec forward declaration to the top
> > [2/3]: quote: drop sq_dequote_to_argv()
> > [3/3]: quote: simplify internals of dequoting
> >
> > quote.c | 21 ++-------------------
> > quote.h | 14 ++++----------
> > 2 files changed, 6 insertions(+), 29 deletions(-)
> >
> > -Peff
>
> These were very straight-forward and pleasant to read. Queued.
> Thanks.
Indeed, all of these steps make sense to me. Thanks!
Patrick
^ permalink raw reply
* Re: [PATCH] http: handle absolute-path alternates from server root
From: Patrick Steinhardt @ 2026-05-21 11:50 UTC (permalink / raw)
To: Jeff King; +Cc: Junio C Hamano, git, slonkazoid
In-Reply-To: <20260515170134.GC88375@coredump.intra.peff.net>
On Fri, May 15, 2026 at 01:01:34PM -0400, Jeff King wrote:
> On Fri, May 15, 2026 at 09:41:06AM +0200, Patrick Steinhardt wrote:
>
> > > We talked about dropping it a few years ago, but Eric countered that
> > > dumb clones are easier on the server in some cases (like gigantic
> > > public-inbox repos that are packed to keep most of the old history in
> > > one big pack that is never updated). The verbatim pack-reuse feature
> > > tries to get smart clones closer to that, but it's hard to beat serving
> > > a static file from the server's perspective. I haven't measured anything
> > > in that area in a while, though.
> >
> > In theory we can get much closer with packfile URIs, too, can't we? If
> > the packfiles are directly accessible anyway the server could just
> > announce these directly and have the client fetch them. That should
> > significantly reduce the load on the server even further.
>
> Packfile URIs help with the actual pack generation (even if we're
> blitting out bits from the disk with verbatim packfile reuse, we still
> have to handle gaps and compute the checksum over the output pack).
>
> But it doesn't help with the server computing the set of objects the
> client needs in the first place. IIRC, packfile URIs work by the server
> saying "oh, I was going to send you object XYZ, but you can get it from
> this stable pack instead". So the server still has to compute the set of
> objects (and send any that are not mentioned in URI packs). Bitmaps
> help, but there's still non-trivial computation and storage on the
> server.
I guess it depends on the actual server-side implementation, but in the
general case this is of course true. A server could decide to for
example overserve objects in case the client does a full clone, or it
could arrange packfiles in a special way that allows it to serve at
least some kinds of requests efficiently.
> Contrast that with a client that instead pulls a packfile over dumb
> storage on its own, and then comes to the server for a top-off fetch.
> The server still has to do some computation, but it's usually quite
> small, because both sides agree quickly that there's no need to dig down
> further than the tips in that dumb packfile.
So this here is in theory possible with packfile URIs, as well, by
computing the top-off fetch depending on the packfile layout.
But this requires quite a bunch of server-side logic and very specific
layouts, I guess.
> > Of course, the big downside is that "fetch.uriProtocols" is empty by
> > default, so Git will not use them. Makes me wonder whether this is
> > something we want to eventually change, but I guess the current default
> > behaviour is somewhat insecure as it would allow the server to redirect
> > clients to arbitrary locations. It would be great if we had a mechanism
> > that only allowed packfile URIs that use the same host, which would make
> > this a lot more reasonable to enable by default.
>
> It's been a while since I've looked at it, but I seem to recall that the
> server-side tools for specifying which packfile URIs to use were not
> that mature. Maybe that has changed, though (I'm probably 5 years out of
> date since the last time I really thought about these things).
Packfile URIs definitely need some love to become feasible, yes, and I
don't think they have evolved much since their introduction. I still
feel like they are the better mechanism for offloading traffic compared
to bundle URIs though, as we already have packfiles around anyway.
Patrick
^ permalink raw reply
* Re: [PATCH v4 2/2] config: add "worktree" and "worktree/i" includeIf conditions
From: Patrick Steinhardt @ 2026-05-21 12:09 UTC (permalink / raw)
To: me; +Cc: git, Kristoffer Haugsbakk, Junio C Hamano, Phillip Wood
In-Reply-To: <20260513-includeif-worktree-v4-2-f8e6212d1fba@black-desk.cn>
On Wed, May 13, 2026 at 04:08:18PM +0800, Chen Linxuan via B4 Relay wrote:
> diff --git a/Documentation/config.adoc b/Documentation/config.adoc
> index 62eebe7c5450..6299b1e3a019 100644
> --- a/Documentation/config.adoc
> +++ b/Documentation/config.adoc
> @@ -146,6 +146,46 @@ refer to linkgit:gitignore[5] for details. For convenience:
> This is the same as `gitdir` except that matching is done
> case-insensitively (e.g. on case-insensitive file systems)
>
> +`worktree`::
> + The data that follows the keyword `worktree` and a colon is used as a
> + glob pattern. If the working directory of the current worktree matches
> + the pattern, the include condition is met.
> ++
> +The worktree location is the path where files are checked out (as returned
> +by `git rev-parse --show-toplevel`). This is different from `gitdir`, which
> +matches the `.git` directory path. In a linked worktree, the worktree path
> +is the directory where that worktree's files are located, not the main
> +repository's `.git` directory.
Nit: I feel like the first sentence already says it all, and the
remainder is not adding much value. But I'm probably also quite biased
given that I'm familiar with interals, so I don't insist on any change
here.
[snip]
> +While `extensions.worktreeConfig` (see linkgit:git-worktree[1]) also supports
> +per-worktree configuration, it stores the config inside each repository's
> +`.git/config.worktree` file and requires running `git config --worktree`
> +inside each worktree individually. In contrast, `includeIf "worktree:..."`
> +can be set once in a global or system-level configuration file (e.g.
> +`~/.config/git/config`) and applies to all repositories at once based on
> +their worktree location.
Nit: I tihnk saying "global or system-level" is totally sufficient,
there really is no need to explain where those files live.
But again, I'm not sure whether we need a new version for this change.
> diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
> index 6e51f892f320..07b6fb649cd2 100755
> --- a/t/t1305-config-include.sh
> +++ b/t/t1305-config-include.sh
> @@ -396,4 +396,117 @@ test_expect_success 'onbranch without repository but explicit nonexistent Git di
[snip]
> +test_expect_success 'conditional include, worktree, icase' '
> + git init wt-icase &&
> + (
> + cd wt-icase &&
> + test_commit initial &&
> + wt_path="$(pwd)" &&
> + wt_upper=$(echo "$wt_path" | tr a-z A-Z) &&
> + echo "[includeIf \"worktree/i:$wt_upper\"]path=icase-inc" >>.git/config &&
> + echo "[test]wticase=1" >.git/icase-inc &&
> + echo 1 >expect &&
> + git config test.wticase >actual &&
> + test_cmp expect actual
> + )
> +'
Ah, one more thing I didn't notice for the last version: it's good that
we have a check for the case-insensitive behaviour, but we're missing a
test that verifies that we're in fact case-sensitive by default. That
test would of course not work with a case-insensitive filesystem, but we
can depend on the `!CASE_INSENSITIVE_FS` prerequisite for that.
Other than that this series looks good to me, thanks!
Patrick
^ permalink raw reply
* Re: [PATCH v9 3/5] branch: add --prune-merged <remote>
From: Harald Nordgren @ 2026-05-21 12:37 UTC (permalink / raw)
To: phillip.wood
Cc: Harald Nordgren via GitGitGadget, git, Kristoffer Haugsbakk,
Johannes Sixt
In-Reply-To: <6501a3d5-a5ec-421b-8526-ee7d4ae5ea98@gmail.com>
> I think being able to prune branches that have been merged into their
> upstream is a good idea. However, I find the focus on remotes rather
> than upstream branches in the UI a bit confusing. While the upstream of
> a branch is often a remote tracking branch it doesn't have to be. For my
> personal projects I often do
>
> git checkout -b topic master
>
> and it would be nice to be able to run
>
> git branch --prune-merged master
>
> to clean up those topics that have been merged. Similarly I think it is
> confusing that
>
> git checkout -b topic origin
>
> starts a branch from the default branch on origin, but if I run
>
> git branch --prune-merged origin
>
> to clean it up, it will clean up all the branches with an upstream on
> origin, not just those whose upstream matches origin/HEAD.
>
> So I like the idea, but would prefer the arguments to --prune-merged to
> be upstream branches, not remotes. We could support globs so that
>
> git branch --prune-merges 'origin/*'
>
> would clean up all the branches whose upstream is on origin if that is
> useful.
>
> Thanks
>
> Phillip
Hi Phillip!
This seems like a big change. It almost becomes a different feature.
Would be interesting to hear what others have to say as well.
Harald
^ permalink raw reply
* Re: [PATCH v11] checkout: extend --track with a "fetch" mode to refresh start-point
From: Junio C Hamano @ 2026-05-21 12:58 UTC (permalink / raw)
To: Phillip Wood
Cc: Harald Nordgren via GitGitGadget, git, Ramsay Jones,
D. Ben Knoble, Kristoffer Haugsbakk, Marc Branchaud,
Harald Nordgren
In-Reply-To: <b8932b27-8006-4b43-b7e5-1fac0fbf42c7@gmail.com>
Phillip Wood <phillip.wood123@gmail.com> writes:
>> One. Have you considered the case where the remote-tracking refs
>> are overlapping, e.g., where "origin" and "upstream" point at
>> different URLs but they both store in "refs/remotes/upstream/*"?
>> Perhaps their URLs may textually be different but are pointing
>> logically at the same place (e.g., one ssh:// the other https:// for
>> example).
>>
>> What should happen? What does happen after you apply this patch?
>
> It would be worth looking at what "git checkout --track" does in that
> case and seeing if we can share the code.
It always is a good idea to think how we can share code for
different purposes to solve a new problem, but in this particular
one, I am not sure if "git checkout -t -b topic upstream/main"
codepath has much to offer to solve what the new "before the
checkout, update from the remote" feature wants to do. To the
former, it does not matter how refs/remotes/upstream/* are updated
and by fetching which remote at all. The only thing it cares about
is to leave the record that this new "topic" branch works with
refs/remotes/upstrea/main. But the latter needs to be able to
compute which remote it should fetch from. It is a problem that
existing code had no need to solve.
^ permalink raw reply
* Re: [PATCH v9 3/5] branch: add --prune-merged <remote>
From: Junio C Hamano @ 2026-05-21 13:29 UTC (permalink / raw)
To: Phillip Wood
Cc: Harald Nordgren via GitGitGadget, git, Kristoffer Haugsbakk,
Johannes Sixt, Harald Nordgren
In-Reply-To: <6501a3d5-a5ec-421b-8526-ee7d4ae5ea98@gmail.com>
Phillip Wood <phillip.wood123@gmail.com> writes:
> I think being able to prune branches that have been merged into their
> upstream is a good idea. However, I find the focus on remotes rather
> than upstream branches in the UI a bit confusing. While the upstream of
> a branch is often a remote tracking branch it doesn't have to be. For my
> personal projects I often do
>
> git checkout -b topic master
>
> and it would be nice to be able to run
>
> git branch --prune-merged master
>
> to clean up those topics that have been merged.
Excellent suggestion.
The task at hand is to make a list of things that "track" (in the
"checkout -t" sense) something, see which one in that list are
descendant of that something, and removing those that have already
been merged into that something. There is nothing that requires
that something to be a remote-tracking branch at all.
If
git branch --forked master
(i.e., "who tracks 'master' in the 'checkout -t' sense") were
available, then
git branch --merged master | sort >1
git branch --forked master | sort >2
comm -12 1 2
would give us the list of branches that forked from 'master' and
have already been merged, i.e., candidate to be removed via the
"prune-merged" mechanism. With the built-in "git branch -d"
protection to require capital "-D" to force removal of a new work
that is not merged to tracked, we actually need only
git branch --forked master |
xargs git branch -d
to do so. We may want to give --dry-run to "git branch -d" so that
you can say
git branch --forked master |
xargs git branch -d --dry-run
to see what would be removed.
And again, there is no strong reason why "--forked" has to work only
with remote-tracking branches.
> Similarly I think it is
> confusing that
>
> git checkout -b topic origin
>
> starts a branch from the default branch on origin, but if I run
>
> git branch --prune-merged origin
Sorry, this was my bad. I agree that it is not a useful thing for
"origin" to mean "origin/*" here when it is established that
"origin" to mean "origin/HEAD".
^ permalink raw reply
* Re: [PATCH v5 0/4] approxidate: tweak special date formats
From: Junio C Hamano @ 2026-05-21 13:33 UTC (permalink / raw)
To: Tuomas Ahola; +Cc: git, Jeff King
In-Reply-To: <20260521105408.8222-1-taahol@utu.fi>
Tuomas Ahola <taahol@utu.fi> writes:
> -static void date_today(struct tm *tm, struct tm *now, int *num UNUSED)
> +static void date_today(struct tm *tm, struct tm *now, int *num)
> {
> if (tm->tm_hour == now->tm_hour &&
> tm->tm_min == now->tm_min &&
> tm->tm_sec == now->tm_sec)
> date_time(tm, 0);
> + *num = 0;
> tm->tm_mday = -1;
> update_tm(tm, now, 0);
> }
Hmph, what is this change about? Does the lack of this clearing
break some test?
In any case, will queue. It seems that we are getting to the point
of diminishing returns and better off declaring victory soonish?
> diff --git a/t/t0006-date.sh b/t/t0006-date.sh
> index b187b1bfc4..9a76b84ed9 100755
> --- a/t/t0006-date.sh
> +++ b/t/t0006-date.sh
> @@ -212,13 +212,14 @@ check_approxidate 'noon today' '2009-08-30 12:00:00'
> check_approxidate 'today at noon' '2009-08-30 12:00:00' '-12 hours'
> check_approxidate 'noon today' '2009-09-01 12:00:00' '+36 hours'
> check_approxidate 'noon yesterday' '2009-08-29 12:00:00'
> +check_approxidate 'noon yesterday' '2009-08-29 12:00:00' '-12 hours'
> check_approxidate 'last Friday at noon' '2009-08-28 12:00:00'
> check_approxidate 'last Friday at noon' '2009-08-28 12:00:00' '-12 hours'
> -check_approxidate 'noon yesterday' '2009-08-29 12:00:00' '-12 hours'
> check_approxidate 'tea last saturday' '2009-08-29 17:00:00'
> check_approxidate 'tea last saturday' '2009-08-29 17:00:00' '-12 hours'
> check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00'
> check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00' '-12 hours'
> +check_approxidate 'January 5th today pm' '2009-01-30 12:00:00'
> check_approxidate '10am noon' '2009-08-29 12:00:00'
> check_approxidate 'January 5th yesterday' '2009-01-29 19:20:00'
> check_approxidate 'January 5th yesterday' '2008-12-31 19:20:00' '+2 days'
>
> base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0
^ permalink raw reply
* [PATCH v3] git-jump: pick a mode automatically when invoked without arguments
From: Greg Hurrell via GitGitGadget @ 2026-05-21 13:45 UTC (permalink / raw)
To: git
Cc: Jeff King, Greg Hurrell, Erik Cervin Edin, Junio C Hamano,
Greg Hurrell, Greg Hurrell
In-Reply-To: <pull.2108.v2.git.1779280307112.gitgitgadget@gmail.com>
From: Greg Hurrell <greg.hurrell@datadoghq.com>
When `git jump` is invoked with no positional arguments (and no
arguments after `--stdout`) it currently prints usage and exits with
status 1.
But there are two situations where we can usefully infer the most
valuable and likely mode that a user would want to use, and select it
automatically:
1. When there are unmerged paths in the index, the user likely
wants `git jump merge`.
2. When the working tree has unstaged changes, the user likely
wants `git jump diff`.
In this commit we teach `git jump` a new "auto" mode which detects these
cases and dispatches to the corresponding mode automatically. The user
can either explicitly spell out `git jump auto`, or just leave it at
`git jump` (because "auto" is the default).
If none of the interesting cases listed above applies, then auto mode
falls back to the existing usage-and-exit behavior.
Signed-off-by: Greg Hurrell <greg.hurrell@datadoghq.com>
---
git-jump: pick a mode automatically when invoked without arguments
Changes since v2; all of these in response to feedback from Junio:
* Removed stray # from README.
* Don't both teaching "auto" to select "ws" mode, because it is always
subsumed by "diff".
* Update usage string to make clear that git jump --stdout foo is not a
synonym for git jump --stdout auto foo, because distinguishing
between foo as <mode> and foo as <arg> is fraught with ambiguity.
In answer to Junio's question:
> If more than one interesting cases apply, what happens, and what
> should happen?
it's an ordered choice (merge > diff).
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2108%2Fwincent%2Fauto-jump-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2108/wincent/auto-jump-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/2108
Range-diff vs v2:
1: 5fbc8480ef ! 1: af758dcfd2 git-jump: pick a mode automatically when invoked without arguments
@@ Commit message
arguments after `--stdout`) it currently prints usage and exits with
status 1.
- But there are several situations where we can usefully infer the most
+ But there are two situations where we can usefully infer the most
valuable and likely mode that a user would want to use, and select it
automatically:
@@ Commit message
2. When the working tree has unstaged changes, the user likely
wants `git jump diff`.
- 3. In the presence of conflict markers or whitespace errors (as reported
- by `git diff --check`), the user likely wants `git jump ws`.
-
In this commit we teach `git jump` a new "auto" mode which detects these
cases and dispatches to the corresponding mode automatically. The user
can either explicitly spell out `git jump auto`, or just leave it at
@@ contrib/git-jump/README: git jump grep foo_bar
git jump grep -i foo_bar
+# jump to places with conflict markers or whitespace errors
-+# (as reported by # `git diff --check`)
++# (as reported by `git diff --check`)
+git jump ws
+
# use the silver searcher for git jump grep
@@ contrib/git-jump/README: git jump grep foo_bar
+# whitespace problems; otherwise show usage
+git jump auto
+
-+# with no explicit mode, same as "auto"
++# with no explicit mode and no args, same as "auto"
+git jump
--------------------------------------------------
@@ contrib/git-jump/README: git jump grep foo_bar
## contrib/git-jump/git-jump ##
@@
-
usage() {
cat <<\EOF
--usage: git jump [--stdout] <mode> [<args>]
-+usage: git jump [--stdout] [<mode>] [<args>]
+ usage: git jump [--stdout] <mode> [<args>]
++ or: git jump [--stdout]
Jump to interesting elements in an editor.
-The <mode> parameter is one of:
-+The <mode> parameter is one of the following,
-+defaulting to "auto" if omitted:
++The <mode> parameter is one of the following.
++With no <mode> and no <args>, it defaults to "auto".
diff: elements are diff hunks. Arguments are given to diff.
@@ contrib/git-jump/git-jump: mode_ws() {
+ mode_merge "$@"
+ elif ! git diff --quiet "$@"; then
+ mode_diff "$@"
-+ elif ! git diff --check >/dev/null 2>&1; then
-+ mode_ws "$@"
+ else
+ usage >&2
+ exit 1
contrib/git-jump/README | 12 ++++++++++++
contrib/git-jump/git-jump | 26 +++++++++++++++++++++++---
2 files changed, 35 insertions(+), 3 deletions(-)
diff --git a/contrib/git-jump/README b/contrib/git-jump/README
index 3211841305..aabec4a756 100644
--- a/contrib/git-jump/README
+++ b/contrib/git-jump/README
@@ -75,8 +75,20 @@ git jump grep foo_bar
# arbitrary grep options
git jump grep -i foo_bar
+# jump to places with conflict markers or whitespace errors
+# (as reported by `git diff --check`)
+git jump ws
+
# use the silver searcher for git jump grep
git config jump.grepCmd "ag --column"
+
+# pick a mode automatically: "merge" if there are unmerged paths,
+# "diff" if the worktree has unstaged changes, "ws" if there are
+# whitespace problems; otherwise show usage
+git jump auto
+
+# with no explicit mode and no args, same as "auto"
+git jump
--------------------------------------------------
You can use the optional argument '--stdout' to print the listing to
diff --git a/contrib/git-jump/git-jump b/contrib/git-jump/git-jump
index 8d1d5d79a6..79286d8112 100755
--- a/contrib/git-jump/git-jump
+++ b/contrib/git-jump/git-jump
@@ -3,9 +3,11 @@
usage() {
cat <<\EOF
usage: git jump [--stdout] <mode> [<args>]
+ or: git jump [--stdout]
Jump to interesting elements in an editor.
-The <mode> parameter is one of:
+The <mode> parameter is one of the following.
+With no <mode> and no <args>, it defaults to "auto".
diff: elements are diff hunks. Arguments are given to diff.
@@ -16,6 +18,10 @@ grep: elements are grep hits. Arguments are given to git grep or, if
ws: elements are whitespace errors. Arguments are given to diff --check.
+auto: select one of the other modes based on worktree state;
+ "merge" if there are unmerged paths, "diff" if there are
+ unstaged changes, "ws" if there are whitespace errors.
+
If the optional argument `--stdout` is given, print the quickfix
lines to standard output instead of feeding it to the editor.
EOF
@@ -82,6 +88,21 @@ mode_ws() {
git diff --check "$@"
}
+mode_auto() {
+ if test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" != "true"; then
+ usage >&2
+ exit 1
+ fi
+ if test -n "$(git ls-files -u "$@")"; then
+ mode_merge "$@"
+ elif ! git diff --quiet "$@"; then
+ mode_diff "$@"
+ else
+ usage >&2
+ exit 1
+ fi
+}
+
use_stdout=
while test $# -gt 0; do
case "$1" in
@@ -99,8 +120,7 @@ while test $# -gt 0; do
shift
done
if test $# -lt 1; then
- usage >&2
- exit 1
+ set -- auto
fi
mode=$1; shift
type "mode_$mode" >/dev/null 2>&1 || { usage >&2; exit 1; }
base-commit: aec3f587505a472db67e9462d0702e7d463a449d
--
gitgitgadget
^ permalink raw reply related
* Re: [PATCH v3] git-jump: pick a mode automatically when invoked without arguments
From: Junio C Hamano @ 2026-05-21 14:00 UTC (permalink / raw)
To: Greg Hurrell via GitGitGadget
Cc: git, Jeff King, Greg Hurrell, Erik Cervin Edin, Greg Hurrell
In-Reply-To: <pull.2108.v3.git.1779371110195.gitgitgadget@gmail.com>
"Greg Hurrell via GitGitGadget" <gitgitgadget@gmail.com> writes:
> * Removed stray # from README.
> * Don't both teaching "auto" to select "ws" mode, because it is always
> subsumed by "diff".
> * Update usage string to make clear that git jump --stdout foo is not a
> synonym for git jump --stdout auto foo, because distinguishing
> between foo as <mode> and foo as <arg> is fraught with ambiguity.
>
> In answer to Junio's question:
>
> > If more than one interesting cases apply, what happens, and what
> > should happen?
>
> it's an ordered choice (merge > diff).
After 'diff --quiet "$@"' says "nothing interesting between the
index and the working tree", I actually think it may be worth using
either 'git diff --check HEAD "$@"' or 'git diff --cached --check "$@"'
to see if ws fix is needed.
But I am not a target audience of this feature, so I'll let others
figure out what to do here.
^ permalink raw reply
* [PATCH v4] remote: qualify "git pull" advice for non-upstream compareBranches
From: Harald Nordgren via GitGitGadget @ 2026-05-21 14:06 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
In-Reply-To: <pull.2301.v3.git.git.1779282625696.gitgitgadget@gmail.com>
From: Harald Nordgren <haraldnordgren@gmail.com>
Enable ENABLE_ADVICE_PULL for push-branch comparisons too, not just
the upstream entry, so the "use git pull" hint prints when the local
branch is behind its push branch.
Spell out "git pull <remote> <branch>" so running the suggested
command actually pulls the ref the user was told about; plain
"git pull" would fetch the upstream instead.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote: qualify "git pull" advice for non-upstream branches
* Don't suggest git pull when we have no good command to suggest.
* New test for this. Asserts the behind line shows with no follow-up
advice.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2301%2FHaraldNordgren%2Fstatus-pull-advice-qualified-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2301/HaraldNordgren/status-pull-advice-qualified-v4
Pull-Request: https://github.com/git/git/pull/2301
Range-diff vs v3:
1: 3703be9aac ! 1: ef54dacb07 remote: qualify "git pull" advice for non-upstream compareBranches
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
full_ref = resolve_compare_branch(branch,
branches.items[i].string);
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
- if (reported)
- strbuf_addstr(sb, "\n");
-- if (is_upstream)
-+ if (is_upstream || is_push)
+ if (is_upstream)
flags |= ENABLE_ADVICE_PULL;
- if (is_push)
- flags |= ENABLE_ADVICE_PUSH;
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ if (push_remote_name &&
+ skip_prefix(full_ref, "refs/remotes/", &push_branch_name) &&
+ skip_prefix(push_branch_name, push_remote_name, &push_branch_name) &&
-+ *push_branch_name == '/')
++ *push_branch_name == '/') {
+ push_branch_name++;
-+ else
++ flags |= ENABLE_ADVICE_PULL;
++ } else {
+ push_remote_name = NULL;
++ }
++ } else {
++ flags |= ENABLE_ADVICE_PULL;
+ }
+ }
format_branch_comparison(sb, !cmp, ours, theirs, short_ref,
@@ t/t6040-tracking-info.sh: test_expect_success 'status.compareBranches with remap
+ EOF
+ test_cmp expect actual
+'
++
++test_expect_success 'status.compareBranches suppresses advice when push tracking ref is unconventional' '
++ test_config -C test push.default current &&
++ test_config -C test remote.imported.url ../. &&
++ test_config -C test remote.imported.fetch "+refs/heads/*:refs/imported/imported/*" &&
++ test_config -C test branch.feature17.pushRemote imported &&
++ test_config -C test status.compareBranches "@{push}" &&
++ git -C test fetch imported &&
++ git -C test checkout --no-track -b feature17 refs/imported/imported/main &&
++ (cd test && advance work17) &&
++ git -C test push imported HEAD:feature17 &&
++ git -C test fetch imported &&
++ git -C test reset --hard HEAD^ &&
++ git -C test status >actual &&
++ cat >expect <<-EOF &&
++ On branch feature17
++ Your branch is behind ${SQ}imported/imported/feature17${SQ} by 1 commit, and can be fast-forwarded.
++
++ nothing to commit, working tree clean
++ EOF
++ test_cmp expect actual
++'
+
test_done
remote.c | 48 +++++++++++++++----
t/t6040-tracking-info.sh | 100 +++++++++++++++++++++++++++++++++++++++
2 files changed, 140 insertions(+), 8 deletions(-)
diff --git a/remote.c b/remote.c
index 24a8118d25..193e1dd1f1 100644
--- a/remote.c
+++ b/remote.c
@@ -2268,6 +2268,8 @@ static void format_branch_comparison(struct strbuf *sb,
bool up_to_date,
int ours, int theirs,
const char *branch_name,
+ const char *push_remote_name,
+ const char *push_branch_name,
enum ahead_behind_flags abf,
unsigned flags)
{
@@ -2303,9 +2305,15 @@ static void format_branch_comparison(struct strbuf *sb,
"and can be fast-forwarded.\n",
theirs),
branch_name, theirs);
- if (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git pull\" to update your local branch)\n"));
+ if (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS)) {
+ if (push_remote_name && push_branch_name)
+ strbuf_addf(sb,
+ _(" (use \"git pull %s %s\" to update your local branch)\n"),
+ push_remote_name, push_branch_name);
+ else
+ strbuf_addstr(sb,
+ _(" (use \"git pull\" to update your local branch)\n"));
+ }
} else {
strbuf_addf(sb,
Q_("Your branch and '%s' have diverged,\n"
@@ -2316,9 +2324,15 @@ static void format_branch_comparison(struct strbuf *sb,
"respectively.\n",
ours + theirs),
branch_name, ours, theirs);
- if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+ if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS)) {
+ if (push_remote_name && push_branch_name)
+ strbuf_addf(sb,
+ _(" (use \"git pull %s %s\" if you want to integrate the remote branch with yours)\n"),
+ push_remote_name, push_branch_name);
+ else
+ strbuf_addstr(sb,
+ _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+ }
}
}
@@ -2356,6 +2370,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
int ours, theirs, cmp;
int is_upstream, is_push;
unsigned flags = 0;
+ const char *push_remote_name = NULL;
+ const char *push_branch_name = NULL;
full_ref = resolve_compare_branch(branch,
branches.items[i].string);
@@ -2399,11 +2415,27 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
if (is_upstream)
flags |= ENABLE_ADVICE_PULL;
- if (is_push)
- flags |= ENABLE_ADVICE_PUSH;
if (show_divergence_advice && is_upstream)
flags |= ENABLE_ADVICE_DIVERGENCE;
+ if (is_push) {
+ flags |= ENABLE_ADVICE_PUSH;
+ if (!upstream_ref || strcmp(upstream_ref, full_ref)) {
+ push_remote_name = pushremote_for_branch(branch, NULL);
+ if (push_remote_name &&
+ skip_prefix(full_ref, "refs/remotes/", &push_branch_name) &&
+ skip_prefix(push_branch_name, push_remote_name, &push_branch_name) &&
+ *push_branch_name == '/') {
+ push_branch_name++;
+ flags |= ENABLE_ADVICE_PULL;
+ } else {
+ push_remote_name = NULL;
+ }
+ } else {
+ flags |= ENABLE_ADVICE_PULL;
+ }
+ }
format_branch_comparison(sb, !cmp, ours, theirs, short_ref,
+ push_remote_name, push_branch_name,
abf, flags);
reported = 1;
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 0242b5bf7a..91cbb8775d 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -646,4 +646,104 @@ test_expect_success 'status.compareBranches with remapped push and upstream remo
test_cmp expect actual
'
+test_expect_success 'status.compareBranches behind both upstream and push' '
+ test_config -C test push.default current &&
+ test_config -C test remote.pushDefault origin &&
+ test_config -C test status.compareBranches "@{upstream} @{push}" &&
+ git -C test checkout -b feature13 upstream/main &&
+ (cd test && advance work13) &&
+ git -C test push origin &&
+ git -C test branch --set-upstream-to upstream/ahead &&
+ git -C test reset --hard HEAD^ &&
+ git -C test status >actual &&
+ cat >expect <<-EOF &&
+ On branch feature13
+ Your branch is behind ${SQ}upstream/ahead${SQ} by 1 commit, and can be fast-forwarded.
+ (use "git pull" to update your local branch)
+
+ Your branch is behind ${SQ}origin/feature13${SQ} by 1 commit, and can be fast-forwarded.
+ (use "git pull origin feature13" to update your local branch)
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches with remapped push and behind push branch' '
+ test_config -C test remote.pushDefault origin &&
+ test_config -C test remote.origin.push refs/heads/feature14:refs/heads/remapped14 &&
+ test_config -C test status.compareBranches "@{push}" &&
+ git -C test checkout -b feature14 upstream/main &&
+ (cd test && advance work14) &&
+ git -C test push &&
+ git -C test reset --hard HEAD^ &&
+ git -C test status >actual &&
+ cat >expect <<-EOF &&
+ On branch feature14
+ Your branch is behind ${SQ}origin/remapped14${SQ} by 1 commit, and can be fast-forwarded.
+ (use "git pull origin remapped14" to update your local branch)
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches with behind push branch and no upstream' '
+ test_config -C test push.default current &&
+ test_config -C test remote.pushDefault origin &&
+ test_config -C test status.compareBranches "@{push}" &&
+ git -C test checkout --no-track -b feature15 upstream/main &&
+ (cd test && advance work15) &&
+ git -C test push origin &&
+ git -C test reset --hard HEAD^ &&
+ git -C test status >actual &&
+ cat >expect <<-EOF &&
+ On branch feature15
+ Your branch is behind ${SQ}origin/feature15${SQ} by 1 commit, and can be fast-forwarded.
+ (use "git pull origin feature15" to update your local branch)
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches behind upstream-equals-push suggests plain pull' '
+ test_config -C test status.compareBranches "@{upstream} @{push}" &&
+ git -C test checkout -b feature16 origin/main &&
+ (cd test && advance work16) &&
+ git -C test push origin HEAD:main &&
+ git -C test reset --hard HEAD^ &&
+ git -C test status >actual &&
+ cat >expect <<-EOF &&
+ On branch feature16
+ Your branch is behind ${SQ}origin/main${SQ} by 1 commit, and can be fast-forwarded.
+ (use "git pull" to update your local branch)
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches suppresses advice when push tracking ref is unconventional' '
+ test_config -C test push.default current &&
+ test_config -C test remote.imported.url ../. &&
+ test_config -C test remote.imported.fetch "+refs/heads/*:refs/imported/imported/*" &&
+ test_config -C test branch.feature17.pushRemote imported &&
+ test_config -C test status.compareBranches "@{push}" &&
+ git -C test fetch imported &&
+ git -C test checkout --no-track -b feature17 refs/imported/imported/main &&
+ (cd test && advance work17) &&
+ git -C test push imported HEAD:feature17 &&
+ git -C test fetch imported &&
+ git -C test reset --hard HEAD^ &&
+ git -C test status >actual &&
+ cat >expect <<-EOF &&
+ On branch feature17
+ Your branch is behind ${SQ}imported/imported/feature17${SQ} by 1 commit, and can be fast-forwarded.
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
test_done
base-commit: aec3f587505a472db67e9462d0702e7d463a449d
--
gitgitgadget
^ permalink raw reply related
* Re: [PATCH v11] checkout: extend --track with a "fetch" mode to refresh start-point
From: Phillip Wood @ 2026-05-21 14:06 UTC (permalink / raw)
To: Junio C Hamano
Cc: Harald Nordgren via GitGitGadget, git, Ramsay Jones,
D. Ben Knoble, Kristoffer Haugsbakk, Marc Branchaud,
Harald Nordgren
In-Reply-To: <xmqqtss02a2o.fsf@gitster.g>
On 21/05/2026 13:58, Junio C Hamano wrote:
> Phillip Wood <phillip.wood123@gmail.com> writes:
>
>>> One. Have you considered the case where the remote-tracking refs
>>> are overlapping, e.g., where "origin" and "upstream" point at
>>> different URLs but they both store in "refs/remotes/upstream/*"?
>>> Perhaps their URLs may textually be different but are pointing
>>> logically at the same place (e.g., one ssh:// the other https:// for
>>> example).
>>>
>>> What should happen? What does happen after you apply this patch?
>>
>> It would be worth looking at what "git checkout --track" does in that
>> case and seeing if we can share the code.
>
> It always is a good idea to think how we can share code for
> different purposes to solve a new problem, but in this particular
> one, I am not sure if "git checkout -t -b topic upstream/main"
> codepath has much to offer to solve what the new "before the
> checkout, update from the remote" feature wants to do. To the
> former, it does not matter how refs/remotes/upstream/* are updated
> and by fetching which remote at all.
Don't we want to avoid creating a branch with an ambiguous upstream so
that a subsequent "git pull" works though? Looking at
branch.c:setup_tracking() it seems to reject upstream branches that
match more than one remote.
Thanks
Phillip
> The only thing it cares about
> is to leave the record that this new "topic" branch works with
> refs/remotes/upstrea/main. But the latter needs to be able to
> compute which remote it should fetch from. It is a problem that
> existing code had no need to solve.
>
^ permalink raw reply
* Re: [PATCH v5 0/4] approxidate: tweak special date formats
From: Tuomas Ahola @ 2026-05-21 14:14 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Jeff King
In-Reply-To: <xmqqik8g28gw.fsf@gitster.g>
Junio C Hamano <gitster@pobox.com> wrote:
> Tuomas Ahola <taahol@utu.fi> writes:
>
> > -static void date_today(struct tm *tm, struct tm *now, int *num UNUSED)
> > +static void date_today(struct tm *tm, struct tm *now, int *num)
> > {
> > if (tm->tm_hour == now->tm_hour &&
> > tm->tm_min == now->tm_min &&
> > tm->tm_sec == now->tm_sec)
> > date_time(tm, 0);
> > + *num = 0;
> > tm->tm_mday = -1;
> > update_tm(tm, now, 0);
> > }
>
> Hmph, what is this change about? Does the lack of this clearing
> break some test?
>
As you can see, many other date_*() functions have that same assignment, too.
It looked a bit off, so I first left it out, but this revision does add a
corner case test[*] that would fail without it.
> In any case, will queue. It seems that we are getting to the point
> of diminishing returns and better off declaring victory soonish?
>
Yes, I agree. The patch series is effectively finished as I don't have any
further itches or ideas. And I think the series gets its work done quite well
actually.
> > +check_approxidate 'January 5th today pm' '2009-01-30 12:00:00'
[*] Namely, this one.
^ permalink raw reply
* Re: [PATCH 4/9] run-command: add support for timeout in command finisher
From: Johannes Sixt @ 2026-05-21 14:36 UTC (permalink / raw)
To: Siddh Raman Pant
Cc: git@vger.kernel.org, gitster@pobox.com, newren@gmail.com,
ps@pks.im, oswald.buddenhagen@gmx.de, code@khaugsbakk.name
In-Reply-To: <2f7eea03273ffaacc50a9ae186673da88fc3345f.camel@oracle.com>
Am 21.05.26 um 11:59 schrieb Siddh Raman Pant:
> The timeout is for the failure path, where the external helper has
> already stopped following that protocol or is blocked on something
> outside git's control. Since git starts the helper and puts it on the
> log/grep path, git also needs a bounded way to recover when that helper
> does not make progress. Otherwise an optional note source can prevent
> the main git command from completing.
That Git communicates with a process that looks like it stopped is the
normal case, for example:
- Output is sent to the pager. The user can take their time to study the
output. All the while, git waits patiently for the user to advance the
pager.
- Git fetch transfers large amounts of data across the network. Most of
the time it waits for data to arrive and does nothing. The peer process
looks like it hangs. Git does not decide to kill the connection at any
time. It is the user's decision to do so.
If the notes provider hangs, then it is not on Git to decide when it has
waited long enough.
-- Hannes
^ permalink raw reply
* Re: [PATCH 03/18] odb/source-loose: start converting to a proper `struct odb_source`
From: Junio C Hamano @ 2026-05-21 15:49 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git
In-Reply-To: <20260521-b4-pks-odb-source-loose-v1-3-6553b399be2d@pks.im>
Patrick Steinhardt <ps@pks.im> writes:
> Start converting `struct odb_source_loose` into a proper pluggable
> `struct odb_source` by embedding the base struct and assigning it the
> new `ODB_SOURCE_LOOSE` type. Furthermore, wire up lifecycle management
> of this source by implementing the `free` callback and taking ownership
> of the chdir notifications.
>
> Note that the loose source is not yet functional as a standalone `struct
> odb_source`, as it's missing all of the callback implementations. These
> will be wired up in subsequent commits.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> object-file.c | 17 -----------------
> object-file.h | 2 --
> odb/source-files.c | 2 +-
> odb/source-loose.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
> odb/source-loose.h | 14 ++++++++++++++
> odb/source.h | 3 +++
> 6 files changed, 63 insertions(+), 20 deletions(-)
>
> diff --git a/object-file.c b/object-file.c
> index 7a1908bfc0..977d959d33 100644
> --- a/object-file.c
> +++ b/object-file.c
> @@ -2041,14 +2041,6 @@ static struct oidtree *odb_source_loose_cache(struct odb_source *source,
> return files->loose->cache;
> }
>
> -static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
> -{
> - oidtree_clear(loose->cache);
> - FREE_AND_NULL(loose->cache);
> - memset(&loose->subdir_seen, 0,
> - sizeof(loose->subdir_seen));
> -}
> -
> void odb_source_loose_reprepare(struct odb_source *source)
> {
> struct odb_source_files *files = odb_source_files_downcast(source);
> @@ -2205,15 +2197,6 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)
> return &transaction->base;
> }
>
> -void odb_source_loose_free(struct odb_source_loose *loose)
> -{
> - if (!loose)
> - return;
> - odb_source_loose_clear_cache(loose);
> - loose_object_map_clear(&loose->map);
> - free(loose);
> -}
> -
> struct odb_loose_read_stream {
> struct odb_read_stream base;
> git_zstream z;
> diff --git a/object-file.h b/object-file.h
> index 1d8312cf7f..02c9680980 100644
> --- a/object-file.h
> +++ b/object-file.h
> @@ -21,8 +21,6 @@ struct object_info;
> struct odb_read_stream;
> struct odb_source;
>
> -void odb_source_loose_free(struct odb_source_loose *loose);
> -
> /* Reprepare the loose source by emptying the loose object cache. */
> void odb_source_loose_reprepare(struct odb_source *source);
>
> diff --git a/odb/source-files.c b/odb/source-files.c
> index 185cc6903e..ccc637311b 100644
> --- a/odb/source-files.c
> +++ b/odb/source-files.c
> @@ -27,7 +27,7 @@ static void odb_source_files_free(struct odb_source *source)
> {
> struct odb_source_files *files = odb_source_files_downcast(source);
> chdir_notify_unregister(NULL, odb_source_files_reparent, files);
> - odb_source_loose_free(files->loose);
> + odb_source_free(&files->loose->base);
> packfile_store_free(files->packed);
> odb_source_release(&files->base);
> free(files);
> diff --git a/odb/source-loose.c b/odb/source-loose.c
> index c9e7414814..92e18f5adb 100644
> --- a/odb/source-loose.c
> +++ b/odb/source-loose.c
> @@ -1,10 +1,55 @@
> #include "git-compat-util.h"
> +#include "abspath.h"
> +#include "chdir-notify.h"
> +#include "loose.h"
> +#include "odb.h"
> +#include "odb/source-files.h"
> #include "odb/source-loose.h"
> +#include "oidtree.h"
> +
> +void odb_source_loose_clear_cache(struct odb_source_loose *loose)
> +{
> + oidtree_clear(loose->cache);
> + FREE_AND_NULL(loose->cache);
> + memset(&loose->subdir_seen, 0,
> + sizeof(loose->subdir_seen));
> +}
> +
> +static void odb_source_loose_reparent(const char *name UNUSED,
> + const char *old_cwd,
> + const char *new_cwd,
> + void *cb_data)
> +{
> + struct odb_source_loose *loose = cb_data;
> + char *path = reparent_relative_path(old_cwd, new_cwd,
> + loose->base.path);
> + free(loose->base.path);
> + loose->base.path = path;
> +}
> +
> +static void odb_source_loose_free(struct odb_source *source)
> +{
> + struct odb_source_loose *loose = odb_source_loose_downcast(source);
> + odb_source_loose_clear_cache(loose);
> + loose_object_map_clear(&loose->map);
> + chdir_notify_unregister(NULL, odb_source_loose_reparent, loose);
> + odb_source_release(&loose->base);
> + free(loose);
> +}
>
> struct odb_source_loose *odb_source_loose_new(struct odb_source_files *files)
> {
> struct odb_source_loose *loose;
> +
> CALLOC_ARRAY(loose, 1);
> + odb_source_init(&loose->base, files->base.odb, ODB_SOURCE_LOOSE,
> + files->base.path, files->base.local);
> loose->files = files;
> +
> + loose->base.free = odb_source_loose_free;
> +
> + if (!is_absolute_path(loose->base.path))
> + chdir_notify_register(NULL, odb_source_loose_reparent, loose);
> +
> return loose;
> }
> diff --git a/odb/source-loose.h b/odb/source-loose.h
> index bf61e767c8..441da9e418 100644
> --- a/odb/source-loose.h
> +++ b/odb/source-loose.h
> @@ -12,6 +12,7 @@ struct oidtree;
> * file per object. This source is part of the files source.
> */
> struct odb_source_loose {
> + struct odb_source base;
> struct odb_source_files *files;
>
> /*
> @@ -32,4 +33,17 @@ struct odb_source_loose {
>
> struct odb_source_loose *odb_source_loose_new(struct odb_source_files *files);
>
> +/*
> + * Cast the given object database source to the loose backend. This will cause
> + * a BUG in case the source uses doesn't use this backend.
"uses doesn't use"???
> + */
> +static inline struct odb_source_loose *odb_source_loose_downcast(struct odb_source *source)
> +{
> + if (source->type != ODB_SOURCE_LOOSE)
> + BUG("trying to downcast source of type '%d' to loose", source->type);
> + return container_of(source, struct odb_source_loose, base);
> +}
> +
> +void odb_source_loose_clear_cache(struct odb_source_loose *loose);
> +
> #endif
> diff --git a/odb/source.h b/odb/source.h
> index 0a440884e4..8bcb67787e 100644
> --- a/odb/source.h
> +++ b/odb/source.h
> @@ -14,6 +14,9 @@ enum odb_source_type {
> /* The "files" backend that uses loose objects and packfiles. */
> ODB_SOURCE_FILES,
>
> + /* The "loose" backend that uses loose objects, only. */
> + ODB_SOURCE_LOOSE,
> +
> /* The "in-memory" backend that stores objects in memory. */
> ODB_SOURCE_INMEMORY,
> };
^ permalink raw reply
* [PATCH 0/4] doc: hook: small improvements
From: kristofferhaugsbakk @ 2026-05-21 16:25 UTC (permalink / raw)
To: git; +Cc: Kristoffer Haugsbakk, jn.avila, adrian.ratiu
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
Topic name: kh/doc-hook
Topic summary: Small improvements to git-hook(1) and the associated config.
[1/4] doc: hook: remove stray backtick
[2/4] doc: hook: consistently capitalize Git
[3/4] doc: config: include existing git-hook(1) section
[4/4] doc: hook: don’t self-link via config include
Documentation/config.adoc | 2 ++
Documentation/config/hook.adoc | 19 +++++++++++++------
Documentation/git-hook.adoc | 11 ++++++-----
3 files changed, 21 insertions(+), 11 deletions(-)
base-commit: aec3f587505a472db67e9462d0702e7d463a449d
--
2.54.0.13.g9c7419e39f8
^ permalink raw reply
* [PATCH 1/4] doc: hook: remove stray backtick
From: kristofferhaugsbakk @ 2026-05-21 16:25 UTC (permalink / raw)
To: git; +Cc: Kristoffer Haugsbakk, jn.avila, adrian.ratiu
In-Reply-To: <CV_doc_hook.6f0@msgid.xyz>
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Documentation/git-hook.adoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Documentation/git-hook.adoc b/Documentation/git-hook.adoc
index 46ea52db55f..96c5c9c3c23 100644
--- a/Documentation/git-hook.adoc
+++ b/Documentation/git-hook.adoc
@@ -41,7 +41,7 @@ spell-checker for your commit messages, you would write a configuration like so:
With this config, when you run 'git commit', first `~/bin/linter --cpp20` will
have a chance to check your files to be committed (during the `pre-commit` hook
-event`), and then `~/bin/spellchecker` will have a chance to check your commit
+event), and then `~/bin/spellchecker` will have a chance to check your commit
message (during the `commit-msg` hook event).
Commands are run in the order Git encounters their associated
--
2.54.0.13.g9c7419e39f8
^ permalink raw reply related
* [PATCH 2/4] doc: hook: consistently capitalize Git
From: kristofferhaugsbakk @ 2026-05-21 16:25 UTC (permalink / raw)
To: git; +Cc: Kristoffer Haugsbakk, jn.avila, adrian.ratiu
In-Reply-To: <CV_doc_hook.6f0@msgid.xyz>
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Documentation/git-hook.adoc | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Documentation/git-hook.adoc b/Documentation/git-hook.adoc
index 96c5c9c3c23..750df58e58e 100644
--- a/Documentation/git-hook.adoc
+++ b/Documentation/git-hook.adoc
@@ -3,7 +3,7 @@ git-hook(1)
NAME
----
-git-hook - Run git hooks
+git-hook - Run Git hooks
SYNOPSIS
--------
@@ -15,8 +15,8 @@ SYNOPSIS
DESCRIPTION
-----------
-A command interface for running git hooks (see linkgit:githooks[5]),
-for use by other scripted git commands.
+A command interface for running Git hooks (see linkgit:githooks[5]),
+for use by other scripted Git commands.
This command parses the default configuration files for sets of configs like
so:
@@ -161,7 +161,7 @@ setting, allowing all hooks for the event to run concurrently, even if they
are not individually marked as parallel.
+
Some hooks always run sequentially regardless of this flag or the
-`hook.jobs` config, because git knows they cannot safely run in parallel:
+`hook.jobs` config, because Git knows they cannot safely run in parallel:
`applypatch-msg`, `pre-commit`, `prepare-commit-msg`, `commit-msg`,
`post-commit`, `post-checkout`, and `push-to-checkout`.
--
2.54.0.13.g9c7419e39f8
^ permalink raw reply related
* [PATCH 3/4] doc: config: include existing git-hook(1) section
From: kristofferhaugsbakk @ 2026-05-21 16:25 UTC (permalink / raw)
To: git; +Cc: Kristoffer Haugsbakk, jn.avila, adrian.ratiu
In-Reply-To: <CV_doc_hook.6f0@msgid.xyz>
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
It is already included in git-hook(1) but missing from git-config(1).
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Documentation/config.adoc | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/config.adoc b/Documentation/config.adoc
index dcea3c0c15e..a80e7db46d9 100644
--- a/Documentation/config.adoc
+++ b/Documentation/config.adoc
@@ -451,6 +451,8 @@ include::config/guitool.adoc[]
include::config/help.adoc[]
+include::config/hook.adoc[]
+
include::config/http.adoc[]
include::config/i18n.adoc[]
--
2.54.0.13.g9c7419e39f8
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox