diff options
author | Daniel Barkalow <barkalow@iabervon.org> | 2007-09-10 23:03:25 -0400 |
---|---|---|
committer | Junio C Hamano <gitster@pobox.com> | 2007-09-19 03:22:30 -0700 |
commit | b888d61c8308027433df9c243fa551f42db1c76a (patch) | |
tree | 94459873b07253fce86f467af8347f1a2d74f140 /builtin-fetch.c | |
parent | c7a8a16239c6bdbb4041dd8a8773ae055d3cccf8 (diff) | |
download | git-b888d61c8308027433df9c243fa551f42db1c76a.tar.gz git-b888d61c8308027433df9c243fa551f42db1c76a.tar.xz |
Make fetch a builtin
Thanks to Johannes Schindelin for review and fixes, and Julian
Phillips for the original C translation.
This changes a few small bits of behavior:
branch.<name>.merge is parsed as if it were the lhs of a fetch
refspec, and does not have to exactly match the actual lhs of a
refspec, so long as it is a valid abbreviation for the same ref.
branch.<name>.merge is no longer ignored if the remote is configured
with a branches/* file. Neither behavior is useful, because there can
only be one ref that gets fetched, but this is more consistant.
Also, fetch prints different information to standard out.
Signed-off-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'builtin-fetch.c')
-rw-r--r-- | builtin-fetch.c | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/builtin-fetch.c b/builtin-fetch.c new file mode 100644 index 000000000..64392f317 --- /dev/null +++ b/builtin-fetch.c @@ -0,0 +1,552 @@ +/* + * "git fetch" + */ +#include "cache.h" +#include "refs.h" +#include "commit.h" +#include "builtin.h" +#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 int unpacklimit; + +static char *default_rla = NULL; + +static void find_merge_config(struct ref *ref_map, struct remote *remote) +{ + struct ref *rm = ref_map; + struct branch *branch = branch_get(NULL); + + for (rm = ref_map; rm; rm = rm->next) { + if (!branch_has_merge_config(branch)) { + if (remote && remote->fetch && + !strcmp(remote->fetch[0].src, rm->name)) + rm->merge = 1; + } else { + if (branch_merges(branch, rm->name)) + rm->merge = 1; + } + } +} + +static struct ref *get_ref_map(struct transport *transport, + struct refspec *refs, int ref_count, int tags, + int *autotags) +{ + int i; + struct ref *rm; + struct ref *ref_map = NULL; + struct ref **tail = &ref_map; + + struct ref *remote_refs = transport_get_remote_refs(transport); + + if (ref_count || tags) { + for (i = 0; i < ref_count; i++) { + get_fetch_map(remote_refs, &refs[i], &tail); + if (refs[i].dst && refs[i].dst[0]) + *autotags = 1; + } + /* Merge everything on the command line, but not --tags */ + for (rm = ref_map; rm; rm = rm->next) + rm->merge = 1; + if (tags) { + struct refspec refspec; + refspec.src = "refs/tags/"; + refspec.dst = "refs/tags/"; + refspec.pattern = 1; + refspec.force = 0; + get_fetch_map(remote_refs, &refspec, &tail); + } + } else { + /* Use the defaults */ + struct remote *remote = transport->remote; + if (remote->fetch_refspec_nr) { + for (i = 0; i < remote->fetch_refspec_nr; i++) { + get_fetch_map(remote_refs, &remote->fetch[i], &tail); + if (remote->fetch[i].dst && + remote->fetch[i].dst[0]) + *autotags = 1; + } + find_merge_config(ref_map, remote); + } else { + ref_map = get_remote_ref(remote_refs, "HEAD"); + + ref_map->merge = 1; + } + } + + return ref_map; +} + +static void show_new(enum object_type type, unsigned char *sha1_new) +{ + fprintf(stderr, " %s: %s\n", typename(type), + find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); +} + +static int s_update_ref(const char *action, + struct ref *ref, + int check_old) +{ + char msg[1024]; + char *rla = getenv("GIT_REFLOG_ACTION"); + static struct ref_lock *lock; + + if (!rla) + rla = default_rla; + snprintf(msg, sizeof(msg), "%s: %s", rla, action); + lock = lock_any_ref_for_update(ref->name, + check_old ? ref->old_sha1 : NULL, 0); + if (!lock) + return 1; + if (write_ref_sha1(lock, ref->new_sha1, msg) < 0) + return 1; + return 0; +} + +static int update_local_ref(struct ref *ref, + const char *note, + int verbose) +{ + char oldh[41], newh[41]; + struct commit *current = NULL, *updated; + enum object_type type; + struct branch *current_branch = branch_get(NULL); + + type = sha1_object_info(ref->new_sha1, NULL); + if (type < 0) + die("object %s not found", sha1_to_hex(ref->new_sha1)); + + if (!*ref->name) { + /* Not storing */ + if (verbose) { + fprintf(stderr, "* fetched %s\n", note); + show_new(type, ref->new_sha1); + } + return 0; + } + + if (!hashcmp(ref->old_sha1, ref->new_sha1)) { + if (verbose) { + fprintf(stderr, "* %s: same as %s\n", + ref->name, note); + show_new(type, ref->new_sha1); + } + return 0; + } + + if (!strcmp(ref->name, current_branch->name) && + !(update_head_ok || is_bare_repository()) && + !is_null_sha1(ref->old_sha1)) { + /* + * If this is the head, and it's not okay to update + * the head, and the old value of the head isn't empty... + */ + fprintf(stderr, + " * %s: Cannot fetch into the current branch.\n", + ref->name); + return 1; + } + + if (!is_null_sha1(ref->old_sha1) && + !prefixcmp(ref->name, "refs/tags/")) { + fprintf(stderr, "* %s: updating with %s\n", + ref->name, note); + show_new(type, ref->new_sha1); + return s_update_ref("updating tag", ref, 0); + } + + current = lookup_commit_reference(ref->old_sha1); + updated = lookup_commit_reference(ref->new_sha1); + if (!current || !updated) { + char *msg; + if (!strncmp(ref->name, "refs/tags/", 10)) + msg = "storing tag"; + else + msg = "storing head"; + fprintf(stderr, "* %s: storing %s\n", + ref->name, note); + show_new(type, ref->new_sha1); + return s_update_ref(msg, ref, 0); + } + + strcpy(oldh, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); + strcpy(newh, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); + + if (in_merge_bases(current, &updated, 1)) { + fprintf(stderr, "* %s: fast forward to %s\n", + ref->name, note); + fprintf(stderr, " old..new: %s..%s\n", oldh, newh); + return s_update_ref("fast forward", ref, 1); + } + if (!force && !ref->force) { + fprintf(stderr, + "* %s: not updating to non-fast forward %s\n", + ref->name, note); + fprintf(stderr, + " old...new: %s...%s\n", oldh, newh); + return 1; + } + fprintf(stderr, + "* %s: forcing update to non-fast forward %s\n", + ref->name, note); + fprintf(stderr, " old...new: %s...%s\n", oldh, newh); + return s_update_ref("forced-update", ref, 1); +} + +static void store_updated_refs(const char *url, struct ref *ref_map) +{ + FILE *fp; + struct commit *commit; + int url_len, i, note_len; + char note[1024]; + const char *what, *kind; + struct ref *rm; + + fp = fopen(git_path("FETCH_HEAD"), "a"); + for (rm = ref_map; rm; rm = rm->next) { + struct ref *ref = NULL; + + if (rm->peer_ref) { + ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1); + strcpy(ref->name, rm->peer_ref->name); + hashcpy(ref->old_sha1, rm->peer_ref->old_sha1); + hashcpy(ref->new_sha1, rm->old_sha1); + ref->force = rm->force; + } + + commit = lookup_commit_reference(rm->old_sha1); + if (!commit) + rm->merge = 0; + + if (!strcmp(rm->name, "HEAD")) { + kind = ""; + what = ""; + } + else if (!prefixcmp(rm->name, "refs/heads/")) { + kind = "branch"; + what = rm->name + 11; + } + else if (!prefixcmp(rm->name, "refs/tags/")) { + kind = "tag"; + what = rm->name + 10; + } + else if (!prefixcmp(rm->name, "refs/remotes/")) { + kind = "remote branch"; + what = rm->name + 13; + } + else { + kind = ""; + what = rm->name; + } + + url_len = strlen(url); + for (i = url_len - 1; url[i] == '/' && 0 <= i; i--) + ; + url_len = i + 1; + if (4 < i && !strncmp(".git", url + i - 3, 4)) + url_len = i - 3; + + note_len = 0; + if (*what) { + if (*kind) + note_len += sprintf(note + note_len, "%s ", + kind); + note_len += sprintf(note + note_len, "'%s' of ", what); + } + note_len += sprintf(note + note_len, "%.*s", url_len, url); + fprintf(fp, "%s\t%s\t%s\n", + sha1_to_hex(commit ? commit->object.sha1 : + rm->old_sha1), + rm->merge ? "" : "not-for-merge", + note); + + if (ref) + update_local_ref(ref, note, verbose); + } + fclose(fp); +} + +static int fetch_refs(struct transport *transport, struct ref *ref_map) +{ + int ret = transport_fetch_refs(transport, ref_map); + if (!ret) + store_updated_refs(transport->url, ref_map); + return ret; +} + +static int add_existing(const char *refname, const unsigned char *sha1, + int flag, void *cbdata) +{ + struct path_list *list = (struct path_list *)cbdata; + path_list_insert(refname, list); + return 0; +} + +static struct ref *find_non_local_tags(struct transport *transport, + struct ref *fetch_map) +{ + static struct path_list existing_refs = { NULL, 0, 0, 0 }; + struct path_list new_refs = { NULL, 0, 0, 1 }; + char *ref_name; + int ref_name_len; + unsigned char *ref_sha1; + struct ref *tag_ref; + struct ref *rm = NULL; + struct ref *ref_map = NULL; + struct ref **tail = &ref_map; + struct ref *ref; + + for_each_ref(add_existing, &existing_refs); + for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) { + if (prefixcmp(ref->name, "refs/tags")) + continue; + + ref_name = xstrdup(ref->name); + ref_name_len = strlen(ref_name); + ref_sha1 = ref->old_sha1; + + if (!strcmp(ref_name + ref_name_len - 3, "^{}")) { + ref_name[ref_name_len - 3] = 0; + tag_ref = transport_get_remote_refs(transport); + while (tag_ref) { + if (!strcmp(tag_ref->name, ref_name)) { + ref_sha1 = tag_ref->old_sha1; + break; + } + tag_ref = tag_ref->next; + } + } + + if (!path_list_has_path(&existing_refs, ref_name) && + !path_list_has_path(&new_refs, ref_name) && + lookup_object(ref->old_sha1)) { + fprintf(stderr, "Auto-following %s\n", + ref_name); + + path_list_insert(ref_name, &new_refs); + + rm = alloc_ref(strlen(ref_name) + 1); + strcpy(rm->name, ref_name); + rm->peer_ref = alloc_ref(strlen(ref_name) + 1); + strcpy(rm->peer_ref->name, ref_name); + hashcpy(rm->old_sha1, ref_sha1); + + *tail = rm; + tail = &rm->next; + } + free(ref_name); + } + + return ref_map; +} + +static int do_fetch(struct transport *transport, + struct refspec *refs, int ref_count) +{ + 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 == -1) + no_tags = 1; + + if (!transport->ops || !transport->ops->get_refs_list || + !(transport->ops->fetch_refs || transport->ops->fetch_objs)) + 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")); + + ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags); + + for (rm = ref_map; rm; rm = rm->next) { + if (rm->peer_ref) + read_ref(rm->peer_ref->name, rm->peer_ref->old_sha1); + + printf("%s : %s\n", rm->name, rm->peer_ref ? rm->peer_ref->name : NULL); + printf(" < %s\n", sha1_to_hex(rm->old_sha1)); + if (rm->peer_ref) + printf(" > %s\n", sha1_to_hex(rm->peer_ref->old_sha1)); + if (!rm->peer_ref || + hashcmp(rm->old_sha1, rm->peer_ref->old_sha1)) { + printf("%s needs update.\n", rm->name); + } + } + + if (fetch_refs(transport, ref_map)) { + free_refs(ref_map); + return 1; + } + + fetch_map = ref_map; + + /* if neither --no-tags nor --tags was specified, do automated tag + * following ... */ + if (!(tags || no_tags) && autotags) { + ref_map = find_non_local_tags(transport, fetch_map); + if (ref_map) { + transport_set_option(transport, TRANS_OPT_DEPTH, "0"); + fetch_refs(transport, ref_map); + } + free_refs(ref_map); + } + + free_refs(fetch_map); + + return 0; +} + +static int fetch_config(const char *var, const char *value) +{ + if (strcmp(var, "fetch.unpacklimit") == 0) { + unpacklimit = git_config_int(var, value); + return 0; + } + + if (strcmp(var, "transfer.unpacklimit") == 0) { + unpacklimit = git_config_int(var, value); + return 0; + } + + return git_default_config(var, value); +} + +int cmd_fetch(int argc, const char **argv, const char *prefix) +{ + struct remote *remote; + struct transport *transport; + int i, j, rla_offset; + static const char **refs = NULL; + int ref_nr = 0; + int cmd_len = 0; + const char *depth = NULL, *upload_pack = NULL; + int keep = 0; + + git_config(fetch_config); + + 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")) { + 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]); + + 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]); + } + + if (i == argc) + remote = remote_get(NULL); + else + remote = remote_get(argv[i++]); + + transport = transport_get(remote, remote->uri[0], 1); + if (verbose >= 2) + transport->verbose = 1; + if (quiet) + transport->verbose = 0; + if (upload_pack) + transport_set_option(transport, TRANS_OPT_UPLOADPACK, upload_pack); + if (keep) + transport_set_option(transport, TRANS_OPT_KEEP, "yes"); + transport_set_option(transport, TRANS_OPT_DEPTH, depth); + + if (!transport->url) + die("Where do you want to fetch from today?"); + + if (i < argc) { + int j = 0; + refs = xcalloc(argc - i + 1, sizeof(const char *)); + while (i < argc) { + if (!strcmp(argv[i], "tag")) { + char *ref; + i++; + ref = xmalloc(strlen(argv[i]) * 2 + 22); + strcpy(ref, "refs/tags/"); + strcat(ref, argv[i]); + strcat(ref, ":refs/tags/"); + strcat(ref, argv[i]); + refs[j++] = ref; + } else + refs[j++] = argv[i]; + i++; + } + refs[j] = NULL; + ref_nr = j; + for (j = 0; refs[j]; j++) + printf("ref: %s\n", refs[j]); + } + + return do_fetch(transport, parse_ref_spec(ref_nr, refs), ref_nr); +} |