From: Stefan Sperling Subject: make rewinding of branches work with gotd To: gameoftrees@openbsd.org Date: Mon, 10 Apr 2023 18:23:39 +0200 Fix a spurious empty packfile error from gotd when rewinding a branch. ok? diff 7755e2f80723b22eb4fcc203ffd75608db7055c7 8146f0364982425931d98b5cf31a12252b3dff39 commit - 7755e2f80723b22eb4fcc203ffd75608db7055c7 commit + 8146f0364982425931d98b5cf31a12252b3dff39 blob - 317d11a2dde1181dd8dca488ba5ed7f353895c70 blob + eefdc7de1087c2a2956a9fdbbd25ad81719a783c --- gotd/repo_write.c +++ gotd/repo_write.c @@ -97,6 +97,7 @@ static struct repo_write_client { int nref_updates; int nref_del; int nref_new; + int nref_move; } repo_write_client; static volatile sig_atomic_t sigint_received; @@ -265,6 +266,7 @@ list_refs(struct imsg *imsg) client->nref_updates = 0; client->nref_del = 0; client->nref_new = 0; + client->nref_move = 0; imsg_init(&ibuf, client_fd); @@ -971,6 +973,24 @@ recv_packdata(off_t *outsize, uint32_t *nobj, uint8_t } static const struct got_error * +ensure_all_objects_exist_locally(struct gotd_ref_updates *ref_updates) +{ + const struct got_error *err = NULL; + struct gotd_ref_update *ref_update; + struct got_object *obj; + + STAILQ_FOREACH(ref_update, ref_updates, entry) { + err = got_object_open(&obj, repo_write.repo, + &ref_update->new_id); + if (err) + return err; + got_object_close(obj); + } + + return NULL; +} + +static const struct got_error * recv_packdata(off_t *outsize, uint32_t *nobj, uint8_t *sha1, int infd, int outfd) { @@ -1019,8 +1039,20 @@ recv_packdata(off_t *outsize, uint32_t *nobj, uint8_t client->nref_updates == client->nref_new) return NULL; - return got_error_msg(GOT_ERR_BAD_PACKFILE, - "bad packfile with zero objects"); + /* + * Clients which only move existing refs will send us an empty + * pack file. All referenced objects must exist locally. + */ + err = ensure_all_objects_exist_locally(&client->ref_updates); + if (err) { + if (err->code != GOT_ERR_NO_OBJ) + return err; + return got_error_msg(GOT_ERR_BAD_PACKFILE, + "bad packfile with zero objects"); + } + + client->nref_move = client->nref_updates; + return NULL; } log_debug("expecting %d objects", *nobj); @@ -1271,6 +1303,16 @@ recv_packfile(int *have_packfile, struct imsg *imsg) client->nref_updates == client->nref_del) goto done; + /* + * Clients which only move existing refs will send us an empty + * pack file. All referenced objects must exist locally. + */ + if (nobj == 0 && + pack_filesize == sizeof(struct got_packfile_hdr) && + client->nref_move > 0 && + client->nref_updates == client->nref_move) + goto done; + pack->filesize = pack_filesize; *have_packfile = 1; blob - d75e82812ee4b8e5c04144e1f6f981703b541699 blob + daff924b47ca360160665215790f700bf0f8bfda --- regress/gotd/repo_write.sh +++ regress/gotd/repo_write.sh @@ -458,8 +458,95 @@ test_parseargs "$@" test_done "$testroot" 0 } +test_rewind_branch() { + local testroot=`test_init rewind_branch 1` + + got clone -a -q ${GOTD_TEST_REPO_URL} $testroot/repo-clone + ret=$? + if [ $ret -ne 0 ]; then + echo "got clone failed unexpectedly" >&2 + test_done "$testroot" 1 + return 1 + fi + + got checkout -q $testroot/repo-clone $testroot/wt >/dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "got checkout failed unexpectedly" >&2 + test_done "$testroot" 1 + return 1 + fi + + (cd $testroot/wt && got branch foo) >/dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "got branch failed unexpectedly" >&2 + test_done "$testroot" 1 + return 1 + fi + + echo modified alpha > $testroot/wt/alpha + (cd $testroot/wt && got commit -m 'edit alpha') >/dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "got commit failed unexpectedly" >&2 + test_done "$testroot" 1 + return 1 + fi + + if ! got send -q -r $testroot/repo-clone -b foo; then + echo "got send failed unexpectedly" >&2 + test_done "$testroot" 1 + return 1 + fi + + local foo_id=`git_show_branch_head "$testroot/repo-clone" foo` + local main_id=`git_show_branch_head "$testroot/repo-clone" main` + local tag_id=`got ref -r "$testroot/repo-clone" -l refs/tags/1.0 | \ + awk '{print $2}'` + + (cd $testroot/wt && got update -c $main_id) >/dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "got update failed unexpectedly" >&2 + test_done "$testroot" 1 + return 1 + fi + + (cd $testroot/wt && got histedit -d) >/dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "got histedit failed unexpectedly" >&2 + test_done "$testroot" 1 + return 1 + fi + + if ! got send -q -r $testroot/repo-clone -f -b foo; then + echo "got send failed unexpectedly" >&2 + test_done "$testroot" 1 + return 1 + fi + + got fetch -q -r $testroot/repo-clone -l >$testroot/refs + cat <$testroot/refs.expected +HEAD: refs/heads/main +HEAD: $main_id +refs/heads/foo: $main_id +refs/heads/main: $main_id +refs/tags/1.0: $tag_id +EOF + if ! cmp -s $testroot/refs.expected $testroot/refs; then + diff -u $testroot/refs.expected $testroot/refs + test_done "$testroot" 1 + return 1 + fi + + test_done "$testroot" 0 +} + test_parseargs "$@" run_test test_send_basic run_test test_fetch_more_history run_test test_send_new_empty_branch run_test test_delete_branch +run_test test_rewind_branch