#include "list-objects-filter-options.h"
#include "commit-reach.h"
+#define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000)
+
static const char * const builtin_fetch_usage[] = {
N_("git fetch [<options>] [<repository> [<refspec>...]]"),
N_("git fetch [<options>] <group>"),
};
static int fetch_prune_config = -1; /* unspecified */
+static int fetch_show_forced_updates = 1;
+static uint64_t forced_updates_ms = 0;
static int prune = -1; /* unspecified */
#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity, deepen_relative;
static int progress = -1;
+static int enable_auto_gc = 1;
static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
static int max_children = 1;
static enum transport_family family;
return 0;
}
+ if (!strcmp(k, "fetch.showforcedupdates")) {
+ fetch_show_forced_updates = git_config_bool(k, v);
+ return 0;
+ }
+
if (!strcmp(k, "submodule.recurse")) {
int r = git_config_bool(k, v) ?
RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"),
N_("report that we have only objects reachable from this object")),
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
+ OPT_BOOL(0, "auto-gc", &enable_auto_gc,
+ N_("run 'gc --auto' after fetching")),
+ OPT_BOOL(0, "show-forced-updates", &fetch_show_forced_updates,
+ N_("check for forced-updates on all updated branches")),
OPT_END()
};
}
}
-static int add_existing(const char *refname, const struct object_id *oid,
- int flag, void *cbdata)
-{
- struct string_list *list = (struct string_list *)cbdata;
- struct string_list_item *item = string_list_insert(list, refname);
- struct object_id *old_oid = xmalloc(sizeof(*old_oid));
-
- oidcpy(old_oid, oid);
- item->util = old_oid;
- return 0;
-}
-
static int will_fetch(struct ref **head, const unsigned char *sha1)
{
struct ref *rm = *head;
return 0;
}
+struct refname_hash_entry {
+ struct hashmap_entry ent; /* must be the first member */
+ struct object_id oid;
+ int ignore;
+ char refname[FLEX_ARRAY];
+};
+
+static int refname_hash_entry_cmp(const void *hashmap_cmp_fn_data,
+ const void *e1_,
+ const void *e2_,
+ const void *keydata)
+{
+ const struct refname_hash_entry *e1 = e1_;
+ const struct refname_hash_entry *e2 = e2_;
+
+ return strcmp(e1->refname, keydata ? keydata : e2->refname);
+}
+
+static struct refname_hash_entry *refname_hash_add(struct hashmap *map,
+ const char *refname,
+ const struct object_id *oid)
+{
+ struct refname_hash_entry *ent;
+ size_t len = strlen(refname);
+
+ FLEX_ALLOC_MEM(ent, refname, refname, len);
+ hashmap_entry_init(ent, strhash(refname));
+ oidcpy(&ent->oid, oid);
+ hashmap_add(map, ent);
+ return ent;
+}
+
+static int add_one_refname(const char *refname,
+ const struct object_id *oid,
+ int flag, void *cbdata)
+{
+ struct hashmap *refname_map = cbdata;
+
+ (void) refname_hash_add(refname_map, refname, oid);
+ return 0;
+}
+
+static void refname_hash_init(struct hashmap *map)
+{
+ hashmap_init(map, refname_hash_entry_cmp, NULL, 0);
+}
+
+static int refname_hash_exists(struct hashmap *map, const char *refname)
+{
+ return !!hashmap_get_from_hash(map, strhash(refname), refname);
+}
+
+static void clear_item(struct refname_hash_entry *item)
+{
+ item->ignore = 1;
+}
+
static void find_non_local_tags(const struct ref *refs,
struct ref **head,
struct ref ***tail)
{
- struct string_list existing_refs = STRING_LIST_INIT_DUP;
- struct string_list remote_refs = STRING_LIST_INIT_NODUP;
+ struct hashmap existing_refs;
+ struct hashmap remote_refs;
+ struct string_list remote_refs_list = STRING_LIST_INIT_NODUP;
+ struct string_list_item *remote_ref_item;
const struct ref *ref;
- struct string_list_item *item = NULL;
+ struct refname_hash_entry *item = NULL;
+
+ refname_hash_init(&existing_refs);
+ refname_hash_init(&remote_refs);
- for_each_ref(add_existing, &existing_refs);
+ for_each_ref(add_one_refname, &existing_refs);
for (ref = refs; ref; ref = ref->next) {
if (!starts_with(ref->name, "refs/tags/"))
continue;
!has_object_file_with_flags(&ref->old_oid,
OBJECT_INFO_QUICK) &&
!will_fetch(head, ref->old_oid.hash) &&
- !has_sha1_file_with_flags(item->util,
- OBJECT_INFO_QUICK) &&
- !will_fetch(head, item->util))
- item->util = NULL;
+ !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) &&
+ !will_fetch(head, item->oid.hash))
+ clear_item(item);
item = NULL;
continue;
}
* fetch.
*/
if (item &&
- !has_sha1_file_with_flags(item->util, OBJECT_INFO_QUICK) &&
- !will_fetch(head, item->util))
- item->util = NULL;
+ !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) &&
+ !will_fetch(head, item->oid.hash))
+ clear_item(item);
item = NULL;
/* skip duplicates and refs that we already have */
- if (string_list_has_string(&remote_refs, ref->name) ||
- string_list_has_string(&existing_refs, ref->name))
+ if (refname_hash_exists(&remote_refs, ref->name) ||
+ refname_hash_exists(&existing_refs, ref->name))
continue;
- item = string_list_insert(&remote_refs, ref->name);
- item->util = (void *)&ref->old_oid;
+ item = refname_hash_add(&remote_refs, ref->name, &ref->old_oid);
+ string_list_insert(&remote_refs_list, ref->name);
}
- string_list_clear(&existing_refs, 1);
+ hashmap_free(&existing_refs, 1);
/*
* We may have a final lightweight tag that needs to be
* checked to see if it needs fetching.
*/
if (item &&
- !has_sha1_file_with_flags(item->util, OBJECT_INFO_QUICK) &&
- !will_fetch(head, item->util))
- item->util = NULL;
+ !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) &&
+ !will_fetch(head, item->oid.hash))
+ clear_item(item);
/*
- * For all the tags in the remote_refs string list,
+ * For all the tags in the remote_refs_list,
* add them to the list of refs to be fetched
*/
- for_each_string_list_item(item, &remote_refs) {
+ for_each_string_list_item(remote_ref_item, &remote_refs_list) {
+ const char *refname = remote_ref_item->string;
+ struct ref *rm;
+
+ item = hashmap_get_from_hash(&remote_refs, strhash(refname), refname);
+ if (!item)
+ BUG("unseen remote ref?");
+
/* Unless we have already decided to ignore this item... */
- if (item->util)
- {
- struct ref *rm = alloc_ref(item->string);
- rm->peer_ref = alloc_ref(item->string);
- oidcpy(&rm->old_oid, item->util);
- **tail = rm;
- *tail = &rm->next;
- }
- }
+ if (item->ignore)
+ continue;
- string_list_clear(&remote_refs, 0);
+ rm = alloc_ref(item->refname);
+ rm->peer_ref = alloc_ref(item->refname);
+ oidcpy(&rm->old_oid, &item->oid);
+ **tail = rm;
+ *tail = &rm->next;
+ }
+ hashmap_free(&remote_refs, 1);
+ string_list_clear(&remote_refs_list, 0);
}
static struct ref *get_ref_map(struct remote *remote,
/* opportunistically-updated references: */
struct ref *orefs = NULL, **oref_tail = &orefs;
- struct string_list existing_refs = STRING_LIST_INIT_DUP;
+ struct hashmap existing_refs;
if (rs->nr) {
struct refspec *fetch_refspec;
ref_map = ref_remove_duplicates(ref_map);
- for_each_ref(add_existing, &existing_refs);
+ refname_hash_init(&existing_refs);
+ for_each_ref(add_one_refname, &existing_refs);
+
for (rm = ref_map; rm; rm = rm->next) {
if (rm->peer_ref) {
- struct string_list_item *peer_item =
- string_list_lookup(&existing_refs,
- rm->peer_ref->name);
+ const char *refname = rm->peer_ref->name;
+ struct refname_hash_entry *peer_item;
+
+ peer_item = hashmap_get_from_hash(&existing_refs,
+ strhash(refname),
+ refname);
if (peer_item) {
- struct object_id *old_oid = peer_item->util;
+ struct object_id *old_oid = &peer_item->oid;
oidcpy(&rm->peer_ref->old_oid, old_oid);
}
}
}
- string_list_clear(&existing_refs, 1);
+ hashmap_free(&existing_refs, 1);
return ref_map;
}
const char *needle,
const char *placeholder)
{
- const char *p = strstr(haystack->buf, needle);
+ const char *p = NULL;
int plen, nlen;
+ nlen = strlen(needle);
+ if (ends_with(haystack->buf, needle))
+ p = haystack->buf + haystack->len - nlen;
+ else
+ p = strstr(haystack->buf, needle);
if (!p)
return 0;
return 0;
plen = strlen(p);
- nlen = strlen(needle);
if (plen > nlen && p[nlen] != '/')
return 0;
enum object_type type;
struct branch *current_branch = branch_get(NULL);
const char *pretty_ref = prettify_refname(ref->name);
+ int fast_forward = 0;
type = oid_object_info(the_repository, &ref->new_oid, NULL);
if (type < 0)
what = _("[new ref]");
}
- if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
- (recurse_submodules != RECURSE_SUBMODULES_ON))
- check_for_new_submodule_commits(&ref->new_oid);
r = s_update_ref(msg, ref, 0);
format_display(display, r ? '!' : '*', what,
r ? _("unable to update local ref") : NULL,
return r;
}
- if (in_merge_bases(current, updated)) {
+ if (fetch_show_forced_updates) {
+ uint64_t t_before = getnanotime();
+ fast_forward = in_merge_bases(current, updated);
+ forced_updates_ms += (getnanotime() - t_before) / 1000000;
+ } else {
+ fast_forward = 1;
+ }
+
+ if (fast_forward) {
struct strbuf quickref = STRBUF_INIT;
int r;
+
strbuf_add_unique_abbrev(&quickref, ¤t->object.oid, DEFAULT_ABBREV);
strbuf_addstr(&quickref, "..");
strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV);
- if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
- (recurse_submodules != RECURSE_SUBMODULES_ON))
- check_for_new_submodule_commits(&ref->new_oid);
r = s_update_ref("fast-forward", ref, 1);
format_display(display, r ? '!' : ' ', quickref.buf,
r ? _("unable to update local ref") : NULL,
strbuf_add_unique_abbrev(&quickref, ¤t->object.oid, DEFAULT_ABBREV);
strbuf_addstr(&quickref, "...");
strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV);
- if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
- (recurse_submodules != RECURSE_SUBMODULES_ON))
- check_for_new_submodule_commits(&ref->new_oid);
r = s_update_ref("forced-update", ref, 1);
format_display(display, r ? '!' : '+', quickref.buf,
r ? _("unable to update local ref") : _("forced update"),
return 0;
}
+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"
+ "flag or run 'git config fetch.showForcedUpdates true'.");
+static const char warn_time_show_forced_updates[] =
+N_("It took %.2f seconds to check forced updates. You can use\n"
+ "'--no-show-forced-updates' or run 'git config fetch.showForcedUpdates false'\n"
+ " to avoid this check.\n");
+
static int store_updated_refs(const char *raw_url, const char *remote_name,
int connectivity_checked, struct ref *ref_map)
{
ref->force = rm->peer_ref->force;
}
+ if (recurse_submodules != RECURSE_SUBMODULES_OFF)
+ check_for_new_submodule_commits(&rm->old_oid);
if (!strcmp(rm->name, "HEAD")) {
kind = "";
" 'git remote prune %s' to remove any old, conflicting "
"branches"), remote_name);
+ if (advice_fetch_show_forced_updates) {
+ if (!fetch_show_forced_updates) {
+ warning(_(warn_show_forced_updates));
+ } else if (forced_updates_ms > FORCED_UPDATES_DELAY_WARNING_IN_MS) {
+ warning(_(warn_time_show_forced_updates),
+ forced_updates_ms / 1000.0);
+ }
+ }
+
abort:
strbuf_release(¬e);
free(url);
static struct transport *prepare_transport(struct remote *remote, int deepen)
{
struct transport *transport;
+
transport = transport_get(remote, NULL);
transport_set_verbosity(transport, verbosity, progress);
transport->family = family;
if (update_shallow)
set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
if (filter_options.choice) {
+ struct strbuf expanded_filter_spec = STRBUF_INIT;
+ expand_list_objects_filter_spec(&filter_options,
+ &expanded_filter_spec);
set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER,
- filter_options.filter_spec);
+ expanded_filter_spec.buf);
set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
+ strbuf_release(&expanded_filter_spec);
}
if (negotiation_tip.nr) {
if (transport->smart_options)
return errcode;
}
- argv_array_pushl(&argv, "fetch", "--append", NULL);
+ argv_array_pushl(&argv, "fetch", "--append", "--no-auto-gc", NULL);
add_options_to_argv(&argv);
for (i = 0; i < list->nr; i++) {
*/
if (strcmp(remote->name, repository_format_partial_clone)) {
if (filter_options.choice)
- die(_("--filter can only be used with the remote configured in core.partialClone"));
+ die(_("--filter can only be used with the remote "
+ "configured in extensions.partialClone"));
return;
}
sigchain_push_common(unlock_pack_on_signal);
atexit(unlock_pack);
+ sigchain_push(SIGPIPE, SIG_IGN);
exit_code = do_fetch(gtransport, &rs);
+ sigchain_pop(SIGPIPE);
refspec_clear(&rs);
transport_disconnect(gtransport);
gtransport = NULL;
result = fetch_one(remote, argc, argv, prune_tags_ok);
} else {
if (filter_options.choice)
- die(_("--filter can only be used with the remote configured in core.partialClone"));
+ die(_("--filter can only be used with the remote "
+ "configured in extensions.partialclone"));
/* TODO should this also die if we have a previous partial-clone? */
result = fetch_multiple(&list);
}
string_list_clear(&list, 0);
- close_all_packs(the_repository->objects);
+ close_object_store(the_repository->objects);
- argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL);
- if (verbosity < 0)
- argv_array_push(&argv_gc_auto, "--quiet");
- run_command_v_opt(argv_gc_auto.argv, RUN_GIT_CMD);
- argv_array_clear(&argv_gc_auto);
+ if (enable_auto_gc) {
+ argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL);
+ if (verbosity < 0)
+ argv_array_push(&argv_gc_auto, "--quiet");
+ run_command_v_opt(argv_gc_auto.argv, RUN_GIT_CMD);
+ argv_array_clear(&argv_gc_auto);
+ }
return result;
}