Commit Diff


commit - e9e7947043acbb1cb2962a4d2361c95af3dfe3b4
commit + f9542c24c5340ee3ab60b432efa61c97bcc04381
blob - c42b6ea7059df811d07c1a23bc6a6089f931691f
blob + c852e6474eee75d7c3d0773e41b63b4ba08c30a2
--- gotd/repo_write.c
+++ gotd/repo_write.c
@@ -94,6 +94,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;
@@ -262,6 +263,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);
 
@@ -968,6 +970,24 @@ validate_object_type(int obj_type)
 }
 
 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)
 {
@@ -1016,8 +1036,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);
@@ -1275,6 +1307,16 @@ recv_packfile(int *have_packfile, struct imsg *imsg)
 	if (nobj == 0 &&
 	    client->nref_del > 0 &&
 	    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;
blob - d75e82812ee4b8e5c04144e1f6f981703b541699
blob + daff924b47ca360160665215790f700bf0f8bfda
--- regress/gotd/repo_write.sh
+++ regress/gotd/repo_write.sh
@@ -458,8 +458,95 @@ EOF
 	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 <<EOF >$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