X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=fetch-pack.c;h=a3bcad016f52c09897c836112e03eec859a5eb1e;hb=dafc88b136b508c1c08129b756b6ba23b7c9d341;hp=57602b9561160a3ff14b893a92d69b46c1ffa9df;hpb=40a10462498bdd23d4e49f02867b8be50eb78704;p=git.git diff --git a/fetch-pack.c b/fetch-pack.c index 57602b95..a3bcad01 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -3,71 +3,78 @@ #include "pkt-line.h" #include "commit.h" #include "tag.h" -#include -#include +static int keep_pack; static int quiet; static int verbose; +static int fetch_all; static const char fetch_pack_usage[] = -"git-fetch-pack [-q] [-v] [--exec=upload-pack] [host:]directory ..."; +"git-fetch-pack [--all] [-q] [-v] [-k] [--thin] [--exec=upload-pack] [host:]directory ..."; static const char *exec = "git-upload-pack"; #define COMPLETE (1U << 0) #define COMMON (1U << 1) -#define COMMON_REF (1U << 2 | COMMON) +#define COMMON_REF (1U << 2) #define SEEN (1U << 3) #define POPPED (1U << 4) static struct commit_list *rev_list = NULL; -static struct commit_list *rev_list_end = NULL; -static unsigned long non_common_revs = 0; +static int non_common_revs = 0, multi_ack = 0, use_thin_pack = 0; -static void rev_list_append(struct commit *commit, int mark) +static void rev_list_push(struct commit *commit, int mark) { if (!(commit->object.flags & mark)) { commit->object.flags |= mark; - if (rev_list == NULL) { - commit_list_insert(commit, &rev_list); - rev_list_end = rev_list; - } else { - commit_list_insert(commit, &(rev_list_end->next)); - rev_list_end = rev_list_end->next; - } + if (!(commit->object.parsed)) + parse_commit(commit); + + insert_by_date(commit, &rev_list); if (!(commit->object.flags & COMMON)) non_common_revs++; } } -static int rev_list_append_sha1(const char *path, const unsigned char *sha1) +static int rev_list_insert_ref(const char *path, const unsigned char *sha1) { - struct object *o = deref_tag(parse_object(sha1)); + struct object *o = deref_tag(parse_object(sha1), path, 0); - if (o->type == commit_type) - rev_list_append((struct commit *)o, SEEN); + if (o && o->type == commit_type) + rev_list_push((struct commit *)o, SEEN); return 0; } -static void mark_common(struct commit *commit) +/* + This function marks a rev and its ancestors as common. + In some cases, it is desirable to mark only the ancestors (for example + when only the server does not yet know that they are common). +*/ + +static void mark_common(struct commit *commit, + int ancestors_only, int dont_parse) { if (commit != NULL && !(commit->object.flags & COMMON)) { struct object *o = (struct object *)commit; - o->flags |= COMMON; + + if (!ancestors_only) + o->flags |= COMMON; + if (!(o->flags & SEEN)) - rev_list_append(commit, SEEN); + rev_list_push(commit, SEEN); else { struct commit_list *parents; - if (!(o->flags & POPPED)) + if (!ancestors_only && !(o->flags & POPPED)) non_common_revs--; - if (!o->parsed) + if (!o->parsed && !dont_parse) parse_commit(commit); + for (parents = commit->parents; parents; parents = parents->next) - mark_common(parents->item); + mark_common(parents->item, 0, dont_parse); } } } @@ -76,7 +83,7 @@ static void mark_common(struct commit *commit) Get the next rev to send, ignoring the common. */ -static const unsigned char* get_rev() +static const unsigned char* get_rev(void) { struct commit *commit = NULL; @@ -108,10 +115,10 @@ static const unsigned char* get_rev() mark = SEEN; while (parents) { + if (!(parents->item->object.flags & SEEN)) + rev_list_push(parents->item, mark); if (mark & COMMON) - mark_common(parents->item); - else - rev_list_append(parents->item, mark); + mark_common(parents->item, 1, 0); parents = parents->next; } @@ -125,10 +132,10 @@ static int find_common(int fd[2], unsigned char *result_sha1, struct ref *refs) { int fetching; - int count = 0, flushes = 0, multi_ack = 0, retval; + int count = 0, flushes = 0, retval; const unsigned char *sha1; - for_each_ref(rev_list_append_sha1); + for_each_ref(rev_list_insert_ref); fetching = 0; for ( ; refs ; refs = refs->next) { @@ -146,17 +153,13 @@ static int find_common(int fd[2], unsigned char *result_sha1, * reachable and we have already scanned it. */ if (((o = lookup_object(remote)) != NULL) && - (o->flags & COMPLETE)) { - o = deref_tag(o); - - if (o->type == commit_type) - rev_list_append((struct commit *)o, - COMMON_REF | SEEN); - + (o->flags & COMPLETE)) { continue; } - packet_write(fd[1], "want %s multi_ack\n", sha1_to_hex(remote)); + packet_write(fd[1], "want %s%s%s\n", sha1_to_hex(remote), + (multi_ack ? " multi_ack" : ""), + (use_thin_pack ? " thin-pack" : "")); fetching++; } packet_flush(fd[1]); @@ -188,38 +191,37 @@ static int find_common(int fd[2], unsigned char *result_sha1, fprintf(stderr, "got ack %d %s\n", ack, sha1_to_hex(result_sha1)); if (ack == 1) { - if (!multi_ack) - flushes = 0; + flushes = 0; + multi_ack = 0; retval = 0; goto done; } else if (ack == 2) { - multi_ack = 1; - mark_common((struct commit *) - lookup_object(result_sha1)); + struct commit *commit = + lookup_commit(result_sha1); + mark_common(commit, 0, 1); retval = 0; } - } while(ack); + } while (ack); flushes--; } } done: - if (multi_ack) { - packet_flush(fd[1]); - flushes++; - } packet_write(fd[1], "done\n"); if (verbose) fprintf(stderr, "done\n"); - if (retval != 0) + if (retval != 0) { + multi_ack = 0; flushes++; - while (flushes) { - if (get_ack(fd[0], result_sha1)) { + } + while (flushes || multi_ack) { + int ack = get_ack(fd[0], result_sha1); + if (ack) { if (verbose) - fprintf(stderr, "got ack %s\n", + fprintf(stderr, "got ack (%d) %s\n", ack, sha1_to_hex(result_sha1)); - if (!multi_ack) + if (ack == 1) return 0; - retval = 0; + multi_ack = 1; continue; } flushes--; @@ -258,7 +260,27 @@ static void mark_recent_complete_commits(unsigned long cutoff) } } -static int everything_local(struct ref *refs) +static void filter_refs(struct ref **refs, int nr_match, char **match) +{ + struct ref *prev, *current, *next; + + for (prev = NULL, current = *refs; current; current = next) { + next = current->next; + if ((!memcmp(current->name, "refs/", 5) && + check_ref_format(current->name + 5)) || + (!fetch_all && + !path_match(current->name, nr_match, match))) { + if (prev == NULL) + *refs = next; + else + prev->next = next; + free(current); + } else + prev = current; + } +} + +static int everything_local(struct ref **refs, int nr_match, char **match) { struct ref *ref; int retval; @@ -267,7 +289,7 @@ static int everything_local(struct ref *refs) track_object_refs = 0; save_commit_buffer = 0; - for (ref = refs; ref; ref = ref->next) { + for (ref = *refs; ref; ref = ref->next) { struct object *o; o = parse_object(ref->old_sha1); @@ -289,28 +311,48 @@ static int everything_local(struct ref *refs) if (cutoff) mark_recent_complete_commits(cutoff); - for (retval = 1; refs ; refs = refs->next) { - const unsigned char *remote = refs->old_sha1; + /* + * Mark all complete remote refs as common refs. + * Don't mark them common yet; the server has to be told so first. + */ + for (ref = *refs; ref; ref = ref->next) { + struct object *o = deref_tag(lookup_object(ref->old_sha1), + NULL, 0); + + if (!o || o->type != commit_type || !(o->flags & COMPLETE)) + continue; + + if (!(o->flags & SEEN)) { + rev_list_push((struct commit *)o, COMMON_REF | SEEN); + + mark_common((struct commit *)o, 1, 1); + } + } + + filter_refs(refs, nr_match, match); + + for (retval = 1, ref = *refs; ref ; ref = ref->next) { + const unsigned char *remote = ref->old_sha1; unsigned char local[20]; struct object *o; - o = parse_object(remote); + o = lookup_object(remote); if (!o || !(o->flags & COMPLETE)) { retval = 0; if (!verbose) continue; fprintf(stderr, "want %s (%s)\n", sha1_to_hex(remote), - refs->name); + ref->name); continue; } - memcpy(refs->new_sha1, local, 20); + memcpy(ref->new_sha1, local, 20); if (!verbose) continue; fprintf(stderr, "already have %s (%s)\n", sha1_to_hex(remote), - refs->name); + ref->name); } return retval; } @@ -320,53 +362,43 @@ static int fetch_pack(int fd[2], int nr_match, char **match) struct ref *ref; unsigned char sha1[20]; int status; - pid_t pid; - get_remote_heads(fd[0], &ref, nr_match, match, 1); + get_remote_heads(fd[0], &ref, 0, NULL, 0); + if (server_supports("multi_ack")) { + if (verbose) + fprintf(stderr, "Server supports multi_ack\n"); + multi_ack = 1; + } if (!ref) { packet_flush(fd[1]); die("no matching remote head"); } - if (everything_local(ref)) { + if (everything_local(&ref, nr_match, match)) { packet_flush(fd[1]); goto all_done; } if (find_common(fd, sha1, ref) < 0) - fprintf(stderr, "warning: no common commits\n"); - pid = fork(); - if (pid < 0) - die("git-fetch-pack: unable to fork off git-unpack-objects"); - if (!pid) { - dup2(fd[0], 0); - close(fd[0]); - close(fd[1]); - execlp("git-unpack-objects", "git-unpack-objects", - quiet ? "-q" : NULL, NULL); - die("git-unpack-objects exec failed"); - } - close(fd[0]); - close(fd[1]); - while (waitpid(pid, &status, 0) < 0) { - if (errno != EINTR) - die("waiting for git-unpack-objects: %s", strerror(errno)); - } - if (WIFEXITED(status)) { - int code = WEXITSTATUS(status); - if (code) - die("git-unpack-objects died with error code %d", code); -all_done: - while (ref) { - printf("%s %s\n", - sha1_to_hex(ref->old_sha1), ref->name); - ref = ref->next; - } - return 0; - } - if (WIFSIGNALED(status)) { - int sig = WTERMSIG(status); - die("git-unpack-objects died of signal %d", sig); + if (!keep_pack) + /* When cloning, it is not unusual to have + * no common commit. + */ + fprintf(stderr, "warning: no common commits\n"); + + if (keep_pack) + status = receive_keep_pack(fd, "git-fetch-pack", quiet); + else + status = receive_unpack_pack(fd, "git-fetch-pack", quiet); + + if (status) + die("git-fetch-pack: fetch failed."); + + all_done: + while (ref) { + printf("%s %s\n", + sha1_to_hex(ref->old_sha1), ref->name); + ref = ref->next; } - die("Sherlock Holmes! git-unpack-objects died of unnatural causes %d!", status); + return 0; } int main(int argc, char **argv) @@ -376,6 +408,8 @@ int main(int argc, char **argv) int fd[2]; pid_t pid; + setup_git_directory(); + nr_heads = 0; heads = NULL; for (i = 1; i < argc; i++) { @@ -386,10 +420,22 @@ int main(int argc, char **argv) exec = arg + 7; continue; } - if (!strcmp("-q", arg)) { + if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) { quiet = 1; continue; } + if (!strcmp("--keep", arg) || !strcmp("-k", arg)) { + keep_pack = 1; + continue; + } + if (!strcmp("--thin", arg)) { + use_thin_pack = 1; + continue; + } + if (!strcmp("--all", arg)) { + fetch_all = 1; + continue; + } if (!strcmp("-v", arg)) { verbose = 1; continue; @@ -403,6 +449,8 @@ int main(int argc, char **argv) } if (!dest) usage(fetch_pack_usage); + if (keep_pack) + use_thin_pack = 0; pid = git_connect(fd, dest, exec); if (pid < 0) return 1; @@ -410,5 +458,19 @@ int main(int argc, char **argv) close(fd[0]); close(fd[1]); finish_connect(pid); + + if (!ret && nr_heads) { + /* If the heads to pull were given, we should have + * consumed all of them by matching the remote. + * Otherwise, 'git-fetch remote no-such-ref' would + * silently succeed without issuing an error. + */ + for (i = 0; i < nr_heads; i++) + if (heads[i] && heads[i][0]) { + error("no such remote ref %s", heads[i]); + ret = 1; + } + } + return ret; }