#include #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, set &updates) { const char *path = unfuck_path(entry); if(status_flags & GIT_STATUS_WT_NEW || status_flags & GIT_STATUS_INDEX_NEW) { updates.insert(string{path}); } if(status_flags & GIT_STATUS_WT_MODIFIED || status_flags & GIT_STATUS_INDEX_MODIFIED) { updates.insert(string{path}); } // need to confirm this gets the new name if(status_flags & GIT_STATUS_WT_RENAMED || status_flags & GIT_STATUS_INDEX_RENAMED) { updates.insert(string{path}); } } void list_git_changes(set &updates, 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); for(size_t i=0; i < count; i++) { const git_status_entry *entry = git_status_byindex(statuses, i); add_status(entry, entry->status, updates); } } class UpdateListener : public efsw::FileWatchListener { public: bool changes = false; git_repository* repo = nullptr; UpdateListener(git_repository *r) : repo(r) {}; void handleFileAction(efsw::WatchID watchid, const std::string& dir, const std::string& filename, efsw::Action action, std::string oldFilename) override { set updates; list_git_changes(updates, repo); bool in_git = updates.contains(filename); // this makes it so we only get it set one time when somethign is in git changes = changes || in_git; } void reset_state() { changes = false; } }; void run_build(const char* command) { regex err_re("(.*?):([0-9]+):([0-9]+):\\s*(.*?):\\s*(.*)\n*"); char buffer[BUF_MAX]; // BUF_MAX is a define already? ofstream stats_out; stats_out.open("stats.csv", ios::out | ios::app); std::time_t tstamp = std::time(nullptr); 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 smatch err; bool match = regex_match(line, err, err_re); if(match) { string file_name = err[1].str(); string lnumber = err[2].str(); string col = err[3].str(); string type = err[4].str(); string message = err[5].str(); string result = format("{:%FT%T},{},{},{},{},{}\n", fmt::localtime(tstamp), file_name, lnumber, col, type, message); stats_out << result; cout << "MATCHED: " << quoted(result); } } 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]; println("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(repo); 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) { print("WAITING...\r"); fileWatcher->watch(); if(listener->changes) { sleep(1); println("CHANGES! Running build {}", build_cmd); run_build(build_cmd); listener->reset_state(); } 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(); }