From: Junio C Hamano Date: Mon, 9 Sep 2013 21:30:29 +0000 (-0700) Subject: Merge branch 'jc/push-cas' X-Git-Tag: v1.8.5-rc0~171 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/2233ad4534db8a37b1bf726312d52d4a0a51db0a?ds=inline;hp=-c Merge branch 'jc/push-cas' Allow a safer "rewind of the remote tip" push than blind "--force", by requiring that the overwritten remote ref to be unchanged since the new history to replace it was prepared. The machinery is more or less ready. The "--force" option is again the big red button to override any safety, thanks to J6t's sanity (the original round allowed --lockref to defeat --force). The logic to choose the default implemented here is fragile (e.g. "git fetch" after seeing a failure will update the remote-tracking branch and will make the next "push" pass, defeating the safety pretty easily). It is suitable only for the simplest workflows, and it may hurt users more than it helps them. * jc/push-cas: push: teach --force-with-lease to smart-http transport send-pack: fix parsing of --force-with-lease option t5540/5541: smart-http does not support "--force-with-lease" t5533: test "push --force-with-lease" push --force-with-lease: tie it all together push --force-with-lease: implement logic to populate old_sha1_expect[] remote.c: add command line option parser for "--force-with-lease" builtin/push.c: use OPT_BOOL, not OPT_BOOLEAN cache.h: move remote/connect API out of it --- 2233ad4534db8a37b1bf726312d52d4a0a51db0a diff --combined builtin/fetch-pack.c index 3e19d7149e,c6888c66ce..c8e858232a --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@@ -1,6 -1,8 +1,8 @@@ #include "builtin.h" #include "pkt-line.h" #include "fetch-pack.h" + #include "remote.h" + #include "connect.h" static const char fetch_pack_usage[] = "git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] " @@@ -100,10 -102,6 +102,10 @@@ int cmd_fetch_pack(int argc, const cha pack_lockfile_ptr = &pack_lockfile; continue; } + if (!strcmp("--check-self-contained-and-connected", arg)) { + args.check_self_contained_and_connected = 1; + continue; + } usage(fetch_pack_usage); } @@@ -156,11 -154,6 +158,11 @@@ printf("lock %s\n", pack_lockfile); fflush(stdout); } + if (args.check_self_contained_and_connected && + args.self_contained_and_connected) { + printf("connectivity-ok\n"); + fflush(stdout); + } close(fd[0]); close(fd[1]); if (finish_connect(conn)) diff --combined builtin/push.c index aff507c9f6,2fd0a70fa8..50bbfd62b1 --- a/builtin/push.c +++ b/builtin/push.c @@@ -21,6 -21,8 +21,8 @@@ static const char *receivepack static int verbosity; static int progress = -1; + static struct push_cas_option cas; + static const char **refspec; static int refspec_nr; static int refspec_alloc; @@@ -92,7 -94,7 +94,7 @@@ static NORETURN int die_push_simple(str if (!short_upstream) short_upstream = branch->merge[0]->src; /* - * Don't show advice for people who explicitely set + * Don't show advice for people who explicitly set * push.default. */ if (push_default == PUSH_DEFAULT_UNSPECIFIED) @@@ -120,11 -122,10 +122,11 @@@ static const char message_detached_head "\n" " git push %s HEAD:\n"); -static void setup_push_upstream(struct remote *remote, int simple) +static void setup_push_upstream(struct remote *remote, struct branch *branch, + int triangular) { struct strbuf refspec = STRBUF_INIT; - struct branch *branch = branch_get(NULL); + if (!branch) die(_(message_detached_head_die), remote->name); if (!branch->merge_nr || !branch->merge || !branch->remote_name) @@@ -138,29 -139,18 +140,29 @@@ if (branch->merge_nr != 1) die(_("The current branch %s has multiple upstream branches, " "refusing to push."), branch->name); - if (strcmp(branch->remote_name, remote->name)) + if (triangular) die(_("You are pushing to remote '%s', which is not the upstream of\n" "your current branch '%s', without telling me what to push\n" "to update which remote branch."), remote->name, branch->name); - if (simple && strcmp(branch->refname, branch->merge[0]->src)) - die_push_simple(branch, remote); + + if (push_default == PUSH_DEFAULT_SIMPLE) { + /* Additional safety */ + if (strcmp(branch->refname, branch->merge[0]->src)) + die_push_simple(branch, remote); + } strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src); add_refspec(refspec.buf); } +static void setup_push_current(struct remote *remote, struct branch *branch) +{ + if (!branch) + die(_(message_detached_head_die), remote->name); + add_refspec(branch->name); +} + static char warn_unspecified_push_default_msg[] = N_("push.default is unset; its implicit value is changing in\n" "Git 2.0 from 'matching' to 'simple'. To squelch this message\n" @@@ -185,16 -175,9 +187,16 @@@ static void warn_unspecified_push_defau warning("%s\n", _(warn_unspecified_push_default_msg)); } +static int is_workflow_triangular(struct remote *remote) +{ + struct remote *fetch_remote = remote_get(NULL); + return (fetch_remote && fetch_remote != remote); +} + static void setup_default_push_refspecs(struct remote *remote) { - struct branch *branch; + struct branch *branch = branch_get(NULL); + int triangular = is_workflow_triangular(remote); switch (push_default) { default: @@@ -207,18 -190,18 +209,18 @@@ break; case PUSH_DEFAULT_SIMPLE: - setup_push_upstream(remote, 1); + if (triangular) + setup_push_current(remote, branch); + else + setup_push_upstream(remote, branch, triangular); break; case PUSH_DEFAULT_UPSTREAM: - setup_push_upstream(remote, 0); + setup_push_upstream(remote, branch, triangular); break; case PUSH_DEFAULT_CURRENT: - branch = branch_get(NULL); - if (!branch) - die(_(message_detached_head_die), remote->name); - add_refspec(branch->name); + setup_push_current(remote, branch); break; case PUSH_DEFAULT_NOTHING: @@@ -230,8 -213,8 +232,8 @@@ static const char message_advice_pull_before_push[] = N_("Updates were rejected because the tip of your current branch is behind\n" - "its remote counterpart. Merge the remote changes (e.g. 'git pull')\n" - "before pushing again.\n" + "its remote counterpart. Integrate the remote changes (e.g.\n" + "'git pull ...') before pushing again.\n" "See the 'Note about fast-forwards' in 'git push --help' for details."); static const char message_advice_use_upstream[] = @@@ -242,15 -225,15 +244,15 @@@ static const char message_advice_checkout_pull_push[] = N_("Updates were rejected because a pushed branch tip is behind its remote\n" - "counterpart. Check out this branch and merge the remote changes\n" - "(e.g. 'git pull') before pushing again.\n" + "counterpart. Check out this branch and integrate the remote changes\n" + "(e.g. 'git pull ...') before pushing again.\n" "See the 'Note about fast-forwards' in 'git push --help' for details."); static const char message_advice_ref_fetch_first[] = N_("Updates were rejected because the remote contains work that you do\n" "not have locally. This is usually caused by another repository pushing\n" - "to the same ref. You may want to first merge the remote changes (e.g.,\n" - "'git pull') before pushing again.\n" + "to the same ref. You may want to first integrate the remote changes\n" + "(e.g., 'git pull ...') before pushing again.\n" "See the 'Note about fast-forwards' in 'git push --help' for details."); static const char message_advice_ref_already_exists[] = @@@ -316,6 -299,13 +318,13 @@@ static int push_with_options(struct tra if (thin) transport_set_option(transport, TRANS_OPT_THIN, "yes"); + if (!is_empty_cas(&cas)) { + if (!transport->smart_options) + die("underlying transport does not support --%s option", + CAS_OPT_NAME); + transport->smart_options->cas = &cas; + } + if (verbosity > 0) fprintf(stderr, _("Pushing to %s\n"), transport->url); err = transport_push(transport, refspec_nr, refspec, flags, @@@ -451,6 -441,10 +460,10 @@@ int cmd_push(int argc, const char **arg OPT_BIT('n' , "dry-run", &flags, N_("dry run"), TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, N_("machine-readable output"), TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, N_("force updates"), TRANSPORT_PUSH_FORCE), + { OPTION_CALLBACK, + 0, CAS_OPT_NAME, &cas, N_("refname>:object.flags & COMMON)) non_common_revs++; @@@ -123,10 -123,10 +124,10 @@@ static const unsigned char *get_rev(voi unsigned int mark; struct commit_list *parents; - if (rev_list == NULL || non_common_revs == 0) + if (rev_list.nr == 0 || non_common_revs == 0) return NULL; - commit = rev_list->item; + commit = prio_queue_get(&rev_list); if (!commit->object.parsed) parse_commit(commit); parents = commit->parents; @@@ -153,6 -153,8 +154,6 @@@ mark_common(parents->item, 1, 0); parents = parents->next; } - - rev_list = rev_list->next; } return commit->object.sha1; @@@ -441,7 -443,7 +442,7 @@@ static int find_common(struct fetch_pac in_vain = 0; got_continue = 1; if (ack == ACK_ready) { - rev_list = NULL; + clear_prio_queue(&rev_list); got_ready = 1; } break; @@@ -504,7 -506,7 +505,7 @@@ static int mark_complete(const char *re struct commit *commit = (struct commit *)o; if (!(commit->object.flags & COMPLETE)) { commit->object.flags |= COMPLETE; - commit_list_insert_by_date(commit, &complete); + commit_list_insert(commit, &complete); } } return 0; @@@ -621,7 -623,6 +622,7 @@@ static int everything_local(struct fetc if (!args->depth) { for_each_ref(mark_complete, NULL); for_each_alternate_ref(mark_alternate_complete, NULL); + commit_list_sort_by_date(&complete); if (cutoff) mark_recent_complete_commits(args, cutoff); } @@@ -897,8 -898,6 +898,8 @@@ static struct ref *do_fetch_pack(struc packet_flush(fd[1]); if (args->depth > 0) setup_alternate_shallow(); + else + alternate_shallow_file = NULL; if (get_pack(args, fd, pack_lockfile)) die("git fetch-pack: fetch failed."); @@@ -989,7 -988,7 +990,7 @@@ struct ref *fetch_pack(struct fetch_pac } ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought, pack_lockfile); - if (alternate_shallow_file) { + if (args->depth > 0 && alternate_shallow_file) { if (*alternate_shallow_file == '\0') { /* --unshallow */ unlink_or_warn(git_path("shallow")); rollback_lock_file(&shallow_lock); diff --combined refs.c index 7922261580,330060c866..d78860c46d --- a/refs.c +++ b/refs.c @@@ -72,6 -72,10 +72,6 @@@ int check_refname_format(const char *re { int component_len, component_count = 0; - if (!strcmp(refname, "@")) - /* Refname is a single character '@'. */ - return -1; - while (1) { /* We are at the start of a path component. */ component_len = check_refname_component(refname, flags); @@@ -630,9 -634,7 +630,9 @@@ struct ref_entry_cb static int do_one_ref(struct ref_entry *entry, void *cb_data) { struct ref_entry_cb *data = cb_data; + struct ref_entry *old_current_ref; int retval; + if (prefixcmp(entry->name, data->base)) return 0; @@@ -640,12 -642,10 +640,12 @@@ !ref_resolves_to_object(entry)) return 0; + /* Store the old value, in case this is a recursive call: */ + old_current_ref = current_ref; current_ref = entry; retval = data->fn(entry->name + data->trim, entry->u.value.sha1, entry->flag, data->cb_data); - current_ref = NULL; + current_ref = old_current_ref; return retval; } @@@ -2174,14 -2174,11 +2174,14 @@@ int lock_packed_refs(int flags { struct packed_ref_cache *packed_ref_cache; - /* Discard the old cache because it might be invalid: */ - clear_packed_ref_cache(&ref_cache); if (hold_lock_file_for_update(&packlock, git_path("packed-refs"), flags) < 0) return -1; - /* Read the current packed-refs while holding the lock: */ + /* + * Get the current packed-refs while holding the lock. If the + * packed-refs file has been modified since we last read it, + * this will automatically invalidate the cache and re-read + * the packed-refs file. + */ packed_ref_cache = get_packed_ref_cache(&ref_cache); packed_ref_cache->lock = &packlock; /* Increment the reference count to prevent it from being freed: */ @@@ -3196,14 -3193,6 +3196,6 @@@ int update_ref(const char *action, cons return 0; } - struct ref *find_ref_by_name(const struct ref *list, const char *name) - { - for ( ; list; list = list->next) - if (!strcmp(list->name, name)) - return (struct ref *)list; - return NULL; - } - /* * generate a format suitable for scanf from a ref_rev_parse_rules * rule, that is replace the "%.*s" spec with a "%s" spec diff --combined remote-curl.c index 6918668dc2,53c8a3d1a3..b5ebe01800 --- a/remote-curl.c +++ b/remote-curl.c @@@ -6,8 -6,8 +6,9 @@@ #include "exec_cmd.h" #include "run-command.h" #include "pkt-line.h" + #include "string-list.h" #include "sideband.h" +#include "argv-array.h" static struct remote *remote; static const char *url; /* always ends with a trailing slash */ @@@ -16,12 -16,12 +17,13 @@@ struct options int verbosity; unsigned long depth; unsigned progress : 1, + check_self_contained_and_connected : 1, followtags : 1, dry_run : 1, thin : 1; }; static struct options options; + static struct string_list cas_options = STRING_LIST_INIT_DUP; static int set_option(const char *name, const char *value) { @@@ -68,15 -68,13 +70,22 @@@ return -1; return 0; } + else if (!strcmp(name, "check-connectivity")) { + if (!strcmp(value, "true")) + options.check_self_contained_and_connected = 1; + else if (!strcmp(value, "false")) + options.check_self_contained_and_connected = 0; + else + return -1; + return 0; + } + else if (!strcmp(name, "cas")) { + struct strbuf val = STRBUF_INIT; + strbuf_addf(&val, "--" CAS_OPT_NAME "=%s", value); + string_list_append(&cas_options, val.buf); + strbuf_release(&val); + return 0; + } else { return 1 /* unsupported */; } @@@ -664,7 -662,7 +673,7 @@@ static int fetch_git(struct discovery * struct strbuf preamble = STRBUF_INIT; char *depth_arg = NULL; int argc = 0, i, err; - const char *argv[15]; + const char *argv[16]; argv[argc++] = "fetch-pack"; argv[argc++] = "--stateless-rpc"; @@@ -678,8 -676,6 +687,8 @@@ argv[argc++] = "-v"; argv[argc++] = "-v"; } + if (options.check_self_contained_and_connected) + argv[argc++] = "--check-self-contained-and-connected"; if (!options.progress) argv[argc++] = "--no-progress"; if (options.depth) { @@@ -800,35 -796,41 +809,38 @@@ static int push_dav(int nr_spec, char * static int push_git(struct discovery *heads, int nr_spec, char **specs) { struct rpc_state rpc; - const char **argv; - int argc = 0, i, err; + int i, err; + struct argv_array args; + struct string_list_item *cas_option; - argv = xmalloc((10 + nr_spec + cas_options.nr) * sizeof(char *)); - argv[argc++] = "send-pack"; - argv[argc++] = "--stateless-rpc"; - argv[argc++] = "--helper-status"; + argv_array_init(&args); + argv_array_pushl(&args, "send-pack", "--stateless-rpc", "--helper-status", + NULL); + if (options.thin) - argv[argc++] = "--thin"; + argv_array_push(&args, "--thin"); if (options.dry_run) - argv[argc++] = "--dry-run"; + argv_array_push(&args, "--dry-run"); if (options.verbosity == 0) - argv[argc++] = "--quiet"; + argv_array_push(&args, "--quiet"); else if (options.verbosity > 1) - argv[argc++] = "--verbose"; - argv[argc++] = options.progress ? "--progress" : "--no-progress"; - + argv_array_push(&args, "--verbose"); + argv_array_push(&args, options.progress ? "--progress" : "--no-progress"); + for_each_string_list_item(cas_option, &cas_options) - argv[argc++] = cas_option->string; - - argv[argc++] = url; ++ argv_array_push(&args, cas_option->string); + argv_array_push(&args, url); for (i = 0; i < nr_spec; i++) - argv[argc++] = specs[i]; - argv[argc++] = NULL; + argv_array_push(&args, specs[i]); memset(&rpc, 0, sizeof(rpc)); rpc.service_name = "git-receive-pack", - rpc.argv = argv; + rpc.argv = args.argv; err = rpc_service(&rpc, heads); if (rpc.result.len) write_or_die(1, rpc.result.buf, rpc.result.len); strbuf_release(&rpc.result); - free(argv); + argv_array_clear(&args); return err; } @@@ -951,7 -953,6 +963,7 @@@ int main(int argc, const char **argv printf("fetch\n"); printf("option\n"); printf("push\n"); + printf("check-connectivity\n"); printf("\n"); fflush(stdout); } else { diff --combined remote.c index 8f0f2dd10e,922822c3cb..24334679e0 --- a/remote.c +++ b/remote.c @@@ -148,7 -148,6 +148,7 @@@ static struct remote *make_remote(cons } ret = xcalloc(1, sizeof(struct remote)); + ret->prune = -1; /* unspecified */ ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc); remotes[remotes_nr++] = ret; if (len) @@@ -405,8 -404,6 +405,8 @@@ static int handle_config(const char *ke remote->skip_default_update = git_config_bool(key, value); else if (!strcmp(subkey, ".skipfetchall")) remote->skip_default_update = git_config_bool(key, value); + else if (!strcmp(subkey, ".prune")) + remote->prune = git_config_bool(key, value); else if (!strcmp(subkey, ".url")) { const char *v; if (git_config_string(&v, key, value)) @@@ -1305,14 -1302,14 +1305,22 @@@ static void add_missing_tags(struct re free(sent_tips.tip); } + struct ref *find_ref_by_name(const struct ref *list, const char *name) + { + for ( ; list; list = list->next) + if (!strcmp(list->name, name)) + return (struct ref *)list; + return NULL; + } + +static void prepare_ref_index(struct string_list *ref_index, struct ref *ref) +{ + for ( ; ref; ref = ref->next) + string_list_append_nodup(ref_index, ref->name)->util = ref; + + sort_string_list(ref_index); +} + /* * Given the set of refs the local repository has, the set of refs the * remote repository has, and the refspec used for push, determine @@@ -1331,7 -1328,6 +1339,7 @@@ int match_push_refs(struct ref *src, st int errs; static const char *default_refspec[] = { ":", NULL }; struct ref *ref, **dst_tail = tail_ref(dst); + struct string_list dst_ref_index = STRING_LIST_INIT_NODUP; if (!nr_refspec) { nr_refspec = 1; @@@ -1342,7 -1338,6 +1350,7 @@@ /* pick the remainder */ for (ref = src; ref; ref = ref->next) { + struct string_list_item *dst_item; struct ref *dst_peer; const struct refspec *pat = NULL; char *dst_name; @@@ -1351,11 -1346,7 +1359,11 @@@ if (!dst_name) continue; - dst_peer = find_ref_by_name(*dst, dst_name); + if (!dst_ref_index.nr) + prepare_ref_index(&dst_ref_index, *dst); + + dst_item = string_list_lookup(&dst_ref_index, dst_name); + dst_peer = dst_item ? dst_item->util : NULL; if (dst_peer) { if (dst_peer->peer_ref) /* We're already sending something to this ref. */ @@@ -1372,8 -1363,6 +1380,8 @@@ /* Create a new one and link it */ dst_peer = make_linked_ref(dst_name, &dst_tail); hashcpy(dst_peer->new_sha1, ref->new_sha1); + string_list_insert(&dst_ref_index, + dst_peer->name)->util = dst_peer; } dst_peer->peer_ref = copy_ref(ref); dst_peer->force = pat->force; @@@ -1381,13 -1370,10 +1389,13 @@@ free(dst_name); } + string_list_clear(&dst_ref_index, 0); + if (flags & MATCH_REFS_FOLLOW_TAGS) add_missing_tags(src, dst, &dst_tail); if (send_prune) { + struct string_list src_ref_index = STRING_LIST_INIT_NODUP; /* check for missing refs on the remote */ for (ref = *dst; ref; ref = ref->next) { char *src_name; @@@ -1398,15 -1384,11 +1406,15 @@@ src_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_DST, NULL); if (src_name) { - if (!find_ref_by_name(src, src_name)) + if (!src_ref_index.nr) + prepare_ref_index(&src_ref_index, src); + if (!string_list_has_string(&src_ref_index, + src_name)) ref->peer_ref = alloc_delete_ref(); free(src_name); } } + string_list_clear(&src_ref_index, 0); } if (errs) return -1; @@@ -1414,12 -1396,13 +1422,13 @@@ } void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, - int force_update) + int force_update) { struct ref *ref; for (ref = remote_refs; ref; ref = ref->next) { int force_ref_update = ref->force || force_update; + int reject_reason = 0; if (ref->peer_ref) hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); @@@ -1434,6 -1417,26 +1443,26 @@@ } /* + * Bypass the usual "must fast-forward" check but + * replace it with a weaker "the old value must be + * this value we observed". If the remote ref has + * moved and is now different from what we expect, + * reject any push. + * + * It also is an error if the user told us to check + * with the remote-tracking branch to find the value + * to expect, but we did not have such a tracking + * branch. + */ + if (ref->expect_old_sha1) { + if (ref->expect_old_no_trackback || + hashcmp(ref->old_sha1, ref->old_sha1_expect)) + reject_reason = REF_STATUS_REJECT_STALE; + } + + /* + * The usual "must fast-forward" rules. + * * Decide whether an individual refspec A:B can be * pushed. The push will succeed if any of the * following are true: @@@ -1451,24 -1454,26 +1480,26 @@@ * passing the --force argument */ - if (!ref->deletion && !is_null_sha1(ref->old_sha1)) { - int why = 0; /* why would this push require --force? */ - + else if (!ref->deletion && !is_null_sha1(ref->old_sha1)) { if (!prefixcmp(ref->name, "refs/tags/")) - why = REF_STATUS_REJECT_ALREADY_EXISTS; + reject_reason = REF_STATUS_REJECT_ALREADY_EXISTS; else if (!has_sha1_file(ref->old_sha1)) - why = REF_STATUS_REJECT_FETCH_FIRST; + reject_reason = REF_STATUS_REJECT_FETCH_FIRST; else if (!lookup_commit_reference_gently(ref->old_sha1, 1) || !lookup_commit_reference_gently(ref->new_sha1, 1)) - why = REF_STATUS_REJECT_NEEDS_FORCE; + reject_reason = REF_STATUS_REJECT_NEEDS_FORCE; else if (!ref_newer(ref->new_sha1, ref->old_sha1)) - why = REF_STATUS_REJECT_NONFASTFORWARD; - - if (!force_ref_update) - ref->status = why; - else if (why) - ref->forced_update = 1; + reject_reason = REF_STATUS_REJECT_NONFASTFORWARD; } + + /* + * "--force" will defeat any rejection implemented + * by the rules above. + */ + if (!force_ref_update) + ref->status = reject_reason; + else if (reject_reason) + ref->forced_update = 1; } } @@@ -1939,3 -1944,121 +1970,121 @@@ struct ref *get_stale_heads(struct refs string_list_clear(&ref_names, 0); return stale_refs; } + + /* + * Compare-and-swap + */ + void clear_cas_option(struct push_cas_option *cas) + { + int i; + + for (i = 0; i < cas->nr; i++) + free(cas->entry[i].refname); + free(cas->entry); + memset(cas, 0, sizeof(*cas)); + } + + static struct push_cas *add_cas_entry(struct push_cas_option *cas, + const char *refname, + size_t refnamelen) + { + struct push_cas *entry; + ALLOC_GROW(cas->entry, cas->nr + 1, cas->alloc); + entry = &cas->entry[cas->nr++]; + memset(entry, 0, sizeof(*entry)); + entry->refname = xmemdupz(refname, refnamelen); + return entry; + } + + int parse_push_cas_option(struct push_cas_option *cas, const char *arg, int unset) + { + const char *colon; + struct push_cas *entry; + + if (unset) { + /* "--no-