Created
April 7, 2016 23:52
-
-
Save cameron314/c9d55a82cc91e45496ab0c38a31e69cb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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(¢ry, 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