-
Notifications
You must be signed in to change notification settings - Fork 899
Expose git_index_write_tree and git_index_read_tree with a nice API #799
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
Those aren't libgit2 names, but git names. libgit2 uses read-tree and write-tree because that's what the plumbing command is called which does precisely this. |
@carlosmn I'm sorry, I didn't mean to complain about the libgit2 names. I just tried to choose different ones because I thought an Index.WriteTree that does not mutate the index and an Index.ReadTree that modifies the index would be confusing for C# developers: comparing with Stream, for example, where Stream.Read does not mutate the stream and Stream.Write does. |
My point was that these are the names of basic Git operations and exposing them with different names would add confusion for someone who is looking at it from the Git perspective. |
I personally had to read the libgit2 methods description before I could tell which one does what; I could not tell at once based on their names. But I agree, they do what their names say and they are 1:1 mappings to the git core commands. I think you've done a very good job with the libgit2Sharp API so far, and I don't have any attachment to the names, should I rename the two methods to Index.WriteTree and Index.ReadTree ? Thank you! |
Actually, I'm not sure that those methods should be exposed by the For instance, rather than I haven't a proposal yet regarding how to expose the replacement of the Index from a Tree in such a manner that the newbie user won't shoot himself in the foot by mistake.
Getting a glimpse of the "end-user need" may help us design the API in a cleaner way (or identify existing features which could solve your requirements). Could you please share a little bit about the higher level scenarios that led you to implement such features? Other than that, your PR looks a very nice piece of work ⚡. Thanks a lot for having taken the time to match the general code style ❤️ |
It's easy enough to create a |
Sold! 👍 |
One scenario (as silly as it sounds :)) that I need to implement is a logical, incremental backup tool for git repositories. Let's say I've taken care of backing-up the objects database incrementally and now I need to backup the index incrementally. I can probably accomplish my goal by iterating through the index entries when creating the backup and build some tree data-structure from it. I can probably do the same when restoring a backup, by doing some sort of delta-logic in C#, by looking at the entries in the index, figuring out which ones are OK and which ones I should modify. However, in reality, I need to quickly figure out if the index has changed since my last backup, then save the entire index and have the ability to restore it. Since I've got the backing up code for the objects database working, in my scenario it would work out great if the index's tree would be there too. I might be biased because I found them, but I share your concern with the placement of the methods. For the other method,
Thanks again for your time & for libgit2Sharp, which I really think has a great API. I'd hate to ruin it with my scenario, but at the same time I really want to use the nice API and not have to resort to exec git core or do some reflection hacks in my code. |
Reading a tree into the index will not restore the original index, but will replace the entries and all the auxiliary information will be lost. It will also not store entries in higher stages, which means you'd lose any chance of restoring an in-progress merge. And that's assuming you have created a tree out of an in-progress merge, which means you've thrown away the merge information. This is why we don't let you perform write-tree on an index with higher-staged entries. The index is a completely different data structure to a tree. You cannot pretend one is the other without losing information. |
Here's a customer facing issue we have. Whenever we make a commit with GHfW we need to explicitly unstage everything first and then stage the selected files. We were doing that using a mixed reset but unfortunately a mixed reset will clear MERGE_HEAD and related metadata files needed to create a merge commit. We solved that by going one step lower and using read-tree to populate the index with the tree from HEAD. I can't remember the exact reason why simply calling Unstage didn't work. @niik do you recall? I don't care if we implement a Also, we want to be able to unstage files added to a newly initialized repository that doesn't have any commits yet. The Reset method requires a SHA I believe. We can't do the equivalent of |
Maybe this is obvious to GUI people, but... why? If it's in order to only commit a few files, it sounds like you're coming at it from the wrong side. While there is the index, you can still use other indices, even with git, to prepare commits. Have you considered using the libgit2sharp equivalent to this (
This is something that should definitely be fixed then. Though an issue with "unstage" and "reset" is that it's rather context-dependent and it can do different things depending on how and when you call it. |
That can be done very easily.
@haacked @niik Do you think you could produce a failing test? We'd be happy to fix that. |
Yep, that's why. The user can check the set of files to commit.
I have not! Got a code sample of doing that? |
I don't know if we have something turorialy, but the tests show some of the basic methods. You basically go var td = TreeDefinition.From(someCommitOrTree);
td.Add("some/path", someBlob, theMode);
td.Remove("file/i/dont/want"); |
Forgive my ignorance, but how do I create a commit with that. Here's the signature of the |
Maybe a better question to ask is what do I pass to |
@haacked Take a look at the tests in ObjectDatabaseFixture.cs. Some of them demonstrate how to create a Commit from a TreeDefinition. |
You use As for creating the commit. The pretty [0] https://github.com/libgit2/libgit2sharp/blob/vNext/LibGit2Sharp/ObjectDatabase.cs#L291 |
Thanks fellas. I played around with this and it's a bit of a pain for my scenario.
And for the most part, this is what I want. Using What I really want is an overload of public Commit Commit(string message, IEnumerable<string> paths); And have it only commit the files specified in the paths regardless of the current status of the index. I think one problem we ran into with unstaging all the paths is that it would wipe away the MERGE_HEAD. So we need to be able to unstage all the changes without wiping the MERGE_HEAD. |
That was when we did a reset, yes. I think the main problem with unstaging is that we'd have to iterate/pass in every file including ones not in the previous commit (I could be mistaken on this) whereas with read-tree we can just overwrite the index with the tree of a previous commit.
I think an even more interesting overload for us would be public Commit Commit(string message, TreeDefinition tree); That way we could load up the tree from the previous commit, add in the files that has been selected and write the commit without worrying about what's in the index and still have Commit do all the heavy lifting around determining whether or not it should be a merge commit and such. I don't know how feasible something like this would be or what sort of edge-cases there might be though.
A mixed reset didn't work for us because it blows away MERGE_HEAD. Unstaging explicitly isn't something we've explored due to the reasons I outlined above. |
|
The problem I ran into is all we have is a set of string paths. But the method to add to a tree definition looks like: public virtual TreeDefinition Add(string targetTreeEntryPath, Blob blob, Mode mode) Per @carlosmn's suggestion, I can use So to reiterate, I have a list of paths and I want libGit2Sharp to commit the files in those paths in the same way Git.exe would if I had individually added them to the index with no other changes in the index. I don't want to have to try and determine if the file is a binary, executable, non-executable, etc. |
A This method would have to work like git-commit's partial commits, by taking HEAD's tree and adding these changes. This is rather trivial in git or raw libgit2 where you can temporarily attach an index to a repository, but I'm not sure if that's possible in libgit2sharp, even internally. The one edge case here is what to do with new files (git won't let you commit them unless they're in the index, presumably to avoid typos and such). I wouldn't mind lifting that (maybe optionally) since we're dealing with programs which do error checking at different levels. The mentions of MERGE_BASE by @haacked and @niik make me worried you guys are allowing partial commits as a resolution to a merge, which you may not want to expose. git itself forbids it as it clashes with the conceptual model of the resolution, and it has a pretty high bar for letting you do stupid stuff. Or is the reset-command-like leaking into stuff? |
@haacked You may be after this overload which will dynamically create the As for the mode, in 99% of the cases, there are only one entry that make sense, on Windows, in the context of that method: |
BTW, I think we may be derailing this thread off of its initial topic. Could we move the |
Yeah, let's move the partial commit discussion into its own issue. It will likely depend on this one, but it's a different discussion. @nulltoken you can't just pass As for the actual topic of this PR and the name for read-tree, I don't like |
Thanks for the feedback! :) I've pushed another commit that exposes the two methods as In my backup tool, I treat the case that the index has conflicts in the same way I treat the case that a file I want to backup is not readable (busy), so I do not back up a tree with conflicts. Once the tree becomes fully merged, I begin backing it up again (similar to a file that becomes readable). It is definitely not perfect and I want to improve this in the future, but for now it is ok to backup the index in the 99% of time that it doesn't contain unresolved conflicts. Cheers, |
@alexandrudima Neat! One thought though. I wonder if creating a new overload for The current |
|
FWIW, there's even a full test suite dedicated to resetting the index (ie. https://github.com/libgit2/libgit2sharp/blob/vNext/LibGit2Sharp.Tests/ResetIndexFixture.cs). |
I guess |
Er, scratch that - |
Does this version of reset actually peform a reset of the repository state? That's something we most definitely want to avoid. |
@carlosmn The actual implementation updates the index entries that differs.
What are you referring to? |
Following the code is turning out to be confusing. I realise that git exposes this under the same name, but these are two completely different commands. One is about doing index operations from trees rather than files in the workdir, the other is about clearing the repository state and (possibly) moving elsewhere. |
@carlosmn Ok. Proposal would be to:
Deal? |
Counterproposal:
|
👍 on implementing this already as |
👍 as well 😉 @dahlbyk Could you please log a new issue for the future refactoring to be made? 🙏 |
@alexandrudima Given these. I think it's now only a matter of renaming |
|
❤️💣 |
@nulltoken I've pushed another commit that renames Thanks! |
@ethomson @dahlbyk @carlosmn I've done a quick test against
Thoughts? |
|
@carlosmn Thanks for the feedback.
So adding a |
For the read-tree case, yes. The index needs to be fully merged, since we're setting the index to look like the tree. |
@alexandrudima Could you please add this little assertion? Thinking about it a little more, I think we rather express this as |
Done! :) The tests pass on my machine, but the build failed, looks like it couldn't clone properly. Maybe you know how to trigger it again without another push from my side?
Thanks, |
I restarted the build. |
@alexandrudima Awesome! Last request: Could you please squash those commits into one and rebase them onto vNext? Spoiler: 😉 First, make sure to fetch the latest tips from libgit2/libgit2sharp. This should update your local Then, given that your local checked out branch is The following should do the trick
Then a force push should dynamically update the PR. |
@carlosmn Thank you! Cheers, |
It's in! Cool job, Sir! ✨ |
Hi,
In my project I need to be able to read/write the index from/to a tree (libgit2 git_index_write_tree and git_index_read_tree). I understand these are plumbing operations and probably not useful for everybody, but, hey, I need them, and perhaps if they're exposed in a nice way you're gonna be OK with them :)
I tried to stay away from libgit2 names, as they were confusing for me at first, so I thought to expose git_index_write_tree as Index.CreateTree and git_index_read_tree as Index.ReplaceWithTree. I am not feeling strong about the names, so please feel free to make better suggestions :).
I've added tests for the following:
Cheers,
Alex