Skip to content

transports: Avoid walking over non-commit objects - #4383

Closed
asandroq wants to merge 3 commits into
libgit2:masterfrom
asandroq:fix-walk-on-tagged-blobs
Closed

transports: Avoid walking over non-commit objects#4383
asandroq wants to merge 3 commits into
libgit2:masterfrom
asandroq:fix-walk-on-tagged-blobs

Conversation

@asandroq

Copy link
Copy Markdown

References may point to objects that are not commits, such as tags to
blobs. In that case the reference must not be walked over further.

Fixes #3595.

References may point to objects that are not commits, such as tags to
blobs. In that case the reference must not be walked over further.

Fixes libgit2#3595.
Comment thread src/transports/smart_protocol.c Outdated
if ((error = git_object_lookup(&oobj, repo, oid, GIT_OBJ_ANY)) < 0)
goto on_error;

error = git_object_peel(&obj, oobj, GIT_OBJ_COMMIT);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why are you peeling here? What type of objects could you find above (in the lookup) that would need to be peeled? It seems like this could be simplified as:

oid = git_reference_target(ref);
if ((error = git_object_lookup(&obj, repo, oid, GIT_OBJ_ANY)) < 0)
    goto on_error;

if (git_object_type(obj) != GIT_OBJ_COMMIT) {
    git_object_free(obj);
    continue;
}

It might be nicer to specify the type to git_object_lookup and modify git_object_lookup to return a unique error code when the type of the object is not the type requested.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my use-case the first object is actually an annotated tag, which when peeled points to a blob. But in other cases it could point to a commit, and in this case the commit can be added to the revwalk.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks for the explanation.

@ethomson

Copy link
Copy Markdown
Member

Nice - thanks for the pull request and for adding tests! ✨

My assumption here is that there's not a significant perf impact to looking up these objects ahead of time just to validate their type... presumably they'll get cached and then the revwalk will pull them from the cache.

Another option is to move this logic into the revwalk enqueue functionality, which could prevent a second odb lookup.

@ethomson

Copy link
Copy Markdown
Member

Right - looking at git_revwalk_push, it does this exact lookup and peel step... I think it would be nice to not duplicate that right before calling it.

Couple of options:

  1. Introduce a new API that only pushes commits and ignores non-commits that get peeled that then calls push_commit with the from_glob option as false. (And probably renames from_globs to something like ignore_non_commits).

  2. Disambiguate the error in push_commit so that it's catchable as a "this is not a commit". This seems like it would have broader uses for end users.

@carlosmn do you have an opinion here?

@pks-t pks-t left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review is regarding your changes only, without judgement whether it'd be better to push this into git_revwalk_push.

Re @ethomson. I'm not a fan of the first option. It seems a bit fragile to simply pretend everything is alright when we in fact just ignore what we were told to do. The second possibility looks much saner to me. I think we should aim for that solution -- I'm not a fan of premature optimization, but if we can easily avoid a second lookup/peel while making the API more useful for callers it sounds like a win-win to me.

Comment thread src/transports/smart_protocol.c Outdated
continue;

if ((error = git_revwalk_push(walk, git_reference_target(ref))) < 0)
/* only walk over commits */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment here is a bit terse, in my opinion. I'd love to have it mention why we need the separate lookup/peel steps and why we need to filter out tags-to-blobs here if there's a comment 10 lines before which pretends to filter out tags already. I've been confused by that, as well, as I didn't expect you to have tags outside of the "refs/tags" hierarchy.

Comment thread src/transports/smart_protocol.c Outdated
git_object_free(oobj);
if (error == GIT_ENOTFOUND || error == GIT_EPEEL) {
continue;
} else if (error != 0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually avoid braces around single-line statements

Comment thread src/transports/smart_protocol.c Outdated

for (i = 0; i < refs.count; ++i) {
const git_oid *oid;
git_object *obj, *oobj;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These object are leaking as soon as we encounter an error. You could instead just initialize them to NULL, git_object_free at the beginning of each iteration as well as freeing in the on_error section. This requires us to define these variables at the beginning of the function.

Comment thread tests/refs/iterator.c
}

static const char *refnames[] = {
"refs/blobs/annotated_tag_to_blob",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This thing here was missing to complete my picture of your issue. I didn't realize you had tags in non-standard locations. Anyway, we should definitly be able to cope with that as users do strange things.

@asandroq

asandroq commented Oct 23, 2017

Copy link
Copy Markdown
Author

Right - looking at git_revwalk_push, it does this exact lookup and peel step... I think it would be nice to not duplicate that right before calling it.

@ethomson, I've had this patch for a couple of years now so I forgot how I wrote it, but probably I got the code from there. :)

This situation was caught previously but returned a generic error
code.
Instead, use the return code to check if reference does not point to a
committish.
@carlosmn

Copy link
Copy Markdown
Member

I'm not fond of adding a new error case for this. We already have error codes for peeling (though they are admittedly more complex than I'd like) which is what we're doing.

The point of the the revwalk during fetch is to convert as many reference tips into commits as we can, so we can reduce the amount of data that's transmitted, so we should go with that idea into the code. The loop under /* No tags */ is, as it has been pointed out, deficient, as it assumes only stuff under refs/tags/ is a commit, but then also fails to enqueue those, which could be a useful source of commits.

This code predates git_revwalk_push_glob which is why it simply uses git_revwalk_push and attempts to do the tag filtering on its own. I think the best way forward here would be to make use of that convenience function, which is made for precisely this kind of situation.

We should actually be able to get rid of the whole of fetch_setup_walk's body and replace it with

git_revwalk_new(&walk, repo);
git_revwalk_push_glob(&walk, "refs/*");

(plus error handling) with the extra advantage that we're using an iterator instead of having to allocate an array.

@ethomson

Copy link
Copy Markdown
Member

Closed for #5195

@ethomson ethomson closed this Jul 31, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

libgit2 assumes everything not under refs/tags are commits

4 participants