diff --git a/branch.c b/branch.c index 22f4f46b96ed3a..27be5ecc3ed9bb 100644 --- a/branch.c +++ b/branch.c @@ -249,12 +249,13 @@ static int inherit_tracking(struct tracking *tracking, const char *orig_ref) * will not be expanded to "refs/remotes/origin/main", so it is not safe * for 'orig_ref' to be raw user input. */ -static void setup_tracking(const char *new_ref, const char *orig_ref, - enum branch_track track, int quiet) +static int setup_tracking(const char *new_ref, const char *orig_ref, + enum branch_track track, int quiet) { struct tracking tracking; struct string_list tracking_srcs = STRING_LIST_INIT_DUP; int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE; + int ret = 0; struct find_tracked_branch_cb ftb_cb = { .tracking = &tracking, .ambiguous_remotes = STRING_LIST_INIT_DUP, @@ -343,11 +344,12 @@ static void setup_tracking(const char *new_ref, const char *orig_ref, string_list_append(tracking.srcs, orig_ref); if (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote, tracking.srcs) < 0) - exit(1); + ret = -1; cleanup: string_list_clear(&tracking_srcs, 0); string_list_clear(&ftb_cb.ambiguous_remotes, 0); + return ret; } int read_branch_desc(struct strbuf *buf, const char *branch_name) @@ -627,10 +629,10 @@ static void dwim_branch_start(struct repository *r, const char *start_name, FREE_AND_NULL(real_ref); } -void create_branch(struct repository *r, - const char *name, const char *start_name, - int force, int clobber_head_ok, int reflog, - int quiet, enum branch_track track, int dry_run) +int create_branch(struct repository *r, + const char *name, const char *start_name, + int force, int clobber_head_ok, int reflog, + int quiet, enum branch_track track, int dry_run) { struct object_id oid; char *real_ref; @@ -639,6 +641,7 @@ void create_branch(struct repository *r, struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; int flags = 0; + int ret = 0; char *msg; if (track == BRANCH_TRACK_OVERRIDE) @@ -676,21 +679,25 @@ void create_branch(struct repository *r, free(msg); if (real_ref && track) - setup_tracking(ref.buf + 11, real_ref, track, quiet); + ret = setup_tracking(ref.buf + 11, real_ref, track, quiet); cleanup: strbuf_release(&ref); free(real_ref); + return ret; } -void dwim_and_setup_tracking(struct repository *r, const char *new_ref, - const char *orig_ref, enum branch_track track, - int quiet) +int dwim_and_setup_tracking(struct repository *r, const char *new_ref, + const char *orig_ref, enum branch_track track, + int quiet) { char *real_orig_ref = NULL; + int ret; + dwim_branch_start(r, orig_ref, track, &real_orig_ref, NULL); - setup_tracking(new_ref, real_orig_ref, track, quiet); + ret = setup_tracking(new_ref, real_orig_ref, track, quiet); free(real_orig_ref); + return ret; } /** @@ -840,8 +847,8 @@ void create_branches_recursively(struct repository *r, const char *name, * tedious to determine whether or not tracking was set up in the * superproject. */ - if (track) - setup_tracking(name, tracking_name, track, quiet); + if (track && setup_tracking(name, tracking_name, track, quiet)) + exit(1); for (i = 0; i < submodule_entry_list.entry_nr; i++) { if (submodule_create_branch( diff --git a/branch.h b/branch.h index e9b1f7b37df06f..4bc4527284e23b 100644 --- a/branch.h +++ b/branch.h @@ -34,10 +34,13 @@ enum branch_track { * causes new_ref to track whatever orig_ref tracks. * * - quiet suppresses tracking information. + * + * Returns 0 on success, or -1 if the tracking configuration could not be + * written. */ -void dwim_and_setup_tracking(struct repository *r, const char *new_ref, - const char *orig_ref, enum branch_track track, - int quiet); +int dwim_and_setup_tracking(struct repository *r, const char *new_ref, + const char *orig_ref, enum branch_track track, + int quiet); /* * Creates a new branch, where: @@ -63,12 +66,14 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref, * * - dry_run causes the branch to be validated but not created. * + * Returns 0 on success, or -1 if the tracking configuration could not be + * written. */ -void create_branch(struct repository *r, - const char *name, const char *start_name, - int force, int clobber_head_ok, - int reflog, int quiet, enum branch_track track, - int dry_run); +int create_branch(struct repository *r, + const char *name, const char *start_name, + int force, int clobber_head_ok, + int reflog, int quiet, enum branch_track track, + int dry_run); /* * Creates a new branch in a repository and its submodules (and its diff --git a/builtin/branch.c b/builtin/branch.c index a613148fc72786..b536b619bb03ca 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -1253,9 +1253,13 @@ int cmd_branch(int argc, die(_("branch '%s' does not exist"), branch->name); } - dwim_and_setup_tracking(the_repository, branch->name, - new_upstream, BRANCH_TRACK_OVERRIDE, - quiet); + if (dwim_and_setup_tracking(the_repository, branch->name, + new_upstream, BRANCH_TRACK_OVERRIDE, + quiet)) { + strbuf_release(&buf); + ret = 1; + goto out; + } strbuf_release(&buf); } else if (unset_upstream) { struct branch *branch; @@ -1305,8 +1309,11 @@ int cmd_branch(int argc, ret = 0; goto out; } - create_branch(the_repository, branch_name, start_name, force, 0, - reflog, quiet, track, 0); + if (create_branch(the_repository, branch_name, start_name, + force, 0, reflog, quiet, track, 0)) { + ret = 1; + goto out; + } } else usage_with_options(builtin_branch_usage, options); diff --git a/builtin/checkout.c b/builtin/checkout.c index 2d575a4f57f76e..c5e2202026ef80 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -979,6 +979,12 @@ static void update_refs_for_switch(const struct checkout_opts *opts, free(refname); } else + /* + * Not fatal: the error and hint have already been + * shown, and like "push -u" and "clone" we finish + * the switch instead of leaving HEAD on the old + * branch with the worktree already updated. + */ create_branch(the_repository, opts->new_branch, new_branch_info->name, opts->new_branch_force ? 1 : 0, diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh index c01f1cd617c72b..c3be183bfcc411 100755 --- a/t/t2027-checkout-track.sh +++ b/t/t2027-checkout-track.sh @@ -47,6 +47,26 @@ test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' ' test_cmp_config refs/heads/main branch.b4.merge ' +test_expect_success 'checkout -b completes when tracking config cannot be written' ' + test_when_finished "git update-ref -d refs/remotes/lockremote/lockbranch" && + test_when_finished "git branch -D lock-new" && + test_when_finished "git checkout main" && + test_config remote.lockremote.url . && + test_config remote.lockremote.fetch "+refs/heads/*:refs/remotes/lockremote/*" && + git update-ref refs/remotes/lockremote/lockbranch one && + test_when_finished "rm -f .git/config.lock" && + >.git/config.lock && + git -c core.configLockTimeout=0 \ + checkout -b lock-new lockremote/lockbranch 2>err && + test_grep "unable to write upstream branch configuration" err && + echo refs/heads/lock-new >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual && + git diff --cached --exit-code && + rm -f .git/config.lock && + test_must_fail git config branch.lock-new.remote +' + test_expect_success 'ambiguous tracking info' ' # Set up a few remote repositories git init --bare --initial-branch=trunk src1 && diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh index cec2fe6876c2fe..b4f137acc3b6fe 100755 --- a/t/t2060-switch.sh +++ b/t/t2060-switch.sh @@ -146,6 +146,26 @@ test_expect_success 'tracking info copied with autoSetupMerge=inherit' ' test_cmp_config "" --default "" branch.main2.merge ' +test_expect_success 'switch -c completes when tracking config cannot be written' ' + test_when_finished "git update-ref -d refs/remotes/lockremote/lockbranch" && + test_when_finished "git branch -D lock-new" && + test_when_finished "git switch main" && + test_config remote.lockremote.url . && + test_config remote.lockremote.fetch "+refs/heads/*:refs/remotes/lockremote/*" && + git update-ref refs/remotes/lockremote/lockbranch first-branch && + test_when_finished "rm -f .git/config.lock" && + >.git/config.lock && + git -c core.configLockTimeout=0 \ + switch -c lock-new lockremote/lockbranch 2>err && + test_grep "unable to write upstream branch configuration" err && + echo refs/heads/lock-new >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual && + git diff --cached --exit-code && + rm -f .git/config.lock && + test_must_fail git config branch.lock-new.remote +' + test_expect_success 'switch back when temporarily detached and checked out elsewhere ' ' test_when_finished " git worktree remove wt1 ||: diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index cdb6c6a6342a0c..ca1a1c7f867eb3 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -1080,6 +1080,19 @@ test_expect_success '--set-upstream-to fails on locked config' ' test_grep "could not lock config file .git/config" err ' +test_expect_success 'branch with start point fails when tracking config cannot be written' ' + test_when_finished "git branch -D lock-new || :" && + test_when_finished "git update-ref -d refs/remotes/lockremote/lockbranch" && + test_config remote.lockremote.url . && + test_config remote.lockremote.fetch "+refs/heads/*:refs/remotes/lockremote/*" && + git update-ref refs/remotes/lockremote/lockbranch HEAD && + test_when_finished "rm -f .git/config.lock" && + >.git/config.lock && + test_must_fail git -c core.configLockTimeout=0 \ + branch lock-new lockremote/lockbranch 2>err && + test_grep "unable to write upstream branch configuration" err +' + test_expect_success 'use --set-upstream-to modify HEAD' ' test_config branch.main.remote foo && test_config branch.main.merge foo &&