Skip to content

Commit 9d57a5f

Browse files
calvin-wan-googlegitster
authored andcommitted
fetch-pack.c: do not declare local commits as "have" in partial repos
In a partial repository, creating a local commit and then fetching causes the following state to occur: commit tree blob C3 ---- T3 -- B3 (fetched from remote, in promisor pack) | C2 ---- T2 -- B2 (created locally, in non-promisor pack) | C1 ---- T1 -- B1 (fetched from remote, in promisor pack) During garbage collection, parents of promisor objects are marked as UNINTERESTING and are subsequently garbage collected. In this case, C2 would be deleted and attempts to access that commit would result in "bad object" errors (originally reported here[1]). This is not a bug in gc since it should be the case that parents of promisor objects are also promisor objects (fsck assumes this as well). When promisor objects are fetched, the state of the repository should ensure that the above holds true. Therefore, do not declare local commits as "have" in partial repositores so they can be fetched into a promisor pack. [1] https://lore.kernel.org/git/[email protected]/ Helped-by: Jonathan Tan <[email protected]> Signed-off-by: Calvin Wan <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 0bde44a commit 9d57a5f

File tree

2 files changed

+43
-3
lines changed

2 files changed

+43
-3
lines changed

fetch-pack.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,12 +1295,23 @@ static void add_common(struct strbuf *req_buf, struct oidset *common)
12951295

12961296
static int add_haves(struct fetch_negotiator *negotiator,
12971297
struct strbuf *req_buf,
1298-
int *haves_to_send)
1298+
int *haves_to_send,
1299+
int from_promisor)
12991300
{
13001301
int haves_added = 0;
13011302
const struct object_id *oid;
13021303

13031304
while ((oid = negotiator->next(negotiator))) {
1305+
/*
1306+
* In partial repos, do not declare local objects as "have"
1307+
* so that they can be fetched into a promisor pack. Certain
1308+
* operations mark parent commits of promisor objects as
1309+
* UNINTERESTING and are subsequently garbage collected so
1310+
* this ensures local commits are still available in promisor
1311+
* packs after a fetch + gc.
1312+
*/
1313+
if (from_promisor && !is_in_promisor_pack(oid, 0))
1314+
continue;
13041315
packet_buf_write(req_buf, "have %s\n", oid_to_hex(oid));
13051316
if (++haves_added >= *haves_to_send)
13061317
break;
@@ -1403,7 +1414,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
14031414
/* Add all of the common commits we've found in previous rounds */
14041415
add_common(&req_buf, common);
14051416

1406-
haves_added = add_haves(negotiator, &req_buf, haves_to_send);
1417+
haves_added = add_haves(negotiator, &req_buf, haves_to_send, args->from_promisor);
14071418
*in_vain += haves_added;
14081419
trace2_data_intmax("negotiation_v2", the_repository, "haves_added", haves_added);
14091420
trace2_data_intmax("negotiation_v2", the_repository, "in_vain", *in_vain);
@@ -2176,7 +2187,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,
21762187

21772188
packet_buf_write(&req_buf, "wait-for-done");
21782189

2179-
haves_added = add_haves(&negotiator, &req_buf, &haves_to_send);
2190+
haves_added = add_haves(&negotiator, &req_buf, &haves_to_send, 0);
21802191
in_vain += haves_added;
21812192
if (!haves_added || (seen_ack && in_vain >= MAX_IN_VAIN))
21822193
last_iteration = 1;

t/t5616-partial-clone.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,35 @@ test_expect_success 'lazy-fetch in submodule succeeds' '
693693
git -C client restore --recurse-submodules --source=HEAD^ :/
694694
'
695695

696+
test_expect_success 'fetching from promisor remote fetches previously local commits' '
697+
# Setup
698+
git init full &&
699+
git -C full config uploadpack.allowfilter 1 &&
700+
git -C full config uploadpack.allowanysha1inwant 1 &&
701+
touch full/foo &&
702+
git -C full add foo &&
703+
git -C full commit -m "commit 1" &&
704+
git -C full checkout --detach &&
705+
706+
# Partial clone and push commit to remote
707+
git clone "file://$(pwd)/full" --filter=blob:none partial &&
708+
echo "hello" > partial/foo &&
709+
git -C partial commit -a -m "commit 2" &&
710+
git -C partial push &&
711+
712+
# gc in partial repo
713+
git -C partial gc --prune=now &&
714+
715+
# Create another commit in normal repo
716+
git -C full checkout main &&
717+
echo " world" >> full/foo &&
718+
git -C full commit -a -m "commit 3" &&
719+
720+
# Pull from remote in partial repo, and run gc again
721+
git -C partial pull &&
722+
git -C partial gc --prune=now
723+
'
724+
696725
. "$TEST_DIRECTORY"/lib-httpd.sh
697726
start_httpd
698727

0 commit comments

Comments
 (0)