#include "path-list.h"
#include "remote.h"
#include "transport.h"
-
-static const char fetch_usage[] = "git-fetch [-a | --append] [--upload-pack <upload-pack>] [-f | --force] [--no-tags] [-t | --tags] [-k | --keep] [-u | --update-head-ok] [--depth <depth>] [-v | --verbose] [<repository> <refspec>...]";
-
-static int append, force, tags, no_tags, update_head_ok, verbose, quiet;
-static char *default_rla = NULL;
+#include "run-command.h"
+#include "parse-options.h"
+
+static const char * const builtin_fetch_usage[] = {
+ "git-fetch [options] [<repository> <refspec>...]",
+ NULL
+};
+
+enum {
+ TAGS_UNSET = 0,
+ TAGS_DEFAULT = 1,
+ TAGS_SET = 2
+};
+
+static int append, force, keep, update_head_ok, verbose, quiet;
+static int tags = TAGS_DEFAULT;
+static const char *depth;
+static const char *upload_pack;
+static struct strbuf default_rla = STRBUF_INIT;
static struct transport *transport;
+static struct option builtin_fetch_options[] = {
+ OPT__QUIET(&quiet),
+ OPT__VERBOSE(&verbose),
+ OPT_BOOLEAN('a', "append", &append,
+ "append to .git/FETCH_HEAD instead of overwriting"),
+ OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
+ "path to upload pack on remote end"),
+ OPT_BOOLEAN('f', "force", &force,
+ "force overwrite of local branch"),
+ OPT_SET_INT('t', "tags", &tags,
+ "fetch all tags and associated objects", TAGS_SET),
+ OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
+ OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
+ "allow updating of HEAD ref"),
+ OPT_STRING(0, "depth", &depth, "DEPTH",
+ "deepen history of shallow clone"),
+ OPT_END()
+};
+
static void unlock_pack(void)
{
if (transport)
}
static void add_merge_config(struct ref **head,
- struct ref *remote_refs,
+ const struct ref *remote_refs,
struct branch *branch,
struct ref ***tail)
{
struct ref *ref_map = NULL;
struct ref **tail = &ref_map;
- struct ref *remote_refs = transport_get_remote_refs(transport);
+ const struct ref *remote_refs = transport_get_remote_refs(transport);
- if (ref_count || tags) {
+ if (ref_count || tags == TAGS_SET) {
for (i = 0; i < ref_count; i++) {
get_fetch_map(remote_refs, &refs[i], &tail, 0);
if (refs[i].dst && refs[i].dst[0])
/* Merge everything on the command line, but not --tags */
for (rm = ref_map; rm; rm = rm->next)
rm->merge = 1;
- if (tags) {
+ if (tags == TAGS_SET) {
struct refspec refspec;
refspec.src = "refs/tags/";
refspec.dst = "refs/tags/";
static struct ref_lock *lock;
if (!rla)
- rla = default_rla;
+ rla = default_rla.buf;
snprintf(msg, sizeof(msg), "%s: %s", rla, action);
lock = lock_any_ref_for_update(ref->name,
check_old ? ref->old_sha1 : NULL, 0);
}
}
-static void store_updated_refs(const char *url, struct ref *ref_map)
+static int store_updated_refs(const char *url, struct ref *ref_map)
{
FILE *fp;
struct commit *commit;
char note[1024];
const char *what, *kind;
struct ref *rm;
+ char *filename = git_path("FETCH_HEAD");
- fp = fopen(git_path("FETCH_HEAD"), "a");
+ fp = fopen(filename, "a");
+ if (!fp)
+ return error("cannot open %s: %s\n", filename, strerror(errno));
for (rm = ref_map; rm; rm = rm->next) {
struct ref *ref = NULL;
}
}
fclose(fp);
+ return 0;
+}
+
+/*
+ * We would want to bypass the object transfer altogether if
+ * everything we are going to fetch already exists and connected
+ * locally.
+ *
+ * The refs we are going to fetch are in to_fetch (nr_heads in
+ * total). If running
+ *
+ * $ git-rev-list --objects to_fetch[0] to_fetch[1] ... --not --all
+ *
+ * does not error out, that means everything reachable from the
+ * refs we are going to fetch exists and is connected to some of
+ * our existing refs.
+ */
+static int quickfetch(struct ref *ref_map)
+{
+ struct child_process revlist;
+ struct ref *ref;
+ char **argv;
+ int i, err;
+
+ /*
+ * If we are deepening a shallow clone we already have these
+ * objects reachable. Running rev-list here will return with
+ * a good (0) exit status and we'll bypass the fetch that we
+ * really need to perform. Claiming failure now will ensure
+ * we perform the network exchange to deepen our history.
+ */
+ if (depth)
+ return -1;
+
+ for (i = 0, ref = ref_map; ref; ref = ref->next)
+ i++;
+ if (!i)
+ return 0;
+
+ argv = xmalloc(sizeof(*argv) * (i + 6));
+ i = 0;
+ argv[i++] = xstrdup("rev-list");
+ argv[i++] = xstrdup("--quiet");
+ argv[i++] = xstrdup("--objects");
+ for (ref = ref_map; ref; ref = ref->next)
+ argv[i++] = xstrdup(sha1_to_hex(ref->old_sha1));
+ argv[i++] = xstrdup("--not");
+ argv[i++] = xstrdup("--all");
+ argv[i++] = NULL;
+
+ memset(&revlist, 0, sizeof(revlist));
+ revlist.argv = (const char**)argv;
+ revlist.git_cmd = 1;
+ revlist.no_stdin = 1;
+ revlist.no_stdout = 1;
+ revlist.no_stderr = 1;
+ err = run_command(&revlist);
+
+ for (i = 0; argv[i]; i++)
+ free(argv[i]);
+ free(argv);
+ return err;
}
static int fetch_refs(struct transport *transport, struct ref *ref_map)
{
- int ret = transport_fetch_refs(transport, ref_map);
+ int ret = quickfetch(ref_map);
+ if (ret)
+ ret = transport_fetch_refs(transport, ref_map);
if (!ret)
- store_updated_refs(transport->url, ref_map);
+ ret |= store_updated_refs(transport->url, ref_map);
transport_unlock_pack(transport);
return ret;
}
struct path_list new_refs = { NULL, 0, 0, 1 };
char *ref_name;
int ref_name_len;
- unsigned char *ref_sha1;
- struct ref *tag_ref;
+ const unsigned char *ref_sha1;
+ const struct ref *tag_ref;
struct ref *rm = NULL;
struct ref *ref_map = NULL;
struct ref **tail = &ref_map;
- struct ref *ref;
+ const struct ref *ref;
for_each_ref(add_existing, &existing_refs);
for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
if (!path_list_has_path(&existing_refs, ref_name) &&
!path_list_has_path(&new_refs, ref_name) &&
- lookup_object(ref->old_sha1)) {
+ has_sha1_file(ref->old_sha1)) {
path_list_insert(ref_name, &new_refs);
rm = alloc_ref(strlen(ref_name) + 1);
struct ref *ref_map, *fetch_map;
struct ref *rm;
int autotags = (transport->remote->fetch_tags == 1);
- if (transport->remote->fetch_tags == 2 && !no_tags)
- tags = 1;
+ if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET)
+ tags = TAGS_SET;
if (transport->remote->fetch_tags == -1)
- no_tags = 1;
+ tags = TAGS_UNSET;
if (!transport->get_refs_list || !transport->fetch)
die("Don't know how to fetch from %s", transport->url);
/* if not appending, truncate FETCH_HEAD */
- if (!append)
- fclose(fopen(git_path("FETCH_HEAD"), "w"));
+ if (!append) {
+ char *filename = git_path("FETCH_HEAD");
+ FILE *fp = fopen(filename, "w");
+ if (!fp)
+ return error("cannot open %s: %s\n", filename, strerror(errno));
+ fclose(fp);
+ }
ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
/* if neither --no-tags nor --tags was specified, do automated tag
* following ... */
- if (!(tags || no_tags) && autotags) {
+ if (tags == TAGS_DEFAULT && autotags) {
ref_map = find_non_local_tags(transport, fetch_map);
if (ref_map) {
transport_set_option(transport, TRANS_OPT_DEPTH, "0");
int cmd_fetch(int argc, const char **argv, const char *prefix)
{
struct remote *remote;
- int i, j, rla_offset;
+ int i;
static const char **refs = NULL;
int ref_nr = 0;
- int cmd_len = 0;
- const char *depth = NULL, *upload_pack = NULL;
- int keep = 0;
-
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
- cmd_len += strlen(arg);
-
- if (arg[0] != '-')
- break;
- if (!strcmp(arg, "--append") || !strcmp(arg, "-a")) {
- append = 1;
- continue;
- }
- if (!prefixcmp(arg, "--upload-pack=")) {
- upload_pack = arg + 14;
- continue;
- }
- if (!strcmp(arg, "--upload-pack")) {
- i++;
- if (i == argc)
- usage(fetch_usage);
- upload_pack = argv[i];
- continue;
- }
- if (!strcmp(arg, "--force") || !strcmp(arg, "-f")) {
- force = 1;
- continue;
- }
- if (!strcmp(arg, "--no-tags")) {
- no_tags = 1;
- continue;
- }
- if (!strcmp(arg, "--tags") || !strcmp(arg, "-t")) {
- tags = 1;
- continue;
- }
- if (!strcmp(arg, "--keep") || !strcmp(arg, "-k")) {
- keep = 1;
- continue;
- }
- if (!strcmp(arg, "--update-head-ok") || !strcmp(arg, "-u")) {
- update_head_ok = 1;
- continue;
- }
- if (!prefixcmp(arg, "--depth=")) {
- depth = arg + 8;
- continue;
- }
- if (!strcmp(arg, "--depth")) {
- i++;
- if (i == argc)
- usage(fetch_usage);
- depth = argv[i];
- continue;
- }
- if (!strcmp(arg, "--quiet") || !strcmp(arg, "-q")) {
- quiet = 1;
- continue;
- }
- if (!strcmp(arg, "--verbose") || !strcmp(arg, "-v")) {
- verbose++;
- continue;
- }
- usage(fetch_usage);
- }
- for (j = i; j < argc; j++)
- cmd_len += strlen(argv[j]);
+ /* Record the command line for the reflog */
+ strbuf_addstr(&default_rla, "fetch");
+ for (i = 1; i < argc; i++)
+ strbuf_addf(&default_rla, " %s", argv[i]);
- default_rla = xmalloc(cmd_len + 5 + argc + 1);
- sprintf(default_rla, "fetch");
- rla_offset = strlen(default_rla);
- for (j = 1; j < argc; j++) {
- sprintf(default_rla + rla_offset, " %s", argv[j]);
- rla_offset += strlen(argv[j]) + 1;
- }
+ argc = parse_options(argc, argv,
+ builtin_fetch_options, builtin_fetch_usage, 0);
- if (i == argc)
+ if (argc == 0)
remote = remote_get(NULL);
else
- remote = remote_get(argv[i++]);
+ remote = remote_get(argv[0]);
transport = transport_get(remote, remote->url[0]);
if (verbose >= 2)
if (!transport->url)
die("Where do you want to fetch from today?");
- if (i < argc) {
+ if (argc > 1) {
int j = 0;
- refs = xcalloc(argc - i + 1, sizeof(const char *));
- while (i < argc) {
+ refs = xcalloc(argc + 1, sizeof(const char *));
+ for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "tag")) {
char *ref;
i++;
refs[j++] = ref;
} else
refs[j++] = argv[i];
- i++;
}
refs[j] = NULL;
ref_nr = j;