Working prototype of an auto builder that watches for changes and runs the build after getting the git status. It's missing the necessary next step of matching up changes to git files, but it does the most of the build already.
parent
39f89ecbf2
commit
96b1297c62
@ -0,0 +1,10 @@ |
|||||||
|
[wrap-git] |
||||||
|
url = https://github.com/SpartanJ/efsw.git |
||||||
|
revision = 1.3.1 |
||||||
|
depth = 1 |
||||||
|
# patch_filename = |
||||||
|
# patch_hash = |
||||||
|
|
||||||
|
|
||||||
|
[provide] |
||||||
|
efsw = efsw_dep |
@ -0,0 +1,12 @@ |
|||||||
|
[wrap-file] |
||||||
|
directory = libgit2-1.8.0 |
||||||
|
source_url = https://github.com/libgit2/libgit2/archive/refs/tags/v1.8.0.tar.gz |
||||||
|
source_filename = v1.8.0.tar.gz |
||||||
|
source_hash = 9e1d6a880d59026b675456fbb1593c724c68d73c34c0d214d6eb848e9bbd8ae4 |
||||||
|
wrapdb_version = 2.4.1-3 |
||||||
|
# patch_filename = |
||||||
|
# patch_hash = |
||||||
|
|
||||||
|
|
||||||
|
[provide] |
||||||
|
libgit2 = libgit2_dep |
@ -0,0 +1,206 @@ |
|||||||
|
#include <iostream> |
||||||
|
#include <iomanip> |
||||||
|
#include <fstream> |
||||||
|
#include <fmt/core.h> |
||||||
|
#include <regex> |
||||||
|
#include <string> |
||||||
|
#include <iterator> |
||||||
|
#include <ctime> |
||||||
|
#include "dbc.h" |
||||||
|
#include <unistd.h> |
||||||
|
#include <stdio.h> |
||||||
|
#include <git2/sys/errors.h> |
||||||
|
#include <git2.h> |
||||||
|
#include <efsw/efsw.hpp> |
||||||
|
#include <regex> |
||||||
|
|
||||||
|
using namespace std; |
||||||
|
using namespace fmt; |
||||||
|
|
||||||
|
/*
|
||||||
|
* 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<string> &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<string> 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[LINE_MAX]; // LINE_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, LINE_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(); |
||||||
|
} |
Loading…
Reference in new issue