I am trying to build a hobby app to provide a UI for Git. I am trying to perform the equivalent of git restore --staged <file>. The below seems to successfully perform the correct action in the case where the file is newly added to the index, so I can just remove it form the index, but I am not sure how to handle the case where the file has previously been added to the index and has been modified. I have a limited understanding of both Git and C which makes it hard for me to work this out myself.
use git2;
fn main() {
let repo = git2::Repository::open("myrepo").unwrap();
let mut index = repo.index().unwrap();
let statuses = repo.statuses(None).unwrap();
let file_status_entry = statuses.get(0).unwrap();
let ct_path = std::path::Path::new(file_status_entry.path().unwrap());
let file_status = file_status_entry.status();
if file_status.is_index_new() {
index.remove(ct_path, 0).unwrap();
} else {
// not sure what to do here
index.remove(ct_path, 0).unwrap();
}
index.write().unwrap();
}
The
git restorecommand is ... complicated. Emulating it exactly is therefore also complicated, regardless of what language and library you use. However, if you limit yourself to just one instance ofgit restore's behavior (e.g.,git restore --staged), that simplifies things.I'm not familiar with the Rust library version here, so all I can tell you is what CGit does with
git restore --staged path. The goal of this operation is to copy some path into Git's index, from some source commit. When--stagedis used, the source commit defaults toHEAD(unless overridden with--source). The working tree is left untouched by this operation (unless you add--worktreeas well of course).The Rust equivalent will amount to:
At this point you're ready for the logic. Note: for
git restore, the argument is not a path name but rather a pathspec, which requires iterating over all matching path names in various cases (e.g.,git restore --staged "*"). This can affect the logic below (if you'regit restore-ing*/fooand there are six directories that could have afoobut only four of them do have afoo, the "no match" case should not error out for the other two directories). If you're handling only a single path name your job is simplified and is what is listed below.error: pathspec 'foo' did not match any file(s) know to git).Once you've done this for a single file (no pathspec magic, just one file listed) or all appropriate files (globbing or whatever), if you've updated the index, it is now time to put it back. This involves writing the new content index to the
index.lockfile obtained as the lock in the second initial step, flushing that to disk (be sure tofsyncit if the OS hasfsync), and renaming it to release the lock and put it in place as the index.Remember, too, to handle any
git worktree addcomplications (these result in a different default index file) and/orGIT_INDEX_FILEenvironment complications. These may (or may not) be covered by the library.