#include #include #include #include #include #include #include #include #include "dbc.h" #include #include #include #include #include #include using namespace std; using namespace fmt; #define BUF_MAX 1024 /* * No idea what the semantics of this are. Will need * to research git's dumb terminology to figure out why * they have 4 different versions of the path for status. */ const char *unfuck_path(const git_status_entry *entry) { if(entry->head_to_index != nullptr) { if(entry->head_to_index->new_file.path) { return entry->head_to_index->new_file.path; } else { return entry->head_to_index->old_file.path; } } if(entry->index_to_workdir != nullptr) { if(entry->index_to_workdir->new_file.path) { return entry->index_to_workdir->new_file.path; } else { return entry->index_to_workdir->old_file.path; } } return nullptr; } void add_status(const git_status_entry *entry, unsigned int status_flags, vector &updates) { const char *path = unfuck_path(entry); if(status_flags & GIT_STATUS_WT_NEW || status_flags & GIT_STATUS_INDEX_NEW) { updates.push_back(string{path}); } if(status_flags & GIT_STATUS_WT_MODIFIED || status_flags & GIT_STATUS_INDEX_MODIFIED) { updates.push_back(string{path}); } // need to confirm this gets the new name if(status_flags & GIT_STATUS_WT_RENAMED || status_flags & GIT_STATUS_INDEX_RENAMED) { updates.push_back(string{path}); } } class UpdateListener : public efsw::FileWatchListener { public: bool changes = false; void handleFileAction(efsw::WatchID watchid, const std::string& dir, const std::string& filename, efsw::Action action, std::string oldFilename) override { changes = true; switch(action) { case efsw::Actions::Add: print("ADD {} {} {}\n", dir, filename, oldFilename); break; case efsw::Actions::Delete: print("DEL {} {} {}\n", dir, filename, oldFilename); break; case efsw::Actions::Modified: print("MOD {} {} {}\n", dir, filename, oldFilename); break; case efsw::Actions::Moved: print("MOV {} {} {}\n", dir, filename, oldFilename); break; default: dbc::sentinel("Unknown efsw action."); } } void reset_state() { changes = false; } }; void list_git_changes(git_repository* repo) { git_status_options opts = GIT_STATUS_OPTIONS_INIT; git_status_list *statuses = nullptr; //TODO: does this leak? int err = git_status_list_new(&statuses, repo, &opts); dbc::check(err == 0, git_error_last()->message); size_t count = git_status_list_entrycount(statuses); vector updates; for(size_t i=0; i < count; i++) { const git_status_entry *entry = git_status_byindex(statuses, i); add_status(entry, entry->status, updates); } for(string path : updates) { print("PATH {}\n", path); } } void run_build(const char* command) { regex err_re("(.*?):([0-9]+):([0-9]+):\\s*(.*?):\\s*(.*)"); char buffer[BUF_MAX]; // BUF_MAX is a define already? smatch err; ofstream stats_out; stats_out.open("stats.csv", ios::out | ios::app); auto t = time(nullptr); auto tm = *std::gmtime(&t); dbc::check(stats_out.good(), "Error opening stats.csv file."); dbc::pre("simple test", [&]() { return stats_out.good(); }); FILE *build_out = popen(command, "r"); dbc::check(build_out != nullptr, "Failed to run command."); while(fgets(buffer, BUF_MAX, build_out) != nullptr) { string line(buffer); // yeah, that's probably a problem print("{}\n", line); if(regex_match(line, err, err_re)) { string file_name = err[1].str(); string line = err[2].str(); string col = err[3].str(); string type = err[4].str(); string message = err[5].str(); stats_out << put_time(&tm, "%FT%TZ"); stats_out << format(",{},{},{},{},{}\n", file_name, line, col, type, message); } } stats_out.close(); dbc::post("a post test", [&]() { return !stats_out.is_open(); }); } int main(int argc, char *argv[]) { git_repository* repo = nullptr; try { dbc::check(argc == 3, "USAGE: watchgit PATH BUILD_CMD"); const char *git_path = argv[1]; const char *build_cmd = argv[2]; print("Using build command: {}", build_cmd); efsw::FileWatcher* fileWatcher = new efsw::FileWatcher(); dbc::check(fileWatcher != nullptr, "Failed to create filewatcher."); git_libgit2_init(); int err = git_repository_open(&repo, git_path); dbc::check(err == 0, git_error_last()->message); UpdateListener* listener = new UpdateListener(); dbc::check(listener != nullptr, "Failed to create listener."); print("Watching directory {} for changes...\n", git_path); efsw::WatchID wid = fileWatcher->addWatch(git_path, listener, true); while(true) { fileWatcher->watch(); if(listener->changes) { sleep(1); list_git_changes(repo); listener->reset_state(); run_build(build_cmd); } sleep(1); } git_libgit2_shutdown(); } catch(dbc::Error &err) { print("ERROR: {}\n", err.message); if(repo != nullptr) git_repository_free(repo); return 1; } git_libgit2_shutdown(); }