Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
// Compile with g++ git-last-commits.cpp -std=c++11 -O3 -g -DNDEBUG -o git-last-commits -Ilibgit2-0.23.1/include -Llibgit2-0.23.1/build -lgit2 -lz -pthread
#include <git2.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <cerrno>
#include <map>
#include <set>
#include <string>
struct fast_str_compare
{
bool operator()(std::string const& a, std::string const& b) const
{
if (a.size() != b.size())
return a.size() < b.size();
return a < b;
}
};
static void check(int errCode, const char* action)
{
git_error const* err = giterr_last();
if (!errCode)
return;
std::fprintf(stderr, "Error %s: %d: %s\n", action, errCode, err && err->message ? err->message : "<unknown error>");
std::exit(errCode);
}
int main(int argc, char** argv)
{
git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
git_libgit2_init();
char cwd[FILENAME_MAX];
if (!getcwd(cwd, sizeof(cwd)))
return errno;
git_buf repoBuf;
check(git_repository_discover(&repoBuf, cwd, 0, nullptr), "discovering repository");
git_repository* repo;
check(git_repository_open(&repo, repoBuf.ptr), "opening repository");
int firstPath = 2;
for (; firstPath < argc && argv[firstPath][0] != '-'; ++firstPath);
if (++firstPath >= argc) {
std::fprintf(stderr, "No paths specified. Usage: git-last-commits ROOT_OID -- paths...\n");
return 1;
}
git_object* rootObject; // Either commit or branch
check(git_revparse_single(&rootObject, repo, argv[1]), "determining root object");
std::set<std::string, fast_str_compare> paths;
for (int i = firstPath; i != argc; ++i) {
paths.emplace(argv[i]);
}
// Traverse the entire log, looking for the latest commits that match the given paths
git_revwalk* walker;
check(git_revwalk_new(&walker, repo), "creating revision walker");
git_revwalk_sorting(walker, GIT_SORT_TIME);
check(git_revwalk_push(walker, git_object_id(rootObject)), "could not add root");
std::map<std::string, git_oid, fast_str_compare> results;
git_commit* commit = nullptr;
for (git_oid oid; !git_revwalk_next(&oid, walker); git_commit_free(commit)) {
check(git_commit_lookup(&commit, repo, &oid), "looking up commit");
// Check if this commit matches any of the given files.
unsigned parentCount = git_commit_parentcount(commit);
if (parentCount > 1) {
continue; // ignore merges -- this should probably be made into an option...
}
// See if file was changed or added in this commit
git_commit* parent = nullptr;
git_tree *a = nullptr, *b;
if (parentCount > 0) {
check(git_commit_parent(&parent, commit, /* parent index */ 0), "getting parent commit");
check(git_commit_tree(&a, parent), "getting tree for parent");
}
check(git_commit_tree(&b, commit), "getting tree for commit");
for (auto it = paths.begin(); it != paths.end(); ) {
git_tree_entry *pentry, *centry;
if (a == nullptr || git_tree_entry_bypath(&pentry, a, it->c_str()) != 0) {
pentry = nullptr;
}
if (git_tree_entry_bypath(&centry, b, it->c_str()) != 0) {
centry = nullptr;
}
if (pentry == nullptr) {
// must be new file in this commit, or non-existent
if (centry != nullptr) {
results.emplace(*it, oid);
}
it = paths.erase(it);
}
else {
// was the file changed in this commit?
if (!git_oid_equal(git_tree_entry_id(pentry), git_tree_entry_id(centry))) {
results.emplace(*it, oid);
it = paths.erase(it);
}
else {
++it;
}
}
git_tree_entry_free(pentry);
git_tree_entry_free(centry);
}
git_tree_free(a);
git_tree_free(b);
git_commit_free(parent);
if (paths.size() == 0 || parentCount == 0) {
break;
}
}
for (int i = firstPath; i != argc; ++i) {
auto it = results.find(argv[i]);
if (it == results.end()) {
std::printf("0\n");
}
else {
char buf[GIT_OID_HEXSZ + 1];
git_oid_tostr(buf, sizeof(buf), &it->second);
std::printf("%s\n", buf);
}
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment