Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

tiennou
Copy link
Contributor

@tiennou tiennou commented Jun 23, 2018

This adds an example on how to perform checkouts with libgit2.

Note that it only does the "branch moving" part of checkout, so no git checkout src/file.c.

Ref #2824.

@tiennou
Copy link
Contributor Author

tiennou commented Jun 23, 2018

Note that while working with refishs, I've grown to like git_annotated_commit for its ability to abstract away the details (like the automated reflog messages).

And looking around its API, it might be worthwhile to add a git_repository_set_head_from_annotated, that would underlie all others set_heads we have. Also, I was a little confused that all the "virtual commits" creation lies in "merge.c", so maybe moving that back to "annotated.c" so the separation is cleaner ?

Copy link
Member

@pks-t pks-t left a comment

Choose a reason for hiding this comment

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

Thanks for this example, @tiennou.

While there's a lot of comments, I guess most of them are fast to address. One thing I'd like to see though is to have a lot more /** */-style documentation. The examples are being parsed and presented on Doxygen to serve as some kind of walkthrough with explanations, so that would benefit users a lot, I guess. It's not a requirement to get this merged, though.

# define PRIdZ "zd"
#endif

/** The following example demonstrates how to do checkouts with libgit2.
Copy link
Member

Choose a reason for hiding this comment

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

Nit: multiline-comments usually start with an empty /** line

/** The following example demonstrates how to do checkouts with libgit2.
*
* Recognized options are :
* --force: don't actually commit the merge.
Copy link
Member

Choose a reason for hiding this comment

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

This comment looks like a copy-paste error?


static void print_usage(void)
{
fprintf(stderr, "usage: checkout [--force] <branch>\n");
Copy link
Member

Choose a reason for hiding this comment

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

You're missing the other options

static void print_checkout_progress(const char *path, size_t completed_steps, size_t total_steps, void *payload)
{
(void)payload;
printf("checkout: %s %" PRIuZ "/%" PRIuZ "\n", path, completed_steps, total_steps);
Copy link
Member

Choose a reason for hiding this comment

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

Usually you'd have to first erase the previous line and write on that same line again. But for a simple example this is sufficient, I guess

}



Copy link
Member

Choose a reason for hiding this comment

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

A few blank lines too much


parse_options(&path, &opts, &args);

git_libgit2_init();
Copy link
Member

Choose a reason for hiding this comment

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

You need to check the return value of that function.

fprintf(stderr, "missing argument\n");
err = -1;
goto cleanup;
}
Copy link
Member

Choose a reason for hiding this comment

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

This check looks like it belongs to parse_opts, as well

err = perform_checkout_ref(repo, checkout_target, &opts);
} else {
printf("failed to resolve %s: %s\n", args.argv[args.pos], giterr_last()->message);
}
Copy link
Member

Choose a reason for hiding this comment

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

I'd use our usual error handling style instead.

if (err < 0) {
    printf("failed...");
    goto cleanup;
}

err = perform_checkout_ref(repo, checkout_target, &opts);


neg_opt = malloc(strlen(opt) + 3);
strcpy(neg_opt, "--no-");
strcat(neg_opt, opt + 2);
Copy link
Member

Choose a reason for hiding this comment

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

This seems a bit heavy-handed ;)

if (!strncmp(found, "--no-", strlen("--no-")) &&
    !strcmp(found + strlen("--no-"), opt + 2)) {
        *out = 0;
        res = 1;
}

This is untested, but something like that would work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice, that's soo much better !


#ifdef _MSC_VER
#define snprintf sprintf_s
#endif
Copy link
Member

Choose a reason for hiding this comment

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

This is unused

@tiennou tiennou force-pushed the examples/checkout branch 2 times, most recently from b97c7ba to ffc3918 Compare June 30, 2018 13:05
@tiennou
Copy link
Contributor Author

tiennou commented Jun 30, 2018

I've fixed/clarified most of the review comments. Clarifications because sometime™ we might want to demonstrate path-based checkouts, so the code in main was convoluted because of that.

The code is also commented now.

res = 1;
}

return res;
Copy link
Member

Choose a reason for hiding this comment

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

You could avoid the res variable by just returning 1 in the previous branch and 0 at the end of this function. That would make the asymmetry between the first and second conditional go away. Not urgent though, you should only address this if you're rerolling anyway

* Check current `args` entry against a "bool" `opt` (ie. --[no-]progress).
* If `opt` matches positively, out will be set to 1, or if `opt` matches
* negatively, out will be set to 0, and 1 will be returned.
* If neither the positive or the negative form of opt matched, out will be -1,
Copy link
Member

Choose a reason for hiding this comment

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

The documentation is wrong. You either set out to 0 or 1, but never -1.

const char *curr = args->argv[args->pos];
int bool_arg;

if (strcmp(curr, "--") == 0) {
Copy link
Member

Choose a reason for hiding this comment

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

You should probably use !strcmp, for consistency with the next conditional. Might confuse readers otherwise

if (git_annotated_commit_ref(target)) {
err = git_repository_set_head(repo, git_annotated_commit_ref(target));
} else {
err = git_repository_set_head_detached(repo, git_annotated_commit_id(target));
Copy link
Member

Choose a reason for hiding this comment

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

There's git_repository_set_head_detached_from_annotated

print_usage();

/* Default values */
opts->progress = 1;
Copy link
Member

Choose a reason for hiding this comment

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

You should memset(opts, 0, sizeof(opts)), as you never initialize the struct to all-zeroes

}

if (args.pos >= args.argc) {
fprintf(stderr, "unhandled\n");
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this check be part of parse_options, together with usage instructions? I'd expect that function to only ever return where the options are correct


fprintf(stderr, "unhandled path-based checkout\n");
err = 1;
goto cleanup;
Copy link
Member

Choose a reason for hiding this comment

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

I'd also just remove this and error out in parse_options in case where paths are given. It makes the remaining logic a bit easier to read, I guess

/**
* Try to resolve a "refish" argument to a target libgit2 can use
*/
err = resolve_refish(&checkout_target, repo, args.argv[args.pos]);
Copy link
Member

Choose a reason for hiding this comment

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

Resolving the ref here makes sense, but I'd parse the reference itself into the options structure in parse_options

tiennou added 4 commits July 7, 2018 13:10
As git_annotated_commit seems to behave like cgit's refish, it's quite
helpful to abstract away "targets" via git_annotated_commit_from_id/from_ref.
As the former is accessible via git_annotated_commit_id, make the latter
also available to users.
@tiennou tiennou force-pushed the examples/checkout branch from ffc3918 to 4570fd5 Compare July 7, 2018 12:52
@tiennou
Copy link
Contributor Author

tiennou commented Jul 7, 2018

I've fixed some of the comments.

It seems we have a slight difference in how we expect option parsing to happen though, so here's my "view" : I can't access libgit2 while parsing options, because there might be a --git-dir option after the current argument (option which I'm using to test), so I can't resolve refishs, or paths, because the "current working directory" might be updated later. Hence, I really much prefer option parsing to be kept dumb, and I think it actually makes more sense as example code (if you want to perform a checkout, you can basically look at what main() is doing and ignore the option parsing).

Also, I've attempted to handle path-based checkouts, but just ended up craving for libgit2's already written support for those (it only took one "any directory relative stat call" to throw my hands up at the incoming strcat mess 😉).

Copy link
Member

@pks-t pks-t left a comment

Choose a reason for hiding this comment

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

It seems we have a slight difference in how we expect option parsing to happen though, so here's my "view" : I can't access libgit2 while parsing options, because there might be a --git-dir option after the current argument (option which I'm using to test), so I can't resolve refishs, or paths, because the "current working directory" might be updated later. Hence, I really much prefer option parsing to be kept dumb, and I think it actually makes more sense as example code (if you want to perform a checkout, you can basically look at what main() is doing and ignore the option parsing).

Fair enough, so be it.

I'm fine with this PR then, except for one instance of undefined behaviour. Ready to merge as soon as that's fixed.


/**
* This function is called to report progression, ie. it's called once with
* a NULL path and the number of total steps, then for each subsequent path,
Copy link
Member

Choose a reason for hiding this comment

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

If this function is indeed called with a NULL path, then the printf call is undefined behaviour as "%s" expects a valid pointer

@tiennou tiennou force-pushed the examples/checkout branch from 4570fd5 to b24202e Compare July 17, 2018 19:43
@tiennou
Copy link
Contributor Author

tiennou commented Jul 17, 2018

Fixed the UB. As always, thanks for the review @pks-t !

@pks-t pks-t merged commit ea9e2c1 into libgit2:master Jul 20, 2018
@pks-t
Copy link
Member

pks-t commented Jul 20, 2018

And again, thanks @tiennou! :)

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.

2 participants