Thanks to visit codestin.com
Credit goes to Github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Documentation/config/checkout.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ commands or functionality in the future.
option in `git checkout` and `git switch`. See
linkgit:git-switch[1] and linkgit:git-checkout[1].

`checkout.remoteBranchTemplate`::
Template pattern applied to remote ref names during checkout DWIM
and when pushing with `push.autoSetupRemote`. Uses `%s` for the
branch name and `%%` for a literal `%`.
+
With `feature/%s`, `git checkout <branch>` searches for `origin/feature/<branch>`.
+
Useful when remote branches expects specific prefixes (e.g., `feature/`, `user/`).
Invalid templates (missing `%s`) trigger a warning and are ignored.

`checkout.workers`::
The number of parallel workers to use when updating the working tree.
The default is one, i.e. sequential execution. If set to a value less
Expand Down
3 changes: 3 additions & 0 deletions Documentation/config/push.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
to be set. Workflows most likely to benefit from this option are
`simple` central workflows where all branches are expected to
have the same name on the remote.
+
When combined with `checkout.remoteBranchTemplate`, creates remote
branches using the templated name.

`push.default`::
Defines the action `git push` should take if no refspec is
Expand Down
23 changes: 23 additions & 0 deletions builtin/push.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "builtin.h"
#include "advice.h"
#include "branch.h"
#include "checkout.h"
#include "config.h"
#include "environment.h"
#include "gettext.h"
Expand Down Expand Up @@ -281,6 +282,28 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)
if ((*flags & TRANSPORT_PUSH_AUTO_UPSTREAM) && branch->merge_nr == 0)
*flags |= TRANSPORT_PUSH_SET_UPSTREAM;

/*
* Apply template to destination ref if configured and we're setting
* up upstream (either automatically or explicitly with -u).
*/
if (branch->merge_nr == 0 && (*flags & (TRANSPORT_PUSH_SET_UPSTREAM | TRANSPORT_PUSH_AUTO_UPSTREAM))) {
const char *short_name;
char *templated_dst = NULL;

if (skip_prefix(dst, "refs/heads/", &short_name)) {
char *template_result = expand_remote_branch_template(short_name);
if (template_result) {
templated_dst = xstrfmt("refs/heads/%s", template_result);
free(template_result);
dst = templated_dst;
}
}

refspec_appendf(&rs, "%s:%s", branch->refname, dst);
free(templated_dst);
return;
}

refspec_appendf(&rs, "%s:%s", branch->refname, dst);
}

Expand Down
7 changes: 7 additions & 0 deletions builtin/worktree.c
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,13 @@ static char *dwim_branch(const char *path, char **new_branch)
if (guess_remote) {
struct object_id oid;
char *remote = unique_tracking_name(*new_branch, &oid, NULL);
char *templated_name = expand_remote_branch_template(*new_branch);

if (!remote && templated_name) {
die(_("No remote branch found for '%s' (set to '%s' by checkout.remoteBranchTemplate)"),
*new_branch, templated_name);
}
free(templated_name);
return remote;
}
return NULL;
Expand Down
49 changes: 48 additions & 1 deletion checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "checkout.h"
#include "config.h"
#include "strbuf.h"
#include "gettext.h"

struct tracking_name_data {
/* const */ char *src_ref;
Expand Down Expand Up @@ -52,14 +53,23 @@ char *unique_tracking_name(const char *name, struct object_id *oid,
{
struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
const char *default_remote = NULL;
char *templated_name = NULL;
const char *search_name;

if (!repo_config_get_string_tmp(the_repository, "checkout.defaultremote", &default_remote))
cb_data.default_remote = default_remote;
cb_data.src_ref = xstrfmt("refs/heads/%s", name);

templated_name = expand_remote_branch_template(name);
search_name = templated_name ? templated_name : name;

cb_data.src_ref = xstrfmt("refs/heads/%s", search_name);
cb_data.dst_oid = oid;
for_each_remote(check_tracking_name, &cb_data);
if (dwim_remotes_matched)
*dwim_remotes_matched = cb_data.num_matches;
free(cb_data.src_ref);
free(templated_name);

if (cb_data.num_matches == 1) {
free(cb_data.default_dst_ref);
free(cb_data.default_dst_oid);
Expand All @@ -73,3 +83,40 @@ char *unique_tracking_name(const char *name, struct object_id *oid,
}
return NULL;
}

char *expand_remote_branch_template(const char *name)
{
const char *tpl = NULL;
const char *fmt;
struct strbuf out = STRBUF_INIT;
int saw_placeholder = 0;

if (repo_config_get_string_tmp(the_repository,
"checkout.remoteBranchTemplate",
&tpl))
return NULL;

fmt = tpl;
while (strbuf_expand_step(&out, &fmt)) {
if (skip_prefix(fmt, "%", &fmt)) {
strbuf_addch(&out, '%');
} else if (skip_prefix(fmt, "s", &fmt)) {
strbuf_addstr(&out, name);
saw_placeholder = 1;
} else {
/*
* Unknown placeholder: keep '%' literal to avoid
* surprising behavior (e.g., "%x" stays "%x").
*/
strbuf_addch(&out, '%');
}
}

if (!saw_placeholder) {
strbuf_release(&out);
warning("%s", _("checkout.remoteBranchTemplate missing '%%s' placeholder; ignoring"));
return NULL;
}

return strbuf_detach(&out, NULL);
}
9 changes: 9 additions & 0 deletions checkout.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@

#include "hash.h"

/*
* If checkout.remoteBranchTemplate is set, expand it using printf-style
* substitution:
* %s -> the branch name
* %% -> a literal %
* Returns a newly allocated string, or NULL if unset/invalid.
*/
char *expand_remote_branch_template(const char *name);

/*
* Check if the branch name uniquely matches a branch name on a remote
* tracking branch. Return the name of the remote if such a branch
Expand Down
3 changes: 3 additions & 0 deletions t/t1402-check-ref-format.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ invalid_ref 'foo.lock/bar'
invalid_ref 'foo.lock///bar'
valid_ref 'heads/foo@bar'
invalid_ref 'heads/v@{ation'
valid_ref 'heads/foo%bar'
valid_ref 'heads/foo%s'
valid_ref 'heads/100%special'
invalid_ref 'heads/foo\bar'
invalid_ref "$(printf 'heads/foo\t')"
invalid_ref "$(printf 'heads/foo\177')"
Expand Down
162 changes: 162 additions & 0 deletions t/t2024-checkout-dwim.sh
Original file line number Diff line number Diff line change
Expand Up @@ -347,4 +347,166 @@ test_expect_success 'disambiguate dwim branch and checkout path (2)' '
grep bar dwim-arg2
'

test_expect_success 'setup for remoteBranchTemplate tests' '
(
cd repo_a &&
git checkout -b feature/newbar &&
test_commit a_feature_newbar &&
git checkout -b "100%special" &&
test_commit a_special &&
git checkout -b template_test &&
test_commit a_template_test
) &&
git fetch repo_a
'

test_expect_success 'checkout.remoteBranchTemplate with prefix' '
git checkout -B main &&
git reset --hard &&
test_might_fail git branch -D newbar &&
test_config checkout.remoteBranchTemplate "feature/%s" &&

git checkout newbar &&
status_uno_is_clean &&
test_branch newbar &&
test_cmp_rev remotes/repo_a/feature/newbar HEAD &&
test_branch_upstream newbar repo_a feature/newbar
'

test_expect_success 'checkout.remoteBranchTemplate handles literal %%' '
git checkout -B main &&
git reset --hard &&
test_might_fail git branch -D special &&
test_config checkout.remoteBranchTemplate "100%%%s" &&

git checkout special &&
status_uno_is_clean &&
test_branch special &&
test_cmp_rev remotes/repo_a/100%special HEAD &&
test_branch_upstream special repo_a 100%special
'

test_expect_success 'checkout.remoteBranchTemplate without %s is ignored with warning' '
git checkout -B main &&
git reset --hard &&
test_might_fail git branch -D template_test &&
test_config checkout.remoteBranchTemplate "fixed-name" &&

git checkout template_test 2>stderr &&
status_uno_is_clean &&
test_branch template_test &&
test_cmp_rev remotes/repo_a/template_test HEAD &&
test_grep "missing.*%s.*placeholder" stderr
'

test_expect_success 'checkout.remoteBranchTemplate with multiple %s placeholders' '
(
cd repo_a &&
git checkout -b user/multi/multi &&
test_commit a_multi_placeholder
) &&
git fetch repo_a &&

git checkout -B main &&
git reset --hard &&
test_might_fail git branch -D multi &&
test_config checkout.remoteBranchTemplate "user/%s/%s" &&

git checkout multi &&
status_uno_is_clean &&
test_branch multi &&
test_cmp_rev remotes/repo_a/user/multi/multi HEAD &&
test_branch_upstream multi repo_a user/multi/multi
'

test_expect_success 'checkout.remoteBranchTemplate + defaultRemote resolves ambiguity' '
(
cd repo_a &&
git checkout -b team/shared &&
test_commit a_team_shared
) &&
(
cd repo_b &&
git checkout -b team/shared &&
test_commit b_team_shared
) &&
git fetch --multiple repo_a repo_b &&

git checkout -B main &&
git reset --hard &&
test_might_fail git branch -D shared &&
test_config checkout.remoteBranchTemplate "team/%s" &&
test_config checkout.defaultRemote repo_b &&

git checkout shared &&
status_uno_is_clean &&
test_branch shared &&
test_cmp_rev remotes/other_b/team/shared HEAD &&
test_branch_upstream shared repo_b team/shared
'

test_expect_success 'checkout.remoteBranchTemplate with no matches fails' '
git checkout -B main &&
test_might_fail git branch -D nonexistent &&
test_config checkout.remoteBranchTemplate "feature/%s" &&

test_must_fail git checkout nonexistent &&
test_must_fail git rev-parse --verify refs/heads/nonexistent &&
test_branch main
'

test_expect_success 'checkout.remoteBranchTemplate still fails on ambiguity' '
git checkout -B main &&
test_might_fail git branch -D shared &&
test_config checkout.remoteBranchTemplate "team/%s" &&

test_must_fail git checkout shared 2>stderr &&
test_grep "matched multiple.*remote tracking branches" stderr &&
test_must_fail git rev-parse --verify refs/heads/shared &&
test_branch main
'

test_expect_success 'checkout.remoteBranchTemplate respects --no-guess' '
git checkout -B main &&
git reset --hard &&
test_might_fail git branch -D newbar &&
test_config checkout.remoteBranchTemplate "feature/%s" &&

test_must_fail git checkout --no-guess newbar &&
test_must_fail git rev-parse --verify refs/heads/newbar &&
test_branch main
'

test_expect_success 'checkout.remoteBranchTemplate respects checkout.guess = false' '
git checkout -B main &&
git reset --hard &&
test_might_fail git branch -D newbar &&
test_config checkout.remoteBranchTemplate "feature/%s" &&
test_config checkout.guess false &&

test_must_fail git checkout newbar &&
test_must_fail git rev-parse --verify refs/heads/newbar &&
test_branch main
'

test_expect_success 'checkout.remoteBranchTemplate with unknown placeholder kept literal' '
(
cd repo_a &&
git checkout -b "%d/literal" &&
test_commit a_literal_placeholder
) &&
git fetch repo_a &&

git checkout -B main &&
git reset --hard &&
test_might_fail git branch -D literal &&
test_config checkout.remoteBranchTemplate "%d/%s" &&

git checkout literal &&
status_uno_is_clean &&
test_branch literal &&
test_cmp_rev remotes/repo_a/%d/literal HEAD &&
test_branch_upstream literal repo_a %d/literal
'

test_done
Loading
Loading