Merge branch 'cc/rev-list-allow-missing-tips'

"git rev-list --missing=print" has learned to optionally take
"--allow-missing-tips", which allows the objects at the starting
points to be missing.

* cc/rev-list-allow-missing-tips:
  revision: fix --missing=[print|allow*] for annotated tags
  rev-list: allow missing tips with --missing=[print|allow*]
  t6022: fix 'test' style and 'even though' typo
  oidset: refactor oidset_insert_from_set()
  revision: clarify a 'return NULL' in get_reference()
maint
Junio C Hamano 2024-03-07 15:59:40 -08:00
commit 76d1cd8e5e
7 changed files with 129 additions and 19 deletions

View File

@ -1019,6 +1019,10 @@ Unexpected missing objects will raise an error.
+
The form '--missing=print' is like 'allow-any', but will also print a
list of the missing objects. Object IDs are prefixed with a ``?'' character.
+
If some tips passed to the traversal are missing, they will be
considered as missing too, and the traversal will ignore them. In case
we cannot get their Object ID though, an error will be raised.

--exclude-promisor-objects::
(For internal use only.) Prefilter object traversal at

View File

@ -545,6 +545,18 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
*
* Let "--missing" to conditionally set fetch_if_missing.
*/
/*
* NEEDSWORK: These loops that attempt to find presence of
* options without understanding that the options they are
* skipping are broken (e.g., it would not know "--grep
* --exclude-promisor-objects" is not triggering
* "--exclude-promisor-objects" option). We really need
* setup_revisions() to have a mechanism to allow and disallow
* some sets of options for different commands (like rev-list,
* replay, etc). Such a mechanism should do an early parsing
* of options and be able to manage the `--missing=...` and
* `--exclude-promisor-objects` options below.
*/
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--exclude-promisor-objects")) {
@ -753,8 +765,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)

if (arg_print_omitted)
oidset_init(&omitted_objects, DEFAULT_OIDSET_SIZE);
if (arg_missing_action == MA_PRINT)
if (arg_missing_action == MA_PRINT) {
oidset_init(&missing_objects, DEFAULT_OIDSET_SIZE);
/* Add missing tips */
oidset_insert_from_set(&missing_objects, &revs.missing_commits);
oidset_clear(&revs.missing_commits);
}

traverse_commit_list_filtered(
&revs, show_commit, show_object, &info,

View File

@ -711,15 +711,6 @@ static void filter_combine__free(void *filter_data)
free(d);
}

static void add_all(struct oidset *dest, struct oidset *src) {
struct oidset_iter iter;
struct object_id *src_oid;

oidset_iter_init(src, &iter);
while ((src_oid = oidset_iter_next(&iter)) != NULL)
oidset_insert(dest, src_oid);
}

static void filter_combine__finalize_omits(
struct oidset *omits,
void *filter_data)
@ -728,7 +719,7 @@ static void filter_combine__finalize_omits(
size_t sub;

for (sub = 0; sub < d->nr; sub++) {
add_all(omits, &d->sub[sub].omits);
oidset_insert_from_set(omits, &d->sub[sub].omits);
oidset_clear(&d->sub[sub].omits);
}
}

View File

@ -23,6 +23,16 @@ int oidset_insert(struct oidset *set, const struct object_id *oid)
return !added;
}

void oidset_insert_from_set(struct oidset *dest, struct oidset *src)
{
struct oidset_iter iter;
struct object_id *src_oid;

oidset_iter_init(src, &iter);
while ((src_oid = oidset_iter_next(&iter)))
oidset_insert(dest, src_oid);
}

int oidset_remove(struct oidset *set, const struct object_id *oid)
{
khiter_t pos = kh_get_oid_set(&set->set, *oid);

View File

@ -47,6 +47,12 @@ int oidset_contains(const struct oidset *set, const struct object_id *oid);
*/
int oidset_insert(struct oidset *set, const struct object_id *oid);

/**
* Insert all the oids that are in set 'src' into set 'dest'; a copy
* is made of each oid inserted into set 'dest'.
*/
void oidset_insert_from_set(struct oidset *dest, struct oidset *src);

/**
* Remove the oid from the set.
*

View File

@ -385,9 +385,13 @@ static struct object *get_reference(struct rev_info *revs, const char *name,

if (!object) {
if (revs->ignore_missing)
return object;
return NULL;
if (revs->exclude_promisor_objects && is_promisor_object(oid))
return NULL;
if (revs->do_not_die_on_missing_objects) {
oidset_insert(&revs->missing_commits, oid);
return NULL;
}
die("bad object %s", name);
}
object->flags |= flags;
@ -415,15 +419,21 @@ static struct commit *handle_commit(struct rev_info *revs,
*/
while (object->type == OBJ_TAG) {
struct tag *tag = (struct tag *) object;
struct object_id *oid;
if (revs->tag_objects && !(flags & UNINTERESTING))
add_pending_object(revs, object, tag->tag);
object = parse_object(revs->repo, get_tagged_oid(tag));
oid = get_tagged_oid(tag);
object = parse_object(revs->repo, oid);
if (!object) {
if (revs->ignore_missing_links || (flags & UNINTERESTING))
return NULL;
if (revs->exclude_promisor_objects &&
is_promisor_object(&tag->tagged->oid))
return NULL;
if (revs->do_not_die_on_missing_objects && oid) {
oidset_insert(&revs->missing_commits, oid);
return NULL;
}
die("bad object %s", oid_to_hex(&tag->tagged->oid));
}
object->flags |= flags;
@ -1945,6 +1955,7 @@ void repo_init_revisions(struct repository *r,
init_display_notes(&revs->notes_opt);
list_objects_filter_init(&revs->filter);
init_ref_exclusions(&revs->ref_excludes);
oidset_init(&revs->missing_commits, 0);
}

static void add_pending_commit_list(struct rev_info *revs,
@ -2176,13 +2187,18 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl
if (revarg_opt & REVARG_COMMITTISH)
get_sha1_flags |= GET_OID_COMMITTISH;

/*
* Even if revs->do_not_die_on_missing_objects is set, we
* should error out if we can't even get an oid, as
* `--missing=print` should be able to report missing oids.
*/
if (get_oid_with_context(revs->repo, arg, get_sha1_flags, &oid, &oc))
return revs->ignore_missing ? 0 : -1;
if (!cant_be_filename)
verify_non_filename(revs->prefix, arg);
object = get_reference(revs, arg, &oid, flags ^ local_flags);
if (!object)
return revs->ignore_missing ? 0 : -1;
return (revs->ignore_missing || revs->do_not_die_on_missing_objects) ? 0 : -1;
add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);
add_pending_object_with_path(revs, object, arg, oc.mode, oc.path);
free(oc.path);
@ -3828,8 +3844,6 @@ int prepare_revision_walk(struct rev_info *revs)
FOR_EACH_OBJECT_PROMISOR_ONLY);
}

oidset_init(&revs->missing_commits, 0);

if (!revs->reflog_info)
prepare_to_use_bloom_filter(revs);
if (!revs->unsorted_input)

View File

@ -10,7 +10,10 @@ TEST_PASSES_SANITIZE_LEAK=true
test_expect_success 'create repository and alternate directory' '
test_commit 1 &&
test_commit 2 &&
test_commit 3
test_commit 3 &&
git tag -m "tag message" annot_tag HEAD~1 &&
git tag regul_tag HEAD~1 &&
git branch a_branch HEAD~1
'

# We manually corrupt the repository, which means that the commit-graph may
@ -46,9 +49,10 @@ do
git rev-list --objects --no-object-names \
HEAD ^$obj >expect.raw &&

# Blobs are shared by all commits, so evethough a commit/tree
# Blobs are shared by all commits, so even though a commit/tree
# might be skipped, its blob must be accounted for.
if [ $obj != "HEAD:1.t" ]; then
if test $obj != "HEAD:1.t"
then
echo $(git rev-parse HEAD:1.t) >>expect.raw &&
echo $(git rev-parse HEAD:2.t) >>expect.raw
fi &&
@ -77,4 +81,69 @@ do
done
done

for missing_tip in "annot_tag" "regul_tag" "a_branch" "HEAD~1" "HEAD~1^{tree}" "HEAD:1.t"
do
# We want to check that things work when both
# - all the tips passed are missing (case existing_tip = ""), and
# - there is one missing tip and one existing tip (case existing_tip = "HEAD")
for existing_tip in "" "HEAD"
do
for action in "allow-any" "print"
do
test_expect_success "--missing=$action with tip '$missing_tip' missing and tip '$existing_tip'" '
# Before the object is made missing, we use rev-list to
# get the expected oids.
if test "$existing_tip" = "HEAD"
then
git rev-list --objects --no-object-names \
HEAD ^$missing_tip >expect.raw
else
>expect.raw
fi &&

# Blobs are shared by all commits, so even though a commit/tree
# might be skipped, its blob must be accounted for.
if test "$existing_tip" = "HEAD" && test $missing_tip != "HEAD:1.t"
then
echo $(git rev-parse HEAD:1.t) >>expect.raw &&
echo $(git rev-parse HEAD:2.t) >>expect.raw
fi &&

missing_oid="$(git rev-parse $missing_tip)" &&

if test "$missing_tip" = "annot_tag"
then
oid="$(git rev-parse $missing_tip^{commit})" &&
echo "$missing_oid" >>expect.raw
else
oid="$missing_oid"
fi &&

path=".git/objects/$(test_oid_to_path $oid)" &&

mv "$path" "$path.hidden" &&
test_when_finished "mv $path.hidden $path" &&

git rev-list --missing=$action --objects --no-object-names \
$missing_oid $existing_tip >actual.raw &&

# When the action is to print, we should also add the missing
# oid to the expect list.
case $action in
allow-any)
;;
print)
grep ?$oid actual.raw &&
echo ?$oid >>expect.raw
;;
esac &&

sort actual.raw >actual &&
sort expect.raw >expect &&
test_cmp expect actual
'
done
done
done

test_done