From: Junio C Hamano <hidden> Date: 2021-01-04 23:42:12
Jiang Xin [off-list ref] writes:
Therefore, before calling `prepare_revision_walk()` function, make a
copy on `revs.pending` for later use.
The in-core objects pointed by the list elements are shared between
the original and the copy, and the object flag bits that are used to
control the traversal (like SEEN, SHOWN and BOUNDARY bits) would be
smudged during the traversal. So depending on how the "later use"
uses the copied list, it may or may not be sufficient to just copy
the list.
Apparently, you've tested the updated code well enough to send to
the list, so it must be sufficient to make a copy of the list to
support the way the updated code uses it, but it is not clear how it
is so, only from what is in the proposed log message.
From: Jiang Xin <redacted>
`git rev-list` will list one commit for the following command:
$ git rev-list 'main^!'
<tip-commit-of-main-branch>
But providing the same rev-list args to `git bundle`, fail to create
a bundle file.
$ git bundle create - 'main^!'
# v2 git bundle
-<OID> <one-line-message>
fatal: Refusing to create empty bundle.
This is because when removing duplicate objects in function
`object_array_remove_duplicates()`, one unique pending object which has
the same name is deleted by mistake. The revision arg 'main^!' in the
above example is parsed by `handle_revision_arg()`, and at lease two
different objects will be appended to `revs.pending`, one points to the
parent commit of the "main" branch, and the other points to the tip
commit of the "main" branch. These two objects have the same name
"main". Only one object is left with the name "main" after calling the
function `object_array_remove_duplicates()`.
And what's worse, when adding boundary commits into pending list, we use
one-line commit message as names, and the arbitory names may surprise
git-bundle.
Only comparing objects themselves (".item") is also not good enough,
because user may want to create a bundle with two identical objects but
with different reference names, such as: "HEAD" and "refs/heads/main".
Add new function `contains_object()` which compare both the address and
the name of the object.
Signed-off-by: Jiang Xin <redacted>
---
object.c | 10 +-
t/t6020-bundle-misc.sh | 413 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 419 insertions(+), 4 deletions(-)
create mode 100755 t/t6020-bundle-misc.sh
@@ -0,0 +1,413 @@+test_description='Test git-bundle'++GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main+exportGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME++../test-lib.sh++test_bundle_object_count(){+gitverify-pack-v"$1">verify.out&&+count=$(grep"^$OID_REGEX "verify.out|wc-l)&&+test$2=$count&&return0+echoobjectcountis$count,not$2+return1+}++convert_bundle_to_pack(){+whilereadx&&test-n"$x"+do+:;+done+cat+}++# Format the output of git commands to make a user-friendly and stable+# text. We can easily prepare the expect text without having to worry+# about future changes of the commit ID and spaces of the output.+make_user_friendly_and_stable_output(){+sed\+-e"s/ *\$//"\+-e"s/$A/<COMMIT-A>/g"\+-e"s/$B/<COMMIT-B>/g"\+-e"s/$C/<COMMIT-C>/g"\+-e"s/$D/<COMMIT-D>/g"\+-e"s/$E/<COMMIT-E>/g"\+-e"s/$F/<COMMIT-F>/g"\+-e"s/$G/<COMMIT-G>/g"\+-e"s/$H/<COMMIT-H>/g"\+-e"s/$I/<COMMIT-I>/g"\+-e"s/$J/<COMMIT-J>/g"\+-e"s/$K/<COMMIT-K>/g"\+-e"s/$L/<COMMIT-L>/g"\+-e"s/$M/<COMMIT-M>/g"\+-e"s/$N/<COMMIT-N>/g"\+-e"s/$O/<COMMIT-O>/g"\+-e"s/$P/<COMMIT-P>/g"\+-e"s/$(echo$A|cut-c1-7)[0-9a-f]*/<OID-A>/g"\+-e"s/$(echo$B|cut-c1-7)[0-9a-f]*/<OID-B>/g"\+-e"s/$(echo$C|cut-c1-7)[0-9a-f]*/<OID-C>/g"\+-e"s/$(echo$D|cut-c1-7)[0-9a-f]*/<OID-D>/g"\+-e"s/$(echo$E|cut-c1-7)[0-9a-f]*/<OID-E>/g"\+-e"s/$(echo$F|cut-c1-7)[0-9a-f]*/<OID-F>/g"\+-e"s/$(echo$G|cut-c1-7)[0-9a-f]*/<OID-G>/g"\+-e"s/$(echo$H|cut-c1-7)[0-9a-f]*/<OID-H>/g"\+-e"s/$(echo$I|cut-c1-7)[0-9a-f]*/<OID-I>/g"\+-e"s/$(echo$J|cut-c1-7)[0-9a-f]*/<OID-J>/g"\+-e"s/$(echo$K|cut-c1-7)[0-9a-f]*/<OID-K>/g"\+-e"s/$(echo$L|cut-c1-7)[0-9a-f]*/<OID-L>/g"\+-e"s/$(echo$M|cut-c1-7)[0-9a-f]*/<OID-M>/g"\+-e"s/$(echo$N|cut-c1-7)[0-9a-f]*/<OID-N>/g"\+-e"s/$(echo$O|cut-c1-7)[0-9a-f]*/<OID-O>/g"\+-e"s/$(echo$P|cut-c1-7)[0-9a-f]*/<OID-P>/g"\+-e"s/$TAG1/<TAG-1>/g"\+-e"s/$TAG2/<TAG-2>/g"\+-e"s/$TAG3/<TAG-3>/g"\+-e"s/$(echo$TAG1|cut-c1-7)[0-9a-f]*/<OID-TAG-1>/g"\+-e"s/$(echo$TAG2|cut-c1-7)[0-9a-f]*/<OID-TAG-2>/g"\+-e"s/$(echo$TAG3|cut-c1-7)[0-9a-f]*/<OID-TAG-3>/g"\+-e"s/$ZERO_OID/<ZERO-OID>/g"+}++# (C) (D, pull/1/head, topic/1)+# o --- o+# / \ (L, tags/v1)+# / \ o (H, topic/2) (M, tags/v2)+# / (F) \ / (N, tags/v3)+# / o --------- o (G, pull/2/head) o --- o --- o (release)+# / / \ \ / \+# o --- o --- o -------- o -- o ------------------ o ------- o --- o (main)+# (A) (B) (E) (I) (J) (K) (O) (P)+#+test_expect_success'setup''+# commit A & B+cat>main.txt<<-EOF&&+CommitA+EOF+gitaddmain.txt&&+test_tick&&+gitcommit-m"Commit A"&&++cat>main.txt<<-EOF&&+CommitB+EOF+gitaddmain.txt&&+test_tick&&+gitcommit-m"Commit B"&&+A=$(gitrev-parseHEAD~)&&+B=$(gitrev-parseHEAD)&&++# branch topic/1+gitcheckout-btopic/1&&+cat>topic-1.txt<<-EOF&&+CommitC+EOF+gitaddtopic-1.txt&&+test_tick&&+gitcommit-m"Commit C"&&++cat>topic-1.txt<<-EOF&&+CommitD+EOF+gitadd-u&&+test_tick&&+gitcommit-m"Commit D"&&+gitupdate-refrefs/pull/1/headHEAD&&+C=$(gitrev-parsetopic/1~)&&+D=$(gitrev-parsetopic/1)&&++# commit E+gitcheckoutmain&&+cat>main.txt<<-EOF&&+CommitE+EOF+gitaddmain.txt&&+test_tick&&+gitcommit-m"Commit E"&&+E=$(gitrev-parseHEAD)&&++# branch topic/2+gitcheckout-btopic/2&&+cat>topic-2.txt<<-EOF&&+CommitF+EOF+gitaddtopic-2.txt&&+test_tick&&+gitcommit-m"Commit F"&&++cat>topic-2.txt<<-EOF&&+CommitG+EOF+gitadd-u&&+test_tick&&+gitcommit-m"Commit G"&&+gitupdate-refrefs/pull/2/headHEAD&&++cat>topic-2.txt<<-EOF&&+CommitH+EOF+gitadd-u&&+test_tick&&+gitcommit-m"Commit H"&&+F=$(gitrev-parsetopic/2~2)&&+G=$(gitrev-parsetopic/2~)&&+H=$(gitrev-parsetopic/2)&&++# merge commit I & J+gitcheckoutmain&&+test_tick&&+gitmerge--no-ff--no-edittopic/1&&+test_tick&&+gitmerge--no-ff--no-editrefs/pull/2/head&&+I=$(gitrev-parseHEAD~)&&+J=$(gitrev-parseHEAD)&&++# commit K+gitcheckoutmain&&+cat>main.txt<<-EOF&&+CommitK+EOF+gitaddmain.txt&&+test_tick&&+gitcommit-m"Commit K"&&+K=$(gitrev-parseHEAD)&&++# branch release+gitcheckout-brelease&&+cat>release.txt<<-EOF&&+CommitL+EOF+gitaddrelease.txt&&+test_tick&&+gitcommit-m"Commit L"&&+test_tick&&+gittag-m"v1"v1HEAD&&++cat>release.txt<<-EOF&&+CommitM+EOF+gitadd-u&&+test_tick&&+gitcommit-m"Commit M"&&+test_tick&&+gittag-m"v2"v2HEAD&&++cat>release.txt<<-EOF&&+CommitN+EOF+gitadd-u&&+test_tick&&+gitcommit-m"Commit N"&&+test_tick&&+gittag-m"v3"v3HEAD&&+L=$(gitrev-parseHEAD~2)&&+M=$(gitrev-parseHEAD~)&&+N=$(gitrev-parseHEAD)&&+TAG1=$(gitrev-parserefs/tags/v1)&&+TAG2=$(gitrev-parserefs/tags/v2)&&+TAG3=$(gitrev-parserefs/tags/v3)&&++# merge commit O+gitcheckoutmain&&+test_tick&&+gitmerge--no-ff--no-edittags/v2&&+O=$(gitrev-parseHEAD)&&++# commit P+gitcheckoutmain&&+cat>main.txt<<-EOF&&+CommitP+EOF+gitaddmain.txt&&+test_tick&&+gitcommit-m"Commit P"&&+P=$(gitrev-parseHEAD)+'++test_expect_success'create bundle from special rev: main^!''+gitbundlecreatespecial-rev.bdl"main^!"&&++gitbundlelist-headsspecial-rev.bdl|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-P>refs/heads/main+EOF+test_i18ncmpexpectactual&&++gitbundleverifyspecial-rev.bdl|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+Thebundlecontainsthisref:+<COMMIT-P>refs/heads/main+Thebundlerequiresthisref:+<COMMIT-O>+EOF+test_i18ncmpexpectactual&&++convert_bundle_to_pack<special-rev.bdl>special-rev.pack&&+gitindex-packspecial-rev.pack&&+test_bundle_object_countspecial-rev.pack3+'++test_expect_success'create bundle 1 - no prerequisites''+# create bundle from args+gitbundlecreate1.bdltopic/1topic/2&&++cat>expect<<-EOF&&+Thebundlecontainsthese2refs:+<COMMIT-D>refs/heads/topic/1+<COMMIT-H>refs/heads/topic/2+Thebundlerecordsacompletehistory.+EOF++# verify bundle, which has no prerequisites+gitbundleverify1.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++convert_bundle_to_pack<1.bdl>1.pack&&+gitindex-pack1.pack&&+test_bundle_object_count1.pack24+'++test_expect_success'create bundle 2 - has prerequisites''+# create bundle from args+gitbundlecreate2.bdl\+--ignore-missing\+^topic/deleted\+^$D\+^topic/2\+release&&++cat>expect<<-EOF&&+Thebundlecontainsthisref:+<COMMIT-N>refs/heads/release+Thebundlerequiresthese3refs:+<COMMIT-D>+<COMMIT-E>+<COMMIT-G>+EOF++gitbundleverify2.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++convert_bundle_to_pack<2.bdl>2.pack&&+gitindex-pack2.pack&&+test_bundle_object_count2.pack16+'++test_expect_success'fail to verify bundle without prerequisites''+gitinit--baretest1.git&&++cat>expect<<-EOF&&+error:Repositorylackstheseprerequisitecommits:+error:<COMMIT-D>+error:<COMMIT-E>+error:<COMMIT-G>+EOF++test_must_failgit-Ctest1.gitbundleverify../2.bdl2>&1|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual+'++test_expect_success'create bundle 3 - two refs, same object''+# create bundle from args+gitbundlecreate--version=33.bdl\+^release\+^topic/1\+^topic/2\+main\+HEAD&&++cat>expect<<-EOF&&+Thebundlecontainsthese2refs:+<COMMIT-P>refs/heads/main+<COMMIT-P>HEAD+Thebundlerequiresthese2refs:+<COMMIT-M>+<COMMIT-K>+EOF++gitbundleverify3.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++convert_bundle_to_pack<3.bdl>3.pack&&+gitindex-pack3.pack&&+test_bundle_object_count3.pack4+'++test_expect_success'create bundle 4 - with tags''+# create bundle from args+gitbundlecreate4.bdl\+^main\+^release\+^topic/1\+^topic/2\+--all&&++cat>expect<<-EOF&&+Thebundlecontainsthese3refs:+<TAG-1>refs/tags/v1+<TAG-2>refs/tags/v2+<TAG-3>refs/tags/v3+Thebundlerecordsacompletehistory.+EOF++gitbundleverify4.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++convert_bundle_to_pack<4.bdl>4.pack&&+gitindex-pack4.pack&&+test_bundle_object_count4.pack3+'++test_expect_success'clone from bundle''+gitclone--mirror1.bdlmirror.git&&+git-Cmirror.gitshow-ref|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-D>refs/heads/topic/1+<COMMIT-H>refs/heads/topic/2+EOF+test_cmpexpectactual&&++git-Cmirror.gitfetch../2.bdl"+refs/*:refs/*"&&+git-Cmirror.gitshow-ref|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-N>refs/heads/release+<COMMIT-D>refs/heads/topic/1+<COMMIT-H>refs/heads/topic/2+EOF+test_cmpexpectactual&&++git-Cmirror.gitfetch../3.bdl"+refs/*:refs/*"&&+git-Cmirror.gitshow-ref|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-P>refs/heads/main+<COMMIT-N>refs/heads/release+<COMMIT-D>refs/heads/topic/1+<COMMIT-H>refs/heads/topic/2+EOF+test_cmpexpectactual&&++git-Cmirror.gitfetch../4.bdl"+refs/*:refs/*"&&+git-Cmirror.gitshow-ref|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-P>refs/heads/main+<COMMIT-N>refs/heads/release+<COMMIT-D>refs/heads/topic/1+<COMMIT-H>refs/heads/topic/2+<TAG-1>refs/tags/v1+<TAG-2>refs/tags/v2+<TAG-3>refs/tags/v3+EOF+test_cmpexpectactual+'++test_done
From: Jiang Xin <redacted>
In order to create an incremental bundle, we need to pass many arguments
to let git-bundle ignore some already packed commits. It will be more
convenient to pass args via stdin. But the current implementation does
not allow us to do this.
This is because args are parsed twice when creating bundle. The first
time for parsing args is in `compute_and_write_prerequisites()` by
running `git-rev-list` command to write prerequisites in bundle file,
and stdin is consumed in this step if "--stdin" option is provided for
`git-bundle`. Later nothing can be read from stdin when running
`setup_revisions()` in `create_bundle()`.
Remove the entire `compute_and_write_prerequisites()` function, and
parse the args once by `setup_revisions()`. The first step for creating
a bundle is to write prerequisites ("-" obj-id SP comment LF), but after
calling `prepare_revision_walk()`, the `revs.pending` is left empty.
Following steps could not work properly without data in `revs.pending`.
Therefore, before calling `prepare_revision_walk()` function, make a
copy `revs_copy` from `revs` for later use. Even though `revs_copy` and
`revs` share the same objects, but changes on these objects will not
change the behavior of function `write_bundle_refs()` and
`write_pack_data()`.
Also add testcases for git bundle in t6020, which read args from stdin.
Signed-off-by: Jiang Xin <redacted>
---
bundle.c | 109 ++++++++++++++++++++++------------------
t/t5607-clone-bundle.sh | 4 +-
t/t6020-bundle-misc.sh | 85 +++++++++++++++++++++++++++++--
3 files changed, 142 insertions(+), 56 deletions(-)
@@ -523,16 +511,37 @@ int create_bundle(struct repository *r, const char *path,gotoerr;}-object_array_remove_duplicates(&revs.pending);+/* save revs.pending in revs_copy for later use */+memcpy(&revs_copy,&revs,sizeof(revs));+revs_copy.pending.nr=0;+revs_copy.pending.alloc=0;+revs_copy.pending.objects=NULL;+for(i=0;i<revs.pending.nr;i++){+structobject_array_entry*e=revs.pending.objects+i;+if(e)+add_object_array_with_path(e->item,e->name,+&revs_copy.pending,+e->mode,e->path);+}-ref_count=write_bundle_refs(bundle_fd,&revs);+/* write prerequisites */+revs.boundary=1;+if(prepare_revision_walk(&revs))+die("revision walk setup failed");+bpi.fd=bundle_fd;+bpi.pending=&revs_copy.pending;+traverse_commit_list(&revs,write_bundle_prerequisites,NULL,&bpi);+object_array_remove_duplicates(&revs_copy.pending);++/* write bundle refs */+ref_count=write_bundle_refs(bundle_fd,&revs_copy);if(!ref_count)die(_("Refusing to create empty bundle."));elseif(ref_count<0)gotoerr;/* write pack */-if(write_pack_data(bundle_fd,&revs,pack_options))+if(write_pack_data(bundle_fd,&revs_copy,pack_options))gotoerr;if(!bundle_to_stdout){
From: Jiang Xin <redacted>
In order to create an incremental bundle, we need to pass many arguments
to let git-bundle ignore some already packed commits. It will be more
convenient to pass args via stdin. But the current implementation does
not allow us to do this.
This is because args are parsed twice when creating bundle. The first
time for parsing args is in `compute_and_write_prerequisites()` by
running `git-rev-list` command to write prerequisites in bundle file,
and stdin is consumed in this step if "--stdin" option is provided for
`git-bundle`. Later nothing can be read from stdin when running
`setup_revisions()` in `create_bundle()`.
The solution is to parse args once by removing the entire function
`compute_and_write_prerequisites()` and then calling function
`setup_revisions()`. In order to write prerequisites for bundle, will
call `prepare_revision_walk()` and `traverse_commit_list()`. But after
calling `prepare_revision_walk()`, the object array `revs.pending` is
left empty, and the following steps could not work properly with the
empty object array (`revs.pending`). Therefore, make a copy of `revs`
to `revs_copy` for later use right after calling `setup_revisions()`.
The copy of `revs_copy` is not a deep copy, it shares the same objects
with `revs`. The object array of `revs` has been cleared, but objects
themselves are still kept. Flags of objects may change after calling
`prepare_revision_walk()`, we can use these changed flags without
calling the `git rev-list` command and parsing its output like the
former implementation.
Also add testcases for git bundle in t6020, which read args from stdin.
Signed-off-by: Jiang Xin <redacted>
---
bundle.c | 111 ++++++++++++++++++++++------------------
t/t5607-clone-bundle.sh | 4 +-
t/t6020-bundle-misc.sh | 77 ++++++++++++++++++++++++++--
3 files changed, 135 insertions(+), 57 deletions(-)
@@ -425,7 +383,7 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs)*constraints.*/if(!(e->item->flags&SHOWN)&&e->item->type==OBJ_COMMIT){-warning(_("ref '%s' is excluded by the rev-list options"),+warning(_("ref '%s' is excluded by the limiting options"),e->name);gotoskip_write_ref;}
@@ -523,16 +511,37 @@ int create_bundle(struct repository *r, const char *path,gotoerr;}-object_array_remove_duplicates(&revs.pending);+/* save revs.pending in revs_copy for later use */+memcpy(&revs_copy,&revs,sizeof(revs));+revs_copy.pending.nr=0;+revs_copy.pending.alloc=0;+revs_copy.pending.objects=NULL;+for(i=0;i<revs.pending.nr;i++){+structobject_array_entry*e=revs.pending.objects+i;+if(e)+add_object_array_with_path(e->item,e->name,+&revs_copy.pending,+e->mode,e->path);+}-ref_count=write_bundle_refs(bundle_fd,&revs);+/* write prerequisites */+revs.boundary=1;+if(prepare_revision_walk(&revs))+die("revision walk setup failed");+bpi.fd=bundle_fd;+bpi.pending=&revs_copy.pending;+traverse_commit_list(&revs,write_bundle_prerequisites,NULL,&bpi);+object_array_remove_duplicates(&revs_copy.pending);++/* write bundle refs */+ref_count=write_bundle_refs(bundle_fd,&revs_copy);if(!ref_count)die(_("Refusing to create empty bundle."));elseif(ref_count<0)gotoerr;/* write pack */-if(write_pack_data(bundle_fd,&revs,pack_options))+if(write_pack_data(bundle_fd,&revs_copy,pack_options))gotoerr;if(!bundle_to_stdout){
@@ -336,8 +336,16 @@ test_expect_success 'create bundle with --since option' '' test_expect_success'create bundle 1 - no prerequisites''+# create bundle from argsgitbundlecreate1.bdltopic/1topic/2&&+# create bundle from stdin+cat>input<<-EOF&&+topic/1+topic/2+EOF+gitbundlecreatestdin-1.bdl--stdin<input&&+cat>expect<<-EOF&&Thebundlecontainsthese2refs:<COMMIT-D>refs/heads/topic/1
@@ -350,10 +358,16 @@ test_expect_success 'create bundle 1 - no prerequisites' 'make_user_friendly_and_stable_output>actual&&test_i18ncmpexpectactual&&-test_bundle_object_count1.bdl24+gitbundleverifystdin-1.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_bundle_object_count1.bdl24&&+test_bundle_object_countstdin-1.bdl24' test_expect_success'create bundle 2 - has prerequisites''+# create bundle from argsgitbundlecreate2.bdl\--ignore-missing\^topic/deleted\
@@ -361,6 +375,18 @@ test_expect_success 'create bundle 2 - has prerequisites' '^topic/2\release&&+# create bundle from stdin+# input has a non-exist reference: "topic/deleted"+cat>input<<-EOF&&+^topic/deleted+^$D+^topic/2+EOF+gitbundlecreatestdin-2.bdl\+--ignore-missing\+--stdin\+release<input&&+cat>expect<<-EOF&&Thebundlecontainsthisref:<COMMIT-N>refs/heads/release
@@ -374,7 +400,12 @@ test_expect_success 'create bundle 2 - has prerequisites' 'make_user_friendly_and_stable_output>actual&&test_i18ncmpexpectactual&&-test_bundle_object_count2.bdl16+gitbundleverifystdin-2.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_bundle_object_count2.bdl16&&+test_bundle_object_countstdin-2.bdl16' test_expect_success'fail to verify bundle without prerequisites''
@@ -389,10 +420,15 @@ test_expect_success 'fail to verify bundle without prerequisites' 'test_must_failgit-Ctest1.gitbundleverify../2.bdl2>&1|make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_must_failgit-Ctest1.gitbundleverify../stdin-2.bdl2>&1|+make_user_friendly_and_stable_output>actual&&test_i18ncmpexpectactual' test_expect_success'create bundle 3 - two refs, same object''+# create bundle from argsgitbundlecreate--version=33.bdl\^release\^topic/1\
@@ -400,6 +436,16 @@ test_expect_success 'create bundle 3 - two refs, same object' 'main\HEAD&&+# create bundle from stdin+cat>input<<-EOF&&+^release+^topic/1+^topic/2+EOF+gitbundlecreate--version=3stdin-3.bdl\+--stdin\+mainHEAD<input&&+cat>expect<<-EOF&&Thebundlecontainsthese2refs:<COMMIT-P>refs/heads/main
@@ -413,10 +459,16 @@ test_expect_success 'create bundle 3 - two refs, same object' 'make_user_friendly_and_stable_output>actual&&test_i18ncmpexpectactual&&-test_bundle_object_count3.bdl4+gitbundleverifystdin-3.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_bundle_object_count3.bdl4&&+test_bundle_object_countstdin-3.bdl4' test_expect_success'create bundle 4 - with tags''+# create bundle from argsgitbundlecreate4.bdl\^main\^release\
@@ -424,6 +476,18 @@ test_expect_success 'create bundle 4 - with tags' '^topic/2\--all&&+# create bundle from stdin+cat>input<<-EOF&&+^main+^release+^topic/1+^topic/2+EOF+gitbundlecreatestdin-4.bdl\+--ignore-missing\+--stdin\+--all<input&&+cat>expect<<-EOF&&Thebundlecontainsthese3refs:<TAG-1>refs/tags/v1
@@ -436,7 +500,12 @@ test_expect_success 'create bundle 4 - with tags' 'make_user_friendly_and_stable_output>actual&&test_i18ncmpexpectactual&&-test_bundle_object_count4.bdl3+gitbundleverifystdin-4.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_bundle_object_count4.bdl3&&+test_bundle_object_countstdin-4.bdl3' test_expect_success'clone from bundle''
From: Jiang Xin <redacted>
`git rev-list` will list one commit for the following command:
$ git rev-list 'main^!'
<tip-commit-of-main-branch>
But providing the same rev-list args to `git bundle`, fail to create
a bundle file.
$ git bundle create - 'main^!'
# v2 git bundle
-<OID> <one-line-message>
fatal: Refusing to create empty bundle.
This is because when removing duplicate objects in function
`object_array_remove_duplicates()`, one unique pending object which has
the same name is deleted by mistake. The revision arg 'main^!' in the
above example is parsed by `handle_revision_arg()`, and at lease two
different objects will be appended to `revs.pending`, one points to the
parent commit of the "main" branch, and the other points to the tip
commit of the "main" branch. These two objects have the same name
"main". Only one object is left with the name "main" after calling the
function `object_array_remove_duplicates()`.
And what's worse, when adding boundary commits into pending list, we use
one-line commit message as names, and the arbitory names may surprise
git-bundle.
Only comparing objects themselves (".item") is also not good enough,
because user may want to create a bundle with two identical objects but
with different reference names, such as: "HEAD" and "refs/heads/main".
Add new function `contains_object()` which compare both the address and
the name of the object.
Signed-off-by: Jiang Xin <redacted>
---
object.c | 10 +-
t/t6020-bundle-misc.sh | 488 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 494 insertions(+), 4 deletions(-)
create mode 100755 t/t6020-bundle-misc.sh
@@ -0,0 +1,488 @@+#!/bin/sh+#+# Copyright (c) 2021 Jiang Xin+#++test_description='Test git-bundle'++GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main+exportGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME++../test-lib.sh++test_bundle_object_count(){+bundle=$1&&+pack=${bundle%.bdl}.pack&&+convert_bundle_to_pack<"$bundle">"$pack"&&+gitindex-pack"$pack"&&+gitverify-pack-v"$pack">verify.out&&+count=$(grep"^$OID_REGEX "verify.out|wc-l)&&+test$2=$count&&return0+echoobjectcountfor$bundleis$count,not$2+return1+}+++test_thin_bundle_object_count(){+bundle=$1&&+pack=${bundle%.bdl}.pack&&+convert_bundle_to_pack<"$bundle"|+test_must_failgitindex-pack--stdin"$pack"&&+rm-f"$pack"&&+convert_bundle_to_pack<"$bundle"|+gitindex-pack--stdin--fix-thin"$pack"&&+gitverify-pack-v"$pack">verify.out&&+count=$(grep"^$OID_REGEX "verify.out|wc-l)&&+test$2=$count&&return0+echoobjectcountfor$bundleis$count,not$2+return1+}++convert_bundle_to_pack(){+whilereadx&&test-n"$x"+do+:;+done+cat+}++# Format the output of git commands to make a user-friendly and stable+# text. We can easily prepare the expect text without having to worry+# about future changes of the commit ID and spaces of the output.+make_user_friendly_and_stable_output(){+sed\+-e"s/ *\$//"\+-e"s/$A/<COMMIT-A>/g"\+-e"s/$B/<COMMIT-B>/g"\+-e"s/$C/<COMMIT-C>/g"\+-e"s/$D/<COMMIT-D>/g"\+-e"s/$E/<COMMIT-E>/g"\+-e"s/$F/<COMMIT-F>/g"\+-e"s/$G/<COMMIT-G>/g"\+-e"s/$H/<COMMIT-H>/g"\+-e"s/$I/<COMMIT-I>/g"\+-e"s/$J/<COMMIT-J>/g"\+-e"s/$K/<COMMIT-K>/g"\+-e"s/$L/<COMMIT-L>/g"\+-e"s/$M/<COMMIT-M>/g"\+-e"s/$N/<COMMIT-N>/g"\+-e"s/$O/<COMMIT-O>/g"\+-e"s/$P/<COMMIT-P>/g"\+-e"s/$(echo$A|cut-c1-7)[0-9a-f]*/<OID-A>/g"\+-e"s/$(echo$B|cut-c1-7)[0-9a-f]*/<OID-B>/g"\+-e"s/$(echo$C|cut-c1-7)[0-9a-f]*/<OID-C>/g"\+-e"s/$(echo$D|cut-c1-7)[0-9a-f]*/<OID-D>/g"\+-e"s/$(echo$E|cut-c1-7)[0-9a-f]*/<OID-E>/g"\+-e"s/$(echo$F|cut-c1-7)[0-9a-f]*/<OID-F>/g"\+-e"s/$(echo$G|cut-c1-7)[0-9a-f]*/<OID-G>/g"\+-e"s/$(echo$H|cut-c1-7)[0-9a-f]*/<OID-H>/g"\+-e"s/$(echo$I|cut-c1-7)[0-9a-f]*/<OID-I>/g"\+-e"s/$(echo$J|cut-c1-7)[0-9a-f]*/<OID-J>/g"\+-e"s/$(echo$K|cut-c1-7)[0-9a-f]*/<OID-K>/g"\+-e"s/$(echo$L|cut-c1-7)[0-9a-f]*/<OID-L>/g"\+-e"s/$(echo$M|cut-c1-7)[0-9a-f]*/<OID-M>/g"\+-e"s/$(echo$N|cut-c1-7)[0-9a-f]*/<OID-N>/g"\+-e"s/$(echo$O|cut-c1-7)[0-9a-f]*/<OID-O>/g"\+-e"s/$(echo$P|cut-c1-7)[0-9a-f]*/<OID-P>/g"\+-e"s/$TAG1/<TAG-1>/g"\+-e"s/$TAG2/<TAG-2>/g"\+-e"s/$TAG3/<TAG-3>/g"\+-e"s/$(echo$TAG1|cut-c1-7)[0-9a-f]*/<OID-TAG-1>/g"\+-e"s/$(echo$TAG2|cut-c1-7)[0-9a-f]*/<OID-TAG-2>/g"\+-e"s/$(echo$TAG3|cut-c1-7)[0-9a-f]*/<OID-TAG-3>/g"\+-e"s/$ZERO_OID/<ZERO-OID>/g"+}++# (C) (D, pull/1/head, topic/1)+# o --- o+# / \ (L)+# / \ o (H, topic/2) (M, tag:v2)+# / (F) \ / (N, tag:v3)+# / o --------- o (G, pull/2/head) o --- o --- o (release)+# / / \ \ / \+# o --- o --- o -------- o -- o ------------------ o ------- o --- o (main)+# (A) (B) (E, tag:v1) (I) (J) (K) (O) (P)+#+test_expect_success'setup''+# commit A & B+cat>main.txt<<-EOF&&+CommitA+EOF+gitaddmain.txt&&+test_tick&&+gitcommit-m"Commit A"&&++cat>main.txt<<-EOF&&+CommitB+EOF+gitaddmain.txt&&+test_tick&&+gitcommit-m"Commit B"&&+A=$(gitrev-parseHEAD~)&&+B=$(gitrev-parseHEAD)&&++# branch topic/1+gitcheckout-btopic/1&&+cat>topic-1.txt<<-EOF&&+CommitC+EOF+gitaddtopic-1.txt&&+test_tick&&+gitcommit-m"Commit C"&&++cat>topic-1.txt<<-EOF&&+CommitD+EOF+gitadd-u&&+test_tick&&+gitcommit-m"Commit D"&&+gitupdate-refrefs/pull/1/headHEAD&&+C=$(gitrev-parsetopic/1~)&&+D=$(gitrev-parsetopic/1)&&++# commit E+gitcheckoutmain&&+cat>main.txt<<-EOF&&+CommitE+EOF+gitaddmain.txt&&+test_tick&&+gitcommit-m"Commit E"&&+E=$(gitrev-parseHEAD)&&+test_tick&&+gittag-m"v1"v1HEAD&&+TAG1=$(gitrev-parserefs/tags/v1)&&++# branch topic/2+gitcheckout-btopic/2&&+cat>topic-2.txt<<-EOF&&+CommitF+EOF+gitaddtopic-2.txt&&+test_tick&&+gitcommit-m"Commit F"&&++cat>topic-2.txt<<-EOF&&+CommitG+EOF+gitadd-u&&+test_tick&&+gitcommit-m"Commit G"&&+gitupdate-refrefs/pull/2/headHEAD&&++cat>topic-2.txt<<-EOF&&+CommitH+EOF+gitadd-u&&+test_tick&&+gitcommit-m"Commit H"&&+F=$(gitrev-parsetopic/2~2)&&+G=$(gitrev-parsetopic/2~)&&+H=$(gitrev-parsetopic/2)&&++# merge commit I & J+gitcheckoutmain&&+test_tick&&+gitmerge--no-ff--no-edittopic/1&&+test_tick&&+gitmerge--no-ff--no-editrefs/pull/2/head&&+I=$(gitrev-parseHEAD~)&&+J=$(gitrev-parseHEAD)&&++# commit K+gitcheckoutmain&&+cat>main.txt<<-EOF&&+CommitK+EOF+gitaddmain.txt&&+test_tick&&+gitcommit-m"Commit K"&&+K=$(gitrev-parseHEAD)&&++# branch release+gitcheckout-brelease&&+cat>release.txt<<-EOF&&+CommitL+EOF+gitaddrelease.txt&&+test_tick&&+gitcommit-m"Commit L"&&++cat>release.txt<<-EOF&&+CommitM+EOF+gitadd-u&&+test_tick&&+gitcommit-m"Commit M"&&+test_tick&&+gittag-m"v2"v2HEAD&&++cat>release.txt<<-EOF&&+CommitN+EOF+gitadd-u&&+test_tick&&+gitcommit-m"Commit N"&&+test_tick&&+gittag-m"v3"v3HEAD&&+L=$(gitrev-parseHEAD~2)&&+M=$(gitrev-parseHEAD~)&&+N=$(gitrev-parseHEAD)&&+TAG2=$(gitrev-parserefs/tags/v2)&&+TAG3=$(gitrev-parserefs/tags/v3)&&++# merge commit O+gitcheckoutmain&&+test_tick&&+gitmerge--no-ff--no-edittags/v2&&+O=$(gitrev-parseHEAD)&&++# commit P+gitcheckoutmain&&+cat>main.txt<<-EOF&&+CommitP+EOF+gitaddmain.txt&&+test_tick&&+gitcommit-m"Commit P"&&+P=$(gitrev-parseHEAD)+'++test_expect_success'create bundle from special rev: main^!''+gitbundlecreatespecial-rev.bdl"main^!"&&++gitbundlelist-headsspecial-rev.bdl|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-P>refs/heads/main+EOF+test_i18ncmpexpectactual&&++gitbundleverifyspecial-rev.bdl|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+Thebundlecontainsthisref:+<COMMIT-P>refs/heads/main+Thebundlerequiresthisref:+<COMMIT-O>+EOF+test_i18ncmpexpectactual&&++test_bundle_object_countspecial-rev.bdl3+'++test_expect_success'create bundle with --max-count option''+gitbundlecreatemax-count.bdl--max-count1\+main\+"^release"\+refs/tags/v1\+refs/pull/1/head\+refs/pull/2/head&&++gitbundlelist-headsmax-count.bdl|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-P>refs/heads/main+<TAG-1>refs/tags/v1+EOF+test_i18ncmpexpectactual&&++gitbundleverifymax-count.bdl|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+Thebundlecontainsthese2refs:+<COMMIT-P>refs/heads/main+<TAG-1>refs/tags/v1+Thebundlerequiresthisref:+<COMMIT-O>+EOF+test_i18ncmpexpectactual&&++test_bundle_object_countmax-count.bdl4+'++test_expect_success'create bundle with --since option''+gitbundlecreatesince.bdl\+--since"Thu Apr 7 15:26:13 2005 -0700"\+--all&&++gitbundlelist-headssince.bdl|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-P>refs/heads/main+<COMMIT-N>refs/heads/release+<TAG-2>refs/tags/v2+<TAG-3>refs/tags/v3+<COMMIT-P>HEAD+EOF+test_i18ncmpexpectactual&&++gitbundleverifysince.bdl|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+Thebundlecontainsthese5refs:+<COMMIT-P>refs/heads/main+<COMMIT-N>refs/heads/release+<TAG-2>refs/tags/v2+<TAG-3>refs/tags/v3+<COMMIT-P>HEAD+Thebundlerequiresthese2refs:+<COMMIT-L>+<COMMIT-K>+EOF+test_i18ncmpexpectactual&&++test_thin_bundle_object_countsince.bdl16+'++test_expect_success'create bundle 1 - no prerequisites''+gitbundlecreate1.bdltopic/1topic/2&&++cat>expect<<-EOF&&+Thebundlecontainsthese2refs:+<COMMIT-D>refs/heads/topic/1+<COMMIT-H>refs/heads/topic/2+Thebundlerecordsacompletehistory.+EOF++# verify bundle, which has no prerequisites+gitbundleverify1.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_bundle_object_count1.bdl24+'++test_expect_success'create bundle 2 - has prerequisites''+gitbundlecreate2.bdl\+--ignore-missing\+^topic/deleted\+^$D\+^topic/2\+release&&++cat>expect<<-EOF&&+Thebundlecontainsthisref:+<COMMIT-N>refs/heads/release+Thebundlerequiresthese3refs:+<COMMIT-D>+<COMMIT-E>+<COMMIT-G>+EOF++gitbundleverify2.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_bundle_object_count2.bdl16+'++test_expect_success'fail to verify bundle without prerequisites''+gitinit--baretest1.git&&++cat>expect<<-EOF&&+error:Repositorylackstheseprerequisitecommits:+error:<COMMIT-D>+error:<COMMIT-E>+error:<COMMIT-G>+EOF++test_must_failgit-Ctest1.gitbundleverify../2.bdl2>&1|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual+'++test_expect_success'create bundle 3 - two refs, same object''+gitbundlecreate--version=33.bdl\+^release\+^topic/1\+^topic/2\+main\+HEAD&&++cat>expect<<-EOF&&+Thebundlecontainsthese2refs:+<COMMIT-P>refs/heads/main+<COMMIT-P>HEAD+Thebundlerequiresthese2refs:+<COMMIT-M>+<COMMIT-K>+EOF++gitbundleverify3.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_bundle_object_count3.bdl4+'++test_expect_success'create bundle 4 - with tags''+gitbundlecreate4.bdl\+^main\+^release\+^topic/1\+^topic/2\+--all&&++cat>expect<<-EOF&&+Thebundlecontainsthese3refs:+<TAG-1>refs/tags/v1+<TAG-2>refs/tags/v2+<TAG-3>refs/tags/v3+Thebundlerecordsacompletehistory.+EOF++gitbundleverify4.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_bundle_object_count4.bdl3+'++test_expect_success'clone from bundle''+gitclone--mirror1.bdlmirror.git&&+git-Cmirror.gitshow-ref|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-D>refs/heads/topic/1+<COMMIT-H>refs/heads/topic/2+EOF+test_cmpexpectactual&&++git-Cmirror.gitfetch../2.bdl"+refs/*:refs/*"&&+git-Cmirror.gitshow-ref|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-N>refs/heads/release+<COMMIT-D>refs/heads/topic/1+<COMMIT-H>refs/heads/topic/2+EOF+test_cmpexpectactual&&++git-Cmirror.gitfetch../3.bdl"+refs/*:refs/*"&&+git-Cmirror.gitshow-ref|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-P>refs/heads/main+<COMMIT-N>refs/heads/release+<COMMIT-D>refs/heads/topic/1+<COMMIT-H>refs/heads/topic/2+EOF+test_cmpexpectactual&&++git-Cmirror.gitfetch../4.bdl"+refs/*:refs/*"&&+git-Cmirror.gitshow-ref|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-P>refs/heads/main+<COMMIT-N>refs/heads/release+<COMMIT-D>refs/heads/topic/1+<COMMIT-H>refs/heads/topic/2+<TAG-1>refs/tags/v1+<TAG-2>refs/tags/v2+<TAG-3>refs/tags/v3+EOF+test_cmpexpectactual+'++test_done
From: Đoàn Trần Công Danh <hidden> Date: 2021-01-07 15:38:02
On 2021-01-07 08:50:24-0500, Jiang Xin [off-list ref] wrote:
quoted hunk
From: Jiang Xin <redacted>
`git rev-list` will list one commit for the following command:
$ git rev-list 'main^!'
<tip-commit-of-main-branch>
But providing the same rev-list args to `git bundle`, fail to create
a bundle file.
$ git bundle create - 'main^!'
# v2 git bundle
-<OID> <one-line-message>
fatal: Refusing to create empty bundle.
This is because when removing duplicate objects in function
`object_array_remove_duplicates()`, one unique pending object which has
the same name is deleted by mistake. The revision arg 'main^!' in the
above example is parsed by `handle_revision_arg()`, and at lease two
different objects will be appended to `revs.pending`, one points to the
parent commit of the "main" branch, and the other points to the tip
commit of the "main" branch. These two objects have the same name
"main". Only one object is left with the name "main" after calling the
function `object_array_remove_duplicates()`.
And what's worse, when adding boundary commits into pending list, we use
one-line commit message as names, and the arbitory names may surprise
git-bundle.
Only comparing objects themselves (".item") is also not good enough,
because user may want to create a bundle with two identical objects but
with different reference names, such as: "HEAD" and "refs/heads/main".
Add new function `contains_object()` which compare both the address and
the name of the object.
Signed-off-by: Jiang Xin <redacted>
---
object.c | 10 +-
t/t6020-bundle-misc.sh | 488 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 494 insertions(+), 4 deletions(-)
create mode 100755 t/t6020-bundle-misc.sh
I think the comparison of `item == object->item` is a bit too fragile.
Yes, we all know `item` must be an entry of array.
However, it's separated from its usage may lead to misuse in the
future. Perhaps using `oidcmp` or adding a comment to indicate that
`item` must be an entry of `array`.
quoted hunk
return 1;
return 0;
}
@@ -432,7 +433,8 @@ void object_array_remove_duplicates(struct object_array *array) array->nr = 0; for (src = 0; src < nr; src++) {- if (!contains_name(array, objects[src].name)) {+ if (!contains_object(array, objects[src].item,+ objects[src].name)) { if (src != array->nr) objects[array->nr] = objects[src]; array->nr++;
I think we can use 'grep -c' instead of `grep .. | wc -l`.
Or
grep "^$OID_REGEX " verify.out >verify.filtered &&
test_line_count = $2 verify.filtered
The same comment applied to test_thin_bundle_object_count
+ test $2 = $count && return 0
+ echo object count for $bundle is $count, not $2
+ return 1
+}
+
+
+test_thin_bundle_object_count () {
+ bundle=$1 &&
+ pack=${bundle%.bdl}.pack &&
+ convert_bundle_to_pack <"$bundle" |
+ test_must_fail git index-pack --stdin "$pack" &&
+ rm -f "$pack" &&
+ convert_bundle_to_pack <"$bundle" |
+ git index-pack --stdin --fix-thin "$pack" &&
+ git verify-pack -v "$pack" >verify.out &&
+ count=$(grep "^$OID_REGEX " verify.out | wc -l) &&
+ test $2 = $count && return 0
+ echo object count for $bundle is $count, not $2
+ return 1
+}
+
+convert_bundle_to_pack () {
+ while read x && test -n "$x"
+ do
+ :;
+ done
+ cat
I'm not sure what you would expect in this case,
but in my experience, I replace this whole block with
sed '1,/^$/d'
also works.
IOW, I would apply this on top of your change:
----8<-----
@@ -32,18 +30,12 @@ test_thin_bundle_object_count () {convert_bundle_to_pack<"$bundle"|gitindex-pack--stdin--fix-thin"$pack"&&gitverify-pack-v"$pack">verify.out&&-count=$(grep"^$OID_REGEX "verify.out|wc-l)&&-test$2=$count&&return0-echoobjectcountfor$bundleis$count,not$2-return1+grep"^$OID_REGEX "verify.out>verify.filtered&&+test_line_count=$2verify.filtered} convert_bundle_to_pack(){-whilereadx&&test-n"$x"-do-:;-done-cat+sed'1,/^$/d'}# Format the output of git commands to make a user-friendly and stable----->8-----
For the below change, I haven't checked but I think test_commit should work, no?
-- Danh
Đoàn Trần Công Danh [off-list ref] 于2021年1月7日周四 下午11:37写道:
quoted
-static int contains_name(struct object_array *array, const char *name)
+static int contains_object(struct object_array *array,
+ const struct object *item, const char *name)
{
unsigned nr = array->nr, i;
struct object_array_entry *object = array->objects;
for (i = 0; i < nr; i++, object++)
- if (!strcmp(object->name, name))
+ if (item == object->item && !strcmp(object->name, name))
I think the comparison of `item == object->item` is a bit too fragile.
Yes, we all know `item` must be an entry of array.
However, it's separated from its usage may lead to misuse in the
future. Perhaps using `oidcmp` or adding a comment to indicate that
`item` must be an entry of `array`.
I think we can use 'grep -c' instead of `grep .. | wc -l`.
This function is borrowed from `t5510-fetch.sh`, and will change like
this. Thanks.
quoted
+
+convert_bundle_to_pack () {
+ while read x && test -n "$x"
+ do
+ :;
+ done
+ cat
I'm not sure what you would expect in this case,
but in my experience, I replace this whole block with
sed '1,/^$/d'
This function is used to convert bundle file to pack file by strip the
header, which has a signature, prerequisites, references. This
function is also borrowed from "t5510-fetch.sh".
For the below change, I haven't checked but I think test_commit should work, no?
I should refactor these code, but I forgot. After examine the
`test_commit` function, I have to write a new helper, because it does
not meet my needs.
1. It should not create a tag every time.
2. The tag it created is not an annotated tag.
3. I need to store the object id to a variable.
So I write a new helper `test_commit_setvar()` in next reroll. And
make this testcase much simpler.
test_expect_success 'setup' '
# branch main: commit A & B
test_commit_setvar A "Commit A" main.txt &&
test_commit_setvar B "Commit B" main.txt &&
# branch topic/1: commit C & D, refs/pull/1/head
git checkout -b topic/1 &&
test_commit_setvar C "Commit C" topic-1.txt &&
test_commit_setvar D "Commit D" topic-1.txt &&
git update-ref refs/pull/1/head HEAD &&
# branch topic/1: commit E, tag v1
git checkout main &&
test_commit_setvar E "Commit E" main.txt &&
test_commit_setvar TAG1 --tag v1 &&
# branch topic/2: commit F & G, refs/pull/2/head
git checkout -b topic/2 &&
test_commit_setvar F "Commit F" topic-2.txt &&
test_commit_setvar G "Commit G" topic-2.txt &&
git update-ref refs/pull/2/head HEAD &&
test_commit_setvar H "Commit H" topic-2.txt &&
# branch main: merge commit I & J
git checkout main &&
test_tick &&
test_commit_setvar I --merge topic/1 "Merge commit I" &&
test_commit_setvar J --merge refs/pull/2/head "Merge commit J" &&
# branch main: commit K
git checkout main &&
test_commit_setvar K "Commit K" main.txt &&
# branch release:
git checkout -b release &&
test_commit_setvar L "Commit L" release.txt &&
test_commit_setvar M "Commit M" release.txt &&
test_commit_setvar TAG2 --tag v2 &&
test_commit_setvar N "Commit N" release.txt &&
test_commit_setvar TAG3 --tag v3 &&
# branch main: merge commit O, commit P
git checkout main &&
test_commit_setvar O --merge tags/v2 "Merge commit O" &&
test_commit_setvar P "Commit P" main.txt
'
--
Jiang Xin
From: Jiang Xin <redacted>
In order to create an incremental bundle, we need to pass many arguments
to let git-bundle ignore some already packed commits. It will be more
convenient to pass args via stdin. But the current implementation does
not allow us to do this.
This is because args are parsed twice when creating bundle. The first
time for parsing args is in `compute_and_write_prerequisites()` by
running `git-rev-list` command to write prerequisites in bundle file,
and stdin is consumed in this step if "--stdin" option is provided for
`git-bundle`. Later nothing can be read from stdin when running
`setup_revisions()` in `create_bundle()`.
The solution is to parse args once by removing the entire function
`compute_and_write_prerequisites()` and then calling function
`setup_revisions()`. In order to write prerequisites for bundle, will
call `prepare_revision_walk()` and `traverse_commit_list()`. But after
calling `prepare_revision_walk()`, the object array `revs.pending` is
left empty, and the following steps could not work properly with the
empty object array (`revs.pending`). Therefore, make a copy of `revs`
to `revs_copy` for later use right after calling `setup_revisions()`.
The copy of `revs_copy` is not a deep copy, it shares the same objects
with `revs`. The object array of `revs` has been cleared, but objects
themselves are still kept. Flags of objects may change after calling
`prepare_revision_walk()`, we can use these changed flags without
calling the `git rev-list` command and parsing its output like the
former implementation.
Also add testcases for git bundle in t6020, which read args from stdin.
Signed-off-by: Jiang Xin <redacted>
---
bundle.c | 111 ++++++++++++++++++++++------------------
t/t5607-clone-bundle.sh | 4 +-
t/t6020-bundle-misc.sh | 77 ++++++++++++++++++++++++++--
3 files changed, 135 insertions(+), 57 deletions(-)
@@ -425,7 +383,7 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs)*constraints.*/if(!(e->item->flags&SHOWN)&&e->item->type==OBJ_COMMIT){-warning(_("ref '%s' is excluded by the rev-list options"),+warning(_("ref '%s' is excluded by the limiting options"),e->name);gotoskip_write_ref;}
@@ -523,16 +511,37 @@ int create_bundle(struct repository *r, const char *path,gotoerr;}-object_array_remove_duplicates(&revs.pending);+/* save revs.pending in revs_copy for later use */+memcpy(&revs_copy,&revs,sizeof(revs));+revs_copy.pending.nr=0;+revs_copy.pending.alloc=0;+revs_copy.pending.objects=NULL;+for(i=0;i<revs.pending.nr;i++){+structobject_array_entry*e=revs.pending.objects+i;+if(e)+add_object_array_with_path(e->item,e->name,+&revs_copy.pending,+e->mode,e->path);+}-ref_count=write_bundle_refs(bundle_fd,&revs);+/* write prerequisites */+revs.boundary=1;+if(prepare_revision_walk(&revs))+die("revision walk setup failed");+bpi.fd=bundle_fd;+bpi.pending=&revs_copy.pending;+traverse_commit_list(&revs,write_bundle_prerequisites,NULL,&bpi);+object_array_remove_duplicates(&revs_copy.pending);++/* write bundle refs */+ref_count=write_bundle_refs(bundle_fd,&revs_copy);if(!ref_count)die(_("Refusing to create empty bundle."));elseif(ref_count<0)gotoerr;/* write pack */-if(write_pack_data(bundle_fd,&revs,pack_options))+if(write_pack_data(bundle_fd,&revs_copy,pack_options))gotoerr;if(!bundle_to_stdout){
@@ -325,8 +325,16 @@ test_expect_success 'create bundle with --since option' '' test_expect_success'create bundle 1 - no prerequisites''+# create bundle from argsgitbundlecreate1.bdltopic/1topic/2&&+# create bundle from stdin+cat>input<<-EOF&&+topic/1+topic/2+EOF+gitbundlecreatestdin-1.bdl--stdin<input&&+cat>expect<<-EOF&&Thebundlecontainsthese2refs:<COMMIT-D>refs/heads/topic/1
@@ -339,10 +347,16 @@ test_expect_success 'create bundle 1 - no prerequisites' 'make_user_friendly_and_stable_output>actual&&test_i18ncmpexpectactual&&-test_bundle_object_count1.bdl24+gitbundleverifystdin-1.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_bundle_object_count1.bdl24&&+test_bundle_object_countstdin-1.bdl24' test_expect_success'create bundle 2 - has prerequisites''+# create bundle from argsgitbundlecreate2.bdl\--ignore-missing\^topic/deleted\
@@ -350,6 +364,18 @@ test_expect_success 'create bundle 2 - has prerequisites' '^topic/2\release&&+# create bundle from stdin+# input has a non-exist reference: "topic/deleted"+cat>input<<-EOF&&+^topic/deleted+^$D+^topic/2+EOF+gitbundlecreatestdin-2.bdl\+--ignore-missing\+--stdin\+release<input&&+cat>expect<<-EOF&&Thebundlecontainsthisref:<COMMIT-N>refs/heads/release
@@ -363,7 +389,12 @@ test_expect_success 'create bundle 2 - has prerequisites' 'make_user_friendly_and_stable_output>actual&&test_i18ncmpexpectactual&&-test_bundle_object_count2.bdl16+gitbundleverifystdin-2.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_bundle_object_count2.bdl16&&+test_bundle_object_countstdin-2.bdl16' test_expect_success'fail to verify bundle without prerequisites''
@@ -378,10 +409,15 @@ test_expect_success 'fail to verify bundle without prerequisites' 'test_must_failgit-Ctest1.gitbundleverify../2.bdl2>&1|make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_must_failgit-Ctest1.gitbundleverify../stdin-2.bdl2>&1|+make_user_friendly_and_stable_output>actual&&test_i18ncmpexpectactual' test_expect_success'create bundle 3 - two refs, same object''+# create bundle from argsgitbundlecreate--version=33.bdl\^release\^topic/1\
@@ -389,6 +425,16 @@ test_expect_success 'create bundle 3 - two refs, same object' 'main\HEAD&&+# create bundle from stdin+cat>input<<-EOF&&+^release+^topic/1+^topic/2+EOF+gitbundlecreate--version=3stdin-3.bdl\+--stdin\+mainHEAD<input&&+cat>expect<<-EOF&&Thebundlecontainsthese2refs:<COMMIT-P>refs/heads/main
@@ -402,10 +448,16 @@ test_expect_success 'create bundle 3 - two refs, same object' 'make_user_friendly_and_stable_output>actual&&test_i18ncmpexpectactual&&-test_bundle_object_count3.bdl4+gitbundleverifystdin-3.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_bundle_object_count3.bdl4&&+test_bundle_object_countstdin-3.bdl4' test_expect_success'create bundle 4 - with tags''+# create bundle from argsgitbundlecreate4.bdl\^main\^release\
@@ -413,6 +465,18 @@ test_expect_success 'create bundle 4 - with tags' '^topic/2\--all&&+# create bundle from stdin+cat>input<<-EOF&&+^main+^release+^topic/1+^topic/2+EOF+gitbundlecreatestdin-4.bdl\+--ignore-missing\+--stdin\+--all<input&&+cat>expect<<-EOF&&Thebundlecontainsthese3refs:<TAG-1>refs/tags/v1
@@ -425,7 +489,12 @@ test_expect_success 'create bundle 4 - with tags' 'make_user_friendly_and_stable_output>actual&&test_i18ncmpexpectactual&&-test_bundle_object_count4.bdl3+gitbundleverifystdin-4.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_bundle_object_count4.bdl3&&+test_bundle_object_countstdin-4.bdl3' test_expect_success'clone from bundle''
From: Jiang Xin <redacted>
`git rev-list` will list one commit for the following command:
$ git rev-list 'main^!'
<tip-commit-of-main-branch>
But providing the same rev-list args to `git bundle`, fail to create
a bundle file.
$ git bundle create - 'main^!'
# v2 git bundle
-<OID> <one-line-message>
fatal: Refusing to create empty bundle.
This is because when removing duplicate objects in function
`object_array_remove_duplicates()`, one unique pending object which has
the same name is deleted by mistake. The revision arg 'main^!' in the
above example is parsed by `handle_revision_arg()`, and at lease two
different objects will be appended to `revs.pending`, one points to the
parent commit of the "main" branch, and the other points to the tip
commit of the "main" branch. These two objects have the same name
"main". Only one object is left with the name "main" after calling the
function `object_array_remove_duplicates()`.
And what's worse, when adding boundary commits into pending list, we use
one-line commit message as names, and the arbitory names may surprise
git-bundle.
Only comparing objects themselves (".item") is also not good enough,
because user may want to create a bundle with two identical objects but
with different reference names, such as: "HEAD" and "refs/heads/main".
Add new function `contains_object()` which compare both the address and
the name of the object.
Signed-off-by: Jiang Xin <redacted>
---
object.c | 10 +-
t/t6020-bundle-misc.sh | 477 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 483 insertions(+), 4 deletions(-)
create mode 100755 t/t6020-bundle-misc.sh
@@ -0,0 +1,477 @@+#!/bin/sh+#+# Copyright (c) 2021 Jiang Xin+#++test_description='Test git-bundle'++GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main+exportGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME++../test-lib.sh++# Check count of objects in a bundle file.+# We can use "--thin" opiton to check thin pack, which must be fixed by+# command `git-index-pack --fix-thin --stdin`.+test_bundle_object_count(){+thin=&&+iftest"$1"="--thin"+then+thin=yes+shift+fi&&+iftest$#-ne2+then+echo>&2"args should be: <bundle> <count>"+return1+fi+bundle=$1&&+pack=${bundle%.bdl}.pack&&+convert_bundle_to_pack<"$bundle">"$pack"&&+iftest-n"$thin"+then+test_must_failgitindex-pack"$pack"&&+mv"$pack""$pack"-thin&&+cat"$pack"-thin|+gitindex-pack--stdin--fix-thin"$pack"+else+gitindex-pack"$pack"+fi&&+gitverify-pack-v"$pack">verify.out+iftest$?-ne0+then+echo>&2"error: fail to convert $bundle to $pack"+return1+fi+count=$(grep-c"^$OID_REGEX "verify.out)&&+test$2=$count&&return0+echo>&2"error: object count for $bundle is $count, not $2"+return1+}++# Display the pack data contained in the bundle file, bypassing the+# header that contains the signature, prerequisites and references.+convert_bundle_to_pack(){+whilereadx&&test-n"$x"+do+:;+done+cat+}++# Create a commit or tag and set the variable with the object ID.+test_commit_setvar(){+notick=&&+signoff=&&+indir=&&+merge=&&+tag=&&+var=&&+whiletest$#!=0+do+case"$1"in+--merge)+merge=yes+;;+--tag)+tag=yes+;;+--notick)+notick=yes+;;+--signoff)+signoff="$1"+;;+-C)+indir="$2"+shift+;;+-*)+echo>&2"error: unknown option $1"+return1+;;+*)+test-n"$var"&&break+var=$1+;;+esac+shift+done&&+indir=${indir:+"$indir"/}&&+iftest$#-eq0+then+echo>&2"no args provided"+return1+fi&&+iftest-z"$notick"+then+test_tick+fi&&+iftest-n"$merge"+then+git${indir:+ -C "$indir"}merge--no-edit--no-ff\+${2:+-m "$2"}"$1"&&+oid=$(git${indir:+ -C "$indir"}rev-parseHEAD)+eliftest-n"$tag"+then+git${indir:+ -C "$indir"}tag-m"$1""$1"&&+oid=$(git${indir:+ -C "$indir"}rev-parse"$1")+else+file=${2:-"$1.t"}&&+echo"${3-$1}">"$indir$file"&&+git${indir:+ -C "$indir"}add"$file"&&+git${indir:+ -C "$indir"}commit$signoff-m"$1"&&+oid=$(git${indir:+ -C "$indir"}rev-parseHEAD)+fi&&+eval$var=$oid+}+++# Format the output of git commands to make a user-friendly and stable+# text. We can easily prepare the expect text without having to worry+# about future changes of the commit ID and spaces of the output.+make_user_friendly_and_stable_output(){+sed\+-e"s/$A/<COMMIT-A>/"\+-e"s/$B/<COMMIT-B>/"\+-e"s/$C/<COMMIT-C>/"\+-e"s/$D/<COMMIT-D>/"\+-e"s/$E/<COMMIT-E>/"\+-e"s/$F/<COMMIT-F>/"\+-e"s/$G/<COMMIT-G>/"\+-e"s/$H/<COMMIT-H>/"\+-e"s/$I/<COMMIT-I>/"\+-e"s/$J/<COMMIT-J>/"\+-e"s/$K/<COMMIT-K>/"\+-e"s/$L/<COMMIT-L>/"\+-e"s/$M/<COMMIT-M>/"\+-e"s/$N/<COMMIT-N>/"\+-e"s/$O/<COMMIT-O>/"\+-e"s/$P/<COMMIT-P>/"\+-e"s/$TAG1/<TAG-1>/"\+-e"s/$TAG2/<TAG-2>/"\+-e"s/$TAG3/<TAG-3>/"\+-e"s/$(echo$A|cut-c1-7)[0-9a-f]*/<OID-A>/g"\+-e"s/$(echo$B|cut-c1-7)[0-9a-f]*/<OID-B>/g"\+-e"s/$(echo$C|cut-c1-7)[0-9a-f]*/<OID-C>/g"\+-e"s/$(echo$D|cut-c1-7)[0-9a-f]*/<OID-D>/g"\+-e"s/$(echo$E|cut-c1-7)[0-9a-f]*/<OID-E>/g"\+-e"s/$(echo$F|cut-c1-7)[0-9a-f]*/<OID-F>/g"\+-e"s/$(echo$G|cut-c1-7)[0-9a-f]*/<OID-G>/g"\+-e"s/$(echo$H|cut-c1-7)[0-9a-f]*/<OID-H>/g"\+-e"s/$(echo$I|cut-c1-7)[0-9a-f]*/<OID-I>/g"\+-e"s/$(echo$J|cut-c1-7)[0-9a-f]*/<OID-J>/g"\+-e"s/$(echo$K|cut-c1-7)[0-9a-f]*/<OID-K>/g"\+-e"s/$(echo$L|cut-c1-7)[0-9a-f]*/<OID-L>/g"\+-e"s/$(echo$M|cut-c1-7)[0-9a-f]*/<OID-M>/g"\+-e"s/$(echo$N|cut-c1-7)[0-9a-f]*/<OID-N>/g"\+-e"s/$(echo$O|cut-c1-7)[0-9a-f]*/<OID-O>/g"\+-e"s/$(echo$P|cut-c1-7)[0-9a-f]*/<OID-P>/g"\+-e"s/$(echo$TAG1|cut-c1-7)[0-9a-f]*/<OID-TAG-1>/g"\+-e"s/$(echo$TAG2|cut-c1-7)[0-9a-f]*/<OID-TAG-2>/g"\+-e"s/$(echo$TAG3|cut-c1-7)[0-9a-f]*/<OID-TAG-3>/g"\+-e"s/ *\$//"+}++# (C) (D, pull/1/head, topic/1)+# o --- o+# / \ (L)+# / \ o (H, topic/2) (M, tag:v2)+# / (F) \ / (N, tag:v3)+# / o --------- o (G, pull/2/head) o --- o --- o (release)+# / / \ \ / \+# o --- o --- o -------- o -- o ------------------ o ------- o --- o (main)+# (A) (B) (E, tag:v1) (I) (J) (K) (O) (P)+#+test_expect_success'setup''+# Try to make a stable fixed width for abbreviated commit ID,+# this fixed-width oid will be replaced with "<OID>".+gitconfigcore.abbrev7&&++# branch main: commit A & B+test_commit_setvarA"Commit A"main.txt&&+test_commit_setvarB"Commit B"main.txt&&++# branch topic/1: commit C & D, refs/pull/1/head+gitcheckout-btopic/1&&+test_commit_setvarC"Commit C"topic-1.txt&&+test_commit_setvarD"Commit D"topic-1.txt&&+gitupdate-refrefs/pull/1/headHEAD&&++# branch topic/1: commit E, tag v1+gitcheckoutmain&&+test_commit_setvarE"Commit E"main.txt&&+test_commit_setvarTAG1--tagv1&&++# branch topic/2: commit F & G, refs/pull/2/head+gitcheckout-btopic/2&&+test_commit_setvarF"Commit F"topic-2.txt&&+test_commit_setvarG"Commit G"topic-2.txt&&+gitupdate-refrefs/pull/2/headHEAD&&+test_commit_setvarH"Commit H"topic-2.txt&&++# branch main: merge commit I & J+gitcheckoutmain&&+test_commit_setvarI--mergetopic/1"Merge commit I"&&+test_commit_setvarJ--mergerefs/pull/2/head"Merge commit J"&&++# branch main: commit K+gitcheckoutmain&&+test_commit_setvarK"Commit K"main.txt&&++# branch release:+gitcheckout-brelease&&+test_commit_setvarL"Commit L"release.txt&&+test_commit_setvarM"Commit M"release.txt&&+test_commit_setvarTAG2--tagv2&&+test_commit_setvarN"Commit N"release.txt&&+test_commit_setvarTAG3--tagv3&&++# branch main: merge commit O, commit P+gitcheckoutmain&&+test_commit_setvarO--mergetags/v2"Merge commit O"&&+test_commit_setvarP"Commit P"main.txt+'++test_expect_success'create bundle from special rev: main^!''+gitbundlecreatespecial-rev.bdl"main^!"&&++gitbundlelist-headsspecial-rev.bdl|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-P>refs/heads/main+EOF+test_i18ncmpexpectactual&&++gitbundleverifyspecial-rev.bdl|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+Thebundlecontainsthisref:+<COMMIT-P>refs/heads/main+Thebundlerequiresthisref:+<COMMIT-O>+EOF+test_i18ncmpexpectactual&&++test_bundle_object_countspecial-rev.bdl3+'++test_expect_success'create bundle with --max-count option''+gitbundlecreatemax-count.bdl--max-count1\+main\+"^release"\+refs/tags/v1\+refs/pull/1/head\+refs/pull/2/head&&++gitbundlelist-headsmax-count.bdl|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-P>refs/heads/main+<TAG-1>refs/tags/v1+EOF+test_i18ncmpexpectactual&&++gitbundleverifymax-count.bdl|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+Thebundlecontainsthese2refs:+<COMMIT-P>refs/heads/main+<TAG-1>refs/tags/v1+Thebundlerequiresthisref:+<COMMIT-O>+EOF+test_i18ncmpexpectactual&&++test_bundle_object_countmax-count.bdl4+'++test_expect_success'create bundle with --since option''+since="Thu Apr 7 15:26:13 2005 -0700"&&+gitlog-1--pretty="%ad"$M>actual&&+echo"$since">expect&&+test_cmpexpectactual&&++gitbundlecreatesince.bdl\+--since"$since"--all&&++gitbundlelist-headssince.bdl|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-P>refs/heads/main+<COMMIT-N>refs/heads/release+<TAG-2>refs/tags/v2+<TAG-3>refs/tags/v3+<COMMIT-P>HEAD+EOF+test_i18ncmpexpectactual&&++gitbundleverifysince.bdl|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+Thebundlecontainsthese5refs:+<COMMIT-P>refs/heads/main+<COMMIT-N>refs/heads/release+<TAG-2>refs/tags/v2+<TAG-3>refs/tags/v3+<COMMIT-P>HEAD+Thebundlerequiresthese2refs:+<COMMIT-L>+<COMMIT-K>+EOF+test_i18ncmpexpectactual&&++test_bundle_object_count--thinsince.bdl16+'++test_expect_success'create bundle 1 - no prerequisites''+gitbundlecreate1.bdltopic/1topic/2&&++cat>expect<<-EOF&&+Thebundlecontainsthese2refs:+<COMMIT-D>refs/heads/topic/1+<COMMIT-H>refs/heads/topic/2+Thebundlerecordsacompletehistory.+EOF++# verify bundle, which has no prerequisites+gitbundleverify1.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_bundle_object_count1.bdl24+'++test_expect_success'create bundle 2 - has prerequisites''+gitbundlecreate2.bdl\+--ignore-missing\+^topic/deleted\+^$D\+^topic/2\+release&&++cat>expect<<-EOF&&+Thebundlecontainsthisref:+<COMMIT-N>refs/heads/release+Thebundlerequiresthese3refs:+<COMMIT-D>+<COMMIT-E>+<COMMIT-G>+EOF++gitbundleverify2.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_bundle_object_count2.bdl16+'++test_expect_success'fail to verify bundle without prerequisites''+gitinit--baretest1.git&&++cat>expect<<-EOF&&+error:Repositorylackstheseprerequisitecommits:+error:<COMMIT-D>+error:<COMMIT-E>+error:<COMMIT-G>+EOF++test_must_failgit-Ctest1.gitbundleverify../2.bdl2>&1|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual+'++test_expect_success'create bundle 3 - two refs, same object''+gitbundlecreate--version=33.bdl\+^release\+^topic/1\+^topic/2\+main\+HEAD&&++cat>expect<<-EOF&&+Thebundlecontainsthese2refs:+<COMMIT-P>refs/heads/main+<COMMIT-P>HEAD+Thebundlerequiresthese2refs:+<COMMIT-M>+<COMMIT-K>+EOF++gitbundleverify3.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_bundle_object_count3.bdl4+'++test_expect_success'create bundle 4 - with tags''+gitbundlecreate4.bdl\+^main\+^release\+^topic/1\+^topic/2\+--all&&++cat>expect<<-EOF&&+Thebundlecontainsthese3refs:+<TAG-1>refs/tags/v1+<TAG-2>refs/tags/v2+<TAG-3>refs/tags/v3+Thebundlerecordsacompletehistory.+EOF++gitbundleverify4.bdl|+make_user_friendly_and_stable_output>actual&&+test_i18ncmpexpectactual&&++test_bundle_object_count4.bdl3+'++test_expect_success'clone from bundle''+gitclone--mirror1.bdlmirror.git&&+git-Cmirror.gitshow-ref|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-D>refs/heads/topic/1+<COMMIT-H>refs/heads/topic/2+EOF+test_cmpexpectactual&&++git-Cmirror.gitfetch../2.bdl"+refs/*:refs/*"&&+git-Cmirror.gitshow-ref|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-N>refs/heads/release+<COMMIT-D>refs/heads/topic/1+<COMMIT-H>refs/heads/topic/2+EOF+test_cmpexpectactual&&++git-Cmirror.gitfetch../3.bdl"+refs/*:refs/*"&&+git-Cmirror.gitshow-ref|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-P>refs/heads/main+<COMMIT-N>refs/heads/release+<COMMIT-D>refs/heads/topic/1+<COMMIT-H>refs/heads/topic/2+EOF+test_cmpexpectactual&&++git-Cmirror.gitfetch../4.bdl"+refs/*:refs/*"&&+git-Cmirror.gitshow-ref|+make_user_friendly_and_stable_output>actual&&+cat>expect<<-EOF&&+<COMMIT-P>refs/heads/main+<COMMIT-N>refs/heads/release+<COMMIT-D>refs/heads/topic/1+<COMMIT-H>refs/heads/topic/2+<TAG-1>refs/tags/v1+<TAG-2>refs/tags/v2+<TAG-3>refs/tags/v3+EOF+test_cmpexpectactual+'++test_done