From f88395ac233572b19f67b3a47b9fe024f397a85b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 3 Aug 2005 16:35:29 -0700 Subject: [PATCH] Renaming push. This allows git-send-pack to push local refs to a destination repository under different names. Here is the name mapping rules for refs. * If there is no ref mapping on the command line: - if '--all' is specified, it is equivalent to specifying ":" for all the existing local refs on the command line - otherwise, it is equivalent to specifying ":" for all the refs that exist on both sides. * is just a shorthand for ":" * ":" push ref that matches to ref that matches . - It is an error if does not match exactly one of local refs. - It is an error if matches more than one remote refs. - If does not match any remote refs, either - it has to start with "refs/"; is used as the destination literally in this case. - == and the ref that matched the must not exist in the set of remote refs; the ref matched locally is used as the name of the destination. For example, - "git-send-pack --all " works exactly as before; - "git-send-pack master:upstream" pushes local master to remote ref that matches "upstream". If there is no such ref, it is an error. - "git-send-pack master:refs/heads/upstream" pushes local master to remote refs/heads/upstream, even when refs/heads/upstream does not exist. - "git-send-pack master" into an empty remote repository pushes the local ref/heads/master to the remote ref/heads/master. Signed-off-by: Junio C Hamano --- cache.h | 3 + connect.c | 172 +++++++++++++++++++++++++++++++++++++++++++++++++++- send-pack.c | 145 +++++++++++++++++-------------------------- 3 files changed, 226 insertions(+), 94 deletions(-) diff --git a/cache.h b/cache.h index 10243f0b8c..fd4988ed9a 100644 --- a/cache.h +++ b/cache.h @@ -302,12 +302,15 @@ struct ref { struct ref *next; unsigned char old_sha1[20]; unsigned char new_sha1[20]; + struct ref *peer_ref; /* when renaming */ char name[0]; }; extern int git_connect(int fd[2], char *url, const char *prog); extern int finish_connect(pid_t pid); extern int path_match(const char *path, int nr, char **match); +extern int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, + int nr_refspec, char **refspec, int all); extern int get_ack(int fd, unsigned char *result_sha1); extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match); diff --git a/connect.c b/connect.c index a910af93d8..d913f296d3 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, diff --git a/send-pack.c b/send-pack.c index 3b66185401..633fc471af 100644 --- a/send-pack.c +++ b/send-pack.c @@ -104,21 +104,6 @@ static int pack_objects(int fd, struct ref *refs) return 0; } -static int read_ref(const char *ref, unsigned char *sha1) -{ - int fd, ret; - char buffer[60]; - - fd = open(git_path("%s", ref), O_RDONLY); - if (fd < 0) - return -1; - ret = -1; - if (read(fd, buffer, sizeof(buffer)) >= 40) - ret = get_sha1_hex(buffer, sha1); - close(fd); - return ret; -} - static int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1) { struct commit *new, *old; @@ -144,114 +129,92 @@ static int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha return 0; } -static int local_ref_nr_match; -static char **local_ref_match; -static struct ref *local_ref_list; -static struct ref **local_last_ref; +static struct ref *local_refs, **local_tail; +static struct ref *remote_refs, **remote_tail; -static int try_to_match(const char *refname, const unsigned char *sha1) +static int one_local_ref(const char *refname, const unsigned char *sha1) { struct ref *ref; - int len; - - if (!path_match(refname, local_ref_nr_match, local_ref_match)) { - if (!send_all) - return 0; - - /* If we have it listed already, skip it */ - for (ref = local_ref_list ; ref ; ref = ref->next) { - if (!strcmp(ref->name, refname)) - return 0; - } - } - - len = strlen(refname)+1; - ref = xmalloc(sizeof(*ref) + len); - memset(ref->old_sha1, 0, 20); + int len = strlen(refname) + 1; + ref = xcalloc(1, sizeof(*ref) + len); memcpy(ref->new_sha1, sha1, 20); memcpy(ref->name, refname, len); - ref->next = NULL; - *local_last_ref = ref; - local_last_ref = &ref->next; + *local_tail = ref; + local_tail = &ref->next; return 0; } -static int send_pack(int in, int out, int nr_match, char **match) +static void get_local_heads(void) +{ + local_tail = &local_refs; + for_each_ref(one_local_ref); +} + +static int send_pack(int in, int out, int nr_refspec, char **refspec) { - struct ref *ref_list, **last_ref; struct ref *ref; int new_refs; - /* First we get all heads, whether matching or not.. */ - last_ref = get_remote_heads(in, &ref_list, 0, NULL); + /* No funny business with the matcher */ + remote_tail = get_remote_heads(in, &remote_refs, 0, NULL); + get_local_heads(); + /* match them up */ + if (!remote_tail) + remote_tail = &remote_refs; + if (match_refs(local_refs, remote_refs, &remote_tail, + nr_refspec, refspec, send_all)) + return -1; /* - * Go through the refs, see if we want to update - * any of them.. + * Finally, tell the other end! */ - for (ref = ref_list; ref; ref = ref->next) { - unsigned char new_sha1[20]; - char *name = ref->name; - - if (nr_match && !path_match(name, nr_match, match)) - continue; - - if (read_ref(name, new_sha1) < 0) - continue; - - if (!memcmp(ref->old_sha1, new_sha1, 20)) { - fprintf(stderr, "'%s' unchanged\n", name); + new_refs = 0; + for (ref = remote_refs; ref; ref = ref->next) { + char old_hex[60], *new_hex; + if (!ref->peer_ref) continue; + if (!is_zero_sha1(ref->old_sha1)) { + if (!has_sha1_file(ref->old_sha1)) { + error("remote '%s' object %s does not " + "exist on local", + ref->name, sha1_to_hex(ref->old_sha1)); + continue; + } + if (!ref_newer(ref->peer_ref->new_sha1, + ref->old_sha1)) { + error("remote ref '%s' is not a strict " + "subset of local ref '%s'.", ref->name, + ref->peer_ref->name); + continue; + } } - - if (!has_sha1_file(ref->old_sha1)) { - error("remote '%s' object %s does not exist on local", - name, sha1_to_hex(ref->old_sha1)); + if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) { + fprintf(stderr, "'%s': up-to-date\n", ref->name); continue; } - - if (!ref_newer(new_sha1, ref->old_sha1)) { - error("remote '%s' isn't a strict parent of local", name); + memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20); + if (is_zero_sha1(ref->new_sha1)) { + error("cannot happen anymore"); continue; } - - /* Ok, mark it for update */ - memcpy(ref->new_sha1, new_sha1, 20); - } - - /* - * See if we have any refs that the other end didn't have - */ - if (nr_match || send_all) { - local_ref_nr_match = nr_match; - local_ref_match = match; - local_ref_list = ref_list; - local_last_ref = last_ref; - for_each_ref(try_to_match); - } - - /* - * Finally, tell the other end! - */ - new_refs = 0; - for (ref = ref_list; ref; ref = ref->next) { - char old_hex[60], *new_hex; - if (is_zero_sha1(ref->new_sha1)) - continue; new_refs++; strcpy(old_hex, sha1_to_hex(ref->old_sha1)); new_hex = sha1_to_hex(ref->new_sha1); packet_write(out, "%s %s %s", old_hex, new_hex, ref->name); - fprintf(stderr, "'%s': updating from %s to %s\n", ref->name, old_hex, new_hex); + fprintf(stderr, "updating '%s'", ref->name); + if (strcmp(ref->name, ref->peer_ref->name)) + fprintf(stderr, " using '%s'", ref->peer_ref->name); + fprintf(stderr, "\n from %s\n to %s\n", old_hex, new_hex); } - + packet_flush(out); if (new_refs) - pack_objects(out, ref_list); + pack_objects(out, remote_refs); close(out); return 0; } + int main(int argc, char **argv) { int i, nr_heads = 0;