Commit Diff


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)