Merge branch 'xx/bundie-uri-fixes'

When bundleURI interface fetches multiple bundles, Git failed to
take full advantage of all bundles and ended up slurping duplicated
objects.

* xx/bundie-uri-fixes:
  unbundle: extend object verification for fetches
  fetch-pack: expose fsckObjects configuration logic
  bundle-uri: verify oid before writing refs
This commit is contained in:
Junio C Hamano 2024-07-08 14:53:11 -07:00
commit 125e389470
8 changed files with 243 additions and 14 deletions

View File

@ -11,6 +11,7 @@
#include "hashmap.h"
#include "pkt-line.h"
#include "config.h"
#include "fetch-pack.h"
#include "remote.h"
static struct {
@ -375,7 +376,7 @@ static int unbundle_from_file(struct repository *r, const char *file)
* the prerequisite commits.
*/
if ((result = unbundle(r, &header, bundle_fd, NULL,
VERIFY_BUNDLE_QUIET)))
VERIFY_BUNDLE_QUIET | (fetch_pack_fsck_objects() ? VERIFY_BUNDLE_FSCK : 0))))
return 1;
/*
@ -402,8 +403,7 @@ static int unbundle_from_file(struct repository *r, const char *file)
refs_update_ref(get_main_ref_store(the_repository),
"fetched bundle", bundle_ref.buf, oid,
has_old ? &old_oid : NULL,
REF_SKIP_OID_VERIFICATION,
UPDATE_REFS_MSG_ON_ERR);
0, UPDATE_REFS_MSG_ON_ERR);
}
bundle_header_release(&header);

View File

@ -636,6 +636,9 @@ int unbundle(struct repository *r, struct bundle_header *header,
if (header->filter.choice)
strvec_push(&ip.args, "--promisor=from-bundle");
if (flags & VERIFY_BUNDLE_FSCK)
strvec_push(&ip.args, "--fsck-objects");
if (extra_index_pack_args) {
strvec_pushv(&ip.args, extra_index_pack_args->v);
strvec_clear(extra_index_pack_args);

View File

@ -33,6 +33,7 @@ int create_bundle(struct repository *r, const char *path,
enum verify_bundle_flags {
VERIFY_BUNDLE_VERBOSE = (1 << 0),
VERIFY_BUNDLE_QUIET = (1 << 1),
VERIFY_BUNDLE_FSCK = (1 << 2),
};
int verify_bundle(struct repository *r, struct bundle_header *header,

View File

@ -956,12 +956,7 @@ static int get_pack(struct fetch_pack_args *args,
strvec_push(&cmd.args, alternate_shallow_file);
}
if (fetch_fsck_objects >= 0
? fetch_fsck_objects
: transfer_fsck_objects >= 0
? transfer_fsck_objects
: 0)
fsck_objects = 1;
fsck_objects = fetch_pack_fsck_objects();
if (do_keep || args->from_promisor || index_pack_args || fsck_objects) {
if (pack_lockfiles || fsck_objects)
@ -2050,6 +2045,16 @@ static const struct object_id *iterate_ref_map(void *cb_data)
return &ref->old_oid;
}
int fetch_pack_fsck_objects(void)
{
fetch_pack_setup();
if (fetch_fsck_objects >= 0)
return fetch_fsck_objects;
if (transfer_fsck_objects >= 0)
return transfer_fsck_objects;
return 0;
}
struct ref *fetch_pack(struct fetch_pack_args *args,
int fd[],
const struct ref *ref,

View File

@ -101,4 +101,9 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,
*/
int report_unmatched_refs(struct ref **sought, int nr_sought);
/*
* Return true if checks for broken objects in received pack are required.
*/
int fetch_pack_fsck_objects(void);
#endif

View File

@ -3,6 +3,7 @@
test_description='test fetching bundles with --bundle-uri'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-bundle.sh
test_expect_success 'fail to clone from non-existent file' '
test_when_finished rm -rf test &&
@ -19,10 +20,39 @@ test_expect_success 'fail to clone from non-bundle file' '
test_expect_success 'create bundle' '
git init clone-from &&
git -C clone-from checkout -b topic &&
test_commit -C clone-from A &&
test_commit -C clone-from B &&
git -C clone-from bundle create B.bundle topic
(
cd clone-from &&
git checkout -b topic &&
test_commit A &&
git bundle create A.bundle topic &&
test_commit B &&
git bundle create B.bundle topic &&
# Create a bundle with reference pointing to non-existent object.
commit_a=$(git rev-parse A) &&
commit_b=$(git rev-parse B) &&
sed -e "/^$/q" -e "s/$commit_a /$commit_b /" \
<A.bundle >bad-header.bundle &&
convert_bundle_to_pack \
<A.bundle >>bad-header.bundle &&
tree_b=$(git rev-parse B^{tree}) &&
cat >data <<-EOF &&
tree $tree_b
parent $commit_b
author A U Thor
committer A U Thor
commit: this is a commit with bad emails
EOF
bad_commit=$(git hash-object --literally -t commit -w --stdin <data) &&
git branch bad $bad_commit &&
git bundle create bad-object.bundle bad &&
git update-ref -d refs/heads/bad
)
'
test_expect_success 'clone with path bundle' '
@ -33,6 +63,33 @@ test_expect_success 'clone with path bundle' '
test_cmp expect actual
'
test_expect_success 'clone with bundle that has bad header' '
# Write bundle ref fails, but clone can still proceed.
git clone --bundle-uri="clone-from/bad-header.bundle" \
clone-from clone-bad-header 2>err &&
commit_b=$(git -C clone-from rev-parse B) &&
test_grep "trying to write ref '\''refs/bundles/topic'\'' with nonexistent object $commit_b" err &&
git -C clone-bad-header for-each-ref --format="%(refname)" >refs &&
test_grep ! "refs/bundles/" refs
'
test_expect_success 'clone with bundle that has bad object' '
# Unbundle succeeds if no fsckObjects configured.
git clone --bundle-uri="clone-from/bad-object.bundle" \
clone-from clone-bad-object-no-fsck &&
git -C clone-bad-object-no-fsck for-each-ref --format="%(refname)" >refs &&
grep "refs/bundles/" refs >actual &&
test_write_lines refs/bundles/bad >expect &&
test_cmp expect actual &&
# Unbundle fails with fsckObjects set true, but clone can still proceed.
git -c fetch.fsckObjects=true clone --bundle-uri="clone-from/bad-object.bundle" \
clone-from clone-bad-object-fsck 2>err &&
test_grep "missingEmail" err &&
git -C clone-bad-object-fsck for-each-ref --format="%(refname)" >refs &&
test_grep ! "refs/bundles/" refs
'
test_expect_success 'clone with path bundle and non-default hash' '
test_when_finished "rm -rf clone-path-non-default-hash" &&
GIT_DEFAULT_HASH=sha256 git clone --bundle-uri="clone-from/B.bundle" \
@ -259,6 +316,128 @@ test_expect_success 'clone bundle list (file, any mode, all failures)' '
! grep "refs/bundles/" refs
'
test_expect_success 'negotiation: bundle with part of wanted commits' '
test_when_finished "rm -f trace*.txt" &&
GIT_TRACE_PACKET="$(pwd)/trace-packet.txt" \
git clone --no-local --bundle-uri="clone-from/A.bundle" \
clone-from nego-bundle-part &&
git -C nego-bundle-part for-each-ref --format="%(refname)" >refs &&
grep "refs/bundles/" refs >actual &&
test_write_lines refs/bundles/topic >expect &&
test_cmp expect actual &&
# Ensure that refs/bundles/topic are sent as "have".
tip=$(git -C clone-from rev-parse A) &&
test_grep "clone> have $tip" trace-packet.txt
'
test_expect_success 'negotiation: bundle with all wanted commits' '
test_when_finished "rm -f trace*.txt" &&
GIT_TRACE_PACKET="$(pwd)/trace-packet.txt" \
git clone --no-local --single-branch --branch=topic --no-tags \
--bundle-uri="clone-from/B.bundle" \
clone-from nego-bundle-all &&
git -C nego-bundle-all for-each-ref --format="%(refname)" >refs &&
grep "refs/bundles/" refs >actual &&
test_write_lines refs/bundles/topic >expect &&
test_cmp expect actual &&
# We already have all needed commits so no "want" needed.
test_grep ! "clone> want " trace-packet.txt
'
test_expect_success 'negotiation: bundle list (no heuristic)' '
test_when_finished "rm -f trace*.txt" &&
cat >bundle-list <<-EOF &&
[bundle]
version = 1
mode = all
[bundle "bundle-1"]
uri = file://$(pwd)/clone-from/bundle-1.bundle
[bundle "bundle-2"]
uri = file://$(pwd)/clone-from/bundle-2.bundle
EOF
GIT_TRACE_PACKET="$(pwd)/trace-packet.txt" \
git clone --no-local --bundle-uri="file://$(pwd)/bundle-list" \
clone-from nego-bundle-list-no-heuristic &&
git -C nego-bundle-list-no-heuristic for-each-ref --format="%(refname)" >refs &&
grep "refs/bundles/" refs >actual &&
cat >expect <<-\EOF &&
refs/bundles/base
refs/bundles/left
EOF
test_cmp expect actual &&
tip=$(git -C nego-bundle-list-no-heuristic rev-parse refs/bundles/left) &&
test_grep "clone> have $tip" trace-packet.txt
'
test_expect_success 'negotiation: bundle list (creationToken)' '
test_when_finished "rm -f trace*.txt" &&
cat >bundle-list <<-EOF &&
[bundle]
version = 1
mode = all
heuristic = creationToken
[bundle "bundle-1"]
uri = file://$(pwd)/clone-from/bundle-1.bundle
creationToken = 1
[bundle "bundle-2"]
uri = file://$(pwd)/clone-from/bundle-2.bundle
creationToken = 2
EOF
GIT_TRACE_PACKET="$(pwd)/trace-packet.txt" \
git clone --no-local --bundle-uri="file://$(pwd)/bundle-list" \
clone-from nego-bundle-list-heuristic &&
git -C nego-bundle-list-heuristic for-each-ref --format="%(refname)" >refs &&
grep "refs/bundles/" refs >actual &&
cat >expect <<-\EOF &&
refs/bundles/base
refs/bundles/left
EOF
test_cmp expect actual &&
tip=$(git -C nego-bundle-list-heuristic rev-parse refs/bundles/left) &&
test_grep "clone> have $tip" trace-packet.txt
'
test_expect_success 'negotiation: bundle list with all wanted commits' '
test_when_finished "rm -f trace*.txt" &&
cat >bundle-list <<-EOF &&
[bundle]
version = 1
mode = all
heuristic = creationToken
[bundle "bundle-1"]
uri = file://$(pwd)/clone-from/bundle-1.bundle
creationToken = 1
[bundle "bundle-2"]
uri = file://$(pwd)/clone-from/bundle-2.bundle
creationToken = 2
EOF
GIT_TRACE_PACKET="$(pwd)/trace-packet.txt" \
git clone --no-local --single-branch --branch=left --no-tags \
--bundle-uri="file://$(pwd)/bundle-list" \
clone-from nego-bundle-list-all &&
git -C nego-bundle-list-all for-each-ref --format="%(refname)" >refs &&
grep "refs/bundles/" refs >actual &&
cat >expect <<-\EOF &&
refs/bundles/base
refs/bundles/left
EOF
test_cmp expect actual &&
# We already have all needed commits so no "want" needed.
test_grep ! "clone> want " trace-packet.txt
'
#########################################################################
# HTTP tests begin here

View File

@ -139,6 +139,41 @@ test_expect_success 'fetch SHA-1 from bundle' '
git fetch --no-tags foo/tip.bundle "$(cat hash)"
'
test_expect_success 'clone bundle with different fsckObjects configurations' '
test_create_repo bundle-fsck &&
(
cd bundle-fsck &&
test_commit A &&
commit_a=$(git rev-parse A) &&
tree_a=$(git rev-parse A^{tree}) &&
cat >data <<-EOF &&
tree $tree_a
parent $commit_a
author A U Thor
committer A U Thor
commit: this is a commit with bad emails
EOF
bad_commit=$(git hash-object --literally -t commit -w --stdin <data) &&
git branch bad $bad_commit &&
git bundle create bad.bundle bad
) &&
git clone bundle-fsck/bad.bundle bundle-no-fsck &&
git -c fetch.fsckObjects=false -c transfer.fsckObjects=true \
clone bundle-fsck/bad.bundle bundle-fetch-no-fsck &&
test_must_fail git -c fetch.fsckObjects=true \
clone bundle-fsck/bad.bundle bundle-fetch-fsck 2>err &&
test_grep "missingEmail" err &&
test_must_fail git -c transfer.fsckObjects=true \
clone bundle-fsck/bad.bundle bundle-transfer-fsck 2>err &&
test_grep "missingEmail" err
'
test_expect_success 'git bundle uses expected default format' '
git bundle create bundle HEAD^.. &&
cat >expect <<-EOF &&

View File

@ -186,7 +186,8 @@ static int fetch_refs_from_bundle(struct transport *transport,
if (!data->get_refs_from_bundle_called)
get_refs_from_bundle_inner(transport);
ret = unbundle(the_repository, &data->header, data->fd,
&extra_index_pack_args, 0);
&extra_index_pack_args,
fetch_pack_fsck_objects() ? VERIFY_BUNDLE_FSCK : 0);
transport->hash_algo = data->header.hash_algo;
return ret;
}