commit 7848a0e1655d11a8d85bb505fc6232bd779cddae from: Stefan Sperling date: Thu Mar 19 16:57:27 2020 UTC implement 'got fetch' commit - 5a48964292096032ae4d1425bdf8ce32c465fa62 commit + 7848a0e1655d11a8d85bb505fc6232bd779cddae blob - 6020acf53f18e27979ff823bb2a70cffabc55a18 blob + 6a2b9b414c22a2c1659c1119036c102f15e0f703 --- got/got.1 +++ got/got.1 @@ -176,6 +176,16 @@ to use: Short alias for git+ssh. .El .Pp +.Cm got clone +creates a remote repository entry in the +.Pa config +file of the cloned repository to store the +.Ar repository-url +for future use by +.Cm got fetch +and +.Xr git-fetch 1 . +.Pp The options for .Cm got clone are as follows: @@ -196,6 +206,58 @@ The maximum is 3. .It Cm cl Short alias for .Cm clone . +.It Cm fetch Oo Fl r Ar repository-path Oc Oo Fl q Oc Oo Fl v Oc Op Ar remote-repository-name +Fetch new changes from a remote repository. +If no +.Ar remote-repository-name +is specified the name +.Dq origin +will be used. +The remote repository's URL is obtained from the corresponding entry in the +.Pa config +file of the repository, as created by +.Cm got clone . +.Pp +Branch references in the +.Dq refs/remotes/ +reference namespace will be updated to point at the newly fetched commits. +The +.Cm got rebase +command can then be used to make new changes visible on branches in the +.Dq refs/heads/ +reference namespace. +.Pp +Existing references in the +.Dq refs/tags/ +namespace will be changed to match tags contained in the remote repository. +.Pp +The options for +.Cm got fetch +are as follows: +.Bl -tag -width Ds +.It Fl r Ar repository-path +Use the repository at the specified path. +If not specified, assume the repository is located at or above the current +working directory. +If this directory is a +.Nm +work tree, use the repository path associated with this work tree. +.It Fl q +Suppress progress reporting output. +The same option will be passed to +.Xr ssh 1 +if applicable. +.It Fl v +Increase the verbosity of progress reporting output. +The same option will be passed to +.Xr ssh 1 +if applicable. +Multiple -v options increase the verbosity. +The maximum is 3. +.El +.It Cm fe +Short alias for +.Cm fetch . .It Cm checkout Oo Fl E Oc Oo Fl b Ar branch Oc Oo Fl c Ar commit Oc Oo Fl p Ar path-prefix Oc Ar repository-path Op Ar work-tree-path Copy files from a repository into a new work tree. Show the status of each affected file, using the following status codes: @@ -1681,11 +1743,11 @@ branch: .Pp Additional steps are necessary if local changes need to be pushed back to the remote repository, which currently requires -.Cm git fetch -and .Cm git push . Before working against existing branches in a repository cloned with -.Dq git clone --bare , +.Dq git clone --bare +instead of +.Cm got clone , a Git .Dq refspec must be configured to map all references in the remote repository @@ -1724,10 +1786,10 @@ Branches in the .Dq remotes/origin namespace can be updated with incoming changes from the remote repository with -.Cm git fetch : +.Cm got fetch : .Pp .Dl $ cd /var/git/repo -.Dl $ git fetch +.Dl $ got fetch .Pp To make changes fetched from the remote repository appear on the .Dq master blob - 086fa5e94b0ac5144f558461eee4c5016a4898ff blob + 0da589c91c4b3f77a139cfb32d30413a5afd5b84 --- got/got.c +++ got/got.c @@ -86,6 +86,7 @@ __dead static void usage(int); __dead static void usage_init(void); __dead static void usage_import(void); __dead static void usage_clone(void); +__dead static void usage_fetch(void); __dead static void usage_checkout(void); __dead static void usage_update(void); __dead static void usage_log(void); @@ -112,6 +113,7 @@ __dead static void usage_cat(void); static const struct got_error* cmd_init(int, char *[]); static const struct got_error* cmd_import(int, char *[]); static const struct got_error* cmd_clone(int, char *[]); +static const struct got_error* cmd_fetch(int, char *[]); static const struct got_error* cmd_checkout(int, char *[]); static const struct got_error* cmd_update(int, char *[]); static const struct got_error* cmd_log(int, char *[]); @@ -139,6 +141,7 @@ static struct got_cmd got_commands[] = { { "init", cmd_init, usage_init, "in" }, { "import", cmd_import, usage_import, "im" }, { "clone", cmd_clone, usage_clone, "cl" }, + { "fetch", cmd_fetch, usage_fetch, "fe" }, { "checkout", cmd_checkout, usage_checkout, "co" }, { "update", cmd_update, usage_update, "up" }, { "log", cmd_log, usage_log, "" }, @@ -1022,8 +1025,9 @@ cmd_clone(int argc, char *argv[]) fpa.last_p_indexed = -1; fpa.last_p_resolved = -1; fpa.verbosity = verbosity; - error = got_fetch_pack(&pack_hash, &refs, &symrefs, fetchfd, - repo, fetch_progress, &fpa); + error = got_fetch_pack(&pack_hash, &refs, &symrefs, + GOT_FETCH_DEFAULT_REMOTE_NAME, fetchfd, repo, + fetch_progress, &fpa); if (error) goto done; @@ -1153,9 +1157,330 @@ done: free(gitconfig_path); free(git_url); return error; +} + +static const struct got_error * +create_ref(const char *refname, struct got_object_id *id, + const char *id_str, struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_reference *ref; + + printf("Creating %s: %s\n", refname, id_str); + + err = got_ref_alloc(&ref, refname, id); + if (err) + return err; + + err = got_ref_write(ref, repo); + got_ref_close(ref); + return err; +} + +static const struct got_error * +update_ref(struct got_reference *ref, struct got_object_id *new_id, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + char *new_id_str = NULL; + struct got_object_id *old_id = NULL; + + err = got_object_id_str(&new_id_str, new_id); + if (err) + goto done; + + if (got_ref_is_symbolic(ref)) { + struct got_reference *new_ref; + err = got_ref_alloc(&new_ref, got_ref_get_name(ref), new_id); + if (err) + goto done; + printf("Deleting symbolic reference %s -> %s\n", + got_ref_get_name(ref), got_ref_get_symref_target(ref)); + err = got_ref_delete(ref, repo); + if (err) + goto done; + printf("Setting %s to %s\n", got_ref_get_name(ref), + new_id_str); + err = got_ref_write(new_ref, repo); + if (err) + goto done; + } else { + err = got_ref_resolve(&old_id, repo, ref); + if (err) + goto done; + if (got_object_id_cmp(old_id, new_id) != 0) { + printf("Setting %s to %s\n", + got_ref_get_name(ref), new_id_str); + err = got_ref_change_ref(ref, new_id); + if (err) + goto done; + err = got_ref_write(ref, repo); + if (err) + goto done; + } + } +done: + free(old_id); + free(new_id_str); + return err; } __dead static void +usage_fetch(void) +{ + fprintf(stderr, "usage: %s fetch [-r repository-path] [-q] [-v] " + "[remote-repository-name]\n", getprogname()); + exit(1); +} + +static const struct got_error * +cmd_fetch(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + char *cwd = NULL, *repo_path = NULL; + const char *remote_name; + char *proto = NULL, *host = NULL, *port = NULL; + char *repo_name = NULL, *server_path = NULL; + struct got_remote_repo *remotes, *remote = NULL; + int nremotes; + char *id_str = NULL; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + struct got_pathlist_head refs, symrefs; + struct got_pathlist_entry *pe; + struct got_object_id *pack_hash = NULL; + int i, ch, fetchfd = -1; + struct got_fetch_progress_arg fpa; + int verbosity = 0; + + TAILQ_INIT(&refs); + TAILQ_INIT(&symrefs); + + while ((ch = getopt(argc, argv, "r:vq")) != -1) { + switch (ch) { + case 'r': + repo_path = realpath(optarg, NULL); + if (repo_path == NULL) + return got_error_from_errno2("realpath", + optarg); + got_path_strip_trailing_slashes(repo_path); + break; + case 'v': + if (verbosity < 0) + verbosity = 0; + else if (verbosity < 3) + verbosity++; + break; + case 'q': + verbosity = -1; + break; + default: + usage_fetch(); + break; + } + } + argc -= optind; + argv += optind; + + if (argc == 0) + remote_name = GOT_FETCH_DEFAULT_REMOTE_NAME; + else if (argc == 1) + remote_name = argv[0]; + else + usage_fetch(); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + else + error = NULL; + if (worktree) { + repo_path = + strdup(got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) + error = got_error_from_errno("strdup"); + if (error) + goto done; + } else { + repo_path = strdup(cwd); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + + error = got_repo_open(&repo, repo_path, NULL); + if (error) + goto done; + + got_repo_get_gitconfig_remotes(&nremotes, &remotes, repo); + for (i = 0; i < nremotes; i++) { + remote = &remotes[i]; + if (strcmp(remote->name, remote_name) == 0) + break; + } + if (i == nremotes) { + error = got_error_path(remote_name, GOT_ERR_NO_REMOTE); + goto done; + } + + error = got_fetch_parse_uri(&proto, &host, &port, &server_path, + &repo_name, remote->url); + if (error) + goto done; + + if (strcmp(proto, "git") == 0) { +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec " + "sendfd dns inet unveil", NULL) == -1) + err(1, "pledge"); +#endif + } else if (strcmp(proto, "git+ssh") == 0 || + strcmp(proto, "ssh") == 0) { +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec " + "sendfd unveil", NULL) == -1) + err(1, "pledge"); +#endif + } else if (strcmp(proto, "http") == 0 || + strcmp(proto, "git+http") == 0) { + error = got_error_path(proto, GOT_ERR_NOT_IMPL); + goto done; + } else { + error = got_error_path(proto, GOT_ERR_BAD_PROTO); + goto done; + } + + if (strcmp(proto, "git+ssh") == 0 || strcmp(proto, "ssh") == 0) { + if (unveil(GOT_FETCH_PATH_SSH, "x") != 0) { + error = got_error_from_errno2("unveil", + GOT_FETCH_PATH_SSH); + goto done; + } + } + error = apply_unveil(got_repo_get_path(repo), 0, NULL); + if (error) + goto done; + + error = got_fetch_connect(&fetchfd, proto, host, port, server_path, + verbosity); + if (error) + goto done; + + if (verbosity >= 0) + printf("Connected to \"%s\" %s:%s\n", remote->name, host, port); + + fpa.last_scaled_size[0] = '\0'; + fpa.last_p_indexed = -1; + fpa.last_p_resolved = -1; + fpa.verbosity = verbosity; + error = got_fetch_pack(&pack_hash, &refs, &symrefs, remote->name, + fetchfd, repo, fetch_progress, &fpa); + if (error) + goto done; + + if (pack_hash == NULL) { + if (verbosity >= 0) + printf("Already up-to-date\n"); + goto done; + } + + error = got_object_id_str(&id_str, pack_hash); + if (error) + goto done; + if (verbosity >= 0) + printf("Fetched %s.pack\n", id_str); + free(id_str); + id_str = NULL; + + /* Update references provided with the pack file. */ + TAILQ_FOREACH(pe, &refs, entry) { + const char *refname = pe->path; + struct got_object_id *id = pe->data; + struct got_reference *ref; + char *remote_refname; + + error = got_object_id_str(&id_str, id); + if (error) + goto done; + + if (strncmp("refs/tags/", refname, 10) == 0) { + error = got_ref_open(&ref, repo, refname, 0); + if (error) { + if (error->code != GOT_ERR_NOT_REF) + goto done; + error = create_ref(refname, id, id_str, repo); + if (error) + goto done; + } else { + error = update_ref(ref, id, repo); + got_ref_close(ref); + if (error) + goto done; + } + } else if (strncmp("refs/heads/", refname, 11) == 0) { + if (asprintf(&remote_refname, "refs/remotes/%s/%s", + remote_name, refname + 11) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + + error = got_ref_open(&ref, repo, remote_refname, 0); + if (error) { + if (error->code != GOT_ERR_NOT_REF) + goto done; + error = create_ref(refname, id, id_str, repo); + if (error) + goto done; + } else { + error = update_ref(ref, id, repo); + got_ref_close(ref); + if (error) + goto done; + } + } + free(id_str); + id_str = NULL; + } +done: + if (fetchfd != -1 && close(fetchfd) == -1 && error == NULL) + error = got_error_from_errno("close"); + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + TAILQ_FOREACH(pe, &refs, entry) { + free((void *)pe->path); + free(pe->data); + } + got_pathlist_free(&refs); + TAILQ_FOREACH(pe, &symrefs, entry) { + free((void *)pe->path); + free(pe->data); + } + got_pathlist_free(&symrefs); + free(id_str); + free(cwd); + free(repo_path); + free(pack_hash); + free(proto); + free(host); + free(port); + free(server_path); + free(repo_name); + return error; +} + + +__dead static void usage_checkout(void) { fprintf(stderr, "usage: %s checkout [-E] [-b branch] [-c commit] " blob - 55d93ab70d98f9f6dfbba566cc862f46d032a1ef blob + d29011ab619ea8abc514f16e7e1fa5a63b015a55 --- include/got_error.h +++ include/got_error.h @@ -137,6 +137,7 @@ #define GOT_ERR_BAD_PROTO 120 #define GOT_ERR_ADDRINFO 121 #define GOT_ERR_BAD_PACKET 122 +#define GOT_ERR_NO_REMOTE 123 static const struct got_error { int code; @@ -280,6 +281,7 @@ static const struct got_error { { GOT_ERR_BAD_PROTO, "unknown protocol" }, { GOT_ERR_ADDRINFO, "getaddrinfo failed" }, { GOT_ERR_BAD_PACKET, "bad packet received" }, + { GOT_ERR_NO_REMOTE, "remote repository not found" }, }; /* blob - d7b583b6dcab6977ecda8490496bbba79462bc20 blob + 8b9f1d33c0c131a2bcf34368195401b48de3bead --- include/got_fetch.h +++ include/got_fetch.h @@ -58,5 +58,5 @@ typedef const struct got_error *(*got_fetch_progress_c * references and symbolic references learned from the server. */ const struct got_error *got_fetch_pack(struct got_object_id **, - struct got_pathlist_head *, struct got_pathlist_head *, int, - struct got_repository *, got_fetch_progress_cb, void *); + struct got_pathlist_head *, struct got_pathlist_head *, const char *, + int, struct got_repository *, got_fetch_progress_cb, void *); blob - 0d45d173276f8ac93d898c2c552e2430bdbb95eb blob + 1f30cd2190941327b8e9cfdaa88af0b083e996d6 --- lib/fetch.c +++ lib/fetch.c @@ -387,8 +387,9 @@ check_pack_hash(int fd, size_t sz, uint8_t *hcomp) const struct got_error* got_fetch_pack(struct got_object_id **pack_hash, struct got_pathlist_head *refs, - struct got_pathlist_head *symrefs, int fetchfd, struct got_repository *repo, - got_fetch_progress_cb progress_cb, void *progress_arg) + struct got_pathlist_head *symrefs, const char *remote_name, int fetchfd, + struct got_repository *repo, got_fetch_progress_cb progress_cb, + void *progress_arg) { int imsg_fetchfds[2], imsg_idxfds[2]; int packfd = -1, npackfd = -1, idxfd = -1, nidxfd = -1, nfetchfd = -1; @@ -402,7 +403,11 @@ got_fetch_pack(struct got_object_id **pack_hash, struc const char *repo_path = got_repo_get_path(repo); struct got_pathlist_head have_refs; struct got_pathlist_entry *pe; + struct got_reflist_head my_refs; + struct got_reflist_entry *re; off_t packfile_size = 0; + char *ref_prefix = NULL; + size_t ref_prefixlen = 0; char *path; *pack_hash = NULL; @@ -410,7 +415,62 @@ got_fetch_pack(struct got_object_id **pack_hash, struc tmpfds[i] = -1; TAILQ_INIT(&have_refs); + SIMPLEQ_INIT(&my_refs); + + if (asprintf(&ref_prefix, "refs/remotes/%s/", remote_name) == -1) + return got_error_from_errno("asprintf"); + ref_prefixlen = strlen(ref_prefix); + + err = got_ref_list(&my_refs, repo, NULL, got_ref_cmp_by_name, NULL); + if (err) + goto done; + + SIMPLEQ_FOREACH(re, &my_refs, entry) { + struct got_object_id *id; + const char *refname; + + if (got_ref_is_symbolic(re->ref)) + continue; + refname = got_ref_get_name(re->ref); + if (strncmp("refs/tags/", refname, 10) == 0) { + char *tagname; + + err = got_ref_resolve(&id, repo, re->ref); + if (err) + goto done; + tagname = strdup(refname); + if (tagname == NULL) { + err = got_error_from_errno("strdup"); + goto done; + } + err = got_pathlist_append(&have_refs, tagname, id); + if (err) { + free(tagname); + goto done; + } + } + + if (strncmp(ref_prefix, refname, ref_prefixlen) == 0) { + char *branchname; + + err = got_ref_resolve(&id, repo, re->ref); + if (err) + goto done; + + if (asprintf(&branchname, "refs/heads/%s", + refname + ref_prefixlen) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + err = got_pathlist_append(&have_refs, branchname, id); + if (err) { + free(branchname); + goto done; + } + } + } + if (asprintf(&path, "%s/%s/fetching.pack", repo_path, GOT_OBJECTS_PACK_DIR) == -1) { err = got_error_from_errno("asprintf"); @@ -490,15 +550,19 @@ got_fetch_pack(struct got_object_id **pack_hash, struc struct got_object_id *id = NULL; char *refname = NULL; char *server_progress = NULL; - off_t packfile_size_cur; + off_t packfile_size_cur = 0; err = got_privsep_recv_fetch_progress(&done, &id, &refname, symrefs, &server_progress, &packfile_size_cur, &fetchibuf); if (err != NULL) goto done; - if (done) - *pack_hash = id; + if (done) { + if (packfile_size > 0) + *pack_hash = id; + else + free(id); + } else if (refname && id) { err = got_pathlist_append(refs, refname, id); if (err) @@ -529,6 +593,10 @@ got_fetch_pack(struct got_object_id **pack_hash, struc goto done; } + /* If zero data was fetched without error we are already up-to-date. */ + if (packfile_size == 0) + goto done; + if (lseek(packfd, 0, SEEK_SET) == -1) { err = got_error_from_errno("lseek"); goto done; @@ -615,12 +683,20 @@ got_fetch_pack(struct got_object_id **pack_hash, struc err = got_error_from_errno3("rename", tmppackpath, packpath); goto done; } + free(tmppackpath); + tmppackpath = NULL; if (rename(tmpidxpath, idxpath) == -1) { err = got_error_from_errno3("rename", tmpidxpath, idxpath); goto done; } + free(tmpidxpath); + tmpidxpath = NULL; done: + if (tmppackpath && unlink(tmppackpath) == -1 && err == NULL) + err = got_error_from_errno2("unlink", tmppackpath); + if (tmpidxpath && unlink(tmpidxpath) == -1 && err == NULL) + err = got_error_from_errno2("unlink", tmpidxpath); if (nfetchfd != -1 && close(nfetchfd) == -1 && err == NULL) err = got_error_from_errno("close"); if (npackfd != -1 && close(npackfd) == -1 && err == NULL) @@ -637,7 +713,14 @@ done: free(tmpidxpath); free(idxpath); free(packpath); + free(ref_prefix); + TAILQ_FOREACH(pe, &have_refs, entry) { + free((char *)pe->path); + free(pe->data); + } + got_pathlist_free(&have_refs); + got_ref_list_free(&my_refs); if (err) { free(*pack_hash); *pack_hash = NULL; blob - 060d60ceb22e8d199bea83b95a778e986b0bfc62 blob + 11db131869f793fff0ce97b621c4c2b7624386b5 --- lib/got_lib_privsep.h +++ lib/got_lib_privsep.h @@ -244,7 +244,8 @@ struct got_imsg_fetch_have_ref { uint8_t id[SHA1_DIGEST_LENGTH]; size_t name_len; /* Followed by name_len data bytes. */ -}; +} __attribute__((__packed__)); + struct got_imsg_fetch_have_refs { size_t n_have_refs; /* Followed by n_have_refs times of got_imsg_fetch_have_ref data. */ blob - 03fb686ffdb8eb99293c6996252cfa4738d6fa99 blob + 72ebc3760b2c98899dac5ee47669da3c7ec6e7e3 --- lib/privsep.c +++ lib/privsep.c @@ -421,9 +421,7 @@ got_privsep_send_fetch_req(struct imsgbuf *ibuf, int f len = sizeof(struct got_imsg_fetch_symrefs); TAILQ_FOREACH(pe, have_refs, entry) { - struct got_object_id *id = pe->data; - len += sizeof(struct got_imsg_fetch_have_ref) + - pe->path_len + sizeof(id->sha1); + len += sizeof(struct got_imsg_fetch_have_ref) + pe->path_len; n_have_refs++; } if (len >= MAX_IMSGSIZE - IMSG_HEADER_SIZE) { blob - 6a3bed95d5319a66b970cafd9a692e11ff2877d6 blob + 548824f71f789dc7064065fc3f8a3347db2cb07e --- libexec/got-fetch-pack/got-fetch-pack.c +++ libexec/got-fetch-pack/got-fetch-pack.c @@ -145,7 +145,7 @@ static const struct got_error * readpkt(int *outlen, int fd, char *buf, int buflen) { const struct got_error *err = NULL; - int datalen; + int datalen, i; ssize_t n; err = read_pkthdr(&datalen, fd); @@ -161,6 +161,16 @@ readpkt(int *outlen, int fd, char *buf, int buflen) if (n != datalen) return got_error_msg(GOT_ERR_BAD_PACKET, "short packet"); + if (chattygit) { + fprintf(stderr, "readpkt: %zd:\t", n); + fwrite(buf, 1, n, stderr); + for (i = 0; i < n; i++) { + if (isprint(buf[i])) + fputc(buf[i], stderr); + } + fputc('\n', stderr); + } + *outlen = n; return NULL; } @@ -196,22 +206,23 @@ writepkt(int fd, char *buf, int nbuf) return NULL; } -static const struct got_error * -match_remote_ref(struct got_pathlist_head *have_refs, struct got_object_id *id, - char *refname, char *id_str) +static void +match_remote_ref(struct got_pathlist_head *have_refs, + struct got_object_id *my_id, char *refname) { struct got_pathlist_entry *pe; - memset(id, 0, sizeof(*id)); + /* XXX zero-hash signifies we don't have this ref; + * we should use a flag instead */ + memset(my_id, 0, sizeof(*my_id)); TAILQ_FOREACH(pe, have_refs, entry) { - if (strcmp(pe->path, refname) == 0) { - if (!got_parse_sha1_digest(id->sha1, id_str)) - return got_error(GOT_ERR_BAD_OBJ_ID_STR); + struct got_object_id *id = pe->data; + if (strcmp(pe->path, refname) == 0) { + memcpy(my_id, id, sizeof(*my_id)); break; } } - return NULL; } static int @@ -468,7 +479,7 @@ fetch_pack(int fd, int packfd, struct got_object_id *p char hashstr[SHA1_DIGEST_STRING_LENGTH]; struct got_object_id *have, *want; int is_firstpkt = 1, nref = 0, refsz = 16; - int i, n, req; + int i, n, nwant = 0, nhave = 0, acked = 0; off_t packsz = 0, last_reported_packsz = 0; char *id_str = NULL, *refname = NULL; char *server_capabilities = NULL, *my_capabilities = NULL; @@ -516,8 +527,9 @@ fetch_pack(int fd, int packfd, struct got_object_id *p err = got_privsep_send_fetch_symrefs(ibuf, &symrefs); if (err) goto done; + is_firstpkt = 0; + continue; } - is_firstpkt = 0; if (strstr(refname, "^{}")) continue; if (fetchbranch && !match_branch(refname, fetchbranch)) @@ -539,21 +551,17 @@ fetch_pack(int fd, int packfd, struct got_object_id *p err = got_error(GOT_ERR_BAD_OBJ_ID_STR); goto done; } - - err = match_remote_ref(have_refs, &have[nref], id_str, refname); - if (err) - goto done; - + match_remote_ref(have_refs, &have[nref], refname); err = got_privsep_send_fetch_ref(ibuf, &want[nref], refname); if (err) goto done; + if (chattygit) fprintf(stderr, "remote %s\n", refname); nref++; } - req = 0; for (i = 0; i < nref; i++) { if (got_object_id_cmp(&have[i], &want[i]) == 0) continue; @@ -568,15 +576,22 @@ fetch_pack(int fd, int packfd, struct got_object_id *p err = writepkt(fd, buf, n); if (err) goto done; - req = 1; + nwant++; } err = flushpkt(fd); if (err) goto done; + + if (nwant == 0) { + if (chattygit) + fprintf(stderr, "up to date\n"); + goto done; + } + for (i = 0; i < nref; i++) { if (got_object_id_cmp(&have[i], &zhash) == 0) continue; - got_sha1_digest_to_str(want[i].sha1, hashstr, sizeof(hashstr)); + got_sha1_digest_to_str(have[i].sha1, hashstr, sizeof(hashstr)); n = snprintf(buf, sizeof(buf), "have %s\n", hashstr); if (n >= sizeof(buf)) { err = got_error(GOT_ERR_NO_SPACE); @@ -585,32 +600,52 @@ fetch_pack(int fd, int packfd, struct got_object_id *p err = writepkt(fd, buf, n + 1); if (err) goto done; + nhave++; } - if (!req) { - if (chattygit) - fprintf(stderr, "up to date\n"); - err = flushpkt(fd); + + while (nhave > 0 && !acked) { + struct got_object_id common_id; + + /* The server should ACK the object IDs we need. */ + err = readpkt(&n, fd, buf, sizeof(buf)); if (err) goto done; + if (n >= 4 && strncmp(buf, "ERR ", 4) == 0) { + err = fetch_error(&buf[4], n - 4); + goto done; + } + if (n >= 4 && strncmp(buf, "NAK\n", 4) == 0) { + /* Server has not located our objects yet. */ + continue; + } + if (n < 4 + SHA1_DIGEST_STRING_LENGTH || + strncmp(buf, "ACK ", 4) != 0) { + err = got_error_msg(GOT_ERR_BAD_PACKET, + "unexpected message from server"); + goto done; + } + if (!got_parse_sha1_digest(common_id.sha1, buf + 4)) { + err = got_error_msg(GOT_ERR_BAD_PACKET, + "bad object ID in ACK packet from server"); + goto done; + } + acked++; } + n = snprintf(buf, sizeof(buf), "done\n"); err = writepkt(fd, buf, n); if (err) goto done; - if (!req) - return 0; - err = readpkt(&n, fd, buf, sizeof(buf)); - if (err) - goto done; - /* - * For now, we only support a full clone, in which case the server - * will now send a "NAK" (meaning no common objects were found). - */ - if (n != 4 || strncmp(buf, "NAK\n", n) != 0) { - err = got_error_msg(GOT_ERR_BAD_PACKET, - "unexpected message from server"); - goto done; + if (nhave == 0) { + err = readpkt(&n, fd, buf, sizeof(buf)); + if (err) + goto done; + if (n != 4 || strncmp(buf, "NAK\n", n) != 0) { + err = got_error_msg(GOT_ERR_BAD_PACKET, + "unexpected message from server"); + goto done; + } } if (chattygit) @@ -739,13 +774,20 @@ int main(int argc, char **argv) { const struct got_error *err = NULL; - int fetchfd, packfd = -1; + int fetchfd, packfd = -1, i; struct got_object_id packid; struct imsgbuf ibuf; struct imsg imsg; struct got_pathlist_head have_refs; + struct got_pathlist_entry *pe; struct got_imsg_fetch_have_refs *fetch_have_refs = NULL; + struct got_imsg_fetch_have_ref *href = NULL; size_t datalen; +#if 0 + static int attached; + while (!attached) + sleep (1); +#endif TAILQ_INIT(&have_refs); @@ -780,17 +822,45 @@ main(int argc, char **argv) goto done; } fetch_have_refs = (struct got_imsg_fetch_have_refs *)imsg.data; - if (datalen != sizeof(struct got_imsg_fetch_have_refs) + + if (datalen < sizeof(struct got_imsg_fetch_have_refs) + sizeof(struct got_imsg_fetch_have_ref) * fetch_have_refs->n_have_refs) { err = got_error(GOT_ERR_PRIVSEP_LEN); goto done; } - if (fetch_have_refs->n_have_refs != 0) { - /* TODO: Incremental fetch support */ - err = got_error(GOT_ERR_NOT_IMPL); - goto done; - } + href = (struct got_imsg_fetch_have_ref *)( + (uint8_t *)fetch_have_refs + sizeof(fetch_have_refs->n_have_refs)); + for (i = 0; i < fetch_have_refs->n_have_refs; i++) { + struct got_object_id *id; + char *refname; + + if (datalen < sizeof(*href) + href->name_len) { + err = got_error(GOT_ERR_PRIVSEP_LEN); + goto done; + } + datalen -= sizeof(*href) + href->name_len; + refname = strndup((uint8_t *)href + sizeof(href->id) + + sizeof(href->name_len), href->name_len); + if (refname == NULL) { + err = got_error_from_errno("strndump"); + goto done; + } + id = malloc(sizeof(*id)); + if (id == NULL) { + free(refname); + err = got_error_from_errno("malloc"); + goto done; + } + memcpy(id->sha1, href->id, SHA1_DIGEST_LENGTH); + err = got_pathlist_append(&have_refs, refname, id); + if (err) { + free(refname); + free(id); + goto done; + } + href = (struct got_imsg_fetch_have_ref *)( + (uint8_t *)href + sizeof(*href) + href->name_len); + } fetchfd = imsg.fd; if ((err = got_privsep_recv_imsg(&imsg, &ibuf, 0)) != 0) { @@ -812,6 +882,11 @@ main(int argc, char **argv) err = fetch_pack(fetchfd, packfd, &packid, &have_refs, &ibuf); done: + TAILQ_FOREACH(pe, &have_refs, entry) { + free((char *)pe->path); + free(pe->data); + } + got_pathlist_free(&have_refs); if (packfd != -1 && close(packfd) == -1 && err == NULL) err = got_error_from_errno("close"); if (err != NULL)