From: Stefan Sperling Subject: Re: fix pack exclusion via an ancestor commit To: gameoftrees@openbsd.org Date: Mon, 27 Jan 2025 13:56:59 +0100 On Mon, Jan 27, 2025 at 10:42:50AM +0100, Stefan Sperling wrote: > On Mon, Jan 27, 2025 at 10:33:32AM +0100, Stefan Sperling wrote: > > Extend test coverage to cover both code paths in gotd > > I just realized this part (the addition of the > modify_protected_branch_with_deep_history test case) is bogus. > > Because with this packing bug fixed, there will always be an empty > pack file sent to gotd, regardless of the number of parent commits > used by this test. > > So I will drop the regress/gotd changes before commit, and write a > better test case later (which would try to replace the tip commit, > rather than simply trying to remove the tip). Here is a patch on top to ensure that got-read-pack's internal commit coloring loop gets the same fix. This is a bit involved because it turns out that we had a state-mismatch between the main process and got-read-pack, so they would run slightly different traversals. This is fixed here as well. OK? rework got-read-pack's commit coloring loop Port the parent commit coloring fix to got-read-pack, and ensure that it starts off with the same state as the main process. got-read-pack did not have access to the main process's ids array, and was thus working with a different initial state. With these changes the same commit traversal happens regardless of whether coloring is "offloaded" to got-read-pack or not (verified manually by placing debug printfs). M lib/got_lib_privsep.h | 1+ 9- M lib/pack_create_privsep.c | 141+ 63- M lib/privsep.c | 2+ 10- M libexec/got-read-pack/got-read-pack.c | 144+ 15- M regress/cmdline/pack.sh | 70+ 0- 5 files changed, 358 insertions(+), 97 deletions(-) commit - 9d5dd35582418ff1c51c6ccd42d5f4a5af512486 commit + cd031b0200bec58109add5affdf6090289094b8b blob - de309e2be9e023e1af81a25efd0fb50d70776905 blob + a539f5cf968d5bec802bad6082fe43acfc4dc609 --- lib/got_lib_privsep.h +++ lib/got_lib_privsep.h @@ -346,13 +346,6 @@ struct got_imsg_reused_deltas { / sizeof(struct got_imsg_reused_delta)) }; -/* Structure for GOT_IMSG_COMMIT_PAINTING_REQUEST. */ -struct got_imsg_commit_painting_request { - struct got_object_id id; - int idx; - int color; -} __attribute__((__packed__)); - /* Structure for GOT_IMSG_PAINTED_COMMITS. */ struct got_imsg_painted_commit { struct got_object_id id; @@ -833,8 +826,7 @@ const struct got_error *got_privsep_recv_reused_deltas 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); +const struct got_error *got_privsep_send_painting_request(struct imsgbuf *); 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 *, blob - 05666f0e8981ce94a832dd0fd1ae61c5a4fdcb24 blob + b8de462a868ea019db806d3f84da929a32cc590b --- lib/pack_create_privsep.c +++ lib/pack_create_privsep.c @@ -96,6 +96,39 @@ send_idset(struct imsgbuf *ibuf, struct got_object_ids } static const struct got_error * +send_filtered_id_queue(struct imsgbuf *ibuf, struct got_object_id_queue *ids, + uintptr_t color) +{ + const struct got_error *err = NULL; + struct got_object_qid *qid; + struct got_object_id *filtered_ids[GOT_IMSG_OBJ_ID_LIST_MAX_NIDS]; + int nids = 0; + + STAILQ_FOREACH(qid, ids, entry) { + if (color != (intptr_t)qid->data) + continue; + + filtered_ids[nids++] = &qid->id; + if (nids >= GOT_IMSG_OBJ_ID_LIST_MAX_NIDS) { + err = got_privsep_send_object_idlist(ibuf, + filtered_ids, nids); + if (err) + return err; + nids = 0; + } + + } + + if (nids > 0) { + err = got_privsep_send_object_idlist(ibuf, filtered_ids, 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) { @@ -211,6 +244,7 @@ struct recv_painted_commit_arg { struct got_ratelimit *rl; got_cancel_cb cancel_cb; void *cancel_arg; + struct got_object_qid **qid0; }; static const struct got_error * @@ -255,6 +289,8 @@ recv_painted_commit(void *arg, struct got_object_id *i continue; STAILQ_REMOVE(a->ids, qid, got_object_qid, entry); color = (intptr_t)qid->data; + if (*(a->qid0) == qid) + *(a->qid0) = NULL; got_object_qid_free(qid); (*a->nqueued)--; if (color == COLOR_SKIP) @@ -267,9 +303,9 @@ recv_painted_commit(void *arg, struct got_object_id *i } 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, +paint_packed_commits(struct got_object_qid **qid0, + struct got_pack *pack, 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, @@ -282,11 +318,25 @@ paint_packed_commits(struct got_pack *pack, struct got STAILQ_INIT(&next_ids); - err = got_privsep_send_painting_request(pack->privsep_child->ibuf, - idx, id, color); + err = got_privsep_send_painting_request(pack->privsep_child->ibuf); if (err) return err; + err = send_filtered_id_queue(pack->privsep_child->ibuf, ids, + COLOR_KEEP); + if (err) + return err; + + err = send_filtered_id_queue(pack->privsep_child->ibuf, ids, + COLOR_DROP); + if (err) + return err; + + err = send_filtered_id_queue(pack->privsep_child->ibuf, ids, + COLOR_SKIP); + if (err) + return err; + arg.ncolored = ncolored; arg.nqueued = nqueued; arg.nskip = nskip; @@ -299,6 +349,7 @@ paint_packed_commits(struct got_pack *pack, struct got arg.rl = rl; arg.cancel_cb = cancel_cb; arg.cancel_arg = cancel_arg; + arg.qid0 = qid0; err = got_privsep_recv_painted_commits(&next_ids, recv_painted_commit, &arg, pack->privsep_child->ibuf); if (err) @@ -338,6 +389,33 @@ paint_packed_commits(struct got_pack *pack, struct got return err; } +static const struct got_error * +pin_pack_for_commit_painting(struct got_pack **pack, + struct got_packidx **packidx, struct got_object_id_queue *ids, int nqueued, + struct got_repository *repo) +{ + const struct got_error *err; + + err = got_pack_find_pack_for_commit_painting(packidx, ids, nqueued, + repo); + if (err || *packidx == NULL) { + *pack = NULL; + return err; + } + + err = got_pack_cache_pack_for_packidx(pack, *packidx, repo); + if (err) + return err; + + if ((*pack)->privsep_child == NULL) { + err = got_pack_start_privsep_child(*pack, *packidx); + if (err) + return err; + } + + return got_repo_pin_pack(repo, *packidx, *pack); +} + const struct got_error * got_pack_paint_commits(int *ncolored, struct got_object_id_queue *ids, int nids, struct got_object_idset *keep, struct got_object_idset *drop, @@ -354,6 +432,10 @@ got_pack_paint_commits(int *ncolored, struct got_objec int nqueued = nids, nskip = 0; int idx; + err = pin_pack_for_commit_painting(&pack, &packidx, ids, nqueued, repo); + if (err) + return err; + while (!STAILQ_EMPTY(ids) && nskip != nqueued) { intptr_t color; @@ -363,10 +445,63 @@ got_pack_paint_commits(int *ncolored, struct got_objec break; } + /* Pinned pack may have moved to different cache slot. */ + pack = got_repo_get_pinned_pack(repo); + if (pack == NULL) { + err = pin_pack_for_commit_painting(&pack, &packidx, + ids, nqueued, repo); + if (err) + break; + } + qid = STAILQ_FIRST(ids); + color = (intptr_t)qid->data; + + /* Use the fast path if we have a suitable pack file. */ + if (packidx && pack) { + idx = got_packidx_get_object_idx(packidx, &qid->id); + if (idx != -1) { + 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 = paint_packed_commits(&qid, pack, + color, ncolored, &nqueued, &nskip, + ids, keep, drop, skip, repo, + progress_cb, progress_arg, rl, + cancel_cb, cancel_arg); + if (err) + goto done; + if (qid) { + STAILQ_REMOVE(ids, qid, + got_object_qid, entry); + nqueued--; + got_object_qid_free(qid); + qid = NULL; + } + continue; + } + } + STAILQ_REMOVE_HEAD(ids, entry); nqueued--; - color = (intptr_t)qid->data; + + got_repo_unpin_pack(repo); + pack = NULL; + packidx = NULL; + if (color == COLOR_SKIP) nskip--; @@ -388,25 +523,6 @@ got_pack_paint_commits(int *ncolored, struct got_objec 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(drop, &qid->id)) { @@ -486,44 +602,6 @@ got_pack_paint_commits(int *ncolored, struct got_objec } } - if (pack == NULL && (commit->flags & GOT_COMMIT_FLAG_PACKED)) { - if (packidx == NULL) { - err = got_pack_find_pack_for_commit_painting( - &packidx, ids, nqueued, repo); - if (err) - goto done; - } - if (packidx != NULL) { - err = got_pack_cache_pack_for_packidx(&pack, - packidx, repo); - if (err) - goto done; - if (pack->privsep_child == NULL) { - err = got_pack_start_privsep_child( - pack, packidx); - 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; blob - 2c49d4419a4c395afafe7cca1614b53619d50ed5 blob + 9c7646c870a7ddbf9a22618d09e3549ef5d9cdb4 --- lib/privsep.c +++ lib/privsep.c @@ -3387,18 +3387,10 @@ got_privsep_init_commit_painting(struct imsgbuf *ibuf) } const struct got_error * -got_privsep_send_painting_request(struct imsgbuf *ibuf, int idx, - struct got_object_id *id, intptr_t color) +got_privsep_send_painting_request(struct imsgbuf *ibuf) { - struct got_imsg_commit_painting_request ireq; - - memset(&ireq, 0, sizeof(ireq)); - memcpy(&ireq.id, id, 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) + NULL, 0) == -1) return got_error_from_errno("imsg_compose " "COMMIT_PAINTING_REQUEST"); blob - 8049481513dd138d1818a8eb293862548487a4dd blob + e63820b191b97c51147aab80a31cbca5d23faca3 --- libexec/got-read-pack/got-read-pack.c +++ libexec/got-read-pack/got-read-pack.c @@ -1054,8 +1054,8 @@ recv_object_ids(struct got_object_idset *idset, struct } static const struct got_error * -recv_object_id_queue(struct got_object_id_queue *queue, - struct got_object_idset *queued_ids, struct imsgbuf *ibuf) +recv_object_id_queue(size_t *nids_total, struct got_object_id_queue *queue, + void *data, struct got_object_idset *queued_ids, struct imsgbuf *ibuf) { const struct got_error *err = NULL; int done = 0; @@ -1063,19 +1063,26 @@ recv_object_id_queue(struct got_object_id_queue *queue struct got_object_id *ids; size_t nids, i; + *nids_total = 0; for (;;) { err = got_privsep_recv_object_idlist(&done, &ids, &nids, ibuf); if (err || done) break; + *nids_total += nids; for (i = 0; i < nids; i++) { err = got_object_qid_alloc_partial(&qid); if (err) goto done; memcpy(&qid->id, &ids[i], sizeof(qid->id)); + if (data) + qid->data = data; STAILQ_INSERT_TAIL(queue, qid, entry); - err = got_object_idset_add(queued_ids, &qid->id, NULL); - if (err) - goto done; + if (queued_ids) { + err = got_object_idset_add(queued_ids, + &qid->id, NULL); + if (err) + goto done; + } } free(ids); ids = NULL; @@ -1456,7 +1463,7 @@ enumeration_request(struct imsg *imsg, struct imsgbuf struct got_object_idset *idset, *queued_ids = NULL; int i, idx, have_all_entries = 1; struct enumerated_tree *trees = NULL; - size_t ntrees = 0, nalloc = 16; + size_t ntrees = 0, nalloc = 16, nids = 0; STAILQ_INIT(&commit_ids); @@ -1476,7 +1483,7 @@ enumeration_request(struct imsg *imsg, struct imsgbuf goto done; } - err = recv_object_id_queue(&commit_ids, queued_ids, ibuf); + err = recv_object_id_queue(&nids, &commit_ids, NULL, queued_ids, ibuf); if (err) goto done; @@ -1674,6 +1681,87 @@ queue_commit_id(struct got_object_id_queue *ids, struc } static const struct got_error * +repaint_parent_commits(struct got_object_id *commit_id, int color, + struct got_object_idset *set, struct got_object_idset *skip, + struct got_pack *pack, struct got_packidx *packidx, + struct got_object_cache *objcache) +{ + const struct got_error *err; + struct got_object_id_queue ids; + struct got_object_qid *qid = NULL; + struct got_commit_object *commit; + const struct got_object_id_queue *parents; + int idx; + + STAILQ_INIT(&ids); + + idx = got_packidx_get_object_idx(packidx, commit_id); + if (idx == -1) + return got_error(GOT_ERR_NO_OBJ); + + err = open_commit(&commit, pack, packidx, idx, commit_id, objcache); + if (err) + return err; + + while (commit) { + parents = got_object_commit_get_parent_ids(commit); + if (parents) { + struct got_object_qid *pid; + STAILQ_FOREACH(pid, parents, entry) { + /* + * No need to traverse parents which are + * already in the desired set or are + * marked for skipping already. + */ + if (got_object_idset_contains(set, &pid->id)) + continue; + if (skip != set && + got_object_idset_contains(skip, &pid->id)) + continue; + + err = queue_commit_id(&ids, &pid->id, color); + if (err) + break; + } + } + got_object_commit_close(commit); + commit = NULL; + + qid = STAILQ_FIRST(&ids); + if (qid) { + STAILQ_REMOVE_HEAD(&ids, entry); + if (!got_object_idset_contains(set, &qid->id)) { + err = got_object_idset_add(set, &qid->id, + NULL); + if (err) + break; + } + + idx = got_packidx_get_object_idx(packidx, &qid->id); + if (idx == -1) { + err = got_error(GOT_ERR_NO_OBJ); + break; + } + + err = open_commit(&commit, pack, packidx, idx, + &qid->id, objcache); + if (err) + break; + + got_object_qid_free(qid); + qid = NULL; + } + } + + if (commit) + got_object_commit_close(commit); + if (qid) + got_object_qid_free(qid); + got_object_id_queue_free(&ids); + + return err; +} +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, @@ -1728,6 +1816,15 @@ paint_commits(struct got_object_id_queue *ids, int *ni err = paint_commit(qid, COLOR_SKIP); if (err) goto done; + err = got_object_idset_add(skip, &qid->id, + NULL); + if (err) + goto done; + err = repaint_parent_commits(&qid->id, + COLOR_SKIP, skip, skip, pack, packidx, + objcache); + if (err) + goto done; } err = got_object_idset_add(keep, &qid->id, NULL); if (err) @@ -1743,6 +1840,15 @@ paint_commits(struct got_object_id_queue *ids, int *ni err = paint_commit(qid, COLOR_SKIP); if (err) goto done; + err = got_object_idset_add(skip, &qid->id, + NULL); + if (err) + goto done; + err = repaint_parent_commits(&qid->id, + COLOR_SKIP, skip, skip, pack, packidx, + objcache); + if (err) + goto done; } err = got_object_idset_add(drop, &qid->id, NULL); if (err) @@ -1873,25 +1979,48 @@ commit_painting_request(struct imsg *imsg, struct imsg 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; + size_t nkeep = 0, ndrop = 0, nskip = 0; int nids = 0; + uintptr_t color; STAILQ_INIT(&ids); datalen = imsg->hdr.len - IMSG_HEADER_SIZE; - if (datalen != sizeof(ireq)) + if (datalen != 0) return got_error(GOT_ERR_PRIVSEP_LEN); - memcpy(&ireq, imsg->data, sizeof(ireq)); - memcpy(&id, &ireq.id, sizeof(id)); - err = queue_commit_id(&ids, &id, ireq.color); + color = COLOR_KEEP; + err = recv_object_id_queue(&nkeep, &ids, (void *)color, NULL, ibuf); if (err) - return err; - nids = 1; + goto done; + if (nids + nkeep > INT_MAX) { + err = got_error(GOT_ERR_PRIVSEP_LEN); + goto done; + } + nids += nkeep; + color = COLOR_DROP; + err = recv_object_id_queue(&ndrop, &ids, (void *)color, NULL, ibuf); + if (err) + goto done; + if (nids + ndrop > INT_MAX) { + err = got_error(GOT_ERR_PRIVSEP_LEN); + goto done; + } + nids += ndrop; + + color = COLOR_SKIP; + err = recv_object_id_queue(&nskip, &ids, (void *)color, NULL, ibuf); + if (err) + goto done; + if (nids + nskip > INT_MAX) { + err = got_error(GOT_ERR_PRIVSEP_LEN); + goto done; + } + nids += nskip; + err = paint_commits(&ids, &nids, keep, drop, skip, pack, packidx, ibuf, objcache); if (err) blob - 5038d17342275e2c833422660801b878be6e5f4e blob + c9209d96f9e169e5ea494d92a6af8092d68cdca0 --- regress/cmdline/pack.sh +++ regress/cmdline/pack.sh @@ -772,6 +772,75 @@ test_pack_exclude_via_ancestor_commit() { test_done "$testroot" "$ret" } +test_pack_exclude_via_ancestor_commit_packed() { + local testroot=`test_init pack_exclude` + local commit0=`git_show_head $testroot/repo` + + # no pack files should exist yet + ls $testroot/repo/.git/objects/pack/ > $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + test_done "$testroot" "$ret" + return 1 + fi + echo -n > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + got checkout $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done "$testroot" "$ret" + return 1 + fi + + for i in 1 2 3 4 5 6 7; do + echo a new line >> $testroot/wt/alpha + (cd $testroot/wt && got commit -m "edit alpha" >/dev/null) + done + local parent_commit=`git_show_head $testroot/repo` + + echo a new line >> $testroot/wt/alpha + (cd $testroot/wt && got commit -m "edit alpha" >/dev/null) + + got ref -r $testroot/repo -c $parent_commit refs/heads/pleasepackthis + + # pack the repository and remove loose objects + gotadmin cleanup -aq -r $testroot/repo > /dev/null 2> $testroot/stderr + ret=$? + if [ $ret -ne 0 ]; then + echo "gotadmin cleanup failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + # Packing the 'pleasepackthis' branch while exluding commits + # reachable via 'master' should result in an empty pack file. + gotadmin pack -a -r $testroot/repo -x master pleasepackthis \ + > $testroot/stdout 2> $testroot/stderr + ret=$? + if [ $ret -eq 0 ]; then + echo "gotadmin pack succeeded unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + echo "gotadmin: not enough objects to pack" > $testroot/stderr.expected + cmp -s $testroot/stderr.expected $testroot/stderr + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stderr.expected $testroot/stderr + test_done "$testroot" "$ret" + return 1 + fi + + test_done "$testroot" "$ret" +} test_parseargs "$@" run_test test_pack_all_loose_objects run_test test_pack_exclude @@ -783,3 +852,4 @@ run_test test_pack_all_objects run_test test_pack_bad_ref run_test test_pack_tagged_tag run_test test_pack_exclude_via_ancestor_commit +run_test test_pack_exclude_via_ancestor_commit_packed