aboutsummaryrefslogtreecommitdiff
path: root/connect.c
diff options
context:
space:
mode:
Diffstat (limited to 'connect.c')
-rw-r--r--connect.c172
1 files changed, 169 insertions, 3 deletions
diff --git a/connect.c b/connect.c
index a910af93d..d913f296d 100644
--- a/connect.c
+++ b/connect.c
@@ -31,11 +31,9 @@ struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **ma
name = buffer + 41;
if (nr_match && !path_match(name, nr_match, match))
continue;
- ref = xmalloc(sizeof(*ref) + len - 40);
+ ref = xcalloc(1, sizeof(*ref) + len - 40);
memcpy(ref->old_sha1, old_sha1, 20);
- memset(ref->new_sha1, 0, 20);
memcpy(ref->name, buffer + 41, len - 40);
- ref->next = NULL;
*list = ref;
list = &ref->next;
}
@@ -81,6 +79,174 @@ int path_match(const char *path, int nr, char **match)
return 0;
}
+struct refspec {
+ char *src;
+ char *dst;
+};
+
+static struct refspec *parse_ref_spec(int nr_refspec, char **refspec)
+{
+ int i;
+ struct refspec *rs = xmalloc(sizeof(*rs) * (nr_refspec + 1));
+ for (i = 0; i < nr_refspec; i++) {
+ char *sp, *dp, *ep;
+ sp = refspec[i];
+ ep = strchr(sp, ':');
+ if (ep) {
+ dp = ep + 1;
+ *ep = 0;
+ }
+ else
+ dp = sp;
+ rs[i].src = sp;
+ rs[i].dst = dp;
+ }
+ rs[nr_refspec].src = rs[nr_refspec].dst = NULL;
+ return rs;
+}
+
+static int count_refspec_match(const char *pattern,
+ struct ref *refs,
+ struct ref **matched_ref)
+{
+ int match;
+ int patlen = strlen(pattern);
+
+ for (match = 0; refs; refs = refs->next) {
+ char *name = refs->name;
+ int namelen = strlen(name);
+ if (namelen < patlen ||
+ memcmp(name + namelen - patlen, pattern, patlen))
+ continue;
+ if (namelen != patlen && name[namelen - patlen - 1] != '/')
+ continue;
+ match++;
+ *matched_ref = refs;
+ }
+ return match;
+}
+
+static void link_dst_tail(struct ref *ref, struct ref ***tail)
+{
+ **tail = ref;
+ *tail = &ref->next;
+ **tail = NULL;
+}
+
+static int match_explicit_refs(struct ref *src, struct ref *dst,
+ struct ref ***dst_tail, struct refspec *rs)
+{
+ int i, errs;
+ for (i = errs = 0; rs[i].src; i++) {
+ struct ref *matched_src, *matched_dst;
+
+ matched_src = matched_dst = NULL;
+ switch (count_refspec_match(rs[i].src, src, &matched_src)) {
+ case 1:
+ break;
+ case 0:
+ errs = 1;
+ error("src refspec %s does not match any.");
+ break;
+ default:
+ errs = 1;
+ error("src refspec %s matches more than one.",
+ rs[i].src);
+ break;
+ }
+ switch (count_refspec_match(rs[i].dst, dst, &matched_dst)) {
+ case 1:
+ break;
+ case 0:
+ if (!memcmp(rs[i].dst, "refs/", 5)) {
+ int len = strlen(rs[i].dst) + 1;
+ matched_dst = xcalloc(1, sizeof(*dst) + len);
+ memcpy(matched_dst->name, rs[i].dst, len);
+ link_dst_tail(matched_dst, dst_tail);
+ }
+ else if (!strcmp(rs[i].src, rs[i].dst) &&
+ matched_src) {
+ /* pushing "master:master" when
+ * remote does not have master yet.
+ */
+ int len = strlen(matched_src->name);
+ matched_dst = xcalloc(1, sizeof(*dst) + len);
+ memcpy(matched_dst->name, matched_src->name,
+ len);
+ link_dst_tail(matched_dst, dst_tail);
+ }
+ else {
+ errs = 1;
+ error("dst refspec %s does not match any "
+ "existing ref on the remote and does "
+ "not start with refs/.", rs[i].dst);
+ }
+ break;
+ default:
+ errs = 1;
+ error("dst refspec %s matches more than one.",
+ rs[i].dst);
+ break;
+ }
+ if (errs)
+ continue;
+ if (matched_src->peer_ref) {
+ errs = 1;
+ error("src ref %s is sent to more than one dst.",
+ matched_src->name);
+ }
+ else
+ matched_src->peer_ref = matched_dst;
+ if (matched_dst->peer_ref) {
+ errs = 1;
+ error("dst ref %s receives from more than one src.",
+ matched_dst->name);
+ }
+ else
+ matched_dst->peer_ref = matched_src;
+ }
+ return -errs;
+}
+
+static struct ref *find_ref_by_name(struct ref *list, const char *name)
+{
+ for ( ; list; list = list->next)
+ if (!strcmp(list->name, name))
+ return list;
+ return NULL;
+}
+
+int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
+ int nr_refspec, char **refspec, int all)
+{
+ struct refspec *rs = parse_ref_spec(nr_refspec, refspec);
+
+ if (nr_refspec)
+ return match_explicit_refs(src, dst, dst_tail, rs);
+
+ /* pick the remainder */
+ for ( ; src; src = src->next) {
+ struct ref *dst_peer;
+ if (src->peer_ref)
+ continue;
+ dst_peer = find_ref_by_name(dst, src->name);
+ if (dst_peer && dst_peer->peer_ref)
+ continue;
+ if (!dst_peer) {
+ if (!all)
+ continue;
+ /* Create a new one and link it */
+ int len = strlen(src->name) + 1;
+ dst_peer = xcalloc(1, sizeof(*dst_peer) + len);
+ memcpy(dst_peer->name, src->name, len);
+ memcpy(dst_peer->new_sha1, src->new_sha1, 20);
+ link_dst_tail(dst_peer, dst_tail);
+ }
+ dst_peer->peer_ref = src;
+ }
+ return 0;
+}
+
enum protocol {
PROTO_LOCAL = 1,
PROTO_SSH,