Add a checkout example#4692
Conversation
|
Note that while working with refishs, I've grown to like And looking around its API, it might be worthwhile to add a |
pks-t
left a comment
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
This comment looks like a copy-paste error?
|
|
||
| static void print_usage(void) | ||
| { | ||
| fprintf(stderr, "usage: checkout [--force] <branch>\n"); |
| 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); |
There was a problem hiding this comment.
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
| } | ||
|
|
||
|
|
||
|
|
|
|
||
| parse_options(&path, &opts, &args); | ||
|
|
||
| git_libgit2_init(); |
There was a problem hiding this comment.
You need to check the return value of that function.
| fprintf(stderr, "missing argument\n"); | ||
| err = -1; | ||
| goto cleanup; | ||
| } |
There was a problem hiding this comment.
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); | ||
| } |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Nice, that's soo much better !
|
|
||
| #ifdef _MSC_VER | ||
| #define snprintf sprintf_s | ||
| #endif |
b97c7ba to
ffc3918
Compare
|
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; |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
There's git_repository_set_head_detached_from_annotated
| print_usage(); | ||
|
|
||
| /* Default values */ | ||
| opts->progress = 1; |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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]); |
There was a problem hiding this comment.
Resolving the ref here makes sense, but I'd parse the reference itself into the options structure in parse_options
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.
ffc3918 to
4570fd5
Compare
|
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 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 😉). |
pks-t
left a comment
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
If this function is indeed called with a NULL path, then the printf call is undefined behaviour as "%s" expects a valid pointer
4570fd5 to
b24202e
Compare
|
Fixed the UB. As always, thanks for the review @pks-t ! |
|
And again, thanks @tiennou! :) |
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.