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

Skip to content

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

Closed
wants to merge 1 commit into from

Conversation

swisspol
Copy link
Contributor

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.

@swisspol
Copy link
Contributor Author

See the comments in apply_untracked() and unstage_modified_files() for the 2 parts where I wasn't sure I had the best implementation.

@swisspol
Copy link
Contributor Author

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.

@swisspol swisspol force-pushed the stash_apply branch 2 times, most recently from 7337dd8 to e0d35a5 Compare November 14, 2014 04:06

/* If applicable, restore untracked / ignored files in workdir */
if (untracked_tree) {

Copy link
Member

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!

@swisspol swisspol force-pushed the stash_apply branch 2 times, most recently from 26a0a65 to f9589eb Compare November 14, 2014 11:02
@Therzok
Copy link
Member

Therzok commented Nov 14, 2014

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
Copy link
Member

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.

@swisspol
Copy link
Contributor Author

I addressed the above comments and added some extra unit tests to check error codes.

@ethomson
Copy link
Member

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.

@swisspol
Copy link
Contributor Author

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;
}
Copy link
Member

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.

Copy link
Contributor Author

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 :)

Copy link
Member

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(...);
}

@swisspol
Copy link
Contributor Author

Done

* most recent stashed state.
*
* @param reinstate_index Try to reinstate not only the working tree's changes,
* but also the index's ones.
Copy link
Member

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.

@swisspol swisspol force-pushed the stash_apply branch 2 times, most recently from 50ebd99 to 87fdcaf Compare November 23, 2014 01:00
@swisspol
Copy link
Contributor Author

I rebased the PR and replaced the reinstate_index arg by a generic flags one as I think that's quite more future proof - one might want customize what happens in case of conflicts for instance.

@swisspol
Copy link
Contributor Author

This latest update significantly improves documentation and test coverage.

@swisspol
Copy link
Contributor Author

@ethomson I found a conflict case that wasn't handled exactly like in native Git so I'm working on fixing it, but I'd need help with #2728.

@swisspol
Copy link
Contributor Author

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.

@swisspol
Copy link
Contributor Author

I was also able to replace GIT_CHECKOUT_SAFE_CREATE by GIT_CHECKOUT_SAFE now and it works fine.

@ethomson
Copy link
Member

@ethomson I found a conflict case that wasn't handled exactly like in native Git so I'm working on fixing it, but I'd need help with #2728.

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
Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Contributor Author

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.

@swisspol
Copy link
Contributor Author

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.

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.

@swisspol
Copy link
Contributor Author

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.

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:

  • Finalize the implementation of apply_untracked() (see my long comment above with the 3 choices)
  • Consider using merge_indexes() in unstage_modified_files() (I have no clue what this would look like nor how to ensure it has the exact same behavior though)
  • Consider adding docs for GIT_STASH_DEFAULT and GIT_APPLY_DEFAULT
  • Consider remapping the GIT_EUNMERGED error code in case of conflict when using GIT_APPLY_REINSTATE_INDEX to something else

@swisspol
Copy link
Contributor Author

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;
}
Copy link
Member

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);

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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(&current, index);

git_tree *current_tree;
git_tree_lookup(&current_tree, repo, &current);

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);

Copy link
Contributor Author

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.

@Therzok
Copy link
Member

Therzok commented Dec 24, 2014

This is relevant to my interests. Any way we can move it forward? 🙏

@swisspol
Copy link
Contributor Author

swisspol commented Jan 1, 2015

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 :)

@ethomson
Copy link
Member

ethomson commented Jan 5, 2015

As seen above with the discussions with @ethomson some changes suggested to have a "cleaner" implementation result in changes in the actual behavior.

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?

@swisspol
Copy link
Contributor Author

swisspol commented Jan 5, 2015

I don't see any behavioral changes

These were potential changes based on some earlier discussions which are gone from the history for some reason, but anyway it doesn't matter :)

all your tests passed with the code that I posted above

Do you mean this code:

git_index *index;
git_repository_index(&index, repo);

git_oid current;
git_index_write_tree(&current, index);

git_tree *current_tree;
git_tree_lookup(&current_tree, repo, &current);

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);

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.

@swisspol
Copy link
Contributor Author

Closing per #3018

@swisspol swisspol closed this Mar 24, 2015
@swisspol swisspol deleted the stash_apply branch March 24, 2015 22:04
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.

6 participants