summaryrefslogtreecommitdiff
path: root/builtin/fetch.c
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/fetch.c')
-rw-r--r--builtin/fetch.c330
1 files changed, 263 insertions, 67 deletions
diff --git a/builtin/fetch.c b/builtin/fetch.c
index ecf8537605..9191620e50 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -48,6 +48,7 @@ enum {
static int fetch_prune_config = -1; /* unspecified */
static int fetch_show_forced_updates = 1;
static uint64_t forced_updates_ms = 0;
+static int prefetch = 0;
static int prune = -1; /* unspecified */
#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
@@ -63,6 +64,7 @@ static int enable_auto_gc = 1;
static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
static int max_jobs = -1, submodule_fetch_jobs_config = -1;
static int fetch_parallel_config = 1;
+static int atomic_fetch;
static enum transport_family family;
static const char *depth;
static const char *deepen_since;
@@ -81,6 +83,7 @@ static struct string_list server_options = STRING_LIST_INIT_DUP;
static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP;
static int fetch_write_commit_graph = -1;
static int stdin_refspecs = 0;
+static int negotiate_only;
static int git_fetch_config(const char *k, const char *v, void *cb)
{
@@ -144,6 +147,8 @@ static struct option builtin_fetch_options[] = {
N_("set upstream for git pull/fetch")),
OPT_BOOL('a', "append", &append,
N_("append to .git/FETCH_HEAD instead of overwriting")),
+ OPT_BOOL(0, "atomic", &atomic_fetch,
+ N_("use atomic transaction to update references")),
OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
N_("path to upload pack on remote end")),
OPT__FORCE(&force, N_("force overwrite of local reference"), 0),
@@ -155,6 +160,8 @@ static struct option builtin_fetch_options[] = {
N_("do not fetch all tags (--no-tags)"), TAGS_UNSET),
OPT_INTEGER('j', "jobs", &max_jobs,
N_("number of submodules fetched in parallel")),
+ OPT_BOOL(0, "prefetch", &prefetch,
+ N_("modify the refspec to place all refs within refs/prefetch/")),
OPT_BOOL('p', "prune", &prune,
N_("prune remote-tracking branches no longer on remote")),
OPT_BOOL('P', "prune-tags", &prune_tags,
@@ -199,6 +206,8 @@ static struct option builtin_fetch_options[] = {
TRANSPORT_FAMILY_IPV6),
OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"),
N_("report that we have only objects reachable from this object")),
+ OPT_BOOL(0, "negotiate-only", &negotiate_only,
+ N_("do not fetch a packfile; instead, print ancestors of negotiation tips")),
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
OPT_BOOL(0, "auto-maintenance", &enable_auto_gc,
N_("run 'maintenance --auto' after fetching")),
@@ -433,6 +442,56 @@ static void find_non_local_tags(const struct ref *refs,
oidset_clear(&fetch_oids);
}
+static void filter_prefetch_refspec(struct refspec *rs)
+{
+ int i;
+
+ if (!prefetch)
+ return;
+
+ for (i = 0; i < rs->nr; i++) {
+ struct strbuf new_dst = STRBUF_INIT;
+ char *old_dst;
+ const char *sub = NULL;
+
+ if (rs->items[i].negative)
+ continue;
+ if (!rs->items[i].dst ||
+ (rs->items[i].src &&
+ !strncmp(rs->items[i].src, "refs/tags/", 10))) {
+ int j;
+
+ free(rs->items[i].src);
+ free(rs->items[i].dst);
+
+ for (j = i + 1; j < rs->nr; j++) {
+ rs->items[j - 1] = rs->items[j];
+ rs->raw[j - 1] = rs->raw[j];
+ }
+ rs->nr--;
+ i--;
+ continue;
+ }
+
+ old_dst = rs->items[i].dst;
+ strbuf_addstr(&new_dst, "refs/prefetch/");
+
+ /*
+ * If old_dst starts with "refs/", then place
+ * sub after that prefix. Otherwise, start at
+ * the beginning of the string.
+ */
+ if (!skip_prefix(old_dst, "refs/", &sub))
+ sub = old_dst;
+ strbuf_addstr(&new_dst, sub);
+
+ rs->items[i].dst = strbuf_detach(&new_dst, NULL);
+ rs->items[i].force = 1;
+
+ free(old_dst);
+ }
+}
+
static struct ref *get_ref_map(struct remote *remote,
const struct ref *remote_refs,
struct refspec *rs,
@@ -449,6 +508,10 @@ static struct ref *get_ref_map(struct remote *remote,
struct hashmap existing_refs;
int existing_refs_populated = 0;
+ filter_prefetch_refspec(rs);
+ if (remote)
+ filter_prefetch_refspec(&remote->fetch);
+
if (rs->nr) {
struct refspec *fetch_refspec;
@@ -517,7 +580,7 @@ static struct ref *get_ref_map(struct remote *remote,
if (has_merge &&
!strcmp(branch->remote_name, remote->name))
add_merge_config(&ref_map, remote_refs, branch, &tail);
- } else {
+ } else if (!prefetch) {
ref_map = get_remote_ref(remote_refs, "HEAD");
if (!ref_map)
die(_("Couldn't find remote ref HEAD"));
@@ -583,13 +646,14 @@ static struct ref *get_ref_map(struct remote *remote,
static int s_update_ref(const char *action,
struct ref *ref,
+ struct ref_transaction *transaction,
int check_old)
{
char *msg;
char *rla = getenv("GIT_REFLOG_ACTION");
- struct ref_transaction *transaction;
+ struct ref_transaction *our_transaction = NULL;
struct strbuf err = STRBUF_INIT;
- int ret, df_conflict = 0;
+ int ret;
if (dry_run)
return 0;
@@ -597,31 +661,47 @@ static int s_update_ref(const char *action,
rla = default_rla.buf;
msg = xstrfmt("%s: %s", rla, action);
- transaction = ref_transaction_begin(&err);
- if (!transaction ||
- ref_transaction_update(transaction, ref->name,
- &ref->new_oid,
- check_old ? &ref->old_oid : NULL,
- 0, msg, &err))
- goto fail;
+ /*
+ * If no transaction was passed to us, we manage the transaction
+ * ourselves. Otherwise, we trust the caller to handle the transaction
+ * lifecycle.
+ */
+ if (!transaction) {
+ transaction = our_transaction = ref_transaction_begin(&err);
+ if (!transaction) {
+ ret = STORE_REF_ERROR_OTHER;
+ goto out;
+ }
+ }
- ret = ref_transaction_commit(transaction, &err);
+ ret = ref_transaction_update(transaction, ref->name, &ref->new_oid,
+ check_old ? &ref->old_oid : NULL,
+ 0, msg, &err);
if (ret) {
- df_conflict = (ret == TRANSACTION_NAME_CONFLICT);
- goto fail;
+ ret = STORE_REF_ERROR_OTHER;
+ goto out;
}
- ref_transaction_free(transaction);
- strbuf_release(&err);
- free(msg);
- return 0;
-fail:
- ref_transaction_free(transaction);
- error("%s", err.buf);
+ if (our_transaction) {
+ switch (ref_transaction_commit(our_transaction, &err)) {
+ case 0:
+ break;
+ case TRANSACTION_NAME_CONFLICT:
+ ret = STORE_REF_ERROR_DF_CONFLICT;
+ goto out;
+ default:
+ ret = STORE_REF_ERROR_OTHER;
+ goto out;
+ }
+ }
+
+out:
+ ref_transaction_free(our_transaction);
+ if (ret)
+ error("%s", err.buf);
strbuf_release(&err);
free(msg);
- return df_conflict ? STORE_REF_ERROR_DF_CONFLICT
- : STORE_REF_ERROR_OTHER;
+ return ret;
}
static int refcol_width = 10;
@@ -759,6 +839,7 @@ static void format_display(struct strbuf *display, char code,
}
static int update_local_ref(struct ref *ref,
+ struct ref_transaction *transaction,
const char *remote,
const struct ref *remote_ref,
struct strbuf *display,
@@ -799,7 +880,7 @@ static int update_local_ref(struct ref *ref,
starts_with(ref->name, "refs/tags/")) {
if (force || ref->force) {
int r;
- r = s_update_ref("updating tag", ref, 0);
+ r = s_update_ref("updating tag", ref, transaction, 0);
format_display(display, r ? '!' : 't', _("[tag update]"),
r ? _("unable to update local ref") : NULL,
remote, pretty_ref, summary_width);
@@ -836,7 +917,7 @@ static int update_local_ref(struct ref *ref,
what = _("[new ref]");
}
- r = s_update_ref(msg, ref, 0);
+ r = s_update_ref(msg, ref, transaction, 0);
format_display(display, r ? '!' : '*', what,
r ? _("unable to update local ref") : NULL,
remote, pretty_ref, summary_width);
@@ -858,7 +939,7 @@ static int update_local_ref(struct ref *ref,
strbuf_add_unique_abbrev(&quickref, &current->object.oid, DEFAULT_ABBREV);
strbuf_addstr(&quickref, "..");
strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV);
- r = s_update_ref("fast-forward", ref, 1);
+ r = s_update_ref("fast-forward", ref, transaction, 1);
format_display(display, r ? '!' : ' ', quickref.buf,
r ? _("unable to update local ref") : NULL,
remote, pretty_ref, summary_width);
@@ -870,7 +951,7 @@ static int update_local_ref(struct ref *ref,
strbuf_add_unique_abbrev(&quickref, &current->object.oid, DEFAULT_ABBREV);
strbuf_addstr(&quickref, "...");
strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV);
- r = s_update_ref("forced-update", ref, 1);
+ r = s_update_ref("forced-update", ref, transaction, 1);
format_display(display, r ? '!' : '+', quickref.buf,
r ? _("unable to update local ref") : _("forced update"),
remote, pretty_ref, summary_width);
@@ -897,6 +978,89 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid)
return 0;
}
+struct fetch_head {
+ FILE *fp;
+ struct strbuf buf;
+};
+
+static int open_fetch_head(struct fetch_head *fetch_head)
+{
+ const char *filename = git_path_fetch_head(the_repository);
+
+ if (write_fetch_head) {
+ fetch_head->fp = fopen(filename, "a");
+ if (!fetch_head->fp)
+ return error_errno(_("cannot open %s"), filename);
+ strbuf_init(&fetch_head->buf, 0);
+ } else {
+ fetch_head->fp = NULL;
+ }
+
+ return 0;
+}
+
+static void append_fetch_head(struct fetch_head *fetch_head,
+ const struct object_id *old_oid,
+ enum fetch_head_status fetch_head_status,
+ const char *note,
+ const char *url, size_t url_len)
+{
+ char old_oid_hex[GIT_MAX_HEXSZ + 1];
+ const char *merge_status_marker;
+ size_t i;
+
+ if (!fetch_head->fp)
+ return;
+
+ switch (fetch_head_status) {
+ case FETCH_HEAD_NOT_FOR_MERGE:
+ merge_status_marker = "not-for-merge";
+ break;
+ case FETCH_HEAD_MERGE:
+ merge_status_marker = "";
+ break;
+ default:
+ /* do not write anything to FETCH_HEAD */
+ return;
+ }
+
+ strbuf_addf(&fetch_head->buf, "%s\t%s\t%s",
+ oid_to_hex_r(old_oid_hex, old_oid), merge_status_marker, note);
+ for (i = 0; i < url_len; ++i)
+ if ('\n' == url[i])
+ strbuf_addstr(&fetch_head->buf, "\\n");
+ else
+ strbuf_addch(&fetch_head->buf, url[i]);
+ strbuf_addch(&fetch_head->buf, '\n');
+
+ /*
+ * When using an atomic fetch, we do not want to update FETCH_HEAD if
+ * any of the reference updates fails. We thus have to write all
+ * updates to a buffer first and only commit it as soon as all
+ * references have been successfully updated.
+ */
+ if (!atomic_fetch) {
+ strbuf_write(&fetch_head->buf, fetch_head->fp);
+ strbuf_reset(&fetch_head->buf);
+ }
+}
+
+static void commit_fetch_head(struct fetch_head *fetch_head)
+{
+ if (!fetch_head->fp || !atomic_fetch)
+ return;
+ strbuf_write(&fetch_head->buf, fetch_head->fp);
+}
+
+static void close_fetch_head(struct fetch_head *fetch_head)
+{
+ if (!fetch_head->fp)
+ return;
+
+ fclose(fetch_head->fp);
+ strbuf_release(&fetch_head->buf);
+}
+
static const char warn_show_forced_updates[] =
N_("Fetch normally indicates which branches had a forced update,\n"
"but that check has been disabled. To re-enable, use '--show-forced-updates'\n"
@@ -909,22 +1073,20 @@ N_("It took %.2f seconds to check forced updates. You can use\n"
static int store_updated_refs(const char *raw_url, const char *remote_name,
int connectivity_checked, struct ref *ref_map)
{
- FILE *fp;
+ struct fetch_head fetch_head;
struct commit *commit;
int url_len, i, rc = 0;
- struct strbuf note = STRBUF_INIT;
+ struct strbuf note = STRBUF_INIT, err = STRBUF_INIT;
+ struct ref_transaction *transaction = NULL;
const char *what, *kind;
struct ref *rm;
char *url;
- const char *filename = (!write_fetch_head
- ? "/dev/null"
- : git_path_fetch_head(the_repository));
int want_status;
int summary_width = transport_summary_width(ref_map);
- fp = fopen(filename, "a");
- if (!fp)
- return error_errno(_("cannot open %s"), filename);
+ rc = open_fetch_head(&fetch_head);
+ if (rc)
+ return -1;
if (raw_url)
url = transport_anonymize_url(raw_url);
@@ -941,6 +1103,14 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
}
}
+ if (atomic_fetch) {
+ transaction = ref_transaction_begin(&err);
+ if (!transaction) {
+ error("%s", err.buf);
+ goto abort;
+ }
+ }
+
prepare_format_display(ref_map);
/*
@@ -953,11 +1123,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
want_status++) {
for (rm = ref_map; rm; rm = rm->next) {
struct ref *ref = NULL;
- const char *merge_status_marker = "";
if (rm->status == REF_STATUS_REJECT_SHALLOW) {
if (want_status == FETCH_HEAD_MERGE)
- warning(_("reject %s because shallow roots are not allowed to be updated"),
+ warning(_("rejected %s because shallow roots are not allowed to be updated"),
rm->peer_ref ? rm->peer_ref->name : rm->name);
continue;
}
@@ -1011,31 +1180,15 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
strbuf_addf(&note, "%s ", kind);
strbuf_addf(&note, "'%s' of ", what);
}
- switch (rm->fetch_head_status) {
- case FETCH_HEAD_NOT_FOR_MERGE:
- merge_status_marker = "not-for-merge";
- /* fall-through */
- case FETCH_HEAD_MERGE:
- fprintf(fp, "%s\t%s\t%s",
- oid_to_hex(&rm->old_oid),
- merge_status_marker,
- note.buf);
- for (i = 0; i < url_len; ++i)
- if ('\n' == url[i])
- fputs("\\n", fp);
- else
- fputc(url[i], fp);
- fputc('\n', fp);
- break;
- default:
- /* do not write anything to FETCH_HEAD */
- break;
- }
+
+ append_fetch_head(&fetch_head, &rm->old_oid,
+ rm->fetch_head_status,
+ note.buf, url, url_len);
strbuf_reset(&note);
if (ref) {
- rc |= update_local_ref(ref, what, rm, &note,
- summary_width);
+ rc |= update_local_ref(ref, transaction, what,
+ rm, &note, summary_width);
free(ref);
} else if (write_fetch_head || dry_run) {
/*
@@ -1060,6 +1213,17 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
}
}
+ if (!rc && transaction) {
+ rc = ref_transaction_commit(transaction, &err);
+ if (rc) {
+ error("%s", err.buf);
+ goto abort;
+ }
+ }
+
+ if (!rc)
+ commit_fetch_head(&fetch_head);
+
if (rc & STORE_REF_ERROR_DF_CONFLICT)
error(_("some local refs could not be updated; try running\n"
" 'git remote prune %s' to remove any old, conflicting "
@@ -1076,8 +1240,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
abort:
strbuf_release(&note);
+ strbuf_release(&err);
+ ref_transaction_free(transaction);
free(url);
- fclose(fp);
+ close_fetch_head(&fetch_head);
return rc;
}
@@ -1349,7 +1515,8 @@ static int do_fetch(struct transport *transport,
int autotags = (transport->remote->fetch_tags == 1);
int retcode = 0;
const struct ref *remote_refs;
- struct strvec ref_prefixes = STRVEC_INIT;
+ struct transport_ls_refs_options transport_ls_refs_options =
+ TRANSPORT_LS_REFS_OPTIONS_INIT;
int must_list_refs = 1;
if (tags == TAGS_DEFAULT) {
@@ -1369,7 +1536,7 @@ static int do_fetch(struct transport *transport,
if (rs->nr) {
int i;
- refspec_ref_prefixes(rs, &ref_prefixes);
+ refspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes);
/*
* We can avoid listing refs if all of them are exact
@@ -1383,22 +1550,25 @@ static int do_fetch(struct transport *transport,
}
}
} else if (transport->remote && transport->remote->fetch.nr)
- refspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes);
+ refspec_ref_prefixes(&transport->remote->fetch,
+ &transport_ls_refs_options.ref_prefixes);
if (tags == TAGS_SET || tags == TAGS_DEFAULT) {
must_list_refs = 1;
- if (ref_prefixes.nr)
- strvec_push(&ref_prefixes, "refs/tags/");
+ if (transport_ls_refs_options.ref_prefixes.nr)
+ strvec_push(&transport_ls_refs_options.ref_prefixes,
+ "refs/tags/");
}
if (must_list_refs) {
trace2_region_enter("fetch", "remote_refs", the_repository);
- remote_refs = transport_get_remote_refs(transport, &ref_prefixes);
+ remote_refs = transport_get_remote_refs(transport,
+ &transport_ls_refs_options);
trace2_region_leave("fetch", "remote_refs", the_repository);
} else
remote_refs = NULL;
- strvec_clear(&ref_prefixes);
+ strvec_clear(&transport_ls_refs_options.ref_prefixes);
ref_map = get_ref_map(transport->remote, remote_refs, rs,
tags, &autotags);
@@ -1876,7 +2046,29 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
}
}
- if (remote) {
+ if (negotiate_only) {
+ struct oidset acked_commits = OIDSET_INIT;
+ struct oidset_iter iter;
+ const struct object_id *oid;
+
+ if (!remote)
+ die(_("must supply remote when using --negotiate-only"));
+ gtransport = prepare_transport(remote, 1);
+ if (gtransport->smart_options) {
+ gtransport->smart_options->acked_commits = &acked_commits;
+ } else {
+ warning(_("Protocol does not support --negotiate-only, exiting."));
+ return 1;
+ }
+ if (server_options.nr)
+ gtransport->server_options = &server_options;
+ result = transport_fetch_refs(gtransport, NULL);
+
+ oidset_iter_init(&acked_commits, &iter);
+ while ((oid = oidset_iter_next(&iter)))
+ printf("%s\n", oid_to_hex(oid));
+ oidset_clear(&acked_commits);
+ } else if (remote) {
if (filter_options.choice || has_promisor_remote())
fetch_one_setup_partial(remote);
result = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs);
@@ -1887,6 +2079,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
die(_("--filter can only be used with the remote "
"configured in extensions.partialclone"));
+ if (atomic_fetch)
+ die(_("--atomic can only be used when fetching "
+ "from one remote"));
+
if (stdin_refspecs)
die(_("--stdin can only be used when fetching "
"from one remote"));