"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
Re: findtwixt in got-read-pack
To:
gameoftrees@openbsd.org
Date:
Tue, 28 Jun 2022 11:14:48 +0200

Download raw body.

Thread
On Mon, Jun 27, 2022 at 11:53:49PM -0600, Tracey Emery wrote:
> On Mon, Jun 27, 2022 at 11:12:30PM +0200, Stefan Sperling wrote:
> > This patch offloads parts of findtwixt() into got-read-pack if possible.
> > This significantly speeds up commit coloring if a large pack file is
> > available.
> > 
> > I have one some light testing, packing and got send still work as
> > expected for me. More testing would be welcome.
> > 
> > ok?
> > 
> 
> Ok with a couple of things below. If I've missed anything, and I
> probably have, because my normal daily-lack-of-sleep-pattern has now
> resumed, we can fix it in the tree.

There was a bug in pinned pack handling where we could end up
sending a commit painting requests to a non-pinned got-read-pack
process, resulting in an unexpected privsep message error.
I found this during 'got send'.

This bug was fixed at the spot where we move a cached pack to the
front of the cache.

+		if (repo->pinned_pack == 0)
+			repo->pinned_pack = i;

becomes:

+		if (repo->pinned_pack == 0)
+			repo->pinned_pack = i;
+		else if (repo->pinned_pack == i)
+			repo->pinned_pack = 0;

And we now store the PID of the pinned got-read-pack helper.
The PID isn't really used for anything, but was very helpful
for debugging and won't hurt, so I decided to leave it in.

diff refs/heads/main refs/heads/findtwixt-pack
commit - 3d589bee0bbbe812bb91f3b0284fbf2596304132
commit + 7105b92e16596bdb1e37984d3ac5e04a91195984
blob - 8ae16da40c07f4dcb3133f9ff08328a10605722b
blob + 22a9264b9f8d0c0b20b48895dd8ea59708e61d48
--- include/got_error.h
+++ include/got_error.h
@@ -168,6 +168,7 @@
 #define GOT_ERR_HUNK_FAILED	150
 #define GOT_ERR_PATCH_FAILED	151
 #define GOT_ERR_FILEIDX_DUP_ENTRY 152
+#define GOT_ERR_PIN_PACK	153
 
 struct got_error {
         int code;
blob - 29541c0234b7f96a4638157fdd9017888571a76c
blob + 8decec3748ea2448a4255cba6eccf77ced9fab2a
--- lib/error.c
+++ lib/error.c
@@ -216,6 +216,7 @@ static const struct got_error got_errors[] = {
 	{ GOT_ERR_HUNK_FAILED, "hunk failed to apply" },
 	{ GOT_ERR_PATCH_FAILED, "patch failed to apply" },
 	{ GOT_ERR_FILEIDX_DUP_ENTRY, "duplicate file index entry" },
+	{ GOT_ERR_PIN_PACK, "could not pin pack file" },
 };
 
 static struct got_custom_error {
blob - bfee6f4277cb3046739bccf25fc842f391a9b670
blob + 24c7c68b7574559306b53c587c4f7d5e5374d02d
--- lib/got_lib_pack.h
+++ lib/got_lib_pack.h
@@ -218,5 +218,3 @@ const struct got_error *got_packfile_extract_object_to
 const struct got_error *got_packfile_extract_raw_delta(uint8_t **, size_t *,
     size_t *, off_t *, off_t *, struct got_object_id *, uint64_t *, uint64_t *,
     struct got_pack *, struct got_packidx *, int);
-struct got_pack *got_repo_get_cached_pack(struct got_repository *,
-    const char *);
blob - 8e99bbe74f6769ad1550cbcbe10d5ff1368d116d
blob + 6ffe646e98676cf9a0d19fe3ad27f3e63ab04fcc
--- lib/got_lib_privsep.h
+++ lib/got_lib_privsep.h
@@ -194,6 +194,12 @@ enum got_imsg_type {
 	GOT_IMSG_REUSED_DELTAS,
 	GOT_IMSG_DELTA_REUSE_DONE,
 
+	/* Commit coloring in got-read-pack. */
+	GOT_IMSG_COMMIT_PAINTING_INIT,
+	GOT_IMSG_COMMIT_PAINTING_REQUEST,
+	GOT_IMSG_PAINTED_COMMITS,
+	GOT_IMSG_COMMIT_PAINTING_DONE,
+
 	/* Transfer a list of object IDs. */
 	GOT_IMSG_OBJ_ID_LIST,
 	GOT_IMSG_OBJ_ID_LIST_DONE,
@@ -344,6 +350,27 @@ struct got_imsg_reused_deltas {
 	/ sizeof(struct got_imsg_reused_delta))
 };
 
+/* Structure for GOT_IMSG_COMMIT_PAINTING_REQUEST. */
+struct got_imsg_commit_painting_request {
+	uint8_t id[SHA1_DIGEST_LENGTH];
+	int idx;
+	int color;
+} __attribute__((__packed__));
+
+/* Structure for GOT_IMSG_PAINTED_COMMITS. */
+struct got_imsg_painted_commit {
+	uint8_t id[SHA1_DIGEST_LENGTH];
+	intptr_t color;
+} __attribute__((__packed__));
+
+struct got_imsg_painted_commits {
+	int ncommits;
+	int present_in_pack;
+	/*
+	 * Followed by ncommits * struct got_imsg_painted_commit.
+	 */
+} __attribute__((__packed__));
+
 /* Structure for GOT_IMSG_TAG data. */
 struct got_imsg_tag_object {
 	uint8_t id[SHA1_DIGEST_LENGTH];
@@ -782,4 +809,16 @@ const struct got_error *got_privsep_send_reused_deltas
 const struct got_error *got_privsep_recv_reused_deltas(int *, 
     struct got_imsg_reused_delta *, size_t *, struct imsgbuf *);
 
+const struct got_error *got_privsep_init_commit_painting(struct imsgbuf *);
+const struct got_error *got_privsep_send_painting_request(struct imsgbuf *,
+    int, struct got_object_id *, intptr_t);
+typedef const struct got_error *(*got_privsep_recv_painted_commit_cb)(void *,
+    struct got_object_id *, intptr_t);
+const struct got_error *got_privsep_send_painted_commits(struct imsgbuf *,
+    struct got_object_id_queue *, int *, int, int);
+const struct got_error *got_privsep_send_painting_commits_done(struct imsgbuf *);
+const struct got_error *got_privsep_recv_painted_commits(
+    struct got_object_id_queue *, got_privsep_recv_painted_commit_cb, void *,
+    struct imsgbuf *);
+
 void got_privsep_exec_child(int[2], const char *, const char *);
blob - c75567b209bd9f2ce134e3ca05a66884b8dcbc04
blob + 02b998107e294bc2eca6ffb21a128c5973771755
--- lib/got_lib_repository.h
+++ lib/got_lib_repository.h
@@ -72,6 +72,16 @@ struct got_repository {
 	 */
 	int pack_cache_size;
 
+	/*
+	 * Index to cache entries which are pinned to avoid eviction.
+	 * This may be used to keep one got-index-pack process alive
+	 * across searches for arbitrary objects which may be stored
+	 * in other pack files.
+	 */
+	int pinned_pack;
+	pid_t pinned_pid;
+	int pinned_packidx;
+
 	/* Handles to child processes for reading loose objects. */
 	struct got_privsep_child privsep_children[5];
 #define GOT_REPO_PRIVSEP_CHILD_OBJECT	0
@@ -134,3 +144,10 @@ const struct got_error *got_repo_get_packidx(struct go
     struct got_repository *);
 const struct got_error *got_repo_cache_pack(struct got_pack **,
     struct got_repository *, const char *, struct got_packidx *);
+struct got_pack *got_repo_get_cached_pack(struct got_repository *,
+    const char *);
+const struct got_error *got_repo_pin_pack(struct got_repository *,
+    struct got_packidx *, struct got_pack *);
+struct got_pack *got_repo_get_pinned_pack(struct got_repository *);
+void got_repo_unpin_pack(struct got_repository *);
+
blob - 16d1a758fc03fec245de789888abc9cb072f408d
blob + 4ab652c3f6cf093947a9bbce2173c764fb9cb44e
--- lib/pack_create.c
+++ lib/pack_create.c
@@ -563,6 +563,28 @@ send_id(struct got_object_id *id, void *data, void *ar
 }
 
 static const struct got_error *
+send_idset(struct imsgbuf *ibuf, struct got_object_idset *idset)
+{
+	const struct got_error *err;
+	struct send_id_arg sia;
+
+	memset(&sia, 0, sizeof(sia));
+	sia.ibuf = ibuf;
+	err = got_object_idset_for_each(idset, send_id, &sia);
+	if (err)
+		return err;
+
+	if (sia.nids > 0) {
+		err = got_privsep_send_object_idlist(ibuf, sia.ids, sia.nids);
+		if (err)
+			return err;
+	}
+
+	return got_privsep_send_object_idlist_done(ibuf);
+}
+
+
+static const struct got_error *
 recv_reused_delta(struct got_imsg_reused_delta *delta,
     struct got_object_idset *idset, struct got_pack_metavec *v)
 {
@@ -595,10 +617,10 @@ recv_reused_delta(struct got_imsg_reused_delta *delta,
 }
 
 static const struct got_error *
-prepare_delta_reuse(struct got_pack **pack, struct got_packidx *packidx,
-    int delta_outfd, struct got_repository *repo)
+cache_pack_for_packidx(struct got_pack **pack, struct got_packidx *packidx,
+    struct got_repository *repo)
 {
-	const struct got_error *err = NULL;
+	const struct got_error *err;
 	char *path_packfile = NULL;
 
 	err = got_packidx_get_packfile_path(&path_packfile,
@@ -617,8 +639,18 @@ prepare_delta_reuse(struct got_pack **pack, struct got
 		if (err)
 			goto done;
 	}
+done:
+	free(path_packfile);
+	return NULL;
+}
 
-	if (!(*pack)->child_has_delta_outfd) {
+static const struct got_error *
+prepare_delta_reuse(struct got_pack *pack, struct got_packidx *packidx,
+    int delta_outfd, struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+
+	if (!pack->child_has_delta_outfd) {
 		int outfd_child;
 		outfd_child = dup(delta_outfd);
 		if (outfd_child == -1) {
@@ -626,15 +658,14 @@ prepare_delta_reuse(struct got_pack **pack, struct got
 			goto done;
 		}
 		err = got_privsep_send_raw_delta_outfd(
-		    (*pack)->privsep_child->ibuf, outfd_child);
+		    pack->privsep_child->ibuf, outfd_child);
 		if (err)
 			goto done;
-		(*pack)->child_has_delta_outfd = 1;
+		pack->child_has_delta_outfd = 1;
 	}
 
-	err = got_privsep_send_delta_reuse_req((*pack)->privsep_child->ibuf);
+	err = got_privsep_send_delta_reuse_req(pack->privsep_child->ibuf);
 done:
-	free(path_packfile);
 	return err;
 }
 
@@ -649,7 +680,6 @@ search_deltas(struct got_pack_metavec *v, struct got_o
 	const struct got_error *err = NULL;
 	struct got_packidx *packidx;
 	struct got_pack *pack;
-	struct send_id_arg sia;
 	struct got_imsg_reused_delta deltas[GOT_IMSG_REUSED_DELTAS_MAX_NDELTAS];
 	size_t ndeltas, i;
 
@@ -660,22 +690,15 @@ search_deltas(struct got_pack_metavec *v, struct got_o
 	if (packidx == NULL)
 		return NULL;
 
-	err = prepare_delta_reuse(&pack, packidx, delta_cache_fd, repo);
+	err = cache_pack_for_packidx(&pack, packidx, repo);
 	if (err)
 		return err;
 
-	memset(&sia, 0, sizeof(sia));
-	sia.ibuf = pack->privsep_child->ibuf;
-	err = got_object_idset_for_each(idset, send_id, &sia);
+	err = prepare_delta_reuse(pack, packidx, delta_cache_fd, repo);
 	if (err)
 		return err;
-	if (sia.nids > 0) {
-		err = got_privsep_send_object_idlist(pack->privsep_child->ibuf,
-		    sia.ids, sia.nids);
-		if (err)
-			return err;
-	}
-	err = got_privsep_send_object_idlist_done(pack->privsep_child->ibuf);
+
+	err = send_idset(pack->privsep_child->ibuf, idset);
 	if (err)
 		return err;
 
@@ -1217,30 +1240,23 @@ done:
 enum findtwixt_color {
 	COLOR_KEEP = 0,
 	COLOR_DROP,
-	COLOR_BLANK,
 	COLOR_SKIP,
+	COLOR_MAX,
 };
 
-static const int findtwixt_colors[] = {
-	COLOR_KEEP,
-	COLOR_DROP,
-	COLOR_BLANK,
-	COLOR_SKIP,
-};
-
 static const struct got_error *
-paint_commit(struct got_object_qid *qid, int color)
+paint_commit(struct got_object_qid *qid, intptr_t color)
 {
-	if (color < 0 || color >= nitems(findtwixt_colors))
+	if (color < 0 || color >= COLOR_MAX)
 		return got_error(GOT_ERR_RANGE);
 
-	qid->data = (void *)&findtwixt_colors[color];
+	qid->data = (void *)color;
 	return NULL;
 }
 
 static const struct got_error *
 queue_commit_id(struct got_object_id_queue *ids, struct got_object_id *id,
-    int color, struct got_repository *repo)
+    intptr_t color, struct got_repository *repo)
 {
 	const struct got_error *err;
 	struct got_object_qid *qid;
@@ -1277,7 +1293,7 @@ append_id(struct got_object_id *id, void *data, void *
 }
 
 static const struct got_error *
-queue_commit_or_tag_id(struct got_object_id *id, int color,
+queue_commit_or_tag_id(struct got_object_id *id, intptr_t color,
     struct got_object_id_queue *ids, struct got_repository *repo)
 {
 	const struct got_error *err;
@@ -1307,7 +1323,197 @@ done:
 	return err;
 }
 
+struct recv_painted_commit_arg {
+	int *ncolored;
+	int *nqueued;
+	int *nskip;
+	struct got_object_id_queue *ids;
+	struct got_object_idset *keep;
+	struct got_object_idset *drop;
+	struct got_object_idset *skip;
+	got_pack_progress_cb progress_cb;
+	void *progress_arg;
+	struct got_ratelimit *rl;
+	got_cancel_cb cancel_cb;
+	void *cancel_arg;
+};
+
 static const struct got_error *
+recv_painted_commit(void *arg, struct got_object_id *id, intptr_t color)
+{
+	const struct got_error *err = NULL;
+	struct recv_painted_commit_arg *a = arg;
+	struct got_object_qid *qid, *tmp;
+
+	if (a->cancel_cb) {
+		err = a->cancel_cb(a->cancel_arg);
+		if (err)
+			return err;
+	}
+
+	switch (color) {
+	case COLOR_KEEP:
+		err = got_object_idset_add(a->keep, id, NULL);
+		if (err)
+			return err;
+		(*a->ncolored)++;
+		break;
+	case COLOR_DROP:
+		err = got_object_idset_add(a->drop, id, NULL);
+		if (err)
+			return err;
+		(*a->ncolored)++;
+		break;
+	case COLOR_SKIP:
+		err = got_object_idset_add(a->skip, id, NULL);
+		if (err)
+			return err;
+		break;
+	default:
+		/* should not happen */
+		return got_error_fmt(GOT_ERR_NOT_IMPL,
+		    "%s invalid commit color %d", __func__, color);
+	}
+
+	STAILQ_FOREACH_SAFE(qid, a->ids, entry, tmp) {
+		if (got_object_id_cmp(&qid->id, id) != 0)
+			continue;
+		STAILQ_REMOVE(a->ids, qid, got_object_qid, entry);
+		color = (intptr_t)qid->data;
+		got_object_qid_free(qid);
+		(*a->nqueued)--;
+		if (color == COLOR_SKIP)
+			(*a->nskip)--;
+		break;
+	}
+
+	return report_progress(a->progress_cb, a->progress_arg, a->rl,
+	    *a->ncolored, 0, 0, 0L, 0, 0, 0, 0);
+}
+
+static const struct got_error *
+paint_packed_commits(struct got_pack *pack, struct got_object_id *id,
+    int idx, intptr_t color, int *ncolored, int *nqueued, int *nskip,
+    struct got_object_id_queue *ids,
+    struct got_object_idset *keep, struct got_object_idset *drop,
+    struct got_object_idset *skip, struct got_repository *repo,
+    got_pack_progress_cb progress_cb, void *progress_arg,
+    struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg)
+{
+	const struct got_error *err = NULL;
+	struct got_object_id_queue next_ids;
+	struct got_object_qid *qid, *tmp;
+	struct recv_painted_commit_arg arg;
+
+	STAILQ_INIT(&next_ids);
+
+	err = got_privsep_send_painting_request(pack->privsep_child->ibuf,
+	    idx, id, color);
+	if (err)
+		return err;
+
+	arg.ncolored = ncolored;
+	arg.nqueued = nqueued;
+	arg.nskip = nskip;
+	arg.ids = ids;
+	arg.keep = keep;
+	arg.drop = drop;
+	arg.skip = skip;
+	arg.progress_cb = progress_cb;
+	arg.progress_arg = progress_arg;
+	arg.rl = rl;
+	arg.cancel_cb = cancel_cb;
+	arg.cancel_arg = cancel_arg;
+	err = got_privsep_recv_painted_commits(&next_ids,
+	    recv_painted_commit, &arg, pack->privsep_child->ibuf);
+	if (err)
+		return err;
+
+	STAILQ_FOREACH_SAFE(qid, &next_ids, entry, tmp) {
+		struct got_object_qid *old_id;
+		intptr_t qcolor, ocolor;
+		STAILQ_FOREACH(old_id, ids, entry) {
+			if (got_object_id_cmp(&qid->id, &old_id->id))
+				continue;
+			qcolor = (intptr_t)qid->data;
+			ocolor = (intptr_t)old_id->data;
+			STAILQ_REMOVE(&next_ids, qid, got_object_qid, entry);
+			got_object_qid_free(qid);
+			qid = NULL;
+			if (qcolor != ocolor) {
+				paint_commit(old_id, qcolor);
+				if (ocolor == COLOR_SKIP)
+					(*nskip)--;
+				else if (qcolor == COLOR_SKIP)
+					(*nskip)++;
+			}
+			break;
+		}
+	}
+	while (!STAILQ_EMPTY(&next_ids)) {
+		qid = STAILQ_FIRST(&next_ids);
+		STAILQ_REMOVE_HEAD(&next_ids, entry);
+		paint_commit(qid, color);
+		STAILQ_INSERT_TAIL(ids, qid, entry);
+		(*nqueued)++;
+		if (color == COLOR_SKIP)
+			(*nskip)++;
+	}
+
+	return err;
+}
+
+static const struct got_error *
+find_pack_for_commit_painting(struct got_packidx **best_packidx,
+    struct got_object_id_queue *ids, int nids, struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	struct got_pathlist_entry *pe;
+	const char *best_packidx_path = NULL;
+	int nobj_max = 0;
+	int ncommits_max = 0;
+
+	*best_packidx = NULL;
+
+	/*
+	 * Find the largest pack which contains at least some of the
+	 * commits we are interested in.
+	 */
+	TAILQ_FOREACH(pe, &repo->packidx_paths, entry) {
+		const char *path_packidx = pe->path;
+		struct got_packidx *packidx;
+		int nobj, idx, ncommits = 0;
+		struct got_object_qid *qid;
+
+		err = got_repo_get_packidx(&packidx, path_packidx, repo);
+		if (err)
+			break;
+
+		nobj = be32toh(packidx->hdr.fanout_table[0xff]);
+		if (nobj <= nobj_max)
+			continue;
+
+		STAILQ_FOREACH(qid, ids, entry) {
+			idx = got_packidx_get_object_idx(packidx, &qid->id);
+			if (idx != -1)
+				ncommits++;
+		}
+		if (ncommits > ncommits_max) {
+			best_packidx_path = path_packidx;
+			nobj_max = nobj;
+			ncommits_max = ncommits;
+		}
+	}
+
+	if (best_packidx_path && err == NULL) {
+		err = got_repo_get_packidx(best_packidx, best_packidx_path,
+		    repo);
+	}
+
+	return err;
+}
+
+static const struct got_error *
 paint_commits(int *ncolored, struct got_object_id_queue *ids, int nids,
     struct got_object_idset *keep, struct got_object_idset *drop,
     struct got_object_idset *skip, struct got_repository *repo,
@@ -1316,12 +1522,15 @@ paint_commits(int *ncolored, struct got_object_id_queu
 {
 	const struct got_error *err = NULL;
 	struct got_commit_object *commit = NULL;
+	struct got_packidx *packidx = NULL;
+	struct got_pack *pack = NULL;
 	const struct got_object_id_queue *parents;
-	struct got_object_qid *qid;
+	struct got_object_qid *qid = NULL;
 	int nqueued = nids, nskip = 0;
+	int idx;
 
 	while (!STAILQ_EMPTY(ids) && nskip != nqueued) {
-		int color;
+		intptr_t color;
 
 		if (cancel_cb) {
 			err = cancel_cb(cancel_arg);
@@ -1332,21 +1541,49 @@ paint_commits(int *ncolored, struct got_object_id_queu
 		qid = STAILQ_FIRST(ids);
 		STAILQ_REMOVE_HEAD(ids, entry);
 		nqueued--;
-		color = *((int *)qid->data);
+		color = (intptr_t)qid->data;
 		if (color == COLOR_SKIP)
 			nskip--;
 
 		if (got_object_idset_contains(skip, &qid->id)) {
 			got_object_qid_free(qid);
+			qid = NULL;
 			continue;
 		}
+		if (color == COLOR_KEEP &&
+		    got_object_idset_contains(keep, &qid->id)) {
+			got_object_qid_free(qid);
+			qid = NULL;
+			continue;
+		}
+		if (color == COLOR_DROP &&
+		    got_object_idset_contains(drop, &qid->id)) {
+			got_object_qid_free(qid);
+			qid = NULL;
+			continue;
+		}
 
+		/* Pinned pack may have moved to different cache slot. */
+		pack = got_repo_get_pinned_pack(repo);
+
+		if (packidx && pack) {
+			idx = got_packidx_get_object_idx(packidx, &qid->id);
+			if (idx != -1) {
+				err = paint_packed_commits(pack, &qid->id,
+				    idx, color, ncolored, &nqueued, &nskip,
+				    ids, keep, drop, skip, repo,
+				    progress_cb, progress_arg, rl,
+				    cancel_cb, cancel_arg);
+				if (err)
+					break;
+				got_object_qid_free(qid);
+				qid = NULL;
+				continue;
+			}
+		}
+
 		switch (color) {
 		case COLOR_KEEP:
-			if (got_object_idset_contains(keep, &qid->id)) {
-				got_object_qid_free(qid);
-				continue;
-			}
 			if (got_object_idset_contains(drop, &qid->id)) {
 				err = paint_commit(qid, COLOR_SKIP);
 				if (err)
@@ -1358,10 +1595,6 @@ paint_commits(int *ncolored, struct got_object_id_queu
 				goto done;
 			break;
 		case COLOR_DROP:
-			if (got_object_idset_contains(drop, &qid->id)) {
-				got_object_qid_free(qid);
-				continue;
-			}
 			if (got_object_idset_contains(keep, &qid->id)) {
 				err = paint_commit(qid, COLOR_SKIP);
 				if (err)
@@ -1392,7 +1625,6 @@ paint_commits(int *ncolored, struct got_object_id_queu
 		if (err)
 			break;
 
-
 		err = got_object_open_as_commit(&commit, repo, &qid->id);
 		if (err)
 			break;
@@ -1400,10 +1632,10 @@ paint_commits(int *ncolored, struct got_object_id_queu
 		parents = got_object_commit_get_parent_ids(commit);
 		if (parents) {
 			struct got_object_qid *pid;
-			color = *((int *)qid->data);
+			color = (intptr_t)qid->data;
 			STAILQ_FOREACH(pid, parents, entry) {
-				err = queue_commit_id(ids, &pid->id, color,
-				    repo);
+				err = queue_commit_id(ids, &pid->id,
+				    color, repo);
 				if (err)
 					break;
 				nqueued++;
@@ -1412,13 +1644,56 @@ paint_commits(int *ncolored, struct got_object_id_queu
 			}
 		}
 
+		if (pack == NULL && (commit->flags & GOT_COMMIT_FLAG_PACKED)) {
+			if (packidx == NULL) {
+				err = find_pack_for_commit_painting(&packidx,
+				    ids, nqueued, repo);
+				if (err)
+					goto done;
+			}
+			if (packidx != NULL) {
+				err = cache_pack_for_packidx(&pack, packidx,
+				    repo);
+				if (err)
+					goto done;
+				err = got_privsep_init_commit_painting(
+				    pack->privsep_child->ibuf);
+				if (err)
+					goto done;
+				err = send_idset(pack->privsep_child->ibuf,
+				    keep);
+				if (err)
+					goto done;
+				err = send_idset(pack->privsep_child->ibuf, drop);
+				if (err)
+					goto done;
+				err = send_idset(pack->privsep_child->ibuf, skip);
+				if (err)
+					goto done;
+				err = got_repo_pin_pack(repo, packidx, pack);
+				if (err)
+					goto done;
+			}
+		}
+
 		got_object_commit_close(commit);
 		commit = NULL;
+
 		got_object_qid_free(qid);
+		qid = NULL;
 	}
 done:
+	if (pack) {
+		const struct got_error *pack_err;
+		pack_err = got_privsep_send_painting_commits_done(
+		    pack->privsep_child->ibuf);
+		if (err == NULL)
+			err = pack_err;
+	}
 	if (commit)
 		got_object_commit_close(commit);
+	got_object_qid_free(qid);
+	got_repo_unpin_pack(repo);
 	return err;
 }
 
blob - 70eb167c5ee71b29c045bdae0de5c7b7859403f7
blob + c0bdac7221a79c5ec97d1728e862406152d51eb9
--- lib/privsep.c
+++ lib/privsep.c
@@ -3328,6 +3328,182 @@ got_privsep_recv_reused_deltas(int *done, struct got_i
 }
 
 const struct got_error *
+got_privsep_init_commit_painting(struct imsgbuf *ibuf)
+{
+	if (imsg_compose(ibuf, GOT_IMSG_COMMIT_PAINTING_INIT,
+	    0, 0, -1, NULL, 0)
+	    == -1)
+		return got_error_from_errno("imsg_compose "
+		    "COMMIT_PAINTING_INIT");
+
+	return flush_imsg(ibuf);
+}
+
+const struct got_error *
+got_privsep_send_painting_request(struct imsgbuf *ibuf, int idx,
+    struct got_object_id *id, intptr_t color)
+{
+	struct got_imsg_commit_painting_request ireq;
+
+	memset(&ireq, 0, sizeof(ireq));
+	memcpy(ireq.id, id->sha1, sizeof(ireq.id));
+	ireq.idx = idx;
+	ireq.color = color;
+
+	if (imsg_compose(ibuf, GOT_IMSG_COMMIT_PAINTING_REQUEST, 0, 0, -1,
+	    &ireq, sizeof(ireq)) == -1)
+		return got_error_from_errno("imsg_compose "
+		    "COMMIT_PAINTING_REQUEST");
+
+	return flush_imsg(ibuf);
+}
+
+static const struct got_error *
+send_painted_commits(struct got_object_id_queue *ids, int *nids,
+    size_t remain, int present_in_pack, struct imsgbuf *ibuf)
+{
+	const struct got_error *err = NULL;
+	struct ibuf *wbuf = NULL;
+	struct got_object_qid *qid;
+	size_t msglen;
+	int ncommits;
+	intptr_t color;
+
+	msglen = MIN(remain, MAX_IMSGSIZE - IMSG_HEADER_SIZE);
+	ncommits = (msglen - sizeof(struct got_imsg_painted_commits)) /
+	    sizeof(struct got_imsg_painted_commit);
+
+	wbuf = imsg_create(ibuf, GOT_IMSG_PAINTED_COMMITS, 0, 0, msglen);
+	if (wbuf == NULL) {
+		err = got_error_from_errno("imsg_create PAINTED_COMMITS");
+		return err;
+	}
+
+	/* Keep in sync with struct got_imsg_painted_commits! */
+	if (imsg_add(wbuf, &ncommits, sizeof(ncommits)) == -1)
+		return got_error_from_errno("imsg_add PAINTED_COMMITS");
+	if (imsg_add(wbuf, &present_in_pack, sizeof(present_in_pack)) == -1)
+		return got_error_from_errno("imsg_add PAINTED_COMMITS");
+
+	while (ncommits > 0) {
+		qid = STAILQ_FIRST(ids);
+		STAILQ_REMOVE_HEAD(ids, entry);
+		ncommits--;
+		(*nids)--;
+		color = (intptr_t)qid->data;
+
+		/* Keep in sync with struct got_imsg_painted_commit! */
+		if (imsg_add(wbuf, qid->id.sha1, SHA1_DIGEST_LENGTH) == -1)
+			return got_error_from_errno("imsg_add PAINTED_COMMITS");
+		if (imsg_add(wbuf, &color, sizeof(color)) == -1)
+			return got_error_from_errno("imsg_add PAINTED_COMMITS");
+
+		got_object_qid_free(qid);
+	}
+
+	wbuf->fd = -1;
+	imsg_close(ibuf, wbuf);
+
+	return flush_imsg(ibuf);
+}
+
+const struct got_error *
+got_privsep_send_painted_commits(struct imsgbuf *ibuf,
+    struct got_object_id_queue *ids, int *nids,
+    int present_in_pack, int flush)
+{
+	const struct got_error *err;
+	size_t remain;
+
+	if (*nids <= 0)
+		return NULL;
+
+	do {
+		remain = (sizeof(struct got_imsg_painted_commits)) +
+		    *nids * sizeof(struct got_imsg_painted_commit);
+		if (flush || remain >= MAX_IMSGSIZE - IMSG_HEADER_SIZE) {
+			err = send_painted_commits(ids, nids, remain,
+			    present_in_pack, ibuf);
+			if (err)
+				return err;
+		}
+	} while (flush && *nids > 0);
+
+	return NULL;
+}
+
+const struct got_error *
+got_privsep_send_painting_commits_done(struct imsgbuf *ibuf)
+{
+	if (imsg_compose(ibuf, GOT_IMSG_COMMIT_PAINTING_DONE,
+	    0, 0, -1, NULL, 0)
+	    == -1)
+		return got_error_from_errno("imsg_compose "
+		    "COMMIT_PAINTING_DONE");
+
+	return flush_imsg(ibuf);
+}
+
+const struct got_error *
+got_privsep_recv_painted_commits(struct got_object_id_queue *new_ids,
+    got_privsep_recv_painted_commit_cb cb, void *cb_arg, struct imsgbuf *ibuf)
+{
+	const struct got_error *err = NULL;
+	struct imsg imsg;
+	struct got_imsg_painted_commits icommits;
+	struct got_imsg_painted_commit icommit;
+	size_t datalen;
+	int i;
+
+	for (;;) {
+		err = got_privsep_recv_imsg(&imsg, ibuf, 0);
+		if (err)
+			return err;
+
+		datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
+		if (imsg.hdr.type == GOT_IMSG_COMMIT_PAINTING_DONE)
+			break;
+		if (imsg.hdr.type != GOT_IMSG_PAINTED_COMMITS)
+			return got_error(GOT_ERR_PRIVSEP_MSG);
+
+		if (datalen < sizeof(icommits))
+			return got_error(GOT_ERR_PRIVSEP_LEN);
+		memcpy(&icommits, imsg.data, sizeof(icommits));
+		if (icommits.ncommits * sizeof(icommit) < icommits.ncommits ||
+		    datalen < sizeof(icommits) +
+		    icommits.ncommits * sizeof(icommit))
+			return got_error(GOT_ERR_PRIVSEP_LEN);
+
+		for (i = 0; i < icommits.ncommits; i++) {
+			memcpy(&icommit,
+			    (uint8_t *)imsg.data + sizeof(icommits) + i * sizeof(icommit),
+			    sizeof(icommit));
+
+			if (icommits.present_in_pack) {
+				struct got_object_id id;
+				memcpy(id.sha1, icommit.id, SHA1_DIGEST_LENGTH);
+				err = cb(cb_arg, &id, icommit.color);
+				if (err)
+					break;
+			} else {
+				struct got_object_qid *qid;
+				err = got_object_qid_alloc_partial(&qid);
+				if (err)
+					break;
+				memcpy(qid->id.sha1, icommit.id,
+				    SHA1_DIGEST_LENGTH);
+				qid->data = (void *)icommit.color;
+				STAILQ_INSERT_TAIL(new_ids, qid, entry);
+			}
+		}
+
+		imsg_free(&imsg);
+	}
+
+	return err;
+}
+
+const struct got_error *
 got_privsep_unveil_exec_helpers(void)
 {
 	const char *helpers[] = {
blob - 4aba251d6858a54219d51424919806df0ad2b260
blob + 4c93c601016c47cab8439703f5925c65095b4b7e
--- lib/repository.c
+++ lib/repository.c
@@ -752,6 +752,9 @@ got_repo_open(struct got_repository **repop, const cha
 			repo->packs[i].accumfd = -1;
 		}
 	}
+	repo->pinned_pack = -1;
+	repo->pinned_packidx = -1;
+	repo->pinned_pid = 0;
 
 	repo_path = realpath(path, NULL);
 	if (repo_path == NULL) {
@@ -1035,7 +1038,10 @@ cache_packidx(struct got_repository *repo, struct got_
 		}
 	}
 	if (i == repo->pack_cache_size) {
-		i = repo->pack_cache_size - 1;
+		do {
+			i--;
+		} while (i > 0 && repo->pinned_packidx >= 0 &&
+		    i == repo->pinned_packidx);
 		err = got_packidx_close(repo->packidx_cache[i]);
 		if (err)
 			return err;
@@ -1171,6 +1177,11 @@ got_repo_search_packidx(struct got_packidx **packidx, 
 				    &repo->packidx_cache[0],
 				    i * sizeof(repo->packidx_cache[0]));
 				repo->packidx_cache[0] = *packidx;
+				if (repo->pinned_packidx >= 0 &&
+				    repo->pinned_packidx < i)
+					repo->pinned_packidx++;
+				else if (repo->pinned_packidx == i)
+					repo->pinned_packidx = 0;
 			}
 			return NULL;
 		}
@@ -1375,17 +1386,25 @@ got_repo_cache_pack(struct got_pack **packp, struct go
 
 	if (i == repo->pack_cache_size) {
 		struct got_pack tmp;
-		err = got_pack_close(&repo->packs[i - 1]);
+		do {
+			i--;
+		} while (i > 0 && repo->pinned_pack >= 0 &&
+		    i == repo->pinned_pack);
+		err = got_pack_close(&repo->packs[i]);
 		if (err)
 			return err;
-		if (ftruncate(repo->packs[i - 1].basefd, 0L) == -1)
+		if (ftruncate(repo->packs[i].basefd, 0L) == -1)
 			return got_error_from_errno("ftruncate");
-		if (ftruncate(repo->packs[i - 1].accumfd, 0L) == -1)
+		if (ftruncate(repo->packs[i].accumfd, 0L) == -1)
 			return got_error_from_errno("ftruncate");
-		memcpy(&tmp, &repo->packs[i - 1], sizeof(tmp)); 
-		memcpy(&repo->packs[i - 1], &repo->packs[0],
-		    sizeof(repo->packs[i - 1]));
+		memcpy(&tmp, &repo->packs[i], sizeof(tmp)); 
+		memcpy(&repo->packs[i], &repo->packs[0],
+		    sizeof(repo->packs[i]));
 		memcpy(&repo->packs[0], &tmp, sizeof(repo->packs[0]));
+		if (repo->pinned_pack == 0)
+			repo->pinned_pack = i;
+		else if (repo->pinned_pack == i)
+			repo->pinned_pack = 0;
 		i = 0;
 	}
 
@@ -1449,6 +1468,51 @@ got_repo_get_cached_pack(struct got_repository *repo, 
 }
 
 const struct got_error *
+got_repo_pin_pack(struct got_repository *repo, struct got_packidx *packidx,
+    struct got_pack *pack)
+{
+	size_t i;
+	int pinned_pack = -1, pinned_packidx = -1;
+
+	for (i = 0; i < repo->pack_cache_size; i++) {
+		if (repo->packidx_cache[i] &&
+		    strcmp(repo->packidx_cache[i]->path_packidx,
+		    packidx->path_packidx) == 0)
+			pinned_packidx = i;
+		if (repo->packs[i].path_packfile &&
+		    strcmp(repo->packs[i].path_packfile,
+		    pack->path_packfile) == 0)
+			pinned_pack = i;
+	}
+
+	if (pinned_packidx == -1 || pinned_pack == -1)
+		return got_error(GOT_ERR_PIN_PACK);
+
+	repo->pinned_pack = pinned_pack;
+	repo->pinned_packidx = pinned_packidx;
+	repo->pinned_pid = repo->packs[pinned_pack].privsep_child->pid;
+	return NULL;
+}
+
+struct got_pack *
+got_repo_get_pinned_pack(struct got_repository *repo)
+{
+	if (repo->pinned_pack >= 0 &&
+	    repo->pinned_pack < repo->pack_cache_size)
+		return &repo->packs[repo->pinned_pack];
+
+	return NULL;
+}
+
+void
+got_repo_unpin_pack(struct got_repository *repo)
+{
+	repo->pinned_packidx = -1;
+	repo->pinned_pack = -1;
+	repo->pinned_pid = 0;
+}
+
+const struct got_error *
 got_repo_init(const char *repo_path)
 {
 	const struct got_error *err = NULL;
blob - 71fcef93a4f835b8c00eda8b486c0c1a7aef8fe8
blob + 30401fee605c3b7937f25d1f3c8f216c945c9525
--- libexec/got-read-pack/got-read-pack.c
+++ libexec/got-read-pack/got-read-pack.c
@@ -45,6 +45,10 @@
 #include "got_lib_privsep.h"
 #include "got_lib_pack.h"
 
+#ifndef nitems
+#define nitems(_a)	(sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
 static volatile sig_atomic_t sigint_received;
 
 static void
@@ -1540,7 +1544,273 @@ done:
 	return err;
 }
 
+enum findtwixt_color {
+	COLOR_KEEP = 0,
+	COLOR_DROP,
+	COLOR_SKIP,
+	COLOR_MAX,
+};
+
 static const struct got_error *
+paint_commit(struct got_object_qid *qid, intptr_t color)
+{
+	if (color < 0 || color >= COLOR_MAX)
+		return got_error(GOT_ERR_RANGE);
+
+	qid->data = (void *)color;
+	return NULL;
+}
+
+static const struct got_error *
+queue_commit_id(struct got_object_id_queue *ids, struct got_object_id *id,
+    intptr_t color)
+{
+	const struct got_error *err;
+	struct got_object_qid *qid;
+
+	err = got_object_qid_alloc_partial(&qid);
+	if (err)
+		return err;
+
+	memcpy(&qid->id, id, sizeof(qid->id));
+	STAILQ_INSERT_TAIL(ids, qid, entry);
+	return paint_commit(qid, color);
+}
+
+static const struct got_error *
+paint_commits(struct got_object_id_queue *ids, int *nids,
+    struct got_object_idset *keep, struct got_object_idset *drop,
+    struct got_object_idset *skip, struct got_pack *pack,
+    struct got_packidx *packidx, struct imsgbuf *ibuf,
+    struct got_object_cache *objcache)
+{
+	const struct got_error *err = NULL;
+	struct got_commit_object *commit = NULL;
+	struct got_object_id_queue painted;
+	const struct got_object_id_queue *parents;
+	struct got_object_qid *qid = NULL;
+	int nqueued = *nids, nskip = 0, npainted = 0;
+
+	STAILQ_INIT(&painted);
+
+	while (!STAILQ_EMPTY(ids) && nskip != nqueued) {
+		int idx;
+		intptr_t color;
+
+		if (sigint_received) {
+			err = got_error(GOT_ERR_CANCELLED);
+			goto done;
+		}
+
+		qid = STAILQ_FIRST(ids);
+		idx = got_packidx_get_object_idx(packidx, &qid->id);
+		if (idx == -1) {
+			qid = NULL;
+			break;
+		}
+
+		STAILQ_REMOVE_HEAD(ids, entry);
+		nqueued--;
+		color = (intptr_t)qid->data;
+		if (color == COLOR_SKIP)
+			nskip--;
+
+		if (got_object_idset_contains(skip, &qid->id)) {
+			got_object_qid_free(qid);
+			qid = NULL;
+			continue;
+		}
+
+		switch (color) {
+		case COLOR_KEEP:
+			if (got_object_idset_contains(keep, &qid->id)) {
+				got_object_qid_free(qid);
+				qid = NULL;
+				continue;
+			}
+			if (got_object_idset_contains(drop, &qid->id)) {
+				err = paint_commit(qid, COLOR_SKIP);
+				if (err)
+					goto done;
+			}
+			err = got_object_idset_add(keep, &qid->id, NULL);
+			if (err)
+				goto done;
+			break;
+		case COLOR_DROP:
+			if (got_object_idset_contains(drop, &qid->id)) {
+				got_object_qid_free(qid);
+				qid = NULL;
+				continue;
+			}
+			if (got_object_idset_contains(keep, &qid->id)) {
+				err = paint_commit(qid, COLOR_SKIP);
+				if (err)
+					goto done;
+			}
+			err = got_object_idset_add(drop, &qid->id, NULL);
+			if (err)
+				goto done;
+			break;
+		case COLOR_SKIP:
+			if (!got_object_idset_contains(skip, &qid->id)) {
+				err = got_object_idset_add(skip, &qid->id,
+				    NULL);
+				if (err)
+					goto done;
+			}
+			break;
+		default:
+			/* should not happen */
+			err = got_error_fmt(GOT_ERR_NOT_IMPL,
+			    "%s invalid commit color %d", __func__, color);
+			goto done;
+		}
+
+		err = open_commit(&commit, pack, packidx, idx, &qid->id,
+		    objcache);
+		if (err)
+			goto done;
+
+		parents = got_object_commit_get_parent_ids(commit);
+		if (parents) {
+			struct got_object_qid *pid;
+			color = (intptr_t)qid->data;
+			STAILQ_FOREACH(pid, parents, entry) {
+				err = queue_commit_id(ids, &pid->id, color);
+				if (err)
+					goto done;
+				nqueued++;
+				if (color == COLOR_SKIP)
+					nskip++;
+			}
+		}
+
+		got_object_commit_close(commit);
+		commit = NULL;
+
+		STAILQ_INSERT_TAIL(&painted, qid, entry);
+		qid = NULL;
+		npainted++;
+
+		err = got_privsep_send_painted_commits(ibuf, &painted,
+		    &npainted, 1, 0);
+		if (err)
+			goto done;
+	}
+
+	err = got_privsep_send_painted_commits(ibuf, &painted, &npainted, 1, 1);
+	if (err)
+		goto done;
+
+	*nids = nqueued;
+done:
+	if (commit)
+		got_object_commit_close(commit);
+	got_object_qid_free(qid);
+	return err;
+}
+
+static void
+commit_painting_free(struct got_object_idset **keep,
+    struct got_object_idset **drop,
+    struct got_object_idset **skip)
+{
+	if (*keep) {
+		got_object_idset_free(*keep);
+		*keep = NULL;
+	}
+	if (*drop) {
+		got_object_idset_free(*drop);
+		*drop = NULL;
+	}
+	if (*skip) {
+		got_object_idset_free(*skip); 
+		*skip = NULL;
+	}
+}
+
+static const struct got_error *
+commit_painting_init(struct imsgbuf *ibuf, struct got_object_idset **keep,
+    struct got_object_idset **drop, struct got_object_idset **skip)
+{
+	const struct got_error *err = NULL;
+
+	*keep = got_object_idset_alloc();
+	if (*keep == NULL) {
+		err = got_error_from_errno("got_object_idset_alloc");
+		goto done;
+	}
+	*drop = got_object_idset_alloc();
+	if (*drop == NULL) {
+		err = got_error_from_errno("got_object_idset_alloc");
+		goto done;
+	}
+	*skip = got_object_idset_alloc();
+	if (*skip == NULL) {
+		err = got_error_from_errno("got_object_idset_alloc");
+		goto done;
+	}
+
+	err = recv_object_ids(*keep, ibuf);
+	if (err)
+		goto done;
+	err = recv_object_ids(*drop, ibuf);
+	if (err)
+		goto done;
+	err = recv_object_ids(*skip, ibuf);
+	if (err)
+		goto done;
+
+done:
+	if (err)
+		commit_painting_free(keep, drop, skip);
+
+	return err;
+}
+
+static const struct got_error *
+commit_painting_request(struct imsg *imsg, struct imsgbuf *ibuf,
+    struct got_pack *pack, struct got_packidx *packidx,
+    struct got_object_cache *objcache, struct got_object_idset *keep,
+    struct got_object_idset *drop, struct got_object_idset *skip)
+{
+	const struct got_error *err = NULL;
+	struct got_imsg_commit_painting_request ireq;
+	struct got_object_id id;
+	size_t datalen;
+	struct got_object_id_queue ids;
+	int nids = 0;
+
+	STAILQ_INIT(&ids);
+
+	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+	if (datalen != sizeof(ireq))
+		return got_error(GOT_ERR_PRIVSEP_LEN);
+	memcpy(&ireq, imsg->data, sizeof(ireq));
+	memcpy(id.sha1, ireq.id, SHA1_DIGEST_LENGTH);
+
+	err = queue_commit_id(&ids, &id, ireq.color);
+	if (err)
+		return err;
+	nids = 1;
+
+	err = paint_commits(&ids, &nids, keep, drop, skip,
+	    pack, packidx, ibuf, objcache);
+	if (err)
+		goto done;
+
+	err = got_privsep_send_painted_commits(ibuf, &ids, &nids, 0, 1);
+	if (err)
+		goto done;
+
+	err = got_privsep_send_painting_commits_done(ibuf);
+done:
+	got_object_id_queue_free(&ids);
+	return err;
+}
+
+static const struct got_error *
 receive_pack(struct got_pack **packp, struct imsgbuf *ibuf)
 {
 	const struct got_error *err = NULL;
@@ -1625,6 +1895,7 @@ main(int argc, char *argv[])
 	struct got_pack *pack = NULL;
 	struct got_object_cache objcache;
 	FILE *basefile = NULL, *accumfile = NULL, *delta_outfile = NULL;
+	struct got_object_idset *keep = NULL, *drop = NULL, *skip = NULL;
 
 	//static int attached;
 	//while (!attached) sleep(1);
@@ -1754,6 +2025,21 @@ main(int argc, char *argv[])
 			err = enumeration_request(&imsg, &ibuf, pack,
 			    packidx, &objcache);
 			break;
+		case GOT_IMSG_COMMIT_PAINTING_INIT:
+			commit_painting_free(&keep, &drop, &skip);
+			err = commit_painting_init(&ibuf, &keep, &drop, &skip);
+			break;
+		case GOT_IMSG_COMMIT_PAINTING_REQUEST:
+			if (keep == NULL || drop == NULL || skip == NULL) {
+				err = got_error(GOT_ERR_PRIVSEP_MSG);
+				break;
+			}
+			err = commit_painting_request(&imsg, &ibuf, pack,
+			    packidx, &objcache, keep, drop, skip);
+			break;
+		case GOT_IMSG_COMMIT_PAINTING_DONE:
+			commit_painting_free(&keep, &drop, &skip);
+			break;
 		default:
 			err = got_error(GOT_ERR_PRIVSEP_MSG);
 			break;
@@ -1766,6 +2052,7 @@ main(int argc, char *argv[])
 			break;
 	}
 
+	commit_painting_free(&keep, &drop, &skip);
 	if (packidx)
 		got_packidx_close(packidx);
 	if (pack)