Skip to content

Commit

Permalink
Merge branch 'xx/bundie-uri-fixes'
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gitster committed Jul 8, 2024
2 parents 3997614 + 63d903f commit 125e389
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 14 deletions.
6 changes: 3 additions & 3 deletions bundle-uri.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "hashmap.h"
#include "pkt-line.h"
#include "config.h"
#include "fetch-pack.h"
#include "remote.h"

static struct {
Expand Down Expand Up @@ -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;

/*
Expand All @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions bundle.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions bundle.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
17 changes: 11 additions & 6 deletions fetch-pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions fetch-pack.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
187 changes: 183 additions & 4 deletions t/t5558-clone-bundle-uri.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand All @@ -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' '
Expand All @@ -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" \
Expand Down Expand Up @@ -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

Expand Down
35 changes: 35 additions & 0 deletions t/t5607-clone-bundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down
3 changes: 2 additions & 1 deletion transport.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down

0 comments on commit 125e389

Please sign in to comment.