Browse Source

Merge branch 'ak/protect-any-current-branch'

"git fetch" without the "--update-head-ok" option ought to protect
a checked out branch from getting updated, to prevent the working
tree that checks it out to go out of sync.  The code was written
before the use of "git worktree" got widespread, and only checked
the branch that was checked out in the current worktree, which has
been updated.
(originally called ak/fetch-not-overwrite-any-current-branch)

* ak/protect-any-current-branch:
  branch: protect branches checked out in all worktrees
  receive-pack: protect current branch for bare repository worktree
  receive-pack: clean dead code from update_worktree()
  fetch: protect branches checked out in all worktrees
  worktree: simplify find_shared_symref() memory ownership model
  branch: lowercase error messages
  receive-pack: lowercase error messages
  fetch: lowercase error messages
maint
Junio C Hamano 3 years ago
parent
commit
13fa77b689
  1. 45
      branch.c
  2. 7
      builtin/branch.c
  3. 119
      builtin/fetch.c
  4. 6
      builtin/notes.c
  5. 92
      builtin/receive-pack.c
  6. 2
      t/t2018-checkout-branch.sh
  7. 11
      t/t3200-branch.sh
  8. 2
      t/t5504-fetch-receive-strict.sh
  9. 32
      t/t5516-fetch-push.sh
  10. 8
      worktree.c
  11. 5
      worktree.h

45
branch.c

@ -64,7 +64,7 @@ int install_branch_config(int flag, const char *local, const char *origin, const
if (skip_prefix(remote, "refs/heads/", &shortname) if (skip_prefix(remote, "refs/heads/", &shortname)
&& !strcmp(local, shortname) && !strcmp(local, shortname)
&& !origin) { && !origin) {
warning(_("Not setting branch %s as its own upstream."), warning(_("not setting branch %s as its own upstream"),
local); local);
return 0; return 0;
} }
@ -116,7 +116,7 @@ int install_branch_config(int flag, const char *local, const char *origin, const


out_err: out_err:
strbuf_release(&key); strbuf_release(&key);
error(_("Unable to write upstream branch configuration")); error(_("unable to write upstream branch configuration"));


advise(_(tracking_advice), advise(_(tracking_advice),
origin ? origin : "", origin ? origin : "",
@ -153,7 +153,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
} }


if (tracking.matches > 1) if (tracking.matches > 1)
die(_("Not tracking: ambiguous information for ref %s"), die(_("not tracking: ambiguous information for ref %s"),
orig_ref); orig_ref);


if (install_branch_config(config_flags, new_ref, tracking.remote, if (install_branch_config(config_flags, new_ref, tracking.remote,
@ -186,7 +186,7 @@ int read_branch_desc(struct strbuf *buf, const char *branch_name)
int validate_branchname(const char *name, struct strbuf *ref) int validate_branchname(const char *name, struct strbuf *ref)
{ {
if (strbuf_check_branch_ref(ref, name)) if (strbuf_check_branch_ref(ref, name))
die(_("'%s' is not a valid branch name."), name); die(_("'%s' is not a valid branch name"), name);


return ref_exists(ref->buf); return ref_exists(ref->buf);
} }
@ -199,18 +199,23 @@ int validate_branchname(const char *name, struct strbuf *ref)
*/ */
int validate_new_branchname(const char *name, struct strbuf *ref, int force) int validate_new_branchname(const char *name, struct strbuf *ref, int force)
{ {
const char *head; struct worktree **worktrees;
const struct worktree *wt;


if (!validate_branchname(name, ref)) if (!validate_branchname(name, ref))
return 0; return 0;


if (!force) if (!force)
die(_("A branch named '%s' already exists."), die(_("a branch named '%s' already exists"),
ref->buf + strlen("refs/heads/")); ref->buf + strlen("refs/heads/"));


head = resolve_ref_unsafe("HEAD", 0, NULL, NULL); worktrees = get_worktrees();
if (!is_bare_repository() && head && !strcmp(head, ref->buf)) wt = find_shared_symref(worktrees, "HEAD", ref->buf);
die(_("Cannot force update the current branch.")); if (wt && !wt->is_bare)
die(_("cannot force update the branch '%s'"
"checked out at '%s'"),
ref->buf + strlen("refs/heads/"), wt->path);
free_worktrees(worktrees);


return 1; return 1;
} }
@ -230,7 +235,7 @@ static int validate_remote_tracking_branch(char *ref)
} }


static const char upstream_not_branch[] = static const char upstream_not_branch[] =
N_("Cannot setup tracking information; starting point '%s' is not a branch."); N_("cannot set up tracking information; starting point '%s' is not a branch");
static const char upstream_missing[] = static const char upstream_missing[] =
N_("the requested upstream branch '%s' does not exist"); N_("the requested upstream branch '%s' does not exist");
static const char upstream_advice[] = static const char upstream_advice[] =
@ -278,7 +283,7 @@ void create_branch(struct repository *r,
} }
die(_(upstream_missing), start_name); die(_(upstream_missing), start_name);
} }
die(_("Not a valid object name: '%s'."), start_name); die(_("not a valid object name: '%s'"), start_name);
} }


switch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref, 0)) { switch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref, 0)) {
@ -298,12 +303,12 @@ void create_branch(struct repository *r,
} }
break; break;
default: default:
die(_("Ambiguous object name: '%s'."), start_name); die(_("ambiguous object name: '%s'"), start_name);
break; break;
} }


if ((commit = lookup_commit_reference(r, &oid)) == NULL) if ((commit = lookup_commit_reference(r, &oid)) == NULL)
die(_("Not a valid branch point: '%s'."), start_name); die(_("not a valid branch point: '%s'"), start_name);
oidcpy(&oid, &commit->object.oid); oidcpy(&oid, &commit->object.oid);


if (reflog) if (reflog)
@ -357,14 +362,16 @@ void remove_branch_state(struct repository *r, int verbose)


void die_if_checked_out(const char *branch, int ignore_current_worktree) void die_if_checked_out(const char *branch, int ignore_current_worktree)
{ {
struct worktree **worktrees = get_worktrees();
const struct worktree *wt; const struct worktree *wt;


wt = find_shared_symref("HEAD", branch); wt = find_shared_symref(worktrees, "HEAD", branch);
if (!wt || (ignore_current_worktree && wt->is_current)) if (wt && (!ignore_current_worktree || !wt->is_current)) {
return; skip_prefix(branch, "refs/heads/", &branch);
skip_prefix(branch, "refs/heads/", &branch); die(_("'%s' is already checked out at '%s'"), branch, wt->path);
die(_("'%s' is already checked out at '%s'"), }
branch, wt->path);
free_worktrees(worktrees);
} }


int replace_each_worktree_head_symref(const char *oldref, const char *newref, int replace_each_worktree_head_symref(const char *oldref, const char *newref,

7
builtin/branch.c

@ -192,6 +192,7 @@ static void delete_branch_config(const char *branchname)
static int delete_branches(int argc, const char **argv, int force, int kinds, static int delete_branches(int argc, const char **argv, int force, int kinds,
int quiet) int quiet)
{ {
struct worktree **worktrees;
struct commit *head_rev = NULL; struct commit *head_rev = NULL;
struct object_id oid; struct object_id oid;
char *name = NULL; char *name = NULL;
@ -228,6 +229,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
if (!head_rev) if (!head_rev)
die(_("Couldn't look up commit object for HEAD")); die(_("Couldn't look up commit object for HEAD"));
} }

worktrees = get_worktrees();

for (i = 0; i < argc; i++, strbuf_reset(&bname)) { for (i = 0; i < argc; i++, strbuf_reset(&bname)) {
char *target = NULL; char *target = NULL;
int flags = 0; int flags = 0;
@ -238,7 +242,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,


if (kinds == FILTER_REFS_BRANCHES) { if (kinds == FILTER_REFS_BRANCHES) {
const struct worktree *wt = const struct worktree *wt =
find_shared_symref("HEAD", name); find_shared_symref(worktrees, "HEAD", name);
if (wt) { if (wt) {
error(_("Cannot delete branch '%s' " error(_("Cannot delete branch '%s' "
"checked out at '%s'"), "checked out at '%s'"),
@ -299,6 +303,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,


free(name); free(name);
strbuf_release(&bname); strbuf_release(&bname);
free_worktrees(worktrees);


return ret; return ret;
} }

119
builtin/fetch.c

@ -28,6 +28,7 @@
#include "promisor-remote.h" #include "promisor-remote.h"
#include "commit-graph.h" #include "commit-graph.h"
#include "shallow.h" #include "shallow.h"
#include "worktree.h"


#define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000) #define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000)


@ -552,7 +553,7 @@ static struct ref *get_ref_map(struct remote *remote,
for (i = 0; i < fetch_refspec->nr; i++) for (i = 0; i < fetch_refspec->nr; i++)
get_fetch_map(ref_map, &fetch_refspec->items[i], &oref_tail, 1); get_fetch_map(ref_map, &fetch_refspec->items[i], &oref_tail, 1);
} else if (refmap.nr) { } else if (refmap.nr) {
die("--refmap option is only meaningful with command-line refspec(s)."); die("--refmap option is only meaningful with command-line refspec(s)");
} else { } else {
/* Use the defaults */ /* Use the defaults */
struct branch *branch = branch_get(NULL); struct branch *branch = branch_get(NULL);
@ -583,7 +584,7 @@ static struct ref *get_ref_map(struct remote *remote,
} else if (!prefetch) { } else if (!prefetch) {
ref_map = get_remote_ref(remote_refs, "HEAD"); ref_map = get_remote_ref(remote_refs, "HEAD");
if (!ref_map) if (!ref_map)
die(_("Couldn't find remote ref HEAD")); die(_("couldn't find remote ref HEAD"));
ref_map->fetch_head_status = FETCH_HEAD_MERGE; ref_map->fetch_head_status = FETCH_HEAD_MERGE;
tail = &ref_map->next; tail = &ref_map->next;
} }
@ -848,13 +849,12 @@ static void format_display(struct strbuf *display, char code,


static int update_local_ref(struct ref *ref, static int update_local_ref(struct ref *ref,
struct ref_transaction *transaction, struct ref_transaction *transaction,
const char *remote, const char *remote, const struct ref *remote_ref,
const struct ref *remote_ref, struct strbuf *display, int summary_width,
struct strbuf *display, struct worktree **worktrees)
int summary_width)
{ {
struct commit *current = NULL, *updated; struct commit *current = NULL, *updated;
struct branch *current_branch = branch_get(NULL); const struct worktree *wt;
const char *pretty_ref = prettify_refname(ref->name); const char *pretty_ref = prettify_refname(ref->name);
int fast_forward = 0; int fast_forward = 0;


@ -868,16 +868,17 @@ static int update_local_ref(struct ref *ref,
return 0; return 0;
} }


if (current_branch && if (!update_head_ok &&
!strcmp(ref->name, current_branch->name) && (wt = find_shared_symref(worktrees, "HEAD", ref->name)) &&
!(update_head_ok || is_bare_repository()) && !wt->is_bare && !is_null_oid(&ref->old_oid)) {
!is_null_oid(&ref->old_oid)) {
/* /*
* If this is the head, and it's not okay to update * If this is the head, and it's not okay to update
* the head, and the old value of the head isn't empty... * the head, and the old value of the head isn't empty...
*/ */
format_display(display, '!', _("[rejected]"), format_display(display, '!', _("[rejected]"),
_("can't fetch in current branch"), wt->is_current ?
_("can't fetch in current branch") :
_("checked out in another worktree"),
remote, pretty_ref, summary_width); remote, pretty_ref, summary_width);
return 1; return 1;
} }
@ -1067,16 +1068,17 @@ static void close_fetch_head(struct fetch_head *fetch_head)
} }


static const char warn_show_forced_updates[] = static const char warn_show_forced_updates[] =
N_("Fetch normally indicates which branches had a forced update,\n" N_("fetch normally indicates which branches had a forced update,\n"
"but that check has been disabled. To re-enable, use '--show-forced-updates'\n" "but that check has been disabled; to re-enable, use '--show-forced-updates'\n"
"flag or run 'git config fetch.showForcedUpdates true'."); "flag or run 'git config fetch.showForcedUpdates true'");
static const char warn_time_show_forced_updates[] = static const char warn_time_show_forced_updates[] =
N_("It took %.2f seconds to check forced updates. You can use\n" N_("it took %.2f seconds to check forced updates; you can use\n"
"'--no-show-forced-updates' or run 'git config fetch.showForcedUpdates false'\n" "'--no-show-forced-updates' or run 'git config fetch.showForcedUpdates false'\n"
" to avoid this check.\n"); "to avoid this check\n");


static int store_updated_refs(const char *raw_url, const char *remote_name, static int store_updated_refs(const char *raw_url, const char *remote_name,
int connectivity_checked, struct ref *ref_map) int connectivity_checked, struct ref *ref_map,
struct worktree **worktrees)
{ {
struct fetch_head fetch_head; struct fetch_head fetch_head;
int url_len, i, rc = 0; int url_len, i, rc = 0;
@ -1205,7 +1207,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
strbuf_reset(&note); strbuf_reset(&note);
if (ref) { if (ref) {
rc |= update_local_ref(ref, transaction, what, rc |= update_local_ref(ref, transaction, what,
rm, &note, summary_width); rm, &note, summary_width,
worktrees);
free(ref); free(ref);
} else if (write_fetch_head || dry_run) { } else if (write_fetch_head || dry_run) {
/* /*
@ -1298,7 +1301,9 @@ static int check_exist_and_connected(struct ref *ref_map)
return check_connected(iterate_ref_map, &rm, &opt); return check_connected(iterate_ref_map, &rm, &opt);
} }


static int fetch_and_consume_refs(struct transport *transport, struct ref *ref_map) static int fetch_and_consume_refs(struct transport *transport,
struct ref *ref_map,
struct worktree **worktrees)
{ {
int connectivity_checked = 1; int connectivity_checked = 1;
int ret; int ret;
@ -1319,10 +1324,8 @@ static int fetch_and_consume_refs(struct transport *transport, struct ref *ref_m
} }


trace2_region_enter("fetch", "consume_refs", the_repository); trace2_region_enter("fetch", "consume_refs", the_repository);
ret = store_updated_refs(transport->url, ret = store_updated_refs(transport->url, transport->remote->name,
transport->remote->name, connectivity_checked, ref_map, worktrees);
connectivity_checked,
ref_map);
trace2_region_leave("fetch", "consume_refs", the_repository); trace2_region_leave("fetch", "consume_refs", the_repository);


out: out:
@ -1385,18 +1388,18 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map,
return result; return result;
} }


static void check_not_current_branch(struct ref *ref_map) static void check_not_current_branch(struct ref *ref_map,
struct worktree **worktrees)
{ {
struct branch *current_branch = branch_get(NULL); const struct worktree *wt;

if (is_bare_repository() || !current_branch)
return;

for (; ref_map; ref_map = ref_map->next) for (; ref_map; ref_map = ref_map->next)
if (ref_map->peer_ref && !strcmp(current_branch->refname, if (ref_map->peer_ref &&
ref_map->peer_ref->name)) (wt = find_shared_symref(worktrees, "HEAD",
die(_("Refusing to fetch into current branch %s " ref_map->peer_ref->name)) &&
"of non-bare repository"), current_branch->refname); !wt->is_bare)
die(_("refusing to fetch into branch '%s' "
"checked out at '%s'"),
ref_map->peer_ref->name, wt->path);
} }


static int truncate_fetch_head(void) static int truncate_fetch_head(void)
@ -1414,10 +1417,10 @@ static void set_option(struct transport *transport, const char *name, const char
{ {
int r = transport_set_option(transport, name, value); int r = transport_set_option(transport, name, value);
if (r < 0) if (r < 0)
die(_("Option \"%s\" value \"%s\" is not valid for %s"), die(_("option \"%s\" value \"%s\" is not valid for %s"),
name, value, transport->url); name, value, transport->url);
if (r > 0) if (r > 0)
warning(_("Option \"%s\" is ignored for %s\n"), warning(_("option \"%s\" is ignored for %s\n"),
name, transport->url); name, transport->url);
} }


@ -1451,7 +1454,7 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
old_nr = oids->nr; old_nr = oids->nr;
for_each_glob_ref(add_oid, s, oids); for_each_glob_ref(add_oid, s, oids);
if (old_nr == oids->nr) if (old_nr == oids->nr)
warning("Ignoring --negotiation-tip=%s because it does not match any refs", warning("ignoring --negotiation-tip=%s because it does not match any refs",
s); s);
} }
smart_options->negotiation_tips = oids; smart_options->negotiation_tips = oids;
@ -1489,12 +1492,13 @@ static struct transport *prepare_transport(struct remote *remote, int deepen)
if (transport->smart_options) if (transport->smart_options)
add_negotiation_tips(transport->smart_options); add_negotiation_tips(transport->smart_options);
else else
warning("Ignoring --negotiation-tip because the protocol does not support it."); warning("ignoring --negotiation-tip because the protocol does not support it");
} }
return transport; return transport;
} }


static void backfill_tags(struct transport *transport, struct ref *ref_map) static void backfill_tags(struct transport *transport, struct ref *ref_map,
struct worktree **worktrees)
{ {
int cannot_reuse; int cannot_reuse;


@ -1515,7 +1519,7 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map)
transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
transport_set_option(transport, TRANS_OPT_DEPTH, "0"); transport_set_option(transport, TRANS_OPT_DEPTH, "0");
transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL); transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL);
fetch_and_consume_refs(transport, ref_map); fetch_and_consume_refs(transport, ref_map, worktrees);


if (gsecondary) { if (gsecondary) {
transport_disconnect(gsecondary); transport_disconnect(gsecondary);
@ -1533,6 +1537,7 @@ static int do_fetch(struct transport *transport,
struct transport_ls_refs_options transport_ls_refs_options = struct transport_ls_refs_options transport_ls_refs_options =
TRANSPORT_LS_REFS_OPTIONS_INIT; TRANSPORT_LS_REFS_OPTIONS_INIT;
int must_list_refs = 1; int must_list_refs = 1;
struct worktree **worktrees = get_worktrees();


if (tags == TAGS_DEFAULT) { if (tags == TAGS_DEFAULT) {
if (transport->remote->fetch_tags == 2) if (transport->remote->fetch_tags == 2)
@ -1588,7 +1593,7 @@ static int do_fetch(struct transport *transport,
ref_map = get_ref_map(transport->remote, remote_refs, rs, ref_map = get_ref_map(transport->remote, remote_refs, rs,
tags, &autotags); tags, &autotags);
if (!update_head_ok) if (!update_head_ok)
check_not_current_branch(ref_map); check_not_current_branch(ref_map, worktrees);


if (tags == TAGS_DEFAULT && autotags) if (tags == TAGS_DEFAULT && autotags)
transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
@ -1606,7 +1611,7 @@ static int do_fetch(struct transport *transport,
transport->url); transport->url);
} }
} }
if (fetch_and_consume_refs(transport, ref_map)) { if (fetch_and_consume_refs(transport, ref_map, worktrees)) {
free_refs(ref_map); free_refs(ref_map);
retcode = 1; retcode = 1;
goto cleanup; goto cleanup;
@ -1651,11 +1656,11 @@ static int do_fetch(struct transport *transport,
else else
warning(_("unknown branch type")); warning(_("unknown branch type"));
} else { } else {
warning(_("no source branch found.\n" warning(_("no source branch found;\n"
"you need to specify exactly one branch with the --set-upstream option.")); "you need to specify exactly one branch with the --set-upstream option"));
} }
} }
skip: skip:
free_refs(ref_map); free_refs(ref_map);


/* if neither --no-tags nor --tags was specified, do automated tag /* if neither --no-tags nor --tags was specified, do automated tag
@ -1665,11 +1670,12 @@ static int do_fetch(struct transport *transport,
ref_map = NULL; ref_map = NULL;
find_non_local_tags(remote_refs, &ref_map, &tail); find_non_local_tags(remote_refs, &ref_map, &tail);
if (ref_map) if (ref_map)
backfill_tags(transport, ref_map); backfill_tags(transport, ref_map, worktrees);
free_refs(ref_map); free_refs(ref_map);
} }


cleanup: cleanup:
free_worktrees(worktrees);
return retcode; return retcode;
} }


@ -1790,7 +1796,7 @@ static int fetch_failed_to_start(struct strbuf *out, void *cb, void *task_cb)
struct parallel_fetch_state *state = cb; struct parallel_fetch_state *state = cb;
const char *remote = task_cb; const char *remote = task_cb;


state->result = error(_("Could not fetch %s"), remote); state->result = error(_("could not fetch %s"), remote);


return 0; return 0;
} }
@ -1845,7 +1851,7 @@ static int fetch_multiple(struct string_list *list, int max_children)
if (verbosity >= 0) if (verbosity >= 0)
printf(_("Fetching %s\n"), name); printf(_("Fetching %s\n"), name);
if (run_command_v_opt(argv.v, RUN_GIT_CMD)) { if (run_command_v_opt(argv.v, RUN_GIT_CMD)) {
error(_("Could not fetch %s"), name); error(_("could not fetch %s"), name);
result = 1; result = 1;
} }
strvec_pop(&argv); strvec_pop(&argv);
@ -1906,8 +1912,8 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
int remote_via_config = remote_is_configured(remote, 0); int remote_via_config = remote_is_configured(remote, 0);


if (!remote) if (!remote)
die(_("No remote repository specified. Please, specify either a URL or a\n" die(_("no remote repository specified; please specify either a URL or a\n"
"remote name from which new revisions should be fetched.")); "remote name from which new revisions should be fetched"));


gtransport = prepare_transport(remote, 1); gtransport = prepare_transport(remote, 1);


@ -1942,7 +1948,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
if (!strcmp(argv[i], "tag")) { if (!strcmp(argv[i], "tag")) {
i++; i++;
if (i >= argc) if (i >= argc)
die(_("You need to specify a tag name.")); die(_("you need to specify a tag name"));


refspec_appendf(&rs, "refs/tags/%s:refs/tags/%s", refspec_appendf(&rs, "refs/tags/%s:refs/tags/%s",
argv[i], argv[i]); argv[i], argv[i]);
@ -2010,7 +2016,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)


if (deepen_relative) { if (deepen_relative) {
if (deepen_relative < 0) if (deepen_relative < 0)
die(_("Negative depth in --deepen is not supported")); die(_("negative depth in --deepen is not supported"));
if (depth) if (depth)
die(_("--deepen and --depth are mutually exclusive")); die(_("--deepen and --depth are mutually exclusive"));
depth = xstrfmt("%d", deepen_relative); depth = xstrfmt("%d", deepen_relative);
@ -2047,14 +2053,15 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
/* All arguments are assumed to be remotes or groups */ /* All arguments are assumed to be remotes or groups */
for (i = 0; i < argc; i++) for (i = 0; i < argc; i++)
if (!add_remote_or_group(argv[i], &list)) if (!add_remote_or_group(argv[i], &list))
die(_("No such remote or remote group: %s"), argv[i]); die(_("no such remote or remote group: %s"),
argv[i]);
} else { } else {
/* Single remote or group */ /* Single remote or group */
(void) add_remote_or_group(argv[0], &list); (void) add_remote_or_group(argv[0], &list);
if (list.nr > 1) { if (list.nr > 1) {
/* More than one remote */ /* More than one remote */
if (argc > 1) if (argc > 1)
die(_("Fetching a group and specifying refspecs does not make sense")); die(_("fetching a group and specifying refspecs does not make sense"));
} else { } else {
/* Zero or one remotes */ /* Zero or one remotes */
remote = remote_get(argv[0]); remote = remote_get(argv[0]);
@ -2075,7 +2082,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
if (gtransport->smart_options) { if (gtransport->smart_options) {
gtransport->smart_options->acked_commits = &acked_commits; gtransport->smart_options->acked_commits = &acked_commits;
} else { } else {
warning(_("Protocol does not support --negotiate-only, exiting.")); warning(_("protocol does not support --negotiate-only, exiting"));
return 1; return 1;
} }
if (server_options.nr) if (server_options.nr)

6
builtin/notes.c

@ -860,15 +860,19 @@ static int merge(int argc, const char **argv, const char *prefix)
update_ref(msg.buf, default_notes_ref(), &result_oid, NULL, 0, update_ref(msg.buf, default_notes_ref(), &result_oid, NULL, 0,
UPDATE_REFS_DIE_ON_ERR); UPDATE_REFS_DIE_ON_ERR);
else { /* Merge has unresolved conflicts */ else { /* Merge has unresolved conflicts */
struct worktree **worktrees;
const struct worktree *wt; const struct worktree *wt;
/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
update_ref(msg.buf, "NOTES_MERGE_PARTIAL", &result_oid, NULL, update_ref(msg.buf, "NOTES_MERGE_PARTIAL", &result_oid, NULL,
0, UPDATE_REFS_DIE_ON_ERR); 0, UPDATE_REFS_DIE_ON_ERR);
/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref()); worktrees = get_worktrees();
wt = find_shared_symref(worktrees, "NOTES_MERGE_REF",
default_notes_ref());
if (wt) if (wt)
die(_("a notes merge into %s is already in-progress at %s"), die(_("a notes merge into %s is already in-progress at %s"),
default_notes_ref(), wt->path); default_notes_ref(), wt->path);
free_worktrees(worktrees);
if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
die(_("failed to store link to current notes ref (%s)"), die(_("failed to store link to current notes ref (%s)"),
default_notes_ref()); default_notes_ref());

92
builtin/receive-pack.c

@ -175,7 +175,7 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
strbuf_addf(&fsck_msg_types, "%c%s=%s", strbuf_addf(&fsck_msg_types, "%c%s=%s",
fsck_msg_types.len ? ',' : '=', var, value); fsck_msg_types.len ? ',' : '=', var, value);
else else
warning("Skipping unknown msg id '%s'", var); warning("skipping unknown msg id '%s'", var);
return 0; return 0;
} }


@ -1434,29 +1434,22 @@ static const char *push_to_checkout(unsigned char *hash,


static const char *update_worktree(unsigned char *sha1, const struct worktree *worktree) static const char *update_worktree(unsigned char *sha1, const struct worktree *worktree)
{ {
const char *retval, *work_tree, *git_dir = NULL; const char *retval, *git_dir;
struct strvec env = STRVEC_INIT; struct strvec env = STRVEC_INIT;


if (worktree && worktree->path) if (!worktree || !worktree->path)
work_tree = worktree->path; BUG("worktree->path must be non-NULL");
else if (git_work_tree_cfg)
work_tree = git_work_tree_cfg;
else
work_tree = "..";


if (is_bare_repository()) if (worktree->is_bare)
return "denyCurrentBranch = updateInstead needs a worktree"; return "denyCurrentBranch = updateInstead needs a worktree";
if (worktree) git_dir = get_worktree_git_dir(worktree);
git_dir = get_worktree_git_dir(worktree);
if (!git_dir)
git_dir = get_git_dir();


strvec_pushf(&env, "GIT_DIR=%s", absolute_path(git_dir)); strvec_pushf(&env, "GIT_DIR=%s", absolute_path(git_dir));


if (!hook_exists(push_to_checkout_hook)) if (!hook_exists(push_to_checkout_hook))
retval = push_to_deploy(sha1, &env, work_tree); retval = push_to_deploy(sha1, &env, worktree->path);
else else
retval = push_to_checkout(sha1, &env, work_tree); retval = push_to_checkout(sha1, &env, worktree->path);


strvec_clear(&env); strvec_clear(&env);
return retval; return retval;
@ -1471,19 +1464,22 @@ static const char *update(struct command *cmd, struct shallow_info *si)
struct object_id *old_oid = &cmd->old_oid; struct object_id *old_oid = &cmd->old_oid;
struct object_id *new_oid = &cmd->new_oid; struct object_id *new_oid = &cmd->new_oid;
int do_update_worktree = 0; int do_update_worktree = 0;
const struct worktree *worktree = is_bare_repository() ? NULL : find_shared_symref("HEAD", name); struct worktree **worktrees = get_worktrees();
const struct worktree *worktree =
find_shared_symref(worktrees, "HEAD", name);


/* only refs/... are allowed */ /* only refs/... are allowed */
if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) { if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
rp_error("refusing to create funny ref '%s' remotely", name); rp_error("refusing to create funny ref '%s' remotely", name);
return "funny refname"; ret = "funny refname";
goto out;
} }


strbuf_addf(&namespaced_name_buf, "%s%s", get_git_namespace(), name); strbuf_addf(&namespaced_name_buf, "%s%s", get_git_namespace(), name);
free(namespaced_name); free(namespaced_name);
namespaced_name = strbuf_detach(&namespaced_name_buf, NULL); namespaced_name = strbuf_detach(&namespaced_name_buf, NULL);


if (worktree) { if (worktree && !worktree->is_bare) {
switch (deny_current_branch) { switch (deny_current_branch) {
case DENY_IGNORE: case DENY_IGNORE:
break; break;
@ -1495,7 +1491,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)
rp_error("refusing to update checked out branch: %s", name); rp_error("refusing to update checked out branch: %s", name);
if (deny_current_branch == DENY_UNCONFIGURED) if (deny_current_branch == DENY_UNCONFIGURED)
refuse_unconfigured_deny(); refuse_unconfigured_deny();
return "branch is currently checked out"; ret = "branch is currently checked out";
goto out;
case DENY_UPDATE_INSTEAD: case DENY_UPDATE_INSTEAD:
/* pass -- let other checks intervene first */ /* pass -- let other checks intervene first */
do_update_worktree = 1; do_update_worktree = 1;
@ -1506,13 +1503,15 @@ static const char *update(struct command *cmd, struct shallow_info *si)
if (!is_null_oid(new_oid) && !has_object_file(new_oid)) { if (!is_null_oid(new_oid) && !has_object_file(new_oid)) {
error("unpack should have generated %s, " error("unpack should have generated %s, "
"but I can't find it!", oid_to_hex(new_oid)); "but I can't find it!", oid_to_hex(new_oid));
return "bad pack"; ret = "bad pack";
goto out;
} }


if (!is_null_oid(old_oid) && is_null_oid(new_oid)) { if (!is_null_oid(old_oid) && is_null_oid(new_oid)) {
if (deny_deletes && starts_with(name, "refs/heads/")) { if (deny_deletes && starts_with(name, "refs/heads/")) {
rp_error("denying ref deletion for %s", name); rp_error("denying ref deletion for %s", name);
return "deletion prohibited"; ret = "deletion prohibited";
goto out;
} }


if (worktree || (head_name && !strcmp(namespaced_name, head_name))) { if (worktree || (head_name && !strcmp(namespaced_name, head_name))) {
@ -1528,9 +1527,11 @@ static const char *update(struct command *cmd, struct shallow_info *si)
if (deny_delete_current == DENY_UNCONFIGURED) if (deny_delete_current == DENY_UNCONFIGURED)
refuse_unconfigured_deny_delete_current(); refuse_unconfigured_deny_delete_current();
rp_error("refusing to delete the current branch: %s", name); rp_error("refusing to delete the current branch: %s", name);
return "deletion of the current branch prohibited"; ret = "deletion of the current branch prohibited";
goto out;
default: default:
return "Invalid denyDeleteCurrent setting"; ret = "Invalid denyDeleteCurrent setting";
goto out;
} }
} }
} }
@ -1548,25 +1549,28 @@ static const char *update(struct command *cmd, struct shallow_info *si)
old_object->type != OBJ_COMMIT || old_object->type != OBJ_COMMIT ||
new_object->type != OBJ_COMMIT) { new_object->type != OBJ_COMMIT) {
error("bad sha1 objects for %s", name); error("bad sha1 objects for %s", name);
return "bad ref"; ret = "bad ref";
goto out;
} }
old_commit = (struct commit *)old_object; old_commit = (struct commit *)old_object;
new_commit = (struct commit *)new_object; new_commit = (struct commit *)new_object;
if (!in_merge_bases(old_commit, new_commit)) { if (!in_merge_bases(old_commit, new_commit)) {
rp_error("denying non-fast-forward %s" rp_error("denying non-fast-forward %s"
" (you should pull first)", name); " (you should pull first)", name);
return "non-fast-forward"; ret = "non-fast-forward";
goto out;
} }
} }
if (run_update_hook(cmd)) { if (run_update_hook(cmd)) {
rp_error("hook declined to update %s", name); rp_error("hook declined to update %s", name);
return "hook declined"; ret = "hook declined";
goto out;
} }


if (do_update_worktree) { if (do_update_worktree) {
ret = update_worktree(new_oid->hash, find_shared_symref("HEAD", name)); ret = update_worktree(new_oid->hash, worktree);
if (ret) if (ret)
return ret; goto out;
} }


if (is_null_oid(new_oid)) { if (is_null_oid(new_oid)) {
@ -1574,9 +1578,9 @@ static const char *update(struct command *cmd, struct shallow_info *si)
if (!parse_object(the_repository, old_oid)) { if (!parse_object(the_repository, old_oid)) {
old_oid = NULL; old_oid = NULL;
if (ref_exists(name)) { if (ref_exists(name)) {
rp_warning("Allowing deletion of corrupt ref."); rp_warning("allowing deletion of corrupt ref");
} else { } else {
rp_warning("Deleting a non-existent ref."); rp_warning("deleting a non-existent ref");
cmd->did_not_exist = 1; cmd->did_not_exist = 1;
} }
} }
@ -1585,17 +1589,19 @@ static const char *update(struct command *cmd, struct shallow_info *si)
old_oid, old_oid,
0, "push", &err)) { 0, "push", &err)) {
rp_error("%s", err.buf); rp_error("%s", err.buf);
strbuf_release(&err); ret = "failed to delete";
return "failed to delete"; } else {
ret = NULL; /* good */
} }
strbuf_release(&err); strbuf_release(&err);
return NULL; /* good */
} }
else { else {
struct strbuf err = STRBUF_INIT; struct strbuf err = STRBUF_INIT;
if (shallow_update && si->shallow_ref[cmd->index] && if (shallow_update && si->shallow_ref[cmd->index] &&
update_shallow_ref(cmd, si)) update_shallow_ref(cmd, si)) {
return "shallow error"; ret = "shallow error";
goto out;
}


if (ref_transaction_update(transaction, if (ref_transaction_update(transaction,
namespaced_name, namespaced_name,
@ -1603,14 +1609,16 @@ static const char *update(struct command *cmd, struct shallow_info *si)
0, "push", 0, "push",
&err)) { &err)) {
rp_error("%s", err.buf); rp_error("%s", err.buf);
strbuf_release(&err); ret = "failed to update ref";

} else {
return "failed to update ref"; ret = NULL; /* good */
} }
strbuf_release(&err); strbuf_release(&err);

return NULL; /* good */
} }

out:
free_worktrees(worktrees);
return ret;
} }


static void run_update_post_hook(struct command *commands) static void run_update_post_hook(struct command *commands)
@ -2476,9 +2484,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0); argc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);


if (argc > 1) if (argc > 1)
usage_msg_opt(_("Too many arguments."), receive_pack_usage, options); usage_msg_opt(_("too many arguments"), receive_pack_usage, options);
if (argc == 0) if (argc == 0)
usage_msg_opt(_("You must specify a directory."), receive_pack_usage, options); usage_msg_opt(_("you must specify a directory"), receive_pack_usage, options);


service_dir = argv[0]; service_dir = argv[0];



2
t/t2018-checkout-branch.sh

@ -148,7 +148,7 @@ test_expect_success 'checkout -b to an existing branch fails' '
test_expect_success 'checkout -b to @{-1} fails with the right branch name' ' test_expect_success 'checkout -b to @{-1} fails with the right branch name' '
git checkout branch1 && git checkout branch1 &&
git checkout branch2 && git checkout branch2 &&
echo >expect "fatal: A branch named '\''branch1'\'' already exists." && echo >expect "fatal: a branch named '\''branch1'\'' already exists" &&
test_must_fail git checkout -b @{-1} 2>actual && test_must_fail git checkout -b @{-1} 2>actual &&
test_cmp expect actual test_cmp expect actual
' '

11
t/t3200-branch.sh

@ -168,6 +168,13 @@ test_expect_success 'git branch -M foo bar should fail when bar is checked out'
test_must_fail git branch -M bar foo test_must_fail git branch -M bar foo
' '


test_expect_success 'git branch -M foo bar should fail when bar is checked out in worktree' '
git branch -f bar &&
test_when_finished "git worktree remove wt && git branch -D wt" &&
git worktree add wt &&
test_must_fail git branch -M bar wt
'

test_expect_success 'git branch -M baz bam should succeed when baz is checked out' ' test_expect_success 'git branch -M baz bam should succeed when baz is checked out' '
git checkout -b baz && git checkout -b baz &&
git branch bam && git branch bam &&
@ -888,7 +895,7 @@ test_expect_success '--set-upstream-to fails on a missing src branch' '
' '


test_expect_success '--set-upstream-to fails on a non-ref' ' test_expect_success '--set-upstream-to fails on a non-ref' '
echo "fatal: Cannot setup tracking information; starting point '"'"'HEAD^{}'"'"' is not a branch." >expect && echo "fatal: cannot set up tracking information; starting point '"'"'HEAD^{}'"'"' is not a branch" >expect &&
test_must_fail git branch --set-upstream-to HEAD^{} 2>err && test_must_fail git branch --set-upstream-to HEAD^{} 2>err &&
test_cmp expect err test_cmp expect err
' '
@ -975,7 +982,7 @@ test_expect_success 'disabled option --set-upstream fails' '
test_expect_success '--set-upstream-to notices an error to set branch as own upstream' ' test_expect_success '--set-upstream-to notices an error to set branch as own upstream' '
git branch --set-upstream-to refs/heads/my13 my13 2>actual && git branch --set-upstream-to refs/heads/my13 my13 2>actual &&
cat >expect <<-\EOF && cat >expect <<-\EOF &&
warning: Not setting branch my13 as its own upstream. warning: not setting branch my13 as its own upstream
EOF EOF
test_expect_code 1 git config branch.my13.remote && test_expect_code 1 git config branch.my13.remote &&
test_expect_code 1 git config branch.my13.merge && test_expect_code 1 git config branch.my13.merge &&

2
t/t5504-fetch-receive-strict.sh

@ -292,7 +292,7 @@ test_expect_success 'push with receive.fsck.missingEmail=warn' '
receive.fsck.missingEmail warn && receive.fsck.missingEmail warn &&
git push --porcelain dst bogus >act 2>&1 && git push --porcelain dst bogus >act 2>&1 &&
grep "missingEmail" act && grep "missingEmail" act &&
test_i18ngrep "Skipping unknown msg id.*whatever" act && test_i18ngrep "skipping unknown msg id.*whatever" act &&
git --git-dir=dst/.git branch -D bogus && git --git-dir=dst/.git branch -D bogus &&
git --git-dir=dst/.git config --add \ git --git-dir=dst/.git config --add \
receive.fsck.missingEmail ignore && receive.fsck.missingEmail ignore &&

32
t/t5516-fetch-push.sh

@ -1778,6 +1778,38 @@ test_expect_success 'denyCurrentBranch and worktrees' '
test_must_fail git -C cloned push origin HEAD:new-wt && test_must_fail git -C cloned push origin HEAD:new-wt &&
test_config receive.denyCurrentBranch updateInstead && test_config receive.denyCurrentBranch updateInstead &&
git -C cloned push origin HEAD:new-wt && git -C cloned push origin HEAD:new-wt &&
test_path_exists new-wt/first.t &&
test_must_fail git -C cloned push --delete origin new-wt test_must_fail git -C cloned push --delete origin new-wt
' '

test_expect_success 'denyCurrentBranch and bare repository worktrees' '
test_when_finished "rm -fr bare.git" &&
git clone --bare . bare.git &&
git -C bare.git worktree add wt &&
test_commit grape &&
git -C bare.git config receive.denyCurrentBranch refuse &&
test_must_fail git push bare.git HEAD:wt &&
git -C bare.git config receive.denyCurrentBranch updateInstead &&
git push bare.git HEAD:wt &&
test_path_exists bare.git/wt/grape.t &&
test_must_fail git push --delete bare.git wt
'

test_expect_success 'refuse fetch to current branch of worktree' '
test_when_finished "git worktree remove --force wt && git branch -D wt" &&
git worktree add wt &&
test_commit apple &&
test_must_fail git fetch . HEAD:wt &&
git fetch -u . HEAD:wt
'

test_expect_success 'refuse fetch to current branch of bare repository worktree' '
test_when_finished "rm -fr bare.git" &&
git clone --bare . bare.git &&
git -C bare.git worktree add wt &&
test_commit banana &&
test_must_fail git -C bare.git fetch .. HEAD:wt &&
git -C bare.git fetch -u .. HEAD:wt
'

test_done test_done

8
worktree.c

@ -404,17 +404,13 @@ int is_worktree_being_bisected(const struct worktree *wt,
* bisect). New commands that do similar things should update this * bisect). New commands that do similar things should update this
* function as well. * function as well.
*/ */
const struct worktree *find_shared_symref(const char *symref, const struct worktree *find_shared_symref(struct worktree **worktrees,
const char *symref,
const char *target) const char *target)
{ {
const struct worktree *existing = NULL; const struct worktree *existing = NULL;
static struct worktree **worktrees;
int i = 0; int i = 0;


if (worktrees)
free_worktrees(worktrees);
worktrees = get_worktrees();

for (i = 0; worktrees[i]; i++) { for (i = 0; worktrees[i]; i++) {
struct worktree *wt = worktrees[i]; struct worktree *wt = worktrees[i];
const char *symref_target; const char *symref_target;

5
worktree.h

@ -143,9 +143,10 @@ void free_worktrees(struct worktree **);
/* /*
* Check if a per-worktree symref points to a ref in the main worktree * Check if a per-worktree symref points to a ref in the main worktree
* or any linked worktree, and return the worktree that holds the ref, * or any linked worktree, and return the worktree that holds the ref,
* or NULL otherwise. The result may be destroyed by the next call. * or NULL otherwise.
*/ */
const struct worktree *find_shared_symref(const char *symref, const struct worktree *find_shared_symref(struct worktree **worktrees,
const char *symref,
const char *target); const char *target);


/* /*

Loading…
Cancel
Save