/** The monitor suffers from various issues - basically event flows while it's running... normall operations - monitors for file changes -- adds to QUEUE when occurs. - queue runs in background. - if it's got stuff in it.. - COMMIT (normally) - now?? - if on master - try and branch == do user selection, until we have branched == then start monitoring again.. */ public class GitMonitor : Monitor { public static GitMonitor gitmonitor; /** * @property {String} the "gitlive" directory, normally ~/gitlive * dset by OWNER... - we should do this as a CTOR. * */ public static string gitlive; private Gee.ArrayList queue ; public bool queueRunning = false; public DateTime lastAdd; public GitMonitor () { this.queue = new Gee.ArrayList(); GitMonitor.gitmonitor = this; Timeout.add_full(Priority.LOW, 500, () => { //GLib.debug("TIMEOUT queue length = %d, is_runing = %s\n", (int)this.queue.length , this.queueRunning ? "Y" : "N"); //stdout.printf("QL %u: QR: %d\n", this.queue.length, this.queueRunning ? 1 : 0); if (this.queue.size < 1 || this.queueRunning) { return true; } var last = -1 * this.lastAdd.difference(new DateTime.now(new TimeZone.local())); // stdout.printf("LAST RUN: %s (expect %s) \n" , // last.to_string(), (5 * TimeSpan.SECOND).to_string() ); if (last < 5 * TimeSpan.SECOND) { // wait 5 seconds before running. ???? return true; } //_this.lastAdd = new Date(); //return 1; this.runQueue(); return true; // }); } public new void pauseError(string failure) { var notification = new Notify.Notification( "Git Live ERROR!!", failure, "dialog-information" ); notification.set_timeout(60); // show errros for longer notification.show(); Canberra.Context context; Canberra.Proplist props; Canberra.Context.create (out context); Canberra.Proplist.create (out props); props.sets (Canberra.PROP_EVENT_ID, "phone-outgoing-busy"); props.sets (Canberra.PROP_EVENT_DESCRIPTION, "Gitlive stopped on error"); context.play_full (0, props, null); this.paused = true; this.queueRunning = false; // what does this do to the old one... //this.queue = new Gee.ArrayList (); this.stop(); StatusIconA.statusicon.pauseError(); var m = new Gtk.MessageDialog(null, Gtk.DialogFlags.MODAL,Gtk.MessageType.ERROR,Gtk.ButtonsType.CLOSE, "A Serious problem occured running git, you will probably have to resolve this manually\n" + "Committing is paused now, so fix the problem, close this window, then press start again\n\n\n" + failure ); m.set_keep_above(true); m.show(); m.set_position(Gtk.WindowPosition.CENTER); m.response.connect( (id_pressed) => { m.hide(); }); } public new void pause() { this.paused = true; // what does this do to the old one... //this.queue = new Gee.ArrayList (); StatusIconA.statusicon.pause(); } public void restoreQueue( Gee.ArrayList queue) { //used to restore teh queue after switch branches?/ - breaks our privte queue idea.. this.queue = queue; } /* public new void resume () { this.paused = false; this.queue = new Array (); StatusIconA.statusicon.resume(); } */ /** * Start the monitoring * and run the queue every 500 milliseconds.. * */ public new void start() { StatusIconA.statusicon.refreshing(); this.lastAdd = new DateTime.now(new TimeZone.local()); Timeout.add_full(Priority.LOW, 500, () => { //stdout.printf("GitMonitor.start :: top.length = %u\n", this.top.length); // call this.monitor on each of 'top' for(int i = 0; i < this.top.length ; i++) { this.monitor(this.top.index(i) ); } StatusIconA.statusicon.resume(); this.paused = false; try { var notification = new Notify.Notification( "Git Live", "%s\nMonitoring %u Directories".printf(GitMonitor.gitlive, this.monitors.length), "dialog-information" ); notification.set_timeout(5); notification.show(); } catch(Error e) { GLib.debug("Error sending notification to screen: %s",e.message); } return false; // do not keep doing this.. }); } public new void stop() { StatusIconA.statusicon.pause(); base.stop(); } public override void monitor (string path, int depth = 0) { //GLib.debug("GitMonitor : monitor %d %s", depth, path); //var depth = typeof(depth) == 'number' ? depth *1 : 0; // if we are not at top level.. and there is a .git directory (it's a submodule .. ignore) if (depth > 1 && FileUtils.test(path + "/.git" , FileTest.IS_DIR)) { return; } if (depth == 1) { if (!FileUtils.test(path + "/.git" , FileTest.IS_DIR)) { return; // skip non-git directories.. } GitRepo.get(path); // FIXME - check if repo is flagged as not autocommit.. //var repo = imports.Scm.Repo.Repo.get(path); //if (!repo || !repo.autocommit()) { // return; //} } // check if the repo is to be monitored. //print("PATH : " + path); base.monitor(path, depth); } /** * run the queue. * - pulls the items off the queue * (as commands run concurrently and new items may get added while it's running) * - runs the queue items * - pushes upstream. * */ public void runQueue() { if (this.paused || this.queue.size < 1 ) { return; } foreach(var q in this.queue) { if (!q.shouldIgnore() && !q.repo.is_wip_branch() && q.repo.is_auto_branch()) { var oldq = this.queue; this.queue = new Gee.ArrayList(); NewBranch.singleton().show(q.repo, oldq); return; } } GLib.debug("GitMonitor.runQueue size =%d\n", this.queue.size); this.queueRunning = true; var cmds = new Gee.ArrayList(); for(var i = 0; i < this.queue.size; i++) { cmds.add(this.queue.get(i)); } this.queue = new Gee.ArrayList();// empty queue! string[] success = {}; string[] failure = {}; //var repos = new Array(); //?? //var done = new Array(); // first build a array of repo's to work with var repo_list = new Array(); // pull and group. //print(JSON.stringify(cmds)); // make sure nothing get's added to the queue where we are doing this.. this.paused = true; var leave_queued = new Gee.ArrayList(); GLib.debug("GitMonitor.runQueue - creating repos"); for(var i = 0; i < cmds.size; i++) { var cmd = cmds.get(i); var gitpath = cmd.gitpath; var repo = GitRepo.get( gitpath ); if ( !repo.is_wip_branch() && repo.is_auto_branch()) { leave_queued.add(cmd); continue; } GLib.debug("GitMonitor.runQueue - finding %s", cmd.gitpath); var ix = GitRepo.indexOf(repo_list, gitpath); if (ix < 0) { repo_list.append_val( GitRepo.get( gitpath )); ix = GitRepo.indexOf(repo_list, cmd.gitpath); } GLib.debug("GitMonitor.runQueue - adding to repolist %d", ix); //if (typeof(repo_list[gitpath]) == 'undefined') { // repo_list[gitpath] = new imports.Scm.Git.Repo.Repo( { repopath : gitpath }); // repo_list[gitpath].cmds = []; // repo_list[gitpath].pull(); //} repo_list.index(ix).cmds.add(cmd); } this.queue = leave_queued; this.paused = false; // build add, remove and commit message list.. GLib.debug("GitMonitor.runQueue - creating actions"); for(var i = 0;i < repo_list.length;i++) { var repo = repo_list.index(i); var add_files = new Gee.ArrayList(); var add_files_f = new Gee.ArrayList(); var remove_files = new Gee.ArrayList(); var messages = new Gee.ArrayList(); //print(JSON.stringify(repo.cmds,null,4)); for(var ii = 0;ii < repo.cmds.size;ii++) { var cmd = repo.cmds.get(ii); if (repo.is_ignore(cmd.vname)) { continue; } switch(cmd.action) { case "add" : if (GitMonitorQueue.indexOfAdd(add_files, cmd.vname) > -1) { break; } add_files.add(cmd); break; case "rm": if (GitMonitorQueue.indexOfAdd(remove_files, cmd.vname) > -1 ) { break; } // if file exists, do not try and delete it. if (FileUtils.test(cmd.fullpath(), FileTest.EXISTS)) { break; } remove_files.add(cmd); break; case "commit" : if (GitMonitorQueue.indexOfMessage(messages, cmd.message) > -1 ) { break; } messages.add(cmd); break; default: stdout.printf("Opps unmatched action %s\n", cmd.action); break; } } repo.cmds.clear(); // reset the repo's command list.. GLib.debug( "ADD : %s", GitMonitorQueue.queueArrayToString(add_files)); GLib.debug( "REMOVE FILES: %s", GitMonitorQueue.queueArrayToString(remove_files)); //repo.debug = 1; // these can fail... at present... as we wildcard stuff. // make sure added files do not get removed.. ?? /* var remove_files_f = new Array(); for(var ii = 0;ii < remove_files.length;ii++) { if (GitMonitorQueue.indexOfAdd(add_files, remove_files.index(ii).vname) > -1 ) { continue; } remove_files_f.append_val(remove_files.index(ii)); }; stdout.printf("REMOVE : %u files\n" , remove_files_f.length); */ // if file was added, then removed, var remove_files_f = new Gee.ArrayList(); for(var ii = 0;ii < remove_files.size;ii++) { if (GitMonitorQueue.indexOfAdd(add_files, remove_files.get(ii).vname) > -1 ) { // in add and remove - do not remvove continue; } remove_files_f.add(remove_files.get(ii)); }; for(var ii = 0;ii < add_files.size;ii++) { if (GitMonitorQueue.indexOfAdd(remove_files, add_files.get(ii).vname) > -1 ) { // the add file is in the remove list, and it does not exist - do not add it.. print("check exists ? %s\n",add_files.get(ii).fullpath()); if (!FileUtils.test(add_files.get(ii).fullpath(), FileTest.EXISTS)) { continue; } } add_files_f.add(add_files.get(ii)); }; GLib.debug( "ADD : %s", GitMonitorQueue.queueArrayToString(add_files_f)); GLib.debug( "REMOVE FILES: %s", GitMonitorQueue.queueArrayToString(remove_files_f)); if (add_files_f.size < 1 && remove_files_f.size < 1) { continue; } // make sure monitoring is paused so it does not recursively pick up // deletions this.paused = true; try { repo.pull(); } catch(Error e) { this.pauseError(e.message); //failure += e.message; //print("Pull failed:\n"); return; } // -- DO STUFF.. try { repo.add(add_files_f); } catch(Error e) { this.pauseError(e.message); return; failure += e.message; GLib.debug("Add failed:"); } try { repo.remove(remove_files_f); } catch(Error e) { this.pauseError(e.message); return; failure += e.message; GLib.debug("Remove failed:"); } try { success += repo.commit( GitMonitorQueue.messageToString(messages), add_files_f ); success += repo.push(); } catch(Error e) { // if the error is 'nothing to commit, working tree clean' // then it's not an error, - just continue; if (/nothing to commit, working tree clean/.match(e.message)) { GLib.debug("%s",e.message); success += e.message; this.paused = false; continue; } this.paused = false; this.pauseError(e.message); return; //failure += e.message; //print("Push failed:\n"); } this.paused = false; } // finally merge all the commit messages. try { // catch notification failures.. so we can carry on.. if (success.length > 0) { var notification = new Notify.Notification( "Git Live Commited", string.joinv("\n",success), "dialog-information" ); notification.set_timeout(5); notification.show(); } //if (failure.length > 0) { // should never get this far... // this.pauseError(); //} } catch(Error e) { GLib.debug(e.message); } this.queueRunning = false; } //string[] just_created; public override void onChanged(MonitorNamePathDir src) { //print("GitMonitor.onChanged\n"); return; // always ignore this..? //this.parsePath(src); } /** * results in git add + git commit.. * */ public override void onChangesDoneHint(MonitorNamePathDir src) { if (this.paused) { return; } GLib.debug("GitMonitor.onChangedHint"); this.lastAdd = new DateTime.now(new TimeZone.local()); var cmd = new GitMonitorQueue(src); if (cmd.shouldIgnore()) { GLib.debug("GitMonitor.onChangedHint - ignored"); return; } //var add_it = false; /* if (this.is_just_created(cmd.path)) { if (typeof(this.just_created[src.path]) !='undefined') { delete this.just_created[src.path]; this.queue.push( [ src.gitpath, 'add', src.vpath ], [ src.gitpath, 'commit', { message: src.vpath} ] ); return; } */ cmd.action = "add"; this.queue.add(cmd); cmd = new GitMonitorQueue(src); cmd.action = "commit"; cmd.message = cmd.vname; this.queue.add(cmd); } public override void onDeleted(MonitorNamePathDir src) { if (this.paused) { return; } GLib.debug("GitMonitor.onDeleted"); this.lastAdd = new DateTime.now(new TimeZone.local()); var cmd = new GitMonitorQueue(src); if (cmd.shouldIgnore()) { return; } // should check if monitor needs removing.. // it should also check if it was a directory.. - so we dont have to commit all.. cmd.action = "rm"; this.queue.add(cmd); cmd = new GitMonitorQueue(src); cmd.action = "commit"; cmd.message = cmd.vname; cmd.commit_all = true; this.queue.add(cmd); } public override void onCreated(MonitorNamePathDir src) { if (this.paused) { return; } GLib.debug("GitMonitor.onCreated"); this.lastAdd = new DateTime.now(new TimeZone.local()); var cmd = new GitMonitorQueue(src); if (cmd.shouldIgnore()) { return; } if (!FileUtils.test(src.path, GLib.FileTest.IS_DIR)) { // this.just_created[src.path] = true; return; // we do not handle file create flags... - use done hint. } // directory has bee created this.monitor(src.path); //this.top.append_val(src.path); //this.monitor(src.path ); // -- no point in adding a dir.. as git does not handle them... // this.queue.push( // [ src.gitpath, 'add' , src.vpath, { all: true } ], // [ src.gitpath, 'commit' , { all: true, message: src.vpath} ] // // ); } public override void onAttributeChanged(MonitorNamePathDir src) { if (this.paused) { return; } GLib.debug("GitMonitor.onAttributeChanged %s", src.name); if (src.dir == GitMonitor.gitlive) { return; // attribute on top level.. } this.lastAdd = new DateTime.now(new TimeZone.local()); var cmd = new GitMonitorQueue(src); if (cmd.shouldIgnore()) { return; } cmd.action = "add"; this.queue.add(cmd); cmd = new GitMonitorQueue(src); cmd.action = "commit"; cmd.message = "Attribute changed " + cmd.vname; this.queue.add(cmd); } public override void onMoved(MonitorNamePathDir src,MonitorNamePathDir dest) { if (this.paused) { return; } GLib.debug("GitMonitor.onMoved"); this.lastAdd = new DateTime.now(new TimeZone.local()); var cmd_s = new GitMonitorQueue(src); var cmd_d = new GitMonitorQueue(dest); if (cmd_d.gitpath != cmd_s.gitpath) { this.onDeleted(src); this.onCreated(dest); this.onChangesDoneHint(dest); return; } // needs to handle move to/from unsupported types.. if (cmd_s.shouldIgnore()) { this.onCreated(dest); this.onChangesDoneHint(dest); return; } if (cmd_d.shouldIgnore()) { this.onDeleted(src); return; } GLib.debug("RM: %s", cmd_s.vname); cmd_s.action = "rm"; this.queue.add(cmd_s); GLib.debug("ADD: %s", cmd_d.vname); cmd_d.action = "add"; this.queue.add(cmd_d); var cmd = new GitMonitorQueue(dest); cmd.action = "commit"; cmd.message = "MOVED " + cmd_s.vname + " to " + cmd_d.vname; if (GitMonitorQueue.queueHas(this.queue, cmd_s, "add")) { cmd.message = cmd_d.vname; } this.queue.add(cmd); } }