aboutsummaryrefslogtreecommitdiff
path: root/remote.c
diff options
context:
space:
mode:
Diffstat (limited to 'remote.c')
-rw-r--r--remote.c307
1 files changed, 189 insertions, 118 deletions
diff --git a/remote.c b/remote.c
index df9675cd33..a664cd166a 100644
--- a/remote.c
+++ b/remote.c
@@ -29,6 +29,12 @@
enum map_direction { FROM_SRC, FROM_DST };
+enum {
+ ENABLE_ADVICE_PULL = (1 << 0),
+ ENABLE_ADVICE_PUSH = (1 << 1),
+ ENABLE_ADVICE_DIVERGENCE = (1 << 2),
+};
+
struct counted_string {
size_t len;
const char *s;
@@ -272,6 +278,7 @@ static void branch_release(struct branch *branch)
free((char *)branch->refname);
free(branch->remote_name);
free(branch->pushremote_name);
+ free(branch->push_tracking_ref);
merge_clear(branch);
}
@@ -1381,12 +1388,7 @@ static struct ref **tail_ref(struct ref **head)
return tail;
}
-struct tips {
- struct commit **tip;
- size_t nr, alloc;
-};
-
-static void add_to_tips(struct tips *tips, const struct object_id *oid)
+static void add_to_tips(struct commit_stack *tips, const struct object_id *oid)
{
struct commit *commit;
@@ -1396,8 +1398,7 @@ static void add_to_tips(struct tips *tips, const struct object_id *oid)
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;
+ commit_stack_push(tips, commit);
}
static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***dst_tail)
@@ -1406,13 +1407,12 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
struct string_list src_tag = STRING_LIST_INIT_NODUP;
struct string_list_item *item;
struct ref *ref;
- struct tips sent_tips;
+ struct commit_stack sent_tips = COMMIT_STACK_INIT;
/*
* 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_oid(&ref->peer_ref->new_oid))
@@ -1422,7 +1422,7 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
if (starts_with(ref->name, "refs/tags/"))
string_list_append(&dst_tag, ref->name);
}
- clear_commit_marks_many(sent_tips.nr, sent_tips.tip, TMP_MARK);
+ clear_commit_marks_many(sent_tips.nr, sent_tips.items, TMP_MARK);
string_list_sort(&dst_tag);
@@ -1450,9 +1450,7 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
if (sent_tips.nr) {
const int reachable_flag = 1;
struct commit_list *found_commits;
- struct commit **src_commits;
- size_t nr_src_commits = 0, alloc_src_commits = 16;
- ALLOC_ARRAY(src_commits, alloc_src_commits);
+ struct commit_stack src_commits = COMMIT_STACK_INIT;
for_each_string_list_item(item, &src_tag) {
struct ref *ref = item->util;
@@ -1467,12 +1465,13 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
/* not pushing a commit, which is not an error */
continue;
- ALLOC_GROW(src_commits, nr_src_commits + 1, alloc_src_commits);
- src_commits[nr_src_commits++] = commit;
+ commit_stack_push(&src_commits, commit);
}
- found_commits = get_reachable_subset(sent_tips.tip, sent_tips.nr,
- src_commits, nr_src_commits,
+ found_commits = get_reachable_subset(sent_tips.items,
+ sent_tips.nr,
+ src_commits.items,
+ src_commits.nr,
reachable_flag);
for_each_string_list_item(item, &src_tag) {
@@ -1502,13 +1501,14 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
dst_ref->peer_ref = copy_ref(ref);
}
- clear_commit_marks_many(nr_src_commits, src_commits, reachable_flag);
- free(src_commits);
- free_commit_list(found_commits);
+ clear_commit_marks_many(src_commits.nr, src_commits.items,
+ reachable_flag);
+ commit_stack_clear(&src_commits);
+ commit_list_free(found_commits);
}
string_list_clear(&src_tag, 0);
- free(sent_tips.tip);
+ commit_stack_clear(&sent_tips);
}
struct ref *find_ref_by_name(const struct ref *list, const char *name)
@@ -1723,7 +1723,7 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
if (!reject_reason && !ref->deletion && !is_null_oid(&ref->old_oid)) {
if (starts_with(ref->name, "refs/tags/"))
reject_reason = REF_STATUS_REJECT_ALREADY_EXISTS;
- else if (!odb_has_object(the_repository->objects, &ref->old_oid, HAS_OBJECT_RECHECK_PACKED))
+ else if (!odb_has_object(the_repository->objects, &ref->old_oid, ODB_HAS_OBJECT_RECHECK_PACKED))
reject_reason = REF_STATUS_REJECT_FETCH_FIRST;
else if (!lookup_commit_reference_gently(the_repository, &ref->old_oid, 1) ||
!lookup_commit_reference_gently(the_repository, &ref->new_oid, 1))
@@ -1838,7 +1838,7 @@ int branch_merge_matches(struct branch *branch,
}
__attribute__((format (printf,2,3)))
-static const char *error_buf(struct strbuf *err, const char *fmt, ...)
+static char *error_buf(struct strbuf *err, const char *fmt, ...)
{
if (err) {
va_list ap;
@@ -1876,9 +1876,9 @@ const char *branch_get_upstream(struct branch *branch, struct strbuf *err)
return branch->merge[0]->dst;
}
-static const char *tracking_for_push_dest(struct remote *remote,
- const char *refname,
- struct strbuf *err)
+static char *tracking_for_push_dest(struct remote *remote,
+ const char *refname,
+ struct strbuf *err)
{
char *ret;
@@ -1890,8 +1890,8 @@ static const char *tracking_for_push_dest(struct remote *remote,
return ret;
}
-static const char *branch_get_push_1(struct repository *repo,
- struct branch *branch, struct strbuf *err)
+static char *branch_get_push_1(struct repository *repo,
+ struct branch *branch, struct strbuf *err)
{
struct remote_state *remote_state = repo->remote_state;
struct remote *remote;
@@ -1906,7 +1906,7 @@ static const char *branch_get_push_1(struct repository *repo,
if (remote->push.nr) {
char *dst;
- const char *ret;
+ char *ret;
dst = apply_refspecs(&remote->push, branch->refname);
if (!dst)
@@ -1931,12 +1931,13 @@ static const char *branch_get_push_1(struct repository *repo,
return tracking_for_push_dest(remote, branch->refname, err);
case PUSH_DEFAULT_UPSTREAM:
- return branch_get_upstream(branch, err);
+ return xstrdup_or_null(branch_get_upstream(branch, err));
case PUSH_DEFAULT_UNSPECIFIED:
case PUSH_DEFAULT_SIMPLE:
{
- const char *up, *cur;
+ const char *up;
+ char *cur;
up = branch_get_upstream(branch, err);
if (!up)
@@ -1944,9 +1945,11 @@ static const char *branch_get_push_1(struct repository *repo,
cur = tracking_for_push_dest(remote, branch->refname, err);
if (!cur)
return NULL;
- if (strcmp(cur, up))
+ if (strcmp(cur, up)) {
+ free(cur);
return error_buf(err,
_("cannot resolve 'simple' push to a single destination"));
+ }
return cur;
}
}
@@ -2237,43 +2240,49 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
- enum ahead_behind_flags abf,
- int show_divergence_advice)
+static char *resolve_compare_branch(struct branch *branch, const char *name)
{
- int ours, theirs, sti;
- const char *full_base;
- char *base;
- int upstream_is_gone = 0;
+ const char *resolved = NULL;
- sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
- if (sti < 0) {
- if (!full_base)
- return 0;
- upstream_is_gone = 1;
+ if (!branch || !name)
+ return NULL;
+
+ if (!strcasecmp(name, "@{upstream}")) {
+ resolved = branch_get_upstream(branch, NULL);
+ } else if (!strcasecmp(name, "@{push}")) {
+ resolved = branch_get_push(branch, NULL);
+ } else {
+ warning(_("ignoring value '%s' for status.compareBranches, "
+ "only @{upstream} and @{push} are supported"),
+ name);
+ return NULL;
}
- base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
- full_base, 0);
- if (upstream_is_gone) {
- strbuf_addf(sb,
- _("Your branch is based on '%s', but the upstream is gone.\n"),
- base);
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git branch --unset-upstream\" to fixup)\n"));
- } else if (!sti) {
+ if (resolved)
+ return xstrdup(resolved);
+ return NULL;
+}
+
+static void format_branch_comparison(struct strbuf *sb,
+ bool up_to_date,
+ int ours, int theirs,
+ const char *branch_name,
+ enum ahead_behind_flags abf,
+ unsigned flags)
+{
+ bool use_push_advice = (flags & ENABLE_ADVICE_PUSH);
+ bool use_pull_advice = (flags & ENABLE_ADVICE_PULL);
+ bool use_divergence_advice = (flags & ENABLE_ADVICE_DIVERGENCE);
+
+ if (up_to_date) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
- base);
+ branch_name);
} else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
- base);
- if (advice_enabled(ADVICE_STATUS_HINTS))
+ branch_name);
+ if (use_push_advice && advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
} else if (!theirs) {
@@ -2281,8 +2290,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
ours),
- base, ours);
- if (advice_enabled(ADVICE_STATUS_HINTS))
+ branch_name, ours);
+ if (use_push_advice && advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours) {
@@ -2292,8 +2301,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"Your branch is behind '%s' by %d commits, "
"and can be fast-forwarded.\n",
theirs),
- base, theirs);
- if (advice_enabled(ADVICE_STATUS_HINTS))
+ branch_name, theirs);
+ if (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
@@ -2305,31 +2314,121 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"and have %d and %d different commits each, "
"respectively.\n",
ours + theirs),
- base, ours, theirs);
- if (show_divergence_advice &&
- advice_enabled(ADVICE_STATUS_HINTS))
+ branch_name, ours, theirs);
+ if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
- free(base);
- return 1;
}
-static int one_local_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
- int flag UNUSED,
- void *cb_data)
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ enum ahead_behind_flags abf,
+ int show_divergence_advice)
+{
+ char *compare_branches = NULL;
+ struct string_list branches = STRING_LIST_INIT_DUP;
+ struct strset processed_refs = STRSET_INIT;
+ int reported = 0;
+ size_t i;
+ const char *upstream_ref;
+ const char *push_ref;
+
+ repo_config_get_string(the_repository, "status.comparebranches",
+ &compare_branches);
+
+ if (compare_branches) {
+ string_list_split(&branches, compare_branches, " ", -1);
+ string_list_remove_empty_items(&branches, 0);
+ } else {
+ string_list_append(&branches, "@{upstream}");
+ }
+
+ upstream_ref = branch_get_upstream(branch, NULL);
+ push_ref = branch_get_push(branch, NULL);
+
+ for (i = 0; i < branches.nr; i++) {
+ char *full_ref;
+ char *short_ref;
+ int ours, theirs, cmp;
+ int is_upstream, is_push;
+ unsigned flags = 0;
+
+ full_ref = resolve_compare_branch(branch,
+ branches.items[i].string);
+ if (!full_ref)
+ continue;
+
+ if (!strset_add(&processed_refs, full_ref)) {
+ free(full_ref);
+ continue;
+ }
+
+ short_ref = refs_shorten_unambiguous_ref(
+ get_main_ref_store(the_repository), full_ref, 0);
+
+ is_upstream = upstream_ref && !strcmp(full_ref, upstream_ref);
+ is_push = push_ref && !strcmp(full_ref, push_ref);
+
+ if (is_upstream && (!push_ref || !strcmp(upstream_ref, push_ref)))
+ is_push = 1;
+
+ cmp = stat_branch_pair(branch->refname, full_ref,
+ &ours, &theirs, abf);
+
+ if (cmp < 0) {
+ if (is_upstream) {
+ strbuf_addf(sb,
+ _("Your branch is based on '%s', but the upstream is gone.\n"),
+ short_ref);
+ if (advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addstr(sb,
+ _(" (use \"git branch --unset-upstream\" to fixup)\n"));
+ reported = 1;
+ }
+ free(full_ref);
+ free(short_ref);
+ continue;
+ }
+
+ if (reported)
+ strbuf_addstr(sb, "\n");
+
+ if (is_upstream)
+ flags |= ENABLE_ADVICE_PULL;
+ if (is_push)
+ flags |= ENABLE_ADVICE_PUSH;
+ if (show_divergence_advice && is_upstream)
+ flags |= ENABLE_ADVICE_DIVERGENCE;
+ format_branch_comparison(sb, !cmp, ours, theirs, short_ref,
+ abf, flags);
+ reported = 1;
+
+ free(full_ref);
+ free(short_ref);
+ }
+
+ string_list_clear(&branches, 0);
+ strset_clear(&processed_refs);
+ free(compare_branches);
+ return reported;
+}
+
+static int one_local_ref(const struct reference *ref, void *cb_data)
{
struct ref ***local_tail = cb_data;
- struct ref *ref;
+ struct ref *local_ref;
/* we already know it starts with refs/ to get here */
- if (check_refname_format(refname + 5, 0))
+ if (check_refname_format(ref->name + 5, 0))
return 0;
- ref = alloc_ref(refname);
- oidcpy(&ref->new_oid, oid);
- **local_tail = ref;
- *local_tail = &ref->next;
+ local_ref = alloc_ref(ref->name);
+ oidcpy(&local_ref->new_oid, ref->oid);
+ **local_tail = local_ref;
+ *local_tail = &local_ref->next;
return 0;
}
@@ -2402,15 +2501,14 @@ struct stale_heads_info {
struct refspec *rs;
};
-static int get_stale_heads_cb(const char *refname, const char *referent UNUSED, const struct object_id *oid,
- int flags, void *cb_data)
+static int get_stale_heads_cb(const struct reference *ref, void *cb_data)
{
struct stale_heads_info *info = cb_data;
struct string_list matches = STRING_LIST_INIT_DUP;
struct refspec_item query;
int i, stale = 1;
memset(&query, 0, sizeof(struct refspec_item));
- query.dst = (char *)refname;
+ query.dst = (char *)ref->name;
refspec_find_all_matches(info->rs, &query, &matches);
if (matches.nr == 0)
@@ -2423,7 +2521,7 @@ static int get_stale_heads_cb(const char *refname, const char *referent UNUSED,
* overlapping refspecs, we need to go over all of the
* matching refs.
*/
- if (flags & REF_ISSYMREF)
+ if (ref->flags & REF_ISSYMREF)
goto clean_exit;
for (i = 0; stale && i < matches.nr; i++)
@@ -2431,8 +2529,8 @@ static int get_stale_heads_cb(const char *refname, const char *referent UNUSED,
stale = 0;
if (stale) {
- struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
- oidcpy(&ref->new_oid, oid);
+ struct ref *linked_ref = make_linked_ref(ref->name, &info->stale_refs_tail);
+ oidcpy(&linked_ref->new_oid, ref->oid);
}
clean_exit:
@@ -2547,36 +2645,9 @@ static int remote_tracking(struct remote *remote, const char *refname,
return 0;
}
-/*
- * The struct "reflog_commit_array" and related helper functions
- * are used for collecting commits into an array during reflog
- * traversals in "check_and_collect_until()".
- */
-struct reflog_commit_array {
- struct commit **item;
- size_t nr, alloc;
-};
-
-#define REFLOG_COMMIT_ARRAY_INIT { 0 }
-
-/* Append a commit to the array. */
-static void append_commit(struct reflog_commit_array *arr,
- struct commit *commit)
-{
- ALLOC_GROW(arr->item, arr->nr + 1, arr->alloc);
- arr->item[arr->nr++] = commit;
-}
-
-/* Free and reset the array. */
-static void free_commit_array(struct reflog_commit_array *arr)
-{
- FREE_AND_NULL(arr->item);
- arr->nr = arr->alloc = 0;
-}
-
struct check_and_collect_until_cb_data {
struct commit *remote_commit;
- struct reflog_commit_array *local_commits;
+ struct commit_stack *local_commits;
timestamp_t remote_reflog_timestamp;
};
@@ -2608,7 +2679,7 @@ static int check_and_collect_until(const char *refname UNUSED,
return 1;
if ((commit = lookup_commit_reference(the_repository, n_oid)))
- append_commit(cb->local_commits, commit);
+ commit_stack_push(cb->local_commits, commit);
/*
* If the reflog entry timestamp is older than the remote ref's
@@ -2636,7 +2707,7 @@ static int is_reachable_in_reflog(const char *local, const struct ref *remote)
struct commit *commit;
struct commit **chunk;
struct check_and_collect_until_cb_data cb;
- struct reflog_commit_array arr = REFLOG_COMMIT_ARRAY_INIT;
+ struct commit_stack arr = COMMIT_STACK_INIT;
size_t size = 0;
int ret = 0;
@@ -2667,8 +2738,8 @@ static int is_reachable_in_reflog(const char *local, const struct ref *remote)
* Check if the remote commit is reachable from any
* of the commits in the collected array, in batches.
*/
- for (chunk = arr.item; chunk < arr.item + arr.nr; chunk += size) {
- size = arr.item + arr.nr - chunk;
+ for (chunk = arr.items; chunk < arr.items + arr.nr; chunk += size) {
+ size = arr.items + arr.nr - chunk;
if (MERGE_BASES_BATCH_SIZE < size)
size = MERGE_BASES_BATCH_SIZE;
@@ -2677,7 +2748,7 @@ static int is_reachable_in_reflog(const char *local, const struct ref *remote)
}
cleanup_return:
- free_commit_array(&arr);
+ commit_stack_clear(&arr);
return ret;
}