X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;ds=sidebyside;f=remote.c;h=d7079c6dd871dc1b482d347d013438fe30cc0908;hb=8851f4800c15fe91b6a53e9e284787b983226c41;hp=2d9af4023eba6f8b2fe528ccbf03569fcaa265ee;hpb=92798702cf6d201f80e257a07d0a0c40565c79fe;p=git.git diff --git a/remote.c b/remote.c index 2d9af4023..d7079c6dd 100644 --- a/remote.c +++ b/remote.c @@ -1,6 +1,20 @@ #include "cache.h" #include "remote.h" #include "refs.h" +#include "commit.h" +#include "diff.h" +#include "revision.h" +#include "dir.h" + +static struct refspec s_tag_refspec = { + 0, + 1, + 0, + "refs/tags/", + "refs/tags/" +}; + +const struct refspec *tag_refspec = &s_tag_refspec; struct counted_string { size_t len; @@ -56,7 +70,7 @@ static const char *alias_url(const char *url) if (!longest) return url; - ret = malloc(rewrite[longest_i]->baselen + + ret = xmalloc(rewrite[longest_i]->baselen + (strlen(url) - longest->len) + 1); strcpy(ret, rewrite[longest_i]->base); strcpy(ret + rewrite[longest_i]->baselen, url + longest->len); @@ -139,7 +153,7 @@ static struct branch *make_branch(const char *name, int len) ret->name = xstrndup(name, len); else ret->name = xstrdup(name); - refname = malloc(strlen(name) + strlen("refs/heads/") + 1); + refname = xmalloc(strlen(name) + strlen("refs/heads/") + 1); strcpy(refname, "refs/heads/"); strcpy(refname + strlen("refs/heads/"), ret->name); ret->refname = refname; @@ -188,6 +202,7 @@ static void read_remotes_file(struct remote *remote) if (!f) return; + remote->origin = REMOTE_REMOTES; while (fgets(buffer, BUF_SIZE, f)) { int value_list; char *s, *p; @@ -232,7 +247,7 @@ static void read_branches_file(struct remote *remote) { const char *slash = strchr(remote->name, '/'); char *frag; - struct strbuf branch; + struct strbuf branch = STRBUF_INIT; int n = slash ? slash - remote->name : 1000; FILE *f = fopen(git_path("branches/%.*s", n, remote->name), "r"); char *s, *p; @@ -248,6 +263,7 @@ static void read_branches_file(struct remote *remote) s++; if (!*s) return; + remote->origin = REMOTE_BRANCHES; p = s + strlen(s); while (isspace(p[-1])) *--p = 0; @@ -270,7 +286,6 @@ static void read_branches_file(struct remote *remote) * #branch specified. The "master" (or specified) branch is * fetched and stored in the local branch of the same name. */ - strbuf_init(&branch, 0); frag = strchr(p, '#'); if (frag) { *(frag++) = '\0'; @@ -285,10 +300,21 @@ static void read_branches_file(struct remote *remote) } add_url_alias(remote, p); add_fetch_refspec(remote, strbuf_detach(&branch, 0)); + /* + * Cogito compatible push: push current HEAD to remote #branch + * (master if missing) + */ + strbuf_init(&branch, 0); + strbuf_addstr(&branch, "HEAD"); + if (frag) + strbuf_addf(&branch, ":refs/heads/%s", frag); + else + strbuf_addstr(&branch, ":refs/heads/master"); + add_push_refspec(remote, strbuf_detach(&branch, 0)); remote->fetch_tags = 1; /* always auto-follow */ } -static int handle_config(const char *key, const char *value) +static int handle_config(const char *key, const char *value, void *cb) { const char *name; const char *subkey; @@ -329,52 +355,59 @@ static int handle_config(const char *key, const char *value) if (prefixcmp(key, "remote.")) return 0; name = key + 7; + if (*name == '/') { + warning("Config remote shorthand cannot begin with '/': %s", + name); + return 0; + } subkey = strrchr(name, '.'); if (!subkey) return error("Config with no key for remote %s", name); - if (*subkey == '/') { - warning("Config remote shorthand cannot begin with '/': %s", name); - return 0; - } remote = make_remote(name, subkey - name); - if (!value) { - /* if we ever have a boolean variable, e.g. "remote.*.disabled" - * [remote "frotz"] - * disabled - * is a valid way to set it to true; we get NULL in value so - * we need to handle it here. - * - * if (!strcmp(subkey, ".disabled")) { - * val = git_config_bool(key, value); - * return 0; - * } else - * - */ - return 0; /* ignore unknown booleans */ - } - if (!strcmp(subkey, ".url")) { - add_url(remote, xstrdup(value)); + remote->origin = REMOTE_CONFIG; + if (!strcmp(subkey, ".mirror")) + remote->mirror = git_config_bool(key, value); + else if (!strcmp(subkey, ".skipdefaultupdate")) + remote->skip_default_update = git_config_bool(key, value); + + else if (!strcmp(subkey, ".url")) { + const char *v; + if (git_config_string(&v, key, value)) + return -1; + add_url(remote, v); } else if (!strcmp(subkey, ".push")) { - add_push_refspec(remote, xstrdup(value)); + const char *v; + if (git_config_string(&v, key, value)) + return -1; + add_push_refspec(remote, v); } else if (!strcmp(subkey, ".fetch")) { - add_fetch_refspec(remote, xstrdup(value)); + const char *v; + if (git_config_string(&v, key, value)) + return -1; + add_fetch_refspec(remote, v); } else if (!strcmp(subkey, ".receivepack")) { + const char *v; + if (git_config_string(&v, key, value)) + return -1; if (!remote->receivepack) - remote->receivepack = xstrdup(value); + remote->receivepack = v; else error("more than one receivepack given, using the first"); } else if (!strcmp(subkey, ".uploadpack")) { + const char *v; + if (git_config_string(&v, key, value)) + return -1; if (!remote->uploadpack) - remote->uploadpack = xstrdup(value); + remote->uploadpack = v; else error("more than one uploadpack given, using the first"); } else if (!strcmp(subkey, ".tagopt")) { if (!strcmp(value, "--no-tags")) remote->fetch_tags = -1; } else if (!strcmp(subkey, ".proxy")) { - remote->http_proxy = xstrdup(value); - } else if (!strcmp(subkey, ".skipdefaultupdate")) - remote->skip_default_update = 1; + return git_config_string((const char **)&remote->http_proxy, + key, value); + } return 0; } @@ -405,10 +438,52 @@ static void read_config(void) current_branch = make_branch(head_ref + strlen("refs/heads/"), 0); } - git_config(handle_config); + git_config(handle_config, NULL); alias_all_urls(); } +/* + * We need to make sure the tracking branches are well formed, but a + * wildcard refspec in "struct refspec" must have a trailing slash. We + * temporarily drop the trailing '/' while calling check_ref_format(), + * and put it back. The caller knows that a CHECK_REF_FORMAT_ONELEVEL + * error return is Ok for a wildcard refspec. + */ +static int verify_refname(char *name, int is_glob) +{ + int result, len = -1; + + if (is_glob) { + len = strlen(name); + assert(name[len - 1] == '/'); + name[len - 1] = '\0'; + } + result = check_ref_format(name); + if (is_glob) + name[len - 1] = '/'; + return result; +} + +/* + * This function frees a refspec array. + * Warning: code paths should be checked to ensure that the src + * and dst pointers are always freeable pointers as well + * as the refspec pointer itself. + */ +static void free_refspecs(struct refspec *refspec, int nr_refspec) +{ + int i; + + if (!refspec) + return; + + for (i = 0; i < nr_refspec; i++) { + free(refspec[i].src); + free(refspec[i].dst); + } + free(refspec); +} + static struct refspec *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch, int verify) { int i; @@ -416,11 +491,11 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp struct refspec *rs = xcalloc(sizeof(*rs), nr_refspec); for (i = 0; i < nr_refspec; i++) { - size_t llen, rlen; + size_t llen; int is_glob; const char *lhs, *rhs; - llen = rlen = is_glob = 0; + llen = is_glob = 0; lhs = refspec[i]; if (*lhs == '+') { @@ -429,13 +504,20 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp } rhs = strrchr(lhs, ':'); + + /* + * Before going on, special case ":" (or "+:") as a refspec + * for matching refs. + */ + if (!fetch && rhs == lhs && rhs[1] == '\0') { + rs[i].matching = 1; + continue; + } + if (rhs) { - rhs++; - rlen = strlen(rhs); + size_t rlen = strlen(++rhs); is_glob = (2 <= rlen && !strcmp(rhs + rlen - 2, "/*")); - if (is_glob) - rlen -= 2; - rs[i].dst = xstrndup(rhs, rlen); + rs[i].dst = xstrndup(rhs, rlen - is_glob); } llen = (rhs ? (rhs - lhs - 1) : strlen(lhs)); @@ -443,7 +525,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp if ((rhs && !is_glob) || (!rhs && fetch)) goto invalid; is_glob = 1; - llen -= 2; + llen--; } else if (rhs && is_glob) { goto invalid; } @@ -460,7 +542,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp if (!*rs[i].src) ; /* empty is ok */ else { - st = check_ref_format(rs[i].src); + st = verify_refname(rs[i].src, is_glob); if (st && st != CHECK_REF_FORMAT_ONELEVEL) goto invalid; } @@ -475,7 +557,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp } else if (!*rs[i].dst) { ; /* ok */ } else { - st = check_ref_format(rs[i].dst); + st = verify_refname(rs[i].dst, is_glob); if (st && st != CHECK_REF_FORMAT_ONELEVEL) goto invalid; } @@ -490,7 +572,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp if (!*rs[i].src) ; /* empty is ok */ else if (is_glob) { - st = check_ref_format(rs[i].src); + st = verify_refname(rs[i].src, is_glob); if (st && st != CHECK_REF_FORMAT_ONELEVEL) goto invalid; } @@ -504,13 +586,13 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp * - otherwise it must be a valid looking ref. */ if (!rs[i].dst) { - st = check_ref_format(rs[i].src); + st = verify_refname(rs[i].src, is_glob); if (st && st != CHECK_REF_FORMAT_ONELEVEL) goto invalid; } else if (!*rs[i].dst) { goto invalid; } else { - st = check_ref_format(rs[i].dst); + st = verify_refname(rs[i].dst, is_glob); if (st && st != CHECK_REF_FORMAT_ONELEVEL) goto invalid; } @@ -520,7 +602,12 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp invalid: if (verify) { - free(rs); + /* + * nr_refspec must be greater than zero and i must be valid + * since it is only possible to reach this point from within + * the for loop above. + */ + free_refspecs(rs, i+1); return NULL; } die("Invalid refspec '%s'", refspec[i]); @@ -532,8 +619,7 @@ int valid_fetch_refspec(const char *fetch_refspec_str) struct refspec *refspec; refspec = parse_refspec_internal(1, fetch_refspec, 1, 1); - if (refspec) - free(refspec); + free_refspecs(refspec, 1); return !!refspec; } @@ -542,17 +628,14 @@ struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec) return parse_refspec_internal(nr_refspec, refspec, 1, 0); } -struct refspec *parse_push_refspec(int nr_refspec, const char **refspec) +static struct refspec *parse_push_refspec(int nr_refspec, const char **refspec) { return parse_refspec_internal(nr_refspec, refspec, 0, 0); } static int valid_remote_nick(const char *name) { - if (!name[0] || /* not empty */ - (name[0] == '.' && /* not "." */ - (!name[1] || /* not ".." */ - (name[1] == '.' && !name[2])))) + if (!name[0] || is_dot_or_dotdot(name)) return 0; return !strchr(name, '/'); /* no slash */ } @@ -659,8 +742,7 @@ int remote_find_tracking(struct remote *remote, struct refspec *refspec) if (!fetch->dst) continue; if (fetch->pattern) { - if (!prefixcmp(needle, key) && - needle[strlen(key)] == '/') { + if (!prefixcmp(needle, key)) { *result = xmalloc(strlen(value) + strlen(needle) - strlen(key) + 1); @@ -679,11 +761,19 @@ int remote_find_tracking(struct remote *remote, struct refspec *refspec) return -1; } -struct ref *alloc_ref(unsigned namelen) +static struct ref *alloc_ref_with_prefix(const char *prefix, size_t prefixlen, + const char *name) { - struct ref *ret = xmalloc(sizeof(struct ref) + namelen); - memset(ret, 0, sizeof(struct ref) + namelen); - return ret; + size_t len = strlen(name); + struct ref *ref = xcalloc(1, sizeof(struct ref) + prefixlen + len + 1); + memcpy(ref->name, prefix, prefixlen); + memcpy(ref->name + prefixlen, name, len); + return ref; +} + +struct ref *alloc_ref(const char *name) +{ + return alloc_ref_with_prefix("", 0, name); } static struct ref *copy_ref(const struct ref *ref) @@ -706,13 +796,22 @@ struct ref *copy_ref_list(const struct ref *ref) return ret; } +static void free_ref(struct ref *ref) +{ + if (!ref) + return; + free(ref->remote_status); + free(ref->symref); + free(ref); +} + void free_refs(struct ref *ref) { struct ref *next; while (ref) { next = ref->next; free(ref->peer_ref); - free(ref); + free_ref(ref); ref = next; } } @@ -783,31 +882,22 @@ static struct ref *try_explicit_object_name(const char *name) { unsigned char sha1[20]; struct ref *ref; - int len; if (!*name) { - ref = alloc_ref(20); - strcpy(ref->name, "(delete)"); + ref = alloc_ref("(delete)"); hashclr(ref->new_sha1); return ref; } if (get_sha1(name, sha1)) return NULL; - len = strlen(name) + 1; - ref = alloc_ref(len); - memcpy(ref->name, name, len); + ref = alloc_ref(name); hashcpy(ref->new_sha1, sha1); return ref; } static struct ref *make_linked_ref(const char *name, struct ref ***tail) { - struct ref *ret; - size_t len; - - len = strlen(name) + 1; - ret = alloc_ref(len); - memcpy(ret->name, name, len); + struct ref *ret = alloc_ref(name); tail_link_ref(ret, tail); return ret; } @@ -834,16 +924,15 @@ static char *guess_ref(const char *name, struct ref *peer) static int match_explicit(struct ref *src, struct ref *dst, struct ref ***dst_tail, - struct refspec *rs, - int errs) + struct refspec *rs) { struct ref *matched_src, *matched_dst; const char *dst_value = rs->dst; char *dst_guess; - if (rs->pattern) - return errs; + if (rs->pattern || rs->matching) + return 0; matched_src = matched_dst = NULL; switch (count_refspec_match(rs->src, src, &matched_src)) { @@ -856,23 +945,16 @@ static int match_explicit(struct ref *src, struct ref *dst, */ matched_src = try_explicit_object_name(rs->src); if (!matched_src) - error("src refspec %s does not match any.", rs->src); + return error("src refspec %s does not match any.", rs->src); break; default: - matched_src = NULL; - error("src refspec %s matches more than one.", rs->src); - break; + return error("src refspec %s matches more than one.", rs->src); } - if (!matched_src) - errs = 1; - if (!dst_value) { unsigned char sha1[20]; int flag; - if (!matched_src) - return errs; dst_value = resolve_ref(matched_src->name, sha1, 1, &flag); if (!dst_value || ((flag & REF_ISSYMREF) && @@ -903,18 +985,16 @@ static int match_explicit(struct ref *src, struct ref *dst, dst_value); break; } - if (errs || !matched_dst) - return 1; - if (matched_dst->peer_ref) { - errs = 1; - error("dst ref %s receives from more than one src.", + if (!matched_dst) + return -1; + if (matched_dst->peer_ref) + return error("dst ref %s receives from more than one src.", matched_dst->name); - } else { matched_dst->peer_ref = matched_src; matched_dst->force = rs->force; } - return errs; + return 0; } static int match_explicit_refs(struct ref *src, struct ref *dst, @@ -923,8 +1003,8 @@ static int match_explicit_refs(struct ref *src, struct ref *dst, { int i, errs; for (i = errs = 0; i < rs_nr; i++) - errs |= match_explicit(src, dst, dst_tail, &rs[i], errs); - return -errs; + errs += match_explicit(src, dst, dst_tail, &rs[i]); + return errs; } static const struct refspec *check_pattern_match(const struct refspec *rs, @@ -932,13 +1012,21 @@ static const struct refspec *check_pattern_match(const struct refspec *rs, const struct ref *src) { int i; + int matching_refs = -1; for (i = 0; i < rs_nr; i++) { - if (rs[i].pattern && - !prefixcmp(src->name, rs[i].src) && - src->name[strlen(rs[i].src)] == '/') + if (rs[i].matching && + (matching_refs == -1 || rs[i].force)) { + matching_refs = i; + continue; + } + + if (rs[i].pattern && !prefixcmp(src->name, rs[i].src)) return rs + i; } - return NULL; + if (matching_refs != -1) + return rs + matching_refs; + else + return NULL; } /* @@ -949,11 +1037,16 @@ static const struct refspec *check_pattern_match(const struct refspec *rs, int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, int nr_refspec, const char **refspec, int flags) { - struct refspec *rs = - parse_push_refspec(nr_refspec, (const char **) refspec); + struct refspec *rs; int send_all = flags & MATCH_REFS_ALL; int send_mirror = flags & MATCH_REFS_MIRROR; + static const char *default_refspec[] = { ":", 0 }; + if (!nr_refspec) { + nr_refspec = 1; + refspec = default_refspec; + } + rs = parse_push_refspec(nr_refspec, (const char **) refspec); if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec)) return -1; @@ -964,48 +1057,50 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, char *dst_name; if (src->peer_ref) continue; - if (nr_refspec) { - pat = check_pattern_match(rs, nr_refspec, src); - if (!pat) - continue; - } - else if (!send_mirror && prefixcmp(src->name, "refs/heads/")) + + pat = check_pattern_match(rs, nr_refspec, src); + if (!pat) + continue; + + if (pat->matching) { /* * "matching refs"; traditionally we pushed everything * including refs outside refs/heads/ hierarchy, but * that does not make much sense these days. */ - continue; + if (!send_mirror && prefixcmp(src->name, "refs/heads/")) + continue; + dst_name = xstrdup(src->name); - if (pat) { + } else { const char *dst_side = pat->dst ? pat->dst : pat->src; dst_name = xmalloc(strlen(dst_side) + strlen(src->name) - strlen(pat->src) + 2); strcpy(dst_name, dst_side); strcat(dst_name, src->name + strlen(pat->src)); - } else - dst_name = xstrdup(src->name); + } dst_peer = find_ref_by_name(dst, dst_name); - if (dst_peer && dst_peer->peer_ref) - /* We're already sending something to this ref. */ - goto free_name; + if (dst_peer) { + if (dst_peer->peer_ref) + /* We're already sending something to this ref. */ + goto free_name; + + } else { + if (pat->matching && !(send_all || send_mirror)) + /* + * Remote doesn't have it, and we have no + * explicit pattern, and we don't have + * --all nor --mirror. + */ + goto free_name; - if (!dst_peer && !nr_refspec && !(send_all || send_mirror)) - /* - * Remote doesn't have it, and we have no - * explicit pattern, and we don't have - * --all nor --mirror. - */ - goto free_name; - if (!dst_peer) { /* Create a new one and link it */ dst_peer = make_linked_ref(dst_name, dst_tail); hashcpy(dst_peer->new_sha1, src->new_sha1); } dst_peer->peer_ref = src; - if (pat) - dst_peer->force = pat->force; + dst_peer->force = pat->force; free_name: free(dst_name); } @@ -1070,10 +1165,8 @@ static struct ref *get_expanded_map(const struct ref *remote_refs, struct ref *cpy = copy_ref(ref); match = ref->name + remote_prefix_len; - cpy->peer_ref = alloc_ref(local_prefix_len + - strlen(match) + 1); - sprintf(cpy->peer_ref->name, "%s%s", - refspec->dst, match); + cpy->peer_ref = alloc_ref_with_prefix(refspec->dst, + local_prefix_len, match); if (refspec->force) cpy->peer_ref->force = 1; *tail = cpy; @@ -1106,27 +1199,18 @@ struct ref *get_remote_ref(const struct ref *remote_refs, const char *name) static struct ref *get_local_ref(const char *name) { - struct ref *ret; if (!name) return NULL; - if (!prefixcmp(name, "refs/")) { - ret = alloc_ref(strlen(name) + 1); - strcpy(ret->name, name); - return ret; - } + if (!prefixcmp(name, "refs/")) + return alloc_ref(name); if (!prefixcmp(name, "heads/") || !prefixcmp(name, "tags/") || - !prefixcmp(name, "remotes/")) { - ret = alloc_ref(strlen(name) + 6); - sprintf(ret->name, "refs/%s", name); - return ret; - } + !prefixcmp(name, "remotes/")) + return alloc_ref_with_prefix("refs/", 5, name); - ret = alloc_ref(strlen(name) + 12); - sprintf(ret->name, "refs/heads/%s", name); - return ret; + return alloc_ref_with_prefix("refs/heads/", 11, name); } int get_fetch_map(const struct ref *remote_refs, @@ -1172,3 +1256,123 @@ int get_fetch_map(const struct ref *remote_refs, return 0; } + +int resolve_remote_symref(struct ref *ref, struct ref *list) +{ + if (!ref->symref) + return 0; + for (; list; list = list->next) + if (!strcmp(ref->symref, list->name)) { + hashcpy(ref->old_sha1, list->old_sha1); + return 0; + } + return 1; +} + +/* + * Return true if there is anything to report, otherwise false. + */ +int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) +{ + unsigned char sha1[20]; + struct commit *ours, *theirs; + char symmetric[84]; + struct rev_info revs; + const char *rev_argv[10], *base; + int rev_argc; + + /* + * Nothing to report unless we are marked to build on top of + * somebody else. + */ + if (!branch || + !branch->merge || !branch->merge[0] || !branch->merge[0]->dst) + return 0; + + /* + * If what we used to build on no longer exists, there is + * nothing to report. + */ + base = branch->merge[0]->dst; + if (!resolve_ref(base, sha1, 1, NULL)) + return 0; + theirs = lookup_commit(sha1); + if (!theirs) + return 0; + + if (!resolve_ref(branch->refname, sha1, 1, NULL)) + return 0; + ours = lookup_commit(sha1); + if (!ours) + return 0; + + /* are we the same? */ + if (theirs == ours) + return 0; + + /* Run "rev-list --left-right ours...theirs" internally... */ + rev_argc = 0; + rev_argv[rev_argc++] = NULL; + rev_argv[rev_argc++] = "--left-right"; + rev_argv[rev_argc++] = symmetric; + rev_argv[rev_argc++] = "--"; + rev_argv[rev_argc] = NULL; + + strcpy(symmetric, sha1_to_hex(ours->object.sha1)); + strcpy(symmetric + 40, "..."); + strcpy(symmetric + 43, sha1_to_hex(theirs->object.sha1)); + + init_revisions(&revs, NULL); + setup_revisions(rev_argc, rev_argv, &revs, NULL); + prepare_revision_walk(&revs); + + /* ... and count the commits on each side. */ + *num_ours = 0; + *num_theirs = 0; + while (1) { + struct commit *c = get_revision(&revs); + if (!c) + break; + if (c->object.flags & SYMMETRIC_LEFT) + (*num_ours)++; + else + (*num_theirs)++; + } + + /* clear object flags smudged by the above traversal */ + clear_commit_marks(ours, ALL_REV_FLAGS); + clear_commit_marks(theirs, ALL_REV_FLAGS); + return 1; +} + +/* + * Return true when there is anything to report, otherwise false. + */ +int format_tracking_info(struct branch *branch, struct strbuf *sb) +{ + int num_ours, num_theirs; + const char *base; + + if (!stat_tracking_info(branch, &num_ours, &num_theirs)) + return 0; + + base = branch->merge[0]->dst; + if (!prefixcmp(base, "refs/remotes/")) { + base += strlen("refs/remotes/"); + } + if (!num_theirs) + strbuf_addf(sb, "Your branch is ahead of '%s' " + "by %d commit%s.\n", + base, num_ours, (num_ours == 1) ? "" : "s"); + else if (!num_ours) + strbuf_addf(sb, "Your branch is behind '%s' " + "by %d commit%s, " + "and can be fast-forwarded.\n", + base, num_theirs, (num_theirs == 1) ? "" : "s"); + else + strbuf_addf(sb, "Your branch and '%s' have diverged,\n" + "and have %d and %d different commit(s) each, " + "respectively.\n", + base, num_ours, num_theirs); + return 1; +}