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

From:
Mark Jamsek <mark@jamsek.com>
Subject:
Re: fix pack exclusion via an ancestor commit
To:
Stefan Sperling <stsp@stsp.name>
Cc:
gameoftrees@openbsd.org
Date:
Tue, 28 Jan 2025 00:17:04 +1100

Download raw body.

Thread
Stefan Sperling <stsp@stsp.name> wrote:
> When a commit is first discovered as a commit which should be included
> in the pack, but is later found to be a parent of a commit which should
> be excluded from the pack, gotadmin pack correctly excluded the commit
> itself but failed to exclude this commit's parents.
> 
> This bug is the reason why our test suite did not notice that gotd
> was not protecting references when clients did not send a pack file.
> In our gotd test case, the parents are in the 'keep' set already and
> were never added to the 'skip' set. A useless pack was built which
> included those parents and nothing else.
> 
> Extend test coverage to cover both code paths in gotd, and add a test
> which triggers the bug via gotadmin pack.
> 
> ok?

ok (I didn't review the regress/gotd component of the diff
based on your follow-up mail)

> M  lib/got_lib_pack_create.h             |    3+  0-
> M  lib/pack_create.c                     |   71+  0-
> M  lib/pack_create_io.c                  |   16+  0-
> M  lib/pack_create_privsep.c             |   16+  0-
> M  regress/cmdline/pack.sh               |   62+  0-
> M  regress/gotd/repo_write_protected.sh  |  124+  0-
> 
> 6 files changed, 292 insertions(+), 0 deletions(-)
> 
> commit - 6ba505793ee92f3acf299c8d06b1409066094261
> commit + 0294cf74d9140a748615f1a7f127291acde3e139
> blob - 265d2bf372d90b0b2c3c124b8d9885056f6f1250
> blob + ca45026cc97b8e4d57488732f016db3ca87aaf80
> --- lib/got_lib_pack_create.h
> +++ lib/got_lib_pack_create.h
> @@ -55,6 +55,9 @@ enum got_pack_findtwixt_color {
>  
>  const struct got_error *got_pack_paint_commit(struct got_object_qid *qid,
>      intptr_t color);
> +const struct got_error *got_pack_repaint_parent_commits(
> +    struct got_object_id *commit_id, int color, struct got_object_idset *set,
> +    struct got_object_idset *skip, struct got_repository *repo);
>  const struct got_error *got_pack_queue_commit_id(
>      struct got_object_id_queue *ids, struct got_object_id *id, intptr_t color,
>      struct got_repository *repo);
> blob - 7cbe1bb9a78f7c06fed0b49bd3658873b9882862
> blob + 3bd5a64f277590883cfa6d747219b4c5509e10fd
> --- lib/pack_create.c
> +++ lib/pack_create.c
> @@ -1049,6 +1049,77 @@ got_pack_paint_commit(struct got_object_qid *qid, intp
>  }
>  
>  const struct got_error *
> +got_pack_repaint_parent_commits(struct got_object_id *commit_id, int color,
> +    struct got_object_idset *set, struct got_object_idset *skip,
> +    struct got_repository *repo)
> +{
> +	const struct got_error *err;
> +	struct got_object_id_queue ids;
> +	struct got_object_qid *qid;
> +	struct got_commit_object *commit;
> +	const struct got_object_id_queue *parents;
> +
> +	STAILQ_INIT(&ids);
> +
> +	err = got_object_open_as_commit(&commit, repo, commit_id);
> +	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 = got_pack_queue_commit_id(&ids, &pid->id,
> +				    color, repo);
> +				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;
> +			}
> +
> +			err = got_object_open_as_commit(&commit, repo,
> +			    &qid->id);
> +			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;
> +}
> +
> +const struct got_error *
>  got_pack_queue_commit_id(struct got_object_id_queue *ids,
>      struct got_object_id *id, intptr_t color, struct got_repository *repo)
>  {
> blob - 0d826389e2105b3dffcab6046a757b0fdb83a8ad
> blob + b8011c9c9311767fc79faf56bc73ca31e59ebf37
> --- lib/pack_create_io.c
> +++ lib/pack_create_io.c
> @@ -278,6 +278,14 @@ got_pack_paint_commits(int *ncolored, struct got_objec
>  				err = got_pack_paint_commit(qid, COLOR_SKIP);
>  				if (err)
>  					goto done;
> +				err = got_object_idset_add(skip, &qid->id,
> +				    NULL);
> +				if (err)
> +					goto done;
> +				err = got_pack_repaint_parent_commits(&qid->id,
> +				    COLOR_SKIP, skip, skip, repo);
> +				if (err)
> +					goto done;
>  			} else
>  				(*ncolored)++;
>  			err = got_object_idset_add(keep, &qid->id, NULL);
> @@ -289,6 +297,14 @@ got_pack_paint_commits(int *ncolored, struct got_objec
>  				err = got_pack_paint_commit(qid, COLOR_SKIP);
>  				if (err)
>  					goto done;
> +				err = got_object_idset_add(skip, &qid->id,
> +				    NULL);
> +				if (err)
> +					goto done;
> +				err = got_pack_repaint_parent_commits(&qid->id,
> +				    COLOR_SKIP, skip, skip, repo);
> +				if (err)
> +					goto done;
>  			} else
>  				(*ncolored)++;
>  			err = got_object_idset_add(drop, &qid->id, NULL);
> blob - ea20b9c4b08199a59e5e822d3319990fd6f175fc
> blob + 05666f0e8981ce94a832dd0fd1ae61c5a4fdcb24
> --- lib/pack_create_privsep.c
> +++ lib/pack_create_privsep.c
> @@ -413,6 +413,14 @@ got_pack_paint_commits(int *ncolored, struct got_objec
>  				err = got_pack_paint_commit(qid, COLOR_SKIP);
>  				if (err)
>  					goto done;
> +				err = got_object_idset_add(skip, &qid->id,
> +				    NULL);
> +				if (err)
> +					goto done;
> +				err = got_pack_repaint_parent_commits(&qid->id,
> +				    COLOR_SKIP, skip, skip, repo);
> +				if (err)
> +					goto done;
>  			} else
>  				(*ncolored)++;
>  			err = got_object_idset_add(keep, &qid->id, NULL);
> @@ -424,6 +432,14 @@ got_pack_paint_commits(int *ncolored, struct got_objec
>  				err = got_pack_paint_commit(qid, COLOR_SKIP);
>  				if (err)
>  					goto done;
> +				err = got_object_idset_add(skip, &qid->id,
> +				    NULL);
> +				if (err)
> +					goto done;
> +				err = got_pack_repaint_parent_commits(&qid->id,
> +				    COLOR_SKIP, skip, skip, repo);
> +				if (err)
> +					goto done;
>  			} else
>  				(*ncolored)++;
>  			err = got_object_idset_add(drop, &qid->id, NULL);
> blob - 6b8fdb74f13c5157983c8ba15522d812f4865a83
> blob + 5038d17342275e2c833422660801b878be6e5f4e
> --- regress/cmdline/pack.sh
> +++ regress/cmdline/pack.sh
> @@ -711,6 +711,67 @@ test_pack_tagged_tag() {
>  	test_done "$testroot" "$ret"
>  }
>  
> +test_pack_exclude_via_ancestor_commit() {
> +	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; 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
> +
> +	# 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
> @@ -721,3 +782,4 @@ run_test test_pack_loose_only
>  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
> blob - d1e70129d2724139bccbc574d043f24c9ec5c7cd
> blob + e4fbb5fd04e0bbf35f12d2246856ca38c8564fd3
> --- regress/gotd/repo_write_protected.sh
> +++ regress/gotd/repo_write_protected.sh
> @@ -309,8 +309,132 @@ test_modify_protected_branch() {
>  	test_done "$testroot" $ret
>  }
>  
> +test_modify_protected_branch_with_deep_history() {
> +	local testroot=`test_init modify_protected_branch`
> +
> +	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 $testroot/repo-clone $testroot/wt >/dev/null
> +
> +	for i in 1 2 3 4 5 6 7 8 9; do
> +		echo "more alpha" >> $testroot/wt/alpha
> +		(cd $testroot/wt && got commit -m "more" >/dev/null)
> +	done
> +	local commit_id=`git_show_head $testroot/repo-clone`
> +	local parent_commit_id=`git_show_parent_commit $testroot/repo-clone \
> +		"$commit_id"`
> +
> +	# Modifying the branch by adding new commits on top should succeed.
> +	got send -q -r $testroot/repo-clone 2> $testroot/stderr
> +	ret=$?
> +	if [ $ret -ne 0 ]; then
> +		echo "got send failed unexpectedly" >&2
> +		test_done "$testroot" 1
> +		return 1
> +	fi
> +
> +	# Verify that the send operation worked fine.
> +	got clone -l ${GOTD_TEST_REPO_URL} | grep main > $testroot/stdout
> +	ret=$?
> +	if [ $ret -ne 0 ]; then
> +		echo "got clone -l failed unexpectedly" >&2
> +		test_done "$testroot" "1"
> +		return 1
> +	fi
> +
> +	echo "HEAD: refs/heads/main" > $testroot/stdout.expected
> +	echo "refs/heads/main: $commit_id" >> $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
> +
> +	# Attempt to remove the tip commit
> +	(cd $testroot/wt && got update -c "$parent_commit_id" >/dev/null)
> +	(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
> +
> +	# The client should reject sending without -f.
> +	got send -q -r $testroot/repo-clone 2> $testroot/stderr
> +	ret=$?
> +	if [ $ret -eq 0 ]; then
> +		echo "got send succeeded unexpectedly" >&2
> +		test_done "$testroot" 1
> +		return 1
> +	fi
> + 
> +	echo -n 'got: refs/heads/main: branch on server has' \
> +	    > $testroot/stderr.expected
> +	echo -n ' a different ancestry; either fetch changes' \
> +	    >> $testroot/stderr.expected
> +	echo -n ' from server and then rebase or merge local' \
> +	    >> $testroot/stderr.expected
> +	echo -n ' branch before sending, or ignore ancestry' \
> +	    >> $testroot/stderr.expected
> +	echo -n ' with send -f (can lead to data loss on' \
> +	    >> $testroot/stderr.expected
> +	echo ' server)' >> $testroot/stderr.expected
> +
> +	if ! cmp -s $testroot/stderr.expected $testroot/stderr; then
> +		diff -u $testroot/stderr.expected $testroot/stderr
> +		test_done "$testroot" 1
> +		return 1
> +	fi
> +
> +	# Try again with -f.
> +	got send -q -r $testroot/repo-clone -f 2> $testroot/stderr
> +	ret=$?
> +	if [ $ret -eq 0 ]; then
> +		echo "got send succeeded unexpectedly" >&2
> +		test_done "$testroot" 1
> +		return 1
> +	fi
> +
> +	if ! egrep -q '(gotsh|got-send-pack): refs/heads/main: reference is protected' \
> +		$testroot/stderr; then
> +		echo -n "error message unexpected or missing: " >&2
> +		cat $testroot/stderr >&2
> +		test_done "$testroot" 1
> +		return 1
> +	fi
> +
> +	# Verify that the send -f operation did not have any effect.
> +	got clone -l ${GOTD_TEST_REPO_URL} | grep main > $testroot/stdout
> +	ret=$?
> +	if [ $ret -ne 0 ]; then
> +		echo "got clone -l failed unexpectedly" >&2
> +		test_done "$testroot" "1"
> +		return 1
> +	fi
> +
> +	echo "HEAD: refs/heads/main" > $testroot/stdout.expected
> +	echo "refs/heads/main: $commit_id" >> $testroot/stdout.expected
> +	cmp -s $testroot/stdout.expected $testroot/stdout
> +	ret=$?
> +	if [ $ret -ne 0 ]; then
> +		diff -u $testroot/stdout.expected $testroot/stdout
> +	fi
> +
> +	test_done "$testroot" $ret
> +}
> +
>  test_parseargs "$@"
>  run_test test_create_protected_branch
>  run_test test_modify_protected_tag_namespace
>  run_test test_delete_protected_branch
>  run_test test_modify_protected_branch
> +run_test test_modify_protected_branch_with_deep_history


-- 
Mark Jamsek <https://bsdbox.org>
GPG: F2FF 13DE 6A06 C471 CA80  E6E2 2930 DC66 86EE CF68