diff options
author | Jonathan Maw <jonathan.maw@codethink.co.uk> | 2013-09-30 15:08:10 +0100 |
---|---|---|
committer | Jonathan Maw <jonathan.maw@codethink.co.uk> | 2013-09-30 15:08:10 +0100 |
commit | 43efcf42382e87de4aa423e5e1607958ad1717d0 (patch) | |
tree | 7e19a0765b0dd6885fbdf69d3a8d0159a1b42de8 /remote.c | |
parent | 45d74c4b0fe38218b4569a90da7102cf48d616c2 (diff) | |
parent | c7fd06b6411fb04eb4d9acd7f8822a288a50dc17 (diff) | |
download | git-43efcf42382e87de4aa423e5e1607958ad1717d0.tar.gz |
Merge branch 'baserock/jonathanmaw/S9007/upgrade-git' into baserock/morphbaserock/morph
Reviewed-by: Lars Wirzenius <lars.wirzenius@codethink.co.uk>
Reviewed-by: Daniel Silverstone <daniel.silverstone@codethink.co.uk>
Diffstat (limited to 'remote.c')
-rw-r--r-- | remote.c | 482 |
1 files changed, 357 insertions, 125 deletions
@@ -7,11 +7,15 @@ #include "dir.h" #include "tag.h" #include "string-list.h" +#include "mergesort.h" + +enum map_direction { FROM_SRC, FROM_DST }; static struct refspec s_tag_refspec = { 0, 1, 0, + 0, "refs/tags/*", "refs/tags/*" }; @@ -45,6 +49,7 @@ static int branches_nr; static struct branch *current_branch; static const char *default_remote_name; +static const char *pushremote_name; static int explicit_default_remote_name; static struct rewrites rewrites; @@ -271,10 +276,9 @@ static void read_remotes_file(struct remote *remote) static void read_branches_file(struct remote *remote) { - const char *slash = strchr(remote->name, '/'); char *frag; struct strbuf branch = STRBUF_INIT; - int n = slash ? slash - remote->name : 1000; + int n = 1000; FILE *f = fopen(git_path("branches/%.*s", n, remote->name), "r"); char *s, *p; int len; @@ -294,21 +298,11 @@ static void read_branches_file(struct remote *remote) while (isspace(p[-1])) *--p = 0; len = p - s; - if (slash) - len += strlen(slash); p = xmalloc(len + 1); strcpy(p, s); - if (slash) - strcat(p, slash); /* - * With "slash", e.g. "git fetch jgarzik/netdev-2.6" when - * reading from $GIT_DIR/branches/jgarzik fetches "HEAD" from - * the partial URL obtained from the branches file plus - * "/netdev-2.6" and does not store it in any tracking ref. - * #branch specifier in the file is ignored. - * - * Otherwise, the branches file would have URL and optionally + * The branches file would have URL and optionally * #branch specified. The "master" (or specified) branch is * fetched and stored in the local branch of the same name. */ @@ -318,12 +312,8 @@ static void read_branches_file(struct remote *remote) strbuf_addf(&branch, "refs/heads/%s", frag); } else strbuf_addstr(&branch, "refs/heads/master"); - if (!slash) { - strbuf_addf(&branch, ":refs/heads/%s", remote->name); - } else { - strbuf_reset(&branch); - strbuf_addstr(&branch, "HEAD:"); - } + + strbuf_addf(&branch, ":refs/heads/%s", remote->name); add_url_alias(remote, p); add_fetch_refspec(remote, strbuf_detach(&branch, NULL)); /* @@ -353,13 +343,16 @@ static int handle_config(const char *key, const char *value, void *cb) return 0; branch = make_branch(name, subkey - name); if (!strcmp(subkey, ".remote")) { - if (!value) - return config_error_nonbool(key); - branch->remote_name = xstrdup(value); + if (git_config_string(&branch->remote_name, key, value)) + return -1; if (branch == current_branch) { default_remote_name = branch->remote_name; explicit_default_remote_name = 1; } + } else if (!strcmp(subkey, ".pushremote")) { + if (branch == current_branch) + if (git_config_string(&pushremote_name, key, value)) + return -1; } else if (!strcmp(subkey, ".merge")) { if (!value) return config_error_nonbool(key); @@ -385,9 +378,16 @@ static int handle_config(const char *key, const char *value, void *cb) add_instead_of(rewrite, xstrdup(value)); } } + if (prefixcmp(key, "remote.")) return 0; name = key + 7; + + /* Handle remote.* variables */ + if (!strcmp(name, "pushdefault")) + return git_config_string(&pushremote_name, key, value); + + /* Handle remote.<name>.* variables */ if (*name == '/') { warning("Config remote shorthand cannot begin with '/': %s", name); @@ -535,7 +535,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp /* * Before going on, special case ":" (or "+:") as a refspec - * for matching refs. + * for pushing matching refs. */ if (!fetch && rhs == lhs && rhs[1] == '\0') { rs[i].matching = 1; @@ -562,26 +562,25 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0); if (fetch) { - /* - * LHS - * - empty is allowed; it means HEAD. - * - otherwise it must be a valid looking ref. - */ + unsigned char unused[40]; + + /* LHS */ if (!*rs[i].src) - ; /* empty is ok */ - else if (check_refname_format(rs[i].src, flags)) + ; /* empty is ok; it means "HEAD" */ + else if (llen == 40 && !get_sha1_hex(rs[i].src, unused)) + rs[i].exact_sha1 = 1; /* ok */ + else if (!check_refname_format(rs[i].src, flags)) + ; /* valid looking ref is ok */ + else goto invalid; - /* - * RHS - * - missing is ok, and is same as empty. - * - empty is ok; it means not to store. - * - otherwise it must be a valid looking ref. - */ + /* RHS */ if (!rs[i].dst) - ; /* ok */ + ; /* missing is ok; it is the same as empty */ else if (!*rs[i].dst) - ; /* ok */ - else if (check_refname_format(rs[i].dst, flags)) + ; /* empty is ok; it means "do not store" */ + else if (!check_refname_format(rs[i].dst, flags)) + ; /* valid looking ref is ok */ + else goto invalid; } else { /* @@ -668,17 +667,21 @@ static int valid_remote_nick(const char *name) return !strchr(name, '/'); /* no slash */ } -struct remote *remote_get(const char *name) +static struct remote *remote_get_1(const char *name, const char *pushremote_name) { struct remote *ret; int name_given = 0; - read_config(); if (name) name_given = 1; else { - name = default_remote_name; - name_given = explicit_default_remote_name; + if (pushremote_name) { + name = pushremote_name; + name_given = 1; + } else { + name = default_remote_name; + name_given = explicit_default_remote_name; + } } ret = make_remote(name, 0); @@ -697,6 +700,18 @@ struct remote *remote_get(const char *name) return ret; } +struct remote *remote_get(const char *name) +{ + read_config(); + return remote_get_1(name, NULL); +} + +struct remote *pushremote_get(const char *name) +{ + read_config(); + return remote_get_1(name, pushremote_name); +} + int remote_is_configured(const char *name) { int i; @@ -916,6 +931,27 @@ void free_refs(struct ref *ref) } } +int ref_compare_name(const void *va, const void *vb) +{ + const struct ref *a = va, *b = vb; + return strcmp(a->name, b->name); +} + +static void *ref_list_get_next(const void *a) +{ + return ((const struct ref *)a)->next; +} + +static void ref_list_set_next(void *a, void *next) +{ + ((struct ref *)a)->next = next; +} + +void sort_ref_list(struct ref **l, int (*cmp)(const void *, const void *)) +{ + *l = llist_mergesort(*l, ref_list_get_next, ref_list_set_next, cmp); +} + static int count_refspec_match(const char *pattern, struct ref *refs, struct ref **matched_ref) @@ -978,16 +1014,20 @@ static void tail_link_ref(struct ref *ref, struct ref ***tail) *tail = &ref->next; } +static struct ref *alloc_delete_ref(void) +{ + struct ref *ref = alloc_ref("(delete)"); + hashclr(ref->new_sha1); + return ref; +} + static struct ref *try_explicit_object_name(const char *name) { unsigned char sha1[20]; struct ref *ref; - if (!*name) { - ref = alloc_ref("(delete)"); - hashclr(ref->new_sha1); - return ref; - } + if (!*name) + return alloc_delete_ref(); if (get_sha1(name, sha1)) return NULL; ref = alloc_ref(name); @@ -1072,6 +1112,9 @@ static int match_explicit(struct ref *src, struct ref *dst, case 0: if (!memcmp(dst_value, "refs/", 5)) matched_dst = make_linked_ref(dst_value, dst_tail); + else if (is_null_sha1(matched_src->new_sha1)) + error("unable to delete '%s': remote ref does not exist", + dst_value); else if ((dst_guess = guess_ref(dst_value, matched_src))) matched_dst = make_linked_ref(dst_guess, dst_tail); else @@ -1110,10 +1153,11 @@ static int match_explicit_refs(struct ref *src, struct ref *dst, return errs; } -static const struct refspec *check_pattern_match(const struct refspec *rs, - int rs_nr, - const struct ref *src) +static char *get_ref_match(const struct refspec *rs, int rs_nr, const struct ref *ref, + int send_mirror, int direction, const struct refspec **ret_pat) { + const struct refspec *pat; + char *name; int i; int matching_refs = -1; for (i = 0; i < rs_nr; i++) { @@ -1123,14 +1167,36 @@ static const struct refspec *check_pattern_match(const struct refspec *rs, continue; } - if (rs[i].pattern && match_name_with_pattern(rs[i].src, src->name, - NULL, NULL)) - return rs + i; + if (rs[i].pattern) { + const char *dst_side = rs[i].dst ? rs[i].dst : rs[i].src; + int match; + if (direction == FROM_SRC) + match = match_name_with_pattern(rs[i].src, ref->name, dst_side, &name); + else + match = match_name_with_pattern(dst_side, ref->name, rs[i].src, &name); + if (match) { + matching_refs = i; + break; + } + } } - if (matching_refs != -1) - return rs + matching_refs; - else + if (matching_refs == -1) return NULL; + + pat = rs + matching_refs; + if (pat->matching) { + /* + * "matching refs"; traditionally we pushed everything + * including refs outside refs/heads/ hierarchy, but + * that does not make much sense these days. + */ + if (!send_mirror && prefixcmp(ref->name, "refs/heads/")) + return NULL; + name = xstrdup(ref->name); + } + if (ret_pat) + *ret_pat = pat; + return name; } static struct ref **tail_ref(struct ref **head) @@ -1141,6 +1207,109 @@ static struct ref **tail_ref(struct ref **head) return tail; } +struct tips { + struct commit **tip; + int nr, alloc; +}; + +static void add_to_tips(struct tips *tips, const unsigned char *sha1) +{ + struct commit *commit; + + if (is_null_sha1(sha1)) + return; + commit = lookup_commit_reference_gently(sha1, 1); + if (!commit || (commit->object.flags & TMP_MARK)) + return; + commit->object.flags |= TMP_MARK; + ALLOC_GROW(tips->tip, tips->nr + 1, tips->alloc); + tips->tip[tips->nr++] = commit; +} + +static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***dst_tail) +{ + struct string_list dst_tag = STRING_LIST_INIT_NODUP; + struct string_list src_tag = STRING_LIST_INIT_NODUP; + struct string_list_item *item; + struct ref *ref; + struct tips sent_tips; + + /* + * Collect everything we know they would have at the end of + * this push, and collect all tags they have. + */ + memset(&sent_tips, 0, sizeof(sent_tips)); + for (ref = *dst; ref; ref = ref->next) { + if (ref->peer_ref && + !is_null_sha1(ref->peer_ref->new_sha1)) + add_to_tips(&sent_tips, ref->peer_ref->new_sha1); + else + add_to_tips(&sent_tips, ref->old_sha1); + if (!prefixcmp(ref->name, "refs/tags/")) + string_list_append(&dst_tag, ref->name); + } + clear_commit_marks_many(sent_tips.nr, sent_tips.tip, TMP_MARK); + + sort_string_list(&dst_tag); + + /* Collect tags they do not have. */ + for (ref = src; ref; ref = ref->next) { + if (prefixcmp(ref->name, "refs/tags/")) + continue; /* not a tag */ + if (string_list_has_string(&dst_tag, ref->name)) + continue; /* they already have it */ + if (sha1_object_info(ref->new_sha1, NULL) != OBJ_TAG) + continue; /* be conservative */ + item = string_list_append(&src_tag, ref->name); + item->util = ref; + } + string_list_clear(&dst_tag, 0); + + /* + * At this point, src_tag lists tags that are missing from + * dst, and sent_tips lists the tips we are pushing or those + * that we know they already have. An element in the src_tag + * that is an ancestor of any of the sent_tips needs to be + * sent to the other side. + */ + if (sent_tips.nr) { + for_each_string_list_item(item, &src_tag) { + struct ref *ref = item->util; + struct ref *dst_ref; + struct commit *commit; + + if (is_null_sha1(ref->new_sha1)) + continue; + commit = lookup_commit_reference_gently(ref->new_sha1, 1); + if (!commit) + /* not pushing a commit, which is not an error */ + continue; + + /* + * Is this tag, which they do not have, reachable from + * any of the commits we are sending? + */ + if (!in_merge_bases_many(commit, sent_tips.nr, sent_tips.tip)) + continue; + + /* Add it in */ + dst_ref = make_linked_ref(ref->name, dst_tail); + hashcpy(dst_ref->new_sha1, ref->new_sha1); + dst_ref->peer_ref = copy_ref(ref); + } + } + string_list_clear(&src_tag, 0); + free(sent_tips.tip); +} + +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 @@ -1155,9 +1324,11 @@ int match_push_refs(struct ref *src, struct ref **dst, struct refspec *rs; int send_all = flags & MATCH_REFS_ALL; int send_mirror = flags & MATCH_REFS_MIRROR; + int send_prune = flags & MATCH_REFS_PRUNE; int errs; static const char *default_refspec[] = { ":", NULL }; - struct ref **dst_tail = tail_ref(dst); + struct ref *ref, **dst_tail = tail_ref(dst); + struct string_list dst_ref_index = STRING_LIST_INIT_NODUP; if (!nr_refspec) { nr_refspec = 1; @@ -1167,39 +1338,25 @@ int match_push_refs(struct ref *src, struct ref **dst, errs = match_explicit_refs(src, *dst, &dst_tail, rs, nr_refspec); /* pick the remainder */ - for ( ; src; src = src->next) { + 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; - if (src->peer_ref) - continue; - pat = check_pattern_match(rs, nr_refspec, src); - if (!pat) + dst_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_SRC, &pat); + if (!dst_name) 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. - */ - if (!send_mirror && prefixcmp(src->name, "refs/heads/")) - continue; - dst_name = xstrdup(src->name); + if (!dst_ref_index.nr) + prepare_ref_index(&dst_ref_index, *dst); - } else { - const char *dst_side = pat->dst ? pat->dst : pat->src; - if (!match_name_with_pattern(pat->src, src->name, - dst_side, &dst_name)) - die("Didn't think it matches any more"); - } - dst_peer = find_ref_by_name(*dst, dst_name); + 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. */ goto free_name; - } else { if (pat->matching && !(send_all || send_mirror)) /* @@ -1211,13 +1368,43 @@ int match_push_refs(struct ref *src, struct ref **dst, /* Create a new one and link it */ dst_peer = make_linked_ref(dst_name, &dst_tail); - hashcpy(dst_peer->new_sha1, src->new_sha1); + 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(src); + dst_peer->peer_ref = copy_ref(ref); dst_peer->force = pat->force; free_name: 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; + + if (ref->peer_ref) + /* We're already sending something to this ref. */ + continue; + + src_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_DST, NULL); + if (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; return 0; @@ -1229,6 +1416,8 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, struct ref *ref; for (ref = remote_refs; ref; ref = ref->next) { + int force_ref_update = ref->force || force_update; + if (ref->peer_ref) hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); else if (!send_mirror) @@ -1241,34 +1430,41 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, continue; } - /* This part determines what can overwrite what. - * The rules are: - * - * (0) you can always use --force or +A:B notation to - * selectively force individual ref pairs. + /* + * Decide whether an individual refspec A:B can be + * pushed. The push will succeed if any of the + * following are true: * - * (1) if the old thing does not exist, it is OK. + * (1) the remote reference B does not exist * - * (2) if you do not have the old thing, you are not allowed - * to overwrite it; you would not know what you are losing - * otherwise. + * (2) the remote reference B is being removed (i.e., + * pushing :B where no source is specified) * - * (3) if both new and old are commit-ish, and new is a - * descendant of old, it is OK. + * (3) the destination is not under refs/tags/, and + * if the old and new value is a commit, the new + * is a descendant of the old. * - * (4) regardless of all of the above, removing :B is - * always allowed. + * (4) it is forced using the +A:B notation, or by + * passing the --force argument */ - ref->nonfastforward = - !ref->deletion && - !is_null_sha1(ref->old_sha1) && - (!has_sha1_file(ref->old_sha1) - || !ref_newer(ref->new_sha1, ref->old_sha1)); - - if (ref->nonfastforward && !ref->force && !force_update) { - ref->status = REF_STATUS_REJECT_NONFASTFORWARD; - continue; + if (!ref->deletion && !is_null_sha1(ref->old_sha1)) { + int why = 0; /* why would this push require --force? */ + + if (!prefixcmp(ref->name, "refs/tags/")) + why = REF_STATUS_REJECT_ALREADY_EXISTS; + else if (!has_sha1_file(ref->old_sha1)) + why = 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; + 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; } } } @@ -1286,8 +1482,7 @@ struct branch *branch_get(const char *name) ret->remote = remote_get(ret->remote_name); if (ret->merge_nr) { int i; - ret->merge = xcalloc(sizeof(*ret->merge), - ret->merge_nr); + ret->merge = xcalloc(ret->merge_nr, sizeof(*ret->merge)); for (i = 0; i < ret->merge_nr; i++) { ret->merge[i] = xcalloc(1, sizeof(**ret->merge)); ret->merge[i]->src = xstrdup(ret->merge_name[i]); @@ -1314,6 +1509,16 @@ int branch_merge_matches(struct branch *branch, return refname_match(branch->merge[i]->src, refname, ref_fetch_rules); } +static int ignore_symref_update(const char *refname) +{ + unsigned char sha1[20]; + int flag; + + if (!resolve_ref_unsafe(refname, sha1, 0, &flag)) + return 0; /* non-existing refs are OK */ + return (flag & REF_ISSYMREF); +} + static struct ref *get_expanded_map(const struct ref *remote_refs, const struct refspec *refspec) { @@ -1327,7 +1532,8 @@ static struct ref *get_expanded_map(const struct ref *remote_refs, if (strchr(ref->name, '^')) continue; /* a dereference item */ if (match_name_with_pattern(refspec->src, ref->name, - refspec->dst, &expn_name)) { + refspec->dst, &expn_name) && + !ignore_symref_update(expn_name)) { struct ref *cpy = copy_ref(ref); cpy->peer_ref = alloc_ref(expn_name); @@ -1390,7 +1596,12 @@ int get_fetch_map(const struct ref *remote_refs, } else { const char *name = refspec->src[0] ? refspec->src : "HEAD"; - ref_map = get_remote_ref(remote_refs, name); + if (refspec->exact_sha1) { + ref_map = alloc_ref(name); + get_sha1_hex(name, ref_map->old_sha1); + } else { + ref_map = get_remote_ref(remote_refs, name); + } if (!missing_ok && !ref_map) die("Couldn't find remote ref %s", name); if (ref_map) { @@ -1402,8 +1613,8 @@ int get_fetch_map(const struct ref *remote_refs, for (rmp = &ref_map; *rmp; ) { if ((*rmp)->peer_ref) { - if (check_refname_format((*rmp)->peer_ref->name + 5, - REFNAME_ALLOW_ONELEVEL)) { + if (prefixcmp((*rmp)->peer_ref->name, "refs/") || + check_refname_format((*rmp)->peer_ref->name, 0)) { struct ref *ignore = *rmp; error("* Ignoring funny ref '%s' locally", (*rmp)->peer_ref->name); @@ -1451,7 +1662,8 @@ int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1) struct commit_list *list, *used; int found = 0; - /* Both new and old must be commit-ish and new is descendant of + /* + * Both new and old must be commit-ish and new is descendant of * old. Otherwise we require --force. */ o = deref_tag(parse_object(old_sha1), NULL, 0); @@ -1571,20 +1783,40 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb) base = branch->merge[0]->dst; base = shorten_unambiguous_ref(base, 0); - 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); + if (!num_theirs) { + strbuf_addf(sb, + Q_("Your branch is ahead of '%s' by %d commit.\n", + "Your branch is ahead of '%s' by %d commits.\n", + num_ours), + base, num_ours); + if (advice_status_hints) + strbuf_addf(sb, + _(" (use \"git push\" to publish your local commits)\n")); + } else if (!num_ours) { + strbuf_addf(sb, + Q_("Your branch is behind '%s' by %d commit, " + "and can be fast-forwarded.\n", + "Your branch is behind '%s' by %d commits, " + "and can be fast-forwarded.\n", + num_theirs), + base, num_theirs); + if (advice_status_hints) + strbuf_addf(sb, + _(" (use \"git pull\" to update your local branch)\n")); + } else { + strbuf_addf(sb, + Q_("Your branch and '%s' have diverged,\n" + "and have %d and %d different commit each, " + "respectively.\n", + "Your branch and '%s' have diverged,\n" + "and have %d and %d different commits each, " + "respectively.\n", + num_theirs), + base, num_ours, num_theirs); + if (advice_status_hints) + strbuf_addf(sb, + _(" (use \"git pull\" to merge the remote branch into yours)\n")); + } return 1; } |