Skip to content

Commit d9fd674

Browse files
derrickstoleegitster
authored andcommitted
bundle: verify using check_connected()
When Git verifies a bundle to see if it is safe for unbundling, it first looks to see if the prerequisite commits are in the object store. This is an easy way to "fail fast" but it is not a sufficient check for updating refs that guarantee closure under reachability. There could still be issues if those commits are not reachable from the repository's references. The repository only has guarantees that its object store is closed under reachability for the objects that are reachable from references. Thus, the code in verify_bundle() has previously had the additional check that all prerequisite commits are reachable from repository references. This is done via a revision walk from all references, stopping only if all prerequisite commits are discovered or all commits are walked. This uses a custom walk to verify_bundle(). This check is more strict than what Git applies to fetched pack-files. In the fetch case, Git guarantees that the new references are closed under reachability by walking from the new references until walking commits that are reachable from repository refs. This is done through the well-used check_connected() method. To better align with the restrictions required by 'git fetch', reimplement this check in verify_bundle() to use check_connected(). This also simplifies the code significantly. The previous change added a test that verified the behavior of 'git bundle verify' and 'git bundle unbundle' in this case, and the error messages looked like this: error: Could not read <missing-commit> fatal: Failed to traverse parents of commit <extant-commit> However, by changing the revision walk slightly within check_connected() and using its quiet mode, we can omit those messages. Instead, we get only this message, tailored to describing the current state of the repository: error: some prerequisite commits exist in the object store, but are not connected to the repository's history (Line break added here for the commit message formatting, only.) While this message does not include any object IDs, there is no guarantee that those object IDs would help the user diagnose what is going on, as they could be separated from the prerequisite commits by some distance. At minimum, this situation describes the situation in a more informative way than the previous error messages. Signed-off-by: Derrick Stolee <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent e72171f commit d9fd674

File tree

2 files changed

+33
-50
lines changed

2 files changed

+33
-50
lines changed

bundle.c

Lines changed: 29 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "refs.h"
1313
#include "strvec.h"
1414
#include "list-objects-filter-options.h"
15+
#include "connected.h"
1516

1617
static const char v2_bundle_signature[] = "# v2 git bundle\n";
1718
static const char v3_bundle_signature[] = "# v3 git bundle\n";
@@ -187,6 +188,21 @@ static int list_refs(struct string_list *r, int argc, const char **argv)
187188
/* Remember to update object flag allocation in object.h */
188189
#define PREREQ_MARK (1u<<16)
189190

191+
struct string_list_iterator {
192+
struct string_list *list;
193+
size_t cur;
194+
};
195+
196+
static const struct object_id *iterate_ref_map(void *cb_data)
197+
{
198+
struct string_list_iterator *iter = cb_data;
199+
200+
if (iter->cur >= iter->list->nr)
201+
return NULL;
202+
203+
return iter->list->items[iter->cur++].util;
204+
}
205+
190206
int verify_bundle(struct repository *r,
191207
struct bundle_header *header,
192208
enum verify_bundle_flags flags)
@@ -196,64 +212,40 @@ int verify_bundle(struct repository *r,
196212
* to be verbose about the errors
197213
*/
198214
struct string_list *p = &header->prerequisites;
199-
struct rev_info revs = REV_INFO_INIT;
200-
const char *argv[] = {NULL, "--all", NULL};
201-
struct commit *commit;
202-
int i, ret = 0, req_nr;
215+
int i, ret = 0;
203216
const char *message = _("Repository lacks these prerequisite commits:");
217+
struct string_list_iterator iter = {
218+
.list = p,
219+
};
220+
struct check_connected_options opts = {
221+
.quiet = 1,
222+
};
204223

205224
if (!r || !r->objects || !r->objects->odb)
206225
return error(_("need a repository to verify a bundle"));
207226

208-
repo_init_revisions(r, &revs, NULL);
209227
for (i = 0; i < p->nr; i++) {
210228
struct string_list_item *e = p->items + i;
211229
const char *name = e->string;
212230
struct object_id *oid = e->util;
213231
struct object *o = parse_object(r, oid);
214-
if (o) {
215-
o->flags |= PREREQ_MARK;
216-
add_pending_object(&revs, o, name);
232+
if (o)
217233
continue;
218-
}
219234
ret++;
220235
if (flags & VERIFY_BUNDLE_QUIET)
221236
continue;
222237
if (ret == 1)
223238
error("%s", message);
224239
error("%s %s", oid_to_hex(oid), name);
225240
}
226-
if (revs.pending.nr != p->nr)
241+
if (ret)
227242
goto cleanup;
228-
req_nr = revs.pending.nr;
229-
setup_revisions(2, argv, &revs, NULL);
230-
231-
list_objects_filter_copy(&revs.filter, &header->filter);
232-
233-
if (prepare_revision_walk(&revs))
234-
die(_("revision walk setup failed"));
235243

236-
i = req_nr;
237-
while (i && (commit = get_revision(&revs)))
238-
if (commit->object.flags & PREREQ_MARK)
239-
i--;
240-
241-
for (i = 0; i < p->nr; i++) {
242-
struct string_list_item *e = p->items + i;
243-
const char *name = e->string;
244-
const struct object_id *oid = e->util;
245-
struct object *o = parse_object(r, oid);
246-
assert(o); /* otherwise we'd have returned early */
247-
if (o->flags & SHOWN)
248-
continue;
249-
ret++;
250-
if (flags & VERIFY_BUNDLE_QUIET)
251-
continue;
252-
if (ret == 1)
253-
error("%s", message);
254-
error("%s %s", oid_to_hex(oid), name);
255-
}
244+
if ((ret = check_connected(iterate_ref_map, &iter, &opts)))
245+
error(_("some prerequisite commits exist in the object store, "
246+
"but are not connected to the repository's history"));
256247

248+
/* TODO: preserve this verbose language. */
257249
if (flags & VERIFY_BUNDLE_VERBOSE) {
258250
struct string_list *r;
259251

@@ -282,15 +274,6 @@ int verify_bundle(struct repository *r,
282274
list_objects_filter_spec(&header->filter));
283275
}
284276
cleanup:
285-
/* Clean up objects used, as they will be reused. */
286-
for (i = 0; i < p->nr; i++) {
287-
struct string_list_item *e = p->items + i;
288-
struct object_id *oid = e->util;
289-
commit = lookup_commit_reference_gently(r, oid, 1);
290-
if (commit)
291-
clear_commit_marks(commit, ALL_REV_FLAGS | PREREQ_MARK);
292-
}
293-
release_revisions(&revs);
294277
return ret;
295278
}
296279

t/t6020-bundle-misc.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -588,14 +588,14 @@ test_expect_success 'verify catches unreachable, broken prerequisites' '
588588
# Verify should fail
589589
test_must_fail git bundle verify \
590590
../clone-from/tip.bundle 2>err &&
591-
grep "Could not read $BAD_OID" err &&
592-
grep "Failed to traverse parents of commit $TIP_OID" err &&
591+
grep "some prerequisite commits .* are not connected" err &&
592+
test_line_count = 1 err &&
593593
594594
# Unbundling should fail
595595
test_must_fail git bundle unbundle \
596596
../clone-from/tip.bundle 2>err &&
597-
grep "Could not read $BAD_OID" err &&
598-
grep "Failed to traverse parents of commit $TIP_OID" err
597+
grep "some prerequisite commits .* are not connected" err &&
598+
test_line_count = 1 err
599599
)
600600
'
601601

0 commit comments

Comments
 (0)