Message ID | 20241209-fix-bundle-create-race-v1-1-e6513bdcbf8a@iotcl.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | bundle: remove unneeded code | expand |
Toon Claes <toon@iotcl.com> writes: > The changes in commit c06793a4ed (allow git-bundle to create bottomless > bundle, 2007-08-08) ensure annotated tags are properly preserved when > creating a bundle using a revision range operation. > > At the time the range notation would peel the ends to their > corresponding commit, meaning ref v2.0 would point to the v2.0^0 commit. > So the above workaround was introduced. This code looks up the ref > before it's written to the bundle, and if the ref doesn't point to the > object we expect (for tags this would be a tag object), we skip the ref > from the bundle. Instead, when the ref is a tag that's the positive end > of the range (e.g. v2.0 from the range "v1.0..v2.0"), then that ref is > written to the bundle instead. > > Later, in 895c5ba3c1 (revision: do not peel tags used in range notation, > 2013-09-19), the behavior of parsing ranges was changed and the problem > was fixed at the cause. But the workaround in bundle.c was not reverted. > Interesting to read the progression in these changes. Good digging. > Now it seems this workaround can cause a race condition. git-bundle(1) > uses setup_revisions() to parse the input into `struct rev_info`. Later, > in write_bundle_refs(), it uses this info to write refs to the bundle. > As mentioned at this point each ref is looked up again and checked > whether it points to the object we expect. If not, the ref is not > written to the bundle. But, when creating a bundle in a heavy traffic > repository (a repo with many references, and frequent ref updates) it's > possible a branch ref was updated between setup_revisions() and > write_bundle_refs() and thus the extra check causes the ref to be > skipped. > This makes sense, once the input is parsed in `setup_revisions()`, those'd be the values we want to use. Checking for values again is a definite race condition. > The workaround was originally added to deal with tags, but the code path > also gets hit by non-tag refs, causing this race condition. Because it's > no longer needed, remove it and fix the possible race condition. Nice, simple fix. [snip] > diff --git a/t/t6020-bundle-misc.sh b/t/t6020-bundle-misc.sh > index 5d444bfe201a330527e86dde7229721fc386fc93..f398a59424dcd025ce616cadcd7eece9be5301a3 100755 > --- a/t/t6020-bundle-misc.sh > +++ b/t/t6020-bundle-misc.sh > @@ -504,6 +504,40 @@ test_expect_success 'unfiltered bundle with --objects' ' > test_cmp expect actual > ' > > +test_expect_success 'bottomless bundle upto tag' ' > + git bundle create v2.bdl \ > + v2 && > + > + git bundle verify v2.bdl | > + make_user_friendly_and_stable_output >actual && > + > + format_and_save_expect <<-EOF && > + The bundle contains this ref: > + <TAG-2> refs/tags/v2 > + The bundle records a complete history. > + $HASH_MESSAGE > + EOF > + test_cmp expect actual > +' > + > +test_expect_success 'bundle between two tags' ' > + git bundle create v1-v2.bdl \ > + v1..v2 && > + > + git bundle verify v1-v2.bdl | > + make_user_friendly_and_stable_output >actual && > + > + format_and_save_expect <<-EOF && > + The bundle contains this ref: > + <TAG-2> refs/tags/v2 > + The bundle requires these 2 refs: > + <COMMIT-E> Z > + <COMMIT-B> Z > + $HASH_MESSAGE > + EOF > + test_cmp expect actual > +' > + Shouldn't we add a test for an annotated tag and verify that the tag object is also included in the bundle? Thanks Karthik
karthik nayak <karthik.188@gmail.com> writes: >> The workaround was originally added to deal with tags, but the code path >> also gets hit by non-tag refs, causing this race condition. Because it's >> no longer needed, remove it and fix the possible race condition. > > Nice, simple fix. > > [snip] > ... > Shouldn't we add a test for an annotated tag and verify that the tag > object is also included in the bundle? I agree that it is a good idea, especially because 895c5ba3c1 (revision: do not peel tags used in range notation, 2013-09-19) naturally does not come with a test for "git bundle". Thanks.
Toon Claes <toon@iotcl.com> writes: > The changes in commit c06793a4ed (allow git-bundle to create bottomless > bundle, 2007-08-08) ensure annotated tags are properly preserved when > creating a bundle using a revision range operation. > > At the time the range notation would peel the ends to their > corresponding commit, meaning ref v2.0 would point to the v2.0^0 commit. > So the above workaround was introduced. This code looks up the ref > before it's written to the bundle, and if the ref doesn't point to the > object we expect (for tags this would be a tag object), we skip the ref > from the bundle. Instead, when the ref is a tag that's the positive end > of the range (e.g. v2.0 from the range "v1.0..v2.0"), then that ref is > written to the bundle instead. > > Later, in 895c5ba3c1 (revision: do not peel tags used in range notation, > 2013-09-19), the behavior of parsing ranges was changed and the problem > was fixed at the cause. But the workaround in bundle.c was not reverted. > > Now it seems this workaround can cause a race condition. git-bundle(1) > uses setup_revisions() to parse the input into `struct rev_info`. Later, > in write_bundle_refs(), it uses this info to write refs to the bundle. > As mentioned at this point each ref is looked up again and checked > whether it points to the object we expect. If not, the ref is not > written to the bundle. But, when creating a bundle in a heavy traffic > repository (a repo with many references, and frequent ref updates) it's > possible a branch ref was updated between setup_revisions() and > write_bundle_refs() and thus the extra check causes the ref to be > skipped. > > The workaround was originally added to deal with tags, but the code path > also gets hit by non-tag refs, causing this race condition. Because it's > no longer needed, remove it and fix the possible race condition. It is always a pleasure to read a patch based on the idea to directly target a nicely analyzed "root cause". Thanks.
diff --git a/bundle.c b/bundle.c index 4773b51eb1df8057466c87f48445c49bc1f594ee..dfb5b7a5ec6b98e00078359afe991bac55cae739 100644 --- a/bundle.c +++ b/bundle.c @@ -420,36 +420,6 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs) e->name); goto skip_write_ref; } - /* - * If you run "git bundle create bndl v1.0..v2.0", the - * name of the positive ref is "v2.0" but that is the - * commit that is referenced by the tag, and not the tag - * itself. - */ - if (!oideq(&oid, &e->item->oid)) { - /* - * Is this the positive end of a range expressed - * in terms of a tag (e.g. v2.0 from the range - * "v1.0..v2.0")? - */ - struct commit *one = lookup_commit_reference(revs->repo, &oid); - struct object *obj; - - if (e->item == &(one->object)) { - /* - * Need to include e->name as an - * independent ref to the pack-objects - * input, so that the tag is included - * in the output; otherwise we would - * end up triggering "empty bundle" - * error. - */ - obj = parse_object_or_die(&oid, e->name); - obj->flags |= SHOWN; - add_pending_object(revs, obj, e->name); - } - goto skip_write_ref; - } ref_count++; write_or_die(bundle_fd, oid_to_hex(&e->item->oid), the_hash_algo->hexsz); diff --git a/t/t6020-bundle-misc.sh b/t/t6020-bundle-misc.sh index 5d444bfe201a330527e86dde7229721fc386fc93..f398a59424dcd025ce616cadcd7eece9be5301a3 100755 --- a/t/t6020-bundle-misc.sh +++ b/t/t6020-bundle-misc.sh @@ -504,6 +504,40 @@ test_expect_success 'unfiltered bundle with --objects' ' test_cmp expect actual ' +test_expect_success 'bottomless bundle upto tag' ' + git bundle create v2.bdl \ + v2 && + + git bundle verify v2.bdl | + make_user_friendly_and_stable_output >actual && + + format_and_save_expect <<-EOF && + The bundle contains this ref: + <TAG-2> refs/tags/v2 + The bundle records a complete history. + $HASH_MESSAGE + EOF + test_cmp expect actual +' + +test_expect_success 'bundle between two tags' ' + git bundle create v1-v2.bdl \ + v1..v2 && + + git bundle verify v1-v2.bdl | + make_user_friendly_and_stable_output >actual && + + format_and_save_expect <<-EOF && + The bundle contains this ref: + <TAG-2> refs/tags/v2 + The bundle requires these 2 refs: + <COMMIT-E> Z + <COMMIT-B> Z + $HASH_MESSAGE + EOF + test_cmp expect actual +' + for filter in "blob:none" "tree:0" "tree:1" "blob:limit=100" do test_expect_success "filtered bundle: $filter" '