I am writing an app that allows performing basic git operations using Objective-Git, but I am having trouble figuring out how to "unstage" a file. More specifically, I have a file with changes that have previously been added to the current GTIndex and I now want to discard those changes (without losing the file's current state on disk).
Here is a rough outline of the relevant logic my program currently follows when toggling the "staged" state of a file:
- Fetch current GTIndex using GTRepository.indexWithError:
- Fetch GTIndexEntry for the file using GTIndex.entryWithPath:
- If the file has an index and GTIndexEntry.status == GTIndexEntryStatusUpdated:
- Here's where I'm stuck
According to this old unanswered question I need to lookup the file's info in the current HEAD. In that case, this is the logic I arrived at:
- Fetch head's GTReference using GTRepository.headReferenceWithError:
- If the reference resolves to a GTCommit object:
- Fetch the GTTree using GTCommit.tree
- Fetch the file's GTTreeEntry using GTTree.entryWithPath:error:
- Stuck again! Do I convert GTTreeEntry to GTIndexEntry somehow?
Any guidance is appreciated! I'm not scared of jumping directly into libgit2 if I have to (this project has already necessitated it once or twice), but since I'm working in Swift I'd like to avoid it if I can and "unstaging" a file seems like such a standard procedure that I figured I must not be understanding how the Objective-Git classes tie together.
After letting this sit a little longer, I discovered that it's actually a lot easier than I was making it; rather than comparing the HEAD tree to the active index, you can simply use GTRepository.statusForFile:success:error: to determine whether the file is staged, unstaged, etc. and then add, remove, or add a blob for the file based on that.
(On the off chance it contains useful info for others down the road.)
This turned out to be simpler than it seemed at first, mainly because transitioning between GTTreeEntry and GTIndexEntry is unnecessary. Instead, I simply needed to grab the GTTreeEntry's GTObject (which is a blob for files), then pass the blob's data into the GTIndex.
I ended up with a code flow that goes roughly like this:
- Fetch head's GTReference using GTRepository.headReferenceWithError:
- If the reference resolves to a GTCommit =>
- Fetch the GTIndexEntry using GTIndex.entryWithPath:
- If we have an index entry =>
- Fetch the GTTree using GTCommit.tree
- Fetch the GTTreeEntry using GTTree.entryWithPath:error:
- If GTTreeEntry.SHA == GTIndexEntry.OID.SHA =>
- GTIndex.addFile:error:
(using this approach, it's possible to end up with identical
tree and index entries, which means the file is unstaged)
- Else if GTTreeEntry.type == GTObjectTypeBlob =>
- Fetch GTBlob using GTTreeEntry.GTObject:error:
- Pass GTBlob.data to GTIndex.addData:withPath:error:
- Else if no GTTreeEntry => GTIndex.removeFile:error:
- Else add the file/directory to the GTIndex (since we have no index entry)
- Write the changes to disk with GTIndex.write:
A couple of notes in hopes they will be helpful to future generations of people bashing their heads against Objective-Git's largely undocumented hide:
+diffIndexFromTree:inRepository:options:error: