-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Added git_stash_apply() and git_stash_pop() APIs #2705
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
See the comments in |
74b2799
to
b5749dd
Compare
All the unit tests are passing, plus the new basic ones I added, and also my own functional tests which sit on top of libgit2. |
7337dd8
to
e0d35a5
Compare
|
||
/* If applicable, restore untracked / ignored files in workdir */ | ||
if (untracked_tree) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty lines here and below!
26a0a65
to
f9589eb
Compare
Tested it, it's marvelous! <3 |
* @param reinstate_index Try to reinstate not only the working tree's changes, | ||
* but also the index's ones. | ||
* | ||
* @return 0 on success, or error code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It maybe interesting to list the common error codes that this function may return.
f9589eb
to
84571ef
Compare
I addressed the above comments and added some extra unit tests to check error codes. |
Thanks @swisspol - I apologize that this has been sitting for a few days, I have been swamped but I am looking forward to taking a look at this next week. |
84571ef
to
f483f06
Compare
Hummm... not sure where @carlosmn's comment went but I addressed it in this latest revision. |
*out_index_tree = index_tree; | ||
*out_index_parent_tree = index_parent_tree; | ||
*out_untracked_tree = untracked_tree; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part seems odd. It's not about cleanup but about setting the outputs. I'd rather see the contents of the 'else' put before the cleanup label and we can leave everything after cleanup to be freeing memory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you just provide the code how you want it and I'll copy-paste it in? I think it'll save some round trips :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically just
*out_stash_tree = stash_tree;
*out_base_tree = base_tree;
*out_index_tree = index_tree;
*out_index_parent_tree = index_parent_tree;
*out_untracked_tree = untracked_tree;
cleanup:
git_commit_free(...);
if (error < 0) {
git_tree_free(...);
}
f483f06
to
6e72bb3
Compare
Done |
* most recent stashed state. | ||
* | ||
* @param reinstate_index Try to reinstate not only the working tree's changes, | ||
* but also the index's ones. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sigh, ignore that last comment, I always forget how stash works.
50ebd99
to
87fdcaf
Compare
I rebased the PR and replaced the |
87fdcaf
to
47b2079
Compare
This latest update significantly improves documentation and test coverage. |
47b2079
to
4efab9c
Compare
I added a couple more test cases and fixed 2 bugs where the API would not behave exactly like Git CLT. As far as I can tell it's all good now from a behavior and test case perspective. |
4efab9c
to
b526200
Compare
I was also able to replace |
Sorry I'm being a little dense - with the subsequent comments, is this still an issue? If so, can you push up the tests, I'd love to see what's going wrong. Sorry I've been so very unavailable the last two weeks, I've been 😓 ing it out in the day job for a tight deadline and haven't had much (or really any) time to give to libgit2. I will have some time this afternoon to attempt to grok this in fullness. I really appreciate both your hard work and especially your patience. |
* in the stash will be left around in the workdir. | ||
* | ||
* If passing the GIT_APPLY_REINSTATE_INDEX flag and there are conflicts when | ||
* reinstating the index, the function will return GIT_EUNMERGED and both the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally we use GIT_EUNMERGED
to reflect that there are unmerged changes in the index when you begin the operation. I don't think we use EUNMERGED
to indicate that we created conflicts and failed.
For example, now I have a conflict on file bar
, and I am trying to unstash a stash that changes bar
:
% git stash pop
bar: needs merge
unable to refresh index
This is the case that I would expect to see GIT_EUNMERGED
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not returning any error code here, just passing through whatever error comes back from the APIs used internally. Am I supposed to remap this error to something else? If so to what?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually the use of GIT_EUNMERGED
also matches the behavior you described: no conflicts are created and then the function fails mid-course. The index and workdir are left untouched. It fails during preflight in a way.
I can change the description to "...there are [potential] conflicts...", but again I'm just passing through the error from the lower-level API. Let me know.
There's nothing wrong, it's just that I didn't know how to write the unit test to properly detect a given file is in "both modified". Your comment in #2728 helped me. |
No problem, there's nothing urgent here. It certainly helps to have some visibility into your availability. To summarize the remaining points to address are:
|
b526200
to
ae6bb09
Compare
I realized I was still missing one test case (restoring the stash would overwrite local modifications in GIT_APPLY_DEFAULT mode). Turns out it this case wasn't behaving like Git CLT, but it's all fixed now. I also updated the docs (again) and changed the error codes / error messages to make them both distinct and explicit. |
if ((error = git_checkout_tree( | ||
repo, (git_object*)untracked_tree, &options)) < 0) | ||
return error; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of checking out files one-by-one, I think that I would prefer to see us merge the untracked_tree with the unstashed tree and check that out. Eg:
git_merge_trees(&merged_index, repo, NULL, untracked_tree, unstashed_tree);
git_checkout_index(repo, merged_index, &options);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you 100% sure the result will be exactly the same?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not 100%, but I'm pretty certain, and we should have tests to ensure that we are accurate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also where is unstashed_tree
coming from here? You need to merge that untracked_tree
with the tree of the workdir which AFAIK is not readily available, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You created it in git_stash_apply
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not following you: unstashed_tree
is the reinstantiated index if requested, it's not the workdir tree.
The implementation I propose is dirt simple, very robust and predictable. You suggest to use git_merge_trees
/ git_checkout_index
, but the potential side effects / edge cases of this approach (and again how do you get the workdir tree here?) are unknown to me, independently of the git apply unit test passing or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not following you: unstashed_tree is the reinstantiated index if requested, it's not the workdir tree.
You just checked it out - so it is the tree that was placed into the workdir. (And if you opted not to use it, then you can just use the index, instead.)
The idea here is that instead of adding one file at a time to the working directory, we can take what's in the index, add this additional data, and write that. checkout_tree
is smart enough to only write the difference, but also should do the conflict detection that you've done yourself.
I don't anticipate edge cases - but if there are bugs, then it would be nice to isolate those bugs to only having one place we check out files and do conflict detection and the like.
Here's some sample code of what I'm suggesting. Though this just reloads the index, always, instead of taking the index tree or the unstashed tree that you previously created:
git_index *index;
git_repository_index(&index, repo);
git_oid current;
git_index_write_tree(¤t, index);
git_tree *current_tree;
git_tree_lookup(¤t_tree, repo, ¤t);
git_index *merged_index;
git_merge_trees(&merged_index, repo, NULL, untracked_tree, current_tree, NULL);
git_oid merged_tree_id;
git_index_write_tree_to(&merged_tree_id, merged_index, repo);
git_tree *merged_tree;
git_tree_lookup(&merged_tree, repo, &merged_tree_id);
options.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DONT_UPDATE_INDEX;
return git_checkout_index(repo, merged_index, &options);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You just checked it out
Where did you see that, it's not supposed to check it out. It's reinstating the index, not touching the workdir.
This is relevant to my interests. Any way we can move it forward? 🙏 |
This implementation has full coverage and works great AFAIK. I also addressed most comments. I wrote this implementation mirroring exactly what Git CLT implementation does, so it should be 100% compatible. If it were to be fully written "libgit2" style i.e. taking advantage of every inner API, the resulting code might be more obvious (and maybe, maybe, more compact, but I'm not sure), but then it would "diverge" from Git CLT. The problem is that the stash system in Git has a lot of intricacies and IMO the easiest to handle them correctly is to mirror the Git implementation, rather than trying to reverse engineer every corner case and rebuild it. As seen above with the discussions with @ethomson some changes suggested to have a "cleaner" implementation result in changes in the actual behavior. Anyway, it's up to the maintainers to decide here :) |
Hmm. I don't see any behavioral changes... all your tests passed with the code that I posted above. Do you have some test that fails? |
These were potential changes based on some earlier discussions which are gone from the history for some reason, but anyway it doesn't matter :)
Do you mean this code:
You said "instead of taking the index tree or the unstashed tree that you previously created" so I assumed you wanted to do that instead. In any case, what's next here? Do you want to grab the PR and modify it with your change? I'm not super comfortable with it in the sense that I'm not sure I 100% understand it. |
Closing per #3018 |
This is a tentative implementation of git_stash_apply() based on the actual Git implementation found at https://github.com/git/git/blob/master/git-stash.sh#L440.
Once you have git_stash_apply(), it's trivial to implement git_stash_pop() so I also added it.