summaryrefslogtreecommitdiff
path: root/remote.c
diff options
context:
space:
mode:
Diffstat (limited to 'remote.c')
-rw-r--r--remote.c175
1 files changed, 162 insertions, 13 deletions
diff --git a/remote.c b/remote.c
index 8f0f2dd..2433467 100644
--- a/remote.c
+++ b/remote.c
@@ -1305,6 +1305,14 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
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)
@@ -1414,12 +1422,13 @@ int match_push_refs(struct ref *src, struct ref **dst,
}
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 +1443,26 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
}
/*
+ * 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 +1480,26 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
* 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 +1970,121 @@ struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fet
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-<option>" */
+ clear_cas_option(cas);
+ return 0;
+ }
+
+ if (!arg) {
+ /* just "--<option>" */
+ cas->use_tracking_for_rest = 1;
+ return 0;
+ }
+
+ /* "--<option>=refname" or "--<option>=refname:value" */
+ colon = strchrnul(arg, ':');
+ entry = add_cas_entry(cas, arg, colon - arg);
+ if (!*colon)
+ entry->use_tracking = 1;
+ else if (get_sha1(colon + 1, entry->expect))
+ return error("cannot parse expected object name '%s'", colon + 1);
+ return 0;
+}
+
+int parseopt_push_cas_option(const struct option *opt, const char *arg, int unset)
+{
+ return parse_push_cas_option(opt->value, arg, unset);
+}
+
+int is_empty_cas(const struct push_cas_option *cas)
+{
+ return !cas->use_tracking_for_rest && !cas->nr;
+}
+
+/*
+ * Look at remote.fetch refspec and see if we have a remote
+ * tracking branch for the refname there. Fill its current
+ * value in sha1[].
+ * If we cannot do so, return negative to signal an error.
+ */
+static int remote_tracking(struct remote *remote, const char *refname,
+ unsigned char sha1[20])
+{
+ char *dst;
+
+ dst = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname);
+ if (!dst)
+ return -1; /* no tracking ref for refname at remote */
+ if (read_ref(dst, sha1))
+ return -1; /* we know what the tracking ref is but we cannot read it */
+ return 0;
+}
+
+static void apply_cas(struct push_cas_option *cas,
+ struct remote *remote,
+ struct ref *ref)
+{
+ int i;
+
+ /* Find an explicit --<option>=<name>[:<value>] entry */
+ for (i = 0; i < cas->nr; i++) {
+ struct push_cas *entry = &cas->entry[i];
+ if (!refname_match(entry->refname, ref->name, ref_rev_parse_rules))
+ continue;
+ ref->expect_old_sha1 = 1;
+ if (!entry->use_tracking)
+ hashcpy(ref->old_sha1_expect, cas->entry[i].expect);
+ else if (remote_tracking(remote, ref->name, ref->old_sha1_expect))
+ ref->expect_old_no_trackback = 1;
+ return;
+ }
+
+ /* Are we using "--<option>" to cover all? */
+ if (!cas->use_tracking_for_rest)
+ return;
+
+ ref->expect_old_sha1 = 1;
+ if (remote_tracking(remote, ref->name, ref->old_sha1_expect))
+ ref->expect_old_no_trackback = 1;
+}
+
+void apply_push_cas(struct push_cas_option *cas,
+ struct remote *remote,
+ struct ref *remote_refs)
+{
+ struct ref *ref;
+ for (ref = remote_refs; ref; ref = ref->next)
+ apply_cas(cas, remote, ref);
+}