If you must write this yourself, you can look at the basic strategy that the existing implementation in libgit2 uses. Let's just think about implementing a forced checkout (i.e. ignoring any modified files in the working directory) because that is a much simpler case.
You haven't mentioned how old your libgit2 is. I'm going to write the following assuming you have access to the diff functionality and I'll even use some of the somewhat more recent accessor functions for diff data. If those accessor functions aren't available in your version, you may have to rework this to use callback functions. If the core diff functionality isn't available, then your libgit2 is too old for this purpose, I believe.
You need to consider the old HEAD you are coming from and the new HEAD you are moving to in order to know about which files are going to be deleted (vs files that are simply untracked in the working directory). The easiest thing to do in libgit2 is something like:
git_diff_list *diff;
git_diff_delta *delta;
git_blob *blob;
size_t i;
FILE *fp;
git_diff_tree_to_tree(&diff, repo, from_tree, to_tree, NULL);
for (i = 0; i < git_diff_num_deltas(diff); ++i) {
git_diff_get_patch(NULL, &delta, diff, i);
switch (delta->status) {
case GIT_DELTA_ADDED:
case GIT_DELTA_MODIFIED:
/* file was added or modified between the two commits */
git_blob_lookup(&blob, repo, &delta->new_file.oid);
fp = fopen(delta->new_file.path, "w");
fwrite(git_blob_rawdata(blob), git_blob_rawsize(blob), 1, fp);
fclose(fp);
git_blob_free(blob);
break;
case GIT_DELTA_DELETED:
/* file was removed between the two commits */
unlink(delta->old_file.path);
break;
default:
/* no change required */
}
}
git_diff_list_free(diff);
/* now update the index with the tree we just wrote out */
git_index_read_tree(index, to_tree);
git_index_write(index);
/* and do the other stuff you have to update the HEAD */
There are lots of problems with the actual code above that you will have to resolve:
- The paths in
delta->new_file.path
anddelta->old_file.path
are relative to the working directory of the repository, not the current working directory of the process, so the calls to open and unlink the files will need to adjust the paths accordingly - The code doesn't deal with directories at all. Before opening a file, you will have to make the directories containing the file. After deleting a file, you will have to delete the directory containing the file if it was the last file in the directory. If you have branches where a directory turns into a regular file or vice versa, you will have to process deletes before adds.
- The code doesn't do any error checking which is a bad idea
- This code ignores pending changes in the index and modifications in the working directory. But we're talking about a forced checkout, so you get what you get.
- I just wrote the above code off the top of my head, so there are likely typos, etc.
Depending on your use case, maybe it will be okay to ignore type changes (i.e. directories that become blobs, etc) and maybe emulating --force
will be acceptable. If not, then this really starts to turn into a lot of code.